autohand-cli 0.7.4 → 0.7.6
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/LICENSE +0 -43
- package/README.md +3 -1
- package/dist/SessionManager-M5ZLCLCW.cjs +9 -0
- package/dist/{SessionManager-IMW2HGR3.js → SessionManager-XDBEQUPG.js} +1 -1
- package/dist/chunk-2W3QTBNG.cjs +151 -0
- package/dist/{chunk-546JQ3ND.js → chunk-4KZCGK7D.js} +4 -101
- package/dist/chunk-5MCDN53U.js +102 -0
- package/dist/{chunk-UPR5PKX4.cjs → chunk-67NJKV5A.cjs} +5 -1
- package/dist/chunk-723DZKBU.js +613 -0
- package/dist/chunk-JYTXG6OV.cjs +102 -0
- package/dist/chunk-K6NBYSME.cjs +613 -0
- package/dist/{chunk-XJZYEURA.cjs → chunk-KJ67C72C.cjs} +6 -103
- package/dist/chunk-R5KNHJ27.js +151 -0
- package/dist/{chunk-2FLBGPE3.js → chunk-VO3JKFUH.js} +5 -1
- package/dist/index.cjs +1979 -585
- package/dist/index.js +1941 -547
- package/dist/resume-ANISKRWL.cjs +9 -0
- package/dist/{resume-2NERFSTD.js → resume-CWYAK6XR.js} +2 -1
- package/dist/share-3PSV53CQ.js +10 -0
- package/dist/share-4ACH6626.cjs +10 -0
- package/dist/status-4U5CPUVT.cjs +9 -0
- package/dist/{status-UT4UQN3H.js → status-GPAZ67ZZ.js} +2 -1
- package/package.json +3 -2
- package/dist/SessionManager-VZNWGX4O.cjs +0 -9
- package/dist/chunk-55DQY6B5.js +0 -49
- package/dist/chunk-RYY5I7QN.cjs +0 -49
- package/dist/resume-OYZMJRNO.cjs +0 -8
- package/dist/status-U5NH6SYY.cjs +0 -8
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PermissionManager
|
|
4
4
|
} from "./chunk-YMP7AGNT.js";
|
|
5
|
+
import "./chunk-27ISZOFA.js";
|
|
5
6
|
import {
|
|
6
7
|
SessionManager
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VO3JKFUH.js";
|
|
8
9
|
import {
|
|
9
10
|
MemoryManager
|
|
10
11
|
} from "./chunk-4L5WYXHN.js";
|
|
@@ -28,7 +29,9 @@ import {
|
|
|
28
29
|
import {
|
|
29
30
|
metadata as metadata26
|
|
30
31
|
} from "./chunk-CXZEPTRI.js";
|
|
31
|
-
import
|
|
32
|
+
import {
|
|
33
|
+
metadata as metadata27
|
|
34
|
+
} from "./chunk-723DZKBU.js";
|
|
32
35
|
import {
|
|
33
36
|
metadata as metadata15
|
|
34
37
|
} from "./chunk-2NUX2RAI.js";
|
|
@@ -39,9 +42,11 @@ import {
|
|
|
39
42
|
metadata as metadata17
|
|
40
43
|
} from "./chunk-OC5YDNFC.js";
|
|
41
44
|
import {
|
|
42
|
-
metadata as metadata18
|
|
45
|
+
metadata as metadata18
|
|
46
|
+
} from "./chunk-4KZCGK7D.js";
|
|
47
|
+
import {
|
|
43
48
|
package_default
|
|
44
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-5MCDN53U.js";
|
|
45
50
|
import {
|
|
46
51
|
metadata as metadata19
|
|
47
52
|
} from "./chunk-2FSQPRPJ.js";
|
|
@@ -93,12 +98,6 @@ import {
|
|
|
93
98
|
applyFormatter,
|
|
94
99
|
metadata as metadata14
|
|
95
100
|
} from "./chunk-XDVG3NM4.js";
|
|
96
|
-
import {
|
|
97
|
-
AUTOHAND_FILES,
|
|
98
|
-
AUTOHAND_HOME,
|
|
99
|
-
AUTOHAND_PATHS,
|
|
100
|
-
PROJECT_DIR_NAME
|
|
101
|
-
} from "./chunk-FUEL6BK7.js";
|
|
102
101
|
import {
|
|
103
102
|
metadata
|
|
104
103
|
} from "./chunk-KZ7VMQTC.js";
|
|
@@ -114,7 +113,13 @@ import {
|
|
|
114
113
|
} from "./chunk-Z3Q2AFDS.js";
|
|
115
114
|
import {
|
|
116
115
|
metadata as metadata5
|
|
117
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-R5KNHJ27.js";
|
|
117
|
+
import {
|
|
118
|
+
AUTOHAND_FILES,
|
|
119
|
+
AUTOHAND_HOME,
|
|
120
|
+
AUTOHAND_PATHS,
|
|
121
|
+
PROJECT_DIR_NAME
|
|
122
|
+
} from "./chunk-FUEL6BK7.js";
|
|
118
123
|
import {
|
|
119
124
|
metadata as metadata6
|
|
120
125
|
} from "./chunk-QJ53OSGF.js";
|
|
@@ -122,8 +127,7 @@ import {
|
|
|
122
127
|
// src/index.ts
|
|
123
128
|
import "dotenv/config";
|
|
124
129
|
import { Command } from "commander";
|
|
125
|
-
import
|
|
126
|
-
import enquirer4 from "enquirer";
|
|
130
|
+
import chalk17 from "chalk";
|
|
127
131
|
import { execSync as execSync3 } from "child_process";
|
|
128
132
|
|
|
129
133
|
// src/startup/checks.ts
|
|
@@ -228,21 +232,67 @@ async function checkWorkspaceWritable(workspaceRoot) {
|
|
|
228
232
|
};
|
|
229
233
|
}
|
|
230
234
|
}
|
|
231
|
-
function
|
|
235
|
+
function isEmptyDirectory(dir) {
|
|
236
|
+
try {
|
|
237
|
+
const entries = fs.readdirSync(dir);
|
|
238
|
+
const significantEntries = entries.filter((e) => !e.startsWith(".") || e === ".git");
|
|
239
|
+
return significantEntries.length === 0;
|
|
240
|
+
} catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function getGitBranch(workspaceRoot) {
|
|
232
245
|
try {
|
|
233
246
|
const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
234
247
|
cwd: workspaceRoot,
|
|
235
248
|
encoding: "utf8",
|
|
236
249
|
timeout: 5e3
|
|
237
250
|
});
|
|
238
|
-
if (result.status === 0) {
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
251
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
252
|
+
return result.stdout.trim();
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const result = spawnSync("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
258
|
+
cwd: workspaceRoot,
|
|
259
|
+
encoding: "utf8",
|
|
260
|
+
timeout: 5e3
|
|
261
|
+
});
|
|
262
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
263
|
+
return result.stdout.trim();
|
|
243
264
|
}
|
|
244
265
|
} catch {
|
|
245
266
|
}
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
function checkGitRepo(workspaceRoot) {
|
|
270
|
+
const gitDirExists = fs.existsSync(`${workspaceRoot}/.git`);
|
|
271
|
+
if (gitDirExists) {
|
|
272
|
+
const branch = getGitBranch(workspaceRoot);
|
|
273
|
+
return {
|
|
274
|
+
isGitRepo: true,
|
|
275
|
+
branch
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (isEmptyDirectory(workspaceRoot)) {
|
|
279
|
+
try {
|
|
280
|
+
const initResult = spawnSync("git", ["init"], {
|
|
281
|
+
cwd: workspaceRoot,
|
|
282
|
+
encoding: "utf8",
|
|
283
|
+
timeout: 5e3
|
|
284
|
+
});
|
|
285
|
+
if (initResult.status === 0) {
|
|
286
|
+
const branch = getGitBranch(workspaceRoot) || "main";
|
|
287
|
+
return {
|
|
288
|
+
isGitRepo: true,
|
|
289
|
+
branch,
|
|
290
|
+
initialized: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
}
|
|
246
296
|
return { isGitRepo: false };
|
|
247
297
|
}
|
|
248
298
|
function getPackageManagerHint() {
|
|
@@ -285,6 +335,7 @@ async function runStartupChecks(workspaceRoot) {
|
|
|
285
335
|
writable: workspaceCheck.writable,
|
|
286
336
|
isGitRepo: gitCheck.isGitRepo,
|
|
287
337
|
branch: gitCheck.branch,
|
|
338
|
+
initialized: gitCheck.initialized,
|
|
288
339
|
error: workspaceCheck.error
|
|
289
340
|
},
|
|
290
341
|
allRequiredMet,
|
|
@@ -294,6 +345,10 @@ async function runStartupChecks(workspaceRoot) {
|
|
|
294
345
|
function printStartupCheckResults(results, verbose = false) {
|
|
295
346
|
const missingRequired = results.tools.filter((t) => t.required && !t.installed);
|
|
296
347
|
const missingOptional = results.tools.filter((t) => !t.required && !t.installed);
|
|
348
|
+
if (results.workspace.initialized) {
|
|
349
|
+
console.log(chalk.green("\u2713 Initialized git repository"));
|
|
350
|
+
console.log();
|
|
351
|
+
}
|
|
297
352
|
if (missingRequired.length === 0 && !verbose) {
|
|
298
353
|
return;
|
|
299
354
|
}
|
|
@@ -1778,6 +1833,26 @@ var OpenRouterClient = class {
|
|
|
1778
1833
|
payload.tool_choice = request.toolChoice;
|
|
1779
1834
|
}
|
|
1780
1835
|
}
|
|
1836
|
+
const model = (request.model ?? this.defaultModel).toLowerCase();
|
|
1837
|
+
if (request.thinkingLevel && request.thinkingLevel !== "normal") {
|
|
1838
|
+
if (model.includes("o1") || model.includes("o3")) {
|
|
1839
|
+
if (request.thinkingLevel === "extended") {
|
|
1840
|
+
payload.reasoning_effort = "high";
|
|
1841
|
+
} else if (request.thinkingLevel === "none") {
|
|
1842
|
+
payload.reasoning_effort = "low";
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
if (model.includes("claude") && request.thinkingLevel === "extended") {
|
|
1846
|
+
payload.provider = {
|
|
1847
|
+
anthropic: {
|
|
1848
|
+
thinking: {
|
|
1849
|
+
type: "enabled",
|
|
1850
|
+
budget_tokens: 1e4
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1781
1856
|
const headers = {
|
|
1782
1857
|
"Content-Type": "application/json",
|
|
1783
1858
|
"HTTP-Referer": "https://github.com/autohandai/code-cli",
|
|
@@ -2197,14 +2272,13 @@ var ProviderFactory = class {
|
|
|
2197
2272
|
};
|
|
2198
2273
|
|
|
2199
2274
|
// src/core/agent.ts
|
|
2200
|
-
import
|
|
2275
|
+
import chalk15 from "chalk";
|
|
2201
2276
|
import fs19 from "fs-extra";
|
|
2202
2277
|
import path18 from "path";
|
|
2203
2278
|
import { randomUUID } from "crypto";
|
|
2204
2279
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
2205
2280
|
import ora from "ora";
|
|
2206
|
-
import
|
|
2207
|
-
import readline4 from "readline";
|
|
2281
|
+
import enquirer4 from "enquirer";
|
|
2208
2282
|
|
|
2209
2283
|
// src/ui/inputPrompt.ts
|
|
2210
2284
|
import chalk5 from "chalk";
|
|
@@ -2296,7 +2370,7 @@ var MentionPreview = class {
|
|
|
2296
2370
|
this.disposed = false;
|
|
2297
2371
|
this.lastSuggestions = [];
|
|
2298
2372
|
const input = rl.input;
|
|
2299
|
-
|
|
2373
|
+
safeEmitKeypressEvents(input);
|
|
2300
2374
|
this.statusLine = statusLine ? chalk3.gray(statusLine) : void 0;
|
|
2301
2375
|
this.keypressHandler = this.handleKeypress.bind(this);
|
|
2302
2376
|
input.prependListener("keypress", this.keypressHandler);
|
|
@@ -2664,6 +2738,13 @@ function drawInputBox(prompt, width) {
|
|
|
2664
2738
|
// src/ui/inputPrompt.ts
|
|
2665
2739
|
var NEWLINE_MARKER = " \u21B5 ";
|
|
2666
2740
|
var MAX_NEWLINES = 2;
|
|
2741
|
+
var instrumentedStreams = /* @__PURE__ */ new WeakSet();
|
|
2742
|
+
function safeEmitKeypressEvents(stream) {
|
|
2743
|
+
if (!instrumentedStreams.has(stream)) {
|
|
2744
|
+
readline2.emitKeypressEvents(stream);
|
|
2745
|
+
instrumentedStreams.add(stream);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2667
2748
|
function countNewlineMarkers(text) {
|
|
2668
2749
|
return (text.match(new RegExp(NEWLINE_MARKER, "g")) || []).length;
|
|
2669
2750
|
}
|
|
@@ -2677,6 +2758,7 @@ async function readInstruction(files, slashCommands, statusLine, io = {}, onImag
|
|
|
2677
2758
|
}, 1e4);
|
|
2678
2759
|
try {
|
|
2679
2760
|
while (true) {
|
|
2761
|
+
await new Promise((resolve) => process.nextTick(resolve));
|
|
2680
2762
|
const result = await promptOnce({
|
|
2681
2763
|
files,
|
|
2682
2764
|
slashCommands,
|
|
@@ -2695,8 +2777,11 @@ async function readInstruction(files, slashCommands, statusLine, io = {}, onImag
|
|
|
2695
2777
|
}
|
|
2696
2778
|
}
|
|
2697
2779
|
function createReadline(stdInput, stdOutput) {
|
|
2698
|
-
|
|
2780
|
+
stdOutput.write("\r");
|
|
2781
|
+
safeEmitKeypressEvents(stdInput);
|
|
2782
|
+
try {
|
|
2699
2783
|
stdInput.resume();
|
|
2784
|
+
} catch {
|
|
2700
2785
|
}
|
|
2701
2786
|
const rl = readline2.createInterface({
|
|
2702
2787
|
input: stdInput,
|
|
@@ -2893,6 +2978,9 @@ ${chalk5.gray("Press Ctrl+C again to exit.")}
|
|
|
2893
2978
|
input.on("keypress", handleKeypress);
|
|
2894
2979
|
rl.setPrompt(`${chalk5.gray("\u203A")} `);
|
|
2895
2980
|
rl.prompt(true);
|
|
2981
|
+
process.nextTick(() => {
|
|
2982
|
+
stdOutput.write(`\r${chalk5.gray("\u203A")} `);
|
|
2983
|
+
});
|
|
2896
2984
|
rl.on("line", (value) => {
|
|
2897
2985
|
let finalValue = convertNewlineMarkersToNewlines(value).trim();
|
|
2898
2986
|
finalValue = processImagesInText(finalValue);
|
|
@@ -3964,7 +4052,8 @@ var SLASH_COMMANDS = [
|
|
|
3964
4052
|
installMetadata,
|
|
3965
4053
|
metadata24,
|
|
3966
4054
|
metadata25,
|
|
3967
|
-
metadata26
|
|
4055
|
+
metadata26,
|
|
4056
|
+
metadata27
|
|
3968
4057
|
];
|
|
3969
4058
|
|
|
3970
4059
|
// src/core/conversationManager.ts
|
|
@@ -8534,7 +8623,7 @@ var SlashCommandHandler = class {
|
|
|
8534
8623
|
return feedback(this.ctx);
|
|
8535
8624
|
}
|
|
8536
8625
|
case "/resume": {
|
|
8537
|
-
const { resume } = await import("./resume-
|
|
8626
|
+
const { resume } = await import("./resume-CWYAK6XR.js");
|
|
8538
8627
|
return resume({ sessionManager: this.ctx.sessionManager, args });
|
|
8539
8628
|
}
|
|
8540
8629
|
case "/sessions": {
|
|
@@ -8592,8 +8681,21 @@ var SlashCommandHandler = class {
|
|
|
8592
8681
|
});
|
|
8593
8682
|
return null;
|
|
8594
8683
|
}
|
|
8684
|
+
case "/share": {
|
|
8685
|
+
const { execute } = await import("./share-3PSV53CQ.js");
|
|
8686
|
+
await execute(args.join(" "), {
|
|
8687
|
+
sessionManager: this.ctx.sessionManager,
|
|
8688
|
+
currentSession: this.ctx.currentSession,
|
|
8689
|
+
model: this.ctx.model,
|
|
8690
|
+
provider: this.ctx.provider,
|
|
8691
|
+
config: this.ctx.config,
|
|
8692
|
+
getTotalTokensUsed: this.ctx.getTotalTokensUsed,
|
|
8693
|
+
workspaceRoot: this.ctx.workspaceRoot
|
|
8694
|
+
});
|
|
8695
|
+
return null;
|
|
8696
|
+
}
|
|
8595
8697
|
case "/status": {
|
|
8596
|
-
const { status } = await import("./status-
|
|
8698
|
+
const { status } = await import("./status-GPAZ67ZZ.js");
|
|
8597
8699
|
return status(this.ctx);
|
|
8598
8700
|
}
|
|
8599
8701
|
case "/login": {
|
|
@@ -8885,184 +8987,1164 @@ var ProjectManager = class {
|
|
|
8885
8987
|
}
|
|
8886
8988
|
};
|
|
8887
8989
|
|
|
8888
|
-
// src/
|
|
8889
|
-
import
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8990
|
+
// src/onboarding/projectAnalyzer.ts
|
|
8991
|
+
import { pathExists, readJson, readFile } from "fs-extra";
|
|
8992
|
+
import { join as join2 } from "path";
|
|
8993
|
+
var ProjectAnalyzer = class {
|
|
8994
|
+
constructor(workspaceRoot) {
|
|
8995
|
+
this.workspaceRoot = workspaceRoot;
|
|
8996
|
+
}
|
|
8997
|
+
/**
|
|
8998
|
+
* Analyze the workspace and return project information
|
|
8999
|
+
*/
|
|
9000
|
+
async analyze() {
|
|
9001
|
+
const info = {};
|
|
9002
|
+
await this.analyzeNodeProject(info);
|
|
9003
|
+
await this.analyzeRustProject(info);
|
|
9004
|
+
await this.analyzeGoProject(info);
|
|
9005
|
+
await this.analyzePythonProject(info);
|
|
9006
|
+
return info;
|
|
9007
|
+
}
|
|
9008
|
+
/**
|
|
9009
|
+
* Analyze Node.js/JavaScript/TypeScript project
|
|
9010
|
+
*/
|
|
9011
|
+
async analyzeNodeProject(info) {
|
|
9012
|
+
const pkgPath = join2(this.workspaceRoot, "package.json");
|
|
9013
|
+
if (!await pathExists(pkgPath)) {
|
|
9014
|
+
return;
|
|
8904
9015
|
}
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
9016
|
+
try {
|
|
9017
|
+
const pkg = await readJson(pkgPath);
|
|
9018
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
9019
|
+
if (deps.typescript || pkg.devDependencies?.typescript) {
|
|
9020
|
+
info.language = "TypeScript";
|
|
9021
|
+
} else {
|
|
9022
|
+
info.language = "JavaScript";
|
|
9023
|
+
}
|
|
9024
|
+
info.framework = this.detectNodeFramework(deps);
|
|
9025
|
+
info.packageManager = await this.detectNodePackageManager();
|
|
9026
|
+
info.testFramework = this.detectNodeTestFramework(deps);
|
|
9027
|
+
info.linter = this.detectNodeLinter(deps);
|
|
9028
|
+
info.formatter = this.detectNodeFormatter(deps);
|
|
9029
|
+
info.buildTool = this.detectNodeBuildTool(deps);
|
|
9030
|
+
} catch {
|
|
8915
9031
|
}
|
|
8916
9032
|
}
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
if (
|
|
8930
|
-
|
|
9033
|
+
/**
|
|
9034
|
+
* Detect Node.js framework from dependencies
|
|
9035
|
+
*/
|
|
9036
|
+
detectNodeFramework(deps) {
|
|
9037
|
+
if (deps.next) return "Next.js";
|
|
9038
|
+
if (deps.nuxt) return "Nuxt";
|
|
9039
|
+
if (deps["@remix-run/node"] || deps["@remix-run/react"]) return "Remix";
|
|
9040
|
+
if (deps["@angular/core"]) return "Angular";
|
|
9041
|
+
if (deps.svelte) return "Svelte";
|
|
9042
|
+
if (deps.vue) return "Vue";
|
|
9043
|
+
if (deps.react) return "React";
|
|
9044
|
+
if (deps.solid) return "Solid";
|
|
9045
|
+
if (deps["@nestjs/core"]) return "NestJS";
|
|
9046
|
+
if (deps.fastify) return "Fastify";
|
|
9047
|
+
if (deps.hono) return "Hono";
|
|
9048
|
+
if (deps.koa) return "Koa";
|
|
9049
|
+
if (deps.express) return "Express";
|
|
9050
|
+
return void 0;
|
|
9051
|
+
}
|
|
9052
|
+
/**
|
|
9053
|
+
* Detect Node.js package manager from lockfiles
|
|
9054
|
+
*/
|
|
9055
|
+
async detectNodePackageManager() {
|
|
9056
|
+
if (await pathExists(join2(this.workspaceRoot, "bun.lockb")) || await pathExists(join2(this.workspaceRoot, "bun.lock"))) {
|
|
9057
|
+
return "bun";
|
|
8931
9058
|
}
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
if (canDelegate) {
|
|
8935
|
-
this.delegator = new AgentDelegator(llm, actionExecutor, {
|
|
8936
|
-
clientContext: options.clientContext,
|
|
8937
|
-
currentDepth: options.depth,
|
|
8938
|
-
maxDepth: options.maxDepth
|
|
8939
|
-
});
|
|
9059
|
+
if (await pathExists(join2(this.workspaceRoot, "pnpm-lock.yaml"))) {
|
|
9060
|
+
return "pnpm";
|
|
8940
9061
|
}
|
|
8941
|
-
this.
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
}
|
|
8949
|
-
if (action.type === "delegate_parallel" && this.delegator) {
|
|
8950
|
-
return this.delegator.delegateParallel(action.tasks);
|
|
8951
|
-
}
|
|
8952
|
-
return this.actionExecutor.execute(action, context);
|
|
8953
|
-
},
|
|
8954
|
-
confirmApproval: async () => true,
|
|
8955
|
-
// Sub-agents auto-approve (inherit from main agent in future)
|
|
8956
|
-
definitions,
|
|
8957
|
-
clientContext: options.clientContext
|
|
8958
|
-
});
|
|
8959
|
-
const enhancedSystemPrompt = this.buildSystemPrompt(config.systemPrompt, definitions);
|
|
8960
|
-
this.conversation = new ConversationManager();
|
|
8961
|
-
this.conversation.reset(enhancedSystemPrompt);
|
|
9062
|
+
if (await pathExists(join2(this.workspaceRoot, "yarn.lock"))) {
|
|
9063
|
+
return "yarn";
|
|
9064
|
+
}
|
|
9065
|
+
if (await pathExists(join2(this.workspaceRoot, "package-lock.json"))) {
|
|
9066
|
+
return "npm";
|
|
9067
|
+
}
|
|
9068
|
+
return "npm";
|
|
8962
9069
|
}
|
|
8963
9070
|
/**
|
|
8964
|
-
*
|
|
9071
|
+
* Detect Node.js test framework
|
|
8965
9072
|
*/
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
return
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
"",
|
|
8976
|
-
"## Response Format",
|
|
8977
|
-
"Always respond with structured JSON:",
|
|
8978
|
-
"```json",
|
|
8979
|
-
"{",
|
|
8980
|
-
' "thought": "Your reasoning about what to do next",',
|
|
8981
|
-
' "toolCalls": [{"tool": "tool_name", "args": {...}}],',
|
|
8982
|
-
' "finalResponse": "Your final answer when done (omit toolCalls if providing this)"',
|
|
8983
|
-
"}",
|
|
8984
|
-
"```",
|
|
8985
|
-
"",
|
|
8986
|
-
`Depth: ${this.options.depth}/${this.options.maxDepth} ${this.delegator ? "(can delegate further)" : "(max depth reached)"}`
|
|
8987
|
-
].join("\n");
|
|
9073
|
+
detectNodeTestFramework(deps) {
|
|
9074
|
+
if (deps.vitest) return "Vitest";
|
|
9075
|
+
if (deps.jest) return "Jest";
|
|
9076
|
+
if (deps.mocha) return "Mocha";
|
|
9077
|
+
if (deps.ava) return "AVA";
|
|
9078
|
+
if (deps["@playwright/test"]) return "Playwright";
|
|
9079
|
+
if (deps.cypress) return "Cypress";
|
|
9080
|
+
if (deps.puppeteer) return "Puppeteer";
|
|
9081
|
+
return void 0;
|
|
8988
9082
|
}
|
|
8989
9083
|
/**
|
|
8990
|
-
*
|
|
9084
|
+
* Detect Node.js linter
|
|
8991
9085
|
*/
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
return `- ${def.name}(${params}): ${def.description}`;
|
|
8998
|
-
}
|
|
8999
|
-
async run(task) {
|
|
9000
|
-
console.log(chalk9.cyan(`
|
|
9001
|
-
\u{1F916} Sub-agent '${this.name}' starting task... (depth ${this.options.depth}/${this.options.maxDepth})`));
|
|
9002
|
-
this.conversation.addMessage({ role: "user", content: task });
|
|
9003
|
-
const tools = this.toolManager.toFunctionDefinitions();
|
|
9004
|
-
const maxIterations = 10;
|
|
9005
|
-
for (let i = 0; i < maxIterations; i++) {
|
|
9006
|
-
const completion = await this.llm.complete({
|
|
9007
|
-
messages: this.conversation.history(),
|
|
9008
|
-
model: this.config.model,
|
|
9009
|
-
temperature: 0.2,
|
|
9010
|
-
tools: tools.length > 0 ? tools : void 0,
|
|
9011
|
-
toolChoice: tools.length > 0 ? "auto" : void 0
|
|
9012
|
-
});
|
|
9013
|
-
const payload = this.parseResponse(completion);
|
|
9014
|
-
if (completion.toolCalls?.length) {
|
|
9015
|
-
this.conversation.addMessage({
|
|
9016
|
-
role: "assistant",
|
|
9017
|
-
content: completion.content || ""
|
|
9018
|
-
});
|
|
9019
|
-
} else {
|
|
9020
|
-
this.conversation.addMessage({ role: "assistant", content: completion.content });
|
|
9021
|
-
}
|
|
9022
|
-
if (payload.thought) {
|
|
9023
|
-
console.log(chalk9.gray(`[${this.name}] ${payload.thought}`));
|
|
9024
|
-
}
|
|
9025
|
-
if (payload.toolCalls && payload.toolCalls.length > 0) {
|
|
9026
|
-
const results = await this.toolManager.execute(payload.toolCalls);
|
|
9027
|
-
for (let j = 0; j < results.length; j++) {
|
|
9028
|
-
const result = results[j];
|
|
9029
|
-
const toolCall = completion.toolCalls?.[j];
|
|
9030
|
-
const content = result.success ? result.output ?? "(no output)" : result.error ?? "Tool failed";
|
|
9031
|
-
this.conversation.addMessage({
|
|
9032
|
-
role: "tool",
|
|
9033
|
-
name: result.tool,
|
|
9034
|
-
content,
|
|
9035
|
-
tool_call_id: toolCall?.id
|
|
9036
|
-
});
|
|
9037
|
-
if (!result.success) {
|
|
9038
|
-
console.log(chalk9.red(`[${this.name}] Tool ${result.tool} failed: ${content}`));
|
|
9039
|
-
}
|
|
9040
|
-
}
|
|
9041
|
-
continue;
|
|
9042
|
-
}
|
|
9043
|
-
const response = payload.finalResponse ?? payload.response ?? completion.content;
|
|
9044
|
-
console.log(chalk9.cyan(`[${this.name}] Finished.`));
|
|
9045
|
-
return response;
|
|
9046
|
-
}
|
|
9047
|
-
return `[${this.name}] Failed to complete task within ${maxIterations} iterations.`;
|
|
9086
|
+
detectNodeLinter(deps) {
|
|
9087
|
+
if (deps["@biomejs/biome"]) return "Biome";
|
|
9088
|
+
if (deps.eslint) return "ESLint";
|
|
9089
|
+
if (deps.oxlint) return "Oxlint";
|
|
9090
|
+
return void 0;
|
|
9048
9091
|
}
|
|
9049
9092
|
/**
|
|
9050
|
-
*
|
|
9093
|
+
* Detect Node.js formatter
|
|
9051
9094
|
*/
|
|
9052
|
-
|
|
9053
|
-
if (
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
tool: tc.function.name,
|
|
9058
|
-
args: this.safeParseJson(tc.function.arguments)
|
|
9059
|
-
}))
|
|
9060
|
-
};
|
|
9061
|
-
}
|
|
9062
|
-
return this.parsePayload(completion.content);
|
|
9095
|
+
detectNodeFormatter(deps) {
|
|
9096
|
+
if (deps.prettier) return "Prettier";
|
|
9097
|
+
if (deps["@biomejs/biome"]) return "Biome";
|
|
9098
|
+
if (deps.dprint) return "dprint";
|
|
9099
|
+
return void 0;
|
|
9063
9100
|
}
|
|
9064
9101
|
/**
|
|
9065
|
-
*
|
|
9102
|
+
* Detect Node.js build tool
|
|
9103
|
+
*/
|
|
9104
|
+
detectNodeBuildTool(deps) {
|
|
9105
|
+
if (deps.vite) return "Vite";
|
|
9106
|
+
if (deps.tsup) return "tsup";
|
|
9107
|
+
if (deps.esbuild) return "esbuild";
|
|
9108
|
+
if (deps.rollup) return "Rollup";
|
|
9109
|
+
if (deps.webpack) return "Webpack";
|
|
9110
|
+
if (deps.parcel) return "Parcel";
|
|
9111
|
+
if (deps.turbopack || deps.turbo) return "Turbopack";
|
|
9112
|
+
return void 0;
|
|
9113
|
+
}
|
|
9114
|
+
/**
|
|
9115
|
+
* Analyze Rust project
|
|
9116
|
+
*/
|
|
9117
|
+
async analyzeRustProject(info) {
|
|
9118
|
+
const cargoPath = join2(this.workspaceRoot, "Cargo.toml");
|
|
9119
|
+
if (!await pathExists(cargoPath)) {
|
|
9120
|
+
return;
|
|
9121
|
+
}
|
|
9122
|
+
info.language = "Rust";
|
|
9123
|
+
info.packageManager = "cargo";
|
|
9124
|
+
try {
|
|
9125
|
+
const cargoContent = await readFile(cargoPath, "utf-8");
|
|
9126
|
+
if (cargoContent.includes("actix-web")) {
|
|
9127
|
+
info.framework = "Actix";
|
|
9128
|
+
} else if (cargoContent.includes("axum")) {
|
|
9129
|
+
info.framework = "Axum";
|
|
9130
|
+
} else if (cargoContent.includes("rocket")) {
|
|
9131
|
+
info.framework = "Rocket";
|
|
9132
|
+
} else if (cargoContent.includes("warp")) {
|
|
9133
|
+
info.framework = "Warp";
|
|
9134
|
+
} else if (cargoContent.includes("tauri")) {
|
|
9135
|
+
info.framework = "Tauri";
|
|
9136
|
+
}
|
|
9137
|
+
} catch {
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
/**
|
|
9141
|
+
* Analyze Go project
|
|
9142
|
+
*/
|
|
9143
|
+
async analyzeGoProject(info) {
|
|
9144
|
+
const goModPath = join2(this.workspaceRoot, "go.mod");
|
|
9145
|
+
if (!await pathExists(goModPath)) {
|
|
9146
|
+
return;
|
|
9147
|
+
}
|
|
9148
|
+
info.language = "Go";
|
|
9149
|
+
info.packageManager = "go";
|
|
9150
|
+
try {
|
|
9151
|
+
const goModContent = await readFile(goModPath, "utf-8");
|
|
9152
|
+
if (goModContent.includes("github.com/gin-gonic/gin")) {
|
|
9153
|
+
info.framework = "Gin";
|
|
9154
|
+
} else if (goModContent.includes("github.com/gofiber/fiber")) {
|
|
9155
|
+
info.framework = "Fiber";
|
|
9156
|
+
} else if (goModContent.includes("github.com/labstack/echo")) {
|
|
9157
|
+
info.framework = "Echo";
|
|
9158
|
+
} else if (goModContent.includes("github.com/gorilla/mux")) {
|
|
9159
|
+
info.framework = "Gorilla";
|
|
9160
|
+
}
|
|
9161
|
+
} catch {
|
|
9162
|
+
}
|
|
9163
|
+
}
|
|
9164
|
+
/**
|
|
9165
|
+
* Analyze Python project
|
|
9166
|
+
*/
|
|
9167
|
+
async analyzePythonProject(info) {
|
|
9168
|
+
const pyprojectPath = join2(this.workspaceRoot, "pyproject.toml");
|
|
9169
|
+
const requirementsPath = join2(this.workspaceRoot, "requirements.txt");
|
|
9170
|
+
const poetryLockPath = join2(this.workspaceRoot, "poetry.lock");
|
|
9171
|
+
const pipfilePath = join2(this.workspaceRoot, "Pipfile");
|
|
9172
|
+
const hasPyproject = await pathExists(pyprojectPath);
|
|
9173
|
+
const hasRequirements = await pathExists(requirementsPath);
|
|
9174
|
+
if (!hasPyproject && !hasRequirements) {
|
|
9175
|
+
return;
|
|
9176
|
+
}
|
|
9177
|
+
info.language = "Python";
|
|
9178
|
+
if (await pathExists(poetryLockPath)) {
|
|
9179
|
+
info.packageManager = "poetry";
|
|
9180
|
+
} else if (await pathExists(pipfilePath)) {
|
|
9181
|
+
info.packageManager = "pipenv";
|
|
9182
|
+
} else if (hasPyproject) {
|
|
9183
|
+
info.packageManager = "pip";
|
|
9184
|
+
} else {
|
|
9185
|
+
info.packageManager = "pip";
|
|
9186
|
+
}
|
|
9187
|
+
let depsContent = "";
|
|
9188
|
+
try {
|
|
9189
|
+
if (hasRequirements) {
|
|
9190
|
+
depsContent = await readFile(requirementsPath, "utf-8");
|
|
9191
|
+
} else if (hasPyproject) {
|
|
9192
|
+
depsContent = await readFile(pyprojectPath, "utf-8");
|
|
9193
|
+
}
|
|
9194
|
+
if (depsContent.includes("django")) {
|
|
9195
|
+
info.framework = "Django";
|
|
9196
|
+
} else if (depsContent.includes("fastapi")) {
|
|
9197
|
+
info.framework = "FastAPI";
|
|
9198
|
+
} else if (depsContent.includes("flask")) {
|
|
9199
|
+
info.framework = "Flask";
|
|
9200
|
+
} else if (depsContent.includes("starlette")) {
|
|
9201
|
+
info.framework = "Starlette";
|
|
9202
|
+
} else if (depsContent.includes("tornado")) {
|
|
9203
|
+
info.framework = "Tornado";
|
|
9204
|
+
}
|
|
9205
|
+
if (depsContent.includes("pytest")) {
|
|
9206
|
+
info.testFramework = "pytest";
|
|
9207
|
+
} else if (depsContent.includes("unittest")) {
|
|
9208
|
+
info.testFramework = "unittest";
|
|
9209
|
+
} else if (depsContent.includes("nose")) {
|
|
9210
|
+
info.testFramework = "nose";
|
|
9211
|
+
}
|
|
9212
|
+
if (depsContent.includes("ruff")) {
|
|
9213
|
+
info.linter = "Ruff";
|
|
9214
|
+
} else if (depsContent.includes("flake8")) {
|
|
9215
|
+
info.linter = "Flake8";
|
|
9216
|
+
} else if (depsContent.includes("pylint")) {
|
|
9217
|
+
info.linter = "Pylint";
|
|
9218
|
+
}
|
|
9219
|
+
if (depsContent.includes("black")) {
|
|
9220
|
+
info.formatter = "Black";
|
|
9221
|
+
} else if (depsContent.includes("ruff")) {
|
|
9222
|
+
info.formatter = "Ruff";
|
|
9223
|
+
} else if (depsContent.includes("autopep8")) {
|
|
9224
|
+
info.formatter = "autopep8";
|
|
9225
|
+
}
|
|
9226
|
+
} catch {
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
};
|
|
9230
|
+
|
|
9231
|
+
// src/onboarding/agentsGenerator.ts
|
|
9232
|
+
var AgentsGenerator = class {
|
|
9233
|
+
/**
|
|
9234
|
+
* Generate AGENTS.md content
|
|
9235
|
+
*/
|
|
9236
|
+
generateContent(info, options) {
|
|
9237
|
+
const sections = [];
|
|
9238
|
+
sections.push("# AGENTS.md");
|
|
9239
|
+
sections.push("");
|
|
9240
|
+
sections.push("This file helps Autohand understand how to work with this project.");
|
|
9241
|
+
sections.push("");
|
|
9242
|
+
sections.push(this.generateProjectOverview(info));
|
|
9243
|
+
if (info.packageManager) {
|
|
9244
|
+
sections.push(this.generateCommandsSection(info));
|
|
9245
|
+
}
|
|
9246
|
+
if (info.testFramework) {
|
|
9247
|
+
sections.push(this.generateTestingSection(info));
|
|
9248
|
+
}
|
|
9249
|
+
if (info.framework) {
|
|
9250
|
+
const frameworkSection = this.generateFrameworkSection(info);
|
|
9251
|
+
if (frameworkSection) {
|
|
9252
|
+
sections.push(frameworkSection);
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
sections.push(this.generateCodeStyleSection(info));
|
|
9256
|
+
sections.push(this.generateConstraintsSection());
|
|
9257
|
+
if (options?.customSections) {
|
|
9258
|
+
for (const section of options.customSections) {
|
|
9259
|
+
sections.push(`## ${section.title}`);
|
|
9260
|
+
sections.push("");
|
|
9261
|
+
sections.push(section.content);
|
|
9262
|
+
sections.push("");
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
return sections.join("\n");
|
|
9266
|
+
}
|
|
9267
|
+
/**
|
|
9268
|
+
* Generate project overview section
|
|
9269
|
+
*/
|
|
9270
|
+
generateProjectOverview(info) {
|
|
9271
|
+
const lines = [];
|
|
9272
|
+
lines.push("## Project Overview");
|
|
9273
|
+
lines.push("");
|
|
9274
|
+
if (info.language) {
|
|
9275
|
+
lines.push(`- **Language**: ${info.language}`);
|
|
9276
|
+
}
|
|
9277
|
+
if (info.framework) {
|
|
9278
|
+
lines.push(`- **Framework**: ${info.framework}`);
|
|
9279
|
+
}
|
|
9280
|
+
if (info.packageManager) {
|
|
9281
|
+
lines.push(`- **Package Manager**: ${info.packageManager}`);
|
|
9282
|
+
}
|
|
9283
|
+
if (info.testFramework) {
|
|
9284
|
+
lines.push(`- **Test Framework**: ${info.testFramework}`);
|
|
9285
|
+
}
|
|
9286
|
+
if (info.buildTool) {
|
|
9287
|
+
lines.push(`- **Build Tool**: ${info.buildTool}`);
|
|
9288
|
+
}
|
|
9289
|
+
lines.push("");
|
|
9290
|
+
return lines.join("\n");
|
|
9291
|
+
}
|
|
9292
|
+
/**
|
|
9293
|
+
* Generate commands section based on package manager
|
|
9294
|
+
*/
|
|
9295
|
+
generateCommandsSection(info) {
|
|
9296
|
+
const lines = [];
|
|
9297
|
+
lines.push("## Commands");
|
|
9298
|
+
lines.push("");
|
|
9299
|
+
const pm = info.packageManager;
|
|
9300
|
+
if (info.language === "Rust") {
|
|
9301
|
+
lines.push("- **Build**: `cargo build`");
|
|
9302
|
+
lines.push("- **Build (release)**: `cargo build --release`");
|
|
9303
|
+
lines.push("- **Run**: `cargo run`");
|
|
9304
|
+
lines.push("- **Test**: `cargo test`");
|
|
9305
|
+
lines.push("- **Check**: `cargo check`");
|
|
9306
|
+
lines.push("- **Format**: `cargo fmt`");
|
|
9307
|
+
lines.push("- **Lint**: `cargo clippy`");
|
|
9308
|
+
} else if (info.language === "Go") {
|
|
9309
|
+
lines.push("- **Build**: `go build`");
|
|
9310
|
+
lines.push("- **Run**: `go run .`");
|
|
9311
|
+
lines.push("- **Test**: `go test ./...`");
|
|
9312
|
+
lines.push("- **Format**: `go fmt ./...`");
|
|
9313
|
+
lines.push("- **Vet**: `go vet ./...`");
|
|
9314
|
+
} else if (info.language === "Python") {
|
|
9315
|
+
if (pm === "poetry") {
|
|
9316
|
+
lines.push("- **Install**: `poetry install`");
|
|
9317
|
+
lines.push("- **Run**: `poetry run python main.py`");
|
|
9318
|
+
lines.push("- **Add dependency**: `poetry add <package>`");
|
|
9319
|
+
if (info.testFramework) {
|
|
9320
|
+
lines.push(`- **Test**: \`poetry run pytest\``);
|
|
9321
|
+
}
|
|
9322
|
+
} else if (pm === "pipenv") {
|
|
9323
|
+
lines.push("- **Install**: `pipenv install`");
|
|
9324
|
+
lines.push("- **Run**: `pipenv run python main.py`");
|
|
9325
|
+
if (info.testFramework) {
|
|
9326
|
+
lines.push(`- **Test**: \`pipenv run pytest\``);
|
|
9327
|
+
}
|
|
9328
|
+
} else {
|
|
9329
|
+
lines.push("- **Install**: `pip install -r requirements.txt`");
|
|
9330
|
+
lines.push("- **Run**: `python main.py`");
|
|
9331
|
+
if (info.testFramework) {
|
|
9332
|
+
lines.push(`- **Test**: \`pytest\``);
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
} else {
|
|
9336
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
9337
|
+
const install = pm === "npm" ? "npm install" : `${pm} install`;
|
|
9338
|
+
lines.push(`- **Install**: \`${install}\``);
|
|
9339
|
+
lines.push(`- **Dev**: \`${run} dev\``);
|
|
9340
|
+
lines.push(`- **Build**: \`${run} build\``);
|
|
9341
|
+
if (info.testFramework) {
|
|
9342
|
+
lines.push(`- **Test**: \`${run} test\``);
|
|
9343
|
+
}
|
|
9344
|
+
if (info.linter) {
|
|
9345
|
+
lines.push(`- **Lint**: \`${run} lint\``);
|
|
9346
|
+
}
|
|
9347
|
+
if (info.formatter) {
|
|
9348
|
+
lines.push(`- **Format**: \`${run} format\``);
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
9351
|
+
lines.push("");
|
|
9352
|
+
return lines.join("\n");
|
|
9353
|
+
}
|
|
9354
|
+
/**
|
|
9355
|
+
* Generate testing section
|
|
9356
|
+
*/
|
|
9357
|
+
generateTestingSection(info) {
|
|
9358
|
+
const lines = [];
|
|
9359
|
+
lines.push("## Testing");
|
|
9360
|
+
lines.push("");
|
|
9361
|
+
lines.push(`This project uses **${info.testFramework}** for testing.`);
|
|
9362
|
+
lines.push("");
|
|
9363
|
+
lines.push("- Write tests for new features before implementation");
|
|
9364
|
+
lines.push("- Run tests before committing changes");
|
|
9365
|
+
lines.push("- Aim for good test coverage on critical paths");
|
|
9366
|
+
if (info.testFramework === "Vitest" || info.testFramework === "Jest") {
|
|
9367
|
+
lines.push("- Use `describe` and `it` blocks to organize tests");
|
|
9368
|
+
lines.push("- Mock external dependencies when appropriate");
|
|
9369
|
+
} else if (info.testFramework === "pytest") {
|
|
9370
|
+
lines.push("- Use fixtures for shared test setup");
|
|
9371
|
+
lines.push("- Use `pytest.mark` for test categorization");
|
|
9372
|
+
} else if (info.testFramework === "Playwright" || info.testFramework === "Cypress") {
|
|
9373
|
+
lines.push("- Write E2E tests for critical user flows");
|
|
9374
|
+
lines.push("- Keep selectors stable and meaningful");
|
|
9375
|
+
}
|
|
9376
|
+
lines.push("");
|
|
9377
|
+
return lines.join("\n");
|
|
9378
|
+
}
|
|
9379
|
+
/**
|
|
9380
|
+
* Generate framework-specific section
|
|
9381
|
+
*/
|
|
9382
|
+
generateFrameworkSection(info) {
|
|
9383
|
+
const lines = [];
|
|
9384
|
+
switch (info.framework) {
|
|
9385
|
+
case "Next.js":
|
|
9386
|
+
lines.push("## Next.js Guidelines");
|
|
9387
|
+
lines.push("");
|
|
9388
|
+
lines.push("- Use the App Router for new pages (`app/` directory)");
|
|
9389
|
+
lines.push("- Prefer Server Components by default");
|
|
9390
|
+
lines.push('- Use `"use client"` only when needed for interactivity');
|
|
9391
|
+
lines.push("- Keep API routes in `app/api/`");
|
|
9392
|
+
lines.push("- Use `next/image` for optimized images");
|
|
9393
|
+
lines.push("- Use `next/link` for client-side navigation");
|
|
9394
|
+
break;
|
|
9395
|
+
case "React":
|
|
9396
|
+
lines.push("## React Guidelines");
|
|
9397
|
+
lines.push("");
|
|
9398
|
+
lines.push("- Use functional components with hooks");
|
|
9399
|
+
lines.push("- Keep components small and focused");
|
|
9400
|
+
lines.push("- Use custom hooks to share logic");
|
|
9401
|
+
lines.push("- Prefer composition over inheritance");
|
|
9402
|
+
lines.push("- Use TypeScript interfaces for props");
|
|
9403
|
+
break;
|
|
9404
|
+
case "Vue":
|
|
9405
|
+
lines.push("## Vue Guidelines");
|
|
9406
|
+
lines.push("");
|
|
9407
|
+
lines.push("- Use Composition API for new components");
|
|
9408
|
+
lines.push("- Keep components in single-file format (.vue)");
|
|
9409
|
+
lines.push("- Use composables to share logic");
|
|
9410
|
+
break;
|
|
9411
|
+
case "Express":
|
|
9412
|
+
lines.push("## Express Guidelines");
|
|
9413
|
+
lines.push("");
|
|
9414
|
+
lines.push("- Use middleware for cross-cutting concerns");
|
|
9415
|
+
lines.push("- Keep route handlers thin, delegate to services");
|
|
9416
|
+
lines.push("- Use async/await with proper error handling");
|
|
9417
|
+
lines.push("- Validate request input before processing");
|
|
9418
|
+
break;
|
|
9419
|
+
case "FastAPI":
|
|
9420
|
+
lines.push("## FastAPI Guidelines");
|
|
9421
|
+
lines.push("");
|
|
9422
|
+
lines.push("- Use Pydantic models for request/response validation");
|
|
9423
|
+
lines.push("- Use dependency injection for shared resources");
|
|
9424
|
+
lines.push("- Keep endpoints in organized routers");
|
|
9425
|
+
lines.push("- Use async functions for I/O operations");
|
|
9426
|
+
break;
|
|
9427
|
+
case "Django":
|
|
9428
|
+
lines.push("## Django Guidelines");
|
|
9429
|
+
lines.push("");
|
|
9430
|
+
lines.push("- Follow Django project structure conventions");
|
|
9431
|
+
lines.push("- Use class-based views where appropriate");
|
|
9432
|
+
lines.push("- Keep business logic in models or services");
|
|
9433
|
+
lines.push("- Use Django ORM for database operations");
|
|
9434
|
+
break;
|
|
9435
|
+
case "Flask":
|
|
9436
|
+
lines.push("## Flask Guidelines");
|
|
9437
|
+
lines.push("");
|
|
9438
|
+
lines.push("- Use blueprints to organize routes");
|
|
9439
|
+
lines.push("- Keep route handlers focused");
|
|
9440
|
+
lines.push("- Use Flask extensions for common functionality");
|
|
9441
|
+
break;
|
|
9442
|
+
case "NestJS":
|
|
9443
|
+
lines.push("## NestJS Guidelines");
|
|
9444
|
+
lines.push("");
|
|
9445
|
+
lines.push("- Follow module-based architecture");
|
|
9446
|
+
lines.push("- Use dependency injection");
|
|
9447
|
+
lines.push("- Use decorators for metadata");
|
|
9448
|
+
lines.push("- Keep controllers thin, services fat");
|
|
9449
|
+
break;
|
|
9450
|
+
default:
|
|
9451
|
+
return null;
|
|
9452
|
+
}
|
|
9453
|
+
lines.push("");
|
|
9454
|
+
return lines.join("\n");
|
|
9455
|
+
}
|
|
9456
|
+
/**
|
|
9457
|
+
* Generate code style section
|
|
9458
|
+
*/
|
|
9459
|
+
generateCodeStyleSection(info) {
|
|
9460
|
+
const lines = [];
|
|
9461
|
+
lines.push("## Code Style");
|
|
9462
|
+
lines.push("");
|
|
9463
|
+
if (info.language === "TypeScript") {
|
|
9464
|
+
lines.push("- Use strict TypeScript settings");
|
|
9465
|
+
lines.push("- Define types/interfaces for data structures");
|
|
9466
|
+
lines.push("- Avoid `any` type - use `unknown` if type is truly unknown");
|
|
9467
|
+
lines.push("- Use type inference where obvious");
|
|
9468
|
+
} else if (info.language === "Python") {
|
|
9469
|
+
lines.push("- Follow PEP 8 style guidelines");
|
|
9470
|
+
lines.push("- Use type hints for function signatures");
|
|
9471
|
+
lines.push("- Use docstrings for public functions");
|
|
9472
|
+
} else if (info.language === "Rust") {
|
|
9473
|
+
lines.push("- Follow Rust naming conventions (snake_case for functions)");
|
|
9474
|
+
lines.push("- Use descriptive error types");
|
|
9475
|
+
lines.push("- Prefer `Result` over panicking");
|
|
9476
|
+
} else if (info.language === "Go") {
|
|
9477
|
+
lines.push("- Follow Go idioms and conventions");
|
|
9478
|
+
lines.push("- Use short variable names in small scopes");
|
|
9479
|
+
lines.push("- Handle errors explicitly");
|
|
9480
|
+
}
|
|
9481
|
+
lines.push("- Follow existing patterns in the codebase");
|
|
9482
|
+
lines.push("- Use meaningful variable and function names");
|
|
9483
|
+
lines.push("- Add comments for complex logic");
|
|
9484
|
+
lines.push("- Keep functions focused and small");
|
|
9485
|
+
if (info.linter) {
|
|
9486
|
+
lines.push(`- Run **${info.linter}** before committing`);
|
|
9487
|
+
}
|
|
9488
|
+
if (info.formatter) {
|
|
9489
|
+
lines.push(`- Format code with **${info.formatter}**`);
|
|
9490
|
+
}
|
|
9491
|
+
lines.push("");
|
|
9492
|
+
return lines.join("\n");
|
|
9493
|
+
}
|
|
9494
|
+
/**
|
|
9495
|
+
* Generate constraints section
|
|
9496
|
+
*/
|
|
9497
|
+
generateConstraintsSection() {
|
|
9498
|
+
const lines = [];
|
|
9499
|
+
lines.push("## Constraints");
|
|
9500
|
+
lines.push("");
|
|
9501
|
+
lines.push("- Do not modify files outside the project directory");
|
|
9502
|
+
lines.push("- Ask before making breaking changes");
|
|
9503
|
+
lines.push("- Prefer editing existing files over creating new ones");
|
|
9504
|
+
lines.push("- Do not delete files without confirmation");
|
|
9505
|
+
lines.push("- Keep dependencies minimal - avoid adding new ones without good reason");
|
|
9506
|
+
lines.push("- Do not commit sensitive data (API keys, secrets, credentials)");
|
|
9507
|
+
lines.push("");
|
|
9508
|
+
return lines.join("\n");
|
|
9509
|
+
}
|
|
9510
|
+
};
|
|
9511
|
+
|
|
9512
|
+
// src/onboarding/setupWizard.ts
|
|
9513
|
+
import chalk9 from "chalk";
|
|
9514
|
+
import enquirer2 from "enquirer";
|
|
9515
|
+
import { pathExists as pathExists2, writeFile } from "fs-extra";
|
|
9516
|
+
import { join as join3 } from "path";
|
|
9517
|
+
var ASCII_FRIEND = [
|
|
9518
|
+
"\u2880\u2874\u281B\u281B\u283B\u28F7\u2844\u2800\u28E0\u2876\u281F\u281B\u283B\u28F6\u2844\u2880\u28F4\u287E\u281B\u281B\u28BF\u28E6\u2800\u2880\u28F4\u281E\u281B\u281B\u2836\u2840",
|
|
9519
|
+
"\u284E\u2800\u28B0\u28F6\u2846\u2808\u28FF\u28F4\u28FF\u2801\u28F4\u28F6\u2844\u2818\u28FF\u28FE\u284F\u2880\u28F6\u28E6\u2800\u28BB\u2847\u28FF\u2803\u28A0\u28F6\u2846\u2800\u28B9",
|
|
9520
|
+
"\u28A7\u2800\u2818\u281B\u2803\u28A0\u287F\u2819\u28FF\u2840\u2819\u281B\u2803\u28F0\u287F\u28BB\u28E7\u2808\u281B\u281B\u2880\u28FE\u2807\u28BB\u28C6\u2808\u281B\u280B\u2800\u287C",
|
|
9521
|
+
"\u2808\u283B\u28B6\u28F6\u287E\u281F\u2801\u2800\u2818\u283F\u28B6\u28F6\u287E\u281F\u2801\u2800\u2819\u2837\u28F6\u28F6\u283F\u280B\u2800\u2808\u283B\u2837\u28F6\u2876\u281A\u2801",
|
|
9522
|
+
"\u2880\u28F4\u283F\u283F\u2837\u28E6\u2840\u2800\u28E0\u28F6\u283F\u283B\u28B7\u28E6\u2840\u2800\u28E0\u287E\u281F\u283F\u28F6\u28C4\u2800\u2880\u28F4\u287E\u283F\u283F\u28F6\u28C4",
|
|
9523
|
+
"\u287E\u2803\u28A0\u28E4\u2844\u2818\u28FF\u28E0\u28FF\u2801\u28E0\u28E4\u2844\u2839\u28F7\u28FC\u284F\u2880\u28E4\u28E4\u2808\u28BF\u2846\u28FE\u280F\u2880\u28E4\u28C4\u2808\u28BF",
|
|
9524
|
+
"\u28A7\u2840\u2838\u283F\u2807\u2880\u28FF\u283A\u28FF\u2840\u283B\u283F\u2803\u28B0\u28FF\u28BF\u28C7\u2808\u283F\u283F\u2800\u28FC\u2847\u28BF\u28C7\u2818\u283F\u2807\u2800\u28F8",
|
|
9525
|
+
"\u2808\u28BF\u28E6\u28E4\u28F4\u287F\u2803\u2800\u2819\u28B7\u28E6\u28E4\u28F6\u287F\u2801\u2808\u283B\u28F7\u28E4\u28E4\u287E\u281B\u2800\u2808\u28BF\u28E6\u28E4\u28E4\u2834\u2801"
|
|
9526
|
+
].join("\n");
|
|
9527
|
+
var SetupWizard = class {
|
|
9528
|
+
constructor(workspaceRoot, existingConfig) {
|
|
9529
|
+
this.workspaceRoot = workspaceRoot;
|
|
9530
|
+
this.existingConfig = existingConfig ?? null;
|
|
9531
|
+
this.state = {
|
|
9532
|
+
currentStep: "welcome",
|
|
9533
|
+
skipped: [],
|
|
9534
|
+
completed: false
|
|
9535
|
+
};
|
|
9536
|
+
}
|
|
9537
|
+
/**
|
|
9538
|
+
* Run the full onboarding wizard
|
|
9539
|
+
*/
|
|
9540
|
+
async run(options) {
|
|
9541
|
+
if (!options?.force && this.isAlreadyConfigured()) {
|
|
9542
|
+
return {
|
|
9543
|
+
success: true,
|
|
9544
|
+
config: {},
|
|
9545
|
+
skippedSteps: ["welcome", "provider", "apiKey", "model", "telemetry", "preferences", "agentsFile"],
|
|
9546
|
+
cancelled: false
|
|
9547
|
+
};
|
|
9548
|
+
}
|
|
9549
|
+
try {
|
|
9550
|
+
if (!options?.skipWelcome) {
|
|
9551
|
+
await this.showWelcome();
|
|
9552
|
+
}
|
|
9553
|
+
const provider = await this.promptProvider();
|
|
9554
|
+
if (!provider) return this.cancelled();
|
|
9555
|
+
if (this.requiresApiKey(provider)) {
|
|
9556
|
+
const apiKey = await this.promptApiKey(provider);
|
|
9557
|
+
if (apiKey === null) return this.cancelled();
|
|
9558
|
+
}
|
|
9559
|
+
const model = await this.promptModel(provider);
|
|
9560
|
+
if (!model) return this.cancelled();
|
|
9561
|
+
await this.promptTelemetry();
|
|
9562
|
+
if (!options?.quickSetup) {
|
|
9563
|
+
await this.promptPreferences();
|
|
9564
|
+
} else {
|
|
9565
|
+
this.state.skipped.push("preferences");
|
|
9566
|
+
}
|
|
9567
|
+
await this.promptAgentsFile();
|
|
9568
|
+
return this.complete();
|
|
9569
|
+
} catch (error) {
|
|
9570
|
+
if (this.isCancellation(error)) {
|
|
9571
|
+
return this.cancelled();
|
|
9572
|
+
}
|
|
9573
|
+
throw error;
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
/**
|
|
9577
|
+
* Check if configuration is already complete
|
|
9578
|
+
*/
|
|
9579
|
+
isAlreadyConfigured() {
|
|
9580
|
+
if (!this.existingConfig) return false;
|
|
9581
|
+
const provider = this.existingConfig.provider;
|
|
9582
|
+
if (!provider) return false;
|
|
9583
|
+
const providerConfig = getProviderConfig(this.existingConfig, provider);
|
|
9584
|
+
return providerConfig !== null;
|
|
9585
|
+
}
|
|
9586
|
+
/**
|
|
9587
|
+
* Show welcome screen
|
|
9588
|
+
*/
|
|
9589
|
+
async showWelcome() {
|
|
9590
|
+
console.clear();
|
|
9591
|
+
console.log(chalk9.gray(ASCII_FRIEND));
|
|
9592
|
+
console.log();
|
|
9593
|
+
console.log(chalk9.cyan.bold(" Welcome to Autohand!"));
|
|
9594
|
+
console.log(chalk9.gray(" Your super fast AI coding agent"));
|
|
9595
|
+
console.log();
|
|
9596
|
+
console.log(chalk9.white(" Let's get you set up in just a few steps."));
|
|
9597
|
+
console.log();
|
|
9598
|
+
await this.pressEnter();
|
|
9599
|
+
}
|
|
9600
|
+
/**
|
|
9601
|
+
* Prompt for provider selection
|
|
9602
|
+
*/
|
|
9603
|
+
async promptProvider() {
|
|
9604
|
+
this.state.currentStep = "provider";
|
|
9605
|
+
const providers = ProviderFactory.getProviderNames();
|
|
9606
|
+
const choices = providers.map((p) => ({
|
|
9607
|
+
name: p,
|
|
9608
|
+
message: this.getProviderDisplayName(p),
|
|
9609
|
+
hint: this.getProviderHint(p)
|
|
9610
|
+
}));
|
|
9611
|
+
const result = await enquirer2.prompt({
|
|
9612
|
+
type: "select",
|
|
9613
|
+
name: "provider",
|
|
9614
|
+
message: "Which LLM provider would you like to use?",
|
|
9615
|
+
choices,
|
|
9616
|
+
initial: this.existingConfig?.provider ? providers.indexOf(this.existingConfig.provider) : 0
|
|
9617
|
+
});
|
|
9618
|
+
this.state.provider = result.provider;
|
|
9619
|
+
return result.provider;
|
|
9620
|
+
}
|
|
9621
|
+
/**
|
|
9622
|
+
* Prompt for API key (cloud providers)
|
|
9623
|
+
*/
|
|
9624
|
+
async promptApiKey(provider) {
|
|
9625
|
+
this.state.currentStep = "apiKey";
|
|
9626
|
+
const existingKey = this.getExistingApiKey(provider);
|
|
9627
|
+
if (existingKey && existingKey !== "replace-me") {
|
|
9628
|
+
const { useExisting } = await enquirer2.prompt({
|
|
9629
|
+
type: "confirm",
|
|
9630
|
+
name: "useExisting",
|
|
9631
|
+
message: `Use existing ${this.getProviderDisplayName(provider)} API key? (ends with ...${existingKey.slice(-4)})`,
|
|
9632
|
+
initial: true
|
|
9633
|
+
});
|
|
9634
|
+
if (useExisting) {
|
|
9635
|
+
this.state.apiKey = existingKey;
|
|
9636
|
+
return existingKey;
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
console.log(chalk9.gray(`
|
|
9640
|
+
Get your API key at: ${this.getApiKeyUrl(provider)}
|
|
9641
|
+
`));
|
|
9642
|
+
const result = await enquirer2.prompt({
|
|
9643
|
+
type: "password",
|
|
9644
|
+
name: "apiKey",
|
|
9645
|
+
message: `Enter your ${this.getProviderDisplayName(provider)} API key`,
|
|
9646
|
+
validate: (val) => {
|
|
9647
|
+
const v = val;
|
|
9648
|
+
if (!v?.trim()) return "API key is required";
|
|
9649
|
+
if (v.length < 10) return "API key seems too short";
|
|
9650
|
+
return true;
|
|
9651
|
+
}
|
|
9652
|
+
});
|
|
9653
|
+
this.state.apiKey = result.apiKey.trim();
|
|
9654
|
+
return this.state.apiKey;
|
|
9655
|
+
}
|
|
9656
|
+
/**
|
|
9657
|
+
* Prompt for model selection
|
|
9658
|
+
*/
|
|
9659
|
+
async promptModel(provider) {
|
|
9660
|
+
this.state.currentStep = "model";
|
|
9661
|
+
const defaultModel = this.getDefaultModel(provider);
|
|
9662
|
+
const result = await enquirer2.prompt({
|
|
9663
|
+
type: "input",
|
|
9664
|
+
name: "model",
|
|
9665
|
+
message: "Enter model ID",
|
|
9666
|
+
initial: defaultModel,
|
|
9667
|
+
validate: (val) => {
|
|
9668
|
+
const v = val;
|
|
9669
|
+
return v?.trim() ? true : "Model is required";
|
|
9670
|
+
}
|
|
9671
|
+
});
|
|
9672
|
+
this.state.model = result.model.trim();
|
|
9673
|
+
return this.state.model;
|
|
9674
|
+
}
|
|
9675
|
+
/**
|
|
9676
|
+
* Prompt for telemetry preference
|
|
9677
|
+
*/
|
|
9678
|
+
async promptTelemetry() {
|
|
9679
|
+
this.state.currentStep = "telemetry";
|
|
9680
|
+
console.log();
|
|
9681
|
+
console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9682
|
+
console.log(chalk9.white.bold(" Help us improve Autohand"));
|
|
9683
|
+
console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9684
|
+
console.log();
|
|
9685
|
+
console.log(chalk9.gray(" We collect anonymous usage data to understand how"));
|
|
9686
|
+
console.log(chalk9.gray(" Autohand is used and where we can make it better."));
|
|
9687
|
+
console.log();
|
|
9688
|
+
console.log(chalk9.gray(" What we collect:"));
|
|
9689
|
+
console.log(chalk9.gray(" - Command usage (which features are popular)"));
|
|
9690
|
+
console.log(chalk9.gray(" - Error rates (to fix bugs faster)"));
|
|
9691
|
+
console.log(chalk9.gray(" - Performance metrics (to speed things up)"));
|
|
9692
|
+
console.log();
|
|
9693
|
+
console.log(chalk9.gray(" What we never collect:"));
|
|
9694
|
+
console.log(chalk9.gray(" - Your code or file contents"));
|
|
9695
|
+
console.log(chalk9.gray(" - API keys or credentials"));
|
|
9696
|
+
console.log(chalk9.gray(" - Personal information"));
|
|
9697
|
+
console.log();
|
|
9698
|
+
const { telemetryEnabled } = await enquirer2.prompt({
|
|
9699
|
+
type: "confirm",
|
|
9700
|
+
name: "telemetryEnabled",
|
|
9701
|
+
message: "Share anonymous usage data to help improve Autohand?",
|
|
9702
|
+
initial: true
|
|
9703
|
+
});
|
|
9704
|
+
this.state.telemetryEnabled = telemetryEnabled;
|
|
9705
|
+
if (telemetryEnabled) {
|
|
9706
|
+
console.log(chalk9.green(" Thanks for helping us improve Autohand!"));
|
|
9707
|
+
} else {
|
|
9708
|
+
console.log(chalk9.gray(" No problem! You can change this anytime in config."));
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
/**
|
|
9712
|
+
* Prompt for additional preferences
|
|
9713
|
+
*/
|
|
9714
|
+
async promptPreferences() {
|
|
9715
|
+
this.state.currentStep = "preferences";
|
|
9716
|
+
const { configurePrefs } = await enquirer2.prompt({
|
|
9717
|
+
type: "confirm",
|
|
9718
|
+
name: "configurePrefs",
|
|
9719
|
+
message: "Would you like to configure additional preferences? (theme, auto-confirm)",
|
|
9720
|
+
initial: false
|
|
9721
|
+
});
|
|
9722
|
+
if (!configurePrefs) {
|
|
9723
|
+
this.state.skipped.push("preferences");
|
|
9724
|
+
return;
|
|
9725
|
+
}
|
|
9726
|
+
const themes = ["dark", "light", "dracula", "sandy", "tui"];
|
|
9727
|
+
const themeDescriptions = {
|
|
9728
|
+
dark: "Default dark theme",
|
|
9729
|
+
light: "Light theme for light backgrounds",
|
|
9730
|
+
dracula: "Popular Dracula color scheme",
|
|
9731
|
+
sandy: "Warm, earthy desert tones",
|
|
9732
|
+
tui: "New Zealand inspired colors"
|
|
9733
|
+
};
|
|
9734
|
+
const { theme } = await enquirer2.prompt({
|
|
9735
|
+
type: "select",
|
|
9736
|
+
name: "theme",
|
|
9737
|
+
message: "Select a theme",
|
|
9738
|
+
choices: themes.map((t) => ({ name: t, message: t, hint: themeDescriptions[t] })),
|
|
9739
|
+
initial: 0
|
|
9740
|
+
});
|
|
9741
|
+
const { autoConfirm } = await enquirer2.prompt({
|
|
9742
|
+
type: "confirm",
|
|
9743
|
+
name: "autoConfirm",
|
|
9744
|
+
message: "Auto-confirm non-destructive actions?",
|
|
9745
|
+
initial: false
|
|
9746
|
+
});
|
|
9747
|
+
const { checkForUpdates: checkForUpdates2 } = await enquirer2.prompt({
|
|
9748
|
+
type: "confirm",
|
|
9749
|
+
name: "checkForUpdates",
|
|
9750
|
+
message: "Check for updates on startup?",
|
|
9751
|
+
initial: true
|
|
9752
|
+
});
|
|
9753
|
+
this.state.preferences = { theme, autoConfirm, checkForUpdates: checkForUpdates2 };
|
|
9754
|
+
}
|
|
9755
|
+
/**
|
|
9756
|
+
* Prompt for AGENTS.md creation
|
|
9757
|
+
*/
|
|
9758
|
+
async promptAgentsFile() {
|
|
9759
|
+
this.state.currentStep = "agentsFile";
|
|
9760
|
+
const agentsPath = join3(this.workspaceRoot, "AGENTS.md");
|
|
9761
|
+
const exists = await pathExists2(agentsPath);
|
|
9762
|
+
if (exists) {
|
|
9763
|
+
const { overwrite } = await enquirer2.prompt({
|
|
9764
|
+
type: "confirm",
|
|
9765
|
+
name: "overwrite",
|
|
9766
|
+
message: "AGENTS.md already exists. Would you like to regenerate it?",
|
|
9767
|
+
initial: false
|
|
9768
|
+
});
|
|
9769
|
+
if (!overwrite) {
|
|
9770
|
+
this.state.skipped.push("agentsFile");
|
|
9771
|
+
console.log(chalk9.gray(" Keeping existing AGENTS.md"));
|
|
9772
|
+
return;
|
|
9773
|
+
}
|
|
9774
|
+
} else {
|
|
9775
|
+
console.log();
|
|
9776
|
+
console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9777
|
+
console.log(chalk9.white.bold(" Project Configuration"));
|
|
9778
|
+
console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9779
|
+
console.log();
|
|
9780
|
+
console.log(chalk9.gray(" AGENTS.md helps Autohand understand your project better."));
|
|
9781
|
+
console.log(chalk9.gray(" It contains instructions specific to your codebase."));
|
|
9782
|
+
console.log();
|
|
9783
|
+
const { createAgents } = await enquirer2.prompt({
|
|
9784
|
+
type: "confirm",
|
|
9785
|
+
name: "createAgents",
|
|
9786
|
+
message: "Generate AGENTS.md based on your project?",
|
|
9787
|
+
initial: true
|
|
9788
|
+
});
|
|
9789
|
+
if (!createAgents) {
|
|
9790
|
+
this.state.skipped.push("agentsFile");
|
|
9791
|
+
console.log(chalk9.gray(" You can create it later with /init"));
|
|
9792
|
+
return;
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
console.log();
|
|
9796
|
+
console.log(chalk9.gray(" Analyzing your project..."));
|
|
9797
|
+
const analyzer = new ProjectAnalyzer(this.workspaceRoot);
|
|
9798
|
+
const projectInfo = await analyzer.analyze();
|
|
9799
|
+
if (Object.keys(projectInfo).length > 0) {
|
|
9800
|
+
console.log();
|
|
9801
|
+
console.log(chalk9.gray(" Detected:"));
|
|
9802
|
+
if (projectInfo.language) {
|
|
9803
|
+
console.log(chalk9.white(` - Language: ${projectInfo.language}`));
|
|
9804
|
+
}
|
|
9805
|
+
if (projectInfo.framework) {
|
|
9806
|
+
console.log(chalk9.white(` - Framework: ${projectInfo.framework}`));
|
|
9807
|
+
}
|
|
9808
|
+
if (projectInfo.packageManager) {
|
|
9809
|
+
console.log(chalk9.white(` - Package manager: ${projectInfo.packageManager}`));
|
|
9810
|
+
}
|
|
9811
|
+
if (projectInfo.testFramework) {
|
|
9812
|
+
console.log(chalk9.white(` - Test framework: ${projectInfo.testFramework}`));
|
|
9813
|
+
}
|
|
9814
|
+
}
|
|
9815
|
+
const generator = new AgentsGenerator();
|
|
9816
|
+
const content = generator.generateContent(projectInfo);
|
|
9817
|
+
await writeFile(agentsPath, content);
|
|
9818
|
+
this.state.agentsFileCreated = true;
|
|
9819
|
+
console.log();
|
|
9820
|
+
console.log(chalk9.green(" Created AGENTS.md"));
|
|
9821
|
+
console.log(chalk9.gray(" You can customize it anytime to improve Autohand's understanding."));
|
|
9822
|
+
}
|
|
9823
|
+
/**
|
|
9824
|
+
* Build final config and return success
|
|
9825
|
+
*/
|
|
9826
|
+
complete() {
|
|
9827
|
+
this.state.currentStep = "complete";
|
|
9828
|
+
this.state.completed = true;
|
|
9829
|
+
const config = {
|
|
9830
|
+
provider: this.state.provider
|
|
9831
|
+
};
|
|
9832
|
+
if (this.state.provider) {
|
|
9833
|
+
if (this.requiresApiKey(this.state.provider)) {
|
|
9834
|
+
config[this.state.provider] = {
|
|
9835
|
+
apiKey: this.state.apiKey,
|
|
9836
|
+
model: this.state.model,
|
|
9837
|
+
baseUrl: this.getDefaultBaseUrl(this.state.provider)
|
|
9838
|
+
};
|
|
9839
|
+
} else {
|
|
9840
|
+
config[this.state.provider] = {
|
|
9841
|
+
model: this.state.model,
|
|
9842
|
+
baseUrl: this.getDefaultBaseUrl(this.state.provider)
|
|
9843
|
+
};
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9846
|
+
config.telemetry = {
|
|
9847
|
+
enabled: this.state.telemetryEnabled ?? true
|
|
9848
|
+
};
|
|
9849
|
+
if (this.state.preferences) {
|
|
9850
|
+
config.ui = {
|
|
9851
|
+
theme: this.state.preferences.theme,
|
|
9852
|
+
autoConfirm: this.state.preferences.autoConfirm,
|
|
9853
|
+
checkForUpdates: this.state.preferences.checkForUpdates
|
|
9854
|
+
};
|
|
9855
|
+
}
|
|
9856
|
+
this.showCompletionMessage();
|
|
9857
|
+
return {
|
|
9858
|
+
success: true,
|
|
9859
|
+
config,
|
|
9860
|
+
skippedSteps: this.state.skipped,
|
|
9861
|
+
cancelled: false,
|
|
9862
|
+
agentsFileCreated: this.state.agentsFileCreated
|
|
9863
|
+
};
|
|
9864
|
+
}
|
|
9865
|
+
/**
|
|
9866
|
+
* Show setup complete message
|
|
9867
|
+
*/
|
|
9868
|
+
showCompletionMessage() {
|
|
9869
|
+
console.log();
|
|
9870
|
+
console.log();
|
|
9871
|
+
console.log(chalk9.green(" Setup complete!"));
|
|
9872
|
+
console.log();
|
|
9873
|
+
console.log(chalk9.gray(" What was created:"));
|
|
9874
|
+
console.log(chalk9.white(" - ~/.autohand/config.json (your settings)"));
|
|
9875
|
+
if (this.state.agentsFileCreated) {
|
|
9876
|
+
console.log(chalk9.white(" - AGENTS.md (project instructions for Autohand)"));
|
|
9877
|
+
}
|
|
9878
|
+
console.log();
|
|
9879
|
+
console.log(chalk9.gray(" Quick tips:"));
|
|
9880
|
+
console.log(chalk9.white(" - Type your request and press Enter to start"));
|
|
9881
|
+
console.log(chalk9.white(" - Use @filename to mention files"));
|
|
9882
|
+
console.log(chalk9.white(" - Type /help for all commands"));
|
|
9883
|
+
console.log(chalk9.white(" - Press Ctrl+C twice to exit"));
|
|
9884
|
+
console.log();
|
|
9885
|
+
}
|
|
9886
|
+
/**
|
|
9887
|
+
* Return cancelled result
|
|
9888
|
+
*/
|
|
9889
|
+
cancelled() {
|
|
9890
|
+
return {
|
|
9891
|
+
success: false,
|
|
9892
|
+
config: {},
|
|
9893
|
+
skippedSteps: [],
|
|
9894
|
+
cancelled: true
|
|
9895
|
+
};
|
|
9896
|
+
}
|
|
9897
|
+
// Helper methods
|
|
9898
|
+
requiresApiKey(provider) {
|
|
9899
|
+
return provider === "openrouter" || provider === "openai";
|
|
9900
|
+
}
|
|
9901
|
+
getProviderDisplayName(provider) {
|
|
9902
|
+
const names = {
|
|
9903
|
+
openrouter: "OpenRouter",
|
|
9904
|
+
openai: "OpenAI",
|
|
9905
|
+
ollama: "Ollama",
|
|
9906
|
+
llamacpp: "llama.cpp",
|
|
9907
|
+
mlx: "MLX (Apple Silicon)"
|
|
9908
|
+
};
|
|
9909
|
+
return names[provider] || provider;
|
|
9910
|
+
}
|
|
9911
|
+
getProviderHint(provider) {
|
|
9912
|
+
const hints = {
|
|
9913
|
+
openrouter: "Cloud - Access to 100+ models (Claude, GPT-4, etc.)",
|
|
9914
|
+
openai: "Cloud - Official OpenAI models (GPT-4o, o1, etc.)",
|
|
9915
|
+
ollama: "Local - Run models on your machine (free)",
|
|
9916
|
+
llamacpp: "Local - Fast inference with GGUF models",
|
|
9917
|
+
mlx: "Local - Optimized for Apple Silicon Macs"
|
|
9918
|
+
};
|
|
9919
|
+
return hints[provider] || "";
|
|
9920
|
+
}
|
|
9921
|
+
getApiKeyUrl(provider) {
|
|
9922
|
+
const urls = {
|
|
9923
|
+
openrouter: "https://openrouter.ai/keys",
|
|
9924
|
+
openai: "https://platform.openai.com/api-keys"
|
|
9925
|
+
};
|
|
9926
|
+
return urls[provider] || "";
|
|
9927
|
+
}
|
|
9928
|
+
getDefaultModel(provider) {
|
|
9929
|
+
const defaults = {
|
|
9930
|
+
openrouter: "anthropic/claude-sonnet-4-20250514",
|
|
9931
|
+
openai: "gpt-4o",
|
|
9932
|
+
ollama: "llama3.2:latest",
|
|
9933
|
+
llamacpp: "default",
|
|
9934
|
+
mlx: "mlx-community/Llama-3.2-3B-Instruct-4bit"
|
|
9935
|
+
};
|
|
9936
|
+
return defaults[provider] || "";
|
|
9937
|
+
}
|
|
9938
|
+
getDefaultBaseUrl(provider) {
|
|
9939
|
+
const urls = {
|
|
9940
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
9941
|
+
openai: "https://api.openai.com/v1",
|
|
9942
|
+
ollama: "http://localhost:11434",
|
|
9943
|
+
llamacpp: "http://localhost:8080",
|
|
9944
|
+
mlx: "http://localhost:8080"
|
|
9945
|
+
};
|
|
9946
|
+
return urls[provider] || "";
|
|
9947
|
+
}
|
|
9948
|
+
getExistingApiKey(provider) {
|
|
9949
|
+
if (!this.existingConfig) return null;
|
|
9950
|
+
const config = this.existingConfig[provider];
|
|
9951
|
+
return config?.apiKey || null;
|
|
9952
|
+
}
|
|
9953
|
+
isCancellation(error) {
|
|
9954
|
+
if (error && typeof error === "object") {
|
|
9955
|
+
const e = error;
|
|
9956
|
+
return e.code === "ERR_USE_AFTER_CLOSE" || e.message?.includes("cancelled") || e.message?.includes("canceled");
|
|
9957
|
+
}
|
|
9958
|
+
return false;
|
|
9959
|
+
}
|
|
9960
|
+
async pressEnter() {
|
|
9961
|
+
console.log(chalk9.gray(" Press Enter to continue..."));
|
|
9962
|
+
await enquirer2.prompt({
|
|
9963
|
+
type: "invisible",
|
|
9964
|
+
name: "continue",
|
|
9965
|
+
message: ""
|
|
9966
|
+
});
|
|
9967
|
+
}
|
|
9968
|
+
};
|
|
9969
|
+
|
|
9970
|
+
// src/core/agents/AgentDelegator.ts
|
|
9971
|
+
import chalk11 from "chalk";
|
|
9972
|
+
|
|
9973
|
+
// src/core/agents/SubAgent.ts
|
|
9974
|
+
import chalk10 from "chalk";
|
|
9975
|
+
var DELEGATION_TOOL_DEFINITIONS = [
|
|
9976
|
+
{
|
|
9977
|
+
name: "delegate_task",
|
|
9978
|
+
description: "Delegate a task to another specialized sub-agent",
|
|
9979
|
+
parameters: {
|
|
9980
|
+
type: "object",
|
|
9981
|
+
properties: {
|
|
9982
|
+
agent_name: { type: "string", description: "Name of the agent to delegate to" },
|
|
9983
|
+
task: { type: "string", description: "Task description for the sub-agent" }
|
|
9984
|
+
},
|
|
9985
|
+
required: ["agent_name", "task"]
|
|
9986
|
+
}
|
|
9987
|
+
},
|
|
9988
|
+
{
|
|
9989
|
+
name: "delegate_parallel",
|
|
9990
|
+
description: "Run multiple sub-agents in parallel (max 5)",
|
|
9991
|
+
parameters: {
|
|
9992
|
+
type: "object",
|
|
9993
|
+
properties: {
|
|
9994
|
+
tasks: { type: "array", description: "Array of {agent_name, task} objects" }
|
|
9995
|
+
},
|
|
9996
|
+
required: ["tasks"]
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
];
|
|
10000
|
+
var SubAgent = class {
|
|
10001
|
+
constructor(config, llm, actionExecutor, options) {
|
|
10002
|
+
this.config = config;
|
|
10003
|
+
this.llm = llm;
|
|
10004
|
+
this.actionExecutor = actionExecutor;
|
|
10005
|
+
this.delegator = null;
|
|
10006
|
+
this.name = config.name;
|
|
10007
|
+
this.options = options;
|
|
10008
|
+
const canDelegate = options.depth < options.maxDepth;
|
|
10009
|
+
const allowedTools = new Set(config.tools);
|
|
10010
|
+
let definitions = DEFAULT_TOOL_DEFINITIONS.filter((def) => allowedTools.has(def.name));
|
|
10011
|
+
if (canDelegate) {
|
|
10012
|
+
definitions = [...definitions, ...DELEGATION_TOOL_DEFINITIONS];
|
|
10013
|
+
}
|
|
10014
|
+
const toolFilter = new ToolFilter(options.clientContext);
|
|
10015
|
+
definitions = toolFilter.filterDefinitions(definitions);
|
|
10016
|
+
if (canDelegate) {
|
|
10017
|
+
this.delegator = new AgentDelegator(llm, actionExecutor, {
|
|
10018
|
+
clientContext: options.clientContext,
|
|
10019
|
+
currentDepth: options.depth,
|
|
10020
|
+
maxDepth: options.maxDepth
|
|
10021
|
+
});
|
|
10022
|
+
}
|
|
10023
|
+
this.toolManager = new ToolManager({
|
|
10024
|
+
executor: async (action, context) => {
|
|
10025
|
+
if (action.type === "delegate_task" && this.delegator) {
|
|
10026
|
+
return this.delegator.delegateTask(
|
|
10027
|
+
action.agent_name,
|
|
10028
|
+
action.task
|
|
10029
|
+
);
|
|
10030
|
+
}
|
|
10031
|
+
if (action.type === "delegate_parallel" && this.delegator) {
|
|
10032
|
+
return this.delegator.delegateParallel(action.tasks);
|
|
10033
|
+
}
|
|
10034
|
+
return this.actionExecutor.execute(action, context);
|
|
10035
|
+
},
|
|
10036
|
+
confirmApproval: async () => true,
|
|
10037
|
+
// Sub-agents auto-approve (inherit from main agent in future)
|
|
10038
|
+
definitions,
|
|
10039
|
+
clientContext: options.clientContext
|
|
10040
|
+
});
|
|
10041
|
+
const enhancedSystemPrompt = this.buildSystemPrompt(config.systemPrompt, definitions);
|
|
10042
|
+
this.conversation = new ConversationManager();
|
|
10043
|
+
this.conversation.reset(enhancedSystemPrompt);
|
|
10044
|
+
}
|
|
10045
|
+
/**
|
|
10046
|
+
* Build system prompt with tool signatures for the LLM
|
|
10047
|
+
*/
|
|
10048
|
+
buildSystemPrompt(basePrompt, tools) {
|
|
10049
|
+
const toolSignatures = tools.map((def) => this.formatToolSignature(def)).join("\n");
|
|
10050
|
+
return [
|
|
10051
|
+
basePrompt,
|
|
10052
|
+
"",
|
|
10053
|
+
"## Available Tools",
|
|
10054
|
+
"You have access to the following tools. Use them when needed:",
|
|
10055
|
+
"",
|
|
10056
|
+
toolSignatures,
|
|
10057
|
+
"",
|
|
10058
|
+
"## Response Format",
|
|
10059
|
+
"Always respond with structured JSON:",
|
|
10060
|
+
"```json",
|
|
10061
|
+
"{",
|
|
10062
|
+
' "thought": "Your reasoning about what to do next",',
|
|
10063
|
+
' "toolCalls": [{"tool": "tool_name", "args": {...}}],',
|
|
10064
|
+
' "finalResponse": "Your final answer when done (omit toolCalls if providing this)"',
|
|
10065
|
+
"}",
|
|
10066
|
+
"```",
|
|
10067
|
+
"",
|
|
10068
|
+
`Depth: ${this.options.depth}/${this.options.maxDepth} ${this.delegator ? "(can delegate further)" : "(max depth reached)"}`
|
|
10069
|
+
].join("\n");
|
|
10070
|
+
}
|
|
10071
|
+
/**
|
|
10072
|
+
* Format a tool definition as a signature string
|
|
10073
|
+
*/
|
|
10074
|
+
formatToolSignature(def) {
|
|
10075
|
+
const params = def.parameters?.properties ? Object.entries(def.parameters.properties).map(([name, prop]) => {
|
|
10076
|
+
const required = def.parameters?.required?.includes(name) ? "" : "?";
|
|
10077
|
+
return `${name}${required}: ${prop.type}`;
|
|
10078
|
+
}).join(", ") : "";
|
|
10079
|
+
return `- ${def.name}(${params}): ${def.description}`;
|
|
10080
|
+
}
|
|
10081
|
+
async run(task) {
|
|
10082
|
+
console.log(chalk10.cyan(`
|
|
10083
|
+
\u{1F916} Sub-agent '${this.name}' starting task... (depth ${this.options.depth}/${this.options.maxDepth})`));
|
|
10084
|
+
this.conversation.addMessage({ role: "user", content: task });
|
|
10085
|
+
const tools = this.toolManager.toFunctionDefinitions();
|
|
10086
|
+
const maxIterations = 10;
|
|
10087
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
10088
|
+
const completion = await this.llm.complete({
|
|
10089
|
+
messages: this.conversation.history(),
|
|
10090
|
+
model: this.config.model,
|
|
10091
|
+
temperature: 0.2,
|
|
10092
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
10093
|
+
toolChoice: tools.length > 0 ? "auto" : void 0
|
|
10094
|
+
});
|
|
10095
|
+
const payload = this.parseResponse(completion);
|
|
10096
|
+
if (completion.toolCalls?.length) {
|
|
10097
|
+
this.conversation.addMessage({
|
|
10098
|
+
role: "assistant",
|
|
10099
|
+
content: completion.content || ""
|
|
10100
|
+
});
|
|
10101
|
+
} else {
|
|
10102
|
+
this.conversation.addMessage({ role: "assistant", content: completion.content });
|
|
10103
|
+
}
|
|
10104
|
+
if (payload.thought) {
|
|
10105
|
+
console.log(chalk10.gray(`[${this.name}] ${payload.thought}`));
|
|
10106
|
+
}
|
|
10107
|
+
if (payload.toolCalls && payload.toolCalls.length > 0) {
|
|
10108
|
+
const results = await this.toolManager.execute(payload.toolCalls);
|
|
10109
|
+
for (let j = 0; j < results.length; j++) {
|
|
10110
|
+
const result = results[j];
|
|
10111
|
+
const toolCall = completion.toolCalls?.[j];
|
|
10112
|
+
const content = result.success ? result.output ?? "(no output)" : result.error ?? "Tool failed";
|
|
10113
|
+
this.conversation.addMessage({
|
|
10114
|
+
role: "tool",
|
|
10115
|
+
name: result.tool,
|
|
10116
|
+
content,
|
|
10117
|
+
tool_call_id: toolCall?.id
|
|
10118
|
+
});
|
|
10119
|
+
if (!result.success) {
|
|
10120
|
+
console.log(chalk10.red(`[${this.name}] Tool ${result.tool} failed: ${content}`));
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
10123
|
+
continue;
|
|
10124
|
+
}
|
|
10125
|
+
const response = payload.finalResponse ?? payload.response ?? completion.content;
|
|
10126
|
+
console.log(chalk10.cyan(`[${this.name}] Finished.`));
|
|
10127
|
+
return response;
|
|
10128
|
+
}
|
|
10129
|
+
return `[${this.name}] Failed to complete task within ${maxIterations} iterations.`;
|
|
10130
|
+
}
|
|
10131
|
+
/**
|
|
10132
|
+
* Parse LLM response, preferring native tool calls over JSON parsing
|
|
10133
|
+
*/
|
|
10134
|
+
parseResponse(completion) {
|
|
10135
|
+
if (completion.toolCalls && completion.toolCalls.length > 0) {
|
|
10136
|
+
return {
|
|
10137
|
+
thought: completion.content || void 0,
|
|
10138
|
+
toolCalls: completion.toolCalls.map((tc) => ({
|
|
10139
|
+
tool: tc.function.name,
|
|
10140
|
+
args: this.safeParseJson(tc.function.arguments)
|
|
10141
|
+
}))
|
|
10142
|
+
};
|
|
10143
|
+
}
|
|
10144
|
+
return this.parsePayload(completion.content);
|
|
10145
|
+
}
|
|
10146
|
+
/**
|
|
10147
|
+
* Safely parse JSON, returning empty object on failure
|
|
9066
10148
|
*/
|
|
9067
10149
|
safeParseJson(json) {
|
|
9068
10150
|
try {
|
|
@@ -9227,7 +10309,7 @@ ${result}`;
|
|
|
9227
10309
|
}
|
|
9228
10310
|
});
|
|
9229
10311
|
const results = await Promise.all(promises);
|
|
9230
|
-
return results.join("\n\n" +
|
|
10312
|
+
return results.join("\n\n" + chalk11.gray("\u2500".repeat(40)) + "\n\n");
|
|
9231
10313
|
}
|
|
9232
10314
|
/**
|
|
9233
10315
|
* Get the current delegation depth
|
|
@@ -9326,7 +10408,7 @@ var ErrorLogger = class {
|
|
|
9326
10408
|
// src/feedback/FeedbackManager.ts
|
|
9327
10409
|
import fs14 from "fs-extra";
|
|
9328
10410
|
import path14 from "path";
|
|
9329
|
-
import
|
|
10411
|
+
import chalk12 from "chalk";
|
|
9330
10412
|
import Enquirer from "enquirer";
|
|
9331
10413
|
|
|
9332
10414
|
// src/feedback/FeedbackApiClient.ts
|
|
@@ -9616,11 +10698,13 @@ var FeedbackManager = class {
|
|
|
9616
10698
|
};
|
|
9617
10699
|
}
|
|
9618
10700
|
saveState() {
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
10701
|
+
setImmediate(() => {
|
|
10702
|
+
try {
|
|
10703
|
+
fs14.ensureDirSync(this.stateDir);
|
|
10704
|
+
fs14.writeJsonSync(this.statePath, this.state, { spaces: 2 });
|
|
10705
|
+
} catch {
|
|
10706
|
+
}
|
|
10707
|
+
});
|
|
9624
10708
|
}
|
|
9625
10709
|
async saveFeedbackResponse(response) {
|
|
9626
10710
|
try {
|
|
@@ -9724,10 +10808,10 @@ var FeedbackManager = class {
|
|
|
9724
10808
|
this.state.lastPromptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9725
10809
|
this.saveState();
|
|
9726
10810
|
console.log();
|
|
9727
|
-
console.log(
|
|
9728
|
-
console.log(
|
|
9729
|
-
console.log(
|
|
9730
|
-
console.log(
|
|
10811
|
+
console.log(chalk12.cyan("\u2501".repeat(50)));
|
|
10812
|
+
console.log(chalk12.cyan.bold(" Quick Feedback"));
|
|
10813
|
+
console.log(chalk12.gray(" Help us improve Autohand (takes 10 seconds)"));
|
|
10814
|
+
console.log(chalk12.cyan("\u2501".repeat(50)));
|
|
9731
10815
|
console.log();
|
|
9732
10816
|
const safePrompt = async (config) => {
|
|
9733
10817
|
try {
|
|
@@ -9740,19 +10824,56 @@ var FeedbackManager = class {
|
|
|
9740
10824
|
}
|
|
9741
10825
|
};
|
|
9742
10826
|
try {
|
|
9743
|
-
const
|
|
9744
|
-
|
|
10827
|
+
const { Select } = Enquirer;
|
|
10828
|
+
const ratingChoices = [
|
|
10829
|
+
{ name: "5", message: `${chalk12.green("5")} - Excellent` },
|
|
10830
|
+
{ name: "4", message: `${chalk12.green("4")} - Good` },
|
|
10831
|
+
{ name: "3", message: `${chalk12.yellow("3")} - Okay` },
|
|
10832
|
+
{ name: "2", message: `${chalk12.red("2")} - Poor` },
|
|
10833
|
+
{ name: "1", message: `${chalk12.red("1")} - Very Poor` },
|
|
10834
|
+
{ name: "skip", message: `${chalk12.gray("s")} - Skip` }
|
|
10835
|
+
];
|
|
10836
|
+
const ratingPrompt = new Select({
|
|
9745
10837
|
name: "score",
|
|
9746
|
-
message: "How would you rate your experience?",
|
|
9747
|
-
choices:
|
|
9748
|
-
{ name: "5", message: `${chalk11.green("5")} - Excellent` },
|
|
9749
|
-
{ name: "4", message: `${chalk11.green("4")} - Good` },
|
|
9750
|
-
{ name: "3", message: `${chalk11.yellow("3")} - Okay` },
|
|
9751
|
-
{ name: "2", message: `${chalk11.red("2")} - Poor` },
|
|
9752
|
-
{ name: "1", message: `${chalk11.red("1")} - Very Poor` },
|
|
9753
|
-
{ name: "skip", message: `${chalk11.gray("Skip")}` }
|
|
9754
|
-
]
|
|
10838
|
+
message: "How would you rate your experience? (press 1-5 or s to skip)",
|
|
10839
|
+
choices: ratingChoices
|
|
9755
10840
|
});
|
|
10841
|
+
const originalKeypress = ratingPrompt.keypress.bind(ratingPrompt);
|
|
10842
|
+
ratingPrompt.keypress = async function(char, key) {
|
|
10843
|
+
if (char >= "1" && char <= "5") {
|
|
10844
|
+
const targetName = char;
|
|
10845
|
+
const index = ratingChoices.findIndex((c) => c.name === targetName);
|
|
10846
|
+
if (index !== -1) {
|
|
10847
|
+
this.index = index;
|
|
10848
|
+
this.selected = this.choices[index];
|
|
10849
|
+
this.value = this.choices[index].name;
|
|
10850
|
+
await this.render();
|
|
10851
|
+
return this.submit();
|
|
10852
|
+
}
|
|
10853
|
+
}
|
|
10854
|
+
if (char === "s" || char === "S") {
|
|
10855
|
+
const index = ratingChoices.findIndex((c) => c.name === "skip");
|
|
10856
|
+
if (index !== -1) {
|
|
10857
|
+
this.index = index;
|
|
10858
|
+
this.selected = this.choices[index];
|
|
10859
|
+
this.value = this.choices[index].name;
|
|
10860
|
+
await this.render();
|
|
10861
|
+
return this.submit();
|
|
10862
|
+
}
|
|
10863
|
+
}
|
|
10864
|
+
return originalKeypress(char, key);
|
|
10865
|
+
};
|
|
10866
|
+
let npsResult = null;
|
|
10867
|
+
try {
|
|
10868
|
+
const score = await ratingPrompt.run();
|
|
10869
|
+
npsResult = { score };
|
|
10870
|
+
} catch (error) {
|
|
10871
|
+
if (error?.code === "ERR_USE_AFTER_CLOSE") {
|
|
10872
|
+
npsResult = null;
|
|
10873
|
+
} else {
|
|
10874
|
+
throw error;
|
|
10875
|
+
}
|
|
10876
|
+
}
|
|
9756
10877
|
if (!npsResult) {
|
|
9757
10878
|
this.state.dismissed++;
|
|
9758
10879
|
this.saveState();
|
|
@@ -9761,7 +10882,7 @@ var FeedbackManager = class {
|
|
|
9761
10882
|
if (npsResult.score === "skip") {
|
|
9762
10883
|
this.state.dismissed++;
|
|
9763
10884
|
this.saveState();
|
|
9764
|
-
console.log(
|
|
10885
|
+
console.log(chalk12.gray("\nNo problem! You can always use /feedback later.\n"));
|
|
9765
10886
|
return false;
|
|
9766
10887
|
}
|
|
9767
10888
|
const npsScore = parseInt(npsResult.score, 10);
|
|
@@ -9808,11 +10929,11 @@ var FeedbackManager = class {
|
|
|
9808
10929
|
this.state.averageNps = this.state.npsScores.reduce((a, b) => a + b, 0) / this.state.npsScores.length;
|
|
9809
10930
|
this.saveState();
|
|
9810
10931
|
console.log();
|
|
9811
|
-
console.log(
|
|
10932
|
+
console.log(chalk12.green("Thank you for your feedback!"));
|
|
9812
10933
|
if (npsScore >= 4) {
|
|
9813
|
-
console.log(
|
|
10934
|
+
console.log(chalk12.gray("Your support helps us build a better tool."));
|
|
9814
10935
|
} else {
|
|
9815
|
-
console.log(
|
|
10936
|
+
console.log(chalk12.gray("We'll work hard to improve your experience."));
|
|
9816
10937
|
}
|
|
9817
10938
|
console.log();
|
|
9818
10939
|
return true;
|
|
@@ -9822,7 +10943,7 @@ var FeedbackManager = class {
|
|
|
9822
10943
|
}
|
|
9823
10944
|
this.state.dismissed++;
|
|
9824
10945
|
this.saveState();
|
|
9825
|
-
console.log(
|
|
10946
|
+
console.log(chalk12.gray("\nFeedback skipped.\n"));
|
|
9826
10947
|
return false;
|
|
9827
10948
|
}
|
|
9828
10949
|
}
|
|
@@ -9834,10 +10955,10 @@ var FeedbackManager = class {
|
|
|
9834
10955
|
async quickRating() {
|
|
9835
10956
|
console.log();
|
|
9836
10957
|
console.log(
|
|
9837
|
-
|
|
10958
|
+
chalk12.cyan("Quick rating: ") + chalk12.gray("Press ") + chalk12.bold("1-5") + chalk12.gray(" to rate your experience (or ") + chalk12.bold("Enter") + chalk12.gray(" to skip)")
|
|
9838
10959
|
);
|
|
9839
10960
|
console.log(
|
|
9840
|
-
|
|
10961
|
+
chalk12.gray(" 1=Poor 2=Fair 3=Good 4=Great 5=Excellent")
|
|
9841
10962
|
);
|
|
9842
10963
|
return new Promise((resolve) => {
|
|
9843
10964
|
const stdin = process.stdin;
|
|
@@ -9869,7 +10990,7 @@ var FeedbackManager = class {
|
|
|
9869
10990
|
const num = parseInt(key, 10);
|
|
9870
10991
|
if (num >= 1 && num <= 5) {
|
|
9871
10992
|
cleanup();
|
|
9872
|
-
console.log(
|
|
10993
|
+
console.log(chalk12.green(` ${num}`));
|
|
9873
10994
|
this.state.npsScores.push(num);
|
|
9874
10995
|
this.state.feedbackCount++;
|
|
9875
10996
|
this.state.lastFeedbackAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9877,7 +10998,7 @@ var FeedbackManager = class {
|
|
|
9877
10998
|
this.hasPromptedThisSession = true;
|
|
9878
10999
|
this.state.lastPromptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9879
11000
|
this.saveState();
|
|
9880
|
-
console.log(
|
|
11001
|
+
console.log(chalk12.green("Thanks!"));
|
|
9881
11002
|
console.log();
|
|
9882
11003
|
resolve(num);
|
|
9883
11004
|
}
|
|
@@ -10796,12 +11917,11 @@ var CommunitySkillsClient = class {
|
|
|
10796
11917
|
};
|
|
10797
11918
|
|
|
10798
11919
|
// src/ui/persistentInput.ts
|
|
10799
|
-
import
|
|
10800
|
-
import readline3 from "readline";
|
|
11920
|
+
import chalk14 from "chalk";
|
|
10801
11921
|
import EventEmitter from "events";
|
|
10802
11922
|
|
|
10803
11923
|
// src/ui/terminalRegions.ts
|
|
10804
|
-
import
|
|
11924
|
+
import chalk13 from "chalk";
|
|
10805
11925
|
var ESC = "\x1B";
|
|
10806
11926
|
var CSI = `${ESC}[`;
|
|
10807
11927
|
var TerminalRegions = class {
|
|
@@ -10865,15 +11985,15 @@ var TerminalRegions = class {
|
|
|
10865
11985
|
this.output.write(`${CSI}s`);
|
|
10866
11986
|
this.output.write(`${CSI}${height - 2};1H`);
|
|
10867
11987
|
this.output.write(`${CSI}K`);
|
|
10868
|
-
this.output.write(
|
|
11988
|
+
this.output.write(chalk13.gray("\u2500".repeat(width)));
|
|
10869
11989
|
this.output.write(`${CSI}${height - 1};1H`);
|
|
10870
11990
|
this.output.write(`${CSI}K`);
|
|
10871
|
-
const queueStatus = queueCount > 0 ?
|
|
11991
|
+
const queueStatus = queueCount > 0 ? chalk13.cyan(` [${queueCount} queued]`) : "";
|
|
10872
11992
|
const statusText = status || "type to queue \xB7 Enter to submit";
|
|
10873
|
-
this.output.write(
|
|
11993
|
+
this.output.write(chalk13.gray(statusText) + queueStatus);
|
|
10874
11994
|
this.output.write(`${CSI}${height};1H`);
|
|
10875
11995
|
this.output.write(`${CSI}K`);
|
|
10876
|
-
this.output.write(
|
|
11996
|
+
this.output.write(chalk13.gray("\u203A") + " " + input);
|
|
10877
11997
|
this.output.write(`${CSI}u`);
|
|
10878
11998
|
}
|
|
10879
11999
|
/**
|
|
@@ -10885,7 +12005,7 @@ var TerminalRegions = class {
|
|
|
10885
12005
|
this.output.write(`${CSI}s`);
|
|
10886
12006
|
this.output.write(`${CSI}${height};1H`);
|
|
10887
12007
|
this.output.write(`${CSI}K`);
|
|
10888
|
-
this.output.write(
|
|
12008
|
+
this.output.write(chalk13.gray("\u203A") + " " + input);
|
|
10889
12009
|
this.output.write(`${CSI}u`);
|
|
10890
12010
|
}
|
|
10891
12011
|
/**
|
|
@@ -10897,8 +12017,8 @@ var TerminalRegions = class {
|
|
|
10897
12017
|
this.output.write(`${CSI}s`);
|
|
10898
12018
|
this.output.write(`${CSI}${height - 1};1H`);
|
|
10899
12019
|
this.output.write(`${CSI}K`);
|
|
10900
|
-
const queueStatus = queueCount > 0 ?
|
|
10901
|
-
this.output.write(
|
|
12020
|
+
const queueStatus = queueCount > 0 ? chalk13.cyan(` [${queueCount} queued]`) : "";
|
|
12021
|
+
this.output.write(chalk13.gray(status) + queueStatus);
|
|
10902
12022
|
this.output.write(`${CSI}u`);
|
|
10903
12023
|
}
|
|
10904
12024
|
/**
|
|
@@ -11019,7 +12139,7 @@ var PersistentInput = class extends EventEmitter {
|
|
|
11019
12139
|
this.currentInput = "";
|
|
11020
12140
|
this.isPaused = false;
|
|
11021
12141
|
if (this.silentMode) {
|
|
11022
|
-
|
|
12142
|
+
safeEmitKeypressEvents(this.input);
|
|
11023
12143
|
const supportsRaw = typeof this.input.setRawMode === "function";
|
|
11024
12144
|
const wasRaw = this.input.isRaw;
|
|
11025
12145
|
if (!wasRaw && supportsRaw) {
|
|
@@ -11030,7 +12150,7 @@ var PersistentInput = class extends EventEmitter {
|
|
|
11030
12150
|
this.input.on("keypress", this.handleKeypress);
|
|
11031
12151
|
} else {
|
|
11032
12152
|
this.regions.enable();
|
|
11033
|
-
|
|
12153
|
+
safeEmitKeypressEvents(this.input);
|
|
11034
12154
|
const supportsRaw = typeof this.input.setRawMode === "function";
|
|
11035
12155
|
if (supportsRaw) {
|
|
11036
12156
|
this.input.setRawMode(true);
|
|
@@ -11140,7 +12260,7 @@ var PersistentInput = class extends EventEmitter {
|
|
|
11140
12260
|
if (this.silentMode) {
|
|
11141
12261
|
this.emit("queue-full", this.maxQueueSize);
|
|
11142
12262
|
} else {
|
|
11143
|
-
this.regions.writeAbove(
|
|
12263
|
+
this.regions.writeAbove(chalk14.yellow(`
|
|
11144
12264
|
\u26A0 Queue full (max ${this.maxQueueSize})
|
|
11145
12265
|
`));
|
|
11146
12266
|
}
|
|
@@ -11152,7 +12272,7 @@ var PersistentInput = class extends EventEmitter {
|
|
|
11152
12272
|
});
|
|
11153
12273
|
const preview = text.length > 40 ? text.slice(0, 37) + "..." : text;
|
|
11154
12274
|
if (!this.silentMode) {
|
|
11155
|
-
this.regions.writeAbove(
|
|
12275
|
+
this.regions.writeAbove(chalk14.cyan(`
|
|
11156
12276
|
\u2713 Queued: "${preview}" (${this.queue.length} pending)
|
|
11157
12277
|
`));
|
|
11158
12278
|
this.regions.updateStatus(this.statusLine, this.queue.length);
|
|
@@ -11255,7 +12375,7 @@ ${truncatedContent}` : outputLines > 0 ? `
|
|
|
11255
12375
|
}
|
|
11256
12376
|
|
|
11257
12377
|
// src/ui/promptCallback.ts
|
|
11258
|
-
import
|
|
12378
|
+
import enquirer3 from "enquirer";
|
|
11259
12379
|
function isExternalCallbackEnabled() {
|
|
11260
12380
|
return !!process.env.AUTOHAND_PERMISSION_CALLBACK_URL;
|
|
11261
12381
|
}
|
|
@@ -11321,7 +12441,7 @@ async function confirm(message, context) {
|
|
|
11321
12441
|
while (process.stdin.read() !== null) {
|
|
11322
12442
|
}
|
|
11323
12443
|
}
|
|
11324
|
-
const { Select, Input } =
|
|
12444
|
+
const { Select, Input } = enquirer3;
|
|
11325
12445
|
const choices = [
|
|
11326
12446
|
{ name: "yes", message: "1. Yes" },
|
|
11327
12447
|
{ name: "no", message: "2. No" },
|
|
@@ -11334,24 +12454,30 @@ async function confirm(message, context) {
|
|
|
11334
12454
|
initial: 0
|
|
11335
12455
|
});
|
|
11336
12456
|
const originalKeypress = prompt.keypress.bind(prompt);
|
|
11337
|
-
prompt.keypress = function(char, key) {
|
|
12457
|
+
prompt.keypress = async function(char, key) {
|
|
11338
12458
|
if (char === "1" || char === "2" || char === "3") {
|
|
11339
12459
|
const index = parseInt(char, 10) - 1;
|
|
11340
12460
|
if (index >= 0 && index < choices.length) {
|
|
11341
12461
|
this.index = index;
|
|
11342
|
-
this.
|
|
11343
|
-
|
|
12462
|
+
this.selected = this.choices[index];
|
|
12463
|
+
this.value = this.choices[index].name;
|
|
12464
|
+
await this.render();
|
|
12465
|
+
return this.submit();
|
|
11344
12466
|
}
|
|
11345
12467
|
}
|
|
11346
12468
|
if (char === "y" || char === "Y") {
|
|
11347
12469
|
this.index = 0;
|
|
11348
|
-
this.
|
|
11349
|
-
|
|
12470
|
+
this.selected = this.choices[0];
|
|
12471
|
+
this.value = this.choices[0].name;
|
|
12472
|
+
await this.render();
|
|
12473
|
+
return this.submit();
|
|
11350
12474
|
}
|
|
11351
12475
|
if (char === "n" || char === "N") {
|
|
11352
12476
|
this.index = 1;
|
|
11353
|
-
this.
|
|
11354
|
-
|
|
12477
|
+
this.selected = this.choices[1];
|
|
12478
|
+
this.value = this.choices[1].name;
|
|
12479
|
+
await this.render();
|
|
12480
|
+
return this.submit();
|
|
11355
12481
|
}
|
|
11356
12482
|
return originalKeypress(char, key);
|
|
11357
12483
|
};
|
|
@@ -11646,7 +12772,7 @@ var IntentDetector = class {
|
|
|
11646
12772
|
|
|
11647
12773
|
// src/core/EnvironmentBootstrap.ts
|
|
11648
12774
|
import fs17 from "fs-extra";
|
|
11649
|
-
import { join as
|
|
12775
|
+
import { join as join4 } from "path";
|
|
11650
12776
|
import { exec } from "child_process";
|
|
11651
12777
|
import { promisify } from "util";
|
|
11652
12778
|
var execAsync = promisify(exec);
|
|
@@ -11741,11 +12867,11 @@ var EnvironmentBootstrap = class {
|
|
|
11741
12867
|
*/
|
|
11742
12868
|
async detectPackageManager(root) {
|
|
11743
12869
|
for (const { file, pm } of this.lockfileChecks) {
|
|
11744
|
-
if (await fs17.pathExists(
|
|
12870
|
+
if (await fs17.pathExists(join4(root, file))) {
|
|
11745
12871
|
return pm;
|
|
11746
12872
|
}
|
|
11747
12873
|
}
|
|
11748
|
-
const pkgPath =
|
|
12874
|
+
const pkgPath = join4(root, "package.json");
|
|
11749
12875
|
if (await fs17.pathExists(pkgPath)) {
|
|
11750
12876
|
try {
|
|
11751
12877
|
const pkg = await fs17.readJson(pkgPath);
|
|
@@ -11779,7 +12905,7 @@ var EnvironmentBootstrap = class {
|
|
|
11779
12905
|
const step = { name: "Git Sync", status: "running" };
|
|
11780
12906
|
const start = Date.now();
|
|
11781
12907
|
try {
|
|
11782
|
-
const isGit = await fs17.pathExists(
|
|
12908
|
+
const isGit = await fs17.pathExists(join4(root, ".git"));
|
|
11783
12909
|
if (!isGit) {
|
|
11784
12910
|
return {
|
|
11785
12911
|
...step,
|
|
@@ -11859,18 +12985,18 @@ var EnvironmentBootstrap = class {
|
|
|
11859
12985
|
const start = Date.now();
|
|
11860
12986
|
const details = [];
|
|
11861
12987
|
try {
|
|
11862
|
-
const nvmrcPath =
|
|
12988
|
+
const nvmrcPath = join4(root, ".nvmrc");
|
|
11863
12989
|
const nvmrcExists = await fs17.pathExists(nvmrcPath);
|
|
11864
|
-
const nodeVersionPath =
|
|
12990
|
+
const nodeVersionPath = join4(root, ".node-version");
|
|
11865
12991
|
const nodeVersionExists = await fs17.pathExists(nodeVersionPath);
|
|
11866
12992
|
if (nvmrcExists || nodeVersionExists) {
|
|
11867
12993
|
details.push("Node version file found");
|
|
11868
12994
|
}
|
|
11869
|
-
const toolVersionsPath =
|
|
12995
|
+
const toolVersionsPath = join4(root, ".tool-versions");
|
|
11870
12996
|
if (await fs17.pathExists(toolVersionsPath)) {
|
|
11871
12997
|
details.push(".tool-versions found");
|
|
11872
12998
|
}
|
|
11873
|
-
const rustToolchainPath =
|
|
12999
|
+
const rustToolchainPath = join4(root, "rust-toolchain.toml");
|
|
11874
13000
|
if (await fs17.pathExists(rustToolchainPath)) {
|
|
11875
13001
|
details.push("Rust toolchain found");
|
|
11876
13002
|
}
|
|
@@ -11949,7 +13075,7 @@ var EnvironmentBootstrap = class {
|
|
|
11949
13075
|
|
|
11950
13076
|
// src/core/CodeQualityPipeline.ts
|
|
11951
13077
|
import fs18 from "fs-extra";
|
|
11952
|
-
import { join as
|
|
13078
|
+
import { join as join5 } from "path";
|
|
11953
13079
|
import { exec as exec2 } from "child_process";
|
|
11954
13080
|
import { promisify as promisify2 } from "util";
|
|
11955
13081
|
var execAsync2 = promisify2(exec2);
|
|
@@ -12017,7 +13143,7 @@ var CodeQualityPipeline = class {
|
|
|
12017
13143
|
* @returns Detected scripts
|
|
12018
13144
|
*/
|
|
12019
13145
|
async detectScripts(root) {
|
|
12020
|
-
const pkgPath =
|
|
13146
|
+
const pkgPath = join5(root, "package.json");
|
|
12021
13147
|
if (!await fs18.pathExists(pkgPath)) {
|
|
12022
13148
|
return {};
|
|
12023
13149
|
}
|
|
@@ -12184,7 +13310,7 @@ var CodeQualityPipeline = class {
|
|
|
12184
13310
|
};
|
|
12185
13311
|
|
|
12186
13312
|
// src/core/agent.ts
|
|
12187
|
-
var AutohandAgent = class {
|
|
13313
|
+
var AutohandAgent = class _AutohandAgent {
|
|
12188
13314
|
constructor(llm, files, runtime) {
|
|
12189
13315
|
this.llm = llm;
|
|
12190
13316
|
this.files = files;
|
|
@@ -12193,6 +13319,8 @@ var AutohandAgent = class {
|
|
|
12193
13319
|
this.contextPercentLeft = 100;
|
|
12194
13320
|
this.toolOutputQueue = Promise.resolve();
|
|
12195
13321
|
this.workspaceFiles = [];
|
|
13322
|
+
this.workspaceFilesCachedAt = 0;
|
|
13323
|
+
// 5 seconds
|
|
12196
13324
|
this.isInstructionActive = false;
|
|
12197
13325
|
this.hasPrintedExplorationHeader = false;
|
|
12198
13326
|
this.taskStartedAt = null;
|
|
@@ -12240,10 +13368,10 @@ var AutohandAgent = class {
|
|
|
12240
13368
|
},
|
|
12241
13369
|
onHookOutput: (result) => {
|
|
12242
13370
|
if (result.stdout && !result.response) {
|
|
12243
|
-
console.log(
|
|
13371
|
+
console.log(chalk15.dim(`[hook:${result.hook.event}] ${result.stdout}`));
|
|
12244
13372
|
}
|
|
12245
13373
|
if (result.stderr && !result.blockingError) {
|
|
12246
|
-
console.error(
|
|
13374
|
+
console.error(chalk15.yellow(`[hook:${result.hook.event}] ${result.stderr}`));
|
|
12247
13375
|
}
|
|
12248
13376
|
}
|
|
12249
13377
|
});
|
|
@@ -12412,7 +13540,7 @@ var AutohandAgent = class {
|
|
|
12412
13540
|
this.inkRenderer.addQueuedInstruction(text);
|
|
12413
13541
|
} else if (this.runtime.spinner) {
|
|
12414
13542
|
const originalText = this.runtime.spinner.text;
|
|
12415
|
-
this.runtime.spinner.text =
|
|
13543
|
+
this.runtime.spinner.text = chalk15.cyan(`\u2713 Queued: "${preview}" (${count} pending)`);
|
|
12416
13544
|
setTimeout(() => {
|
|
12417
13545
|
if (this.runtime.spinner) {
|
|
12418
13546
|
this.runtime.spinner.text = originalText;
|
|
@@ -12420,7 +13548,8 @@ var AutohandAgent = class {
|
|
|
12420
13548
|
}, 1500);
|
|
12421
13549
|
}
|
|
12422
13550
|
});
|
|
12423
|
-
|
|
13551
|
+
const sessionMgr = this.sessionManager;
|
|
13552
|
+
const slashContext = {
|
|
12424
13553
|
promptModelSelection: () => this.promptModelSelection(),
|
|
12425
13554
|
createAgentsFile: () => this.createAgentsFile(),
|
|
12426
13555
|
sessionManager: this.sessionManager,
|
|
@@ -12438,8 +13567,16 @@ var AutohandAgent = class {
|
|
|
12438
13567
|
provider: this.activeProvider,
|
|
12439
13568
|
config: runtime.config,
|
|
12440
13569
|
getContextPercentLeft: () => this.contextPercentLeft,
|
|
12441
|
-
getTotalTokensUsed: () => this.totalTokensUsed
|
|
12442
|
-
|
|
13570
|
+
getTotalTokensUsed: () => this.totalTokensUsed,
|
|
13571
|
+
// Share command needs current session - use getter for dynamic access
|
|
13572
|
+
get currentSession() {
|
|
13573
|
+
return sessionMgr.getCurrentSession() ?? void 0;
|
|
13574
|
+
}
|
|
13575
|
+
};
|
|
13576
|
+
this.slashHandler = new SlashCommandHandler(slashContext, SLASH_COMMANDS);
|
|
13577
|
+
}
|
|
13578
|
+
static {
|
|
13579
|
+
this.WORKSPACE_FILES_CACHE_TTL = 5e3;
|
|
12443
13580
|
}
|
|
12444
13581
|
async runInteractive() {
|
|
12445
13582
|
await this.sessionManager.initialize();
|
|
@@ -12505,6 +13642,7 @@ var AutohandAgent = class {
|
|
|
12505
13642
|
turnDuration,
|
|
12506
13643
|
tokensUsed: this.sessionTokensUsed
|
|
12507
13644
|
});
|
|
13645
|
+
this.ensureStdinReady();
|
|
12508
13646
|
if (this.runtime.config.ui?.terminalBell !== false) {
|
|
12509
13647
|
process.stdout.write("\x07");
|
|
12510
13648
|
}
|
|
@@ -12516,6 +13654,7 @@ var AutohandAgent = class {
|
|
|
12516
13654
|
sessionEndReason: "exit",
|
|
12517
13655
|
duration: Date.now() - this.sessionStartedAt
|
|
12518
13656
|
});
|
|
13657
|
+
this.ensureStdinReady();
|
|
12519
13658
|
await this.telemetryManager.endSession("completed");
|
|
12520
13659
|
}
|
|
12521
13660
|
/**
|
|
@@ -12525,17 +13664,17 @@ var AutohandAgent = class {
|
|
|
12525
13664
|
const info = getAutoCommitInfo(this.runtime.workspaceRoot);
|
|
12526
13665
|
if (!info.canCommit) {
|
|
12527
13666
|
if (info.error !== "No changes to commit") {
|
|
12528
|
-
console.log(
|
|
13667
|
+
console.log(chalk15.yellow(`
|
|
12529
13668
|
\u26A0 Cannot auto-commit: ${info.error}`));
|
|
12530
13669
|
}
|
|
12531
13670
|
return;
|
|
12532
13671
|
}
|
|
12533
|
-
console.log(
|
|
13672
|
+
console.log(chalk15.cyan("\n\u{1F9E0} Auto-commit: Changes detected"));
|
|
12534
13673
|
info.filesChanged.slice(0, 5).forEach((file) => {
|
|
12535
|
-
console.log(
|
|
13674
|
+
console.log(chalk15.gray(` ${file}`));
|
|
12536
13675
|
});
|
|
12537
13676
|
if (info.filesChanged.length > 5) {
|
|
12538
|
-
console.log(
|
|
13677
|
+
console.log(chalk15.gray(` ... and ${info.filesChanged.length - 5} more files`));
|
|
12539
13678
|
}
|
|
12540
13679
|
const autoCommitPrompt = `You have uncommitted changes in the repository. Please perform the following steps:
|
|
12541
13680
|
|
|
@@ -12557,11 +13696,11 @@ Diff summary:
|
|
|
12557
13696
|
${info.diffSummary || "Use git diff to see changes"}
|
|
12558
13697
|
|
|
12559
13698
|
If lint or tests fail, report the issues but do NOT commit.`;
|
|
12560
|
-
console.log(
|
|
13699
|
+
console.log(chalk15.cyan("\n\u{1F504} Running lint, test, and generating commit message...\n"));
|
|
12561
13700
|
try {
|
|
12562
13701
|
await this.runInstruction(autoCommitPrompt);
|
|
12563
13702
|
} catch (error) {
|
|
12564
|
-
console.log(
|
|
13703
|
+
console.log(chalk15.red(`
|
|
12565
13704
|
\u2717 Auto-commit failed: ${error.message}`));
|
|
12566
13705
|
}
|
|
12567
13706
|
}
|
|
@@ -12602,7 +13741,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12602
13741
|
}
|
|
12603
13742
|
await this.injectProjectKnowledge();
|
|
12604
13743
|
this.updateContextUsage(this.conversation.history());
|
|
12605
|
-
console.log(
|
|
13744
|
+
console.log(chalk15.cyan(`
|
|
12606
13745
|
\u{1F4C2} Resumed session ${sessionId}`));
|
|
12607
13746
|
await this.telemetryManager.startSession(
|
|
12608
13747
|
sessionId,
|
|
@@ -12611,7 +13750,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12611
13750
|
);
|
|
12612
13751
|
await this.runInteractiveLoop();
|
|
12613
13752
|
} catch (error) {
|
|
12614
|
-
console.error(
|
|
13753
|
+
console.error(chalk15.red(`Failed to resume session: ${error.message}`));
|
|
12615
13754
|
await this.telemetryManager.trackError({
|
|
12616
13755
|
type: "session_resume_failed",
|
|
12617
13756
|
message: error.message,
|
|
@@ -12630,31 +13769,31 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12630
13769
|
if (this.pendingInkInstructions.length > 0) {
|
|
12631
13770
|
instruction = this.pendingInkInstructions.shift() ?? null;
|
|
12632
13771
|
if (instruction) {
|
|
12633
|
-
console.log(
|
|
13772
|
+
console.log(chalk15.cyan(`
|
|
12634
13773
|
\u25B6 Processing queued request: "${instruction.slice(0, 50)}${instruction.length > 50 ? "..." : ""}"`));
|
|
12635
13774
|
const remaining = this.pendingInkInstructions.length;
|
|
12636
13775
|
if (remaining > 0) {
|
|
12637
|
-
console.log(
|
|
13776
|
+
console.log(chalk15.gray(` ${remaining} more request(s) queued`));
|
|
12638
13777
|
}
|
|
12639
13778
|
}
|
|
12640
13779
|
} else if (this.inkRenderer?.hasQueuedInstructions()) {
|
|
12641
13780
|
instruction = this.inkRenderer.dequeueInstruction() ?? null;
|
|
12642
13781
|
if (instruction) {
|
|
12643
|
-
console.log(
|
|
13782
|
+
console.log(chalk15.cyan(`
|
|
12644
13783
|
\u25B6 Processing queued request: "${instruction.slice(0, 50)}${instruction.length > 50 ? "..." : ""}"`));
|
|
12645
13784
|
const remaining = this.inkRenderer.getQueueCount();
|
|
12646
13785
|
if (remaining > 0) {
|
|
12647
|
-
console.log(
|
|
13786
|
+
console.log(chalk15.gray(` ${remaining} more request(s) queued`));
|
|
12648
13787
|
}
|
|
12649
13788
|
}
|
|
12650
13789
|
} else if (this.persistentInput.hasQueued()) {
|
|
12651
13790
|
const queued = this.persistentInput.dequeue();
|
|
12652
13791
|
if (queued) {
|
|
12653
13792
|
instruction = queued.text;
|
|
12654
|
-
console.log(
|
|
13793
|
+
console.log(chalk15.cyan(`
|
|
12655
13794
|
\u25B6 Processing queued request: "${instruction.slice(0, 50)}${instruction.length > 50 ? "..." : ""}"`));
|
|
12656
13795
|
if (this.persistentInput.hasQueued()) {
|
|
12657
|
-
console.log(
|
|
13796
|
+
console.log(chalk15.gray(` ${this.persistentInput.getQueueLength()} more request(s) queued`));
|
|
12658
13797
|
}
|
|
12659
13798
|
}
|
|
12660
13799
|
}
|
|
@@ -12690,6 +13829,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12690
13829
|
turnDuration,
|
|
12691
13830
|
tokensUsed: this.sessionTokensUsed
|
|
12692
13831
|
});
|
|
13832
|
+
this.ensureStdinReady();
|
|
12693
13833
|
if (this.runtime.config.ui?.terminalBell !== false) {
|
|
12694
13834
|
process.stdout.write("\x07");
|
|
12695
13835
|
}
|
|
@@ -12730,9 +13870,9 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12730
13870
|
await session.save();
|
|
12731
13871
|
}
|
|
12732
13872
|
const errorMessage = error.message || "Unknown error occurred";
|
|
12733
|
-
console.error(
|
|
12734
|
-
console.error(
|
|
12735
|
-
console.error(
|
|
13873
|
+
console.error(chalk15.red("\n\u274C An error occurred:"));
|
|
13874
|
+
console.error(chalk15.red(errorMessage));
|
|
13875
|
+
console.error(chalk15.gray(`Error logged to: ${this.errorLogger.getLogPath()}
|
|
12736
13876
|
`));
|
|
12737
13877
|
continue;
|
|
12738
13878
|
}
|
|
@@ -12760,7 +13900,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12760
13900
|
return null;
|
|
12761
13901
|
}
|
|
12762
13902
|
if (normalized === "/") {
|
|
12763
|
-
console.log(
|
|
13903
|
+
console.log(chalk15.gray("Type a slash command name (e.g. /diff) and press Enter."));
|
|
12764
13904
|
return null;
|
|
12765
13905
|
}
|
|
12766
13906
|
const looksLikeFilePath = (text) => {
|
|
@@ -12797,12 +13937,12 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12797
13937
|
}
|
|
12798
13938
|
async handleMemoryStore(content) {
|
|
12799
13939
|
if (!content) {
|
|
12800
|
-
console.log(
|
|
12801
|
-
console.log(
|
|
13940
|
+
console.log(chalk15.gray("Usage: # <text to remember>"));
|
|
13941
|
+
console.log(chalk15.gray("Example: # Always use TypeScript strict mode"));
|
|
12802
13942
|
return;
|
|
12803
13943
|
}
|
|
12804
13944
|
try {
|
|
12805
|
-
const { Select } =
|
|
13945
|
+
const { Select } = enquirer4;
|
|
12806
13946
|
const prompt = new Select({
|
|
12807
13947
|
name: "level",
|
|
12808
13948
|
message: "Where should this memory be stored?",
|
|
@@ -12815,9 +13955,9 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12815
13955
|
const similar = await this.memoryManager.findSimilar(content, level);
|
|
12816
13956
|
if (similar && similar.score >= 0.6) {
|
|
12817
13957
|
console.log();
|
|
12818
|
-
console.log(
|
|
12819
|
-
console.log(
|
|
12820
|
-
const { Confirm } =
|
|
13958
|
+
console.log(chalk15.yellow("Found similar existing memory:"));
|
|
13959
|
+
console.log(chalk15.gray(` "${similar.entry.content}"`));
|
|
13960
|
+
const { Confirm } = enquirer4;
|
|
12821
13961
|
const confirmPrompt = new Confirm({
|
|
12822
13962
|
name: "update",
|
|
12823
13963
|
message: "Update the existing memory instead of creating a new one?"
|
|
@@ -12825,27 +13965,31 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12825
13965
|
const shouldUpdate = await confirmPrompt.run();
|
|
12826
13966
|
if (shouldUpdate) {
|
|
12827
13967
|
await this.memoryManager.updateMemory(similar.entry.id, content, level);
|
|
12828
|
-
console.log(
|
|
13968
|
+
console.log(chalk15.green("Memory updated."));
|
|
12829
13969
|
return;
|
|
12830
13970
|
}
|
|
12831
13971
|
}
|
|
12832
13972
|
await this.memoryManager.store(content, level);
|
|
12833
|
-
console.log(
|
|
13973
|
+
console.log(chalk15.green(`Memory saved to ${level} level.`));
|
|
12834
13974
|
} catch (error) {
|
|
12835
13975
|
if (error.isCanceled) {
|
|
12836
13976
|
return;
|
|
12837
13977
|
}
|
|
12838
|
-
console.error(
|
|
13978
|
+
console.error(chalk15.red("Failed to store memory:"), error.message);
|
|
12839
13979
|
}
|
|
12840
13980
|
}
|
|
12841
13981
|
async listWorkspaceFiles() {
|
|
12842
13982
|
const entries = await fs19.readdir(this.runtime.workspaceRoot);
|
|
12843
13983
|
const sorted = entries.sort((a, b) => a.localeCompare(b));
|
|
12844
|
-
console.log("\n" +
|
|
13984
|
+
console.log("\n" + chalk15.cyan("Workspace files:"));
|
|
12845
13985
|
console.log(sorted.map((entry) => ` - ${entry}`).join("\n"));
|
|
12846
13986
|
console.log();
|
|
12847
13987
|
}
|
|
12848
13988
|
async collectWorkspaceFiles() {
|
|
13989
|
+
const now = Date.now();
|
|
13990
|
+
if (this.workspaceFiles.length > 0 && now - this.workspaceFilesCachedAt < _AutohandAgent.WORKSPACE_FILES_CACHE_TTL) {
|
|
13991
|
+
return this.workspaceFiles;
|
|
13992
|
+
}
|
|
12849
13993
|
const git = spawnSync5("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
|
|
12850
13994
|
cwd: this.runtime.workspaceRoot,
|
|
12851
13995
|
encoding: "utf8"
|
|
@@ -12858,9 +14002,11 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12858
14002
|
files.push(file);
|
|
12859
14003
|
}
|
|
12860
14004
|
});
|
|
14005
|
+
this.workspaceFilesCachedAt = now;
|
|
12861
14006
|
return files;
|
|
12862
14007
|
}
|
|
12863
14008
|
await this.walkWorkspace(this.runtime.workspaceRoot, files);
|
|
14009
|
+
this.workspaceFilesCachedAt = now;
|
|
12864
14010
|
return files;
|
|
12865
14011
|
}
|
|
12866
14012
|
async walkWorkspace(current, acc) {
|
|
@@ -12898,7 +14044,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12898
14044
|
encoding: "utf8"
|
|
12899
14045
|
});
|
|
12900
14046
|
if (status.status === 0 && status.stdout) {
|
|
12901
|
-
console.log("\n" +
|
|
14047
|
+
console.log("\n" + chalk15.cyan("Git status:"));
|
|
12902
14048
|
console.log(status.stdout.trim() + "\n");
|
|
12903
14049
|
}
|
|
12904
14050
|
const diff = spawnSync5("git", ["diff", "--color=always"], {
|
|
@@ -12906,18 +14052,18 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12906
14052
|
encoding: "utf8"
|
|
12907
14053
|
});
|
|
12908
14054
|
if (diff.status === 0) {
|
|
12909
|
-
console.log(
|
|
12910
|
-
console.log(diff.stdout ||
|
|
14055
|
+
console.log(chalk15.cyan("Git diff:"));
|
|
14056
|
+
console.log(diff.stdout || chalk15.gray("No diff."));
|
|
12911
14057
|
} else {
|
|
12912
|
-
console.log(
|
|
14058
|
+
console.log(chalk15.yellow("Unable to compute git diff. Is this a git repository?"));
|
|
12913
14059
|
}
|
|
12914
14060
|
}
|
|
12915
14061
|
async undoLastMutation() {
|
|
12916
14062
|
try {
|
|
12917
14063
|
await this.files.undoLast();
|
|
12918
|
-
console.log(
|
|
14064
|
+
console.log(chalk15.green("Reverted last mutation."));
|
|
12919
14065
|
} catch (error) {
|
|
12920
|
-
console.log(
|
|
14066
|
+
console.log(chalk15.yellow(error.message));
|
|
12921
14067
|
}
|
|
12922
14068
|
}
|
|
12923
14069
|
async promptModelSelection() {
|
|
@@ -12925,16 +14071,16 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12925
14071
|
const allProviders = ProviderFactory.getProviderNames();
|
|
12926
14072
|
const providerChoices = allProviders.map((name) => {
|
|
12927
14073
|
const isConfigured = this.isProviderConfigured(name);
|
|
12928
|
-
const indicator = isConfigured ?
|
|
12929
|
-
const current = name === this.activeProvider ?
|
|
12930
|
-
const siliconNote = name === "mlx" ?
|
|
14074
|
+
const indicator = isConfigured ? chalk15.green("\u25CF") : chalk15.red("\u25CB");
|
|
14075
|
+
const current = name === this.activeProvider ? chalk15.cyan(" (current)") : "";
|
|
14076
|
+
const siliconNote = name === "mlx" ? chalk15.gray(" (Apple Silicon)") : "";
|
|
12931
14077
|
return {
|
|
12932
14078
|
name,
|
|
12933
14079
|
message: `${indicator} ${name}${current}${siliconNote}`,
|
|
12934
14080
|
value: name
|
|
12935
14081
|
};
|
|
12936
14082
|
});
|
|
12937
|
-
const providerAnswer = await
|
|
14083
|
+
const providerAnswer = await enquirer4.prompt([
|
|
12938
14084
|
{
|
|
12939
14085
|
type: "select",
|
|
12940
14086
|
name: "provider",
|
|
@@ -12944,7 +14090,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
|
|
|
12944
14090
|
]);
|
|
12945
14091
|
const selectedProvider = providerAnswer.provider;
|
|
12946
14092
|
if (!this.isProviderConfigured(selectedProvider)) {
|
|
12947
|
-
console.log(
|
|
14093
|
+
console.log(chalk15.yellow(`
|
|
12948
14094
|
${selectedProvider} is not configured yet. Let's set it up!
|
|
12949
14095
|
`));
|
|
12950
14096
|
await this.configureProvider(selectedProvider);
|
|
@@ -12953,7 +14099,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
12953
14099
|
await this.changeProviderModel(selectedProvider);
|
|
12954
14100
|
} catch (error) {
|
|
12955
14101
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
12956
|
-
console.log(
|
|
14102
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
12957
14103
|
return;
|
|
12958
14104
|
}
|
|
12959
14105
|
throw error;
|
|
@@ -12988,9 +14134,9 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
12988
14134
|
}
|
|
12989
14135
|
async configureOpenRouter() {
|
|
12990
14136
|
try {
|
|
12991
|
-
console.log(
|
|
12992
|
-
console.log(
|
|
12993
|
-
const answers = await
|
|
14137
|
+
console.log(chalk15.cyan("OpenRouter Configuration"));
|
|
14138
|
+
console.log(chalk15.gray("Get your API key at: https://openrouter.ai/keys\n"));
|
|
14139
|
+
const answers = await enquirer4.prompt([
|
|
12994
14140
|
{
|
|
12995
14141
|
type: "password",
|
|
12996
14142
|
name: "apiKey",
|
|
@@ -13012,10 +14158,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13012
14158
|
this.runtime.options.model = answers.model;
|
|
13013
14159
|
await saveConfig(this.runtime.config);
|
|
13014
14160
|
this.resetLlmClient("openrouter", answers.model);
|
|
13015
|
-
console.log(
|
|
14161
|
+
console.log(chalk15.green("\n\u2713 OpenRouter configured successfully!"));
|
|
13016
14162
|
} catch (error) {
|
|
13017
14163
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13018
|
-
console.log(
|
|
14164
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
13019
14165
|
return;
|
|
13020
14166
|
}
|
|
13021
14167
|
throw error;
|
|
@@ -13023,8 +14169,8 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13023
14169
|
}
|
|
13024
14170
|
async configureOllama() {
|
|
13025
14171
|
try {
|
|
13026
|
-
console.log(
|
|
13027
|
-
console.log(
|
|
14172
|
+
console.log(chalk15.cyan("Ollama Configuration"));
|
|
14173
|
+
console.log(chalk15.gray("Make sure Ollama is running: ollama serve\n"));
|
|
13028
14174
|
const ollamaUrl = "http://localhost:11434";
|
|
13029
14175
|
let availableModels = [];
|
|
13030
14176
|
try {
|
|
@@ -13034,13 +14180,13 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13034
14180
|
availableModels = data.models?.map((m) => m.name) || [];
|
|
13035
14181
|
}
|
|
13036
14182
|
} catch {
|
|
13037
|
-
console.log(
|
|
14183
|
+
console.log(chalk15.yellow("\u26A0 Could not connect to Ollama. Make sure it's running.\n"));
|
|
13038
14184
|
}
|
|
13039
14185
|
let modelAnswer;
|
|
13040
14186
|
if (availableModels.length > 0) {
|
|
13041
|
-
console.log(
|
|
14187
|
+
console.log(chalk15.green(`Found ${availableModels.length} model(s)
|
|
13042
14188
|
`));
|
|
13043
|
-
modelAnswer = await
|
|
14189
|
+
modelAnswer = await enquirer4.prompt([
|
|
13044
14190
|
{
|
|
13045
14191
|
type: "select",
|
|
13046
14192
|
name: "model",
|
|
@@ -13049,7 +14195,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13049
14195
|
}
|
|
13050
14196
|
]);
|
|
13051
14197
|
} else {
|
|
13052
|
-
modelAnswer = await
|
|
14198
|
+
modelAnswer = await enquirer4.prompt([
|
|
13053
14199
|
{
|
|
13054
14200
|
type: "input",
|
|
13055
14201
|
name: "model",
|
|
@@ -13066,10 +14212,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13066
14212
|
this.runtime.options.model = modelAnswer.model;
|
|
13067
14213
|
await saveConfig(this.runtime.config);
|
|
13068
14214
|
this.resetLlmClient("ollama", modelAnswer.model);
|
|
13069
|
-
console.log(
|
|
14215
|
+
console.log(chalk15.green("\n\u2713 Ollama configured successfully!"));
|
|
13070
14216
|
} catch (error) {
|
|
13071
14217
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13072
|
-
console.log(
|
|
14218
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
13073
14219
|
return;
|
|
13074
14220
|
}
|
|
13075
14221
|
throw error;
|
|
@@ -13077,9 +14223,9 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13077
14223
|
}
|
|
13078
14224
|
async configureLlamaCpp() {
|
|
13079
14225
|
try {
|
|
13080
|
-
console.log(
|
|
13081
|
-
console.log(
|
|
13082
|
-
const answers = await
|
|
14226
|
+
console.log(chalk15.cyan("llama.cpp Configuration"));
|
|
14227
|
+
console.log(chalk15.gray("Make sure llama.cpp server is running: ./server -m model.gguf\n"));
|
|
14228
|
+
const answers = await enquirer4.prompt([
|
|
13083
14229
|
{
|
|
13084
14230
|
type: "input",
|
|
13085
14231
|
name: "port",
|
|
@@ -13102,10 +14248,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13102
14248
|
this.runtime.options.model = answers.model;
|
|
13103
14249
|
await saveConfig(this.runtime.config);
|
|
13104
14250
|
this.resetLlmClient("llamacpp", answers.model);
|
|
13105
|
-
console.log(
|
|
14251
|
+
console.log(chalk15.green("\n\u2713 llama.cpp configured successfully!"));
|
|
13106
14252
|
} catch (error) {
|
|
13107
14253
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13108
|
-
console.log(
|
|
14254
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
13109
14255
|
return;
|
|
13110
14256
|
}
|
|
13111
14257
|
throw error;
|
|
@@ -13113,8 +14259,8 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13113
14259
|
}
|
|
13114
14260
|
async configureOpenAI() {
|
|
13115
14261
|
try {
|
|
13116
|
-
console.log(
|
|
13117
|
-
console.log(
|
|
14262
|
+
console.log(chalk15.cyan("OpenAI Configuration"));
|
|
14263
|
+
console.log(chalk15.gray("Get your API key at: https://platform.openai.com/api-keys\n"));
|
|
13118
14264
|
const modelChoices = [
|
|
13119
14265
|
"gpt-4o",
|
|
13120
14266
|
"gpt-4o-mini",
|
|
@@ -13122,7 +14268,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13122
14268
|
"gpt-4",
|
|
13123
14269
|
"gpt-3.5-turbo"
|
|
13124
14270
|
];
|
|
13125
|
-
const answers = await
|
|
14271
|
+
const answers = await enquirer4.prompt([
|
|
13126
14272
|
{
|
|
13127
14273
|
type: "password",
|
|
13128
14274
|
name: "apiKey",
|
|
@@ -13144,10 +14290,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13144
14290
|
this.runtime.options.model = answers.model;
|
|
13145
14291
|
await saveConfig(this.runtime.config);
|
|
13146
14292
|
this.resetLlmClient("openai", answers.model);
|
|
13147
|
-
console.log(
|
|
14293
|
+
console.log(chalk15.green("\n\u2713 OpenAI configured successfully!"));
|
|
13148
14294
|
} catch (error) {
|
|
13149
14295
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13150
|
-
console.log(
|
|
14296
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
13151
14297
|
return;
|
|
13152
14298
|
}
|
|
13153
14299
|
throw error;
|
|
@@ -13155,9 +14301,9 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13155
14301
|
}
|
|
13156
14302
|
async configureMLX() {
|
|
13157
14303
|
try {
|
|
13158
|
-
console.log(
|
|
13159
|
-
console.log(
|
|
13160
|
-
console.log(
|
|
14304
|
+
console.log(chalk15.cyan("MLX Configuration (Apple Silicon)"));
|
|
14305
|
+
console.log(chalk15.gray("MLX provides local LLM inference optimized for Apple Silicon."));
|
|
14306
|
+
console.log(chalk15.gray("Make sure mlx-lm server is running: mlx_lm.server --model <model-name>\n"));
|
|
13161
14307
|
const mlxUrl = "http://localhost:8080";
|
|
13162
14308
|
let availableModels = [];
|
|
13163
14309
|
try {
|
|
@@ -13167,11 +14313,11 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13167
14313
|
availableModels = data.data?.map((m) => m.id) || [];
|
|
13168
14314
|
}
|
|
13169
14315
|
} catch {
|
|
13170
|
-
console.log(
|
|
14316
|
+
console.log(chalk15.yellow("\u26A0 Could not connect to MLX server. Make sure it's running.\n"));
|
|
13171
14317
|
}
|
|
13172
14318
|
let modelAnswer;
|
|
13173
14319
|
if (availableModels.length > 0) {
|
|
13174
|
-
modelAnswer = await
|
|
14320
|
+
modelAnswer = await enquirer4.prompt([
|
|
13175
14321
|
{
|
|
13176
14322
|
type: "select",
|
|
13177
14323
|
name: "model",
|
|
@@ -13180,7 +14326,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13180
14326
|
}
|
|
13181
14327
|
]);
|
|
13182
14328
|
} else {
|
|
13183
|
-
modelAnswer = await
|
|
14329
|
+
modelAnswer = await enquirer4.prompt([
|
|
13184
14330
|
{
|
|
13185
14331
|
type: "input",
|
|
13186
14332
|
name: "model",
|
|
@@ -13197,10 +14343,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13197
14343
|
this.runtime.options.model = modelAnswer.model;
|
|
13198
14344
|
await saveConfig(this.runtime.config);
|
|
13199
14345
|
this.resetLlmClient("mlx", modelAnswer.model);
|
|
13200
|
-
console.log(
|
|
14346
|
+
console.log(chalk15.green("\n\u2713 MLX configured successfully!"));
|
|
13201
14347
|
} catch (error) {
|
|
13202
14348
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13203
|
-
console.log(
|
|
14349
|
+
console.log(chalk15.gray("\nConfiguration cancelled."));
|
|
13204
14350
|
return;
|
|
13205
14351
|
}
|
|
13206
14352
|
throw error;
|
|
@@ -13210,6 +14356,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13210
14356
|
try {
|
|
13211
14357
|
const currentSettings = getProviderConfig(this.runtime.config, provider);
|
|
13212
14358
|
const currentModel = this.runtime.options.model ?? currentSettings?.model ?? "";
|
|
14359
|
+
if (provider === "openai" || provider === "openrouter") {
|
|
14360
|
+
await this.changeCloudProviderSettings(provider, currentModel, currentSettings);
|
|
14361
|
+
return;
|
|
14362
|
+
}
|
|
13213
14363
|
if (provider === "ollama" && currentSettings?.baseUrl) {
|
|
13214
14364
|
try {
|
|
13215
14365
|
const response = await fetch(`${currentSettings.baseUrl}/api/tags`);
|
|
@@ -13217,7 +14367,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13217
14367
|
const data = await response.json();
|
|
13218
14368
|
const models = data.models?.map((m) => m.name) || [];
|
|
13219
14369
|
if (models.length > 0) {
|
|
13220
|
-
const answer2 = await
|
|
14370
|
+
const answer2 = await enquirer4.prompt([
|
|
13221
14371
|
{
|
|
13222
14372
|
type: "select",
|
|
13223
14373
|
name: "model",
|
|
@@ -13233,21 +14383,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13233
14383
|
} catch {
|
|
13234
14384
|
}
|
|
13235
14385
|
}
|
|
13236
|
-
|
|
13237
|
-
const models = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
|
|
13238
|
-
const answer2 = await enquirer3.prompt([
|
|
13239
|
-
{
|
|
13240
|
-
type: "select",
|
|
13241
|
-
name: "model",
|
|
13242
|
-
message: "Select a model",
|
|
13243
|
-
choices: models,
|
|
13244
|
-
initial: Math.max(0, models.indexOf(currentModel))
|
|
13245
|
-
}
|
|
13246
|
-
]);
|
|
13247
|
-
await this.applyModelChange(provider, answer2.model, currentModel);
|
|
13248
|
-
return;
|
|
13249
|
-
}
|
|
13250
|
-
const answer = await enquirer3.prompt([
|
|
14386
|
+
const answer = await enquirer4.prompt([
|
|
13251
14387
|
{
|
|
13252
14388
|
type: "input",
|
|
13253
14389
|
name: "model",
|
|
@@ -13258,15 +14394,174 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13258
14394
|
await this.applyModelChange(provider, answer.model?.trim(), currentModel);
|
|
13259
14395
|
} catch (error) {
|
|
13260
14396
|
if (error.name === "ExitPromptError" || error.message?.includes("canceled")) {
|
|
13261
|
-
console.log(
|
|
14397
|
+
console.log(chalk15.gray("\nModel change cancelled."));
|
|
13262
14398
|
return;
|
|
13263
14399
|
}
|
|
13264
14400
|
throw error;
|
|
13265
14401
|
}
|
|
13266
14402
|
}
|
|
14403
|
+
async changeCloudProviderSettings(provider, currentModel, currentSettings) {
|
|
14404
|
+
const providerName = provider === "openai" ? "OpenAI" : "OpenRouter";
|
|
14405
|
+
const maskedKey = currentSettings?.apiKey ? `...${currentSettings.apiKey.slice(-4)}` : "not set";
|
|
14406
|
+
console.log(chalk15.cyan(`
|
|
14407
|
+
${providerName} Settings`));
|
|
14408
|
+
console.log(chalk15.gray(`Current model: ${currentModel || "not set"}`));
|
|
14409
|
+
console.log(chalk15.gray(`Current API key: ${maskedKey}
|
|
14410
|
+
`));
|
|
14411
|
+
const { action } = await enquirer4.prompt([
|
|
14412
|
+
{
|
|
14413
|
+
type: "select",
|
|
14414
|
+
name: "action",
|
|
14415
|
+
message: "What would you like to change?",
|
|
14416
|
+
choices: [
|
|
14417
|
+
{ name: "model", message: "Change model only" },
|
|
14418
|
+
{ name: "apiKey", message: "Change API key only" },
|
|
14419
|
+
{ name: "both", message: "Change both model and API key" }
|
|
14420
|
+
]
|
|
14421
|
+
}
|
|
14422
|
+
]);
|
|
14423
|
+
let newModel = currentModel;
|
|
14424
|
+
let newApiKey = currentSettings?.apiKey || "";
|
|
14425
|
+
if (action === "apiKey" || action === "both") {
|
|
14426
|
+
const keyUrl = provider === "openai" ? "https://platform.openai.com/api-keys" : "https://openrouter.ai/keys";
|
|
14427
|
+
console.log(chalk15.gray(`
|
|
14428
|
+
Get your API key at: ${keyUrl}
|
|
14429
|
+
`));
|
|
14430
|
+
const { apiKey } = await enquirer4.prompt([
|
|
14431
|
+
{
|
|
14432
|
+
type: "password",
|
|
14433
|
+
name: "apiKey",
|
|
14434
|
+
message: `Enter your ${providerName} API key`,
|
|
14435
|
+
validate: (val) => {
|
|
14436
|
+
const v = val;
|
|
14437
|
+
if (!v?.trim()) return "API key is required";
|
|
14438
|
+
if (v.length < 10) return "API key seems too short";
|
|
14439
|
+
return true;
|
|
14440
|
+
}
|
|
14441
|
+
}
|
|
14442
|
+
]);
|
|
14443
|
+
console.log(chalk15.gray("\nValidating API key..."));
|
|
14444
|
+
const validationResult = await this.validateApiKey(provider, apiKey.trim());
|
|
14445
|
+
if (!validationResult.valid) {
|
|
14446
|
+
console.log(chalk15.red(`
|
|
14447
|
+
\u2717 ${validationResult.error}`));
|
|
14448
|
+
console.log(chalk15.gray(validationResult.hint || ""));
|
|
14449
|
+
return;
|
|
14450
|
+
}
|
|
14451
|
+
console.log(chalk15.green("\u2713 API key is valid\n"));
|
|
14452
|
+
newApiKey = apiKey.trim();
|
|
14453
|
+
}
|
|
14454
|
+
if (action === "model" || action === "both") {
|
|
14455
|
+
if (provider === "openai") {
|
|
14456
|
+
const models = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo", "o1", "o1-mini"];
|
|
14457
|
+
const { model } = await enquirer4.prompt([
|
|
14458
|
+
{
|
|
14459
|
+
type: "select",
|
|
14460
|
+
name: "model",
|
|
14461
|
+
message: "Select a model",
|
|
14462
|
+
choices: models,
|
|
14463
|
+
initial: Math.max(0, models.indexOf(currentModel))
|
|
14464
|
+
}
|
|
14465
|
+
]);
|
|
14466
|
+
newModel = model;
|
|
14467
|
+
} else {
|
|
14468
|
+
const { model } = await enquirer4.prompt([
|
|
14469
|
+
{
|
|
14470
|
+
type: "input",
|
|
14471
|
+
name: "model",
|
|
14472
|
+
message: "Enter the model ID",
|
|
14473
|
+
initial: currentModel || "anthropic/claude-sonnet-4-20250514"
|
|
14474
|
+
}
|
|
14475
|
+
]);
|
|
14476
|
+
newModel = model.trim();
|
|
14477
|
+
}
|
|
14478
|
+
}
|
|
14479
|
+
const baseUrl = provider === "openai" ? "https://api.openai.com/v1" : "https://openrouter.ai/api/v1";
|
|
14480
|
+
this.runtime.config[provider] = {
|
|
14481
|
+
apiKey: newApiKey,
|
|
14482
|
+
baseUrl,
|
|
14483
|
+
model: newModel
|
|
14484
|
+
};
|
|
14485
|
+
this.runtime.config.provider = provider;
|
|
14486
|
+
this.runtime.options.model = newModel;
|
|
14487
|
+
await saveConfig(this.runtime.config);
|
|
14488
|
+
this.resetLlmClient(provider, newModel);
|
|
14489
|
+
this.contextWindow = getContextWindow(newModel);
|
|
14490
|
+
this.contextPercentLeft = 100;
|
|
14491
|
+
this.emitStatus();
|
|
14492
|
+
console.log(chalk15.green(`
|
|
14493
|
+
\u2713 ${providerName} settings updated successfully!`));
|
|
14494
|
+
console.log(chalk15.gray(` Provider: ${provider}`));
|
|
14495
|
+
console.log(chalk15.gray(` Model: ${newModel}`));
|
|
14496
|
+
}
|
|
14497
|
+
async validateApiKey(provider, apiKey) {
|
|
14498
|
+
try {
|
|
14499
|
+
const baseUrl = provider === "openai" ? "https://api.openai.com/v1" : "https://openrouter.ai/api/v1";
|
|
14500
|
+
const response = await fetch(`${baseUrl}/models`, {
|
|
14501
|
+
method: "GET",
|
|
14502
|
+
headers: {
|
|
14503
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
14504
|
+
"Content-Type": "application/json",
|
|
14505
|
+
...provider === "openrouter" && {
|
|
14506
|
+
"HTTP-Referer": "https://autohand.dev",
|
|
14507
|
+
"X-Title": "Autohand CLI"
|
|
14508
|
+
}
|
|
14509
|
+
}
|
|
14510
|
+
});
|
|
14511
|
+
if (response.ok) {
|
|
14512
|
+
return { valid: true };
|
|
14513
|
+
}
|
|
14514
|
+
const status = response.status;
|
|
14515
|
+
let errorData = {};
|
|
14516
|
+
try {
|
|
14517
|
+
errorData = await response.json();
|
|
14518
|
+
} catch {
|
|
14519
|
+
}
|
|
14520
|
+
if (status === 401) {
|
|
14521
|
+
return {
|
|
14522
|
+
valid: false,
|
|
14523
|
+
error: "Invalid API key",
|
|
14524
|
+
hint: provider === "openai" ? "Check that your API key is correct at https://platform.openai.com/api-keys" : "Check that your API key is correct at https://openrouter.ai/keys"
|
|
14525
|
+
};
|
|
14526
|
+
}
|
|
14527
|
+
if (status === 403) {
|
|
14528
|
+
return {
|
|
14529
|
+
valid: false,
|
|
14530
|
+
error: "API key does not have permission",
|
|
14531
|
+
hint: "Your API key may have restricted permissions or your account may need to add a payment method."
|
|
14532
|
+
};
|
|
14533
|
+
}
|
|
14534
|
+
if (status === 429) {
|
|
14535
|
+
return {
|
|
14536
|
+
valid: false,
|
|
14537
|
+
error: "Rate limited or quota exceeded",
|
|
14538
|
+
hint: "You may have exceeded your API quota. Check your usage and billing settings."
|
|
14539
|
+
};
|
|
14540
|
+
}
|
|
14541
|
+
return {
|
|
14542
|
+
valid: false,
|
|
14543
|
+
error: errorData?.error?.message || `API returned status ${status}`,
|
|
14544
|
+
hint: "Please verify your API key and try again."
|
|
14545
|
+
};
|
|
14546
|
+
} catch (error) {
|
|
14547
|
+
const err = error;
|
|
14548
|
+
if (err.message?.includes("fetch") || err.message?.includes("network")) {
|
|
14549
|
+
return {
|
|
14550
|
+
valid: false,
|
|
14551
|
+
error: "Network error - could not reach the API",
|
|
14552
|
+
hint: "Check your internet connection and try again."
|
|
14553
|
+
};
|
|
14554
|
+
}
|
|
14555
|
+
return {
|
|
14556
|
+
valid: false,
|
|
14557
|
+
error: `Validation failed: ${err.message}`,
|
|
14558
|
+
hint: "Please try again or check your network connection."
|
|
14559
|
+
};
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
13267
14562
|
async applyModelChange(provider, newModel, currentModel) {
|
|
13268
14563
|
if (!newModel || newModel === currentModel && provider === this.activeProvider) {
|
|
13269
|
-
console.log(
|
|
14564
|
+
console.log(chalk15.gray("Model unchanged."));
|
|
13270
14565
|
return;
|
|
13271
14566
|
}
|
|
13272
14567
|
const previousModel = this.runtime.options.model;
|
|
@@ -13283,10 +14578,10 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13283
14578
|
toModel: newModel,
|
|
13284
14579
|
provider
|
|
13285
14580
|
});
|
|
13286
|
-
console.log(
|
|
14581
|
+
console.log(chalk15.green(`\u2713 Using ${provider} model ${newModel}`));
|
|
13287
14582
|
}
|
|
13288
14583
|
async promptApprovalMode() {
|
|
13289
|
-
const answer = await
|
|
14584
|
+
const answer = await enquirer4.prompt([
|
|
13290
14585
|
{
|
|
13291
14586
|
type: "select",
|
|
13292
14587
|
name: "mode",
|
|
@@ -13300,21 +14595,37 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
13300
14595
|
]);
|
|
13301
14596
|
this.runtime.options.yes = answer.mode === "prompt";
|
|
13302
14597
|
console.log(
|
|
13303
|
-
answer.mode === "prompt" ?
|
|
14598
|
+
answer.mode === "prompt" ? chalk15.yellow("Auto-confirm enabled. Use responsibly.") : chalk15.green("Manual approvals required before risky writes.")
|
|
13304
14599
|
);
|
|
13305
14600
|
}
|
|
13306
14601
|
async createAgentsFile() {
|
|
13307
14602
|
const target = path18.join(this.runtime.workspaceRoot, "AGENTS.md");
|
|
13308
14603
|
if (await fs19.pathExists(target)) {
|
|
13309
|
-
console.log(
|
|
14604
|
+
console.log(chalk15.gray("AGENTS.md already exists in this workspace."));
|
|
13310
14605
|
return;
|
|
13311
14606
|
}
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
14607
|
+
console.log(chalk15.gray("Analyzing project structure..."));
|
|
14608
|
+
const analyzer = new ProjectAnalyzer(this.runtime.workspaceRoot);
|
|
14609
|
+
const projectInfo = await analyzer.analyze();
|
|
14610
|
+
if (Object.keys(projectInfo).length > 0) {
|
|
14611
|
+
console.log(chalk15.gray("Detected:"));
|
|
14612
|
+
if (projectInfo.language) {
|
|
14613
|
+
console.log(chalk15.white(` - Language: ${projectInfo.language}`));
|
|
14614
|
+
}
|
|
14615
|
+
if (projectInfo.framework) {
|
|
14616
|
+
console.log(chalk15.white(` - Framework: ${projectInfo.framework}`));
|
|
14617
|
+
}
|
|
14618
|
+
if (projectInfo.packageManager) {
|
|
14619
|
+
console.log(chalk15.white(` - Package manager: ${projectInfo.packageManager}`));
|
|
14620
|
+
}
|
|
14621
|
+
if (projectInfo.testFramework) {
|
|
14622
|
+
console.log(chalk15.white(` - Test framework: ${projectInfo.testFramework}`));
|
|
14623
|
+
}
|
|
14624
|
+
}
|
|
14625
|
+
const generator = new AgentsGenerator();
|
|
14626
|
+
const content = generator.generateContent(projectInfo);
|
|
14627
|
+
await fs19.writeFile(target, content, "utf8");
|
|
14628
|
+
console.log(chalk15.green("Created AGENTS.md based on your project. Customize it to guide the agent."));
|
|
13318
14629
|
}
|
|
13319
14630
|
/**
|
|
13320
14631
|
* Detect if instruction is simple chat that doesn't need tools
|
|
@@ -13356,7 +14667,7 @@ Describe how Autohand should work in this repo. Include framework commands, test
|
|
|
13356
14667
|
return true;
|
|
13357
14668
|
} catch (error) {
|
|
13358
14669
|
if (error instanceof Error) {
|
|
13359
|
-
console.error(
|
|
14670
|
+
console.error(chalk15.red(error.message));
|
|
13360
14671
|
}
|
|
13361
14672
|
return false;
|
|
13362
14673
|
} finally {
|
|
@@ -13375,7 +14686,7 @@ Describe how Autohand should work in this repo. Include framework commands, test
|
|
|
13375
14686
|
if (intentResult.intent === "implementation") {
|
|
13376
14687
|
const bootstrapResult = await this.runEnvironmentBootstrap();
|
|
13377
14688
|
if (!bootstrapResult.success) {
|
|
13378
|
-
console.log(
|
|
14689
|
+
console.log(chalk15.red("\n[BLOCKED] Environment setup failed. Fix issues before proceeding."));
|
|
13379
14690
|
this.isInstructionActive = false;
|
|
13380
14691
|
return false;
|
|
13381
14692
|
}
|
|
@@ -13388,7 +14699,7 @@ Describe how Autohand should work in this repo. Include framework commands, test
|
|
|
13388
14699
|
canceledByUser = true;
|
|
13389
14700
|
this.stopStatusUpdates();
|
|
13390
14701
|
this.stopUI();
|
|
13391
|
-
console.log("\n" +
|
|
14702
|
+
console.log("\n" + chalk15.yellow("Request canceled by user (ESC)."));
|
|
13392
14703
|
}
|
|
13393
14704
|
});
|
|
13394
14705
|
const cleanupEsc = this.useInkRenderer ? () => {
|
|
@@ -13397,7 +14708,7 @@ Describe how Autohand should work in this repo. Include framework commands, test
|
|
|
13397
14708
|
canceledByUser = true;
|
|
13398
14709
|
this.stopStatusUpdates();
|
|
13399
14710
|
this.stopUI();
|
|
13400
|
-
console.log("\n" +
|
|
14711
|
+
console.log("\n" + chalk15.yellow("Request canceled by user (ESC)."));
|
|
13401
14712
|
}
|
|
13402
14713
|
}, true);
|
|
13403
14714
|
const stopPreparation = this.startPreparationStatus(instruction);
|
|
@@ -13419,7 +14730,7 @@ Describe how Autohand should work in this repo. Include framework commands, test
|
|
|
13419
14730
|
}
|
|
13420
14731
|
if (error instanceof ProviderNotConfiguredError) {
|
|
13421
14732
|
this.cleanupUI();
|
|
13422
|
-
console.log(
|
|
14733
|
+
console.log(chalk15.yellow(`
|
|
13423
14734
|
No provider is configured yet. Let's set one up!
|
|
13424
14735
|
`));
|
|
13425
14736
|
await this.promptModelSelection();
|
|
@@ -13431,9 +14742,9 @@ No provider is configured yet. Let's set one up!
|
|
|
13431
14742
|
if (this.isRetryableSessionError(err) && this.sessionRetryCount < maxRetries) {
|
|
13432
14743
|
this.sessionRetryCount++;
|
|
13433
14744
|
await this.submitSessionFailureBugReport(err, this.sessionRetryCount, maxRetries);
|
|
13434
|
-
console.log(
|
|
14745
|
+
console.log(chalk15.yellow(`
|
|
13435
14746
|
\u26A0 Session encountered an error: ${err.message}`));
|
|
13436
|
-
console.log(
|
|
14747
|
+
console.log(chalk15.cyan(` Attempting recovery (${this.sessionRetryCount}/${maxRetries})...`));
|
|
13437
14748
|
const delay = baseDelay * Math.pow(1.5, this.sessionRetryCount - 1);
|
|
13438
14749
|
await this.sleep(delay);
|
|
13439
14750
|
this.injectContinuationMessage(err, this.sessionRetryCount);
|
|
@@ -13456,7 +14767,7 @@ No provider is configured yet. Let's set one up!
|
|
|
13456
14767
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
13457
14768
|
this.emitOutput({ type: "error", content: errorMessage });
|
|
13458
14769
|
if (error instanceof Error) {
|
|
13459
|
-
console.error(
|
|
14770
|
+
console.error(chalk15.red(error.message));
|
|
13460
14771
|
} else {
|
|
13461
14772
|
console.error(error);
|
|
13462
14773
|
}
|
|
@@ -13470,7 +14781,7 @@ No provider is configured yet. Let's set one up!
|
|
|
13470
14781
|
const tokens = this.formatTokens(this.totalTokensUsed);
|
|
13471
14782
|
const queueCount = this.pendingInkInstructions.length + (this.inkRenderer?.getQueueCount() ?? 0) + this.persistentInput.getQueueLength();
|
|
13472
14783
|
const queueStatus = queueCount > 0 ? ` \xB7 ${queueCount} queued` : "";
|
|
13473
|
-
console.log(
|
|
14784
|
+
console.log(chalk15.gray(`
|
|
13474
14785
|
Completed in ${elapsed} \xB7 ${tokens} used${queueStatus}`));
|
|
13475
14786
|
}
|
|
13476
14787
|
this.sessionTokensUsed += this.totalTokensUsed;
|
|
@@ -13546,7 +14857,7 @@ Completed in ${elapsed} \xB7 ${tokens} used${queueStatus}`));
|
|
|
13546
14857
|
duration: sessionDuration
|
|
13547
14858
|
});
|
|
13548
14859
|
if (!session) {
|
|
13549
|
-
console.log(
|
|
14860
|
+
console.log(chalk15.gray("Ending Autohand session."));
|
|
13550
14861
|
await this.telemetryManager.shutdown();
|
|
13551
14862
|
return;
|
|
13552
14863
|
}
|
|
@@ -13566,12 +14877,12 @@ Completed in ${elapsed} \xB7 ${tokens} used${queueStatus}`));
|
|
|
13566
14877
|
await this.telemetryManager.endSession("completed");
|
|
13567
14878
|
await this.telemetryManager.shutdown();
|
|
13568
14879
|
await this.sessionManager.closeSession(summary);
|
|
13569
|
-
console.log(
|
|
13570
|
-
console.log(
|
|
14880
|
+
console.log(chalk15.gray("\nEnding Autohand session.\n"));
|
|
14881
|
+
console.log(chalk15.cyan(`\u{1F4BE} Session saved: ${session.metadata.sessionId}`));
|
|
13571
14882
|
if (syncResult.success) {
|
|
13572
|
-
console.log(
|
|
14883
|
+
console.log(chalk15.gray(` Synced to cloud: ${syncResult.id}`));
|
|
13573
14884
|
}
|
|
13574
|
-
console.log(
|
|
14885
|
+
console.log(chalk15.gray(` Resume with: autohand resume ${session.metadata.sessionId}
|
|
13575
14886
|
`));
|
|
13576
14887
|
}
|
|
13577
14888
|
async runReactLoop(abortController) {
|
|
@@ -13599,7 +14910,7 @@ Completed in ${elapsed} \xB7 ${tokens} used${queueStatus}`));
|
|
|
13599
14910
|
);
|
|
13600
14911
|
if (contextUsage.isCritical) {
|
|
13601
14912
|
this.runtime.spinner?.stop();
|
|
13602
|
-
console.log(
|
|
14913
|
+
console.log(chalk15.yellow("\n\u26A0 Context at critical level, auto-cropping old messages..."));
|
|
13603
14914
|
const targetTokens = Math.floor(contextUsage.contextWindow * 0.7);
|
|
13604
14915
|
const tokensToRemove = contextUsage.totalTokens - targetTokens;
|
|
13605
14916
|
const avgMessageTokens = 200;
|
|
@@ -13612,12 +14923,12 @@ Completed in ${elapsed} \xB7 ${tokens} used${queueStatus}`));
|
|
|
13612
14923
|
Summary of removed content:
|
|
13613
14924
|
${summary}`
|
|
13614
14925
|
);
|
|
13615
|
-
console.log(
|
|
13616
|
-
console.log(
|
|
14926
|
+
console.log(chalk15.gray(` Removed ${removed.length} messages to free up context space`));
|
|
14927
|
+
console.log(chalk15.gray(` Summary preserved in context`));
|
|
13617
14928
|
}
|
|
13618
14929
|
this.updateContextUsage(this.conversation.history(), tools);
|
|
13619
14930
|
} else if (contextUsage.isWarning && iteration === 0) {
|
|
13620
|
-
console.log(
|
|
14931
|
+
console.log(chalk15.yellow(`
|
|
13621
14932
|
\u26A0 Context at ${Math.round(contextUsage.usagePercent * 100)}% - approaching limit`));
|
|
13622
14933
|
}
|
|
13623
14934
|
if (iteration >= 10 && iteration % 10 === 0) {
|
|
@@ -13630,6 +14941,7 @@ ${summary}`
|
|
|
13630
14941
|
`);
|
|
13631
14942
|
let completion;
|
|
13632
14943
|
try {
|
|
14944
|
+
const thinkingLevel = process.env.AUTOHAND_THINKING_LEVEL || "normal";
|
|
13633
14945
|
completion = await this.llm.complete({
|
|
13634
14946
|
messages: messagesWithImages,
|
|
13635
14947
|
temperature: this.runtime.options.temperature ?? 0.2,
|
|
@@ -13637,8 +14949,9 @@ ${summary}`
|
|
|
13637
14949
|
signal: abortController.signal,
|
|
13638
14950
|
tools: tools.length > 0 ? tools : void 0,
|
|
13639
14951
|
toolChoice: tools.length > 0 ? "auto" : void 0,
|
|
13640
|
-
maxTokens: 16e3
|
|
14952
|
+
maxTokens: 16e3,
|
|
13641
14953
|
// Allow large outputs for file generation
|
|
14954
|
+
thinkingLevel
|
|
13642
14955
|
});
|
|
13643
14956
|
if (debugMode) process.stderr.write(`[AGENT DEBUG] LLM returned: content length=${completion.content?.length ?? 0}, toolCalls=${completion.toolCalls?.length ?? 0}
|
|
13644
14957
|
`);
|
|
@@ -13666,12 +14979,12 @@ ${summary}`
|
|
|
13666
14979
|
await this.saveAssistantMessage(completion.content, payload.toolCalls);
|
|
13667
14980
|
this.updateContextUsage(this.conversation.history(), tools);
|
|
13668
14981
|
if (debugMode) {
|
|
13669
|
-
console.log(
|
|
14982
|
+
console.log(chalk15.yellow(`
|
|
13670
14983
|
[DEBUG] Iteration ${iteration}:`));
|
|
13671
|
-
console.log(
|
|
13672
|
-
console.log(
|
|
13673
|
-
console.log(
|
|
13674
|
-
console.log(
|
|
14984
|
+
console.log(chalk15.yellow(` - toolCalls: ${payload.toolCalls?.length ?? 0}`));
|
|
14985
|
+
console.log(chalk15.yellow(` - thought: ${payload.thought?.slice(0, 100) || "(none)"}`));
|
|
14986
|
+
console.log(chalk15.yellow(` - finalResponse: ${payload.finalResponse?.slice(0, 100) || "(none)"}`));
|
|
14987
|
+
console.log(chalk15.yellow(` - raw content: ${completion.content?.slice(0, 200) || "(empty)"}`));
|
|
13675
14988
|
}
|
|
13676
14989
|
const toolCount = payload.toolCalls?.length ?? 0;
|
|
13677
14990
|
const hasResponse = Boolean(payload.finalResponse || payload.response || !toolCount && payload.thought);
|
|
@@ -13688,7 +15001,7 @@ ${summary}`
|
|
|
13688
15001
|
} else {
|
|
13689
15002
|
if (iteration > 0) {
|
|
13690
15003
|
const status = toolCount > 0 ? `\u2192 Step ${iteration + 1}: calling ${toolCount} tool(s)` : hasResponse ? `\u2192 Step ${iteration + 1}: preparing response` : `\u2192 Step ${iteration + 1}: thinking...`;
|
|
13691
|
-
console.log(
|
|
15004
|
+
console.log(chalk15.gray(status));
|
|
13692
15005
|
}
|
|
13693
15006
|
}
|
|
13694
15007
|
if (payload.toolCalls && payload.toolCalls.length > 0) {
|
|
@@ -13707,8 +15020,8 @@ ${summary}`
|
|
|
13707
15020
|
});
|
|
13708
15021
|
await this.saveToolMessage("smart_context_cropper", content, call.id);
|
|
13709
15022
|
this.updateContextUsage(this.conversation.history(), tools);
|
|
13710
|
-
outputLines.push(`${
|
|
13711
|
-
outputLines.push(
|
|
15023
|
+
outputLines.push(`${chalk15.cyan("\u2702 smart_context_cropper")}`);
|
|
15024
|
+
outputLines.push(chalk15.gray(content));
|
|
13712
15025
|
outputLines.push("");
|
|
13713
15026
|
}
|
|
13714
15027
|
}
|
|
@@ -13826,6 +15139,7 @@ ${summary}`
|
|
|
13826
15139
|
}
|
|
13827
15140
|
this.stopStatusUpdates();
|
|
13828
15141
|
let rawResponse;
|
|
15142
|
+
const usedThoughtAsResponse = Boolean(payload.thought) && !payload.finalResponse && !payload.response && !payload.toolCalls?.length;
|
|
13829
15143
|
if (payload.finalResponse) {
|
|
13830
15144
|
rawResponse = payload.finalResponse;
|
|
13831
15145
|
} else if (payload.response) {
|
|
@@ -13836,7 +15150,10 @@ ${summary}`
|
|
|
13836
15150
|
const cleanedContent = this.cleanupModelResponse(completion.content);
|
|
13837
15151
|
rawResponse = cleanedContent.startsWith("{") ? "" : cleanedContent;
|
|
13838
15152
|
}
|
|
13839
|
-
|
|
15153
|
+
let response = this.cleanupModelResponse(rawResponse.trim());
|
|
15154
|
+
if (!response && usedThoughtAsResponse && payload.thought) {
|
|
15155
|
+
response = payload.thought.trim();
|
|
15156
|
+
}
|
|
13840
15157
|
if (!response && iteration > 0) {
|
|
13841
15158
|
const consecutiveEmptyKey = "__consecutiveEmpty";
|
|
13842
15159
|
const consecutiveEmpty = (this[consecutiveEmptyKey] ?? 0) + 1;
|
|
@@ -13844,7 +15161,7 @@ ${summary}`
|
|
|
13844
15161
|
if (consecutiveEmpty >= 3) {
|
|
13845
15162
|
if (debugMode) process.stderr.write(`[AGENT DEBUG] Exiting after 3 consecutive empty responses
|
|
13846
15163
|
`);
|
|
13847
|
-
console.log(
|
|
15164
|
+
console.log(chalk15.yellow("\n\u26A0 Model not providing response after multiple attempts. Showing available context."));
|
|
13848
15165
|
const fallback = payload.thought || "The model did not provide a clear response. Please try rephrasing your question.";
|
|
13849
15166
|
if (this.inkRenderer) {
|
|
13850
15167
|
this.inkRenderer.setWorking(false);
|
|
@@ -13863,12 +15180,13 @@ ${summary}`
|
|
|
13863
15180
|
continue;
|
|
13864
15181
|
}
|
|
13865
15182
|
this.__consecutiveEmpty = 0;
|
|
13866
|
-
|
|
15183
|
+
const suppressThinking = usedThoughtAsResponse && response.length > 0;
|
|
15184
|
+
if (payload.thought && !suppressThinking) {
|
|
13867
15185
|
this.emitOutput({ type: "thinking", thought: payload.thought });
|
|
13868
15186
|
}
|
|
13869
15187
|
this.emitOutput({ type: "message", content: response });
|
|
13870
15188
|
if (this.inkRenderer) {
|
|
13871
|
-
if (showThinking && payload.thought) {
|
|
15189
|
+
if (showThinking && payload.thought && !suppressThinking) {
|
|
13872
15190
|
this.inkRenderer.setThinking(payload.thought);
|
|
13873
15191
|
}
|
|
13874
15192
|
this.inkRenderer.setElapsed(this.formatElapsedTime(this.sessionStartedAt));
|
|
@@ -13877,8 +15195,8 @@ ${summary}`
|
|
|
13877
15195
|
this.inkRenderer.setFinalResponse(response);
|
|
13878
15196
|
} else {
|
|
13879
15197
|
this.runtime.spinner?.stop();
|
|
13880
|
-
if (showThinking && payload.thought && !payload.thought.trim().startsWith("{")) {
|
|
13881
|
-
console.log(
|
|
15198
|
+
if (showThinking && payload.thought && !suppressThinking && !payload.thought.trim().startsWith("{")) {
|
|
15199
|
+
console.log(chalk15.gray(`Thinking: ${payload.thought}`));
|
|
13882
15200
|
console.log();
|
|
13883
15201
|
}
|
|
13884
15202
|
console.log(response);
|
|
@@ -13887,10 +15205,10 @@ ${summary}`
|
|
|
13887
15205
|
}
|
|
13888
15206
|
this.stopStatusUpdates();
|
|
13889
15207
|
this.runtime.spinner?.stop();
|
|
13890
|
-
console.log(
|
|
15208
|
+
console.log(chalk15.yellow(`
|
|
13891
15209
|
\u26A0 Task exceeded ${maxIterations} tool iterations without completing.`));
|
|
13892
|
-
console.log(
|
|
13893
|
-
console.log(
|
|
15210
|
+
console.log(chalk15.gray("This usually means the task is too complex for a single turn."));
|
|
15211
|
+
console.log(chalk15.gray("Try breaking it into smaller steps or use /new to start fresh."));
|
|
13894
15212
|
throw new Error(`Reached maximum iterations (${maxIterations}) while processing. Try a simpler request or break the task into smaller steps.`);
|
|
13895
15213
|
}
|
|
13896
15214
|
/**
|
|
@@ -13919,8 +15237,8 @@ ${summary}`
|
|
|
13919
15237
|
toolCalls: completion.toolCalls.map((tc) => {
|
|
13920
15238
|
const rawArgs = tc.function.arguments;
|
|
13921
15239
|
if (!rawArgs || rawArgs === "{}" || rawArgs === "") {
|
|
13922
|
-
console.error(
|
|
13923
|
-
console.error(
|
|
15240
|
+
console.error(chalk15.yellow(`\u26A0 Tool "${tc.function.name}" has empty/missing arguments`));
|
|
15241
|
+
console.error(chalk15.gray(` Raw arguments: "${rawArgs}"`));
|
|
13924
15242
|
}
|
|
13925
15243
|
return {
|
|
13926
15244
|
id: tc.id,
|
|
@@ -13937,7 +15255,7 @@ ${summary}`
|
|
|
13937
15255
|
*/
|
|
13938
15256
|
safeParseToolArgs(json) {
|
|
13939
15257
|
if (!json || typeof json !== "string") {
|
|
13940
|
-
console.error(
|
|
15258
|
+
console.error(chalk15.yellow("\u26A0 Tool arguments empty or not a string"));
|
|
13941
15259
|
return void 0;
|
|
13942
15260
|
}
|
|
13943
15261
|
try {
|
|
@@ -13945,11 +15263,11 @@ ${summary}`
|
|
|
13945
15263
|
if (parsed && typeof parsed === "object") {
|
|
13946
15264
|
return parsed;
|
|
13947
15265
|
}
|
|
13948
|
-
console.error(
|
|
15266
|
+
console.error(chalk15.yellow(`\u26A0 Tool arguments parsed but not an object: ${typeof parsed}`));
|
|
13949
15267
|
return void 0;
|
|
13950
15268
|
} catch (err) {
|
|
13951
|
-
console.error(
|
|
13952
|
-
console.error(
|
|
15269
|
+
console.error(chalk15.yellow(`\u26A0 Failed to parse tool arguments: ${err instanceof Error ? err.message : String(err)}`));
|
|
15270
|
+
console.error(chalk15.gray(` Raw JSON: ${json.slice(0, 200)}${json.length > 200 ? "..." : ""}`));
|
|
13953
15271
|
return void 0;
|
|
13954
15272
|
}
|
|
13955
15273
|
}
|
|
@@ -14465,7 +15783,7 @@ ${toolSignatures}
|
|
|
14465
15783
|
const contents = await this.files.readFile(file);
|
|
14466
15784
|
this.mentionContexts.push({ path: file, contents: this.trimContext(contents) });
|
|
14467
15785
|
} catch (error) {
|
|
14468
|
-
console.log(
|
|
15786
|
+
console.log(chalk15.yellow(`Unable to read ${file} for context: ${error.message}`));
|
|
14469
15787
|
}
|
|
14470
15788
|
}
|
|
14471
15789
|
trimContext(content) {
|
|
@@ -14585,11 +15903,11 @@ ${ctx.contents}`).join("\n\n");
|
|
|
14585
15903
|
return;
|
|
14586
15904
|
}
|
|
14587
15905
|
if (!this.hasPrintedExplorationHeader) {
|
|
14588
|
-
console.log("\n" +
|
|
15906
|
+
console.log("\n" + chalk15.bold("* Explored"));
|
|
14589
15907
|
this.hasPrintedExplorationHeader = true;
|
|
14590
15908
|
}
|
|
14591
15909
|
const label = this.formatExplorationLabel(event.kind);
|
|
14592
|
-
console.log(` ${
|
|
15910
|
+
console.log(` ${chalk15.cyan(label)} ${event.target}`);
|
|
14593
15911
|
}
|
|
14594
15912
|
clearExplorationLog() {
|
|
14595
15913
|
this.hasPrintedExplorationHeader = false;
|
|
@@ -14611,7 +15929,7 @@ ${ctx.contents}`).join("\n\n");
|
|
|
14611
15929
|
formatToolResultsBatch(results, charLimit, toolCalls, thought) {
|
|
14612
15930
|
const lines = [];
|
|
14613
15931
|
if (thought && !thought.trim().startsWith("{")) {
|
|
14614
|
-
lines.push(
|
|
15932
|
+
lines.push(chalk15.white(thought));
|
|
14615
15933
|
lines.push("");
|
|
14616
15934
|
}
|
|
14617
15935
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -14622,15 +15940,15 @@ ${ctx.contents}`).join("\n\n");
|
|
|
14622
15940
|
const command = call?.args?.command;
|
|
14623
15941
|
const commandArgs = call?.args?.args;
|
|
14624
15942
|
const display = result.success ? formatToolOutputForDisplay({ tool: result.tool, content, charLimit, filePath, command, commandArgs }) : { output: content, truncated: false, totalChars: content.length };
|
|
14625
|
-
const icon = result.success ?
|
|
14626
|
-
lines.push(`${icon} ${
|
|
15943
|
+
const icon = result.success ? chalk15.green("\u2714") : chalk15.red("\u2716");
|
|
15944
|
+
lines.push(`${icon} ${chalk15.bold(result.tool)}`);
|
|
14627
15945
|
if (content) {
|
|
14628
15946
|
if (result.success) {
|
|
14629
|
-
lines.push(
|
|
15947
|
+
lines.push(chalk15.gray(display.output));
|
|
14630
15948
|
} else {
|
|
14631
|
-
lines.push(
|
|
14632
|
-
lines.push(
|
|
14633
|
-
lines.push(
|
|
15949
|
+
lines.push(chalk15.red("\u250C\u2500 Error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
15950
|
+
lines.push(chalk15.red("\u2502 ") + chalk15.white(content));
|
|
15951
|
+
lines.push(chalk15.red("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
14634
15952
|
}
|
|
14635
15953
|
}
|
|
14636
15954
|
lines.push("");
|
|
@@ -14819,7 +16137,7 @@ ${parts.join("\n")}`
|
|
|
14819
16137
|
return () => {
|
|
14820
16138
|
};
|
|
14821
16139
|
}
|
|
14822
|
-
|
|
16140
|
+
safeEmitKeypressEvents(input);
|
|
14823
16141
|
const supportsRaw = typeof input.setRawMode === "function";
|
|
14824
16142
|
const wasRaw = input.isRaw;
|
|
14825
16143
|
if (!wasRaw && supportsRaw) {
|
|
@@ -14843,7 +16161,7 @@ ${parts.join("\n")}`
|
|
|
14843
16161
|
controller.abort();
|
|
14844
16162
|
onCancel();
|
|
14845
16163
|
} else {
|
|
14846
|
-
console.log(
|
|
16164
|
+
console.log(chalk15.gray("Press Ctrl+C again to exit."));
|
|
14847
16165
|
}
|
|
14848
16166
|
return;
|
|
14849
16167
|
}
|
|
@@ -14859,7 +16177,7 @@ ${parts.join("\n")}`
|
|
|
14859
16177
|
queue.push({ text, timestamp: Date.now() });
|
|
14860
16178
|
const preview = text.length > 30 ? text.slice(0, 27) + "..." : text;
|
|
14861
16179
|
if (this.runtime.spinner) {
|
|
14862
|
-
this.runtime.spinner.text =
|
|
16180
|
+
this.runtime.spinner.text = chalk15.cyan(`\u2713 Queued: "${preview}" (${this.persistentInput.getQueueLength()} pending)`);
|
|
14863
16181
|
}
|
|
14864
16182
|
}
|
|
14865
16183
|
return;
|
|
@@ -15018,7 +16336,7 @@ ${parts.join("\n")}`
|
|
|
15018
16336
|
workspace: this.runtime.workspaceRoot
|
|
15019
16337
|
});
|
|
15020
16338
|
} catch (reportError) {
|
|
15021
|
-
console.error(
|
|
16339
|
+
console.error(chalk15.gray(`[Debug] Failed to submit bug report: ${reportError.message}`));
|
|
15022
16340
|
}
|
|
15023
16341
|
}
|
|
15024
16342
|
/**
|
|
@@ -15029,16 +16347,16 @@ ${parts.join("\n")}`
|
|
|
15029
16347
|
return;
|
|
15030
16348
|
}
|
|
15031
16349
|
if (result.intent === "diagnostic") {
|
|
15032
|
-
console.log(
|
|
16350
|
+
console.log(chalk15.blue("[DIAG] Mode: Diagnostic (read-only analysis)"));
|
|
15033
16351
|
if (result.keywords.length > 0) {
|
|
15034
16352
|
const kws = result.keywords.slice(0, 3).join('", "');
|
|
15035
|
-
console.log(
|
|
16353
|
+
console.log(chalk15.gray(` Detected: "${kws}"`));
|
|
15036
16354
|
}
|
|
15037
16355
|
} else {
|
|
15038
|
-
console.log(
|
|
16356
|
+
console.log(chalk15.yellow("[IMPL] Mode: Implementation"));
|
|
15039
16357
|
if (result.keywords.length > 0) {
|
|
15040
16358
|
const kws = result.keywords.slice(0, 3).join('", "');
|
|
15041
|
-
console.log(
|
|
16359
|
+
console.log(chalk15.gray(` Detected: "${kws}"`));
|
|
15042
16360
|
}
|
|
15043
16361
|
}
|
|
15044
16362
|
console.log();
|
|
@@ -15049,22 +16367,22 @@ ${parts.join("\n")}`
|
|
|
15049
16367
|
async runEnvironmentBootstrap() {
|
|
15050
16368
|
const isDebug = process.env.AUTOHAND_DEBUG === "1";
|
|
15051
16369
|
if (isDebug) {
|
|
15052
|
-
console.log(
|
|
16370
|
+
console.log(chalk15.cyan("[BOOTSTRAP] Running environment setup..."));
|
|
15053
16371
|
}
|
|
15054
16372
|
const result = await this.environmentBootstrap.run(this.runtime.workspaceRoot);
|
|
15055
16373
|
for (const step of result.steps) {
|
|
15056
|
-
const status = step.status === "success" ?
|
|
15057
|
-
const duration = step.duration ?
|
|
15058
|
-
const detail = step.detail ?
|
|
16374
|
+
const status = step.status === "success" ? chalk15.green("[OK]") : step.status === "failed" ? chalk15.red("[FAIL]") : step.status === "skipped" ? chalk15.gray("[SKIP]") : chalk15.gray("[...]");
|
|
16375
|
+
const duration = step.duration ? chalk15.gray(`(${(step.duration / 1e3).toFixed(1)}s)`) : "";
|
|
16376
|
+
const detail = step.detail ? chalk15.gray(` ${step.detail}`) : "";
|
|
15059
16377
|
if (step.status === "failed" || isDebug) {
|
|
15060
16378
|
console.log(` ${status} ${step.name.padEnd(14)} ${duration}${detail}`);
|
|
15061
16379
|
}
|
|
15062
16380
|
if (step.error) {
|
|
15063
|
-
console.log(
|
|
16381
|
+
console.log(chalk15.red(` Error: ${step.error}`));
|
|
15064
16382
|
}
|
|
15065
16383
|
}
|
|
15066
16384
|
if (result.success && isDebug) {
|
|
15067
|
-
console.log(
|
|
16385
|
+
console.log(chalk15.green(`
|
|
15068
16386
|
[READY] Environment ready (${(result.duration / 1e3).toFixed(1)}s)
|
|
15069
16387
|
`));
|
|
15070
16388
|
}
|
|
@@ -15074,26 +16392,26 @@ ${parts.join("\n")}`
|
|
|
15074
16392
|
* Run code quality pipeline after file modifications
|
|
15075
16393
|
*/
|
|
15076
16394
|
async runQualityPipeline() {
|
|
15077
|
-
console.log(
|
|
16395
|
+
console.log(chalk15.cyan("\n[QUALITY] Running quality checks..."));
|
|
15078
16396
|
const result = await this.codeQualityPipeline.run(this.runtime.workspaceRoot);
|
|
15079
16397
|
for (const check of result.checks) {
|
|
15080
|
-
const status = check.status === "passed" ?
|
|
15081
|
-
const duration = check.duration ?
|
|
16398
|
+
const status = check.status === "passed" ? chalk15.green("[OK]") : check.status === "failed" ? chalk15.red("[FAIL]") : check.status === "skipped" ? chalk15.gray("[SKIP]") : chalk15.gray("[...]");
|
|
16399
|
+
const duration = check.duration ? chalk15.gray(`(${(check.duration / 1e3).toFixed(1)}s)`) : "";
|
|
15082
16400
|
console.log(` ${status} ${check.name.padEnd(8)} ${check.command.padEnd(20)} ${duration}`);
|
|
15083
16401
|
if (check.status === "failed" && check.output) {
|
|
15084
16402
|
const errorLines = check.output.split("\n").slice(0, 3);
|
|
15085
16403
|
for (const line of errorLines) {
|
|
15086
16404
|
if (line.trim()) {
|
|
15087
|
-
console.log(
|
|
16405
|
+
console.log(chalk15.red(` ${line}`));
|
|
15088
16406
|
}
|
|
15089
16407
|
}
|
|
15090
16408
|
}
|
|
15091
16409
|
}
|
|
15092
16410
|
if (result.passed) {
|
|
15093
|
-
console.log(
|
|
16411
|
+
console.log(chalk15.green(`
|
|
15094
16412
|
[PASS] ${result.summary} (${(result.duration / 1e3).toFixed(1)}s)`));
|
|
15095
16413
|
} else {
|
|
15096
|
-
console.log(
|
|
16414
|
+
console.log(chalk15.red(`
|
|
15097
16415
|
[FAIL] ${result.summary}`));
|
|
15098
16416
|
}
|
|
15099
16417
|
}
|
|
@@ -15236,7 +16554,7 @@ ${parts.join("\n")}`
|
|
|
15236
16554
|
if (!this.runtime.spinner) return;
|
|
15237
16555
|
const inputPreview = this.queueInput.length > 40 ? "..." + this.queueInput.slice(-37) : this.queueInput;
|
|
15238
16556
|
const inputLine = `
|
|
15239
|
-
${
|
|
16557
|
+
${chalk15.gray("\u203A")} ${inputPreview}${chalk15.gray("\u258B")}`;
|
|
15240
16558
|
const fullText = statusLine + inputLine;
|
|
15241
16559
|
const cacheKey = `${statusLine}|${inputPreview}`;
|
|
15242
16560
|
if (cacheKey === this.lastRenderedStatus) return;
|
|
@@ -15278,6 +16596,25 @@ ${chalk14.gray("\u203A")} ${inputPreview}${chalk14.gray("\u258B")}`;
|
|
|
15278
16596
|
}
|
|
15279
16597
|
this.emitStatus();
|
|
15280
16598
|
}
|
|
16599
|
+
/**
|
|
16600
|
+
* Ensure stdin is in a known good state for readline input.
|
|
16601
|
+
* This is called after operations that may interfere with stdin state,
|
|
16602
|
+
* such as hook execution with shell: true.
|
|
16603
|
+
*/
|
|
16604
|
+
ensureStdinReady() {
|
|
16605
|
+
const stdin = process.stdin;
|
|
16606
|
+
if (!stdin.isTTY) return;
|
|
16607
|
+
try {
|
|
16608
|
+
if (typeof stdin.setRawMode === "function" && stdin.isRaw) {
|
|
16609
|
+
stdin.setRawMode(false);
|
|
16610
|
+
}
|
|
16611
|
+
if (stdin.isPaused()) {
|
|
16612
|
+
stdin.resume();
|
|
16613
|
+
}
|
|
16614
|
+
safeEmitKeypressEvents(stdin);
|
|
16615
|
+
} catch {
|
|
16616
|
+
}
|
|
16617
|
+
}
|
|
15281
16618
|
formatStatusLine() {
|
|
15282
16619
|
const percent = Number.isFinite(this.contextPercentLeft) ? Math.max(0, Math.min(100, this.contextPercentLeft)) : 100;
|
|
15283
16620
|
const queueCount = this.inkRenderer?.getQueueCount() ?? this.persistentInput.getQueueLength();
|
|
@@ -15421,7 +16758,7 @@ ${chalk14.gray("\u203A")} ${inputPreview}${chalk14.gray("\u258B")}`;
|
|
|
15421
16758
|
import fs20 from "fs-extra";
|
|
15422
16759
|
import os8 from "os";
|
|
15423
16760
|
import path19 from "path";
|
|
15424
|
-
import
|
|
16761
|
+
import chalk16 from "chalk";
|
|
15425
16762
|
var AVAILABLE_TOOLS = {
|
|
15426
16763
|
file: [
|
|
15427
16764
|
"read_file",
|
|
@@ -15498,7 +16835,7 @@ var PYTHON_FRAMEWORKS = {
|
|
|
15498
16835
|
tensorflow: ["tensorflow"],
|
|
15499
16836
|
pytorch: ["torch"]
|
|
15500
16837
|
};
|
|
15501
|
-
var
|
|
16838
|
+
var ProjectAnalyzer2 = class {
|
|
15502
16839
|
constructor(projectRoot) {
|
|
15503
16840
|
this.projectRoot = projectRoot;
|
|
15504
16841
|
}
|
|
@@ -15852,8 +17189,8 @@ async function runAutoSkillGeneration(workspaceRoot, llm) {
|
|
|
15852
17189
|
skillsGenerated: 0,
|
|
15853
17190
|
skills: []
|
|
15854
17191
|
};
|
|
15855
|
-
console.log(
|
|
15856
|
-
const analyzer = new
|
|
17192
|
+
console.log(chalk16.cyan("Analyzing project structure..."));
|
|
17193
|
+
const analyzer = new ProjectAnalyzer2(workspaceRoot);
|
|
15857
17194
|
let analysis;
|
|
15858
17195
|
try {
|
|
15859
17196
|
analysis = await analyzer.analyze();
|
|
@@ -15862,17 +17199,17 @@ async function runAutoSkillGeneration(workspaceRoot, llm) {
|
|
|
15862
17199
|
return result;
|
|
15863
17200
|
}
|
|
15864
17201
|
if (analysis.languages.length === 0 && analysis.frameworks.length === 0) {
|
|
15865
|
-
console.log(
|
|
17202
|
+
console.log(chalk16.yellow("Could not detect project type. No skills generated."));
|
|
15866
17203
|
result.error = "Could not detect project type";
|
|
15867
17204
|
return result;
|
|
15868
17205
|
}
|
|
15869
17206
|
const detected = [...analysis.languages, ...analysis.frameworks, ...analysis.patterns].join(", ");
|
|
15870
|
-
console.log(
|
|
15871
|
-
console.log(
|
|
15872
|
-
console.log(
|
|
17207
|
+
console.log(chalk16.gray(`Detected: ${detected}`));
|
|
17208
|
+
console.log(chalk16.gray(`Platform: ${analysis.platform}`));
|
|
17209
|
+
console.log(chalk16.cyan("Generating skills..."));
|
|
15873
17210
|
const skills = await generateAutoSkills(analysis, llm);
|
|
15874
17211
|
if (skills.length === 0) {
|
|
15875
|
-
console.log(
|
|
17212
|
+
console.log(chalk16.yellow("No skills generated."));
|
|
15876
17213
|
result.error = "No skills generated";
|
|
15877
17214
|
return result;
|
|
15878
17215
|
}
|
|
@@ -15882,17 +17219,17 @@ async function runAutoSkillGeneration(workspaceRoot, llm) {
|
|
|
15882
17219
|
if (saved) {
|
|
15883
17220
|
result.skillsGenerated++;
|
|
15884
17221
|
result.skills.push(skill.name);
|
|
15885
|
-
console.log(
|
|
17222
|
+
console.log(chalk16.green(` \u2713 ${skill.name}`));
|
|
15886
17223
|
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
15887
|
-
console.log(
|
|
17224
|
+
console.log(chalk16.gray(` Tools: ${skill.allowedTools.slice(0, 5).join(", ")}${skill.allowedTools.length > 5 ? "..." : ""}`));
|
|
15888
17225
|
}
|
|
15889
17226
|
}
|
|
15890
17227
|
}
|
|
15891
17228
|
if (result.skillsGenerated > 0) {
|
|
15892
17229
|
result.success = true;
|
|
15893
|
-
console.log(
|
|
17230
|
+
console.log(chalk16.green(`
|
|
15894
17231
|
\u2713 Generated ${result.skillsGenerated} skills in ${skillsDir}`));
|
|
15895
|
-
console.log(
|
|
17232
|
+
console.log(chalk16.gray(` Use "/skills" to view and "/skills use <name>" to activate`));
|
|
15896
17233
|
}
|
|
15897
17234
|
return result;
|
|
15898
17235
|
}
|
|
@@ -16454,6 +17791,22 @@ ${sel.text}
|
|
|
16454
17791
|
}
|
|
16455
17792
|
process.stderr.write(`[RPC DEBUG] Instruction completed, success=${success}, content length=${this.currentMessageContent.length}
|
|
16456
17793
|
`);
|
|
17794
|
+
const turnDuration = this.turnStartTime ? Date.now() - this.turnStartTime : 0;
|
|
17795
|
+
const hookManager = this.agent?.getHookManager?.();
|
|
17796
|
+
if (hookManager) {
|
|
17797
|
+
const snapshot2 = this.agent?.getStatusSnapshot();
|
|
17798
|
+
await hookManager.executeHooks("stop", {
|
|
17799
|
+
sessionId: this.sessionId || void 0,
|
|
17800
|
+
turnDuration,
|
|
17801
|
+
tokensUsed: snapshot2?.tokensUsed ?? 0
|
|
17802
|
+
});
|
|
17803
|
+
this.emitHookStop(
|
|
17804
|
+
snapshot2?.tokensUsed ?? 0,
|
|
17805
|
+
0,
|
|
17806
|
+
// toolCallsCount - not tracked per turn currently
|
|
17807
|
+
turnDuration
|
|
17808
|
+
);
|
|
17809
|
+
}
|
|
16457
17810
|
} catch (err) {
|
|
16458
17811
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
16459
17812
|
const errorStack = err instanceof Error ? err.stack : "";
|
|
@@ -17504,7 +18857,7 @@ async function handleSingleRequest(request, adapter) {
|
|
|
17504
18857
|
process.title = "autohand";
|
|
17505
18858
|
function getGitCommit() {
|
|
17506
18859
|
if (true) {
|
|
17507
|
-
return "
|
|
18860
|
+
return "fc318c0";
|
|
17508
18861
|
}
|
|
17509
18862
|
try {
|
|
17510
18863
|
return execSync3("git rev-parse --short HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
@@ -17562,7 +18915,7 @@ process.on("unhandledRejection", (reason, promise) => {
|
|
|
17562
18915
|
globalThis.__autohandLastError = reason;
|
|
17563
18916
|
console.error("[DEBUG] Unhandled Rejection at:", promise, "reason:", reason);
|
|
17564
18917
|
});
|
|
17565
|
-
var
|
|
18918
|
+
var ASCII_FRIEND2 = [
|
|
17566
18919
|
"\u2880\u2874\u281B\u281B\u283B\u28F7\u2844\u2800\u28E0\u2876\u281F\u281B\u283B\u28F6\u2844\u2880\u28F4\u287E\u281B\u281B\u28BF\u28E6\u2800\u2880\u28F4\u281E\u281B\u281B\u2836\u2840",
|
|
17567
18920
|
"\u284E\u2800\u28B0\u28F6\u2846\u2808\u28FF\u28F4\u28FF\u2801\u28F4\u28F6\u2844\u2818\u28FF\u28FE\u284F\u2880\u28F6\u28E6\u2800\u28BB\u2847\u28FF\u2803\u28A0\u28F6\u2846\u2800\u28B9",
|
|
17568
18921
|
"\u28A7\u2800\u2818\u281B\u2803\u28A0\u287F\u2819\u28FF\u2840\u2819\u281B\u2803\u28F0\u287F\u28BB\u28E7\u2808\u281B\u281B\u2880\u28FE\u2807\u28BB\u28C6\u2808\u281B\u280B\u2800\u287C",
|
|
@@ -17573,7 +18926,7 @@ var ASCII_FRIEND = [
|
|
|
17573
18926
|
"\u2808\u28BF\u28E6\u28E4\u28F4\u287F\u2803\u2800\u2819\u28B7\u28E6\u28E4\u28F6\u287F\u2801\u2808\u283B\u28F7\u28E4\u28E4\u287E\u281B\u2800\u2808\u28BF\u28E6\u28E4\u28E4\u2834\u2801"
|
|
17574
18927
|
].join("\n");
|
|
17575
18928
|
var program = new Command();
|
|
17576
|
-
program.name("autohand").description("Autonomous LLM-powered coding agent CLI").version(getVersionString(), "-v, --version", "output the current version").option("-p, --prompt <text>", "Run a single instruction in command mode").option("--path <path>", "Workspace path to operate in").option("-y, --yes", "Auto-confirm risky actions", false).option("--dry-run", "Preview actions without applying mutations", false).option("-d, --debug", "Enable debug output (verbose logging)", false).option("--model <model>", "Override the configured LLM model").option("--config <path>", "Path to config file (default ~/.autohand/config.json)").option("--temperature <value>", "Sampling temperature", parseFloat).option("-c, --auto-commit", "Auto-commit with LLM-generated message (runs lint & test first)", false).option("--unrestricted", "Run without any approval prompts (use with caution)", false).option("--restricted", "Deny all dangerous operations automatically", false).option("--auto-skill", "Auto-generate skills based on project analysis", false).option("--skill-install [skill-name]", "Install a community skill (opens browser if no name)").option("--project", "Install skill to project level (with --skill-install)", false).option("--permissions", "Display current permission settings and exit", false).option("--patch", "Generate git patch without applying changes (requires --prompt)", false).option("--output <file>", "Output file for patch (default: stdout, used with --patch)").option("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").option("--auto-mode <prompt>", "Start autonomous development loop with the given task").option("--max-iterations <n>", "Max auto-mode iterations (default: 50)", parseInt).option("--completion-promise <text>", 'Completion marker text (default: "DONE")').option("--no-worktree", "Disable git worktree isolation in auto-mode").option("--checkpoint-interval <n>", "Git commit every N iterations (default: 5)", parseInt).option("--max-runtime <m>", "Max runtime in minutes (default: 120)", parseInt).option("--max-cost <d>", "Max API cost in dollars (default: 10)", parseFloat).action(async (opts) => {
|
|
18929
|
+
program.name("autohand").description("Autonomous LLM-powered coding agent CLI").version(getVersionString(), "-v, --version", "output the current version").option("-p, --prompt <text>", "Run a single instruction in command mode").option("--path <path>", "Workspace path to operate in").option("-y, --yes", "Auto-confirm risky actions", false).option("--dry-run", "Preview actions without applying mutations", false).option("-d, --debug", "Enable debug output (verbose logging)", false).option("--model <model>", "Override the configured LLM model").option("--config <path>", "Path to config file (default ~/.autohand/config.json)").option("--temperature <value>", "Sampling temperature", parseFloat).option("-c, --auto-commit", "Auto-commit with LLM-generated message (runs lint & test first)", false).option("--unrestricted", "Run without any approval prompts (use with caution)", false).option("--restricted", "Deny all dangerous operations automatically", false).option("--auto-skill", "Auto-generate skills based on project analysis", false).option("--skill-install [skill-name]", "Install a community skill (opens browser if no name)").option("--project", "Install skill to project level (with --skill-install)", false).option("--permissions", "Display current permission settings and exit", false).option("--login", "Sign in to your Autohand account", false).option("--logout", "Sign out of your Autohand account", false).option("--patch", "Generate git patch without applying changes (requires --prompt)", false).option("--output <file>", "Output file for patch (default: stdout, used with --patch)").option("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").option("--auto-mode <prompt>", "Start autonomous development loop with the given task").option("--max-iterations <n>", "Max auto-mode iterations (default: 50)", parseInt).option("--completion-promise <text>", 'Completion marker text (default: "DONE")').option("--no-worktree", "Disable git worktree isolation in auto-mode").option("--checkpoint-interval <n>", "Git commit every N iterations (default: 5)", parseInt).option("--max-runtime <m>", "Max runtime in minutes (default: 120)", parseInt).option("--max-cost <d>", "Max API cost in dollars (default: 10)", parseFloat).option("--setup", "Run the setup wizard to configure or reconfigure Autohand", false).action(async (opts) => {
|
|
17577
18930
|
if (opts.skillInstall !== void 0) {
|
|
17578
18931
|
await runSkillInstall(opts);
|
|
17579
18932
|
return;
|
|
@@ -17582,6 +18935,34 @@ program.name("autohand").description("Autonomous LLM-powered coding agent CLI").
|
|
|
17582
18935
|
await displayPermissions(opts);
|
|
17583
18936
|
return;
|
|
17584
18937
|
}
|
|
18938
|
+
if (opts.login) {
|
|
18939
|
+
const { login } = await import("./login-NYWZRZO5.js");
|
|
18940
|
+
const config = await loadConfig(opts.config);
|
|
18941
|
+
await login({ config });
|
|
18942
|
+
process.exit(0);
|
|
18943
|
+
}
|
|
18944
|
+
if (opts.logout) {
|
|
18945
|
+
const { logout } = await import("./logout-MBS7L3ZW.js");
|
|
18946
|
+
const config = await loadConfig(opts.config);
|
|
18947
|
+
await logout({ config });
|
|
18948
|
+
process.exit(0);
|
|
18949
|
+
}
|
|
18950
|
+
if (opts.setup) {
|
|
18951
|
+
const config = await loadConfig(opts.config);
|
|
18952
|
+
const workspaceRoot = resolveWorkspaceRoot(config, opts.path);
|
|
18953
|
+
const wizard = new SetupWizard(workspaceRoot, config);
|
|
18954
|
+
const result = await wizard.run({ skipWelcome: false });
|
|
18955
|
+
if (result.cancelled) {
|
|
18956
|
+
console.log(chalk17.gray("\nSetup cancelled."));
|
|
18957
|
+
process.exit(0);
|
|
18958
|
+
}
|
|
18959
|
+
if (result.success) {
|
|
18960
|
+
const newConfig = { ...config, ...result.config };
|
|
18961
|
+
await saveConfig(newConfig);
|
|
18962
|
+
console.log(chalk17.green("\nSetup complete! Run `autohand` to start."));
|
|
18963
|
+
}
|
|
18964
|
+
process.exit(0);
|
|
18965
|
+
}
|
|
17585
18966
|
if (opts.patch) {
|
|
17586
18967
|
await runPatchMode(opts);
|
|
17587
18968
|
return;
|
|
@@ -17600,59 +18981,38 @@ program.name("autohand").description("Autonomous LLM-powered coding agent CLI").
|
|
|
17600
18981
|
program.command("resume <sessionId>").description("Resume a previous session").option("--path <path>", "Workspace path to operate in").option("--model <model>", "Override the configured LLM model").action(async (sessionId, opts) => {
|
|
17601
18982
|
await runCLI({ ...opts, resumeSessionId: sessionId });
|
|
17602
18983
|
});
|
|
18984
|
+
program.command("login").description("Sign in to your Autohand account").action(async () => {
|
|
18985
|
+
const { login } = await import("./login-NYWZRZO5.js");
|
|
18986
|
+
const config = await loadConfig();
|
|
18987
|
+
await login({ config });
|
|
18988
|
+
process.exit(0);
|
|
18989
|
+
});
|
|
18990
|
+
program.command("logout").description("Sign out of your Autohand account").action(async () => {
|
|
18991
|
+
const { logout } = await import("./logout-MBS7L3ZW.js");
|
|
18992
|
+
const config = await loadConfig();
|
|
18993
|
+
await logout({ config });
|
|
18994
|
+
process.exit(0);
|
|
18995
|
+
});
|
|
17603
18996
|
async function runCLI(options) {
|
|
17604
18997
|
const statusPanel = null;
|
|
17605
18998
|
try {
|
|
17606
18999
|
let config = await loadConfig(options.config);
|
|
19000
|
+
const workspaceRoot = resolveWorkspaceRoot(config, options.path);
|
|
17607
19001
|
const providerName = config.provider ?? "openrouter";
|
|
17608
19002
|
const providerConfig = getProviderConfig(config, providerName);
|
|
17609
19003
|
if (!providerConfig) {
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
console.log(chalk16.yellow(`No ${providerName} API key configured.
|
|
17616
|
-
`));
|
|
17617
|
-
let apiKey;
|
|
17618
|
-
try {
|
|
17619
|
-
const result = await enquirer4.prompt({
|
|
17620
|
-
type: "password",
|
|
17621
|
-
name: "apiKey",
|
|
17622
|
-
message: `Enter your ${providerName === "openrouter" ? "OpenRouter" : providerName} API key`,
|
|
17623
|
-
validate: (val) => {
|
|
17624
|
-
if (typeof val !== "string" || !val.trim()) {
|
|
17625
|
-
return "API key is required";
|
|
17626
|
-
}
|
|
17627
|
-
return true;
|
|
17628
|
-
}
|
|
17629
|
-
});
|
|
17630
|
-
apiKey = result.apiKey;
|
|
17631
|
-
} catch (error) {
|
|
17632
|
-
if (error?.code === "ERR_USE_AFTER_CLOSE" || error?.message?.includes("cancelled")) {
|
|
17633
|
-
console.log(chalk16.gray("\nSetup cancelled."));
|
|
17634
|
-
process.exit(0);
|
|
17635
|
-
}
|
|
17636
|
-
throw error;
|
|
19004
|
+
const wizard = new SetupWizard(workspaceRoot, config);
|
|
19005
|
+
const result = await wizard.run({ skipWelcome: !config.isNewConfig });
|
|
19006
|
+
if (result.cancelled) {
|
|
19007
|
+
console.log(chalk17.gray("\nSetup cancelled."));
|
|
19008
|
+
process.exit(0);
|
|
17637
19009
|
}
|
|
17638
|
-
if (
|
|
17639
|
-
config
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
baseUrl: config.openrouter?.baseUrl || "https://openrouter.ai/api/v1",
|
|
17643
|
-
model: config.openrouter?.model || "anthropic/claude-sonnet-4-20250514"
|
|
17644
|
-
};
|
|
17645
|
-
} else if (providerName === "openai") {
|
|
17646
|
-
config.openai = {
|
|
17647
|
-
...config.openai,
|
|
17648
|
-
apiKey: apiKey.trim(),
|
|
17649
|
-
model: config.openai?.model || "gpt-4o"
|
|
17650
|
-
};
|
|
19010
|
+
if (result.success) {
|
|
19011
|
+
config = { ...config, ...result.config };
|
|
19012
|
+
await saveConfig(config);
|
|
19013
|
+
console.log();
|
|
17651
19014
|
}
|
|
17652
|
-
await saveConfig(config);
|
|
17653
|
-
console.log(chalk16.green("\u2713 API key saved to config\n"));
|
|
17654
19015
|
}
|
|
17655
|
-
const workspaceRoot = resolveWorkspaceRoot(config, options.path);
|
|
17656
19016
|
const safetyCheck = checkWorkspaceSafety(workspaceRoot);
|
|
17657
19017
|
if (!safetyCheck.safe) {
|
|
17658
19018
|
printDangerousWorkspaceWarning(workspaceRoot, safetyCheck);
|
|
@@ -17672,7 +19032,7 @@ async function runCLI(options) {
|
|
|
17672
19032
|
const checkResults = await runStartupChecks(workspaceRoot);
|
|
17673
19033
|
printStartupCheckResults(checkResults);
|
|
17674
19034
|
if (!checkResults.allRequiredMet) {
|
|
17675
|
-
console.log(
|
|
19035
|
+
console.log(chalk17.yellow("Continuing anyway, but some features may not work correctly.\n"));
|
|
17676
19036
|
}
|
|
17677
19037
|
if (options.model) {
|
|
17678
19038
|
const providerName2 = config.provider ?? "openrouter";
|
|
@@ -17687,16 +19047,17 @@ async function runCLI(options) {
|
|
|
17687
19047
|
const llmProvider = ProviderFactory.create(config);
|
|
17688
19048
|
const files = new FileActionManager(workspaceRoot);
|
|
17689
19049
|
if (options.autoSkill) {
|
|
17690
|
-
console.log(
|
|
19050
|
+
console.log(chalk17.cyan("\nAuto-generating skills for this project...\n"));
|
|
17691
19051
|
const result = await runAutoSkillGeneration(workspaceRoot, llmProvider);
|
|
17692
19052
|
if (!result.success) {
|
|
17693
|
-
console.log(
|
|
19053
|
+
console.log(chalk17.yellow(result.error || "Failed to generate skills"));
|
|
17694
19054
|
}
|
|
17695
19055
|
return;
|
|
17696
19056
|
}
|
|
17697
19057
|
const agent = new AutohandAgent(llmProvider, files, runtime);
|
|
17698
19058
|
if (options.prompt) {
|
|
17699
19059
|
await agent.runCommandMode(options.prompt);
|
|
19060
|
+
process.exit(0);
|
|
17700
19061
|
} else if (options.resumeSessionId) {
|
|
17701
19062
|
await agent.resumeSession(options.resumeSessionId);
|
|
17702
19063
|
} else {
|
|
@@ -17704,7 +19065,7 @@ async function runCLI(options) {
|
|
|
17704
19065
|
}
|
|
17705
19066
|
} catch (error) {
|
|
17706
19067
|
if (error instanceof Error) {
|
|
17707
|
-
console.error(
|
|
19068
|
+
console.error(chalk17.red(error.message));
|
|
17708
19069
|
} else {
|
|
17709
19070
|
console.error(error);
|
|
17710
19071
|
}
|
|
@@ -17716,7 +19077,7 @@ function printBanner() {
|
|
|
17716
19077
|
return;
|
|
17717
19078
|
}
|
|
17718
19079
|
if (process.stdout.isTTY) {
|
|
17719
|
-
console.log(
|
|
19080
|
+
console.log(chalk17.gray(ASCII_FRIEND2));
|
|
17720
19081
|
} else {
|
|
17721
19082
|
console.log("autohand");
|
|
17722
19083
|
}
|
|
@@ -17734,28 +19095,28 @@ function printWelcome(runtime, authUser, versionCheck) {
|
|
|
17734
19095
|
}
|
|
17735
19096
|
})();
|
|
17736
19097
|
const dir = runtime.workspaceRoot;
|
|
17737
|
-
let versionLine = `${
|
|
19098
|
+
let versionLine = `${chalk17.bold("> Autohand")} v${getVersionString()}`;
|
|
17738
19099
|
if (versionCheck) {
|
|
17739
19100
|
if (versionCheck.isUpToDate) {
|
|
17740
|
-
versionLine +=
|
|
19101
|
+
versionLine += chalk17.green(" \u2713 Up to date");
|
|
17741
19102
|
} else if (versionCheck.updateAvailable && versionCheck.latestVersion) {
|
|
17742
|
-
versionLine +=
|
|
19103
|
+
versionLine += chalk17.yellow(` \u2B06 Update available: v${versionCheck.latestVersion}`);
|
|
17743
19104
|
}
|
|
17744
19105
|
}
|
|
17745
19106
|
console.log(versionLine);
|
|
17746
19107
|
if (versionCheck?.updateAvailable) {
|
|
17747
|
-
console.log(
|
|
19108
|
+
console.log(chalk17.gray(" \u21B3 Run: ") + chalk17.cyan("curl -fsSL https://autohand.ai/install.sh | sh"));
|
|
17748
19109
|
}
|
|
17749
19110
|
if (authUser) {
|
|
17750
|
-
console.log(
|
|
19111
|
+
console.log(chalk17.green(`Welcome back, ${authUser.name || authUser.email}!`));
|
|
17751
19112
|
}
|
|
17752
|
-
console.log(`${
|
|
19113
|
+
console.log(`${chalk17.gray("model:")} ${chalk17.cyan(model)} ${chalk17.gray("| directory:")} ${chalk17.cyan(dir)}`);
|
|
17753
19114
|
console.log();
|
|
17754
|
-
console.log(
|
|
17755
|
-
console.log(
|
|
17756
|
-
console.log(
|
|
19115
|
+
console.log(chalk17.gray("To get started, describe a task or try one of these commands:"));
|
|
19116
|
+
console.log(chalk17.cyan("/init ") + chalk17.gray("create an AGENTS.md file with instructions for Autohand"));
|
|
19117
|
+
console.log(chalk17.cyan("/help ") + chalk17.gray("review my current changes and find issues"));
|
|
17757
19118
|
if (!authUser) {
|
|
17758
|
-
console.log(
|
|
19119
|
+
console.log(chalk17.cyan("/login ") + chalk17.gray("sign in to your Autohand account"));
|
|
17759
19120
|
}
|
|
17760
19121
|
console.log();
|
|
17761
19122
|
}
|
|
@@ -17810,44 +19171,44 @@ async function displayPermissions(opts) {
|
|
|
17810
19171
|
const blacklist = manager.getBlacklist();
|
|
17811
19172
|
const settings = manager.getSettings();
|
|
17812
19173
|
console.log();
|
|
17813
|
-
console.log(
|
|
17814
|
-
console.log(
|
|
19174
|
+
console.log(chalk17.bold.cyan("Autohand Permissions"));
|
|
19175
|
+
console.log(chalk17.gray("\u2500".repeat(60)));
|
|
17815
19176
|
console.log();
|
|
17816
|
-
console.log(
|
|
19177
|
+
console.log(chalk17.bold("Mode:"), chalk17.cyan(settings.mode || "interactive"));
|
|
17817
19178
|
console.log();
|
|
17818
|
-
console.log(
|
|
17819
|
-
console.log(
|
|
19179
|
+
console.log(chalk17.bold("Workspace:"), chalk17.gray(workspaceRoot));
|
|
19180
|
+
console.log(chalk17.bold("Config:"), chalk17.gray(config.configPath));
|
|
17820
19181
|
console.log();
|
|
17821
|
-
console.log(
|
|
19182
|
+
console.log(chalk17.bold.green("Approved (Whitelist)"));
|
|
17822
19183
|
if (whitelist.length === 0) {
|
|
17823
|
-
console.log(
|
|
19184
|
+
console.log(chalk17.gray(" No approved patterns"));
|
|
17824
19185
|
} else {
|
|
17825
19186
|
whitelist.forEach((pattern, index) => {
|
|
17826
|
-
console.log(
|
|
19187
|
+
console.log(chalk17.green(` ${index + 1}. ${pattern}`));
|
|
17827
19188
|
});
|
|
17828
19189
|
}
|
|
17829
19190
|
console.log();
|
|
17830
|
-
console.log(
|
|
19191
|
+
console.log(chalk17.bold.red("Denied (Blacklist)"));
|
|
17831
19192
|
if (blacklist.length === 0) {
|
|
17832
|
-
console.log(
|
|
19193
|
+
console.log(chalk17.gray(" No denied patterns"));
|
|
17833
19194
|
} else {
|
|
17834
19195
|
blacklist.forEach((pattern, index) => {
|
|
17835
|
-
console.log(
|
|
19196
|
+
console.log(chalk17.red(` ${index + 1}. ${pattern}`));
|
|
17836
19197
|
});
|
|
17837
19198
|
}
|
|
17838
19199
|
console.log();
|
|
17839
|
-
console.log(
|
|
17840
|
-
console.log(
|
|
19200
|
+
console.log(chalk17.gray("\u2500".repeat(60)));
|
|
19201
|
+
console.log(chalk17.bold("Summary:"), `${whitelist.length} approved, ${blacklist.length} denied`);
|
|
17841
19202
|
console.log();
|
|
17842
|
-
console.log(
|
|
17843
|
-
console.log(
|
|
17844
|
-
console.log(
|
|
19203
|
+
console.log(chalk17.gray("Use /permissions in interactive mode to manage permissions."));
|
|
19204
|
+
console.log(chalk17.gray("Use --unrestricted to skip all approval prompts."));
|
|
19205
|
+
console.log(chalk17.gray("Use --restricted to deny all dangerous operations."));
|
|
17845
19206
|
console.log();
|
|
17846
19207
|
}
|
|
17847
19208
|
async function runPatchMode(opts) {
|
|
17848
19209
|
if (!opts.prompt) {
|
|
17849
|
-
console.error(
|
|
17850
|
-
console.error(
|
|
19210
|
+
console.error(chalk17.red("Error: --patch requires --prompt to specify the instruction"));
|
|
19211
|
+
console.error(chalk17.gray('Usage: autohand --prompt "your instruction" --patch'));
|
|
17851
19212
|
process.exit(1);
|
|
17852
19213
|
}
|
|
17853
19214
|
const fs21 = await import("fs-extra");
|
|
@@ -17881,23 +19242,23 @@ async function runPatchMode(opts) {
|
|
|
17881
19242
|
workspaceRoot,
|
|
17882
19243
|
options: patchOptions
|
|
17883
19244
|
};
|
|
17884
|
-
console.error(
|
|
19245
|
+
console.error(chalk17.cyan("Patch Mode: Changes will be captured without modifying files\n"));
|
|
17885
19246
|
try {
|
|
17886
19247
|
const agent = new AutohandAgent(llmProvider, files, runtime);
|
|
17887
19248
|
await agent.runCommandMode(opts.prompt);
|
|
17888
19249
|
const changes = files.getPendingChanges();
|
|
17889
19250
|
if (changes.length === 0) {
|
|
17890
|
-
console.error(
|
|
19251
|
+
console.error(chalk17.yellow("\nNo changes were made."));
|
|
17891
19252
|
process.exit(0);
|
|
17892
19253
|
}
|
|
17893
19254
|
const patch = generateUnifiedPatch(changes);
|
|
17894
|
-
console.error(
|
|
19255
|
+
console.error(chalk17.green(`
|
|
17895
19256
|
\u2713 ${formatChangeSummary(changes)}`));
|
|
17896
19257
|
if (opts.output) {
|
|
17897
19258
|
await fs21.default.ensureDir((await import("path")).dirname(opts.output));
|
|
17898
19259
|
await fs21.default.writeFile(opts.output, patch);
|
|
17899
|
-
console.error(
|
|
17900
|
-
console.error(
|
|
19260
|
+
console.error(chalk17.green(`\u2713 Patch written to ${opts.output}`));
|
|
19261
|
+
console.error(chalk17.gray("\nTo apply: git apply " + opts.output));
|
|
17901
19262
|
} else {
|
|
17902
19263
|
process.stdout.write(patch);
|
|
17903
19264
|
}
|
|
@@ -17905,14 +19266,14 @@ async function runPatchMode(opts) {
|
|
|
17905
19266
|
process.exit(0);
|
|
17906
19267
|
} catch (error) {
|
|
17907
19268
|
files.exitPreviewMode();
|
|
17908
|
-
console.error(
|
|
19269
|
+
console.error(chalk17.red(`
|
|
17909
19270
|
Error: ${error.message}`));
|
|
17910
19271
|
process.exit(1);
|
|
17911
19272
|
}
|
|
17912
19273
|
}
|
|
17913
19274
|
async function runAutoMode(opts) {
|
|
17914
19275
|
if (!opts.autoMode) {
|
|
17915
|
-
console.error(
|
|
19276
|
+
console.error(chalk17.red("Error: --auto-mode requires a task prompt"));
|
|
17916
19277
|
process.exit(1);
|
|
17917
19278
|
}
|
|
17918
19279
|
const config = await loadConfig(opts.config);
|
|
@@ -17934,9 +19295,9 @@ async function runAutoMode(opts) {
|
|
|
17934
19295
|
}
|
|
17935
19296
|
const { AutomodeManager, getAutomodeOptions } = await import("./AutomodeManager-WIMHLG4W.js");
|
|
17936
19297
|
const { HookManager: HookManager2 } = await import("./HookManager-VIX56KFU.js");
|
|
17937
|
-
const { SessionManager: SessionManager2 } = await import("./SessionManager-
|
|
19298
|
+
const { SessionManager: SessionManager2 } = await import("./SessionManager-XDBEQUPG.js");
|
|
17938
19299
|
const { MemoryManager: MemoryManager2 } = await import("./MemoryManager-UVHILGV5.js");
|
|
17939
|
-
const
|
|
19300
|
+
const readline3 = await import("readline");
|
|
17940
19301
|
const llmProvider = ProviderFactory.create(config);
|
|
17941
19302
|
const files = new FileActionManager(workspaceRoot);
|
|
17942
19303
|
const providerName = config.provider ?? "openrouter";
|
|
@@ -17954,26 +19315,26 @@ async function runAutoMode(opts) {
|
|
|
17954
19315
|
const automodeManager = new AutomodeManager(config, workspaceRoot, hookManager, session, memoryManager);
|
|
17955
19316
|
const automodeOptions = getAutomodeOptions(opts, config);
|
|
17956
19317
|
if (!automodeOptions) {
|
|
17957
|
-
console.error(
|
|
19318
|
+
console.error(chalk17.red("Error: Failed to parse auto-mode options"));
|
|
17958
19319
|
process.exit(1);
|
|
17959
19320
|
}
|
|
17960
19321
|
printBanner();
|
|
17961
|
-
console.log(
|
|
17962
|
-
console.log(
|
|
17963
|
-
console.log(
|
|
17964
|
-
console.log(
|
|
17965
|
-
console.log(
|
|
19322
|
+
console.log(chalk17.bold.cyan("\n\u{1F504} Auto-Mode: Autonomous Development Loop\n"));
|
|
19323
|
+
console.log(chalk17.gray("Task:"), chalk17.white(opts.autoMode));
|
|
19324
|
+
console.log(chalk17.gray("Max Iterations:"), chalk17.cyan(automodeOptions.maxIterations ?? 50));
|
|
19325
|
+
console.log(chalk17.gray("Completion Marker:"), chalk17.cyan(automodeOptions.completionPromise ?? "DONE"));
|
|
19326
|
+
console.log(chalk17.gray("Worktree Isolation:"), chalk17.cyan(automodeOptions.useWorktree !== false ? "enabled" : "disabled"));
|
|
17966
19327
|
console.log();
|
|
17967
19328
|
if (process.stdin.isTTY) {
|
|
17968
|
-
|
|
19329
|
+
readline3.emitKeypressEvents(process.stdin);
|
|
17969
19330
|
process.stdin.setRawMode(true);
|
|
17970
19331
|
process.stdin.on("keypress", (_str, key) => {
|
|
17971
19332
|
if (key && key.name === "escape") {
|
|
17972
|
-
console.log(
|
|
19333
|
+
console.log(chalk17.yellow("\n\u26A0\uFE0F Cancelling auto-mode..."));
|
|
17973
19334
|
automodeManager.cancel("user_escape");
|
|
17974
19335
|
}
|
|
17975
19336
|
if (key && key.ctrl && key.name === "c") {
|
|
17976
|
-
console.log(
|
|
19337
|
+
console.log(chalk17.yellow("\n\u26A0\uFE0F Cancelling auto-mode..."));
|
|
17977
19338
|
automodeManager.cancel("user_escape");
|
|
17978
19339
|
if (process.stdin.isTTY) {
|
|
17979
19340
|
process.stdin.setRawMode(false);
|
|
@@ -18005,7 +19366,7 @@ async function runAutoMode(opts) {
|
|
|
18005
19366
|
} catch (err) {
|
|
18006
19367
|
success = false;
|
|
18007
19368
|
error = err.message;
|
|
18008
|
-
console.error(
|
|
19369
|
+
console.error(chalk17.red(`Iteration error: ${error}`));
|
|
18009
19370
|
}
|
|
18010
19371
|
return {
|
|
18011
19372
|
success,
|
|
@@ -18022,17 +19383,50 @@ async function runAutoMode(opts) {
|
|
|
18022
19383
|
if (finalState) {
|
|
18023
19384
|
session.metadata.automodeIterations = finalState.currentIteration;
|
|
18024
19385
|
const statusText = finalState.status === "completed" ? "completed" : `ended (${finalState.status})`;
|
|
18025
|
-
|
|
18026
|
-
|
|
19386
|
+
console.log(chalk17.gray(`
|
|
19387
|
+
\u{1F4CA} Auto-mode ${statusText} after ${finalState.currentIteration} iterations`));
|
|
19388
|
+
}
|
|
19389
|
+
console.log(chalk17.cyan("\n\u{1F504} Auto-mode finished. You can continue working interactively.\n"));
|
|
19390
|
+
console.log(chalk17.gray("Press Enter to continue in interactive mode, or Ctrl+C to exit.\n"));
|
|
19391
|
+
const continuePromise = new Promise((resolve) => {
|
|
19392
|
+
if (!process.stdin.isTTY) {
|
|
19393
|
+
resolve(false);
|
|
19394
|
+
return;
|
|
19395
|
+
}
|
|
19396
|
+
readline3.emitKeypressEvents(process.stdin);
|
|
19397
|
+
process.stdin.setRawMode(true);
|
|
19398
|
+
process.stdin.resume();
|
|
19399
|
+
const handleKey = (_str, key) => {
|
|
19400
|
+
process.stdin.off("keypress", handleKey);
|
|
19401
|
+
if (process.stdin.isTTY) {
|
|
19402
|
+
process.stdin.setRawMode(false);
|
|
19403
|
+
}
|
|
19404
|
+
if (key && key.ctrl && key.name === "c") {
|
|
19405
|
+
resolve(false);
|
|
19406
|
+
} else if (key && key.name === "return") {
|
|
19407
|
+
resolve(true);
|
|
19408
|
+
} else {
|
|
19409
|
+
resolve(true);
|
|
19410
|
+
}
|
|
19411
|
+
};
|
|
19412
|
+
process.stdin.on("keypress", handleKey);
|
|
19413
|
+
});
|
|
19414
|
+
const shouldContinue = await continuePromise;
|
|
19415
|
+
if (!shouldContinue) {
|
|
19416
|
+
const statusText = finalState?.status === "completed" ? "completed" : `ended (${finalState?.status})`;
|
|
19417
|
+
await sessionManager.closeSession(`Auto-mode ${statusText} after ${finalState?.currentIteration ?? 0} iterations: ${opts.autoMode?.slice(0, 50)}...`);
|
|
19418
|
+
console.log(chalk17.gray(`
|
|
18027
19419
|
\u{1F4C1} Session saved: ${session.metadata.sessionId}`));
|
|
19420
|
+
process.exit(finalState?.status === "completed" ? 0 : 1);
|
|
18028
19421
|
}
|
|
18029
|
-
|
|
19422
|
+
console.log(chalk17.cyan("\n\u25B6\uFE0F Continuing in interactive mode...\n"));
|
|
19423
|
+
await agent.runInteractive();
|
|
18030
19424
|
} catch (error) {
|
|
18031
19425
|
if (process.stdin.isTTY) {
|
|
18032
19426
|
process.stdin.setRawMode(false);
|
|
18033
19427
|
}
|
|
18034
19428
|
await sessionManager.closeSession(`Auto-mode failed: ${error.message}`);
|
|
18035
|
-
console.error(
|
|
19429
|
+
console.error(chalk17.red(`
|
|
18036
19430
|
Auto-mode error: ${error.message}`));
|
|
18037
19431
|
process.exit(1);
|
|
18038
19432
|
}
|