autohand-cli 0.6.4 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/SkillsRegistry-LXDK73BL.cjs +9 -0
  2. package/dist/SkillsRegistry-SP5MX7OA.js +9 -0
  3. package/dist/agents-FH47ZMOI.cjs +10 -0
  4. package/dist/{agents-OJWYZN6X.js → agents-NB5VQN6H.js} +2 -2
  5. package/dist/{agents-new-WQLJOXSS.js → agents-new-M325HGWT.js} +3 -2
  6. package/dist/agents-new-XLEU26YI.cjs +11 -0
  7. package/dist/{chunk-DD2YPHP5.cjs → chunk-2OBNJCG6.cjs} +15 -13
  8. package/dist/{chunk-NWXYG5PQ.js → chunk-37NUB5KX.js} +1 -1
  9. package/dist/{chunk-AL4Z4WKG.cjs → chunk-3DPDLZYY.cjs} +9 -7
  10. package/dist/chunk-3HPUOQJN.cjs +23 -0
  11. package/dist/{chunk-G7SYGATA.cjs → chunk-3ZUWWML7.cjs} +2 -2
  12. package/dist/{chunk-3Y6G5DUX.cjs → chunk-53YDUYNS.cjs} +10 -4
  13. package/dist/{chunk-PRZTK2FX.js → chunk-5WKR4HIB.js} +7 -5
  14. package/dist/{chunk-ZTA2ASFW.cjs → chunk-6ZGNSZRG.cjs} +1 -1
  15. package/dist/chunk-7BYSXAKS.js +23 -0
  16. package/dist/{chunk-6FEZ6JAQ.js → chunk-7HB7GSQF.js} +1 -1
  17. package/dist/{chunk-UBGEAEKS.js → chunk-AY2XV7TH.js} +1 -1
  18. package/dist/{chunk-7RRX7H2X.cjs → chunk-B5N5UAMO.cjs} +20 -12
  19. package/dist/{chunk-KZ2UXXLH.js → chunk-BAHUKJJR.js} +7 -5
  20. package/dist/{chunk-KT55HW6V.js → chunk-CHQMK2ZG.js} +1 -1
  21. package/dist/chunk-CVYEUA3D.cjs +528 -0
  22. package/dist/{chunk-MRQV5HMC.js → chunk-DE7YC5MB.js} +8 -6
  23. package/dist/{chunk-AD4O67ZA.cjs → chunk-EJ77L3KT.cjs} +10 -10
  24. package/dist/{chunk-737A24RB.js → chunk-FUEL6BK7.js} +9 -1
  25. package/dist/{chunk-6MCXWSR3.js → chunk-H4RPZD6H.js} +1 -1
  26. package/dist/chunk-JHGIWNHL.cjs +46 -0
  27. package/dist/{chunk-27JNK5TE.cjs → chunk-LUKMRIKJ.cjs} +40 -61
  28. package/dist/{chunk-4H3B46YX.js → chunk-MWLAHCU7.js} +9 -3
  29. package/dist/{chunk-QCMC2WOC.cjs → chunk-N6ZOJI2M.cjs} +4 -4
  30. package/dist/{chunk-2TPGTNNY.js → chunk-NGSLABLS.js} +37 -58
  31. package/dist/chunk-QHPFA6OE.js +46 -0
  32. package/dist/{chunk-M4LKQQHU.cjs → chunk-REPKBECD.cjs} +2 -2
  33. package/dist/chunk-SKU4M27Z.js +528 -0
  34. package/dist/chunk-W4XTDUGT.js +267 -0
  35. package/dist/{chunk-RDEROLKA.cjs → chunk-XAM7SFVB.cjs} +8 -6
  36. package/dist/chunk-XF4EQ3IV.cjs +267 -0
  37. package/dist/{chunk-IHJDYAYJ.cjs → chunk-XTHHDIBG.cjs} +9 -1
  38. package/dist/{chunk-5N3QP5LJ.js → chunk-YDH2BMEN.js} +19 -11
  39. package/dist/constants-ICQLSGZN.cjs +19 -0
  40. package/dist/constants-N3I2FHCM.js +19 -0
  41. package/dist/feedback-TOGESBX7.cjs +11 -0
  42. package/dist/{feedback-3THCLEBE.js → feedback-YGSYBQEW.js} +3 -2
  43. package/dist/index.cjs +1463 -1146
  44. package/dist/index.js +987 -670
  45. package/dist/login-QVBS7KBK.cjs +13 -0
  46. package/dist/login-XUVEFKCR.js +13 -0
  47. package/dist/logout-75YLPOBK.js +13 -0
  48. package/dist/logout-FYYR5KCP.cjs +13 -0
  49. package/dist/{permissions-CYW62ZK3.js → permissions-3GS4ZWVA.js} +2 -1
  50. package/dist/permissions-E3MTIE7D.cjs +10 -0
  51. package/dist/{skills-HF4SAF5O.js → skills-3M26KASS.js} +3 -1
  52. package/dist/skills-BOFY5RQN.cjs +13 -0
  53. package/dist/skills-install-WJ2LDRQG.js +680 -0
  54. package/dist/skills-install-Z3W5PQWQ.cjs +680 -0
  55. package/dist/skills-new-KIBUN63X.js +12 -0
  56. package/dist/skills-new-XDYS24XW.cjs +12 -0
  57. package/dist/{status-SLYYTKXD.js → status-6FY6RKIS.js} +1 -1
  58. package/dist/status-DJHDT6QH.cjs +9 -0
  59. package/dist/theme-2UK74UWR.cjs +13 -0
  60. package/dist/{theme-THMQ5AIN.js → theme-XNZ2X6HE.js} +3 -3
  61. package/package.json +1 -1
  62. package/dist/agents-EHLYBJLK.cjs +0 -10
  63. package/dist/agents-new-7VPASCBV.cjs +0 -10
  64. package/dist/chunk-NEAJ2UWG.js +0 -191
  65. package/dist/chunk-Z6SGIQWH.cjs +0 -191
  66. package/dist/feedback-PATTKRH5.cjs +0 -10
  67. package/dist/login-QJROML5I.js +0 -12
  68. package/dist/login-X66DSV75.cjs +0 -12
  69. package/dist/logout-3Z7R3F7J.cjs +0 -12
  70. package/dist/logout-RJ5OAXRI.js +0 -12
  71. package/dist/permissions-NOC5DMOH.cjs +0 -9
  72. package/dist/skills-U6J6DFLK.cjs +0 -11
  73. package/dist/skills-new-QDTNEG3R.js +0 -10
  74. package/dist/skills-new-UPVBHIF2.cjs +0 -10
  75. package/dist/status-GR73LEEN.cjs +0 -9
  76. package/dist/theme-YDANJLZR.cjs +0 -13
package/dist/index.js CHANGED
@@ -1,7 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ installMetadata,
4
+ metadata as metadata22
5
+ } from "./chunk-W4XTDUGT.js";
6
+ import {
7
+ metadata as metadata23
8
+ } from "./chunk-NGSLABLS.js";
2
9
  import {
3
10
  metadata as metadata24
4
- } from "./chunk-NWXYG5PQ.js";
11
+ } from "./chunk-37NUB5KX.js";
12
+ import {
13
+ SkillsRegistry
14
+ } from "./chunk-SKU4M27Z.js";
15
+ import "./chunk-QHPFA6OE.js";
16
+ import {
17
+ metadata as metadata15
18
+ } from "./chunk-2NUX2RAI.js";
5
19
  import {
6
20
  metadata as metadata16
7
21
  } from "./chunk-YWKZF2SA.js";
@@ -11,16 +25,16 @@ import {
11
25
  import {
12
26
  metadata as metadata18,
13
27
  package_default
14
- } from "./chunk-6MCXWSR3.js";
28
+ } from "./chunk-H4RPZD6H.js";
15
29
  import {
16
30
  metadata as metadata19
17
- } from "./chunk-MRQV5HMC.js";
31
+ } from "./chunk-DE7YC5MB.js";
18
32
  import {
19
33
  metadata as metadata20
20
- } from "./chunk-KZ2UXXLH.js";
34
+ } from "./chunk-BAHUKJJR.js";
21
35
  import {
22
36
  getAuthClient
23
- } from "./chunk-KT55HW6V.js";
37
+ } from "./chunk-CHQMK2ZG.js";
24
38
  import {
25
39
  ThemeProvider,
26
40
  getProviderConfig,
@@ -31,33 +45,24 @@ import {
31
45
  resolveWorkspaceRoot,
32
46
  saveConfig,
33
47
  useTheme
34
- } from "./chunk-UBGEAEKS.js";
48
+ } from "./chunk-AY2XV7TH.js";
35
49
  import {
36
50
  metadata as metadata21
37
- } from "./chunk-5N3QP5LJ.js";
38
- import {
39
- metadata as metadata22
40
- } from "./chunk-NEAJ2UWG.js";
51
+ } from "./chunk-YDH2BMEN.js";
41
52
  import {
42
- metadata as metadata23,
43
- validateSkillFrontmatter
44
- } from "./chunk-2TPGTNNY.js";
53
+ metadata as metadata7
54
+ } from "./chunk-SVLBJMYO.js";
45
55
  import {
46
56
  AgentRegistry,
47
57
  metadata as metadata8
48
- } from "./chunk-6FEZ6JAQ.js";
58
+ } from "./chunk-7HB7GSQF.js";
49
59
  import {
50
60
  metadata as metadata9
51
- } from "./chunk-PRZTK2FX.js";
61
+ } from "./chunk-5WKR4HIB.js";
52
62
  import {
53
63
  metadata as metadata10
54
- } from "./chunk-4H3B46YX.js";
55
- import {
56
- AUTOHAND_FILES,
57
- AUTOHAND_HOME,
58
- AUTOHAND_PATHS,
59
- PROJECT_DIR_NAME
60
- } from "./chunk-737A24RB.js";
64
+ } from "./chunk-MWLAHCU7.js";
65
+ import "./chunk-7BYSXAKS.js";
61
66
  import {
62
67
  metadata as metadata11
63
68
  } from "./chunk-I4HVBWYF.js";
@@ -72,8 +77,11 @@ import {
72
77
  metadata as metadata14
73
78
  } from "./chunk-XDVG3NM4.js";
74
79
  import {
75
- metadata as metadata15
76
- } from "./chunk-2NUX2RAI.js";
80
+ AUTOHAND_FILES,
81
+ AUTOHAND_HOME,
82
+ AUTOHAND_PATHS,
83
+ PROJECT_DIR_NAME
84
+ } from "./chunk-FUEL6BK7.js";
77
85
  import {
78
86
  metadata
79
87
  } from "./chunk-KZ7VMQTC.js";
@@ -93,9 +101,6 @@ import {
93
101
  import {
94
102
  metadata as metadata6
95
103
  } from "./chunk-QJ53OSGF.js";
96
- import {
97
- metadata as metadata7
98
- } from "./chunk-SVLBJMYO.js";
99
104
  import {
100
105
  __commonJS,
101
106
  __toESM
@@ -2087,6 +2092,18 @@ var GitIgnoreParser = class {
2087
2092
  };
2088
2093
 
2089
2094
  // src/actions/filesystem.ts
2095
+ var FILE_LIMITS = {
2096
+ /** Maximum file size for read operations (10MB) */
2097
+ MAX_READ_SIZE: 10 * 1024 * 1024,
2098
+ /** Maximum file size for write operations (50MB) */
2099
+ MAX_WRITE_SIZE: 50 * 1024 * 1024,
2100
+ /** Maximum number of files in a single directory listing */
2101
+ MAX_DIR_ENTRIES: 1e4,
2102
+ /** Maximum search results to return */
2103
+ MAX_SEARCH_RESULTS: 1e3,
2104
+ /** Maximum undo stack size */
2105
+ MAX_UNDO_STACK: 100
2106
+ };
2090
2107
  var FileActionManager = class {
2091
2108
  constructor(workspaceRoot) {
2092
2109
  this.undoStack = [];
@@ -2101,12 +2118,27 @@ var FileActionManager = class {
2101
2118
  if (!exists) {
2102
2119
  throw new Error(`File ${target} not found in workspace.`);
2103
2120
  }
2121
+ const stats = await fs3.stat(filePath);
2122
+ if (stats.size > FILE_LIMITS.MAX_READ_SIZE) {
2123
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
2124
+ const limitMB = (FILE_LIMITS.MAX_READ_SIZE / 1024 / 1024).toFixed(0);
2125
+ throw new Error(`File ${target} is too large (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
2126
+ }
2104
2127
  return fs3.readFile(filePath, "utf8");
2105
2128
  }
2106
2129
  async writeFile(target, contents) {
2130
+ const contentSize = Buffer.byteLength(contents, "utf8");
2131
+ if (contentSize > FILE_LIMITS.MAX_WRITE_SIZE) {
2132
+ const sizeMB = (contentSize / 1024 / 1024).toFixed(2);
2133
+ const limitMB = (FILE_LIMITS.MAX_WRITE_SIZE / 1024 / 1024).toFixed(0);
2134
+ throw new Error(`Content too large to write (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
2135
+ }
2107
2136
  const filePath = this.resolvePath(target);
2108
2137
  await fs3.ensureDir(path2.dirname(filePath));
2109
2138
  const previous = await fs3.pathExists(filePath) ? await fs3.readFile(filePath, "utf8") : "";
2139
+ if (this.undoStack.length >= FILE_LIMITS.MAX_UNDO_STACK) {
2140
+ this.undoStack.shift();
2141
+ }
2110
2142
  this.undoStack.push({ absolutePath: filePath, previousContents: previous });
2111
2143
  await fs3.writeFile(filePath, contents, "utf8");
2112
2144
  }
@@ -2139,6 +2171,9 @@ var FileActionManager = class {
2139
2171
  "never",
2140
2172
  "--no-binary",
2141
2173
  // Skip binary files
2174
+ "--max-count",
2175
+ String(FILE_LIMITS.MAX_SEARCH_RESULTS),
2176
+ // Enforce result limit
2142
2177
  "--glob",
2143
2178
  "!*.{exe,dll,so,dylib,bin,o,a,lib,pyc,pyo,class,jar,war,ear}",
2144
2179
  "--glob",
@@ -2168,7 +2203,7 @@ var FileActionManager = class {
2168
2203
  encoding: "utf8"
2169
2204
  });
2170
2205
  if (rgResult.status === 0 && rgResult.stdout) {
2171
- return rgResult.stdout.trim().split("\n").filter(Boolean).map((line) => {
2206
+ const results = rgResult.stdout.trim().split("\n").filter(Boolean).slice(0, FILE_LIMITS.MAX_SEARCH_RESULTS).map((line) => {
2172
2207
  const [file, lineNo, ...rest] = line.split(":");
2173
2208
  return {
2174
2209
  file: path2.relative(this.workspaceRoot, path2.join(searchDir, file)),
@@ -2176,6 +2211,7 @@ var FileActionManager = class {
2176
2211
  text: rest.join(":")
2177
2212
  };
2178
2213
  });
2214
+ return results;
2179
2215
  }
2180
2216
  return this.walkFallback(query, searchDir);
2181
2217
  }
@@ -2300,13 +2336,37 @@ var FileActionManager = class {
2300
2336
  if (!await fs3.pathExists(filePath)) {
2301
2337
  return "";
2302
2338
  }
2339
+ const stats = await fs3.stat(filePath);
2340
+ if (stats.size > FILE_LIMITS.MAX_READ_SIZE) {
2341
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
2342
+ const limitMB = (FILE_LIMITS.MAX_READ_SIZE / 1024 / 1024).toFixed(0);
2343
+ throw new Error(`File ${target} is too large (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
2344
+ }
2303
2345
  return fs3.readFile(filePath, "utf8");
2304
2346
  }
2305
2347
  resolvePath(target) {
2306
2348
  const normalized = path2.isAbsolute(target) ? target : path2.join(this.workspaceRoot, target);
2307
2349
  const resolved = path2.resolve(normalized);
2308
- const rootWithSep = this.workspaceRoot.endsWith(path2.sep) ? this.workspaceRoot : `${this.workspaceRoot}${path2.sep}`;
2309
- if (resolved !== this.workspaceRoot && !resolved.startsWith(rootWithSep)) {
2350
+ let realPath;
2351
+ try {
2352
+ realPath = fs3.realpathSync(resolved);
2353
+ } catch {
2354
+ const parentDir = path2.dirname(resolved);
2355
+ try {
2356
+ const realParent = fs3.realpathSync(parentDir);
2357
+ realPath = path2.join(realParent, path2.basename(resolved));
2358
+ } catch {
2359
+ realPath = resolved;
2360
+ }
2361
+ }
2362
+ let realWorkspaceRoot;
2363
+ try {
2364
+ realWorkspaceRoot = fs3.realpathSync(this.workspaceRoot);
2365
+ } catch {
2366
+ realWorkspaceRoot = this.workspaceRoot;
2367
+ }
2368
+ const rootWithSep = realWorkspaceRoot.endsWith(path2.sep) ? realWorkspaceRoot : `${realWorkspaceRoot}${path2.sep}`;
2369
+ if (realPath !== realWorkspaceRoot && !realPath.startsWith(rootWithSep)) {
2310
2370
  throw new Error(`Path ${target} escapes the workspace root ${this.workspaceRoot}`);
2311
2371
  }
2312
2372
  return resolved;
@@ -2314,7 +2374,7 @@ var FileActionManager = class {
2314
2374
  walkFallback(query, baseDir) {
2315
2375
  const hits = [];
2316
2376
  const stack = [baseDir];
2317
- while (stack.length) {
2377
+ while (stack.length && hits.length < FILE_LIMITS.MAX_SEARCH_RESULTS) {
2318
2378
  const current = stack.pop();
2319
2379
  if (!current) {
2320
2380
  continue;
@@ -2336,7 +2396,8 @@ var FileActionManager = class {
2336
2396
  } else if (stats.isFile()) {
2337
2397
  const contents = fs3.readFileSync(current, "utf8");
2338
2398
  const lines = contents.split(/\r?\n/);
2339
- lines.forEach((line, idx) => {
2399
+ for (let idx = 0; idx < lines.length && hits.length < FILE_LIMITS.MAX_SEARCH_RESULTS; idx++) {
2400
+ const line = lines[idx];
2340
2401
  if (line.includes(query)) {
2341
2402
  hits.push({
2342
2403
  file: path2.relative(this.workspaceRoot, current),
@@ -2344,7 +2405,7 @@ var FileActionManager = class {
2344
2405
  text: line.trim()
2345
2406
  });
2346
2407
  }
2347
- });
2408
+ }
2348
2409
  }
2349
2410
  } catch {
2350
2411
  continue;
@@ -3113,8 +3174,8 @@ var ProviderFactory = class {
3113
3174
 
3114
3175
  // src/core/agent.ts
3115
3176
  import chalk13 from "chalk";
3116
- import fs22 from "fs-extra";
3117
- import path20 from "path";
3177
+ import fs20 from "fs-extra";
3178
+ import path19 from "path";
3118
3179
  import { randomUUID } from "crypto";
3119
3180
  import { spawnSync as spawnSync5 } from "child_process";
3120
3181
  import ora from "ora";
@@ -3334,12 +3395,12 @@ var MentionPreview = class {
3334
3395
  const dir = parts.length ? parts.join("/") + "/" : "";
3335
3396
  if (isSelected) {
3336
3397
  const highlighted = chalk2.cyan(filename);
3337
- const path23 = dir ? chalk2.gray(dir) : "";
3338
- return `${pointer} ${path23}${highlighted}`;
3398
+ const path22 = dir ? chalk2.gray(dir) : "";
3399
+ return `${pointer} ${path22}${highlighted}`;
3339
3400
  }
3340
3401
  const dimmedFilename = chalk2.white(filename);
3341
- const path22 = dir ? chalk2.gray(dir) : "";
3342
- return `${pointer} ${path22}${dimmedFilename}`;
3402
+ const path21 = dir ? chalk2.gray(dir) : "";
3403
+ return `${pointer} ${path21}${dimmedFilename}`;
3343
3404
  }
3344
3405
  const text = isSelected ? chalk2.cyan(entry) : entry;
3345
3406
  return `${pointer} ${text}`;
@@ -3549,6 +3610,25 @@ function parseBase64DataUrl(dataUrl) {
3549
3610
  return void 0;
3550
3611
  }
3551
3612
  }
3613
+ var VISION_MODELS = [
3614
+ "claude-3-opus",
3615
+ "claude-3-sonnet",
3616
+ "claude-3-haiku",
3617
+ "claude-3.5-sonnet",
3618
+ "claude-3.5-haiku",
3619
+ "claude-4",
3620
+ "gpt-4-vision",
3621
+ "gpt-4o",
3622
+ "gpt-4o-mini",
3623
+ "gemini-pro-vision",
3624
+ "gemini-1.5-pro",
3625
+ "gemini-1.5-flash",
3626
+ "gemini-2.0"
3627
+ ];
3628
+ function supportsVision(model) {
3629
+ const lowerModel = model.toLowerCase();
3630
+ return VISION_MODELS.some((v) => lowerModel.includes(v.toLowerCase()));
3631
+ }
3552
3632
 
3553
3633
  // src/ui/box.ts
3554
3634
  import chalk3 from "chalk";
@@ -4041,6 +4121,25 @@ function calculateContextUsage(messages, tools, model) {
4041
4121
 
4042
4122
  // src/actions/git.ts
4043
4123
  import { spawnSync as spawnSync3 } from "child_process";
4124
+ var GIT_SAFETY = {
4125
+ /** Branches where force push is blocked */
4126
+ PROTECTED_BRANCHES: ["main", "master", "develop", "production", "staging"],
4127
+ /** Maximum commits to push at once (to prevent accidental mass pushes) */
4128
+ MAX_COMMITS_PER_PUSH: 50
4129
+ };
4130
+ function getCurrentBranch(cwd) {
4131
+ const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf8" });
4132
+ return result.stdout?.trim() || "";
4133
+ }
4134
+ function getCommitsAhead(cwd, remote = "origin", branch) {
4135
+ const currentBranch = branch || getCurrentBranch(cwd);
4136
+ if (!currentBranch) return 0;
4137
+ const result = spawnSync3("git", ["rev-list", "--count", `${remote}/${currentBranch}..HEAD`], {
4138
+ cwd,
4139
+ encoding: "utf8"
4140
+ });
4141
+ return parseInt(result.stdout?.trim() || "0", 10) || 0;
4142
+ }
4044
4143
  function applyGitPatch(cwd, patch) {
4045
4144
  const result = spawnSync3("git", ["apply", "-"], {
4046
4145
  cwd,
@@ -4239,6 +4338,12 @@ function gitCherryPickContinue(cwd) {
4239
4338
  return "Cherry-pick continued";
4240
4339
  }
4241
4340
  function gitRebase(cwd, upstream, options = {}) {
4341
+ const currentBranch = getCurrentBranch(cwd);
4342
+ if (GIT_SAFETY.PROTECTED_BRANCHES.includes(currentBranch)) {
4343
+ throw new Error(
4344
+ `Rebasing protected branch "${currentBranch}" is blocked. Protected branches should not be rebased as it rewrites history. Use merge instead, or switch to a feature branch first.`
4345
+ );
4346
+ }
4242
4347
  const args = ["rebase"];
4243
4348
  if (options.onto) {
4244
4349
  args.push("--onto", options.onto);
@@ -4275,6 +4380,17 @@ function gitRebaseSkip(cwd) {
4275
4380
  return "Skipped commit and continued rebase";
4276
4381
  }
4277
4382
  function gitMerge(cwd, branch, options = {}) {
4383
+ const localBranches = spawnSync3("git", ["branch", "--list"], { cwd, encoding: "utf8" });
4384
+ const remoteBranches = spawnSync3("git", ["branch", "-r", "--list"], { cwd, encoding: "utf8" });
4385
+ const allBranches = (localBranches.stdout || "") + (remoteBranches.stdout || "");
4386
+ const branchExists = allBranches.split("\n").some(
4387
+ (b) => b.trim().replace("* ", "") === branch || b.trim() === `origin/${branch}` || b.trim() === branch
4388
+ );
4389
+ if (!branchExists) {
4390
+ throw new Error(
4391
+ `Branch "${branch}" not found locally or in remotes. For security, only existing branches can be merged. Run 'git fetch' first if the branch exists remotely.`
4392
+ );
4393
+ }
4278
4394
  const args = ["merge"];
4279
4395
  if (options.noCommit) {
4280
4396
  args.push("--no-commit");
@@ -4325,6 +4441,16 @@ function gitAdd(cwd, paths) {
4325
4441
  return result.stdout || `Staged ${paths.join(", ")}`;
4326
4442
  }
4327
4443
  function gitReset(cwd, mode = "mixed", ref) {
4444
+ if (mode === "hard") {
4445
+ const currentBranch = getCurrentBranch(cwd);
4446
+ if (GIT_SAFETY.PROTECTED_BRANCHES.includes(currentBranch)) {
4447
+ throw new Error(
4448
+ `Hard reset on protected branch "${currentBranch}" is blocked for safety.
4449
+ Protected branches: ${GIT_SAFETY.PROTECTED_BRANCHES.join(", ")}
4450
+ Use soft or mixed reset instead, or switch to a feature branch.`
4451
+ );
4452
+ }
4453
+ }
4328
4454
  const args = ["reset", `--${mode}`];
4329
4455
  if (ref) {
4330
4456
  args.push(ref);
@@ -4519,9 +4645,22 @@ function gitPull(cwd, remote, branch) {
4519
4645
  return result.stdout || "Pulled";
4520
4646
  }
4521
4647
  function gitPush(cwd, remote, branch, options = {}) {
4648
+ const targetRemote = remote || "origin";
4649
+ const targetBranch = branch || getCurrentBranch(cwd);
4650
+ if (options.force && GIT_SAFETY.PROTECTED_BRANCHES.includes(targetBranch)) {
4651
+ throw new Error(
4652
+ `Force push to protected branch "${targetBranch}" is blocked. Protected branches: ${GIT_SAFETY.PROTECTED_BRANCHES.join(", ")}`
4653
+ );
4654
+ }
4655
+ const commitsAhead = getCommitsAhead(cwd, targetRemote, targetBranch);
4656
+ if (commitsAhead > GIT_SAFETY.MAX_COMMITS_PER_PUSH) {
4657
+ throw new Error(
4658
+ `Too many commits to push (${commitsAhead}). Maximum allowed: ${GIT_SAFETY.MAX_COMMITS_PER_PUSH}. This limit exists to prevent accidental large pushes. Push manually if intentional.`
4659
+ );
4660
+ }
4522
4661
  const args = ["push"];
4523
4662
  if (options.force) {
4524
- args.push("--force");
4663
+ args.push("--force-with-lease");
4525
4664
  }
4526
4665
  if (options.setUpstream) {
4527
4666
  args.push("--set-upstream");
@@ -4895,6 +5034,7 @@ var SLASH_COMMANDS = [
4895
5034
  metadata20,
4896
5035
  metadata21,
4897
5036
  metadata22,
5037
+ installMetadata,
4898
5038
  metadata23,
4899
5039
  metadata24
4900
5040
  ];
@@ -5944,25 +6084,30 @@ var ToolManager = class _ToolManager {
5944
6084
  const requiresApproval = this.toolFilter.requiresApproval(call.tool, definition?.requiresApproval);
5945
6085
  if (requiresApproval) {
5946
6086
  let message = definition?.approvalMessage ?? `Allow tool ${call.tool}?`;
6087
+ let permContext = { tool: call.tool };
5947
6088
  if (call.tool === "run_command" && call.args) {
5948
- const cmd = call.args.command || "";
6089
+ const cmd = String(call.args.command || "");
5949
6090
  const args = Array.isArray(call.args.args) ? call.args.args.join(" ") : "";
5950
6091
  const fullCommand = args ? `${cmd} ${args}` : cmd;
5951
6092
  const dir = call.args.directory ? ` (in ${call.args.directory})` : "";
5952
6093
  message = `Run this command${dir}?
5953
6094
  $ ${fullCommand}`;
6095
+ permContext.command = fullCommand;
5954
6096
  } else if (call.tool === "delete_path" && call.args?.path) {
5955
6097
  message = `Delete this path?
5956
6098
  ${call.args.path}`;
6099
+ permContext.path = String(call.args.path);
5957
6100
  } else if (call.tool === "write_file" && call.args?.path) {
5958
6101
  message = `Write to this file?
5959
6102
  ${call.args.path}`;
6103
+ permContext.path = String(call.args.path);
5960
6104
  } else if (call.tool === "multi_file_edit" && call.args?.file_path) {
5961
6105
  const editCount = Array.isArray(call.args.edits) ? call.args.edits.length : 0;
5962
6106
  message = `Edit this file (${editCount} change${editCount === 1 ? "" : "s"})?
5963
6107
  ${call.args.file_path}`;
6108
+ permContext.path = String(call.args.file_path);
5964
6109
  }
5965
- const confirmed = await this.confirmApproval(message);
6110
+ const confirmed = await this.confirmApproval(message, permContext);
5966
6111
  if (!confirmed) {
5967
6112
  results.push({
5968
6113
  tool: call.tool,
@@ -7600,6 +7745,96 @@ function mergePermissions(globalSettings, localSettings) {
7600
7745
  }
7601
7746
 
7602
7747
  // src/permissions/PermissionManager.ts
7748
+ var DEFAULT_SECURITY_BLACKLIST = [
7749
+ // === Sensitive Files (read/write blocked) ===
7750
+ // Environment files with secrets
7751
+ "read_file:.env",
7752
+ "read_file:.env.*",
7753
+ "read_file:*.env",
7754
+ "write_file:.env",
7755
+ "write_file:.env.*",
7756
+ "write_file:*.env",
7757
+ // Git credentials and config
7758
+ "read_file:.git/config",
7759
+ "write_file:.git/config",
7760
+ "read_file:.git/credentials",
7761
+ "write_file:.git/credentials",
7762
+ "read_file:.gitconfig",
7763
+ "write_file:.gitconfig",
7764
+ // SSH keys
7765
+ "read_file:*/.ssh/*",
7766
+ "write_file:*/.ssh/*",
7767
+ "read_file:*/id_rsa*",
7768
+ "read_file:*/id_ed25519*",
7769
+ "read_file:*/id_ecdsa*",
7770
+ "write_file:*/id_rsa*",
7771
+ "write_file:*/id_ed25519*",
7772
+ // Cloud credentials
7773
+ "read_file:*/.aws/credentials",
7774
+ "read_file:*/.aws/config",
7775
+ "write_file:*/.aws/*",
7776
+ "read_file:*/.azure/*",
7777
+ "write_file:*/.azure/*",
7778
+ "read_file:*/.gcloud/*",
7779
+ "write_file:*/.gcloud/*",
7780
+ // Private keys and certificates
7781
+ "read_file:*.pem",
7782
+ "read_file:*.key",
7783
+ "read_file:*.p12",
7784
+ "read_file:*.pfx",
7785
+ "write_file:*.pem",
7786
+ "write_file:*.key",
7787
+ // GPG keys
7788
+ "read_file:*/.gnupg/*",
7789
+ "write_file:*/.gnupg/*",
7790
+ // NPM tokens
7791
+ "read_file:.npmrc",
7792
+ "write_file:.npmrc",
7793
+ // Docker credentials
7794
+ "read_file:*/.docker/config.json",
7795
+ "write_file:*/.docker/config.json",
7796
+ // Kubernetes credentials
7797
+ "read_file:*/.kube/config",
7798
+ "write_file:*/.kube/config",
7799
+ // === Dangerous Commands ===
7800
+ // Environment exposure
7801
+ "run_command:printenv",
7802
+ "run_command:printenv *",
7803
+ "run_command:env",
7804
+ "run_command:export",
7805
+ "run_command:set",
7806
+ // System information
7807
+ "run_command:cat /etc/passwd",
7808
+ "run_command:cat /etc/shadow",
7809
+ "run_command:cat /etc/sudoers",
7810
+ // Privilege escalation
7811
+ "run_command:sudo *",
7812
+ "run_command:su *",
7813
+ "run_command:doas *",
7814
+ // Destructive operations
7815
+ "run_command:rm -rf /",
7816
+ "run_command:rm -rf /*",
7817
+ "run_command:rm -rf ~",
7818
+ "run_command:rm -rf ~/*",
7819
+ "run_command:dd if=* of=/dev/*",
7820
+ "run_command:mkfs*",
7821
+ "run_command:wipefs*",
7822
+ "run_command:shred*",
7823
+ // Remote code execution
7824
+ "run_command:curl * | *sh",
7825
+ "run_command:wget * | *sh",
7826
+ "run_command:curl *|*sh",
7827
+ "run_command:wget *|*sh",
7828
+ // Network tools that can exfiltrate
7829
+ "run_command:nc -e*",
7830
+ "run_command:ncat -e*",
7831
+ "run_command:netcat -e*",
7832
+ // Credential theft
7833
+ "run_command:cat */.ssh/*",
7834
+ "run_command:cat */.aws/*",
7835
+ "run_command:cat *.pem",
7836
+ "run_command:cat *.key"
7837
+ ];
7603
7838
  var PermissionManager = class {
7604
7839
  constructor(options = {}) {
7605
7840
  this.sessionCache = /* @__PURE__ */ new Map();
@@ -7608,10 +7843,11 @@ var PermissionManager = class {
7608
7843
  const settings = isOptions ? options.settings ?? {} : options;
7609
7844
  this.onPersist = isOptions ? options.onPersist : void 0;
7610
7845
  this.workspaceRoot = isOptions ? options.workspaceRoot : void 0;
7846
+ const userBlacklist = settings.blacklist ?? [];
7611
7847
  this.settings = {
7612
7848
  mode: "interactive",
7613
7849
  whitelist: [],
7614
- blacklist: [],
7850
+ blacklist: userBlacklist,
7615
7851
  rules: [],
7616
7852
  rememberSession: true,
7617
7853
  ...settings
@@ -7655,6 +7891,9 @@ var PermissionManager = class {
7655
7891
  * Check if an action should be allowed, denied, or prompted
7656
7892
  */
7657
7893
  checkPermission(context) {
7894
+ if (this.isSecurityBlacklisted(context)) {
7895
+ return { allowed: false, reason: "blacklisted" };
7896
+ }
7658
7897
  const cacheKey = this.getCacheKey(context);
7659
7898
  if (this.settings.rememberSession && this.sessionCache.has(cacheKey)) {
7660
7899
  return {
@@ -7733,12 +7972,19 @@ var PermissionManager = class {
7733
7972
  return `${tool}:${value}`;
7734
7973
  }
7735
7974
  /**
7736
- * Check if context matches blacklist (uses merged global + local settings)
7975
+ * Check if context matches the immutable security blacklist
7976
+ * This check CANNOT be bypassed by any mode, whitelist, or user setting
7977
+ */
7978
+ isSecurityBlacklisted(context) {
7979
+ return DEFAULT_SECURITY_BLACKLIST.some((pattern) => this.matchesPattern(context, pattern));
7980
+ }
7981
+ /**
7982
+ * Check if context matches user blacklist (can be modified by user)
7737
7983
  */
7738
7984
  isBlacklisted(context) {
7739
7985
  const merged = this.getMergedSettings();
7740
- const blacklist = merged.blacklist || [];
7741
- return blacklist.some((pattern) => this.matchesPattern(context, pattern));
7986
+ const userBlacklist = merged.blacklist || [];
7987
+ return userBlacklist.some((pattern) => this.matchesPattern(context, pattern));
7742
7988
  }
7743
7989
  /**
7744
7990
  * Check if context matches whitelist (uses merged global + local settings)
@@ -8332,7 +8578,7 @@ var SecurityScanner = class {
8332
8578
 
8333
8579
  // src/core/actionExecutor.ts
8334
8580
  import { execSync as execSync2 } from "child_process";
8335
- var ActionExecutor = class {
8581
+ var ActionExecutor = class _ActionExecutor {
8336
8582
  constructor(deps) {
8337
8583
  this.deps = deps;
8338
8584
  this.searchCache = /* @__PURE__ */ new Map();
@@ -8420,8 +8666,8 @@ var ActionExecutor = class {
8420
8666
  throw new Error(`write_file requires a "path" argument. Received arguments: [${receivedKeys}]`);
8421
8667
  }
8422
8668
  const filePath = this.resolveWorkspacePath(action.path);
8423
- const fs24 = await import("fs-extra");
8424
- const exists = this.files.root && await fs24.pathExists(filePath);
8669
+ const fs22 = await import("fs-extra");
8670
+ const exists = this.files.root && await fs22.pathExists(filePath);
8425
8671
  const oldContent = exists ? await this.files.readFile(action.path) : "";
8426
8672
  const newContent = this.pickText(action.contents, action.content) ?? "";
8427
8673
  if (!exists) {
@@ -8442,7 +8688,8 @@ var ActionExecutor = class {
8442
8688
  const preview = newContent.length > 500 ? newContent.substring(0, 500) + "\n... (truncated)" : newContent;
8443
8689
  console.log(chalk6.gray(preview));
8444
8690
  const confirmed = await this.confirmDangerousAction(
8445
- `Create new file ${action.path}?`
8691
+ `Create new file ${action.path}?`,
8692
+ { tool: "write_file", path: action.path }
8446
8693
  );
8447
8694
  await this.permissionManager.recordDecision(permContext, confirmed);
8448
8695
  if (!confirmed) {
@@ -8546,7 +8793,10 @@ ${hit.snippet}`).join("\n\n");
8546
8793
  if (!action.path) {
8547
8794
  throw new Error('delete_path requires a "path" argument.');
8548
8795
  }
8549
- const confirmed = await this.confirmDangerousAction(`Delete ${action.path}?`);
8796
+ const confirmed = await this.confirmDangerousAction(
8797
+ `Delete ${action.path}?`,
8798
+ { tool: "delete_path", path: action.path }
8799
+ );
8550
8800
  if (!confirmed) {
8551
8801
  return `Skipped deleting ${action.path}`;
8552
8802
  }
@@ -8801,8 +9051,8 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
8801
9051
  const lines = [chalk6.cyan("\u{1F504} Worktree Sync Results:"), ""];
8802
9052
  if (result.synced.length > 0) {
8803
9053
  lines.push(chalk6.green(`Synced (${result.synced.length}):`));
8804
- for (const path22 of result.synced) {
8805
- lines.push(` \u2713 ${path22}`);
9054
+ for (const path21 of result.synced) {
9055
+ lines.push(` \u2713 ${path21}`);
8806
9056
  }
8807
9057
  lines.push("");
8808
9058
  }
@@ -9141,10 +9391,37 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
9141
9391
  if (builtInNames.includes(action.name)) {
9142
9392
  throw new Error(`Cannot create meta-tool "${action.name}": conflicts with built-in tool`);
9143
9393
  }
9144
- const dangerousPatterns = ["rm -rf /", "dd if=", "mkfs.", ":(){:|:&};:"];
9145
- for (const pattern of dangerousPatterns) {
9146
- if (action.handler.includes(pattern)) {
9147
- throw new Error(`Handler contains dangerous pattern: ${pattern}`);
9394
+ const dangerousPatterns = [
9395
+ // Destructive file operations
9396
+ { pattern: /rm\s+(-[rf]+\s+)*\/(?!\w)/i, description: "rm with root path" },
9397
+ { pattern: /rm\s+.*--no-preserve-root/i, description: "rm --no-preserve-root" },
9398
+ { pattern: /dd\s+.*(?:of|if)=\/dev\/[sh]d/i, description: "dd to disk device" },
9399
+ { pattern: /mkfs\./i, description: "filesystem format" },
9400
+ { pattern: /wipefs/i, description: "disk wipe" },
9401
+ // Privilege escalation
9402
+ { pattern: /\bsudo\s/i, description: "sudo command" },
9403
+ { pattern: /\bsu\s+-?\s*\w/i, description: "su command" },
9404
+ { pattern: /chmod\s+[0-7]*7[0-7]*/i, description: "world-writable chmod" },
9405
+ { pattern: /chown\s+root/i, description: "chown to root" },
9406
+ // Remote code execution
9407
+ { pattern: /curl\s+.*\|\s*(ba)?sh/i, description: "curl | bash" },
9408
+ { pattern: /wget\s+.*\|\s*(ba)?sh/i, description: "wget | sh" },
9409
+ { pattern: /\beval\s+[`$]/i, description: "eval with expansion" },
9410
+ // Fork bomb and resource exhaustion
9411
+ { pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/i, description: "fork bomb" },
9412
+ { pattern: /while\s+true.*do.*done/i, description: "infinite loop" },
9413
+ // Reverse shell indicators
9414
+ { pattern: /nc\s+.*-e\s*\/bin/i, description: "netcat reverse shell" },
9415
+ { pattern: /ncat\s+.*-e\s*\/bin/i, description: "ncat reverse shell" },
9416
+ { pattern: /bash\s+-i\s+>&?\s*\/dev\/tcp/i, description: "bash reverse shell" },
9417
+ // Dangerous network operations
9418
+ { pattern: /iptables\s+-F/i, description: "flush firewall rules" },
9419
+ // Crypto operations that could lock out user
9420
+ { pattern: /gpg\s+.*--encrypt.*-r\s+\S+\s+\//i, description: "gpg encrypt root" }
9421
+ ];
9422
+ for (const { pattern, description } of dangerousPatterns) {
9423
+ if (pattern.test(action.handler)) {
9424
+ throw new Error(`Handler contains dangerous pattern: ${description}`);
9148
9425
  }
9149
9426
  }
9150
9427
  const saved = await this.toolsRegistry.saveMetaTool({
@@ -9337,7 +9614,10 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
9337
9614
  if (this.isDestructiveCommand(definition.command)) {
9338
9615
  console.log(chalk6.red("Warning: command may be destructive."));
9339
9616
  }
9340
- const answer = await this.confirmDangerousAction("Add and run this custom command?");
9617
+ const answer = await this.confirmDangerousAction(
9618
+ "Add and run this custom command?",
9619
+ { tool: "run_command", command: definition.command }
9620
+ );
9341
9621
  if (!answer) {
9342
9622
  return "Custom command rejected by user.";
9343
9623
  }
@@ -9350,6 +9630,19 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
9350
9630
  const lowered = command.toLowerCase();
9351
9631
  return lowered.includes("rm ") || lowered.includes("sudo ") || lowered.includes("dd ");
9352
9632
  }
9633
+ static {
9634
+ /**
9635
+ * Shell metacharacters that could enable command injection
9636
+ */
9637
+ this.SHELL_METACHARACTERS = /[|;&$`><(){}[\]!#*?~'"\\]/;
9638
+ }
9639
+ /**
9640
+ * Safely escape a value for shell interpolation
9641
+ * Uses single quotes which prevent all shell expansion except for single quotes themselves
9642
+ */
9643
+ shellEscape(value) {
9644
+ return "'" + value.replace(/'/g, `'"'"'`) + "'";
9645
+ }
9353
9646
  /**
9354
9647
  * Execute a dynamic meta-tool by substituting {{param}} placeholders
9355
9648
  */
@@ -9363,18 +9656,39 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
9363
9656
  if (value === void 0 || value === null) {
9364
9657
  throw new Error(`Missing required parameter "${paramName}" for meta-tool "${metaTool.name}"`);
9365
9658
  }
9366
- const escapedValue = String(value).replace(/(["`$\\])/g, "\\$1");
9367
- command = command.replace(new RegExp(`\\{\\{${paramName}\\}\\}`, "g"), escapedValue);
9659
+ const stringValue = String(value);
9660
+ let safeValue;
9661
+ if (_ActionExecutor.SHELL_METACHARACTERS.test(stringValue)) {
9662
+ safeValue = this.shellEscape(stringValue);
9663
+ console.log(chalk6.yellow(` \u26A0 Parameter "${paramName}" contains shell metacharacters, escaped for safety`));
9664
+ } else {
9665
+ safeValue = stringValue;
9666
+ }
9667
+ command = command.replace(new RegExp(`\\{\\{${paramName}\\}\\}`, "g"), safeValue);
9368
9668
  }
9369
9669
  console.log(chalk6.cyan(`
9370
9670
  \u{1F527} Running meta-tool: ${metaTool.name}`));
9371
9671
  console.log(chalk6.gray(` $ ${command}`));
9372
- const result = await runCommand(command, [], this.runtime.workspaceRoot);
9672
+ const result = await runCommand(command, [], this.runtime.workspaceRoot, { shell: true });
9373
9673
  return [`$ ${command}`, result.stdout, result.stderr].filter(Boolean).join("\n");
9374
9674
  }
9375
9675
  applySearchReplaceBlocks(content, blocks) {
9376
- const MARKERS = { search: "<<<<<<< SEARCH", div: "=======", replace: ">>>>>>> REPLACE" };
9377
9676
  let result = content;
9677
+ if (blocks.includes("SEARCH:") && blocks.includes("REPLACE:") && !blocks.includes("<<<<<<< SEARCH")) {
9678
+ const searchIdx = blocks.indexOf("SEARCH:");
9679
+ const replaceIdx = blocks.indexOf("REPLACE:");
9680
+ if (searchIdx !== -1 && replaceIdx !== -1 && replaceIdx > searchIdx) {
9681
+ const searchText = blocks.slice(searchIdx + 7, replaceIdx).trim();
9682
+ const replaceText = blocks.slice(replaceIdx + 8).trim();
9683
+ const idx = result.indexOf(searchText);
9684
+ if (idx === -1) {
9685
+ throw new Error(`SEARCH text not found: "${searchText.slice(0, 50)}..."`);
9686
+ }
9687
+ result = result.slice(0, idx) + replaceText + result.slice(idx + searchText.length);
9688
+ return result;
9689
+ }
9690
+ }
9691
+ const MARKERS = { search: "<<<<<<< SEARCH", div: "=======", replace: ">>>>>>> REPLACE" };
9378
9692
  let remaining = blocks;
9379
9693
  while (remaining.includes(MARKERS.search)) {
9380
9694
  const searchStart = remaining.indexOf(MARKERS.search);
@@ -9688,6 +10002,12 @@ var SlashCommandHandler = class {
9688
10002
  this.commandMap = /* @__PURE__ */ new Map();
9689
10003
  commands.forEach((cmd) => this.commandMap.set(cmd.command, cmd));
9690
10004
  }
10005
+ /**
10006
+ * Check if a command is supported (exists in the command map)
10007
+ */
10008
+ isCommandSupported(command) {
10009
+ return this.commandMap.has(command);
10010
+ }
9691
10011
  async handle(command, args = []) {
9692
10012
  const meta = this.commandMap.get(command);
9693
10013
  if (!meta) {
@@ -9718,7 +10038,7 @@ var SlashCommandHandler = class {
9718
10038
  return help();
9719
10039
  }
9720
10040
  case "/agents": {
9721
- const { handler } = await import("./agents-OJWYZN6X.js");
10041
+ const { handler } = await import("./agents-NB5VQN6H.js");
9722
10042
  const output = await handler();
9723
10043
  if (output) {
9724
10044
  console.log(output);
@@ -9727,11 +10047,11 @@ var SlashCommandHandler = class {
9727
10047
  }
9728
10048
  case "/agents new":
9729
10049
  case "/agents-new": {
9730
- const { createAgent } = await import("./agents-new-WQLJOXSS.js");
10050
+ const { createAgent } = await import("./agents-new-M325HGWT.js");
9731
10051
  return createAgent(this.ctx);
9732
10052
  }
9733
10053
  case "/feedback": {
9734
- const { feedback } = await import("./feedback-3THCLEBE.js");
10054
+ const { feedback } = await import("./feedback-YGSYBQEW.js");
9735
10055
  return feedback(this.ctx);
9736
10056
  }
9737
10057
  case "/resume": {
@@ -9794,40 +10114,56 @@ var SlashCommandHandler = class {
9794
10114
  return null;
9795
10115
  }
9796
10116
  case "/status": {
9797
- const { status } = await import("./status-SLYYTKXD.js");
10117
+ const { status } = await import("./status-6FY6RKIS.js");
9798
10118
  return status(this.ctx);
9799
10119
  }
9800
10120
  case "/login": {
9801
- const { login } = await import("./login-QJROML5I.js");
10121
+ const { login } = await import("./login-XUVEFKCR.js");
9802
10122
  return login({ config: this.ctx.config });
9803
10123
  }
9804
10124
  case "/logout": {
9805
- const { logout } = await import("./logout-RJ5OAXRI.js");
10125
+ const { logout } = await import("./logout-75YLPOBK.js");
9806
10126
  return logout({ config: this.ctx.config });
9807
10127
  }
9808
10128
  case "/permissions": {
9809
- const { permissions } = await import("./permissions-CYW62ZK3.js");
10129
+ const { permissions } = await import("./permissions-3GS4ZWVA.js");
9810
10130
  return permissions({ permissionManager: this.ctx.permissionManager });
9811
10131
  }
9812
10132
  case "/skills": {
9813
- const { skills } = await import("./skills-HF4SAF5O.js");
10133
+ const { skills } = await import("./skills-3M26KASS.js");
9814
10134
  if (!this.ctx.skillsRegistry) {
9815
- console.log(chalk7.yellow("Skills registry not available."));
9816
- return null;
10135
+ return "Skills registry not available.";
9817
10136
  }
9818
- return skills({ skillsRegistry: this.ctx.skillsRegistry }, args);
10137
+ return skills({
10138
+ skillsRegistry: this.ctx.skillsRegistry,
10139
+ workspaceRoot: this.ctx.workspaceRoot
10140
+ }, args);
10141
+ }
10142
+ case "/skills install": {
10143
+ const { skillsInstall } = await import("./skills-install-WJ2LDRQG.js");
10144
+ if (!this.ctx.skillsRegistry) {
10145
+ return "Skills registry not available.";
10146
+ }
10147
+ const skillName = args.join(" ").trim() || void 0;
10148
+ return skillsInstall({
10149
+ skillsRegistry: this.ctx.skillsRegistry,
10150
+ workspaceRoot: this.ctx.workspaceRoot
10151
+ }, skillName);
9819
10152
  }
9820
10153
  case "/skills new":
9821
10154
  case "/skills-new": {
9822
- const { createSkill } = await import("./skills-new-QDTNEG3R.js");
10155
+ const { createSkill } = await import("./skills-new-KIBUN63X.js");
9823
10156
  if (!this.ctx.skillsRegistry) {
9824
- console.log(chalk7.yellow("Skills registry not available."));
9825
- return null;
10157
+ return "Skills registry not available.";
9826
10158
  }
9827
- return createSkill({ llm: this.ctx.llm, skillsRegistry: this.ctx.skillsRegistry });
10159
+ return createSkill({
10160
+ llm: this.ctx.llm,
10161
+ skillsRegistry: this.ctx.skillsRegistry,
10162
+ workspaceRoot: this.ctx.workspaceRoot
10163
+ });
9828
10164
  }
9829
10165
  case "/theme": {
9830
- const { theme } = await import("./theme-THMQ5AIN.js");
10166
+ const { theme } = await import("./theme-XNZ2X6HE.js");
9831
10167
  if (!this.ctx.config) {
9832
10168
  console.log(chalk7.yellow("Config not available for theme selection."));
9833
10169
  return null;
@@ -11270,8 +11606,18 @@ var FeedbackManager = class {
11270
11606
  console.log(chalk10.gray(" Help us improve Autohand (takes 10 seconds)"));
11271
11607
  console.log(chalk10.cyan("\u2501".repeat(50)));
11272
11608
  console.log();
11609
+ const safePrompt = async (config) => {
11610
+ try {
11611
+ return await Enquirer.prompt(config);
11612
+ } catch (error) {
11613
+ if (error?.code === "ERR_USE_AFTER_CLOSE") {
11614
+ return null;
11615
+ }
11616
+ throw error;
11617
+ }
11618
+ };
11273
11619
  try {
11274
- const npsResult = await Enquirer.prompt({
11620
+ const npsResult = await safePrompt({
11275
11621
  type: "select",
11276
11622
  name: "score",
11277
11623
  message: "How would you rate your experience?",
@@ -11284,6 +11630,11 @@ var FeedbackManager = class {
11284
11630
  { name: "skip", message: `${chalk10.gray("Skip")}` }
11285
11631
  ]
11286
11632
  });
11633
+ if (!npsResult) {
11634
+ this.state.dismissed++;
11635
+ this.saveState();
11636
+ return false;
11637
+ }
11287
11638
  if (npsResult.score === "skip") {
11288
11639
  this.state.dismissed++;
11289
11640
  this.saveState();
@@ -11295,26 +11646,28 @@ var FeedbackManager = class {
11295
11646
  let improvement;
11296
11647
  let recommend;
11297
11648
  if (npsScore >= 4) {
11298
- const followUp = await Enquirer.prompt({
11649
+ const followUp = await safePrompt({
11299
11650
  type: "input",
11300
11651
  name: "reason",
11301
11652
  message: "What do you like most about Autohand? (optional, press Enter to skip)"
11302
11653
  });
11303
- reason = followUp.reason || void 0;
11304
- const recResult = await Enquirer.prompt({
11305
- type: "confirm",
11306
- name: "recommend",
11307
- message: "Would you recommend Autohand to a colleague?",
11308
- initial: true
11309
- });
11310
- recommend = recResult.recommend;
11654
+ reason = followUp?.reason || void 0;
11655
+ if (followUp) {
11656
+ const recResult = await safePrompt({
11657
+ type: "confirm",
11658
+ name: "recommend",
11659
+ message: "Would you recommend Autohand to a colleague?",
11660
+ initial: true
11661
+ });
11662
+ recommend = recResult?.recommend;
11663
+ }
11311
11664
  } else {
11312
- const followUp = await Enquirer.prompt({
11665
+ const followUp = await safePrompt({
11313
11666
  type: "input",
11314
11667
  name: "improvement",
11315
11668
  message: "What could we do better? (optional, press Enter to skip)"
11316
11669
  });
11317
- improvement = followUp.improvement || void 0;
11670
+ improvement = followUp?.improvement || void 0;
11318
11671
  }
11319
11672
  const response = {
11320
11673
  npsScore,
@@ -11341,6 +11694,9 @@ var FeedbackManager = class {
11341
11694
  console.log();
11342
11695
  return true;
11343
11696
  } catch (error) {
11697
+ if (error?.code === "ERR_USE_AFTER_CLOSE") {
11698
+ return false;
11699
+ }
11344
11700
  this.state.dismissed++;
11345
11701
  this.saveState();
11346
11702
  console.log(chalk10.gray("\nFeedback skipped.\n"));
@@ -11840,6 +12196,30 @@ var TelemetryManager = class {
11840
12196
  context: data.context
11841
12197
  });
11842
12198
  }
12199
+ /**
12200
+ * Track a session failure as a bug report with detailed context.
12201
+ * Prefixes the error type with "BUG:" for easy identification.
12202
+ */
12203
+ async trackSessionFailureBug(data) {
12204
+ this.errorsCount++;
12205
+ const sanitizedStack = data.error.stack?.replace(/\/Users\/[^/]+/g, "/Users/***").replace(/\/home\/[^/]+/g, "/home/***").replace(/C:\\Users\\[^\\]+/g, "C:\\Users\\***");
12206
+ const bugData = {
12207
+ type: "BUG:session_failure",
12208
+ errorMessage: data.error.message,
12209
+ errorName: data.error.name,
12210
+ stack: sanitizedStack,
12211
+ retryAttempt: data.retryAttempt,
12212
+ maxRetries: data.maxRetries,
12213
+ conversationLength: data.conversationLength,
12214
+ lastToolCalls: data.lastToolCalls,
12215
+ iterationCount: data.iterationCount,
12216
+ contextUsage: data.contextUsage,
12217
+ model: data.model || this.currentModel || void 0,
12218
+ provider: data.provider || this.currentProvider || void 0,
12219
+ isRetrying: data.retryAttempt < data.maxRetries
12220
+ };
12221
+ await this.trackEvent("session_failure_bug", bugData);
12222
+ }
11843
12223
  /**
11844
12224
  * Track slash command usage
11845
12225
  */
@@ -11959,462 +12339,9 @@ var TelemetryManager = class {
11959
12339
  }
11960
12340
  };
11961
12341
 
11962
- // src/skills/SkillsRegistry.ts
11963
- import fs18 from "fs-extra";
11964
- import path17 from "path";
11965
-
11966
- // src/skills/SkillParser.ts
11967
- import fs17 from "fs-extra";
11968
- import YAML from "yaml";
11969
- var SkillParser = class {
11970
- /**
11971
- * Parse a SKILL.md file from disk
11972
- */
11973
- async parseFile(filePath, source) {
11974
- try {
11975
- const content = await fs17.readFile(filePath, "utf-8");
11976
- return this.parseContent(content, filePath, source);
11977
- } catch (error) {
11978
- return {
11979
- success: false,
11980
- error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
11981
- };
11982
- }
11983
- }
11984
- /**
11985
- * Parse SKILL.md content directly from a string
11986
- */
11987
- parseContent(content, filePath, source) {
11988
- const extraction = this.extractFrontmatter(content);
11989
- if (!extraction) {
11990
- return {
11991
- success: false,
11992
- error: "No valid YAML frontmatter found. SKILL.md must start with --- delimited frontmatter."
11993
- };
11994
- }
11995
- try {
11996
- const parsed = YAML.parse(extraction.frontmatter);
11997
- const validation = validateSkillFrontmatter(parsed);
11998
- if (!validation.valid) {
11999
- return {
12000
- success: false,
12001
- error: `Invalid skill frontmatter: ${validation.errors.join("; ")}`
12002
- };
12003
- }
12004
- const skill = {
12005
- name: parsed.name,
12006
- description: parsed.description,
12007
- license: parsed.license,
12008
- compatibility: parsed.compatibility,
12009
- metadata: parsed.metadata,
12010
- "allowed-tools": parsed["allowed-tools"],
12011
- body: extraction.body,
12012
- path: filePath,
12013
- source,
12014
- isActive: false
12015
- };
12016
- return { success: true, skill };
12017
- } catch (error) {
12018
- return {
12019
- success: false,
12020
- error: `Failed to parse YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
12021
- };
12022
- }
12023
- }
12024
- /**
12025
- * Extract YAML frontmatter and body from content
12026
- * Frontmatter must be delimited by --- at start and end
12027
- */
12028
- extractFrontmatter(content) {
12029
- if (!content.startsWith("---")) {
12030
- return null;
12031
- }
12032
- const lines = content.split("\n");
12033
- let closingIndex = -1;
12034
- for (let i = 1; i < lines.length; i++) {
12035
- if (lines[i].trim() === "---") {
12036
- closingIndex = i;
12037
- break;
12038
- }
12039
- }
12040
- if (closingIndex === -1) {
12041
- return null;
12042
- }
12043
- const frontmatter = lines.slice(1, closingIndex).join("\n");
12044
- const body = lines.slice(closingIndex + 1).join("\n").trim();
12045
- return { frontmatter, body };
12046
- }
12047
- };
12048
-
12049
- // src/skills/SkillsRegistry.ts
12050
- var SIMILARITY_THRESHOLD2 = 0.3;
12051
- var VENDOR_SOURCES = ["codex-user", "claude-user", "codex-project", "claude-project"];
12052
- var SkillsRegistry = class {
12053
- constructor(userSkillsDir, defaultSource = "autohand-user") {
12054
- this.userSkillsDir = userSkillsDir;
12055
- this.skills = /* @__PURE__ */ new Map();
12056
- this.parser = new SkillParser();
12057
- this.workspaceRoot = null;
12058
- this.telemetryManager = null;
12059
- this.communityClient = null;
12060
- this.defaultSource = defaultSource;
12061
- }
12062
- /**
12063
- * Set the telemetry manager for tracking skill events
12064
- */
12065
- setTelemetryManager(telemetryManager) {
12066
- this.telemetryManager = telemetryManager;
12067
- }
12068
- /**
12069
- * Set the community skills client for backup/sync operations
12070
- */
12071
- setCommunityClient(client) {
12072
- this.communityClient = client;
12073
- }
12074
- /**
12075
- * Get the community skills client
12076
- */
12077
- getCommunityClient() {
12078
- return this.communityClient;
12079
- }
12080
- /**
12081
- * Check if any vendor skills (codex/claude) are loaded
12082
- */
12083
- hasVendorSkills() {
12084
- for (const skill of this.skills.values()) {
12085
- if (VENDOR_SOURCES.includes(skill.source)) {
12086
- return true;
12087
- }
12088
- }
12089
- return false;
12090
- }
12091
- /**
12092
- * Get all vendor skills (from codex/claude sources)
12093
- */
12094
- getVendorSkills() {
12095
- return Array.from(this.skills.values()).filter(
12096
- (skill) => VENDOR_SOURCES.includes(skill.source)
12097
- );
12098
- }
12099
- /**
12100
- * Import a community skill package and save to disk
12101
- */
12102
- async importCommunitySkill(pkg, targetDir) {
12103
- if (!pkg.name || !pkg.body) {
12104
- return { success: false, error: "Invalid skill package: missing name or body" };
12105
- }
12106
- const skillDir = path17.join(targetDir, pkg.name);
12107
- const skillPath = path17.join(skillDir, "SKILL.md");
12108
- if (await fs18.pathExists(skillPath)) {
12109
- return { success: false, skipped: true, error: "Skill already exists" };
12110
- }
12111
- try {
12112
- await fs18.ensureDir(skillDir);
12113
- await fs18.writeFile(skillPath, pkg.body, "utf-8");
12114
- const result = await this.parser.parseFile(skillPath, "community");
12115
- if (result.success && result.skill) {
12116
- this.skills.set(result.skill.name, result.skill);
12117
- return { success: true, path: skillPath };
12118
- }
12119
- return { success: false, error: "Failed to parse imported skill" };
12120
- } catch (err) {
12121
- const error = err instanceof Error ? err.message : "Unknown error";
12122
- return { success: false, error };
12123
- }
12124
- }
12125
- /**
12126
- * Initialize the registry by loading skills from the user directory
12127
- */
12128
- async initialize() {
12129
- await this.loadFromDirectory(this.userSkillsDir, this.defaultSource, true);
12130
- }
12131
- /**
12132
- * Set the workspace root and load project-level skills
12133
- */
12134
- async setWorkspace(workspaceRoot) {
12135
- this.workspaceRoot = workspaceRoot;
12136
- const claudeProjectSkillsDir = path17.join(workspaceRoot, ".claude", "skills");
12137
- await this.loadFromDirectory(claudeProjectSkillsDir, "claude-project", false);
12138
- const autohandProjectSkillsDir = path17.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
12139
- await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
12140
- }
12141
- /**
12142
- * Add an additional skill location to search
12143
- */
12144
- async addLocation(directory, source, recursive = true) {
12145
- await this.loadFromDirectory(directory, source, recursive);
12146
- }
12147
- /**
12148
- * Add a skill location with auto-copy to autohand location
12149
- * Copies discovered skills to the target autohand directory, preserving structure
12150
- */
12151
- async addLocationWithAutoCopy(sourceDirectory, source, targetAutohandDir, recursive = true) {
12152
- const result = {
12153
- copiedCount: 0,
12154
- skippedCount: 0,
12155
- errorCount: 0,
12156
- copiedSkills: [],
12157
- skippedSkills: []
12158
- };
12159
- if (!await fs18.pathExists(sourceDirectory)) {
12160
- return result;
12161
- }
12162
- const skillFiles = await this.findSkillFiles(sourceDirectory, recursive);
12163
- for (const skillPath of skillFiles) {
12164
- const parseResult = await this.parser.parseFile(skillPath, source);
12165
- if (!parseResult.success || !parseResult.skill) {
12166
- continue;
12167
- }
12168
- const skill = parseResult.skill;
12169
- const skillName = skill.name;
12170
- const skillDir = path17.dirname(skillPath);
12171
- const relativePath = path17.relative(sourceDirectory, skillDir);
12172
- const targetSkillDir = path17.join(targetAutohandDir, relativePath);
12173
- const targetSkillPath = path17.join(targetSkillDir, "SKILL.md");
12174
- if (await fs18.pathExists(targetSkillPath)) {
12175
- result.skippedCount++;
12176
- result.skippedSkills.push(skillName);
12177
- } else {
12178
- try {
12179
- await fs18.ensureDir(targetSkillDir);
12180
- await fs18.copyFile(skillPath, targetSkillPath);
12181
- result.copiedCount++;
12182
- result.copiedSkills.push(skillName);
12183
- } catch {
12184
- result.errorCount++;
12185
- }
12186
- }
12187
- this.skills.set(skill.name, skill);
12188
- }
12189
- return result;
12190
- }
12191
- /**
12192
- * Set workspace with auto-copy from claude-project to autohand-project
12193
- */
12194
- async setWorkspaceWithAutoCopy(workspaceRoot) {
12195
- this.workspaceRoot = workspaceRoot;
12196
- const claudeProjectSkillsDir = path17.join(workspaceRoot, ".claude", "skills");
12197
- const autohandProjectSkillsDir = path17.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
12198
- await this.addLocationWithAutoCopy(
12199
- claudeProjectSkillsDir,
12200
- "claude-project",
12201
- autohandProjectSkillsDir,
12202
- false
12203
- // Claude project is not recursive
12204
- );
12205
- await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
12206
- }
12207
- /**
12208
- * Add a skill location with auto-copy AND backup to community API
12209
- * Enhanced version that also backs up vendor skills to the API
12210
- */
12211
- async addLocationWithAutoCopyAndBackup(sourceDirectory, source, targetAutohandDir, recursive = true) {
12212
- const result = await this.addLocationWithAutoCopy(
12213
- sourceDirectory,
12214
- source,
12215
- targetAutohandDir,
12216
- recursive
12217
- );
12218
- if (this.communityClient && result.copiedCount > 0) {
12219
- const backupPayloads = [];
12220
- for (const skillName of result.copiedSkills) {
12221
- const skill = this.skills.get(skillName);
12222
- if (skill) {
12223
- backupPayloads.push({
12224
- name: skill.name,
12225
- description: skill.description,
12226
- body: skill.body,
12227
- allowedTools: skill["allowed-tools"],
12228
- originalSource: source,
12229
- originalPath: skill.path
12230
- });
12231
- }
12232
- }
12233
- if (backupPayloads.length > 0) {
12234
- await this.communityClient.backupAllSkills(backupPayloads);
12235
- }
12236
- }
12237
- return result;
12238
- }
12239
- /**
12240
- * Backup all vendor skills to community API
12241
- */
12242
- async backupAllVendorSkills() {
12243
- if (!this.communityClient) {
12244
- return { backed: 0, failed: 0 };
12245
- }
12246
- const vendorSkills = this.getVendorSkills();
12247
- if (vendorSkills.length === 0) {
12248
- return { backed: 0, failed: 0 };
12249
- }
12250
- const payloads = vendorSkills.map((skill) => ({
12251
- name: skill.name,
12252
- description: skill.description,
12253
- body: skill.body,
12254
- allowedTools: skill["allowed-tools"],
12255
- originalSource: skill.source,
12256
- originalPath: skill.path
12257
- }));
12258
- return this.communityClient.backupAllSkills(payloads);
12259
- }
12260
- /**
12261
- * Load skills from a directory
12262
- */
12263
- async loadFromDirectory(directory, source, recursive) {
12264
- if (!await fs18.pathExists(directory)) {
12265
- return;
12266
- }
12267
- const skillFiles = await this.findSkillFiles(directory, recursive);
12268
- for (const skillPath of skillFiles) {
12269
- const result = await this.parser.parseFile(skillPath, source);
12270
- if (result.success && result.skill) {
12271
- this.skills.set(result.skill.name, result.skill);
12272
- }
12273
- }
12274
- }
12275
- /**
12276
- * Find all SKILL.md files in a directory
12277
- */
12278
- async findSkillFiles(directory, recursive) {
12279
- const results = [];
12280
- if (!await fs18.pathExists(directory)) {
12281
- return results;
12282
- }
12283
- const entries = await fs18.readdir(directory, { withFileTypes: true });
12284
- for (const entry of entries) {
12285
- const fullPath = path17.join(directory, entry.name);
12286
- if (entry.isDirectory()) {
12287
- const skillPath = path17.join(fullPath, "SKILL.md");
12288
- if (await fs18.pathExists(skillPath)) {
12289
- results.push(skillPath);
12290
- }
12291
- if (recursive) {
12292
- const nested = await this.findSkillFiles(fullPath, true);
12293
- results.push(...nested);
12294
- }
12295
- }
12296
- }
12297
- return results;
12298
- }
12299
- /**
12300
- * List all available skills
12301
- */
12302
- listSkills() {
12303
- return Array.from(this.skills.values());
12304
- }
12305
- /**
12306
- * Get a specific skill by name
12307
- */
12308
- getSkill(name) {
12309
- return this.skills.get(name) ?? null;
12310
- }
12311
- /**
12312
- * Activate a skill by name
12313
- */
12314
- activateSkill(name) {
12315
- const skill = this.skills.get(name);
12316
- if (!skill) {
12317
- return false;
12318
- }
12319
- skill.isActive = true;
12320
- return true;
12321
- }
12322
- /**
12323
- * Deactivate a skill by name
12324
- */
12325
- deactivateSkill(name) {
12326
- const skill = this.skills.get(name);
12327
- if (!skill) {
12328
- return false;
12329
- }
12330
- skill.isActive = false;
12331
- return true;
12332
- }
12333
- /**
12334
- * Deactivate all active skills
12335
- */
12336
- deactivateAll() {
12337
- for (const skill of this.skills.values()) {
12338
- skill.isActive = false;
12339
- }
12340
- }
12341
- /**
12342
- * Get all currently active skills
12343
- */
12344
- getActiveSkills() {
12345
- return Array.from(this.skills.values()).filter((s) => s.isActive);
12346
- }
12347
- /**
12348
- * Find skills similar to a given query using Jaccard similarity
12349
- */
12350
- findSimilar(query, threshold = SIMILARITY_THRESHOLD2) {
12351
- const queryTokens = this.tokenize(query);
12352
- const matches = [];
12353
- for (const skill of this.skills.values()) {
12354
- const skillText = `${skill.name} ${skill.description}`;
12355
- const skillTokens = this.tokenize(skillText);
12356
- const score = this.calculateJaccard(queryTokens, skillTokens);
12357
- if (score >= threshold) {
12358
- matches.push({ skill, score });
12359
- }
12360
- }
12361
- return matches.sort((a, b) => b.score - a.score);
12362
- }
12363
- /**
12364
- * Calculate Jaccard similarity between two token sets
12365
- */
12366
- calculateJaccard(a, b) {
12367
- if (a.size === 0 || b.size === 0) {
12368
- return 0;
12369
- }
12370
- const intersection = new Set([...a].filter((x) => b.has(x)));
12371
- const union = /* @__PURE__ */ new Set([...a, ...b]);
12372
- return intersection.size / union.size;
12373
- }
12374
- /**
12375
- * Tokenize text into a set of lowercase words
12376
- */
12377
- tokenize(text) {
12378
- return new Set(
12379
- text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
12380
- );
12381
- }
12382
- /**
12383
- * Get the number of loaded skills
12384
- */
12385
- get size() {
12386
- return this.skills.size;
12387
- }
12388
- /**
12389
- * Check if a skill exists by name
12390
- */
12391
- hasSkill(name) {
12392
- return this.skills.has(name);
12393
- }
12394
- /**
12395
- * Save a new skill to the user skills directory
12396
- */
12397
- async saveSkill(name, content) {
12398
- const skillDir = path17.join(this.userSkillsDir, name);
12399
- const skillPath = path17.join(skillDir, "SKILL.md");
12400
- try {
12401
- await fs18.ensureDir(skillDir);
12402
- await fs18.writeFile(skillPath, content, "utf-8");
12403
- const result = await this.parser.parseFile(skillPath, "autohand-user");
12404
- if (result.success && result.skill) {
12405
- this.skills.set(result.skill.name, result.skill);
12406
- return true;
12407
- }
12408
- return false;
12409
- } catch {
12410
- return false;
12411
- }
12412
- }
12413
- };
12414
-
12415
12342
  // src/skills/CommunitySkillsClient.ts
12416
- import fs19 from "fs-extra";
12417
- import path18 from "path";
12343
+ import fs17 from "fs-extra";
12344
+ import path17 from "path";
12418
12345
  var CommunitySkillsClient = class {
12419
12346
  constructor(config) {
12420
12347
  this.queue = [];
@@ -12422,7 +12349,7 @@ var CommunitySkillsClient = class {
12422
12349
  this.apiBaseUrl = config.apiBaseUrl.replace(/\/$/, "");
12423
12350
  this.enabled = config.enabled;
12424
12351
  this.deviceId = config.deviceId || this.loadDeviceId();
12425
- this.queueDir = config.queueDir || path18.join(AUTOHAND_HOME, "community-skills");
12352
+ this.queueDir = config.queueDir || path17.join(AUTOHAND_HOME, "community-skills");
12426
12353
  this.timeout = config.timeout || 1e4;
12427
12354
  this.maxRetries = config.maxRetries || 3;
12428
12355
  }
@@ -12430,10 +12357,10 @@ var CommunitySkillsClient = class {
12430
12357
  * Load device ID from filesystem
12431
12358
  */
12432
12359
  loadDeviceId() {
12433
- const deviceIdPath = path18.join(AUTOHAND_HOME, "device-id");
12360
+ const deviceIdPath = path17.join(AUTOHAND_HOME, "device-id");
12434
12361
  try {
12435
- if (fs19.existsSync(deviceIdPath)) {
12436
- return fs19.readFileSync(deviceIdPath, "utf-8").trim();
12362
+ if (fs17.existsSync(deviceIdPath)) {
12363
+ return fs17.readFileSync(deviceIdPath, "utf-8").trim();
12437
12364
  }
12438
12365
  } catch {
12439
12366
  }
@@ -12445,10 +12372,10 @@ var CommunitySkillsClient = class {
12445
12372
  loadQueue() {
12446
12373
  if (this.queueLoaded) return;
12447
12374
  this.queueLoaded = true;
12448
- const queuePath = path18.join(this.queueDir, "queue.json");
12375
+ const queuePath = path17.join(this.queueDir, "queue.json");
12449
12376
  try {
12450
- if (fs19.existsSync(queuePath)) {
12451
- this.queue = fs19.readJsonSync(queuePath);
12377
+ if (fs17.existsSync(queuePath)) {
12378
+ this.queue = fs17.readJsonSync(queuePath);
12452
12379
  }
12453
12380
  } catch {
12454
12381
  this.queue = [];
@@ -12459,8 +12386,8 @@ var CommunitySkillsClient = class {
12459
12386
  */
12460
12387
  saveQueue() {
12461
12388
  try {
12462
- fs19.ensureDirSync(this.queueDir);
12463
- fs19.writeJsonSync(path18.join(this.queueDir, "queue.json"), this.queue);
12389
+ fs17.ensureDirSync(this.queueDir);
12390
+ fs17.writeJsonSync(path17.join(this.queueDir, "queue.json"), this.queue);
12464
12391
  } catch {
12465
12392
  }
12466
12393
  }
@@ -13137,7 +13064,7 @@ function createPersistentInput(options) {
13137
13064
  }
13138
13065
 
13139
13066
  // src/ui/toolOutput.ts
13140
- import * as path19 from "path";
13067
+ import * as path18 from "path";
13141
13068
  var FILE_SUMMARY_TOOLS = /* @__PURE__ */ new Set([
13142
13069
  "read_file",
13143
13070
  "write_file",
@@ -13177,9 +13104,9 @@ ${truncatedContent}` : outputLines > 0 ? `
13177
13104
  };
13178
13105
  }
13179
13106
  if (FILE_SUMMARY_TOOLS.has(tool) && filePath) {
13180
- const fileName = path19.basename(filePath);
13181
- const dirName = path19.dirname(filePath);
13182
- const displayPath = dirName === "." ? fileName : `${path19.basename(dirName)}/${fileName}`;
13107
+ const fileName = path18.basename(filePath);
13108
+ const dirName = path18.dirname(filePath);
13109
+ const displayPath = dirName === "." ? fileName : `${path18.basename(dirName)}/${fileName}`;
13183
13110
  const lines = countLines(content);
13184
13111
  const size = formatFileSize(Buffer.byteLength(content, "utf8"));
13185
13112
  return {
@@ -14081,7 +14008,7 @@ var IntentDetector = class {
14081
14008
  };
14082
14009
 
14083
14010
  // src/core/EnvironmentBootstrap.ts
14084
- import fs20 from "fs-extra";
14011
+ import fs18 from "fs-extra";
14085
14012
  import { join as join2 } from "path";
14086
14013
  import { exec } from "child_process";
14087
14014
  import { promisify } from "util";
@@ -14177,14 +14104,14 @@ var EnvironmentBootstrap = class {
14177
14104
  */
14178
14105
  async detectPackageManager(root) {
14179
14106
  for (const { file, pm } of this.lockfileChecks) {
14180
- if (await fs20.pathExists(join2(root, file))) {
14107
+ if (await fs18.pathExists(join2(root, file))) {
14181
14108
  return pm;
14182
14109
  }
14183
14110
  }
14184
14111
  const pkgPath = join2(root, "package.json");
14185
- if (await fs20.pathExists(pkgPath)) {
14112
+ if (await fs18.pathExists(pkgPath)) {
14186
14113
  try {
14187
- const pkg = await fs20.readJson(pkgPath);
14114
+ const pkg = await fs18.readJson(pkgPath);
14188
14115
  if (pkg.packageManager) {
14189
14116
  const match = pkg.packageManager.match(/^(bun|pnpm|yarn|npm)@/);
14190
14117
  if (match) {
@@ -14215,7 +14142,7 @@ var EnvironmentBootstrap = class {
14215
14142
  const step = { name: "Git Sync", status: "running" };
14216
14143
  const start = Date.now();
14217
14144
  try {
14218
- const isGit = await fs20.pathExists(join2(root, ".git"));
14145
+ const isGit = await fs18.pathExists(join2(root, ".git"));
14219
14146
  if (!isGit) {
14220
14147
  return {
14221
14148
  ...step,
@@ -14296,18 +14223,18 @@ var EnvironmentBootstrap = class {
14296
14223
  const details = [];
14297
14224
  try {
14298
14225
  const nvmrcPath = join2(root, ".nvmrc");
14299
- const nvmrcExists = await fs20.pathExists(nvmrcPath);
14226
+ const nvmrcExists = await fs18.pathExists(nvmrcPath);
14300
14227
  const nodeVersionPath = join2(root, ".node-version");
14301
- const nodeVersionExists = await fs20.pathExists(nodeVersionPath);
14228
+ const nodeVersionExists = await fs18.pathExists(nodeVersionPath);
14302
14229
  if (nvmrcExists || nodeVersionExists) {
14303
14230
  details.push("Node version file found");
14304
14231
  }
14305
14232
  const toolVersionsPath = join2(root, ".tool-versions");
14306
- if (await fs20.pathExists(toolVersionsPath)) {
14233
+ if (await fs18.pathExists(toolVersionsPath)) {
14307
14234
  details.push(".tool-versions found");
14308
14235
  }
14309
14236
  const rustToolchainPath = join2(root, "rust-toolchain.toml");
14310
- if (await fs20.pathExists(rustToolchainPath)) {
14237
+ if (await fs18.pathExists(rustToolchainPath)) {
14311
14238
  details.push("Rust toolchain found");
14312
14239
  }
14313
14240
  step.status = "success";
@@ -14384,7 +14311,7 @@ var EnvironmentBootstrap = class {
14384
14311
  };
14385
14312
 
14386
14313
  // src/core/CodeQualityPipeline.ts
14387
- import fs21 from "fs-extra";
14314
+ import fs19 from "fs-extra";
14388
14315
  import { join as join3 } from "path";
14389
14316
  import { exec as exec2 } from "child_process";
14390
14317
  import { promisify as promisify2 } from "util";
@@ -14454,11 +14381,11 @@ var CodeQualityPipeline = class {
14454
14381
  */
14455
14382
  async detectScripts(root) {
14456
14383
  const pkgPath = join3(root, "package.json");
14457
- if (!await fs21.pathExists(pkgPath)) {
14384
+ if (!await fs19.pathExists(pkgPath)) {
14458
14385
  return {};
14459
14386
  }
14460
14387
  try {
14461
- const pkg = await fs21.readJson(pkgPath);
14388
+ const pkg = await fs19.readJson(pkgPath);
14462
14389
  const scripts = pkg.scripts || {};
14463
14390
  return {
14464
14391
  lint: this.findScript(scripts, this.scriptPatterns.lint),
@@ -14644,6 +14571,7 @@ var AutohandAgent = class {
14644
14571
  this.lastIntent = "diagnostic";
14645
14572
  this.filesModifiedThisSession = false;
14646
14573
  this.searchQueries = [];
14574
+ this.sessionRetryCount = 0;
14647
14575
  const initialProvider = runtime.config.provider ?? "openrouter";
14648
14576
  const providerSettings = getProviderConfig(runtime.config, initialProvider);
14649
14577
  const model = runtime.options.model ?? providerSettings?.model ?? "unconfigured";
@@ -14670,7 +14598,7 @@ var AutohandAgent = class {
14670
14598
  runtime,
14671
14599
  files,
14672
14600
  resolveWorkspacePath: (relativePath) => this.resolveWorkspacePath(relativePath),
14673
- confirmDangerousAction: (message) => this.confirmDangerousAction(message),
14601
+ confirmDangerousAction: (message, context) => this.confirmDangerousAction(message, context),
14674
14602
  onExploration: (entry) => this.recordExploration(entry),
14675
14603
  onToolOutput: (chunk) => this.handleToolOutput(chunk),
14676
14604
  toolsRegistry: this.toolsRegistry,
@@ -14762,7 +14690,7 @@ var AutohandAgent = class {
14762
14690
  throw error;
14763
14691
  }
14764
14692
  },
14765
- confirmApproval: (message) => this.confirmDangerousAction(message),
14693
+ confirmApproval: (message, context) => this.confirmDangerousAction(message, context),
14766
14694
  definitions: [...DEFAULT_TOOL_DEFINITIONS, ...delegationTools],
14767
14695
  clientContext
14768
14696
  });
@@ -15111,7 +15039,16 @@ var AutohandAgent = class {
15111
15039
  return false;
15112
15040
  };
15113
15041
  if (normalized.startsWith("/") && !looksLikeFilePath(normalized)) {
15114
- const handled = await this.slashHandler.handle(normalized);
15042
+ const parts = normalized.split(/\s+/);
15043
+ let command = parts[0];
15044
+ let args = parts.slice(1);
15045
+ const twoWordCommands = ["/skills new", "/agents new"];
15046
+ const potentialTwoWord = `${parts[0]} ${parts[1] || ""}`.trim();
15047
+ if (twoWordCommands.includes(potentialTwoWord)) {
15048
+ command = potentialTwoWord;
15049
+ args = parts.slice(2);
15050
+ }
15051
+ const handled = await this.slashHandler.handle(command, args);
15115
15052
  if (handled === null) {
15116
15053
  return null;
15117
15054
  }
@@ -15171,7 +15108,7 @@ var AutohandAgent = class {
15171
15108
  }
15172
15109
  }
15173
15110
  async listWorkspaceFiles() {
15174
- const entries = await fs22.readdir(this.runtime.workspaceRoot);
15111
+ const entries = await fs20.readdir(this.runtime.workspaceRoot);
15175
15112
  const sorted = entries.sort((a, b) => a.localeCompare(b));
15176
15113
  console.log("\n" + chalk13.cyan("Workspace files:"));
15177
15114
  console.log(sorted.map((entry) => ` - ${entry}`).join("\n"));
@@ -15198,18 +15135,18 @@ var AutohandAgent = class {
15198
15135
  async walkWorkspace(current, acc) {
15199
15136
  let entries;
15200
15137
  try {
15201
- entries = await fs22.readdir(current);
15138
+ entries = await fs20.readdir(current);
15202
15139
  } catch {
15203
15140
  return;
15204
15141
  }
15205
15142
  for (const entry of entries) {
15206
- const full = path20.join(current, entry);
15207
- const rel = path20.relative(this.runtime.workspaceRoot, full);
15143
+ const full = path19.join(current, entry);
15144
+ const rel = path19.relative(this.runtime.workspaceRoot, full);
15208
15145
  if (rel === "" || this.shouldSkipPath(rel) || this.ignoreFilter.isIgnored(rel)) {
15209
15146
  continue;
15210
15147
  }
15211
15148
  try {
15212
- const stats = await fs22.stat(full);
15149
+ const stats = await fs20.stat(full);
15213
15150
  if (stats.isDirectory()) {
15214
15151
  await this.walkWorkspace(full, acc);
15215
15152
  } else if (stats.isFile()) {
@@ -15579,8 +15516,8 @@ ${selectedProvider} is not configured yet. Let's set it up!
15579
15516
  );
15580
15517
  }
15581
15518
  async createAgentsFile() {
15582
- const target = path20.join(this.runtime.workspaceRoot, "AGENTS.md");
15583
- if (await fs22.pathExists(target)) {
15519
+ const target = path19.join(this.runtime.workspaceRoot, "AGENTS.md");
15520
+ if (await fs20.pathExists(target)) {
15584
15521
  console.log(chalk13.gray("AGENTS.md already exists in this workspace."));
15585
15522
  return;
15586
15523
  }
@@ -15588,7 +15525,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
15588
15525
 
15589
15526
  Describe how Autohand should work in this repo. Include framework commands, testing requirements, and any constraints.
15590
15527
  `;
15591
- await fs22.writeFile(target, template, "utf8");
15528
+ await fs20.writeFile(target, template, "utf8");
15592
15529
  console.log(chalk13.green("Created AGENTS.md template. Customize it to guide the agent."));
15593
15530
  }
15594
15531
  /**
@@ -15700,6 +15637,33 @@ No provider is configured yet. Let's set one up!
15700
15637
  await this.promptModelSelection();
15701
15638
  return this.runInstruction(instruction);
15702
15639
  }
15640
+ const err = error instanceof Error ? error : new Error(String(error));
15641
+ const maxRetries = this.runtime.config.agent?.sessionRetryLimit ?? 3;
15642
+ const baseDelay = this.runtime.config.agent?.sessionRetryDelay ?? 1e3;
15643
+ if (this.isRetryableSessionError(err) && this.sessionRetryCount < maxRetries) {
15644
+ this.sessionRetryCount++;
15645
+ await this.submitSessionFailureBugReport(err, this.sessionRetryCount, maxRetries);
15646
+ console.log(chalk13.yellow(`
15647
+ \u26A0 Session encountered an error: ${err.message}`));
15648
+ console.log(chalk13.cyan(` Attempting recovery (${this.sessionRetryCount}/${maxRetries})...`));
15649
+ const delay = baseDelay * Math.pow(1.5, this.sessionRetryCount - 1);
15650
+ await this.sleep(delay);
15651
+ this.injectContinuationMessage(err, this.sessionRetryCount);
15652
+ try {
15653
+ this.setUIStatus("Recovering session...");
15654
+ await this.runReactLoop(abortController);
15655
+ this.sessionRetryCount = 0;
15656
+ success = true;
15657
+ return success;
15658
+ } catch (retryError) {
15659
+ if (this.sessionRetryCount >= maxRetries) {
15660
+ this.sessionRetryCount = 0;
15661
+ } else {
15662
+ throw retryError;
15663
+ }
15664
+ }
15665
+ }
15666
+ this.sessionRetryCount = 0;
15703
15667
  this.stopUI(true, "Session failed");
15704
15668
  const errorMessage = error instanceof Error ? error.message : String(error);
15705
15669
  this.emitOutput({ type: "error", content: errorMessage });
@@ -16298,7 +16262,8 @@ ${summary}`
16298
16262
  const needApproval = Boolean(args.need_user_approve);
16299
16263
  if (needApproval) {
16300
16264
  const approved = await this.confirmDangerousAction(
16301
- `Crop ${direction} ${Math.floor(amount)} message(s) from the conversation?`
16265
+ `Crop ${direction} ${Math.floor(amount)} message(s) from the conversation?`,
16266
+ { tool: "smart_context_cropper" }
16302
16267
  );
16303
16268
  if (!approved) {
16304
16269
  return "smart_context_cropper canceled by user.";
@@ -16672,16 +16637,16 @@ ${toolSignatures}
16672
16637
  return normalizedSeed || null;
16673
16638
  }
16674
16639
  async fileExists(relativePath) {
16675
- const fullPath = path20.resolve(this.runtime.workspaceRoot, relativePath);
16640
+ const fullPath = path19.resolve(this.runtime.workspaceRoot, relativePath);
16676
16641
  if (!fullPath.startsWith(this.runtime.workspaceRoot)) {
16677
16642
  return false;
16678
16643
  }
16679
- const exists = await fs22.pathExists(fullPath);
16644
+ const exists = await fs20.pathExists(fullPath);
16680
16645
  if (!exists) {
16681
16646
  return false;
16682
16647
  }
16683
16648
  try {
16684
- const stats = await fs22.stat(fullPath);
16649
+ const stats = await fs20.stat(fullPath);
16685
16650
  return stats.isFile();
16686
16651
  } catch {
16687
16652
  return false;
@@ -16967,7 +16932,7 @@ ${ctx.contents}`).join("\n\n");
16967
16932
  encoding: "utf8"
16968
16933
  });
16969
16934
  const gitStatus2 = git.status === 0 ? git.stdout.trim() : void 0;
16970
- const entries = await fs22.readdir(this.runtime.workspaceRoot);
16935
+ const entries = await fs20.readdir(this.runtime.workspaceRoot);
16971
16936
  const recentFiles = entries.filter((entry) => !this.ignoreFilter.isIgnored(entry)).slice(0, 20);
16972
16937
  return {
16973
16938
  workspaceRoot: this.runtime.workspaceRoot,
@@ -16978,17 +16943,17 @@ ${ctx.contents}`).join("\n\n");
16978
16943
  async loadInstructionFiles() {
16979
16944
  const instructions = [];
16980
16945
  const workspace = this.runtime.workspaceRoot;
16981
- const agentsPath = path20.join(workspace, "AGENTS.md");
16982
- if (await fs22.pathExists(agentsPath)) {
16983
- const content = await fs22.readFile(agentsPath, "utf-8");
16946
+ const agentsPath = path19.join(workspace, "AGENTS.md");
16947
+ if (await fs20.pathExists(agentsPath)) {
16948
+ const content = await fs20.readFile(agentsPath, "utf-8");
16984
16949
  instructions.push(`## Project Instructions (AGENTS.md)
16985
16950
  ${content}`);
16986
16951
  }
16987
16952
  const providerFile = this.activeProvider.includes("anthropic") || this.activeProvider === "openrouter" ? "CLAUDE.md" : this.activeProvider.includes("google") ? "GEMINI.md" : null;
16988
16953
  if (providerFile) {
16989
- const providerPath = path20.join(workspace, providerFile);
16990
- if (await fs22.pathExists(providerPath)) {
16991
- const content = await fs22.readFile(providerPath, "utf-8");
16954
+ const providerPath = path19.join(workspace, providerFile);
16955
+ if (await fs20.pathExists(providerPath)) {
16956
+ const content = await fs20.readFile(providerPath, "utf-8");
16992
16957
  instructions.push(`## Provider Instructions (${providerFile})
16993
16958
  ${content}`);
16994
16959
  }
@@ -17139,9 +17104,100 @@ ${parts.join("\n")}`
17139
17104
  return `${tokens} tokens`;
17140
17105
  }
17141
17106
  /**
17142
- * Display the detected intent mode to the user
17107
+ * Sleep helper for retry delays
17108
+ */
17109
+ sleep(ms) {
17110
+ return new Promise((resolve) => setTimeout(resolve, ms));
17111
+ }
17112
+ /**
17113
+ * Categorize errors to determine retry behavior.
17114
+ * Returns true if the error is retryable.
17115
+ */
17116
+ isRetryableSessionError(error) {
17117
+ const message = error.message.toLowerCase();
17118
+ if (message.includes("authentication") || message.includes("api key") || message.includes("unauthorized") || message.includes("forbidden") || message.includes("access denied")) {
17119
+ return false;
17120
+ }
17121
+ if (message.includes("payment required") || message.includes("billing") || message.includes("quota exceeded")) {
17122
+ return false;
17123
+ }
17124
+ if (message.includes("model not found") || message.includes("model does not exist")) {
17125
+ return false;
17126
+ }
17127
+ if (message.includes("cancelled") || message.includes("canceled") || message.includes("aborted") || message.includes("user force closed")) {
17128
+ return false;
17129
+ }
17130
+ if (message.includes("payload too large") || message.includes("context too long")) {
17131
+ return false;
17132
+ }
17133
+ if (message.includes("network") || message.includes("connection") || message.includes("econnreset") || message.includes("enotfound") || message.includes("etimedout")) {
17134
+ return true;
17135
+ }
17136
+ if (message.includes("internal error") || message.includes("service unavailable") || message.includes("bad gateway") || message.includes("gateway timeout") || message.includes("overloaded")) {
17137
+ return true;
17138
+ }
17139
+ if (message.includes("rate limit") || message.includes("too many requests")) {
17140
+ return true;
17141
+ }
17142
+ if (message.includes("timed out") || message.includes("timeout")) {
17143
+ return true;
17144
+ }
17145
+ if (message.includes("json") || message.includes("parse") || message.includes("unexpected token")) {
17146
+ return true;
17147
+ }
17148
+ return true;
17149
+ }
17150
+ /**
17151
+ * Inject a continuation message into the conversation to help the LLM
17152
+ * recover from a failure and continue the task.
17153
+ */
17154
+ injectContinuationMessage(error, retryAttempt) {
17155
+ const continuationPrompts = [
17156
+ // First retry: gentle continuation
17157
+ `[System Recovery] An error occurred (${error.message}). Please continue from where you left off. Review the conversation context and proceed with the next logical step. If you were in the middle of a tool call, retry it. If you completed tools, provide your response.`,
17158
+ // Second retry: more explicit
17159
+ `[System Recovery - Attempt ${retryAttempt + 1}] The previous operation encountered an error. Please analyze the current state and continue. Focus on completing the user's original request. If needed, you can re-read files or re-execute commands to verify the current state.`,
17160
+ // Third retry: most explicit with safety
17161
+ `[System Recovery - Final Attempt] Multiple errors have occurred. Please provide a status update to the user. If the task cannot be completed, explain what was accomplished and what remains. Do not attempt complex operations - focus on providing a helpful response.`
17162
+ ];
17163
+ const promptIndex = Math.min(retryAttempt, continuationPrompts.length - 1);
17164
+ const continuationMessage = continuationPrompts[promptIndex];
17165
+ this.conversation.addSystemNote(continuationMessage);
17166
+ }
17167
+ /**
17168
+ * Submit a detailed bug report when a session failure occurs.
17169
+ */
17170
+ async submitSessionFailureBugReport(error, retryAttempt, maxRetries) {
17171
+ try {
17172
+ const history = this.conversation.history();
17173
+ const recentToolCalls = history.filter((m) => m.role === "assistant" && m.tool_calls).slice(-3).flatMap((m) => m.tool_calls?.map((tc) => tc.function?.name) || []).filter(Boolean);
17174
+ const model = this.runtime.options.model ?? getProviderConfig(this.runtime.config, this.activeProvider)?.model;
17175
+ await this.telemetryManager.trackSessionFailureBug({
17176
+ error,
17177
+ retryAttempt,
17178
+ maxRetries,
17179
+ conversationLength: history.length,
17180
+ lastToolCalls: recentToolCalls,
17181
+ iterationCount: this.__currentIteration ?? 0,
17182
+ contextUsage: this.contextPercentLeft,
17183
+ model,
17184
+ provider: this.activeProvider
17185
+ });
17186
+ await this.errorLogger.log(error, {
17187
+ context: `Session failure (retry ${retryAttempt + 1}/${maxRetries})`,
17188
+ workspace: this.runtime.workspaceRoot
17189
+ });
17190
+ } catch (reportError) {
17191
+ console.error(chalk13.gray(`[Debug] Failed to submit bug report: ${reportError.message}`));
17192
+ }
17193
+ }
17194
+ /**
17195
+ * Display the detected intent mode to the user (only in debug mode)
17143
17196
  */
17144
17197
  displayIntentMode(result) {
17198
+ if (process.env.AUTOHAND_DEBUG !== "1") {
17199
+ return;
17200
+ }
17145
17201
  if (result.intent === "diagnostic") {
17146
17202
  console.log(chalk13.blue("[DIAG] Mode: Diagnostic (read-only analysis)"));
17147
17203
  if (result.keywords.length > 0) {
@@ -17161,18 +17217,23 @@ ${parts.join("\n")}`
17161
17217
  * Run environment bootstrap before implementation
17162
17218
  */
17163
17219
  async runEnvironmentBootstrap() {
17164
- console.log(chalk13.cyan("[BOOTSTRAP] Running environment setup..."));
17220
+ const isDebug = process.env.AUTOHAND_DEBUG === "1";
17221
+ if (isDebug) {
17222
+ console.log(chalk13.cyan("[BOOTSTRAP] Running environment setup..."));
17223
+ }
17165
17224
  const result = await this.environmentBootstrap.run(this.runtime.workspaceRoot);
17166
17225
  for (const step of result.steps) {
17167
17226
  const status = step.status === "success" ? chalk13.green("[OK]") : step.status === "failed" ? chalk13.red("[FAIL]") : step.status === "skipped" ? chalk13.gray("[SKIP]") : chalk13.gray("[...]");
17168
17227
  const duration = step.duration ? chalk13.gray(`(${(step.duration / 1e3).toFixed(1)}s)`) : "";
17169
17228
  const detail = step.detail ? chalk13.gray(` ${step.detail}`) : "";
17170
- console.log(` ${status} ${step.name.padEnd(14)} ${duration}${detail}`);
17229
+ if (step.status === "failed" || isDebug) {
17230
+ console.log(` ${status} ${step.name.padEnd(14)} ${duration}${detail}`);
17231
+ }
17171
17232
  if (step.error) {
17172
17233
  console.log(chalk13.red(` Error: ${step.error}`));
17173
17234
  }
17174
17235
  }
17175
- if (result.success) {
17236
+ if (result.success && isDebug) {
17176
17237
  console.log(chalk13.green(`
17177
17238
  [READY] Environment ready (${(result.duration / 1e3).toFixed(1)}s)
17178
17239
  `));
@@ -17218,6 +17279,45 @@ ${parts.join("\n")}`
17218
17279
  getImageManager() {
17219
17280
  return this.imageManager;
17220
17281
  }
17282
+ /**
17283
+ * Handle a slash command (e.g., /skills, /skills install, /model)
17284
+ * Returns the command output or null if the command doesn't exist
17285
+ */
17286
+ async handleSlashCommand(command, args = []) {
17287
+ return this.slashHandler.handle(command, args);
17288
+ }
17289
+ /**
17290
+ * Check if a string is a slash command
17291
+ */
17292
+ isSlashCommand(input) {
17293
+ return input.trim().startsWith("/");
17294
+ }
17295
+ /**
17296
+ * Check if a slash command is supported (exists in the command map)
17297
+ */
17298
+ isSlashCommandSupported(command) {
17299
+ return this.slashHandler.isCommandSupported(command);
17300
+ }
17301
+ /**
17302
+ * Parse a slash command string into command and args
17303
+ * e.g., "/skills install myskill" -> { command: "/skills install", args: ["myskill"] }
17304
+ */
17305
+ parseSlashCommand(input) {
17306
+ const trimmed = input.trim();
17307
+ const parts = trimmed.split(/\s+/);
17308
+ const twoWordCommands = ["/skills install", "/skills new", "/agents new"];
17309
+ const potentialTwoWord = parts.slice(0, 2).join(" ");
17310
+ if (twoWordCommands.includes(potentialTwoWord)) {
17311
+ return {
17312
+ command: potentialTwoWord,
17313
+ args: parts.slice(2)
17314
+ };
17315
+ }
17316
+ return {
17317
+ command: parts[0],
17318
+ args: parts.slice(1)
17319
+ };
17320
+ }
17221
17321
  /**
17222
17322
  * Get messages with images included for the LLM API call.
17223
17323
  * Modifies the last user message to include any images from the session.
@@ -17375,10 +17475,13 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
17375
17475
  });
17376
17476
  this.activeProvider = provider;
17377
17477
  }
17378
- async confirmDangerousAction(message) {
17478
+ async confirmDangerousAction(message, context) {
17379
17479
  if (this.runtime.options.yes || this.runtime.config.ui?.autoConfirm) {
17380
17480
  return true;
17381
17481
  }
17482
+ if (this.confirmationCallback) {
17483
+ return this.confirmationCallback(message, context);
17484
+ }
17382
17485
  if (isExternalCallbackEnabled()) {
17383
17486
  return confirm(message);
17384
17487
  }
@@ -17409,7 +17512,7 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
17409
17512
  }
17410
17513
  }
17411
17514
  resolveWorkspacePath(relativePath) {
17412
- const resolved = path20.resolve(this.runtime.workspaceRoot, relativePath);
17515
+ const resolved = path19.resolve(this.runtime.workspaceRoot, relativePath);
17413
17516
  if (!resolved.startsWith(this.runtime.workspaceRoot)) {
17414
17517
  throw new Error(`Path ${relativePath} escapes workspace root.`);
17415
17518
  }
@@ -17426,6 +17529,13 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
17426
17529
  setOutputListener(listener) {
17427
17530
  this.outputListener = listener;
17428
17531
  }
17532
+ /**
17533
+ * Set a callback for confirmation prompts (used by RPC mode)
17534
+ * When set, this callback is used instead of the default enquirer prompt
17535
+ */
17536
+ setConfirmationCallback(callback) {
17537
+ this.confirmationCallback = callback;
17538
+ }
17429
17539
  emitOutput(event) {
17430
17540
  if (this.outputListener) {
17431
17541
  this.outputListener(event);
@@ -17441,15 +17551,16 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
17441
17551
  return {
17442
17552
  model: this.runtime.options.model ?? providerSettings?.model ?? "unconfigured",
17443
17553
  workspace: this.runtime.workspaceRoot,
17444
- contextPercent: this.contextPercentLeft
17554
+ contextPercent: this.contextPercentLeft,
17555
+ tokensUsed: this.totalTokensUsed
17445
17556
  };
17446
17557
  }
17447
17558
  };
17448
17559
 
17449
17560
  // src/skills/autoSkill.ts
17450
- import fs23 from "fs-extra";
17561
+ import fs21 from "fs-extra";
17451
17562
  import os6 from "os";
17452
- import path21 from "path";
17563
+ import path20 from "path";
17453
17564
  import chalk14 from "chalk";
17454
17565
  var AVAILABLE_TOOLS = {
17455
17566
  file: [
@@ -17536,7 +17647,7 @@ var ProjectAnalyzer = class {
17536
17647
  */
17537
17648
  async analyze() {
17538
17649
  const analysis = {
17539
- projectName: path21.basename(this.projectRoot),
17650
+ projectName: path20.basename(this.projectRoot),
17540
17651
  languages: [],
17541
17652
  frameworks: [],
17542
17653
  patterns: [],
@@ -17548,8 +17659,8 @@ var ProjectAnalyzer = class {
17548
17659
  hasCI: false,
17549
17660
  packageManager: null
17550
17661
  };
17551
- analysis.hasGit = await fs23.pathExists(path21.join(this.projectRoot, ".git"));
17552
- analysis.hasCI = await fs23.pathExists(path21.join(this.projectRoot, ".github", "workflows")) || await fs23.pathExists(path21.join(this.projectRoot, ".gitlab-ci.yml")) || await fs23.pathExists(path21.join(this.projectRoot, ".circleci"));
17662
+ analysis.hasGit = await fs21.pathExists(path20.join(this.projectRoot, ".git"));
17663
+ analysis.hasCI = await fs21.pathExists(path20.join(this.projectRoot, ".github", "workflows")) || await fs21.pathExists(path20.join(this.projectRoot, ".gitlab-ci.yml")) || await fs21.pathExists(path20.join(this.projectRoot, ".circleci"));
17553
17664
  await this.analyzePackageJson(analysis);
17554
17665
  await this.analyzePython(analysis);
17555
17666
  await this.analyzeRust(analysis);
@@ -17571,12 +17682,12 @@ var ProjectAnalyzer = class {
17571
17682
  * Analyze package.json for Node.js projects
17572
17683
  */
17573
17684
  async analyzePackageJson(analysis) {
17574
- const packageJsonPath = path21.join(this.projectRoot, "package.json");
17575
- if (!await fs23.pathExists(packageJsonPath)) {
17685
+ const packageJsonPath = path20.join(this.projectRoot, "package.json");
17686
+ if (!await fs21.pathExists(packageJsonPath)) {
17576
17687
  return;
17577
17688
  }
17578
17689
  try {
17579
- const pkg = await fs23.readJson(packageJsonPath);
17690
+ const pkg = await fs21.readJson(packageJsonPath);
17580
17691
  analysis.projectName = pkg.name || analysis.projectName;
17581
17692
  const allDeps = {
17582
17693
  ...pkg.dependencies,
@@ -17584,13 +17695,13 @@ var ProjectAnalyzer = class {
17584
17695
  };
17585
17696
  const depNames = Object.keys(allDeps);
17586
17697
  analysis.dependencies.push(...depNames);
17587
- if (await fs23.pathExists(path21.join(this.projectRoot, "bun.lockb"))) {
17698
+ if (await fs21.pathExists(path20.join(this.projectRoot, "bun.lockb"))) {
17588
17699
  analysis.packageManager = "bun";
17589
- } else if (await fs23.pathExists(path21.join(this.projectRoot, "pnpm-lock.yaml"))) {
17700
+ } else if (await fs21.pathExists(path20.join(this.projectRoot, "pnpm-lock.yaml"))) {
17590
17701
  analysis.packageManager = "pnpm";
17591
- } else if (await fs23.pathExists(path21.join(this.projectRoot, "yarn.lock"))) {
17702
+ } else if (await fs21.pathExists(path20.join(this.projectRoot, "yarn.lock"))) {
17592
17703
  analysis.packageManager = "yarn";
17593
- } else if (await fs23.pathExists(path21.join(this.projectRoot, "package-lock.json"))) {
17704
+ } else if (await fs21.pathExists(path20.join(this.projectRoot, "package-lock.json"))) {
17594
17705
  analysis.packageManager = "npm";
17595
17706
  }
17596
17707
  if (allDeps.typescript) {
@@ -17631,14 +17742,14 @@ var ProjectAnalyzer = class {
17631
17742
  * Analyze Python project files
17632
17743
  */
17633
17744
  async analyzePython(analysis) {
17634
- const requirementsPath = path21.join(this.projectRoot, "requirements.txt");
17635
- const pyprojectPath = path21.join(this.projectRoot, "pyproject.toml");
17745
+ const requirementsPath = path20.join(this.projectRoot, "requirements.txt");
17746
+ const pyprojectPath = path20.join(this.projectRoot, "pyproject.toml");
17636
17747
  let hasPython = false;
17637
- if (await fs23.pathExists(requirementsPath)) {
17748
+ if (await fs21.pathExists(requirementsPath)) {
17638
17749
  hasPython = true;
17639
17750
  analysis.packageManager = "pip";
17640
17751
  try {
17641
- const content = await fs23.readFile(requirementsPath, "utf-8");
17752
+ const content = await fs21.readFile(requirementsPath, "utf-8");
17642
17753
  const lines = content.toLowerCase();
17643
17754
  for (const [framework, deps] of Object.entries(PYTHON_FRAMEWORKS)) {
17644
17755
  if (deps.some((d) => lines.includes(d))) {
@@ -17652,7 +17763,7 @@ var ProjectAnalyzer = class {
17652
17763
  } catch {
17653
17764
  }
17654
17765
  }
17655
- if (await fs23.pathExists(pyprojectPath)) {
17766
+ if (await fs21.pathExists(pyprojectPath)) {
17656
17767
  hasPython = true;
17657
17768
  }
17658
17769
  if (hasPython) {
@@ -17663,8 +17774,8 @@ var ProjectAnalyzer = class {
17663
17774
  * Analyze Rust project
17664
17775
  */
17665
17776
  async analyzeRust(analysis) {
17666
- const cargoPath = path21.join(this.projectRoot, "Cargo.toml");
17667
- if (await fs23.pathExists(cargoPath)) {
17777
+ const cargoPath = path20.join(this.projectRoot, "Cargo.toml");
17778
+ if (await fs21.pathExists(cargoPath)) {
17668
17779
  analysis.languages.push("rust");
17669
17780
  analysis.packageManager = "cargo";
17670
17781
  }
@@ -17673,8 +17784,8 @@ var ProjectAnalyzer = class {
17673
17784
  * Analyze Go project
17674
17785
  */
17675
17786
  async analyzeGo(analysis) {
17676
- const goModPath = path21.join(this.projectRoot, "go.mod");
17677
- if (await fs23.pathExists(goModPath)) {
17787
+ const goModPath = path20.join(this.projectRoot, "go.mod");
17788
+ if (await fs21.pathExists(goModPath)) {
17678
17789
  analysis.languages.push("go");
17679
17790
  analysis.packageManager = "go";
17680
17791
  }
@@ -17683,7 +17794,7 @@ var ProjectAnalyzer = class {
17683
17794
  * Detect additional project patterns
17684
17795
  */
17685
17796
  async detectPatterns(analysis) {
17686
- if (await fs23.pathExists(path21.join(this.projectRoot, "Dockerfile"))) {
17797
+ if (await fs21.pathExists(path20.join(this.projectRoot, "Dockerfile"))) {
17687
17798
  analysis.patterns.push("docker");
17688
17799
  }
17689
17800
  const dbFiles = await this.findFiles("**/migrations/**", 2);
@@ -17702,13 +17813,13 @@ var ProjectAnalyzer = class {
17702
17813
  async function walk(dir, depth) {
17703
17814
  if (depth > maxDepth) return;
17704
17815
  try {
17705
- const entries = await fs23.readdir(dir, { withFileTypes: true });
17816
+ const entries = await fs21.readdir(dir, { withFileTypes: true });
17706
17817
  for (const entry of entries) {
17707
- const fullPath = path21.join(dir, entry.name);
17818
+ const fullPath = path20.join(dir, entry.name);
17708
17819
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
17709
17820
  await walk(fullPath, depth + 1);
17710
17821
  } else if (entry.isFile()) {
17711
- const ext = path21.extname(entry.name);
17822
+ const ext = path20.extname(entry.name);
17712
17823
  if (pattern === "*.js" && ext === ".js") {
17713
17824
  results.push(fullPath);
17714
17825
  } else if (pattern === "*.ts" && ext === ".ts") {
@@ -17853,10 +17964,10 @@ async function generateAutoSkills(analysis, llm) {
17853
17964
  }
17854
17965
  }
17855
17966
  async function saveSkill(skill, skillsDir) {
17856
- const skillDir = path21.join(skillsDir, skill.name);
17857
- const skillPath = path21.join(skillDir, "SKILL.md");
17967
+ const skillDir = path20.join(skillsDir, skill.name);
17968
+ const skillPath = path20.join(skillDir, "SKILL.md");
17858
17969
  try {
17859
- await fs23.ensureDir(skillDir);
17970
+ await fs21.ensureDir(skillDir);
17860
17971
  let frontmatter = `---
17861
17972
  name: ${skill.name}
17862
17973
  description: ${skill.description}`;
@@ -17869,7 +17980,7 @@ allowed-tools: ${skill.allowedTools.join(" ")}`;
17869
17980
 
17870
17981
  `;
17871
17982
  const content = frontmatter + skill.body + "\n";
17872
- await fs23.writeFile(skillPath, content, "utf-8");
17983
+ await fs21.writeFile(skillPath, content, "utf-8");
17873
17984
  return true;
17874
17985
  } catch {
17875
17986
  return false;
@@ -17905,7 +18016,7 @@ async function runAutoSkillGeneration(workspaceRoot, llm) {
17905
18016
  result.error = "No skills generated";
17906
18017
  return result;
17907
18018
  }
17908
- const skillsDir = path21.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
18019
+ const skillsDir = path20.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
17909
18020
  for (const skill of skills) {
17910
18021
  const saved = await saveSkill(skill, skillsDir);
17911
18022
  if (saved) {
@@ -17948,7 +18059,8 @@ var RPC_METHODS = {
17948
18059
  RESET: "autohand.reset",
17949
18060
  GET_STATE: "autohand.getState",
17950
18061
  GET_MESSAGES: "autohand.getMessages",
17951
- PERMISSION_RESPONSE: "autohand.permissionResponse"
18062
+ PERMISSION_RESPONSE: "autohand.permissionResponse",
18063
+ PERMISSION_ACKNOWLEDGED: "autohand.permissionAcknowledged"
17952
18064
  };
17953
18065
  var RPC_NOTIFICATIONS = {
17954
18066
  AGENT_START: "autohand.agentStart",
@@ -18212,6 +18324,7 @@ var RPCAdapter = class {
18212
18324
  this.imageManager = null;
18213
18325
  this.sessionId = null;
18214
18326
  this.currentTurnId = null;
18327
+ this.turnStartTime = null;
18215
18328
  this.currentMessageId = null;
18216
18329
  this.currentMessageContent = "";
18217
18330
  this.pendingPermissions = /* @__PURE__ */ new Map();
@@ -18286,16 +18399,37 @@ var RPCAdapter = class {
18286
18399
  this.status = "processing";
18287
18400
  this.abortController = new AbortController();
18288
18401
  this.currentTurnId = generateId("turn");
18402
+ this.turnStartTime = Date.now();
18289
18403
  writeNotification(RPC_NOTIFICATIONS.TURN_START, {
18290
18404
  turnId: this.currentTurnId,
18291
18405
  timestamp: createTimestamp()
18292
18406
  });
18293
18407
  try {
18294
18408
  const imagePlaceholders = [];
18295
- if (params.images && params.images.length > 0 && this.imageManager) {
18409
+ process.stderr.write(`[RPC] handlePrompt: images=${params.images?.length || 0}, hasImageManager=${!!this.imageManager}, model=${this.model}
18410
+ `);
18411
+ if (params.images && params.images.length > 0) {
18412
+ if (!supportsVision(this.model)) {
18413
+ process.stderr.write(`[RPC] WARNING: Model '${this.model}' does not support vision. Images will not be processed.
18414
+ `);
18415
+ writeNotification(RPC_NOTIFICATIONS.ERROR, {
18416
+ code: -32e3,
18417
+ message: `Model '${this.model}' does not support image inputs. Please use a vision-capable model like claude-3.5-sonnet, gpt-4o, or gemini-1.5-pro.`,
18418
+ recoverable: true,
18419
+ timestamp: createTimestamp()
18420
+ });
18421
+ }
18422
+ }
18423
+ if (params.images && params.images.length > 0 && this.imageManager && supportsVision(this.model)) {
18424
+ process.stderr.write(`[RPC] Processing ${params.images.length} images
18425
+ `);
18296
18426
  for (const img of params.images) {
18297
18427
  try {
18428
+ process.stderr.write(`[RPC] Image: mimeType=${img.mimeType}, dataLength=${img.data?.length || 0}
18429
+ `);
18298
18430
  if (!isValidImageMimeType(img.mimeType)) {
18431
+ process.stderr.write(`[RPC] Invalid MIME type: ${img.mimeType}
18432
+ `);
18299
18433
  writeNotification(RPC_NOTIFICATIONS.ERROR, {
18300
18434
  code: -32602,
18301
18435
  // Invalid params
@@ -18306,6 +18440,8 @@ var RPCAdapter = class {
18306
18440
  continue;
18307
18441
  }
18308
18442
  const data = Buffer.from(img.data, "base64");
18443
+ process.stderr.write(`[RPC] Image decoded: ${data.length} bytes
18444
+ `);
18309
18445
  if (data.length > MAX_IMAGE_SIZE) {
18310
18446
  writeNotification(RPC_NOTIFICATIONS.ERROR, {
18311
18447
  code: -32602,
@@ -18331,9 +18467,14 @@ var RPCAdapter = class {
18331
18467
  }
18332
18468
  let instruction = params.message;
18333
18469
  if (imagePlaceholders.length > 0) {
18470
+ process.stderr.write(`[RPC] Image placeholders: ${imagePlaceholders.join(", ")}
18471
+ `);
18334
18472
  instruction = `${imagePlaceholders.join(" ")}
18335
18473
 
18336
18474
  ${instruction}`;
18475
+ } else if (params.images && params.images.length > 0) {
18476
+ process.stderr.write(`[RPC] WARNING: Images provided but no placeholders generated!
18477
+ `);
18337
18478
  }
18338
18479
  if (params.context?.selection) {
18339
18480
  const sel = params.context.selection;
@@ -18355,7 +18496,40 @@ ${sel.text}
18355
18496
  try {
18356
18497
  process.stderr.write(`[RPC DEBUG] Executing instruction: ${instruction.substring(0, 100)}
18357
18498
  `);
18358
- success = await this.agent.runInstruction(instruction);
18499
+ if (this.agent.isSlashCommand(instruction)) {
18500
+ const { command, args } = this.agent.parseSlashCommand(instruction);
18501
+ process.stderr.write(`[RPC DEBUG] Handling slash command: ${command}, args: ${JSON.stringify(args)}
18502
+ `);
18503
+ if (this.agent.isSlashCommandSupported(command)) {
18504
+ const result = await this.agent.handleSlashCommand(command, args);
18505
+ if (result !== null) {
18506
+ this.currentMessageContent = result;
18507
+ writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
18508
+ messageId: this.currentMessageId,
18509
+ delta: result,
18510
+ timestamp: createTimestamp()
18511
+ });
18512
+ } else {
18513
+ this.currentMessageContent = `Command ${command} executed.`;
18514
+ writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
18515
+ messageId: this.currentMessageId,
18516
+ delta: this.currentMessageContent,
18517
+ timestamp: createTimestamp()
18518
+ });
18519
+ }
18520
+ success = true;
18521
+ } else {
18522
+ this.currentMessageContent = `Unknown command: ${command}. Type /help for available commands.`;
18523
+ writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
18524
+ messageId: this.currentMessageId,
18525
+ delta: this.currentMessageContent,
18526
+ timestamp: createTimestamp()
18527
+ });
18528
+ success = false;
18529
+ }
18530
+ } else {
18531
+ success = await this.agent.runInstruction(instruction);
18532
+ }
18359
18533
  process.stderr.write(`[RPC DEBUG] Instruction completed, success=${success}, content length=${this.currentMessageContent.length}
18360
18534
  `);
18361
18535
  } catch (err) {
@@ -18378,24 +18552,34 @@ ${sel.text}
18378
18552
  content: this.currentMessageContent,
18379
18553
  timestamp: createTimestamp()
18380
18554
  });
18555
+ const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
18556
+ const snapshot = this.agent?.getStatusSnapshot();
18381
18557
  writeNotification(RPC_NOTIFICATIONS.TURN_END, {
18382
18558
  turnId: this.currentTurnId,
18383
18559
  timestamp: createTimestamp(),
18384
- contextPercent: this.contextPercent
18560
+ contextPercent: this.contextPercent,
18561
+ tokensUsed: snapshot?.tokensUsed,
18562
+ durationMs
18385
18563
  });
18386
18564
  this.status = "idle";
18387
18565
  this.currentTurnId = null;
18566
+ this.turnStartTime = null;
18388
18567
  this.currentMessageId = null;
18389
18568
  this.abortController = null;
18390
18569
  return { success };
18391
18570
  } catch (error) {
18571
+ const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
18572
+ const snapshot = this.agent?.getStatusSnapshot();
18392
18573
  writeNotification(RPC_NOTIFICATIONS.TURN_END, {
18393
18574
  turnId: this.currentTurnId,
18394
18575
  timestamp: createTimestamp(),
18395
- contextPercent: this.contextPercent
18576
+ contextPercent: this.contextPercent,
18577
+ tokensUsed: snapshot?.tokensUsed,
18578
+ durationMs
18396
18579
  });
18397
18580
  this.status = "idle";
18398
18581
  this.currentTurnId = null;
18582
+ this.turnStartTime = null;
18399
18583
  this.currentMessageId = null;
18400
18584
  this.abortController = null;
18401
18585
  throw error;
@@ -18405,24 +18589,41 @@ ${sel.text}
18405
18589
  * Handle abort request
18406
18590
  */
18407
18591
  handleAbort(requestId) {
18592
+ process.stderr.write(`[RPC] handleAbort called, abortController=${!!this.abortController}
18593
+ `);
18594
+ for (const [permId, pending] of this.pendingPermissions) {
18595
+ process.stderr.write(`[RPC] Clearing pending permission ${permId} due to abort
18596
+ `);
18597
+ if (pending.ackTimeout) clearTimeout(pending.ackTimeout);
18598
+ if (pending.responseTimeout) clearTimeout(pending.responseTimeout);
18599
+ pending.resolve(false);
18600
+ }
18601
+ this.pendingPermissions.clear();
18408
18602
  if (this.abortController) {
18409
18603
  this.abortController.abort();
18410
18604
  this.status = "idle";
18411
18605
  if (this.currentMessageId) {
18412
18606
  writeNotification(RPC_NOTIFICATIONS.MESSAGE_END, {
18413
18607
  messageId: this.currentMessageId,
18414
- content: this.currentMessageContent + "\n\n*[Aborted by user]*",
18608
+ content: this.currentMessageContent,
18609
+ // No marker - UI handles display
18610
+ aborted: true,
18415
18611
  timestamp: createTimestamp()
18416
18612
  });
18417
18613
  }
18418
18614
  if (this.currentTurnId) {
18615
+ const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
18616
+ const snapshot = this.agent?.getStatusSnapshot();
18419
18617
  writeNotification(RPC_NOTIFICATIONS.TURN_END, {
18420
18618
  turnId: this.currentTurnId,
18421
18619
  timestamp: createTimestamp(),
18422
- contextPercent: this.contextPercent
18620
+ contextPercent: this.contextPercent,
18621
+ tokensUsed: snapshot?.tokensUsed,
18622
+ durationMs
18423
18623
  });
18424
18624
  }
18425
18625
  this.currentTurnId = null;
18626
+ this.turnStartTime = null;
18426
18627
  this.currentMessageId = null;
18427
18628
  this.currentMessageContent = "";
18428
18629
  this.abortController = null;
@@ -18470,22 +18671,40 @@ ${sel.text}
18470
18671
  * Handle permission response from client
18471
18672
  */
18472
18673
  handlePermissionResponse(requestId, permRequestId, allowed) {
18674
+ process.stderr.write(`[RPC] handlePermissionResponse called: permRequestId=${permRequestId}, allowed=${allowed}, pending keys=${Array.from(this.pendingPermissions.keys()).join(",")}
18675
+ `);
18473
18676
  const pending = this.pendingPermissions.get(permRequestId);
18474
18677
  if (pending) {
18475
- clearTimeout(pending.timeout);
18678
+ process.stderr.write(`[RPC] Found pending permission, resolving with allowed=${allowed}
18679
+ `);
18680
+ if (pending.ackTimeout) {
18681
+ clearTimeout(pending.ackTimeout);
18682
+ }
18683
+ if (pending.responseTimeout) {
18684
+ clearTimeout(pending.responseTimeout);
18685
+ }
18476
18686
  this.pendingPermissions.delete(permRequestId);
18477
18687
  pending.resolve(allowed);
18478
18688
  this.status = "processing";
18689
+ process.stderr.write(`[RPC] Permission resolved, status set to processing
18690
+ `);
18479
18691
  return { success: true };
18480
18692
  }
18693
+ process.stderr.write(`[RPC] Permission response for unknown request ${permRequestId}
18694
+ `);
18481
18695
  return { success: false };
18482
18696
  }
18483
18697
  /**
18484
18698
  * Request permission from client (called from agent's confirmDangerousAction)
18699
+ * Uses two-phase timeout:
18700
+ * - Phase 1: 30s to receive acknowledgment from extension
18701
+ * - Phase 2: 1 hour for user to respond after ack received
18485
18702
  */
18486
18703
  async requestPermission(tool, description, context) {
18487
18704
  const permRequestId = generateId("perm");
18488
18705
  this.status = "waiting_permission";
18706
+ process.stderr.write(`[RPC] requestPermission: tool=${tool}, permRequestId=${permRequestId}
18707
+ `);
18489
18708
  writeNotification(RPC_NOTIFICATIONS.PERMISSION_REQUEST, {
18490
18709
  requestId: permRequestId,
18491
18710
  tool,
@@ -18494,19 +18713,53 @@ ${sel.text}
18494
18713
  timestamp: createTimestamp()
18495
18714
  });
18496
18715
  return new Promise((resolve, reject) => {
18497
- const timeout = setTimeout(() => {
18716
+ const ackTimeout = setTimeout(() => {
18498
18717
  this.pendingPermissions.delete(permRequestId);
18499
18718
  this.status = "processing";
18719
+ process.stderr.write(`[RPC] Permission ack timeout for ${permRequestId}
18720
+ `);
18500
18721
  resolve(false);
18501
- }, 6e4);
18722
+ }, 3e4);
18502
18723
  this.pendingPermissions.set(permRequestId, {
18503
18724
  requestId: permRequestId,
18504
18725
  resolve,
18505
18726
  reject,
18506
- timeout
18727
+ ackTimeout,
18728
+ responseTimeout: null,
18729
+ acknowledged: false
18507
18730
  });
18508
18731
  });
18509
18732
  }
18733
+ /**
18734
+ * Handle acknowledgment from client that permission UI is shown
18735
+ * Extends timeout since we know extension is alive and user is deciding
18736
+ */
18737
+ handlePermissionAcknowledged(permRequestId) {
18738
+ const pending = this.pendingPermissions.get(permRequestId);
18739
+ if (!pending) {
18740
+ process.stderr.write(`[RPC] Permission ack for unknown request ${permRequestId}
18741
+ `);
18742
+ return { success: false };
18743
+ }
18744
+ if (pending.acknowledged) {
18745
+ return { success: true };
18746
+ }
18747
+ if (pending.ackTimeout) {
18748
+ clearTimeout(pending.ackTimeout);
18749
+ pending.ackTimeout = null;
18750
+ }
18751
+ pending.acknowledged = true;
18752
+ pending.responseTimeout = setTimeout(() => {
18753
+ this.pendingPermissions.delete(permRequestId);
18754
+ this.status = "processing";
18755
+ process.stderr.write(`[RPC] Permission response timeout for ${permRequestId} (1 hour)
18756
+ `);
18757
+ pending.resolve(false);
18758
+ }, 36e5);
18759
+ process.stderr.write(`[RPC] Permission acknowledged for ${permRequestId}
18760
+ `);
18761
+ return { success: true };
18762
+ }
18510
18763
  /**
18511
18764
  * Emit tool execution start notification
18512
18765
  */
@@ -18561,7 +18814,12 @@ ${sel.text}
18561
18814
  */
18562
18815
  shutdown(reason = "completed") {
18563
18816
  for (const [, pending] of this.pendingPermissions) {
18564
- clearTimeout(pending.timeout);
18817
+ if (pending.ackTimeout) {
18818
+ clearTimeout(pending.ackTimeout);
18819
+ }
18820
+ if (pending.responseTimeout) {
18821
+ clearTimeout(pending.responseTimeout);
18822
+ }
18565
18823
  pending.reject(new Error("Adapter shutdown"));
18566
18824
  }
18567
18825
  this.pendingPermissions.clear();
@@ -18736,9 +18994,8 @@ async function runRpcMode(options) {
18736
18994
  config,
18737
18995
  workspaceRoot,
18738
18996
  options: {
18739
- ...options,
18740
- // Auto-approve for MVP - proper permission handling via RPC coming later
18741
- yes: true
18997
+ ...options
18998
+ // Do NOT set yes: true - permissions are handled via RPC
18742
18999
  }
18743
19000
  };
18744
19001
  const provider = ProviderFactory.create(config);
@@ -18756,6 +19013,17 @@ async function runRpcMode(options) {
18756
19013
  options.model ?? config.openrouter?.model ?? "unknown",
18757
19014
  workspaceRoot
18758
19015
  );
19016
+ agent.setConfirmationCallback(async (message, context) => {
19017
+ if (!adapter) {
19018
+ throw new Error("RPC adapter not initialized");
19019
+ }
19020
+ const tool = context?.tool ?? "action";
19021
+ const description = message;
19022
+ const permContext = {};
19023
+ if (context?.command) permContext.command = context.command;
19024
+ if (context?.path) permContext.path = context.path;
19025
+ return adapter.requestPermission(tool, description, permContext);
19026
+ });
18759
19027
  const reader = new LineReader(process.stdin);
18760
19028
  while (true) {
18761
19029
  try {
@@ -18813,8 +19081,21 @@ async function handleSingleRequest(request, adapter) {
18813
19081
  }
18814
19082
  return null;
18815
19083
  }
18816
- result = await adapter.handlePrompt(id, promptParams);
18817
- break;
19084
+ adapter.handlePrompt(id, promptParams).then((promptResult) => {
19085
+ if (shouldRespond) {
19086
+ process.stdout.write(JSON.stringify(createResponse(id, promptResult)) + "\n");
19087
+ }
19088
+ }).catch((error) => {
19089
+ const message = error instanceof Error ? error.message : String(error);
19090
+ if (shouldRespond) {
19091
+ process.stdout.write(JSON.stringify(createErrorResponse(
19092
+ id,
19093
+ JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
19094
+ message
19095
+ )) + "\n");
19096
+ }
19097
+ });
19098
+ return null;
18818
19099
  }
18819
19100
  case RPC_METHODS.ABORT: {
18820
19101
  result = adapter.handleAbort(id);
@@ -18848,6 +19129,21 @@ async function handleSingleRequest(request, adapter) {
18848
19129
  result = adapter.handlePermissionResponse(id, permParams.requestId, permParams.allowed);
18849
19130
  break;
18850
19131
  }
19132
+ case RPC_METHODS.PERMISSION_ACKNOWLEDGED: {
19133
+ const ackParams = params;
19134
+ if (!ackParams?.requestId) {
19135
+ if (shouldRespond) {
19136
+ return createErrorResponse(
19137
+ id,
19138
+ JSON_RPC_ERROR_CODES.INVALID_PARAMS,
19139
+ "Missing required parameter: requestId"
19140
+ );
19141
+ }
19142
+ return null;
19143
+ }
19144
+ result = adapter.handlePermissionAcknowledged(ackParams.requestId);
19145
+ break;
19146
+ }
18851
19147
  default: {
18852
19148
  if (shouldRespond) {
18853
19149
  return createErrorResponse(
@@ -18879,6 +19175,9 @@ async function handleSingleRequest(request, adapter) {
18879
19175
  // src/index.ts
18880
19176
  process.title = "autohand";
18881
19177
  function getGitCommit() {
19178
+ if (true) {
19179
+ return "056d8fb";
19180
+ }
18882
19181
  try {
18883
19182
  return execSync3("git rev-parse --short HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
18884
19183
  } catch {
@@ -18929,6 +19228,9 @@ process.on("uncaughtException", (err) => {
18929
19228
  process.exit(1);
18930
19229
  });
18931
19230
  process.on("unhandledRejection", (reason, promise) => {
19231
+ if (reason && typeof reason === "object" && reason.code === "ERR_USE_AFTER_CLOSE") {
19232
+ return;
19233
+ }
18932
19234
  globalThis.__autohandLastError = reason;
18933
19235
  console.error("[DEBUG] Unhandled Rejection at:", promise, "reason:", reason);
18934
19236
  });
@@ -18943,7 +19245,11 @@ var ASCII_FRIEND = [
18943
19245
  "\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"
18944
19246
  ].join("\n");
18945
19247
  var program = new Command();
18946
- 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("--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 changes after completing tasks", 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("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").action(async (opts) => {
19248
+ 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("--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 changes after completing tasks", 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("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").action(async (opts) => {
19249
+ if (opts.skillInstall !== void 0) {
19250
+ await runSkillInstall(opts);
19251
+ return;
19252
+ }
18947
19253
  if (opts.mode === "rpc") {
18948
19254
  await runRpcMode(opts);
18949
19255
  } else {
@@ -18967,17 +19273,27 @@ async function runCLI(options) {
18967
19273
  }
18968
19274
  console.log(chalk15.yellow(`No ${providerName} API key configured.
18969
19275
  `));
18970
- const { apiKey } = await enquirer4.prompt({
18971
- type: "password",
18972
- name: "apiKey",
18973
- message: `Enter your ${providerName === "openrouter" ? "OpenRouter" : providerName} API key`,
18974
- validate: (val) => {
18975
- if (typeof val !== "string" || !val.trim()) {
18976
- return "API key is required";
19276
+ let apiKey;
19277
+ try {
19278
+ const result = await enquirer4.prompt({
19279
+ type: "password",
19280
+ name: "apiKey",
19281
+ message: `Enter your ${providerName === "openrouter" ? "OpenRouter" : providerName} API key`,
19282
+ validate: (val) => {
19283
+ if (typeof val !== "string" || !val.trim()) {
19284
+ return "API key is required";
19285
+ }
19286
+ return true;
18977
19287
  }
18978
- return true;
19288
+ });
19289
+ apiKey = result.apiKey;
19290
+ } catch (error) {
19291
+ if (error?.code === "ERR_USE_AFTER_CLOSE" || error?.message?.includes("cancelled")) {
19292
+ console.log(chalk15.gray("\nSetup cancelled."));
19293
+ process.exit(0);
18979
19294
  }
18980
- });
19295
+ throw error;
19296
+ }
18981
19297
  if (providerName === "openrouter") {
18982
19298
  config.openrouter = {
18983
19299
  ...config.openrouter,
@@ -19079,6 +19395,21 @@ function printWelcome(runtime, authUser) {
19079
19395
  }
19080
19396
  console.log();
19081
19397
  }
19398
+ async function runSkillInstall(opts) {
19399
+ const config = await loadConfig(opts.config);
19400
+ const workspaceRoot = resolveWorkspaceRoot(config, opts.path);
19401
+ const { SkillsRegistry: SkillsRegistry2 } = await import("./SkillsRegistry-SP5MX7OA.js");
19402
+ const { AUTOHAND_PATHS: AUTOHAND_PATHS2 } = await import("./constants-N3I2FHCM.js");
19403
+ const { skillsInstall } = await import("./skills-install-WJ2LDRQG.js");
19404
+ const skillsRegistry = new SkillsRegistry2(AUTOHAND_PATHS2.skills);
19405
+ await skillsRegistry.initialize();
19406
+ await skillsRegistry.setWorkspace(workspaceRoot);
19407
+ const skillName = typeof opts.skillInstall === "string" ? opts.skillInstall : void 0;
19408
+ await skillsInstall({
19409
+ skillsRegistry,
19410
+ workspaceRoot
19411
+ }, skillName);
19412
+ }
19082
19413
  program.parseAsync();
19083
19414
  /**
19084
19415
  * @license
@@ -19157,20 +19488,6 @@ program.parseAsync();
19157
19488
  * TelemetryManager - High-level telemetry tracking
19158
19489
  * @license Apache-2.0
19159
19490
  */
19160
- /**
19161
- * @license
19162
- * Copyright 2025 Autohand AI LLC
19163
- * SPDX-License-Identifier: Apache-2.0
19164
- *
19165
- * SkillParser - Parses SKILL.md files with YAML frontmatter
19166
- */
19167
- /**
19168
- * @license
19169
- * Copyright 2025 Autohand AI LLC
19170
- * SPDX-License-Identifier: Apache-2.0
19171
- *
19172
- * SkillsRegistry - Manages skill discovery, loading, and activation
19173
- */
19174
19491
  /**
19175
19492
  * @license
19176
19493
  * Copyright 2025 Autohand AI LLC