autohand-cli 0.6.11 → 0.7.2

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 (121) hide show
  1. package/README.md +60 -48
  2. package/dist/AutomodeManager-FQQPBHAD.js +1422 -0
  3. package/dist/AutomodeManager-PUNJS7IZ.cjs +1422 -0
  4. package/dist/{CommunitySkillsCache-FXWTOBSZ.js → CommunitySkillsCache-AY3ZYDTN.js} +1 -1
  5. package/dist/{CommunitySkillsCache-MQOTKCVS.cjs → CommunitySkillsCache-KO4DSKMA.cjs} +1 -1
  6. package/dist/{GitHubRegistryFetcher-S7QFUEKV.cjs → GitHubRegistryFetcher-S4JREWUQ.cjs} +1 -1
  7. package/dist/{GitHubRegistryFetcher-6JQ5JEDZ.js → GitHubRegistryFetcher-V23KTTLM.js} +1 -1
  8. package/dist/HookManager-OGINWAJN.cjs +7 -0
  9. package/dist/HookManager-VG46FXSZ.js +7 -0
  10. package/dist/MemoryManager-AFS5EZJ2.js +8 -0
  11. package/dist/MemoryManager-KE6EKW7Y.cjs +8 -0
  12. package/dist/{PermissionManager-RY3K6CVU.cjs → PermissionManager-MAPKIJMD.cjs} +1 -1
  13. package/dist/{PermissionManager-XIMDETMX.js → PermissionManager-V2Q2OAS2.js} +1 -1
  14. package/dist/SessionManager-MKLGLZWC.cjs +10 -0
  15. package/dist/SessionManager-V25OJDDY.js +10 -0
  16. package/dist/{SkillsRegistry-LXDK73BL.cjs → SkillsRegistry-4RU2OAEQ.cjs} +1 -1
  17. package/dist/{SkillsRegistry-SP5MX7OA.js → SkillsRegistry-FTLVJZMA.js} +1 -1
  18. package/dist/{agents-FH47ZMOI.cjs → agents-CHNQ7LQ4.cjs} +1 -1
  19. package/dist/{agents-NB5VQN6H.js → agents-E4NEH2PN.js} +1 -1
  20. package/dist/{agents-new-XLEU26YI.cjs → agents-new-GHVWXW7T.cjs} +1 -1
  21. package/dist/{agents-new-M325HGWT.js → agents-new-W6HMQ7V5.js} +1 -1
  22. package/dist/automode-BJYGRMEQ.cjs +9 -0
  23. package/dist/automode-XCNP6HP4.js +9 -0
  24. package/dist/chunk-2FLBGPE3.js +199 -0
  25. package/dist/{chunk-5RX6NVQO.cjs → chunk-3L76MLO5.cjs} +20 -2
  26. package/dist/chunk-3RG5ZIWI.js +10 -0
  27. package/dist/chunk-4L5WYXHN.js +246 -0
  28. package/dist/{chunk-SM4I2535.js → chunk-52HPEJ4P.js} +1 -1
  29. package/dist/{chunk-O2GOSS3V.js → chunk-6XITU2RU.js} +1 -1
  30. package/dist/{chunk-GNKSRX4R.js → chunk-B2BPL6IL.js} +216 -27
  31. package/dist/{chunk-SFT6BEYU.js → chunk-BAFJQUWR.js} +20 -2
  32. package/dist/chunk-CXZEPTRI.js +172 -0
  33. package/dist/{chunk-O3UH2TS6.cjs → chunk-E6U4UE3B.cjs} +1 -1
  34. package/dist/{chunk-CJ7JHMK7.cjs → chunk-EBM5QDJ4.cjs} +2 -2
  35. package/dist/{chunk-TF4ROETV.cjs → chunk-FEI2GPAW.cjs} +2 -2
  36. package/dist/{chunk-V6EL2YTY.js → chunk-HUE3GT5M.js} +1 -1
  37. package/dist/{chunk-DGVYSTC6.cjs → chunk-HXAAED4W.cjs} +10 -12
  38. package/dist/{chunk-QF2P64F7.js → chunk-LCCJ26QR.js} +2 -2
  39. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  40. package/dist/chunk-OTS4YFSZ.cjs +172 -0
  41. package/dist/{chunk-6SAX2G75.cjs → chunk-SXUZ3CX3.cjs} +215 -26
  42. package/dist/chunk-SXYYH3VD.cjs +497 -0
  43. package/dist/{chunk-NLJMRNGL.cjs → chunk-U43RFUBR.cjs} +2 -2
  44. package/dist/chunk-UOLJZFPJ.js +497 -0
  45. package/dist/chunk-UPR5PKX4.cjs +199 -0
  46. package/dist/chunk-URY4AS4L.cjs +246 -0
  47. package/dist/{chunk-ERBSCRG6.js → chunk-ZYHQ652D.js} +3 -5
  48. package/dist/{completion-HR3ZT55J.js → completion-2XTEWNMC.js} +1 -1
  49. package/dist/{completion-AMEZDTFT.cjs → completion-BTDPVWVB.cjs} +1 -1
  50. package/dist/{constants-N3I2FHCM.js → constants-RVCOJL3L.js} +1 -1
  51. package/dist/{constants-ICQLSGZN.cjs → constants-V4V43DZQ.cjs} +1 -1
  52. package/dist/{defaultHooks-A7TTVVRI.js → defaultHooks-3GRSZNKA.js} +1 -1
  53. package/dist/{defaultHooks-XGIBEP3Z.cjs → defaultHooks-7EAUU6MN.cjs} +1 -1
  54. package/dist/{export-ULYYSO5V.cjs → export-CB34FSEH.cjs} +1 -1
  55. package/dist/{export-MYBJZD3H.js → export-LNPP3XXH.js} +1 -1
  56. package/dist/{feedback-YGSYBQEW.js → feedback-IZKDNGHP.js} +1 -1
  57. package/dist/{feedback-TOGESBX7.cjs → feedback-OKGBXZZ4.cjs} +1 -1
  58. package/dist/{formatters-3POW3KMP.js → formatters-JJK6RS55.js} +1 -1
  59. package/dist/{formatters-UMUJYWV5.cjs → formatters-OIGPANHJ.cjs} +1 -1
  60. package/dist/{help-AW4QPGIW.js → help-MU553D6W.js} +1 -1
  61. package/dist/{help-2PR7P4UJ.cjs → help-QAAUDLFS.cjs} +1 -1
  62. package/dist/{hooks-2CLU2GWV.js → hooks-4N5VRVOF.js} +2 -2
  63. package/dist/hooks-7DZGBMXP.cjs +10 -0
  64. package/dist/index.cjs +990 -1447
  65. package/dist/index.js +871 -1328
  66. package/dist/{init-OLSCW7ZC.cjs → init-2QEB7GL5.cjs} +1 -1
  67. package/dist/{init-HAQKREMF.js → init-WW4RITLV.js} +1 -1
  68. package/dist/{lint-NJPZWVN2.js → lint-L2TD6NY6.js} +1 -1
  69. package/dist/{lint-D5UOJWIK.cjs → lint-ZLRBEAFF.cjs} +1 -1
  70. package/dist/{localProjectPermissions-5RX7NHAH.cjs → localProjectPermissions-2DP6X55S.cjs} +1 -1
  71. package/dist/{localProjectPermissions-W2CMGIFT.js → localProjectPermissions-5CXHD4DO.js} +1 -1
  72. package/dist/{login-YNFMV67B.js → login-LH62FYMH.js} +3 -4
  73. package/dist/login-TWWYJKX6.cjs +13 -0
  74. package/dist/logout-35XNU6Q2.cjs +13 -0
  75. package/dist/{logout-TSQ4O4GS.js → logout-KPHUXO23.js} +3 -4
  76. package/dist/{memory-F3V5FW6W.js → memory-2SGSO4MW.js} +1 -1
  77. package/dist/{memory-77SWEZYB.cjs → memory-ALXCFFQY.cjs} +1 -1
  78. package/dist/{model-B2PE6XFS.cjs → model-JC43B3KM.cjs} +1 -1
  79. package/dist/{model-JC53RT7A.js → model-U3BWIWVH.js} +1 -1
  80. package/dist/{new-J3ABPMW4.cjs → new-24YT5KVI.cjs} +1 -1
  81. package/dist/{new-356ITOM7.js → new-PCOF6OLV.js} +1 -1
  82. package/dist/{patch-MOD7QC3D.cjs → patch-ETANEGRW.cjs} +1 -1
  83. package/dist/{patch-5F6VBIT3.js → patch-RPK3BZQR.js} +1 -1
  84. package/dist/{permissions-3GS4ZWVA.js → permissions-5W5JOVM7.js} +1 -1
  85. package/dist/{permissions-E3MTIE7D.cjs → permissions-NHPJPHDV.cjs} +1 -1
  86. package/dist/{quit-2QWJ75GZ.js → quit-NIDVPHNL.js} +1 -1
  87. package/dist/{quit-DQ57J67A.cjs → quit-TMKMX2YF.cjs} +1 -1
  88. package/dist/{resume-43XMN4CL.cjs → resume-5C44HAAH.cjs} +1 -1
  89. package/dist/{resume-GA7YZJSO.js → resume-ZZ2D2NMB.js} +1 -1
  90. package/dist/{session-BSU2L5UI.cjs → session-6TMBGN7N.cjs} +1 -1
  91. package/dist/{session-SZMRN5KG.js → session-76F55XKA.js} +1 -1
  92. package/dist/{sessions-OJ4DRK3P.js → sessions-ERKBJ7MC.js} +1 -1
  93. package/dist/{sessions-CVOZGTKR.cjs → sessions-NUPCJVCF.cjs} +1 -1
  94. package/dist/{skills-APRIHHHU.js → skills-R25OBHE5.js} +2 -2
  95. package/dist/skills-UCWKIHHG.cjs +13 -0
  96. package/dist/{skills-install-52LS6X6D.js → skills-install-GNTBBL46.js} +1 -1
  97. package/dist/{skills-install-PDWIMVD5.cjs → skills-install-VUSVGSON.cjs} +1 -1
  98. package/dist/{skills-new-XDYS24XW.cjs → skills-new-AGXQNBRA.cjs} +1 -1
  99. package/dist/{skills-new-KIBUN63X.js → skills-new-L36LQXIE.js} +1 -1
  100. package/dist/status-2QV7C3JJ.cjs +9 -0
  101. package/dist/{status-DHA2IL2O.js → status-VJ6FOSRI.js} +2 -2
  102. package/dist/theme-MI3BM56M.cjs +13 -0
  103. package/dist/theme-ZXGSJBZI.js +13 -0
  104. package/dist/{undo-3Q44LSVS.js → undo-3UU5LWQS.js} +1 -1
  105. package/dist/{undo-WF5HB5VU.cjs → undo-W4VN2Y37.cjs} +1 -1
  106. package/package.json +2 -2
  107. package/dist/InkRenderer-4HTMUNIP.js +0 -2160
  108. package/dist/InkRenderer-DSNXNSRQ.cjs +0 -2160
  109. package/dist/chunk-JSBRDJBE.js +0 -30
  110. package/dist/chunk-N254NRHT.cjs +0 -30
  111. package/dist/chunk-QSPEMIKE.js +0 -230
  112. package/dist/chunk-UTNMIGOL.cjs +0 -230
  113. package/dist/filePalette-3JAGHGK5.js +0 -107
  114. package/dist/filePalette-PBE5D2OV.cjs +0 -107
  115. package/dist/hooks-JTRUWAEF.cjs +0 -10
  116. package/dist/login-2JO6W6PP.cjs +0 -14
  117. package/dist/logout-OGJQLG7K.cjs +0 -14
  118. package/dist/skills-UIIM5CKA.cjs +0 -13
  119. package/dist/status-VAGYUU3H.cjs +0 -9
  120. package/dist/theme-GGDFOBKE.js +0 -14
  121. package/dist/theme-PETVTBN7.cjs +0 -14
@@ -0,0 +1,497 @@
1
+ // src/core/HookManager.ts
2
+ import { spawn } from "child_process";
3
+ import { minimatch } from "minimatch";
4
+ var DEFAULT_HOOK_TIMEOUT = 5e3;
5
+ var HookManager = class {
6
+ constructor(options) {
7
+ this.initialized = false;
8
+ this.settings = options.settings ?? { enabled: true, hooks: [] };
9
+ this.workspaceRoot = options.workspaceRoot;
10
+ this.onPersist = options.onPersist;
11
+ this.onHookOutput = options.onHookOutput;
12
+ }
13
+ /**
14
+ * Initialize hooks - set up default hooks if none exist
15
+ */
16
+ async initialize() {
17
+ if (this.initialized) return;
18
+ this.initialized = true;
19
+ if (!this.settings.hooks || this.settings.hooks.length === 0) {
20
+ await this.installDefaultHooks();
21
+ } else {
22
+ await this.mergeDefaultHooks();
23
+ }
24
+ await this.ensureHookScripts();
25
+ }
26
+ /**
27
+ * Install default hooks (disabled by default)
28
+ */
29
+ async installDefaultHooks() {
30
+ try {
31
+ const { DEFAULT_HOOKS, SMART_COMMIT_HOOK } = await import("./defaultHooks-3GRSZNKA.js");
32
+ this.settings.hooks = [...DEFAULT_HOOKS, SMART_COMMIT_HOOK];
33
+ if (this.onPersist) {
34
+ await this.onPersist();
35
+ }
36
+ } catch {
37
+ }
38
+ }
39
+ /**
40
+ * Get a unique identifier for a hook (used for deduplication)
41
+ * Uses script filename for script-based hooks, or event+description for inline commands
42
+ */
43
+ getHookIdentifier(hook) {
44
+ const scriptMatch = hook.command.match(/([^/]+\.sh)$/);
45
+ if (scriptMatch) {
46
+ return `script:${scriptMatch[1]}`;
47
+ }
48
+ if (hook.description) {
49
+ return `${hook.event}:${hook.description}`;
50
+ }
51
+ return `${hook.event}:${hook.command}`;
52
+ }
53
+ /**
54
+ * Merge any missing default hooks with existing user hooks
55
+ * This ensures new built-in hooks are added when upgrading
56
+ */
57
+ async mergeDefaultHooks() {
58
+ try {
59
+ const { DEFAULT_HOOKS, SMART_COMMIT_HOOK } = await import("./defaultHooks-3GRSZNKA.js");
60
+ const allDefaults = [...DEFAULT_HOOKS, SMART_COMMIT_HOOK];
61
+ const existingHooks = this.settings.hooks ?? [];
62
+ const existingIds = new Set(existingHooks.map((h) => this.getHookIdentifier(h)));
63
+ const missingHooks = allDefaults.filter((h) => !existingIds.has(this.getHookIdentifier(h)));
64
+ if (missingHooks.length > 0) {
65
+ this.settings.hooks = [...existingHooks, ...missingHooks];
66
+ if (this.onPersist) {
67
+ await this.onPersist();
68
+ }
69
+ }
70
+ } catch (error) {
71
+ console.error("[hooks] Failed to merge default hooks:", error);
72
+ }
73
+ }
74
+ /**
75
+ * Ensure all hook scripts exist in ~/.autohand/hooks/
76
+ * Installs bundled scripts for built-in hooks
77
+ */
78
+ async ensureHookScripts() {
79
+ try {
80
+ const fs = await import("fs-extra");
81
+ const path = await import("path");
82
+ const os = await import("os");
83
+ const hooksDir = path.join(os.homedir(), ".autohand", "hooks");
84
+ await fs.ensureDir(hooksDir);
85
+ const { HOOK_SCRIPTS } = await import("./defaultHooks-3GRSZNKA.js");
86
+ for (const [scriptName, scriptContent] of Object.entries(HOOK_SCRIPTS)) {
87
+ const scriptPath = path.join(hooksDir, scriptName);
88
+ if (!await fs.pathExists(scriptPath)) {
89
+ await fs.writeFile(scriptPath, scriptContent, { mode: 493 });
90
+ }
91
+ }
92
+ } catch {
93
+ }
94
+ }
95
+ /**
96
+ * Check if hooks are globally enabled
97
+ */
98
+ isEnabled() {
99
+ return this.settings.enabled !== false;
100
+ }
101
+ /**
102
+ * Get all registered hooks
103
+ */
104
+ getHooks() {
105
+ return this.settings.hooks ?? [];
106
+ }
107
+ /**
108
+ * Get current settings
109
+ */
110
+ getSettings() {
111
+ return { ...this.settings };
112
+ }
113
+ /**
114
+ * Update settings
115
+ */
116
+ async updateSettings(settings) {
117
+ this.settings = { ...this.settings, ...settings };
118
+ if (this.onPersist) {
119
+ await this.onPersist();
120
+ }
121
+ }
122
+ /**
123
+ * Add a new hook
124
+ */
125
+ async addHook(hook) {
126
+ const hooks = this.settings.hooks ?? [];
127
+ hooks.push({ ...hook, enabled: hook.enabled !== false });
128
+ this.settings.hooks = hooks;
129
+ if (this.onPersist) {
130
+ await this.onPersist();
131
+ }
132
+ }
133
+ /**
134
+ * Remove a hook by index within its event type
135
+ */
136
+ async removeHook(event, index) {
137
+ const hooks = this.settings.hooks ?? [];
138
+ const eventHooks = hooks.filter((h) => h.event === event);
139
+ if (index < 0 || index >= eventHooks.length) {
140
+ return false;
141
+ }
142
+ const hookToRemove = eventHooks[index];
143
+ const globalIndex = hooks.indexOf(hookToRemove);
144
+ if (globalIndex !== -1) {
145
+ hooks.splice(globalIndex, 1);
146
+ this.settings.hooks = hooks;
147
+ if (this.onPersist) {
148
+ await this.onPersist();
149
+ }
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+ /**
155
+ * Toggle a hook's enabled status
156
+ */
157
+ async toggleHook(event, index) {
158
+ const hooks = this.settings.hooks ?? [];
159
+ const eventHooks = hooks.filter((h) => h.event === event);
160
+ if (index < 0 || index >= eventHooks.length) {
161
+ return false;
162
+ }
163
+ const hook = eventHooks[index];
164
+ hook.enabled = hook.enabled === false;
165
+ if (this.onPersist) {
166
+ await this.onPersist();
167
+ }
168
+ return true;
169
+ }
170
+ /**
171
+ * Check if a hook's filter matches the context
172
+ */
173
+ matchesFilter(filter, context) {
174
+ if (!filter) return true;
175
+ if (filter.tool && filter.tool.length > 0) {
176
+ if (!context.tool || !filter.tool.includes(context.tool)) {
177
+ return false;
178
+ }
179
+ }
180
+ if (filter.path && filter.path.length > 0) {
181
+ if (!context.path) return false;
182
+ const matches = filter.path.some((pattern) => minimatch(context.path, pattern));
183
+ if (!matches) return false;
184
+ }
185
+ return true;
186
+ }
187
+ /**
188
+ * Check if a hook's regex matcher matches the context
189
+ * Matchers apply to tool names, notification types, session types, etc.
190
+ */
191
+ matchesMatcher(hook, context) {
192
+ if (!hook.matcher) return true;
193
+ let value = "";
194
+ switch (hook.event) {
195
+ case "pre-tool":
196
+ case "post-tool":
197
+ case "permission-request":
198
+ value = context.tool ?? "";
199
+ break;
200
+ case "notification":
201
+ value = context.notificationType ?? "";
202
+ break;
203
+ case "session-start":
204
+ value = context.sessionType ?? "";
205
+ break;
206
+ case "session-end":
207
+ value = context.sessionEndReason ?? "";
208
+ break;
209
+ case "subagent-stop":
210
+ value = context.subagentType ?? "";
211
+ break;
212
+ default:
213
+ return true;
214
+ }
215
+ try {
216
+ return new RegExp(hook.matcher).test(value);
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+ /**
222
+ * Build environment variables from hook context
223
+ */
224
+ buildEnvironment(context) {
225
+ const env = {
226
+ ...process.env,
227
+ HOOK_EVENT: context.event,
228
+ HOOK_WORKSPACE: context.workspace
229
+ };
230
+ if (context.sessionId) env.HOOK_SESSION_ID = context.sessionId;
231
+ if (context.tool) env.HOOK_TOOL = context.tool;
232
+ if (context.toolCallId) env.HOOK_TOOL_CALL_ID = context.toolCallId;
233
+ if (context.args) env.HOOK_ARGS = JSON.stringify(context.args);
234
+ if (context.success !== void 0) env.HOOK_SUCCESS = String(context.success);
235
+ if (context.output) env.HOOK_OUTPUT = context.output;
236
+ if (context.duration !== void 0) env.HOOK_DURATION = String(context.duration);
237
+ if (context.path) env.HOOK_PATH = context.path;
238
+ if (context.changeType) env.HOOK_CHANGE_TYPE = context.changeType;
239
+ if (context.instruction) env.HOOK_INSTRUCTION = context.instruction;
240
+ if (context.mentionedFiles) env.HOOK_MENTIONED_FILES = JSON.stringify(context.mentionedFiles);
241
+ if (context.tokensUsed !== void 0) env.HOOK_TOKENS = String(context.tokensUsed);
242
+ if (context.toolCallsCount !== void 0) env.HOOK_TOOL_CALLS_COUNT = String(context.toolCallsCount);
243
+ if (context.toolCallsInTurn !== void 0) env.HOOK_TURN_TOOL_CALLS = String(context.toolCallsInTurn);
244
+ if (context.turnDuration !== void 0) env.HOOK_TURN_DURATION = String(context.turnDuration);
245
+ if (context.error) env.HOOK_ERROR = context.error;
246
+ if (context.errorCode) env.HOOK_ERROR_CODE = context.errorCode;
247
+ if (context.sessionType) env.HOOK_SESSION_TYPE = context.sessionType;
248
+ if (context.sessionEndReason) env.HOOK_SESSION_END_REASON = context.sessionEndReason;
249
+ if (context.subagentId) env.HOOK_SUBAGENT_ID = context.subagentId;
250
+ if (context.subagentType) env.HOOK_SUBAGENT_TYPE = context.subagentType;
251
+ if (context.permissionType) env.HOOK_PERMISSION_TYPE = context.permissionType;
252
+ if (context.notificationType) env.HOOK_NOTIFICATION_TYPE = context.notificationType;
253
+ if (context.notificationMessage) env.HOOK_NOTIFICATION_MSG = context.notificationMessage;
254
+ return env;
255
+ }
256
+ /**
257
+ * Build JSON input to pass via stdin to hook
258
+ */
259
+ buildJsonInput(context) {
260
+ return JSON.stringify({
261
+ session_id: context.sessionId,
262
+ cwd: context.workspace,
263
+ hook_event_name: context.event,
264
+ // Tool context
265
+ tool_name: context.tool,
266
+ tool_input: context.args,
267
+ tool_use_id: context.toolCallId,
268
+ tool_response: context.output,
269
+ tool_success: context.success,
270
+ // File context
271
+ file_path: context.path,
272
+ change_type: context.changeType,
273
+ // Prompt context
274
+ instruction: context.instruction,
275
+ mentioned_files: context.mentionedFiles,
276
+ // Stop/response context
277
+ tokens_used: context.tokensUsed,
278
+ tool_calls_count: context.toolCallsCount,
279
+ turn_tool_calls: context.toolCallsInTurn,
280
+ turn_duration: context.turnDuration,
281
+ duration: context.duration,
282
+ // Error context
283
+ error: context.error,
284
+ error_code: context.errorCode,
285
+ // Session context
286
+ session_type: context.sessionType,
287
+ session_end_reason: context.sessionEndReason,
288
+ // Subagent context
289
+ subagent_id: context.subagentId,
290
+ subagent_name: context.subagentName,
291
+ subagent_type: context.subagentType,
292
+ subagent_success: context.subagentSuccess,
293
+ subagent_error: context.subagentError,
294
+ subagent_duration: context.subagentDuration,
295
+ // Permission context
296
+ permission_type: context.permissionType,
297
+ // Notification context
298
+ notification_type: context.notificationType,
299
+ notification_message: context.notificationMessage
300
+ });
301
+ }
302
+ /**
303
+ * Parse JSON response from hook stdout
304
+ */
305
+ parseHookResponse(stdout) {
306
+ const trimmed = stdout.trim();
307
+ if (!trimmed.startsWith("{")) {
308
+ return void 0;
309
+ }
310
+ try {
311
+ return JSON.parse(trimmed);
312
+ } catch {
313
+ return void 0;
314
+ }
315
+ }
316
+ /**
317
+ * Execute a single hook
318
+ */
319
+ async executeHook(hook, context) {
320
+ const startTime = Date.now();
321
+ const timeout = hook.timeout ?? DEFAULT_HOOK_TIMEOUT;
322
+ const env = this.buildEnvironment(context);
323
+ const jsonInput = this.buildJsonInput(context);
324
+ return new Promise((resolve) => {
325
+ const child = spawn(hook.command, [], {
326
+ shell: true,
327
+ cwd: this.workspaceRoot,
328
+ env,
329
+ stdio: ["pipe", "pipe", "pipe"]
330
+ // stdin enabled for JSON input
331
+ });
332
+ let stdout = "";
333
+ let stderr = "";
334
+ let killed = false;
335
+ const timeoutId = setTimeout(() => {
336
+ killed = true;
337
+ child.kill("SIGTERM");
338
+ setTimeout(() => child.kill("SIGKILL"), 1e3);
339
+ }, timeout);
340
+ child.stdin?.write(jsonInput);
341
+ child.stdin?.end();
342
+ child.stdout?.on("data", (data) => {
343
+ stdout += data.toString();
344
+ });
345
+ child.stderr?.on("data", (data) => {
346
+ stderr += data.toString();
347
+ });
348
+ child.on("close", (code) => {
349
+ clearTimeout(timeoutId);
350
+ const duration = Date.now() - startTime;
351
+ const exitCode = code ?? 0;
352
+ const isBlockingError = exitCode === 2;
353
+ let response;
354
+ if (exitCode === 0) {
355
+ response = this.parseHookResponse(stdout);
356
+ }
357
+ const result = {
358
+ hook,
359
+ success: !killed && exitCode === 0,
360
+ stdout: stdout.trim() || void 0,
361
+ stderr: stderr.trim() || void 0,
362
+ error: killed ? `Hook timed out after ${timeout}ms` : isBlockingError ? stderr.trim() || "Hook blocked execution" : void 0,
363
+ duration,
364
+ exitCode,
365
+ blockingError: isBlockingError,
366
+ response
367
+ };
368
+ if (this.onHookOutput) {
369
+ this.onHookOutput(result);
370
+ }
371
+ resolve(result);
372
+ });
373
+ child.on("error", (err) => {
374
+ clearTimeout(timeoutId);
375
+ const duration = Date.now() - startTime;
376
+ const result = {
377
+ hook,
378
+ success: false,
379
+ error: err.message,
380
+ duration,
381
+ exitCode: -1
382
+ };
383
+ if (this.onHookOutput) {
384
+ this.onHookOutput(result);
385
+ }
386
+ resolve(result);
387
+ });
388
+ });
389
+ }
390
+ /**
391
+ * Get hooks for an event, including alias handling
392
+ */
393
+ getHooksForEvent(event) {
394
+ const hooks = this.getHooks().filter((h) => h.enabled !== false);
395
+ if (event === "stop") {
396
+ return hooks.filter((h) => h.event === "stop" || h.event === "post-response");
397
+ }
398
+ if (event === "post-response") {
399
+ return hooks.filter((h) => h.event === "stop" || h.event === "post-response");
400
+ }
401
+ return hooks.filter((h) => h.event === event);
402
+ }
403
+ /**
404
+ * Execute all hooks for an event
405
+ *
406
+ * Sync hooks are executed sequentially and block until complete.
407
+ * Async hooks are executed in parallel and don't block.
408
+ */
409
+ async executeHooks(event, context) {
410
+ if (!this.isEnabled()) {
411
+ return [];
412
+ }
413
+ const fullContext = {
414
+ ...context,
415
+ event,
416
+ workspace: this.workspaceRoot
417
+ };
418
+ const hooks = this.getHooksForEvent(event).filter(
419
+ (h) => this.matchesFilter(h.filter, fullContext) && this.matchesMatcher(h, fullContext)
420
+ );
421
+ if (hooks.length === 0) {
422
+ return [];
423
+ }
424
+ const syncHooks = hooks.filter((h) => !h.async);
425
+ const asyncHooks = hooks.filter((h) => h.async);
426
+ const results = [];
427
+ for (const hook of syncHooks) {
428
+ const result = await this.executeHook(hook, fullContext);
429
+ results.push(result);
430
+ if (result.response?.continue === false) {
431
+ break;
432
+ }
433
+ }
434
+ if (asyncHooks.length > 0) {
435
+ const asyncResults = await Promise.all(
436
+ asyncHooks.map((hook) => this.executeHook(hook, fullContext))
437
+ );
438
+ results.push(...asyncResults);
439
+ }
440
+ return results;
441
+ }
442
+ /**
443
+ * Test a hook by executing it with a sample context
444
+ */
445
+ async testHook(hook) {
446
+ const context = {
447
+ event: hook.event,
448
+ workspace: this.workspaceRoot,
449
+ tool: "test_tool",
450
+ toolCallId: "test_123",
451
+ args: { test: true },
452
+ success: true,
453
+ path: "test/file.ts",
454
+ changeType: "modify",
455
+ instruction: "Test instruction",
456
+ tokensUsed: 100
457
+ };
458
+ return this.executeHook(hook, context);
459
+ }
460
+ /**
461
+ * Get a summary of hooks by event
462
+ */
463
+ getSummary() {
464
+ const events = [
465
+ "pre-tool",
466
+ "post-tool",
467
+ "file-modified",
468
+ "pre-prompt",
469
+ "stop",
470
+ "post-response",
471
+ // Alias for 'stop'
472
+ "session-error",
473
+ "subagent-stop",
474
+ "session-start",
475
+ "session-end",
476
+ "permission-request",
477
+ "notification"
478
+ ];
479
+ const summary = {};
480
+ for (const event of events) {
481
+ const eventHooks = this.getHooks().filter((h) => h.event === event);
482
+ summary[event] = {
483
+ total: eventHooks.length,
484
+ enabled: eventHooks.filter((h) => h.enabled !== false).length
485
+ };
486
+ }
487
+ return summary;
488
+ }
489
+ };
490
+
491
+ export {
492
+ HookManager
493
+ };
494
+ /**
495
+ * Hook Manager - Executes lifecycle hooks based on config
496
+ * @license Apache-2.0
497
+ */
@@ -0,0 +1,199 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+ var _chunkXTHHDIBGcjs = require('./chunk-XTHHDIBG.cjs');
4
+
5
+ // src/session/SessionManager.ts
6
+ var _fsextra = require('fs-extra'); var _fsextra2 = _interopRequireDefault(_fsextra);
7
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
8
+ var _crypto = require('crypto'); var _crypto2 = _interopRequireDefault(_crypto);
9
+ var SessionManager = class {
10
+ constructor(baseDir) {
11
+ this.currentSession = null;
12
+ this.index = null;
13
+ this.sessionsDir = _nullishCoalesce(baseDir, () => ( _chunkXTHHDIBGcjs.AUTOHAND_PATHS.sessions));
14
+ }
15
+ async initialize() {
16
+ await _fsextra2.default.ensureDir(this.sessionsDir);
17
+ await this.loadIndex();
18
+ }
19
+ async createSession(projectPath, model) {
20
+ const sessionId = this.generateSessionId();
21
+ const sessionDir = _path2.default.join(this.sessionsDir, sessionId);
22
+ await _fsextra2.default.ensureDir(sessionDir);
23
+ const metadata = {
24
+ sessionId,
25
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
26
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
27
+ projectPath: _path2.default.resolve(projectPath),
28
+ projectName: _path2.default.basename(projectPath),
29
+ model,
30
+ messageCount: 0,
31
+ status: "active"
32
+ };
33
+ const session = new Session(sessionDir, metadata);
34
+ await session.save();
35
+ this.currentSession = session;
36
+ await this.addToIndex(session.metadata);
37
+ return session;
38
+ }
39
+ async loadSession(sessionId) {
40
+ const sessionDir = _path2.default.join(this.sessionsDir, sessionId);
41
+ if (!await _fsextra2.default.pathExists(sessionDir)) {
42
+ throw new Error(`Session not found: ${sessionId}`);
43
+ }
44
+ const metadataPath = _path2.default.join(sessionDir, "metadata.json");
45
+ const metadata = await _fsextra2.default.readJson(metadataPath);
46
+ const session = new Session(sessionDir, metadata);
47
+ await session.load();
48
+ this.currentSession = session;
49
+ return session;
50
+ }
51
+ async listSessions(filter) {
52
+ await this.loadIndex();
53
+ if (!this.index) return [];
54
+ let sessions = this.index.sessions;
55
+ if (_optionalChain([filter, 'optionalAccess', _ => _.project])) {
56
+ const projectPath = _path2.default.resolve(filter.project);
57
+ const sessionIds = this.index.byProject[projectPath] || [];
58
+ sessions = sessions.filter((s) => sessionIds.includes(s.id));
59
+ }
60
+ if (_optionalChain([filter, 'optionalAccess', _2 => _2.since])) {
61
+ sessions = sessions.filter((s) => new Date(s.createdAt) >= filter.since);
62
+ }
63
+ const fullMetadata = [];
64
+ for (const s of sessions) {
65
+ const sessionDir = _path2.default.join(this.sessionsDir, s.id);
66
+ const metadataPath = _path2.default.join(sessionDir, "metadata.json");
67
+ if (await _fsextra2.default.pathExists(metadataPath)) {
68
+ const metadata = await _fsextra2.default.readJson(metadataPath);
69
+ fullMetadata.push(metadata);
70
+ }
71
+ }
72
+ return fullMetadata.sort(
73
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
74
+ );
75
+ }
76
+ async getLastSession(projectPath) {
77
+ const sessions = await this.listSessions(projectPath ? { project: projectPath } : void 0);
78
+ return sessions[0] || null;
79
+ }
80
+ async closeSession(summary) {
81
+ if (!this.currentSession) return;
82
+ this.currentSession.metadata.closedAt = (/* @__PURE__ */ new Date()).toISOString();
83
+ this.currentSession.metadata.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
84
+ this.currentSession.metadata.status = "completed";
85
+ if (summary) {
86
+ this.currentSession.metadata.summary = summary;
87
+ }
88
+ await this.currentSession.save();
89
+ await this.updateIndex(this.currentSession.metadata);
90
+ this.currentSession = null;
91
+ }
92
+ getCurrentSession() {
93
+ return this.currentSession;
94
+ }
95
+ generateSessionId() {
96
+ const timestamp = Date.now();
97
+ const uuid = _crypto2.default.randomUUID();
98
+ return `${uuid}-${timestamp}`;
99
+ }
100
+ async loadIndex() {
101
+ const indexPath = _path2.default.join(this.sessionsDir, "index.json");
102
+ if (await _fsextra2.default.pathExists(indexPath)) {
103
+ this.index = await _fsextra2.default.readJson(indexPath);
104
+ } else {
105
+ this.index = { sessions: [], byProject: {} };
106
+ }
107
+ }
108
+ async saveIndex() {
109
+ const indexPath = _path2.default.join(this.sessionsDir, "index.json");
110
+ await _fsextra2.default.writeJson(indexPath, this.index, { spaces: 2 });
111
+ }
112
+ async addToIndex(metadata) {
113
+ if (!this.index) await this.loadIndex();
114
+ if (!this.index) return;
115
+ this.index.sessions.push({
116
+ id: metadata.sessionId,
117
+ projectPath: metadata.projectPath,
118
+ createdAt: metadata.createdAt,
119
+ summary: metadata.summary
120
+ });
121
+ if (!this.index.byProject[metadata.projectPath]) {
122
+ this.index.byProject[metadata.projectPath] = [];
123
+ }
124
+ this.index.byProject[metadata.projectPath].push(metadata.sessionId);
125
+ await this.saveIndex();
126
+ }
127
+ async updateIndex(metadata) {
128
+ if (!this.index) return;
129
+ const session = this.index.sessions.find((s) => s.id === metadata.sessionId);
130
+ if (session) {
131
+ session.summary = metadata.summary;
132
+ }
133
+ await this.saveIndex();
134
+ }
135
+ };
136
+ var Session = class {
137
+ constructor(sessionDir, metadata) {
138
+ this.messages = [];
139
+ this.state = null;
140
+ this.sessionDir = sessionDir;
141
+ this.metadata = metadata;
142
+ }
143
+ async append(message) {
144
+ this.messages.push(message);
145
+ this.metadata.messageCount = this.messages.length;
146
+ this.metadata.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
147
+ const conversationPath = _path2.default.join(this.sessionDir, "conversation.jsonl");
148
+ await _fsextra2.default.appendFile(conversationPath, JSON.stringify(message) + "\n");
149
+ await this.save();
150
+ }
151
+ async appendTransient(message) {
152
+ const conversationPath = _path2.default.join(this.sessionDir, "conversation.jsonl");
153
+ await _fsextra2.default.appendFile(conversationPath, JSON.stringify(message) + "\n");
154
+ }
155
+ async updateState(state) {
156
+ this.state = state;
157
+ const statePath = _path2.default.join(this.sessionDir, "state.json");
158
+ await _fsextra2.default.writeJson(statePath, state, { spaces: 2 });
159
+ }
160
+ async save() {
161
+ const metadataPath = _path2.default.join(this.sessionDir, "metadata.json");
162
+ await _fsextra2.default.writeJson(metadataPath, this.metadata, { spaces: 2 });
163
+ }
164
+ async load() {
165
+ const conversationPath = _path2.default.join(this.sessionDir, "conversation.jsonl");
166
+ if (await _fsextra2.default.pathExists(conversationPath)) {
167
+ const content = await _fsextra2.default.readFile(conversationPath, "utf-8");
168
+ this.messages = content.trim().split("\n").filter((line) => line).map((line) => JSON.parse(line));
169
+ }
170
+ const statePath = _path2.default.join(this.sessionDir, "state.json");
171
+ if (await _fsextra2.default.pathExists(statePath)) {
172
+ this.state = await _fsextra2.default.readJson(statePath);
173
+ }
174
+ }
175
+ getMessages() {
176
+ return this.messages;
177
+ }
178
+ getState() {
179
+ return this.state;
180
+ }
181
+ async close(summary) {
182
+ this.metadata.closedAt = (/* @__PURE__ */ new Date()).toISOString();
183
+ this.metadata.status = "completed";
184
+ if (summary) {
185
+ this.metadata.summary = summary;
186
+ }
187
+ await this.save();
188
+ }
189
+ };
190
+
191
+
192
+
193
+
194
+ exports.SessionManager = SessionManager; exports.Session = Session;
195
+ /**
196
+ * @license
197
+ * Copyright 2025 Autohand AI LLC
198
+ * SPDX-License-Identifier: Apache-2.0
199
+ */