careervivid 2.1.22 → 2.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"QueryEngine.d.ts","sourceRoot":"","sources":["../../src/agent/QueryEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,uBAAuB,EAAE,OAAO,EAAQ,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,IAAI,EAAsB,MAAM,WAAW,CAAC;AASrD,qFAAqF;AACrF,eAAO,MAAM,0BAA0B,QAAsC,CAAC;AAC9E,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,QAAoC,CAAC;AASpE,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,0FAA0F;IAC1F,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,kDAAkD;IAClD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAyCD;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,GAAE,kBAAuB;IAyBrC,UAAU,IAAI,OAAO,EAAE;IAIvB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE;IAIpC,8CAA8C;IACvC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE;IAW7B,OAAO,CAAC,mBAAmB;YAcb,YAAY;YAOZ,gBAAgB;IA6DjB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAqG5E;;;;OAIG;IACU,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;CAoHtF"}
1
+ {"version":3,"file":"QueryEngine.d.ts","sourceRoot":"","sources":["../../src/agent/QueryEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,uBAAuB,EAAE,OAAO,EAAQ,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,IAAI,EAAsB,MAAM,WAAW,CAAC;AASrD,qFAAqF;AACrF,eAAO,MAAM,0BAA0B,QAAsC,CAAC;AAC9E,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,QAAoC,CAAC;AASpE,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,0FAA0F;IAC1F,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,kDAAkD;IAClD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAwED;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,GAAE,kBAAuB;IAyBrC,UAAU,IAAI,OAAO,EAAE;IAIvB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE;IAIpC,8CAA8C;IACvC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE;IAW7B,OAAO,CAAC,mBAAmB;YAcb,YAAY;YAOZ,gBAAgB;IA6DjB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAqG5E;;;;OAIG;IACU,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;CAoHtF"}
@@ -18,14 +18,39 @@ function isRetryableError(e) {
18
18
  // Google GenAI errors often have a status field
19
19
  if (e?.status && RETRYABLE_STATUS_CODES.has(e.status))
20
20
  return true;
21
- if (e?.message && /rate.?limit|quota|too.?many.?requests|service.?unavailable/i.test(e.message))
21
+ if (e?.message && /rate.?limit|quota|too.?many.?requests|service.?unavailable|429/i.test(e.message))
22
22
  return true;
23
23
  return false;
24
24
  }
25
+ /** Parse the retryDelay (in seconds) from a Gemini 429 error message or details array. */
26
+ function extractRetryDelayMs(e) {
27
+ // Try structured errorDetails array first (e.g. RetryInfo proto)
28
+ if (Array.isArray(e?.errorDetails)) {
29
+ for (const detail of e.errorDetails) {
30
+ const delay = detail?.retryDelay;
31
+ if (delay) {
32
+ // "16s" or "16.44s" → milliseconds
33
+ const secs = parseFloat(String(delay).replace('s', ''));
34
+ if (!isNaN(secs))
35
+ return Math.ceil(secs * 1000);
36
+ }
37
+ }
38
+ }
39
+ // Fallback: parse from error message string ("Please retry in 16.44s")
40
+ if (e?.message) {
41
+ const match = e.message.match(/retry\s*in\s*([\d.]+)\s*s/i);
42
+ if (match) {
43
+ const secs = parseFloat(match[1]);
44
+ if (!isNaN(secs))
45
+ return Math.ceil(secs * 1000);
46
+ }
47
+ }
48
+ return null;
49
+ }
25
50
  async function sleep(ms) {
26
51
  return new Promise(resolve => setTimeout(resolve, ms));
27
52
  }
28
- async function retryWithBackoff(fn, maxRetries = 3, baseDelayMs = 1000) {
53
+ async function retryWithBackoff(fn, maxRetries = 5, baseDelayMs = 5000) {
29
54
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
30
55
  try {
31
56
  return await fn();
@@ -33,8 +58,15 @@ async function retryWithBackoff(fn, maxRetries = 3, baseDelayMs = 1000) {
33
58
  catch (e) {
34
59
  if (attempt === maxRetries || !isRetryableError(e))
35
60
  throw e;
36
- const delay = baseDelayMs * Math.pow(2, attempt);
37
- await sleep(delay);
61
+ // Honour the server-requested retry delay if present, use exponential backoff as floor
62
+ const serverDelayMs = extractRetryDelayMs(e);
63
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
64
+ const delayMs = Math.max(serverDelayMs ?? 0, exponentialDelay);
65
+ // Add ±10% jitter to avoid thundering-herd on shared quota
66
+ const jitter = delayMs * 0.1 * (Math.random() * 2 - 1);
67
+ const finalDelay = Math.ceil(delayMs + jitter);
68
+ console.error(`\n⏳ Rate limited (attempt ${attempt + 1}/${maxRetries}). Retrying in ${(finalDelay / 1000).toFixed(1)}s…`);
69
+ await sleep(finalDelay);
38
70
  }
39
71
  }
40
72
  // TypeScript requires this even though it's unreachable
@@ -1 +1 @@
1
- {"version":3,"file":"agentAuditLog.d.ts","sourceRoot":"","sources":["../../src/agent/agentAuditLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;CACb;AAID,eAAO,IAAI,UAAU,QAA2B,CAAC;AAoCjD,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGpE;AA8BD,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CA8BP;AAyJD,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMnD;AAID,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDhB"}
1
+ {"version":3,"file":"agentAuditLog.d.ts","sourceRoot":"","sources":["../../src/agent/agentAuditLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;CACb;AAID,eAAO,IAAI,UAAU,QAA2B,CAAC;AA0CjD,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGpE;AA8BD,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CA8BP;AA8KD,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMnD;AAID,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEhB"}
@@ -20,7 +20,7 @@ import { appendFileSync, existsSync, mkdirSync } from "fs";
20
20
  import { resolve } from "path";
21
21
  import { homedir } from "os";
22
22
  import { randomUUID } from "crypto";
23
- import { saveSessionMemory } from "./memory.js";
23
+ import { saveSessionMemory, saveCodingSession } from "./memory.js";
24
24
  // ── Session-level state ───────────────────────────────────────────────────────
25
25
  export let SESSION_ID = randomUUID().slice(0, 8);
26
26
  const pendingFirestore = [];
@@ -38,6 +38,8 @@ const sessionState = {
38
38
  coverLetters: [],
39
39
  interviewPrep: [],
40
40
  facts: {},
41
+ filesChanged: [],
42
+ lastBuilt: "",
41
43
  };
42
44
  /** Call once at startup so the session summary records the correct mode and model. */
43
45
  export function initSessionContext(mode, model) {
@@ -196,6 +198,26 @@ function extractSessionFacts(tool, args, result) {
196
198
  addHighlight(`Auto-filled application: ${company} (${role})`);
197
199
  break;
198
200
  }
201
+ // ── Coding file tools ───────────────────────────────────────────────────────────────────
202
+ case "write_file": {
203
+ const filePath = String(args.path || "");
204
+ if (filePath && !sessionState.filesChanged.includes(filePath)) {
205
+ sessionState.filesChanged.push(filePath);
206
+ }
207
+ // Track what was built from the path name
208
+ const fileName = filePath.split("/").pop() || filePath;
209
+ sessionState.lastBuilt = fileName;
210
+ addHighlight(`Created/wrote: ${filePath}`);
211
+ break;
212
+ }
213
+ case "patch_file": {
214
+ const filePath = String(args.path || "");
215
+ if (filePath && !sessionState.filesChanged.includes(filePath)) {
216
+ sessionState.filesChanged.push(filePath);
217
+ }
218
+ addHighlight(`Patched: ${filePath} (lines ${args.start_line}–${args.end_line})`);
219
+ break;
220
+ }
199
221
  default:
200
222
  break;
201
223
  }
@@ -274,6 +296,28 @@ export async function writeSessionSummary(stats) {
274
296
  facts: sessionState.facts,
275
297
  };
276
298
  saveSessionMemory(memoryInput);
299
+ // ── For coding mode: also save the fast pick-up file ──────────────────
300
+ if (sessionState.mode === "coding" && sessionState.filesChanged.length > 0) {
301
+ const { execSync } = await import("child_process");
302
+ let branch = "";
303
+ try {
304
+ branch = execSync("git branch --show-current", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
305
+ }
306
+ catch { /* no git */ }
307
+ const summaryParts = sessionState.highlights.slice(0, 5).map(h => h.outcome);
308
+ const summary = summaryParts.length > 0
309
+ ? summaryParts.join(" · ")
310
+ : `Coding session — ${stats.turns} turns, ${sessionState.filesChanged.length} file(s) changed.`;
311
+ saveCodingSession({
312
+ cwd: process.cwd(),
313
+ savedAt: new Date().toISOString(),
314
+ mode: "coding",
315
+ summary,
316
+ filesChanged: sessionState.filesChanged,
317
+ lastBuilt: sessionState.lastBuilt,
318
+ branch,
319
+ });
320
+ }
277
321
  }
278
322
  catch { /* memory write failure is non-fatal */ }
279
323
  }
@@ -14,6 +14,7 @@
14
14
  export declare const baseIdentity: string;
15
15
  export declare const RESUME_SECTION: string;
16
16
  export declare const CODING_SECTION: string;
17
+ export declare const BROWSER_CODING_SECTION: string;
17
18
  export declare const JOBS_TOOLS_SECTION: string;
18
19
  export declare const JOBS_HARNESS: string;
19
20
  export declare const GREETING_PROTOCOL: string;
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/agent/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsCH,mFAAmF;AACnF,eAAO,MAAM,YAAY,QAAsB,CAAC;AAMhD,eAAO,MAAM,cAAc,QAcnB,CAAC;AAMT,eAAO,MAAM,cAAc,QAqCnB,CAAC;AAMT,eAAO,MAAM,kBAAkB,QA0DvB,CAAC;AAMT,eAAO,MAAM,YAAY,QA6CjB,CAAC;AAMT,eAAO,MAAM,iBAAiB,QAuBtB,CAAC;AAOT;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,CAqCT"}
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/agent/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsCH,mFAAmF;AACnF,eAAO,MAAM,YAAY,QAAsB,CAAC;AAMhD,eAAO,MAAM,cAAc,QAcnB,CAAC;AAMT,eAAO,MAAM,cAAc,QAqDnB,CAAC;AAMT,eAAO,MAAM,sBAAsB,QAiD3B,CAAC;AAMT,eAAO,MAAM,kBAAkB,QA0DvB,CAAC;AAMT,eAAO,MAAM,YAAY,QA6CjB,CAAC;AAMT,eAAO,MAAM,iBAAiB,QAuBtB,CAAC;AAOT;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,CAsCT"}
@@ -68,40 +68,109 @@ You have access to the **get_resume** tool which fetches the user's live CareerV
68
68
  export const CODING_SECTION = `
69
69
  ## Coding Agent Mode
70
70
 
71
- You have access to file I/O, shell execution, and codebase search tools.
71
+ You are a fully autonomous software engineering agent equivalent to Antigravity or Claude Code.
72
+ You write production-ready code directly to local files on disk using your file tools.
72
73
 
73
- ### ⚠️ File Access Scope (STRICTdo not bypass)
74
+ ### ⚠️ Critical: Write Code to Files Never to Articles
74
75
 
75
- You operate with least-privilege file access. This is enforced at the tool level and cannot be overridden.
76
+ When a user asks you to build something, create a feature, write a script, or fix a bug:
77
+ 1. **USE write_file** to create actual code files in the user's working directory.
78
+ 2. **USE patch_file** to surgically edit existing files without rewriting them.
79
+ 3. **NEVER use publish_article** to output code. publish_article is ONLY for publishing markdown content to the CareerVivid community blog.
80
+ 4. **NEVER just show the user a code block** in the chat and say "here you go". Always write the file.
76
81
 
77
- **READ access** is limited to:
78
- - \`career-vivid/\` — your job tracker data, résumé drafts, and career pipeline files
79
- - \`cli/\` — the CLI source code (your own code)
80
- - \`tmp/\` or \`/tmp/\` — temporary scratch files
82
+ ### Your Execution Loop (follow this exactly)
81
83
 
82
- **WRITE access** is limited to:
83
- - \`career-vivid/\`ONLY the career data directory you own
84
- - \`tmp/\` or \`/tmp/\` temporary scratch files
84
+ 1. **Orient** Call list_directory or get_file_tree on the relevant directory to understand the project structure.
85
+ 2. **Read**Call read_file on any existing files you need to understand before modifying them.
86
+ 3. **Plan** Emit a concise "Plan:" (2–5 bullets: what files you will create/change and why).
87
+ 4. **Execute** — Call write_file or patch_file to create/modify every file in the plan.
88
+ 5. **Verify** — Run the compiler or tests via run_command (e.g., \`tsc --noEmit\`, \`npm test\`, \`python -c "import script"\`).
89
+ 6. **Fix** — If verification fails, read the error, patch the file, re-verify. Loop until clean.
90
+ 7. **Report** — Summarise what was created or changed, with the exact file paths.
85
91
 
86
- **NEVER attempt to read or write:**
87
- - \`src/\` — web app source code (React, components, pages)
88
- - \`functions/\` — Firebase Cloud Functions source
89
- - \`next-app/\` — Next.js application source
90
- - Any file outside your allowed prefixes
92
+ ### File Access
91
93
 
92
- If a task requires modifying web app code (\`src/\`, \`functions/\`, etc.), tell the user to make that change in their editor. Your role is career data management, not application development.
94
+ You have full read and write access to any file in the user's working directory EXCEPT:
95
+ - \`.env\` / \`.env.*\` files — contains secrets, never read or write these
96
+ - \`node_modules/\` — managed by npm, never write here
97
+ - \`.git/\` — git internals, use run_command for git operations
93
98
 
94
- ### Workflow
95
- 1. Read relevant files first (read_file). Never overwrite blindly.
96
- 2. Emit a short "Plan:" describing files you will touch and the approach.
97
- 3. Write data using write_file or patch_file (career-vivid/ only).
98
- 4. Verify with run_command (read-only commands like cat, ls, grep).
99
- 5. Fix errors and loop until clean; summarise all changes made.
99
+ ### Code Quality Standards
100
100
 
101
- ### Code Quality
102
- - TypeScript with strict types. Avoid \`any\` unless unavoidable.
103
- - Follow existing code style. Add JSDoc to public APIs.
104
- - Short, single-responsibility functions. Self-documenting names.
101
+ - **TypeScript/JavaScript**: Strict types, no implicit \`any\`, ESM imports, JSDoc on public APIs.
102
+ - **Python**: Type hints, docstrings on public functions, PEP8.
103
+ - **All languages**: Follow existing code style in the project. Keep functions small and single-responsibility.
104
+ - Read the existing file style before writing new code in that file.
105
+
106
+ ### Shell Commands
107
+
108
+ Use run_command for:
109
+ - Compiling: \`tsc --noEmit\`, \`npm run build\`, \`python -m py_compile\`
110
+ - Testing: \`npm test\`, \`pytest\`, \`go test ./...\`
111
+ - Formatting/linting: \`npm run lint\`, \`black .\`, \`gofmt\`
112
+ - Git: \`git status\`, \`git diff\`, \`git log --oneline -10\`
113
+ - Reading output: \`cat file\`, \`ls -la\`, \`grep -rn pattern dir/\`
114
+
115
+ ### What You Must Never Do
116
+
117
+ - ❌ Show a code block in the chat and say "paste this" — always write the file
118
+ - ❌ Call publish_article with code content
119
+ - ❌ Ask "would you like me to write this file?" — just write it
120
+ - ❌ Write to .env, node_modules/, or .git/
121
+ `.trim();
122
+ // ---------------------------------------------------------------------------
123
+ // §3b — Browser debugging section (added in --coding mode)
124
+ // ---------------------------------------------------------------------------
125
+ export const BROWSER_CODING_SECTION = `
126
+ ## Browser Debugging Tools
127
+
128
+ You have a full browser automation suite for debugging web apps during development.
129
+ Use these tools when the user asks you to:
130
+ - Inspect a running local dev server (localhost:3000, :8080, etc.)
131
+ - Debug a visual layout bug, broken UI, or rendering error
132
+ - Check what the browser actually shows vs. what the code produces
133
+ - Verify an HTML/CSS/JS project looks correct after generating it
134
+ - Watch live errors or network failures in a running app
135
+
136
+ ### Browser Debug Workflow
137
+
138
+ 1. **browser_navigate** — Open the local server URL (e.g., \`http://localhost:3000\`)
139
+ 2. **browser_state** — Read the page structure and all interactive elements
140
+ 3. **browser_screenshot** — Visually inspect layout (returns description of current view)
141
+ 4. **browser_scroll** — Scroll to reveal below-the-fold content
142
+ 5. **browser_click** — Interact with elements by their index number from browser_state
143
+ 6. **run_command** — Check server logs, console output, or network errors in parallel
144
+ 7. **patch_file** — Fix the issue in the source file
145
+ 8. **browser_navigate** (reload) — Verify the fix
146
+
147
+ ### When to use browser tools vs. run_command
148
+
149
+ | Use browser tools when… | Use run_command when… |
150
+ |-----------------------------------|------------------------------------|
151
+ | Visual / layout bug | Compile error or type error |
152
+ | JS runtime error in the browser | Test suite failure |
153
+ | Form or interaction doesn't work | Lint warning |
154
+ | Need to see what page renders | File system inspection (ls, cat) |
155
+ | Verifying CSS / responsive design | Running build scripts |
156
+
157
+ ### Browser Tool Quick Reference
158
+
159
+ - **browser_navigate(url)** — Go to a URL
160
+ - **browser_state()** — Get page title, URL, and numbered interactive element list
161
+ - **browser_click(element_index)** — Click by index from browser_state
162
+ - **browser_type(element_index, text)** — Type into a field
163
+ - **browser_scroll(direction, amount)** — Scroll up/down
164
+ - **browser_screenshot()** — Capture current view
165
+ - **browser_wait(seconds)** — Wait for async content to load
166
+ - **browser_close()** — Close the browser when done
167
+
168
+ ### Rules
169
+
170
+ - Always call browser_state() after navigating to get the current element list
171
+ - Use browser_wait() after actions that trigger page loads or AJAX calls
172
+ - When you find a bug visually, fix it in the source file with patch_file, then reload
173
+ - Do NOT use browser_autofill_application in coding mode — that is for job applications only
105
174
  `.trim();
106
175
  // ---------------------------------------------------------------------------
107
176
  // §4 — Jobs tools reference (appended in --jobs mode)
@@ -279,6 +348,7 @@ export function buildSystemPrompt(options) {
279
348
  return [
280
349
  baseIdentity,
281
350
  CODING_SECTION,
351
+ BROWSER_CODING_SECTION,
282
352
  ...memorySections,
283
353
  GREETING_PROTOCOL,
284
354
  ].join("\n\n---\n\n");
@@ -152,4 +152,32 @@ export declare function buildSessionSummary(opts: SessionSummaryInput): string;
152
152
  * Append a session summary to the memory file, merge into KB, compact if needed.
153
153
  */
154
154
  export declare function saveSessionMemory(opts: SessionSummaryInput): void;
155
+ export interface CodingSessionData {
156
+ /** Absolute path of the working directory when the session ended */
157
+ cwd: string;
158
+ /** ISO timestamp of when the session was saved */
159
+ savedAt: string;
160
+ /** Persona mode ("coding", "resume", etc.) */
161
+ mode: string;
162
+ /** Human-readable summary of what happened in the session */
163
+ summary: string;
164
+ /** Files the agent wrote or patched during the session */
165
+ filesChanged: string[];
166
+ /** Short description of the last thing that was built/created */
167
+ lastBuilt: string;
168
+ /** Git branch active during the session (empty if no git repo) */
169
+ branch: string;
170
+ }
171
+ /**
172
+ * Load the last coding session from ~/.careervivid/coding-session.md.
173
+ * Returns null if the file does not exist or cannot be parsed.
174
+ * Intentionally synchronous — called at startup before the agent loop.
175
+ */
176
+ export declare function loadCodingSession(): CodingSessionData | null;
177
+ /**
178
+ * Save the current coding session to ~/.careervivid/coding-session.md.
179
+ * Overwrites the previous entry — only the most recent session is kept here.
180
+ * Full history is preserved in memory.md via saveSessionMemory().
181
+ */
182
+ export declare function saveCodingSession(data: CodingSessionData): void;
155
183
  //# sourceMappingURL=memory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"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"}
@@ -462,3 +462,62 @@ export function saveSessionMemory(opts) {
462
462
  const updated = compactMemory(base + "\n" + entry);
463
463
  saveMemory(updated);
464
464
  }
465
+ // =============================================================================
466
+ // Coding session file — fast persona-specific pick-up context
467
+ // =============================================================================
468
+ const CODING_SESSION_FILE = join(MEMORY_DIR, "coding-session.md");
469
+ /**
470
+ * Load the last coding session from ~/.careervivid/coding-session.md.
471
+ * Returns null if the file does not exist or cannot be parsed.
472
+ * Intentionally synchronous — called at startup before the agent loop.
473
+ */
474
+ export function loadCodingSession() {
475
+ if (!existsSync(CODING_SESSION_FILE))
476
+ return null;
477
+ try {
478
+ const raw = readFileSync(CODING_SESSION_FILE, "utf-8").trim();
479
+ const match = raw.match(/<!-- CODING_SESSION_START -->([\s\S]*?)<!-- CODING_SESSION_END -->/);
480
+ if (!match)
481
+ return null;
482
+ return JSON.parse(match[1].trim());
483
+ }
484
+ catch {
485
+ return null;
486
+ }
487
+ }
488
+ /**
489
+ * Save the current coding session to ~/.careervivid/coding-session.md.
490
+ * Overwrites the previous entry — only the most recent session is kept here.
491
+ * Full history is preserved in memory.md via saveSessionMemory().
492
+ */
493
+ export function saveCodingSession(data) {
494
+ try {
495
+ if (!existsSync(MEMORY_DIR))
496
+ mkdirSync(MEMORY_DIR, { recursive: true });
497
+ const filesList = data.filesChanged.length > 0
498
+ ? "## Files Changed\n" + data.filesChanged.map(f => `- \`${f}\``).join("\n")
499
+ : "";
500
+ const content = [
501
+ "# Last Coding Session",
502
+ "",
503
+ "> Auto-saved by cv agent --coding. Do not edit manually.",
504
+ "",
505
+ `**Saved:** ${data.savedAt}`,
506
+ `**Directory:** \`${data.cwd}\``,
507
+ `**Branch:** ${data.branch || "(no git)"}`,
508
+ "",
509
+ "## Summary",
510
+ data.summary,
511
+ "",
512
+ filesList,
513
+ "",
514
+ "<!-- CODING_SESSION_START -->",
515
+ JSON.stringify(data, null, 2),
516
+ "<!-- CODING_SESSION_END -->",
517
+ ].join("\n");
518
+ writeFileSync(CODING_SESSION_FILE, content, "utf-8");
519
+ }
520
+ catch {
521
+ // Non-fatal — session save failure must never crash the agent
522
+ }
523
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"coding.d.ts","sourceRoot":"","sources":["../../../src/agent/tools/coding.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAyGlC,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG3C;AAMD,eAAO,MAAM,YAAY,EAAE,IAqC1B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA0B3B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA+C3B,CAAC;AAMF,eAAO,MAAM,iBAAiB,EAAE,IA8C/B,CAAC;AAMF,eAAO,MAAM,eAAe,EAAE,IA8C7B,CAAC;AAMF,eAAO,MAAM,cAAc,EAAE,IAmD5B,CAAC;AAEF,iEAAiE;AACjE,wBAAgB,yBAAyB,IAAI,IAAI,CAQhD;AAMD,eAAO,MAAM,eAAe,EAAE,IAkD7B,CAAC;AAMF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAQlC,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"coding.d.ts","sourceRoot":"","sources":["../../../src/agent/tools/coding.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAmGlC,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG3C;AAMD,eAAO,MAAM,YAAY,EAAE,IAqC1B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA0B3B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA+C3B,CAAC;AAMF,eAAO,MAAM,iBAAiB,EAAE,IA8C/B,CAAC;AAMF,eAAO,MAAM,eAAe,EAAE,IA8C7B,CAAC;AAMF,eAAO,MAAM,cAAc,EAAE,IAmD5B,CAAC;AAEF,iEAAiE;AACjE,wBAAgB,yBAAyB,IAAI,IAAI,CAQhD;AAMD,eAAO,MAAM,eAAe,EAAE,IAkD7B,CAAC;AAMF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAQlC,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -5,45 +5,38 @@ import { Type } from '@google/genai';
5
5
  // ============================================================================
6
6
  // Path-based Access Control (Least Privilege)
7
7
  // ============================================================================
8
+ // ============================================================================
9
+ // Path-based Access Control
10
+ // ============================================================================
8
11
  /**
9
- * Directories the agent is ALLOWED to READ.
10
- * These are relative to the repo root (process.cwd()) or absolute paths the agent owns.
11
- */
12
- const READ_ALLOWED_PREFIXES = [
13
- // Agent's own data & config
14
- 'career-vivid/', // job tracker CSV, résumé drafts, career data
15
- 'cli/', // CLI source — agent may read its own code
16
- // Scratch / temporary outputs produced by the agent
17
- 'tmp/',
18
- '/tmp/',
19
- // User's home config file is read by loadConfig() — not by read_file tool
20
- ];
21
- /**
22
- * Directories the agent is ALLOWED to WRITE.
23
- * This is intentionally much narrower than the read list.
24
- * Source code (src/, functions/, next-app/) is completely off-limits.
12
+ * In coding mode the agent operates like a real coding assistant (Antigravity-style).
13
+ * It can read and write files anywhere in the working directory tree.
14
+ *
15
+ * Hard-blocked regardless of mode:
16
+ * - .env / .env.* files (secrets)
17
+ * - node_modules/ (package artifacts use npm install)
18
+ * - .git/ (git internals)
25
19
  */
26
- const WRITE_ALLOWED_PREFIXES = [
27
- 'career-vivid/', // job tracker CSV, résumé drafts — the only structured data the agent owns
28
- 'tmp/',
29
- '/tmp/',
20
+ const HARD_BLOCKED_PATTERNS = [
21
+ /\/\.env(\.\w+)?$/, // .env, .env.local, .env.production, etc.
22
+ /\/node_modules\//, // node_modules anywhere in path
23
+ /\/\.git\//, // .git internals
30
24
  ];
25
+ function isHardBlocked(absPath) {
26
+ return HARD_BLOCKED_PATTERNS.some(re => re.test(absPath));
27
+ }
31
28
  /**
32
- * Resolve a raw path argument and check it against an allowlist.
33
- * @throws if the resolved path falls outside every allowed prefix.
29
+ * Resolve a raw path argument and enforce hard-block rules.
30
+ * @throws if the path matches a hard-blocked pattern.
34
31
  */
35
- function guardPath(rawPath, allowedPrefixes, mode) {
32
+ function guardPath(rawPath, _allowedPrefixes, mode) {
36
33
  const cwd = process.cwd();
37
34
  const absPath = normalize(resolve(rawPath));
38
- const allowed = allowedPrefixes.some(prefix => {
39
- const fullPrefix = prefix.startsWith('/') ? normalize(prefix) : normalize(join(cwd, prefix));
40
- return absPath.startsWith(fullPrefix);
41
- });
42
- if (!allowed) {
35
+ if (isHardBlocked(absPath)) {
43
36
  const rel = relative(cwd, absPath);
44
- throw new Error(`⛔ Access denied: ${mode} access to "${rel}" is not permitted.\n` +
45
- `The agent can only ${mode} files inside: ${allowedPrefixes.join(', ')}\n` +
46
- `To access this file, use your editor or the CareerVivid web app.`);
37
+ throw new Error(`⛔ Access denied: ${mode} access to "${rel}" is blocked for safety.\n` +
38
+ `Blocked paths: .env files, node_modules/, .git/\n` +
39
+ `Edit secrets manually in your editor.`);
47
40
  }
48
41
  return absPath;
49
42
  }
@@ -122,7 +115,7 @@ export const readFileTool = {
122
115
  required: ['path'],
123
116
  },
124
117
  execute: async (args) => {
125
- const absPath = guardPath(args.path, READ_ALLOWED_PREFIXES, 'read');
118
+ const absPath = guardPath(args.path, [], 'read');
126
119
  if (!existsSync(absPath)) {
127
120
  throw new Error(`File not found: ${absPath}`);
128
121
  }
@@ -159,7 +152,7 @@ export const writeFileTool = {
159
152
  required: ['path', 'content'],
160
153
  },
161
154
  execute: async (args) => {
162
- const absPath = guardPath(args.path, WRITE_ALLOWED_PREFIXES, 'write');
155
+ const absPath = guardPath(args.path, [], 'write');
163
156
  mkdirSync(dirname(absPath), { recursive: true });
164
157
  writeFileSync(absPath, args.content, 'utf-8');
165
158
  const lines = args.content.split('\n').length;
@@ -196,7 +189,7 @@ export const patchFileTool = {
196
189
  required: ['path', 'start_line', 'end_line', 'new_content'],
197
190
  },
198
191
  execute: async (args) => {
199
- const absPath = guardPath(args.path, WRITE_ALLOWED_PREFIXES, 'write');
192
+ const absPath = guardPath(args.path, [], 'write');
200
193
  if (!existsSync(absPath))
201
194
  throw new Error(`File not found: ${absPath}`);
202
195
  const lines = readFileSync(absPath, 'utf-8').split('\n');
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAmJpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAqJpD"}
@@ -6,6 +6,7 @@ import { getTools } from "./toolRegistry.js";
6
6
  import { getSystemInstruction, buildEngine, printBanner } from "./engineResolver.js";
7
7
  import { askLoop } from "./repl.js";
8
8
  import { initSessionContext } from "../../agent/agentAuditLog.js";
9
+ import { resolvePersona } from "./personas.js";
9
10
  const { prompt } = pkg;
10
11
  export function registerAgentCommand(program) {
11
12
  const agentCmd = program
@@ -119,10 +120,11 @@ export function registerAgentCommand(program) {
119
120
  }
120
121
  const engine = buildEngine(selectedProvider, selectedModel, systemInstruction, tools, thinkingBudget, includeThoughts, cvApiKey, geminiApiKey, project);
121
122
  printBanner(options, selectedProvider, selectedModel, thinkingBudget);
122
- // agentMode: jobs is now always the default
123
123
  const agentMode = options.resume ? "resume" : options.coding ? "coding" : "jobs";
124
124
  initSessionContext(agentMode, selectedModel);
125
- await askLoop(engine, options, selectedProvider, selectedModel, cvApiKey, systemInstruction, tools);
125
+ // Resolve the active persona — drives menu, pick-up prompt, and context
126
+ const persona = resolvePersona(options);
127
+ await askLoop(engine, options, selectedProvider, selectedModel, cvApiKey, systemInstruction, tools, persona);
126
128
  });
127
129
  agentCmd.command("config")
128
130
  .description("Configure the default LLM provider for cv agent")
@@ -0,0 +1,41 @@
1
+ /**
2
+ * personas.ts — Extensible Persona Registry for cv agent
3
+ *
4
+ * Each persona defines:
5
+ * - id: unique key (matches the CLI flag name)
6
+ * - label: human-readable name shown in the banner
7
+ * - menuItems: first-turn autocomplete menu entries
8
+ * - pickUpPrompt: the system message injected when the user chooses
9
+ * "Pick up where we left off" — workspace-aware, not job-aware
10
+ * - contextGather: async function returning a markdown context snapshot
11
+ * of the current environment relevant to this persona
12
+ *
13
+ * ── Adding a new persona ──────────────────────────────────────────────────────
14
+ * 1. Add a new PersonaDefinition entry in PERSONAS below.
15
+ * 2. Register the matching --flag in index.ts (one line).
16
+ * 3. Done. The menu, context, and system prompt routing all work automatically.
17
+ */
18
+ export interface PersonaDefinition {
19
+ id: string;
20
+ label: string;
21
+ /** Displayed in the first-turn autocomplete menu */
22
+ menuItems: string[];
23
+ /**
24
+ * Optional per-item prompt overrides.
25
+ * Key = exact menu item string. Value = the prompt sent to the agent.
26
+ * Items without an entry get the default emoji-stripped text.
27
+ */
28
+ menuPrompts?: Record<string, string>;
29
+ /** Used as the agent prompt when "Pick up where we left off" is selected */
30
+ pickUpPrompt: string;
31
+ /** Gathers live workspace context for the pick-up prompt */
32
+ contextGather: () => Promise<string>;
33
+ }
34
+ /**
35
+ * Resolve the active PersonaDefinition from the CLI options object.
36
+ * Falls back to the jobs persona.
37
+ */
38
+ export declare function resolvePersona(options: Record<string, any>): PersonaDefinition;
39
+ /** All registered personas — useful for dynamic --help generation. */
40
+ export declare const ALL_PERSONAS: PersonaDefinition[];
41
+ //# sourceMappingURL=personas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personas.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/personas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACtC;AAwTD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAK9E;AAED,sEAAsE;AACtE,eAAO,MAAM,YAAY,qBAA6B,CAAC"}
@@ -0,0 +1,323 @@
1
+ /**
2
+ * personas.ts — Extensible Persona Registry for cv agent
3
+ *
4
+ * Each persona defines:
5
+ * - id: unique key (matches the CLI flag name)
6
+ * - label: human-readable name shown in the banner
7
+ * - menuItems: first-turn autocomplete menu entries
8
+ * - pickUpPrompt: the system message injected when the user chooses
9
+ * "Pick up where we left off" — workspace-aware, not job-aware
10
+ * - contextGather: async function returning a markdown context snapshot
11
+ * of the current environment relevant to this persona
12
+ *
13
+ * ── Adding a new persona ──────────────────────────────────────────────────────
14
+ * 1. Add a new PersonaDefinition entry in PERSONAS below.
15
+ * 2. Register the matching --flag in index.ts (one line).
16
+ * 3. Done. The menu, context, and system prompt routing all work automatically.
17
+ */
18
+ import { execSync } from "child_process";
19
+ import { existsSync, readdirSync, statSync } from "fs";
20
+ import { join } from "path";
21
+ import { loadCodingSession } from "../../agent/memory.js";
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Shared context helpers
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ function tryExec(cmd) {
26
+ try {
27
+ return execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
28
+ }
29
+ catch {
30
+ return "";
31
+ }
32
+ }
33
+ function getRecentlyModifiedFiles(dir, maxFiles = 8) {
34
+ const results = [];
35
+ function walk(current, depth = 0) {
36
+ if (depth > 3)
37
+ return;
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(current);
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ for (const name of entries) {
46
+ if (name.startsWith(".") || name === "node_modules" || name === "__pycache__" || name === ".git")
47
+ continue;
48
+ const full = join(current, name);
49
+ try {
50
+ const st = statSync(full);
51
+ if (st.isDirectory()) {
52
+ walk(full, depth + 1);
53
+ }
54
+ else if (/\.(ts|tsx|js|jsx|py|go|rs|java|cs|cpp|c|md|json|yaml|yml|sh|toml)$/.test(name)) {
55
+ results.push({ path: full.replace(dir + "/", ""), mtime: st.mtimeMs });
56
+ }
57
+ }
58
+ catch {
59
+ // skip
60
+ }
61
+ }
62
+ }
63
+ walk(dir);
64
+ return results
65
+ .sort((a, b) => b.mtime - a.mtime)
66
+ .slice(0, maxFiles)
67
+ .map((f) => f.path);
68
+ }
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Coding Persona
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+ async function gatherCodingContext() {
73
+ const cwd = process.cwd();
74
+ const lines = [];
75
+ // Git branch + last commit
76
+ const branch = tryExec("git branch --show-current");
77
+ const lastCommit = tryExec("git log --oneline -1");
78
+ const gitStatus = tryExec("git status --short").split("\n").slice(0, 10).join("\n");
79
+ if (branch)
80
+ lines.push(`**Git branch:** \`${branch}\``);
81
+ if (lastCommit)
82
+ lines.push(`**Last commit:** ${lastCommit}`);
83
+ if (gitStatus)
84
+ lines.push(`**Uncommitted changes:**\n\`\`\`\n${gitStatus}\n\`\`\``);
85
+ // Recent files
86
+ const recentFiles = getRecentlyModifiedFiles(cwd);
87
+ if (recentFiles.length > 0) {
88
+ lines.push(`**Recently modified files:**\n${recentFiles.map((f) => ` • \`${f}\``).join("\n")}`);
89
+ }
90
+ // Package.json / pyproject.toml project name
91
+ const pkgPath = join(cwd, "package.json");
92
+ const pyPath = join(cwd, "pyproject.toml");
93
+ if (existsSync(pkgPath)) {
94
+ try {
95
+ const pkg = JSON.parse(require("fs").readFileSync(pkgPath, "utf-8"));
96
+ lines.push(`**Project:** ${pkg.name ?? "unknown"} v${pkg.version ?? "?"} (Node.js)`);
97
+ }
98
+ catch { /* ok */ }
99
+ }
100
+ else if (existsSync(pyPath)) {
101
+ const name = tryExec("grep '^name' pyproject.toml | head -1");
102
+ if (name)
103
+ lines.push(`**Project (Python):** ${name}`);
104
+ }
105
+ return lines.length > 0 ? lines.join("\n") : "No git repo or recognised project found in current directory.";
106
+ }
107
+ const CODING_PERSONA = {
108
+ id: "coding",
109
+ label: "Coding",
110
+ menuItems: [
111
+ "🔍 Analyze the current codebase or a specific file",
112
+ "🛠️ Debug an error or failing test",
113
+ "🏗️ Scaffold a new feature or module into an existing project",
114
+ "🔄 Refactor or optimize existing code",
115
+ "📖 Explain how a piece of code works",
116
+ "🧪 Write or improve tests",
117
+ "🆕 Create a brand-new project or app in a new folder",
118
+ "🌐 Debug or test in the browser",
119
+ "🗓️ Pick up where we left off",
120
+ ],
121
+ menuPrompts: {
122
+ "🔍 Analyze the current codebase or a specific file": [
123
+ `The user wants to analyze their codebase. Current working directory: \`${process.cwd()}\`.`,
124
+ `1. Run get_file_tree on the current directory to show the project structure.`,
125
+ `2. Ask the user: "Which file or directory would you like me to analyze? Or should I give you an overview of the whole project?"`,
126
+ `3. Wait for their answer before reading any files.`,
127
+ ].join("\n"),
128
+ "🛠️ Debug an error or failing test": [
129
+ `The user wants to debug an error or failing test. Current working directory: \`${process.cwd()}\`.`,
130
+ `1. Ask the user: "Please paste the full error message or test output, and tell me which file it's coming from."`,
131
+ `2. Once you have the error, read the relevant file(s) and identify the root cause.`,
132
+ `3. Apply a targeted fix with patch_file. Verify with run_command (e.g., npm test or tsc --noEmit).`,
133
+ ].join("\n"),
134
+ "🏗️ Scaffold a new feature or module into an existing project": [
135
+ `The user wants to scaffold a feature or module into their EXISTING project. Current working directory: \`${process.cwd()}\`.`,
136
+ ``,
137
+ `Before writing ANY files, ask the user these two questions in a single message:`,
138
+ `1. "What feature or module would you like to add?" — e.g., authentication, API route, React component, database model`,
139
+ `2. "Which directory should I add it to?" — e.g., src/components, src/api, lib/`,
140
+ ``,
141
+ `Do NOT write any files until you have both answers.`,
142
+ `Once you have the answers: read the relevant existing files first → Plan → write_file/patch_file → run_command to verify → Report all changed paths.`,
143
+ ].join("\n"),
144
+ "🆕 Create a brand-new project or app in a new folder": [
145
+ `The user wants to create a BRAND-NEW standalone project in a new folder. Current working directory: \`${process.cwd()}\`.`,
146
+ ``,
147
+ `Ask the user ONE message with these two questions:`,
148
+ `1. "What would you like to build?"`,
149
+ ` Give them examples to spark ideas:`,
150
+ ` • A modern HTML/CSS/JS dashboard visualising my CareerVivid job pipeline`,
151
+ ` • A React + Vite app with TypeScript`,
152
+ ` • A FastAPI Python REST API`,
153
+ ` • A Next.js full-stack app`,
154
+ ` • A Node.js CLI tool`,
155
+ ` • A static landing page`,
156
+ ` • Or describe anything else`,
157
+ `2. "What should the folder be called?" (e.g., job-dashboard, my-api, cool-app)`,
158
+ ``,
159
+ `Do NOT create any files or run any commands until you have both answers.`,
160
+ ``,
161
+ `Once you have the answers, follow this execution loop:`,
162
+ ` a. Plan: list every file you will create with a one-line description of each`,
163
+ ` b. Create the folder and write EVERY file using write_file`,
164
+ ` — For HTML/CSS/JS projects: write index.html, style.css, app.js (and any sub-files) with REAL, complete, production-quality code — NO placeholders`,
165
+ ` — For Node/Python projects: write package.json/pyproject.toml, README.md, and all source files`,
166
+ ` — For React/Next/Vite apps: write all config files AND source components`,
167
+ ` c. Run run_command to verify (e.g., \`tsc --noEmit\`, \`python -m py_compile\`, or \`open index.html\`)`,
168
+ ` d. Report: list every file created with its absolute path so the user can open them immediately`,
169
+ ``,
170
+ `Special case — Job Pipeline Dashboard:`,
171
+ `If the user asks for a dashboard based on their job application pipeline or CareerVivid data:`,
172
+ ` 1. Run run_command with \`cat ~/career-vivid/jobs.csv\` to read their real job data`,
173
+ ` 2. Parse the CSV and embed the actual data as a JavaScript array in the dashboard`,
174
+ ` 3. Build a beautiful, modern HTML/CSS/JS single-file dashboard with:`,
175
+ ` — Kanban-style status columns (To Apply / Applied / Interview / Offer / Rejected)`,
176
+ ` — Cards for each job showing company, role, status, excitement score`,
177
+ ` — Summary stats bar (total jobs, applied %, top companies)`,
178
+ ` — A color-coded priority chart`,
179
+ ` — Responsive design that works on mobile`,
180
+ ` 4. Write it to \`job-dashboard/index.html\` as a self-contained file (inline CSS + JS)`,
181
+ ` 5. Run \`open job-dashboard/index.html\` so the user sees it immediately in their browser`,
182
+ ].join("\n"),
183
+ "🔄 Refactor or optimize existing code": [
184
+ `The user wants to refactor or optimize code. Current working directory: \`${process.cwd()}\`.`,
185
+ `1. Ask: "Which file or function would you like me to refactor? What's the goal — performance, readability, type safety, or something else?"`,
186
+ `2. Wait for their answer. Then read the file, propose a brief Plan, and apply changes with patch_file.`,
187
+ ].join("\n"),
188
+ "📖 Explain how a piece of code works": [
189
+ `The user wants an explanation of some code. Current working directory: \`${process.cwd()}\`.`,
190
+ `Ask: "Which file, function, or class would you like me to explain?"`,
191
+ `Once they answer, read the file and give a clear, structured explanation.`,
192
+ ].join("\n"),
193
+ "🧪 Write or improve tests": [
194
+ `The user wants to write or improve tests. Current working directory: \`${process.cwd()}\`.`,
195
+ `1. Ask: "Which file or function should I write tests for? And do you want unit tests, integration tests, or browser-based E2E tests?"`,
196
+ `2. Once they answer:`,
197
+ ` • For unit/integration tests: read the source file, detect the test framework (check package.json), write test files to disk with write_file, run with run_command.`,
198
+ ` • For browser E2E tests: use browser_navigate to open the app, browser_state to inspect, browser_click/browser_type to simulate user actions, browser_screenshot to verify results.`,
199
+ `3. Report all test files created and test results.`,
200
+ ].join("\n"),
201
+ "🌐 Debug or test in the browser": [
202
+ `The user wants to debug a visual/runtime issue OR test a feature directly in the browser. Current working directory: \`${process.cwd()}\`.`,
203
+ ``,
204
+ `First, ask ONE clarifying question: "What would you like to do?"`,
205
+ `Give these options:`,
206
+ ` • Debug a visual or layout bug in my running app`,
207
+ ` • Debug a JavaScript runtime error`,
208
+ ` • Smoke test / manually test a feature in the browser`,
209
+ ` • Run a browser-based E2E test on my app`,
210
+ ` • Verify my generated HTML/CSS/JS file looks correct`,
211
+ ``,
212
+ `Once you know what they want, follow the browser debug/test loop:`,
213
+ `1. Ask for the URL if not provided (e.g., http://localhost:3000 or a file path like ./index.html)`,
214
+ `2. Call browser_navigate(url) to open it`,
215
+ `3. Call browser_state() to get the page structure`,
216
+ `4. Call browser_screenshot() to visually inspect the page`,
217
+ `5. For debugging: identify the root cause, patch_file to fix it, browser_navigate to reload and verify`,
218
+ `6. For testing: browser_click / browser_type to simulate user interactions, browser_screenshot to confirm the expected result`,
219
+ `7. Report what was found and fixed/verified`,
220
+ ``,
221
+ `If the user's dev server is not running, offer to start it: run_command with \`npm run dev\` or equivalent.`,
222
+ ].join("\n"),
223
+ },
224
+ pickUpPrompt: (() => {
225
+ // Read the coding session file synchronously — this is O(1), no tool calls.
226
+ const last = loadCodingSession();
227
+ if (last) {
228
+ const fileList = last.filesChanged.length > 0
229
+ ? `Files changed last session:\n${last.filesChanged.slice(0, 10).map(f => ` • \`${f}\``).join("\n")}`
230
+ : "";
231
+ return [
232
+ `## Resuming Last Coding Session`,
233
+ ``,
234
+ `Here is the context from the last coding session (read from local memory — no tool calls needed):`,
235
+ ``,
236
+ `- **Directory:** \`${last.cwd}\``,
237
+ `- **Git branch:** ${last.branch || "(no git repo)"}`,
238
+ `- **Last built:** ${last.lastBuilt || "(unknown)"}`,
239
+ `- **Session saved:** ${new Date(last.savedAt).toLocaleString()}`,
240
+ ``,
241
+ `**What happened last time:**`,
242
+ last.summary,
243
+ ``,
244
+ fileList,
245
+ ``,
246
+ `The current working directory is now: \`${process.cwd()}\``,
247
+ ``,
248
+ `Greet the user with a ONE-LINE recap of what was worked on last time and ask what they want to continue or start next.`,
249
+ `Do NOT run any git commands or scan the filesystem — you already have the context above.`,
250
+ `Only call tools if the user asks for something new.`,
251
+ ].filter(Boolean).join("\n");
252
+ }
253
+ // No previous session — do a lightweight orientation
254
+ return [
255
+ `No previous coding session found in memory.`,
256
+ `Current working directory: \`${process.cwd()}\`.`,
257
+ ``,
258
+ `Ask the user: "What would you like to work on today?" — give them the menu options as a reminder.`,
259
+ `Do NOT run any commands or scan files until they answer.`,
260
+ ].join("\n");
261
+ })(),
262
+ contextGather: gatherCodingContext,
263
+ };
264
+ // ─────────────────────────────────────────────────────────────────────────────
265
+ // Jobs Persona (default)
266
+ // ─────────────────────────────────────────────────────────────────────────────
267
+ const JOBS_PERSONA = {
268
+ id: "jobs",
269
+ label: "Jobs & Applications",
270
+ menuItems: [
271
+ "📄 View or update my resume",
272
+ "🔍 Search for job opportunities",
273
+ "📊 Check my job pipeline / tracker",
274
+ "✉️ Draft a cover letter or tailor my resume",
275
+ "🎙 Start an AI mock interview (voice or text)",
276
+ "📈 Get an overview of my job search progress",
277
+ "🗓️ Pick up where we left off",
278
+ ],
279
+ pickUpPrompt: "The user wants to pick up their last job-search session. Check their resume list (cv tracker resume list), review their job tracker pipeline (cv tracker list), and provide a concise overview of where they left off. Suggest the most impactful next action.",
280
+ contextGather: async () => "",
281
+ };
282
+ // ─────────────────────────────────────────────────────────────────────────────
283
+ // Resume Persona
284
+ // ─────────────────────────────────────────────────────────────────────────────
285
+ const RESUME_PERSONA = {
286
+ id: "resume",
287
+ label: "Resume Builder",
288
+ menuItems: [
289
+ "📄 View or load my current resume",
290
+ "✏️ Update a section of my resume",
291
+ "🎯 Tailor my resume for a specific job",
292
+ "🔗 Sync resume to my portfolio",
293
+ "💡 Get suggestions to improve my resume",
294
+ "🗓️ Pick up where we left off",
295
+ ],
296
+ pickUpPrompt: "The user wants to pick up their last resume session. List their resumes with get_resume or list_resumes, identify the most recently updated one, and summarise what was last worked on. Suggest the next improvement.",
297
+ contextGather: async () => "",
298
+ };
299
+ // ─────────────────────────────────────────────────────────────────────────────
300
+ // Persona registry
301
+ // ─────────────────────────────────────────────────────────────────────────────
302
+ const PERSONA_MAP = {
303
+ coding: CODING_PERSONA,
304
+ jobs: JOBS_PERSONA,
305
+ resume: RESUME_PERSONA,
306
+ // Future:
307
+ // ceo: CEO_PERSONA,
308
+ // finance: FINANCE_PERSONA,
309
+ // marketing: MARKETING_PERSONA,
310
+ };
311
+ /**
312
+ * Resolve the active PersonaDefinition from the CLI options object.
313
+ * Falls back to the jobs persona.
314
+ */
315
+ export function resolvePersona(options) {
316
+ for (const key of Object.keys(PERSONA_MAP)) {
317
+ if (options[key])
318
+ return PERSONA_MAP[key];
319
+ }
320
+ return JOBS_PERSONA;
321
+ }
322
+ /** All registered personas — useful for dynamic --help generation. */
323
+ export const ALL_PERSONAS = Object.values(PERSONA_MAP);
@@ -2,13 +2,22 @@
2
2
  * repl/input.ts
3
3
  *
4
4
  * Handles all user input collection:
5
- * - First-turn quick-action menu (autocomplete)
5
+ * - First-turn quick-action menu (autocomplete) — persona-aware
6
6
  * - Subsequent turn free-text input
7
7
  * - Multi-line paste mode (<<<)
8
8
  * - Fast-paste buffer accumulation
9
9
  */
10
- /** Read the first-turn menu selection. Returns the cleaned user intent string. */
11
- export declare function readFirstTurnInput(): Promise<string>;
10
+ import type { PersonaDefinition } from "../personas.js";
11
+ /**
12
+ * Read the first-turn menu selection.
13
+ * Returns the cleaned user-intent string ready to send to the agent.
14
+ *
15
+ * When the user picks "Pick up where we left off" the persona's
16
+ * `pickUpPrompt` is returned instead of the raw menu label — so
17
+ * the agent receives a rich context-gathering instruction specific
18
+ * to the active mode (coding, jobs, resume, …).
19
+ */
20
+ export declare function readFirstTurnInput(persona: PersonaDefinition): Promise<string>;
12
21
  /** Reads a single multi-line paste block. User ends with an empty Enter. */
13
22
  export declare function readMultiLineInput(prefix: string): Promise<string>;
14
23
  export interface InputResult {
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/repl/input.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiBH,kFAAkF;AAClF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAyB1D;AAED,4EAA4E;AAC5E,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBxE;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,gFAAgF;AAChF,wBAAsB,eAAe,CAAC,gBAAgB,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAUrF"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/repl/input.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMxD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoCpF;AAED,4EAA4E;AAC5E,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBxE;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,gFAAgF;AAChF,wBAAsB,eAAe,CAAC,gBAAgB,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAUrF"}
@@ -2,7 +2,7 @@
2
2
  * repl/input.ts
3
3
  *
4
4
  * Handles all user input collection:
5
- * - First-turn quick-action menu (autocomplete)
5
+ * - First-turn quick-action menu (autocomplete) — persona-aware
6
6
  * - Subsequent turn free-text input
7
7
  * - Multi-line paste mode (<<<)
8
8
  * - Fast-paste buffer accumulation
@@ -10,19 +10,19 @@
10
10
  import chalk from "chalk";
11
11
  import pkg from "enquirer";
12
12
  const { prompt } = pkg;
13
- const MENU_ITEMS = [
14
- "📄 View or update my resume",
15
- "🔍 Search for job opportunities",
16
- "📊 Check my job pipeline / tracker",
17
- "✉️ Draft a cover letter or tailor my resume",
18
- "🎙 Start an AI mock interview (voice or text)",
19
- "📈 Get an overview of my job search progress",
20
- "🗓️ Pick up where we left off",
21
- ];
22
- /** Read the first-turn menu selection. Returns the cleaned user intent string. */
23
- export async function readFirstTurnInput() {
13
+ const PICK_UP_LABEL = "🗓️ Pick up where we left off";
14
+ /**
15
+ * Read the first-turn menu selection.
16
+ * Returns the cleaned user-intent string ready to send to the agent.
17
+ *
18
+ * When the user picks "Pick up where we left off" the persona's
19
+ * `pickUpPrompt` is returned instead of the raw menu label — so
20
+ * the agent receives a rich context-gathering instruction specific
21
+ * to the active mode (coding, jobs, resume, …).
22
+ */
23
+ export async function readFirstTurnInput(persona) {
24
24
  console.log(chalk.dim(" What would you like to do today?\n"));
25
- for (const item of MENU_ITEMS) {
25
+ for (const item of persona.menuItems) {
26
26
  console.log(chalk.dim(` ${item}`));
27
27
  }
28
28
  console.log("");
@@ -30,17 +30,25 @@ export async function readFirstTurnInput() {
30
30
  type: "autocomplete",
31
31
  name: "choice",
32
32
  message: chalk.bold.hex("#6366f1")("❯") + chalk.dim(" ·"),
33
- limit: 7,
33
+ limit: persona.menuItems.length,
34
34
  suggest(input, choices) {
35
35
  if (!input)
36
36
  return choices;
37
37
  return choices.filter((c) => c.value.toLowerCase().includes(input.toLowerCase()));
38
38
  },
39
- choices: MENU_ITEMS.map(item => ({ name: item, value: item })),
39
+ choices: persona.menuItems.map((item) => ({ name: item, value: item })),
40
40
  footer: chalk.dim(" ↑↓ to navigate · type to filter · Enter to send"),
41
41
  });
42
- const raw = firstResp.choice?.trim() || "";
43
- // Strip emoji prefixes so the agent gets clean text
42
+ const raw = firstResp.choice?.trim() ?? "";
43
+ // 1. "Pick up where we left off" persona-specific context-gathering prompt
44
+ if (raw === PICK_UP_LABEL || raw.toLowerCase().includes("pick up")) {
45
+ return persona.pickUpPrompt;
46
+ }
47
+ // 2. Per-item prompt override (e.g. scaffold, debug) → rich targeted prompt
48
+ if (persona.menuPrompts?.[raw]) {
49
+ return persona.menuPrompts[raw];
50
+ }
51
+ // 3. Default: strip emoji prefix and send clean text
44
52
  return raw.replace(/^[\p{Emoji}\s]+/u, "").trim() || raw;
45
53
  }
46
54
  /** Reads a single multi-line paste block. User ends with an empty Enter. */
@@ -11,6 +11,7 @@
11
11
  import { CareerVividProxyEngine } from "../../agent/CareerVividProxyEngine.js";
12
12
  import { QueryEngine } from "../../agent/QueryEngine.js";
13
13
  import { type LLMProvider } from "../../config.js";
14
+ import type { PersonaDefinition } from "./personas.js";
14
15
  export declare function printCreditStatus(remaining: number | null, limit?: number | null): void;
15
16
  export declare function askLoop(engine: QueryEngine | CareerVividProxyEngine | null, options: {
16
17
  verbose?: boolean;
@@ -22,5 +23,5 @@ export declare function askLoop(engine: QueryEngine | CareerVividProxyEngine | n
22
23
  jobs?: boolean;
23
24
  resume?: boolean;
24
25
  coding?: boolean;
25
- }, selectedProvider: LLMProvider, selectedModel: string, cvApiKey: string | undefined, systemInstruction: string, tools: any[]): Promise<void>;
26
+ }, selectedProvider: LLMProvider, selectedModel: string, cvApiKey: string | undefined, systemInstruction: string, tools: any[], persona: PersonaDefinition): Promise<void>;
26
27
  //# sourceMappingURL=repl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAanD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAsBtF;AA+DD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,GAAG,sBAAsB,GAAG,IAAI,EACnD,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,EACD,gBAAgB,EAAE,WAAW,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,GAAG,EAAE,GACX,OAAO,CAAC,IAAI,CAAC,CA4Kf"}
1
+ {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUvD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAsBtF;AA+DD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,GAAG,sBAAsB,GAAG,IAAI,EACnD,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,EACD,gBAAgB,EAAE,WAAW,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,GAAG,EAAE,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA4Kf"}
@@ -95,7 +95,7 @@ async function handle401Error(selectedProvider, options) {
95
95
  return true; // re-prompt
96
96
  }
97
97
  // ── Main REPL loop ────────────────────────────────────────────────────────────
98
- export async function askLoop(engine, options, selectedProvider, selectedModel, cvApiKey, systemInstruction, tools) {
98
+ export async function askLoop(engine, options, selectedProvider, selectedModel, cvApiKey, systemInstruction, tools, persona) {
99
99
  let sessionTurns = 0;
100
100
  let sessionLimit = null;
101
101
  let currentModel = selectedModel;
@@ -123,7 +123,7 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
123
123
  let userInput;
124
124
  // ── Collect user input ──────────────────────────────────────────────
125
125
  if (isFirstTurn) {
126
- userInput = await readFirstTurnInput();
126
+ userInput = await readFirstTurnInput(persona);
127
127
  }
128
128
  else {
129
129
  const { text, isFastLine } = await readNormalInput(pasteBuffer.length > 0);
@@ -1 +1 @@
1
- {"version":3,"file":"toolRegistry.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/toolRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAyO3C,wBAAgB,QAAQ,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,EAAE,CAoDhG"}
1
+ {"version":3,"file":"toolRegistry.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/toolRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAyO3C,wBAAgB,QAAQ,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,EAAE,CAsDhG"}
@@ -274,6 +274,8 @@ export function getTools(options) {
274
274
  }
275
275
  return tools;
276
276
  }
277
- // Default coding mode: file system + publish tools
278
- return [...ALL_CODING_TOOLS, PublishArticleTool, GenerateDiagramTool, ...ALL_COVERLETTER_TOOLS, ...ALL_INTERVIEW_TOOLS];
277
+ // Default coding mode: file system tools + browser debugging tools
278
+ // Browser tools are included for dev debugging (inspect layout, check errors, verify local server).
279
+ // publish_article is NOT included — agent writes code to disk, not to community posts.
280
+ return [...ALL_CODING_TOOLS, ...ALL_BROWSER_TOOLS];
279
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "careervivid",
3
- "version": "2.1.22",
3
+ "version": "2.1.30",
4
4
  "description": "Official CLI for CareerVivid — AI voice interviews, autonomous job applications, resume editing, and portfolio publishing from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,7 +52,8 @@
52
52
  "ajv": "^6.12.6",
53
53
  "micromatch": "^4.0.8",
54
54
  "ws": ">=7.5.10",
55
- "axios": ">=1.7.4"
55
+ "axios": ">=1.7.4",
56
+ "uuid": ">=14.0.0"
56
57
  },
57
58
  "keywords": [
58
59
  "careervivid",