careervivid 2.1.38 → 2.1.39

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.
Files changed (36) hide show
  1. package/dist/agent/Tool.d.ts +1 -1
  2. package/dist/agent/Tool.d.ts.map +1 -1
  3. package/dist/agent/tools/interview.d.ts.map +1 -1
  4. package/dist/agent/tools/interview.js +5 -2
  5. package/dist/agent/tools/urlVerifier.d.ts +1 -0
  6. package/dist/agent/tools/urlVerifier.d.ts.map +1 -1
  7. package/dist/agent/tools/urlVerifier.js +294 -86
  8. package/dist/api.d.ts +7 -0
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js +4 -0
  11. package/dist/apply/browser.d.ts +5 -0
  12. package/dist/apply/browser.d.ts.map +1 -1
  13. package/dist/apply/browser.js +1 -1
  14. package/dist/commands/agent/configurator.d.ts.map +1 -1
  15. package/dist/commands/agent/configurator.js +14 -0
  16. package/dist/commands/agent/index.d.ts.map +1 -1
  17. package/dist/commands/agent/index.js +3 -0
  18. package/dist/commands/agent/repl/engineLoop.d.ts.map +1 -1
  19. package/dist/commands/agent/repl/engineLoop.js +121 -116
  20. package/dist/commands/agent/repl/input.d.ts +2 -7
  21. package/dist/commands/agent/repl/input.d.ts.map +1 -1
  22. package/dist/commands/agent/repl/input.js +226 -17
  23. package/dist/commands/agent/repl.d.ts +3 -0
  24. package/dist/commands/agent/repl.d.ts.map +1 -1
  25. package/dist/commands/agent/repl.js +28 -1
  26. package/dist/commands/apply.d.ts.map +1 -1
  27. package/dist/commands/apply.js +24 -5
  28. package/dist/commands/auth.d.ts.map +1 -1
  29. package/dist/commands/auth.js +5 -4
  30. package/dist/commands/interview.d.ts.map +1 -1
  31. package/dist/commands/interview.js +76 -27
  32. package/dist/commands/jobs.d.ts.map +1 -1
  33. package/dist/commands/jobs.js +147 -4
  34. package/dist/commands/resumes.js +6 -6
  35. package/dist/index.js +4 -2
  36. package/package.json +1 -1
@@ -34,135 +34,140 @@ export async function runEngineLoop(opts) {
34
34
  color: "cyan",
35
35
  spinner: "dots",
36
36
  }).start();
37
- const onChunk = (text) => {
38
- if (firstChunk) {
37
+ try {
38
+ const onChunk = (text) => {
39
+ if (firstChunk) {
40
+ thinkingSpinner.stop();
41
+ process.stdout.write("\n" + chalk.hex("#6366f1")("✦ "));
42
+ firstChunk = false;
43
+ }
44
+ process.stdout.write(text);
45
+ responseAccumulator += text;
46
+ };
47
+ const onThinking = (thought) => {
48
+ if (verbose) {
49
+ console.log(chalk.dim(`\n[thinking] ${thought.substring(0, 200)}...`));
50
+ }
51
+ };
52
+ const onError = (error) => {
39
53
  thinkingSpinner.stop();
40
- process.stdout.write("\n" + chalk.hex("#6366f1")("✦ "));
41
- firstChunk = false;
42
- }
43
- process.stdout.write(text);
44
- responseAccumulator += text;
45
- };
46
- const onThinking = (thought) => {
47
- if (verbose) {
48
- console.log(chalk.dim(`\n[thinking] ${thought.substring(0, 200)}...`));
49
- }
50
- };
51
- const onError = (error) => {
52
- thinkingSpinner.stop();
53
- if (toolState.currentSpinner) {
54
- toolState.currentSpinner.fail("Tool error");
55
- toolState.currentSpinner = null;
54
+ if (toolState.currentSpinner) {
55
+ toolState.currentSpinner.fail("Tool error");
56
+ toolState.currentSpinner = null;
57
+ }
58
+ console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
59
+ };
60
+ const onCompacting = () => {
61
+ console.log(chalk.dim("\n📦 Compacting context window...\n"));
62
+ };
63
+ const boundOnToolCall = (name, args) => onToolCall(name, args, thinkingSpinner, toolState);
64
+ const boundOnToolResult = (name, result) => onToolResult(name, result, toolState);
65
+ // ── CareerVivid Cloud engine ──────────────────────────────────────────────
66
+ if (engine instanceof CareerVividProxyEngine) {
67
+ await engine.runLoopStreaming(userInput, {
68
+ onChunk,
69
+ onThinking,
70
+ onToolCall: boundOnToolCall,
71
+ onToolResult: boundOnToolResult,
72
+ onCompacting,
73
+ onError,
74
+ onResponse: async (creditInfo) => {
75
+ opts.onCreditInfo(creditInfo.creditsRemaining, creditInfo.monthlyLimit);
76
+ printCreditStatus(creditInfo.creditsRemaining, creditInfo.monthlyLimit);
77
+ },
78
+ onCreditLimitReached: (remaining) => {
79
+ console.log(chalk.red(`\n\n⚠️ Credit limit reached (${remaining} remaining).\n` +
80
+ chalk.dim(" Upgrade or top up at ") +
81
+ chalk.underline.blue("careervivid.app/developer")));
82
+ },
83
+ });
84
+ // ── QueryEngine (direct Gemini) ───────────────────────────────────────────
56
85
  }
57
- console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
58
- };
59
- const onCompacting = () => {
60
- console.log(chalk.dim("\n📦 Compacting context window...\n"));
61
- };
62
- const boundOnToolCall = (name, args) => onToolCall(name, args, thinkingSpinner, toolState);
63
- const boundOnToolResult = (name, result) => onToolResult(name, result, toolState);
64
- // ── CareerVivid Cloud engine ──────────────────────────────────────────────
65
- if (engine instanceof CareerVividProxyEngine) {
66
- await engine.runLoopStreaming(userInput, {
67
- onChunk,
68
- onThinking,
69
- onToolCall: boundOnToolCall,
70
- onToolResult: boundOnToolResult,
71
- onCompacting,
72
- onError,
73
- onResponse: async (creditInfo) => {
74
- opts.onCreditInfo(creditInfo.creditsRemaining, creditInfo.monthlyLimit);
75
- printCreditStatus(creditInfo.creditsRemaining, creditInfo.monthlyLimit);
76
- },
77
- onCreditLimitReached: (remaining) => {
78
- console.log(chalk.red(`\n\n⚠️ Credit limit reached (${remaining} remaining).\n` +
79
- chalk.dim(" Upgrade or top up at ") +
80
- chalk.underline.blue("careervivid.app/developer")));
81
- },
82
- });
83
- // ── QueryEngine (direct Gemini) ───────────────────────────────────────────
84
- }
85
- else if (engine instanceof QueryEngine) {
86
- await engine.runLoopStreaming(userInput, {
87
- onChunk,
88
- onThinking,
89
- onToolCall: boundOnToolCall,
90
- onToolResult: boundOnToolResult,
91
- onCompacting,
92
- onError,
93
- });
94
- // ── BYO Provider (OpenAI / Anthropic / OpenRouter / custom) ──────────────
95
- }
96
- else {
97
- const { createOpenAICompatibleProvider } = await import("../../../agent/providers/OpenAIProvider.js");
98
- const { AnthropicProvider } = await import("../../../agent/providers/AnthropicProvider.js");
99
- const byoApiKey = apiKey || getProviderKey(selectedProvider) || loadConfig().llmApiKey || "";
100
- const resolvedBaseUrl = baseUrl || loadConfig().llmBaseUrl;
101
- let provider;
102
- if (selectedProvider === "anthropic") {
103
- provider = new AnthropicProvider({ apiKey: byoApiKey });
86
+ else if (engine instanceof QueryEngine) {
87
+ await engine.runLoopStreaming(userInput, {
88
+ onChunk,
89
+ onThinking,
90
+ onToolCall: boundOnToolCall,
91
+ onToolResult: boundOnToolResult,
92
+ onCompacting,
93
+ onError,
94
+ });
95
+ // ── BYO Provider (OpenAI / Anthropic / OpenRouter / custom) ──────────────
104
96
  }
105
97
  else {
106
- const sub = selectedProvider === "openrouter" ? "openrouter" :
107
- selectedProvider === "custom" ? "custom" : "openai";
108
- provider = createOpenAICompatibleProvider(sub, byoApiKey, resolvedBaseUrl);
109
- }
110
- let userTurn = { role: "user", parts: [{ text: userInput }] };
111
- let round = 0;
112
- while (round < 10) {
113
- const result = await withTimeout(provider.generate({ model: currentModel, history: byoHistory, userTurn, tools, systemInstruction }), 45_000, "LLM generate()");
114
- if (round === 0) {
115
- thinkingSpinner.stop();
116
- process.stdout.write("\n" + chalk.hex("#6366f1")("✦ "));
117
- firstChunk = false;
98
+ const { createOpenAICompatibleProvider } = await import("../../../agent/providers/OpenAIProvider.js");
99
+ const { AnthropicProvider } = await import("../../../agent/providers/AnthropicProvider.js");
100
+ const byoApiKey = apiKey || getProviderKey(selectedProvider) || loadConfig().llmApiKey || "";
101
+ const resolvedBaseUrl = baseUrl || loadConfig().llmBaseUrl;
102
+ let provider;
103
+ if (selectedProvider === "anthropic") {
104
+ provider = new AnthropicProvider({ apiKey: byoApiKey });
118
105
  }
119
- if (result.text) {
120
- process.stdout.write(result.text);
121
- responseAccumulator += result.text;
106
+ else {
107
+ const sub = selectedProvider === "openrouter" ? "openrouter" :
108
+ selectedProvider === "custom" ? "custom" : "openai";
109
+ provider = createOpenAICompatibleProvider(sub, byoApiKey, resolvedBaseUrl);
122
110
  }
123
- byoHistory.push(userTurn);
124
- byoHistory.push({ role: "model", parts: result.rawParts || [{ text: result.text }] });
125
- if (!result.functionCalls?.length)
126
- break;
127
- const fnResponses = [];
128
- for (const fc of result.functionCalls) {
129
- const allow = await boundOnToolCall(fc.name, fc.args);
130
- if (!allow) {
131
- fnResponses.push({
132
- functionResponse: { id: fc.id, name: fc.name, response: { error: "User denied execution." } },
133
- });
134
- continue;
111
+ let userTurn = { role: "user", parts: [{ text: userInput }] };
112
+ let round = 0;
113
+ while (round < 10) {
114
+ const result = await withTimeout(provider.generate({ model: currentModel, history: byoHistory, userTurn, tools, systemInstruction }), 45_000, "LLM generate()");
115
+ if (round === 0) {
116
+ thinkingSpinner.stop();
117
+ process.stdout.write("\n" + chalk.hex("#6366f1")("✦ "));
118
+ firstChunk = false;
135
119
  }
136
- const tool = tools.find((t) => t.name === fc.name);
137
- let out;
138
- try {
139
- if (fc.name === "start_interview") {
140
- process.removeListener("SIGINT", handleSigInt);
141
- try {
142
- out = tool ? await tool.execute(fc.args) : { error: "Tool not found" };
120
+ if (result.text) {
121
+ process.stdout.write(result.text);
122
+ responseAccumulator += result.text;
123
+ }
124
+ byoHistory.push(userTurn);
125
+ byoHistory.push({ role: "model", parts: result.rawParts || [{ text: result.text }] });
126
+ if (!result.functionCalls?.length)
127
+ break;
128
+ const fnResponses = [];
129
+ for (const fc of result.functionCalls) {
130
+ const allow = await boundOnToolCall(fc.name, fc.args);
131
+ if (!allow) {
132
+ fnResponses.push({
133
+ functionResponse: { id: fc.id, name: fc.name, response: { error: "User denied execution." } },
134
+ });
135
+ continue;
136
+ }
137
+ const tool = tools.find((t) => t.name === fc.name);
138
+ let out;
139
+ try {
140
+ if (fc.name === "start_interview") {
141
+ process.removeListener("SIGINT", handleSigInt);
142
+ try {
143
+ out = tool ? await tool.execute(fc.args, currentModel) : { error: "Tool not found" };
144
+ }
145
+ finally {
146
+ process.on("SIGINT", handleSigInt);
147
+ }
143
148
  }
144
- finally {
145
- process.on("SIGINT", handleSigInt);
149
+ else {
150
+ out = tool
151
+ ? await withTimeout(tool.execute(fc.args, currentModel), 45_000, `tool:${fc.name}`)
152
+ : { error: "Tool not found" };
146
153
  }
147
154
  }
148
- else {
149
- out = tool
150
- ? await withTimeout(tool.execute(fc.args), 45_000, `tool:${fc.name}`)
151
- : { error: "Tool not found" };
155
+ catch (e) {
156
+ out = e.message?.includes("No API key configured")
157
+ ? { error: "CareerVivid API key not found. Run 'cv login' to authenticate." }
158
+ : { error: e.message };
152
159
  }
160
+ boundOnToolResult(fc.name, out);
161
+ fnResponses.push({ functionResponse: { id: fc.id, name: fc.name, response: out } });
153
162
  }
154
- catch (e) {
155
- out = e.message?.includes("No API key configured")
156
- ? { error: "CareerVivid API key not found. Run 'cv login' to authenticate." }
157
- : { error: e.message };
158
- }
159
- boundOnToolResult(fc.name, out);
160
- fnResponses.push({ functionResponse: { id: fc.id, name: fc.name, response: out } });
163
+ userTurn = { role: "user", parts: fnResponses };
164
+ round++;
161
165
  }
162
- userTurn = { role: "user", parts: fnResponses };
163
- round++;
164
166
  }
167
+ process.stdout.write("\n" + chalk.dim("─".repeat(48)) + "\n");
168
+ return responseAccumulator;
169
+ }
170
+ finally {
171
+ thinkingSpinner.stop();
165
172
  }
166
- process.stdout.write("\n" + chalk.dim("─".repeat(48)) + "\n");
167
- return responseAccumulator;
168
173
  }
@@ -2,8 +2,8 @@
2
2
  * repl/input.ts
3
3
  *
4
4
  * Handles all user input collection:
5
- * - First-turn quick-action menu (autocomplete) — persona-aware
6
- * - Subsequent turn free-text input
5
+ * - First-turn quick-action menu (autocomplete) — persona-aware with mouse click support & digit shortcuts
6
+ * - Subsequent turn free-text input with mouse click cursor positioning
7
7
  * - Multi-line paste mode (<<<)
8
8
  * - Fast-paste buffer accumulation
9
9
  */
@@ -11,11 +11,6 @@ import type { PersonaDefinition } from "../personas.js";
11
11
  /**
12
12
  * Read the first-turn menu selection.
13
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
14
  */
20
15
  export declare function readFirstTurnInput(persona: PersonaDefinition): Promise<string>;
21
16
  /** Reads a single multi-line paste block. User ends with an empty Enter. */
@@ -1 +1 @@
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"}
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;AA2OxD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwCpF;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,8 +2,8 @@
2
2
  * repl/input.ts
3
3
  *
4
4
  * Handles all user input collection:
5
- * - First-turn quick-action menu (autocomplete) — persona-aware
6
- * - Subsequent turn free-text input
5
+ * - First-turn quick-action menu (autocomplete) — persona-aware with mouse click support & digit shortcuts
6
+ * - Subsequent turn free-text input with mouse click cursor positioning
7
7
  * - Multi-line paste mode (<<<)
8
8
  * - Fast-paste buffer accumulation
9
9
  */
@@ -11,23 +11,227 @@ import chalk from "chalk";
11
11
  import pkg from "enquirer";
12
12
  const { prompt } = pkg;
13
13
  const PICK_UP_LABEL = "🗓️ Pick up where we left off";
14
+ /**
15
+ * ClickableInput
16
+ * Subclasses Enquirer's Input prompt to enable mouse tracking
17
+ * and position the cursor on click.
18
+ */
19
+ class ClickableInput extends pkg.Input {
20
+ originalEmit = null;
21
+ pendingClick = null;
22
+ constructor(options) {
23
+ super(options);
24
+ }
25
+ async run() {
26
+ if (process.stdin.isTTY) {
27
+ this.stdout.write('\x1b[?1000h\x1b[?1006h'); // Enable SGR mouse tracking
28
+ this.originalEmit = process.stdin.emit;
29
+ const self = this;
30
+ process.stdin.emit = function (event, ...args) {
31
+ if (event === 'data' && Buffer.isBuffer(args[0])) {
32
+ let str = args[0].toString();
33
+ let intercepted = false;
34
+ const mouseRegex = /\x1b\[<0;\d+;\d+[Mm]/g;
35
+ if (mouseRegex.test(str)) {
36
+ intercepted = true;
37
+ const firstMatch = str.match(/\x1b\[<0;(\d+);(\d+)([Mm])/);
38
+ if (firstMatch) {
39
+ const col = parseInt(firstMatch[1], 10);
40
+ const row = parseInt(firstMatch[2], 10);
41
+ const isRelease = firstMatch[3] === 'm';
42
+ if (!isRelease) {
43
+ self.pendingClick = { col, row };
44
+ self.stdout.write('\x1b[6n'); // Query cursor position
45
+ }
46
+ }
47
+ str = str.replace(mouseRegex, '');
48
+ }
49
+ const cprRegex = /\x1b\[\d+;\d+R/g;
50
+ if (cprRegex.test(str)) {
51
+ intercepted = true;
52
+ const firstCpr = str.match(/\x1b\[(\d+);(\d+)R/);
53
+ if (firstCpr) {
54
+ const row = parseInt(firstCpr[1], 10);
55
+ const col = parseInt(firstCpr[2], 10);
56
+ self.handleCpr(row, col);
57
+ }
58
+ str = str.replace(cprRegex, '');
59
+ }
60
+ if (intercepted) {
61
+ if (str.length > 0) {
62
+ args[0] = Buffer.from(str);
63
+ }
64
+ else {
65
+ return true; // Consume
66
+ }
67
+ }
68
+ }
69
+ return self.originalEmit.apply(this, [event, ...args]);
70
+ };
71
+ }
72
+ try {
73
+ return await super.run();
74
+ }
75
+ finally {
76
+ if (process.stdin.isTTY) {
77
+ this.stdout.write('\x1b[?1000l\x1b[?1006l'); // Disable SGR mouse tracking
78
+ if (this.originalEmit) {
79
+ process.stdin.emit = this.originalEmit;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ handleCpr(cursorRow, cursorCol) {
85
+ if (!this.pendingClick)
86
+ return;
87
+ const click = this.pendingClick;
88
+ this.pendingClick = null;
89
+ // Extract raw prompt text without ANSI colors
90
+ const promptText = (this.state.prompt || "").replace(/\x1b\[[0-9;]*m/g, "");
91
+ const promptWidth = promptText.length;
92
+ const totalWidth = this.width || 80;
93
+ const lineCount = Math.ceil((promptWidth + this.input.length) / totalWidth) || 1;
94
+ // Check if click row is within the prompt bounds
95
+ if (click.row <= cursorRow && click.row >= cursorRow - lineCount + 1) {
96
+ const relativeRow = click.row - (cursorRow - lineCount + 1);
97
+ const L0_chars = totalWidth - promptWidth;
98
+ let clickedIndex = 0;
99
+ if (relativeRow === 0) {
100
+ clickedIndex = click.col - promptWidth - 1;
101
+ }
102
+ else {
103
+ clickedIndex = L0_chars + (relativeRow - 1) * totalWidth + (click.col - 1);
104
+ }
105
+ // Clamp index
106
+ clickedIndex = Math.max(0, Math.min(this.input.length, clickedIndex));
107
+ this.cursor = clickedIndex;
108
+ this.render();
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * ClickableAutocomplete
114
+ * Subclasses Enquirer's Autocomplete prompt to enable mouse tracking,
115
+ * mouse-click selection, and keyboard digit shortcuts.
116
+ */
117
+ class ClickableAutocomplete extends pkg.AutoComplete {
118
+ originalEmit = null;
119
+ pendingClick = null;
120
+ menuSize;
121
+ constructor(options) {
122
+ super(options);
123
+ this.menuSize = options.choices ? options.choices.length : 0;
124
+ }
125
+ async run() {
126
+ if (process.stdin.isTTY) {
127
+ this.stdout.write('\x1b[?1000h\x1b[?1006h'); // Enable SGR mouse tracking
128
+ this.originalEmit = process.stdin.emit;
129
+ const self = this;
130
+ process.stdin.emit = function (event, ...args) {
131
+ if (event === 'data' && Buffer.isBuffer(args[0])) {
132
+ let str = args[0].toString();
133
+ let intercepted = false;
134
+ const mouseRegex = /\x1b\[<0;\d+;\d+[Mm]/g;
135
+ if (mouseRegex.test(str)) {
136
+ intercepted = true;
137
+ const firstMatch = str.match(/\x1b\[<0;(\d+);(\d+)([Mm])/);
138
+ if (firstMatch) {
139
+ const col = parseInt(firstMatch[1], 10);
140
+ const row = parseInt(firstMatch[2], 10);
141
+ const isRelease = firstMatch[3] === 'm';
142
+ if (!isRelease) {
143
+ self.pendingClick = { col, row };
144
+ self.stdout.write('\x1b[6n'); // Query cursor position
145
+ }
146
+ }
147
+ str = str.replace(mouseRegex, '');
148
+ }
149
+ const cprRegex = /\x1b\[\d+;\d+R/g;
150
+ if (cprRegex.test(str)) {
151
+ intercepted = true;
152
+ const firstCpr = str.match(/\x1b\[(\d+);(\d+)R/);
153
+ if (firstCpr) {
154
+ const row = parseInt(firstCpr[1], 10);
155
+ const col = parseInt(firstCpr[2], 10);
156
+ self.handleCpr(row, col);
157
+ }
158
+ str = str.replace(cprRegex, '');
159
+ }
160
+ if (intercepted) {
161
+ if (str.length > 0) {
162
+ args[0] = Buffer.from(str);
163
+ }
164
+ else {
165
+ return true; // Consume
166
+ }
167
+ }
168
+ }
169
+ return self.originalEmit.apply(this, [event, ...args]);
170
+ };
171
+ }
172
+ try {
173
+ return await super.run();
174
+ }
175
+ finally {
176
+ if (process.stdin.isTTY) {
177
+ this.stdout.write('\x1b[?1000l\x1b[?1006l'); // Disable SGR mouse tracking
178
+ if (this.originalEmit) {
179
+ process.stdin.emit = this.originalEmit;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ async keypress(char, key) {
185
+ // Digit shortcut selection: immediately select and submit
186
+ const isDigitChar = char >= '1' && char <= String(this.choices.length);
187
+ const isDigitKey = key && key.name >= '1' && key.name <= String(this.choices.length);
188
+ if (isDigitChar || isDigitKey) {
189
+ const digitStr = isDigitChar ? char : key.name;
190
+ const idx = parseInt(digitStr, 10) - 1;
191
+ if (idx >= 0 && idx < this.choices.length) {
192
+ this.index = idx;
193
+ return this.submit();
194
+ }
195
+ }
196
+ return super.keypress(char, key);
197
+ }
198
+ handleCpr(cursorRow, cursorCol) {
199
+ if (!this.pendingClick)
200
+ return;
201
+ const click = this.pendingClick;
202
+ this.pendingClick = null;
203
+ // 1. Click is below the prompt (interactive choices list)
204
+ const relativeInteractiveIndex = click.row - cursorRow - 1;
205
+ if (relativeInteractiveIndex >= 0 && relativeInteractiveIndex < this.visible.length) {
206
+ const visibleChoice = this.visible[relativeInteractiveIndex];
207
+ const actualIndex = this.choices.indexOf(visibleChoice);
208
+ if (actualIndex !== -1) {
209
+ this.index = actualIndex;
210
+ this.submit();
211
+ }
212
+ return;
213
+ }
214
+ // 2. Click is above the prompt (static printed menu items)
215
+ const N = this.menuSize;
216
+ const staticStartIndex = cursorRow - 1 - N;
217
+ const relativeStaticIndex = click.row - staticStartIndex;
218
+ if (relativeStaticIndex >= 0 && relativeStaticIndex < N) {
219
+ this.index = relativeStaticIndex;
220
+ this.submit();
221
+ }
222
+ }
223
+ }
14
224
  /**
15
225
  * Read the first-turn menu selection.
16
226
  * 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
227
  */
23
228
  export async function readFirstTurnInput(persona) {
24
229
  console.log(chalk.dim(" What would you like to do today?\n"));
25
- for (const item of persona.menuItems) {
26
- console.log(chalk.dim(` ${item}`));
230
+ for (let i = 0; i < persona.menuItems.length; i++) {
231
+ console.log(chalk.dim(` ${i + 1}. ${persona.menuItems[i]}`));
27
232
  }
28
233
  console.log("");
29
- const firstResp = await prompt({
30
- type: "autocomplete",
234
+ const promptInstance = new ClickableAutocomplete({
31
235
  name: "choice",
32
236
  message: chalk.bold.hex("#6366f1")("❯") + chalk.dim(" ·"),
33
237
  limit: persona.menuItems.length,
@@ -36,10 +240,15 @@ export async function readFirstTurnInput(persona) {
36
240
  return choices;
37
241
  return choices.filter((c) => c.value.toLowerCase().includes(input.toLowerCase()));
38
242
  },
39
- choices: persona.menuItems.map((item) => ({ name: item, value: item })),
40
- footer: chalk.dim(" ↑↓ to navigate · type to filter · Enter to send"),
243
+ choices: persona.menuItems.map((item, idx) => ({
244
+ name: item, // Returned key matches persona.menuPrompts
245
+ message: `${idx + 1}. ${item}`, // Displayed option has the number prefix
246
+ value: item,
247
+ })),
248
+ footer: chalk.dim(" ↑↓ to navigate · type to filter · Enter or number to select"),
41
249
  });
42
- const raw = firstResp.choice?.trim() ?? "";
250
+ const firstResp = await promptInstance.run();
251
+ const raw = firstResp?.trim() ?? "";
43
252
  // 1. "Pick up where we left off" → persona-specific context-gathering prompt
44
253
  if (raw === PICK_UP_LABEL || raw.toLowerCase().includes("pick up")) {
45
254
  return persona.pickUpPrompt;
@@ -75,12 +284,12 @@ export async function readMultiLineInput(prefix) {
75
284
  /** Read a normal subsequent-turn line. Returns the raw text and timing flag. */
76
285
  export async function readNormalInput(showContinuation) {
77
286
  const t0 = Date.now();
78
- const response = await prompt({
79
- type: "input",
287
+ const promptInstance = new ClickableInput({
80
288
  name: "query",
81
289
  message: showContinuation
82
290
  ? chalk.dim("... ")
83
291
  : chalk.bold.hex("#6366f1")("❯") + chalk.dim(" ·"),
84
292
  });
85
- return { text: response.query, isFastLine: Date.now() - t0 < 150 };
293
+ const query = await promptInstance.run();
294
+ return { text: query, isFastLine: Date.now() - t0 < 150 };
86
295
  }
@@ -23,5 +23,8 @@ export declare function askLoop(engine: QueryEngine | CareerVividProxyEngine | n
23
23
  jobs?: boolean;
24
24
  resume?: boolean;
25
25
  coding?: boolean;
26
+ prompt?: string;
27
+ once?: boolean;
28
+ menu?: boolean;
26
29
  }, selectedProvider: LLMProvider, selectedModel: string, cvApiKey: string | undefined, systemInstruction: string, tools: any[], persona: PersonaDefinition): Promise<void>;
27
30
  //# 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;AAInD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUvD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAoBtF;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"}
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,QAoBtF;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;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,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,CAyMf"}
@@ -121,11 +121,24 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
121
121
  let userInput;
122
122
  // ── Collect user input ──────────────────────────────────────────────
123
123
  if (isFirstTurn) {
124
- userInput = await readFirstTurnInput(persona);
124
+ if (options.prompt) {
125
+ userInput = options.prompt;
126
+ }
127
+ else if (options.menu === false) {
128
+ const { text } = await readNormalInput(false);
129
+ userInput = text;
130
+ }
131
+ else {
132
+ userInput = await readFirstTurnInput(persona);
133
+ }
125
134
  }
126
135
  else {
127
136
  const { text, isFastLine } = await readNormalInput(pasteBuffer.length > 0);
128
137
  userInput = text;
138
+ const cleanLower = userInput.trim().toLowerCase();
139
+ if (cleanLower === "hi" || cleanLower === "hey" || cleanLower === "hello" || cleanLower === "menu" || cleanLower === "start") {
140
+ userInput = await readFirstTurnInput(persona);
141
+ }
129
142
  if (userInput.trim() === "<<<" || userInput.trim().toLowerCase().startsWith("<<<")) {
130
143
  const prefix = userInput.trim().slice(3).trim();
131
144
  userInput = await readMultiLineInput(prefix);
@@ -225,6 +238,20 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
225
238
  speakText(response).catch(() => { });
226
239
  }
227
240
  }
241
+ if (options.once) {
242
+ const proxyEngine = currentEngine instanceof CareerVividProxyEngine ? currentEngine : null;
243
+ if (proxyEngine && sessionTurns > 0) {
244
+ console.log(chalk.dim("\n─────────────────────────────────────────"));
245
+ console.log(chalk.dim(`Session: ${sessionTurns} turn${sessionTurns !== 1 ? "s" : ""} · `) +
246
+ chalk.yellow(`${proxyEngine.sessionUsed.toFixed(1)} credits used`));
247
+ if (proxyEngine.remaining !== null) {
248
+ console.log(chalk.dim(`Remaining: ${proxyEngine.remaining} credits`));
249
+ }
250
+ console.log(chalk.dim("─────────────────────────────────────────"));
251
+ }
252
+ await writeSessionSummary({ turns: sessionTurns, mutations: sessionMutations, toolCalls: 0 });
253
+ process.exit(0);
254
+ }
228
255
  return ask();
229
256
  }
230
257
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyLpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA8PpD"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0LpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAiRpD"}