careervivid 2.1.36 → 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.
- package/dist/agent/Tool.d.ts +1 -1
- package/dist/agent/Tool.d.ts.map +1 -1
- package/dist/agent/tools/interview.d.ts.map +1 -1
- package/dist/agent/tools/interview.js +5 -2
- package/dist/agent/tools/urlVerifier.d.ts +1 -0
- package/dist/agent/tools/urlVerifier.d.ts.map +1 -1
- package/dist/agent/tools/urlVerifier.js +294 -86
- package/dist/api.d.ts +7 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -0
- package/dist/apply/browser.d.ts +5 -0
- package/dist/apply/browser.d.ts.map +1 -1
- package/dist/apply/browser.js +1 -1
- package/dist/commands/agent/configurator.d.ts.map +1 -1
- package/dist/commands/agent/configurator.js +14 -0
- package/dist/commands/agent/index.d.ts.map +1 -1
- package/dist/commands/agent/index.js +3 -0
- package/dist/commands/agent/repl/engineLoop.d.ts.map +1 -1
- package/dist/commands/agent/repl/engineLoop.js +121 -116
- package/dist/commands/agent/repl/input.d.ts +2 -7
- package/dist/commands/agent/repl/input.d.ts.map +1 -1
- package/dist/commands/agent/repl/input.js +226 -17
- package/dist/commands/agent/repl.d.ts +3 -0
- package/dist/commands/agent/repl.d.ts.map +1 -1
- package/dist/commands/agent/repl.js +28 -1
- package/dist/commands/apply.d.ts.map +1 -1
- package/dist/commands/apply.js +24 -5
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +5 -4
- package/dist/commands/interview.d.ts.map +1 -1
- package/dist/commands/interview.js +101 -31
- package/dist/commands/jobs.d.ts.map +1 -1
- package/dist/commands/jobs.js +147 -4
- package/dist/commands/resumes.js +6 -6
- package/dist/index.js +4 -2
- 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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
else {
|
|
107
|
+
const sub = selectedProvider === "openrouter" ? "openrouter" :
|
|
108
|
+
selectedProvider === "custom" ? "custom" : "openai";
|
|
109
|
+
provider = createOpenAICompatibleProvider(sub, byoApiKey, resolvedBaseUrl);
|
|
122
110
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
149
|
-
out =
|
|
150
|
-
?
|
|
151
|
-
: { error:
|
|
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
|
-
|
|
155
|
-
|
|
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;
|
|
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 (
|
|
26
|
-
console.log(chalk.dim(` ${
|
|
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
|
|
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) => ({
|
|
40
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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"}
|