Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 3 additions & 49 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,50 +1,4 @@
node_modules/
dist/
!extension/dist/
*.tsbuildinfo
hosted-contract.json
plugin-command-manifest.json
.webcmd/
.superpowers/
.worktrees/
.agents/*
!.agents/plugins/
.agents/plugins/*
!.agents/plugins/marketplace.json
.mcp.json
*.log
.DS_Store
backend/node_modules/
backend/uploads/

# Local-only research, examples, and agent planning artifacts
autoresearch/
cases/
designs/
docs/superpowers/
instagram-test/
llms.txt
sitemaps/

# Extensions & Secrets
*.pem
*.crx
*.zip
.envrc
.windsurf
.claude
.cortex

# Database files
*.db
autoresearch-results.tsv

# webcmd benchmarks (dataset comparison harness)
benchmarks/results/
benchmarks/.venv/
benchmarks/node_modules/
benchmarks/**/__pycache__/
benchmarks/.pytest_cache/
benchmarks/pytest-cache-files-*
benchmarks/datasets/*.json
!benchmarks/datasets/Stealth_Webcmd.json
benchmarks/.playwright-cli/
benchmarks/.playwright/
backend/.env
300 changes: 167 additions & 133 deletions README.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MongoDB connection string (local or Atlas)
MONGO_URI=mongodb://127.0.0.1:27017/application_rescue_agent

# Server port
PORT=5000

# Webcmd mode: DEMO (scripted, always reliable) or REAL (uses installed Webcmd CLI)
WEBCMD_MODE=DEMO

# Only used if WEBCMD_MODE=REAL - path to the Webcmd CLI binary
WEBCMD_CLI_PATH=webcmd
18 changes: 18 additions & 0 deletions backend/config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const mongoose = require("mongoose");

async function connectDB() {
const uri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/application_rescue_agent";

try {
await mongoose.connect(uri);
console.log(`[db] Connected to MongoDB -> ${uri}`);
} catch (err) {
console.error("[db] MongoDB connection failed:", err.message);
console.error(
"[db] Make sure MongoDB is running locally, or set MONGO_URI to an Atlas connection string in backend/.env"
);
process.exit(1);
}
}

module.exports = connectDB;
128 changes: 128 additions & 0 deletions backend/controllers/applicationController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const Application = require("../models/Application");
const Opportunity = require("../models/Opportunity");
const Profile = require("../models/Profile");
const Document = require("../models/Document");
const { getDemoUser } = require("./profileController");
const { ensureVault } = require("./documentController");
const { computeMatchPercent } = require("../services/matchEngine");
const webcmdService = require("../services/webcmdService");

exports.getApplications = async (req, res) => {
try {
const user = await getDemoUser();
const apps = await Application.find({ user: user._id })
.populate("opportunity")
.sort({ updatedAt: -1 });
res.json(apps);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

exports.getApplicationById = async (req, res) => {
try {
const app = await Application.findById(req.params.id).populate("opportunity");
if (!app) return res.status(404).json({ message: "Application not found" });
res.json(app);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

// Start (or resume) a Rescue application for a given opportunity.
exports.startApplication = async (req, res) => {
try {
const { opportunityId } = req.body;
const user = await getDemoUser();

const opportunity = await Opportunity.findById(opportunityId);
if (!opportunity) return res.status(404).json({ message: "Opportunity not found" });

let app = await Application.findOne({ user: user._id, opportunity: opportunityId });

if (!app) {
const profile = await Profile.findOne({ user: user._id });
const matchPercent = profile ? computeMatchPercent(profile, opportunity) : 0;

app = await Application.create({
user: user._id,
opportunity: opportunityId,
status: "Not Started",
matchPercent,
formFields: [],
missingFields: [],
completionPercent: 0,
webcmdLog: [],
});
}

const populated = await app.populate("opportunity");
res.status(201).json(populated);
} catch (err) {
res.status(400).json({ message: err.message });
}
};

// The core "Rescue" step: Webcmd opens the form, auto-fills what it can
// from the profile + document vault, and flags what's missing.
exports.analyzeApplication = async (req, res) => {
try {
const app = await Application.findById(req.params.id).populate("opportunity");
if (!app) return res.status(404).json({ message: "Application not found" });

const user = await getDemoUser();
const profile = await Profile.findOne({ user: user._id });
const documents = await ensureVault(user._id);

app.status = "Analyzing";
await app.save();

const result = await webcmdService.analyzeApplication({
profile,
documents,
opportunity: app.opportunity,
});

app.formFields = result.formFields;
app.missingFields = result.missingFields;
app.completionPercent = result.completionPercent;
app.status = result.status; // "Missing Items" or "Ready for Review"
app.webcmdLog.push(...result.log);

await app.save();

const populated = await app.populate("opportunity");
res.json(populated);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

// Explicit, user-triggered submission. Never happens automatically.
exports.submitApplication = async (req, res) => {
try {
const app = await Application.findById(req.params.id).populate("opportunity");
if (!app) return res.status(404).json({ message: "Application not found" });

if (app.status !== "Ready for Review") {
return res.status(400).json({
message:
"This application still has missing items. Resolve them and re-run analysis before submitting.",
});
}

app.status = "Submitted";
app.submittedAt = new Date();
app.webcmdLog.push({
step: "submit",
message: "User approved and submitted the application.",
timestamp: new Date(),
});

await app.save();
const populated = await app.populate("opportunity");
res.json(populated);
} catch (err) {
res.status(500).json({ message: err.message });
}
};
130 changes: 130 additions & 0 deletions backend/controllers/documentController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const Document = require("../models/Document");
const { getDemoUser } = require("./profileController");

const DOC_TYPES = ["Resume", "Transcript", "Certificates", "SOP"];

async function ensureVault(userId) {
const existing = await Document.find({ user: userId });
const existingTypes = new Set(existing.map((d) => d.type));

const toCreate = DOC_TYPES
.filter((type) => !existingTypes.has(type))
.map((type) => ({
user: userId,
type,
status: "missing",
fileName: "",
filePath: "",
}));

if (toCreate.length) {
await Document.insertMany(toCreate);
}

return Document.find({ user: userId }).sort({ type: 1 });
}

exports.getDocuments = async (req, res) => {
try {
const user = await getDemoUser();
const docs = await ensureVault(user._id);

res.json(docs);
} catch (err) {
console.error("[documents] get error:", err);
res.status(500).json({
message: err.message,
});
}
};
exports.removeDocument = async (req, res) => {
try {
const { type } = req.params;

const user = await getDemoUser();

const doc = await Document.findOneAndUpdate(
{
user: user._id,
type,
},
{
$set: {
status: "missing",
fileName: "",
filePath: "",
uploadedAt: null,
},
},
{ new: true }
);

if (!doc) {
return res.status(404).json({
message: "Document not found.",
});
}

res.json(doc);
} catch (err) {
console.error("[documents] remove error:", err);

res.status(500).json({
message: err.message,
});
}
};
exports.uploadDocument = async (req, res) => {
try {
const { type } = req.body;

if (!DOC_TYPES.includes(type)) {
return res.status(400).json({
message: `type must be one of ${DOC_TYPES.join(", ")}`,
});
}

if (!req.file) {
return res.status(400).json({
message: "Please select a file to upload.",
});
}

const user = await getDemoUser();

const filePath = `/uploads/documents/${req.file.filename}`;

const doc = await Document.findOneAndUpdate(
{
user: user._id,
type,
},
{
$set: {
status: "uploaded",
fileName: req.file.originalname,
filePath,
uploadedAt: new Date(),
},
},
{
new: true,
upsert: true,
}
);

console.log(
`[documents] ${type} uploaded: ${req.file.originalname}`
);

res.json(doc);
} catch (err) {
console.error("[documents] upload error:", err);

res.status(400).json({
message: err.message,
});
}
};

exports.ensureVault = ensureVault;
Loading