@preziosiraffaele/agent-watch 0.2.0 → 0.3.0

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.
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/agent-core/src/types.ts
3
- var AGENT_FILTER_KEYS = ["nvim_server", "repo"];
3
+ var AGENT_FILTER_KEYS = ["repo"];
4
4
  // packages/agent-core/src/protocol.ts
5
5
  var DEFAULT_DAEMON_HOST = "127.0.0.1";
6
6
  var DEFAULT_DAEMON_PORT = 3847;
@@ -30,6 +30,7 @@ var STATES = {
30
30
  FAILED: "failed"
31
31
  };
32
32
  var STALE_AFTER_MS = 30 * 60 * 1000;
33
+ var MAX_EXITED_RECORDS = 50;
33
34
  // packages/agent-core/src/events.ts
34
35
  var CLAUDE_STATE = {
35
36
  SessionStart: STATES.SESSION_STARTED,
@@ -41,9 +42,7 @@ var CLAUDE_STATE = {
41
42
  PostToolUse: STATES.WORKING,
42
43
  PostToolUseFailure: STATES.WORKING,
43
44
  PostToolBatch: STATES.WORKING,
44
- Notification: STATES.WAITING,
45
45
  SubagentStart: STATES.RUNNING_TOOL,
46
- SubagentStop: STATES.IDLE,
47
46
  TaskCreated: STATES.RUNNING_TOOL,
48
47
  TaskCompleted: STATES.WORKING,
49
48
  Stop: STATES.IDLE,
@@ -88,18 +87,19 @@ function stateFor(agent, event) {
88
87
  return STATES.WORKING;
89
88
  }
90
89
  // packages/agent-core/src/repo.ts
91
- import { basename } from "path";
90
+ import { basename, dirname } from "path";
92
91
  function inferRepoContext(folder, runGit) {
93
92
  if (!folder)
94
- return { repo: null, repo_root: null, branch: null };
93
+ return { repo: null, branch: null, project_root: folder };
95
94
  const root = runGit(folder, ["rev-parse", "--show-toplevel"]);
96
95
  if (!root)
97
- return { repo: null, repo_root: null, branch: null };
96
+ return { repo: null, branch: null, project_root: folder };
98
97
  const branch = runGit(folder, ["branch", "--show-current"]) || runGit(folder, ["rev-parse", "--short", "HEAD"]);
98
+ const commonGitDir = runGit(folder, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
99
99
  return {
100
100
  repo: basename(root),
101
- repo_root: root,
102
- branch: branch || null
101
+ branch: branch || null,
102
+ project_root: commonGitDir ? dirname(commonGitDir) : root
103
103
  };
104
104
  }
105
105
  var realGitRunner = (folder, args) => {
@@ -196,7 +196,7 @@ function pickClaudeEventName(payload) {
196
196
  // packages/agent-core/src/daemon-state.ts
197
197
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
198
198
  import { homedir } from "os";
199
- import { dirname, join } from "path";
199
+ import { dirname as dirname2, join } from "path";
200
200
  var DAEMON_RUNTIME_DIR_NAME = ".agent-watch";
201
201
  var DAEMON_STATE_FILE_NAME = "daemon.json";
202
202
  function daemonRuntimeDir(home = homedir()) {
@@ -206,7 +206,7 @@ function daemonStatePath(runtimeDir = daemonRuntimeDir()) {
206
206
  return join(runtimeDir, DAEMON_STATE_FILE_NAME);
207
207
  }
208
208
  function writeDaemonState(state, path = daemonStatePath()) {
209
- mkdirSync(dirname(path), { recursive: true });
209
+ mkdirSync(dirname2(path), { recursive: true });
210
210
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
211
211
  writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}
212
212
  `);
@@ -245,12 +245,11 @@ function createRegistry(options = {}) {
245
245
  tool: "-",
246
246
  folder: reg.folder,
247
247
  repo: repo2.repo,
248
- repo_root: repo2.repo_root,
249
248
  branch: repo2.branch,
249
+ project_root: repo2.project_root,
250
250
  updated: now().toISOString(),
251
251
  agent_process_pid: null,
252
- nvim_server: reg.nvim?.server ?? null,
253
- nvim_terminal_bufnr: reg.nvim?.terminalBufnr ?? null
252
+ client_ref: reg.client_ref ?? null
254
253
  };
255
254
  records.set(id, record);
256
255
  broadcast({ type: "created", record });
@@ -260,28 +259,49 @@ function createRegistry(options = {}) {
260
259
  const current = records.get(id);
261
260
  if (!current)
262
261
  return null;
262
+ const folder = patch.folder ?? current.folder;
263
+ const repo2 = folder !== current.folder ? inferRepoContext(folder, gitRunner) : null;
263
264
  const next = {
264
265
  ...current,
265
266
  agent_process_pid: patch.agent_process_pid !== undefined ? patch.agent_process_pid : current.agent_process_pid,
266
267
  state: patch.state ?? current.state,
267
268
  title: patch.title !== undefined ? patch.title : current.title,
269
+ client_ref: patch.client_ref !== undefined ? patch.client_ref : current.client_ref,
270
+ folder,
271
+ ...repo2 ? {
272
+ repo: repo2.repo,
273
+ branch: repo2.branch,
274
+ project_root: repo2.project_root
275
+ } : {},
268
276
  updated: now().toISOString()
269
277
  };
270
278
  records.set(id, next);
271
279
  broadcast({ type: "updated", record: next });
272
280
  return next;
273
281
  };
282
+ const claimResume = (id, patch) => {
283
+ const current = records.get(id);
284
+ if (!current)
285
+ return null;
286
+ if (current.state !== STATES.EXITED)
287
+ return { type: "not_resumable", state: current.state };
288
+ const record = update(id, { ...patch, state: STATES.SESSION_STARTED });
289
+ if (!record)
290
+ return null;
291
+ return { type: "claimed", record };
292
+ };
274
293
  const applyHook = (id, agent, hook) => {
275
294
  const current = records.get(id);
276
295
  if (!current)
277
296
  return null;
297
+ if (current.state === STATES.EXITED)
298
+ return current;
278
299
  const received_at = now().toISOString();
279
300
  seq += 1;
280
- const state = agent === "claude" && hook.event === "SubagentStop" && current.state === STATES.IDLE ? current.state : hook.state;
281
301
  const next = {
282
302
  ...current,
283
303
  agent: current.agent || agent,
284
- state,
304
+ state: hook.state,
285
305
  tool: hook.tool || current.tool,
286
306
  session_id: hook.session_id ?? current.session_id,
287
307
  recent_events: [...current.recent_events, { event: hook.event, received_at, seq }].slice(-3),
@@ -298,11 +318,44 @@ function createRegistry(options = {}) {
298
318
  broadcast({ type: "removed", id });
299
319
  return true;
300
320
  };
321
+ const markExited = (id) => {
322
+ const current = records.get(id);
323
+ if (!current)
324
+ return null;
325
+ if (!current.session_id) {
326
+ records.delete(id);
327
+ broadcast({ type: "removed", id });
328
+ return { type: "removed" };
329
+ }
330
+ const next = {
331
+ ...current,
332
+ state: STATES.EXITED,
333
+ agent_process_pid: null,
334
+ updated: now().toISOString()
335
+ };
336
+ records.set(id, next);
337
+ broadcast({ type: "updated", record: next });
338
+ evictExcessExited();
339
+ return { type: "exited", record: next };
340
+ };
341
+ const evictExcessExited = () => {
342
+ const exited = Array.from(records.values()).filter((record) => record.state === STATES.EXITED);
343
+ const excess = exited.length - MAX_EXITED_RECORDS;
344
+ if (excess <= 0)
345
+ return;
346
+ exited.sort((a, b) => a.updated.localeCompare(b.updated) || a.id - b.id);
347
+ for (const victim of exited.slice(0, excess)) {
348
+ records.delete(victim.id);
349
+ broadcast({ type: "removed", id: victim.id });
350
+ }
351
+ };
301
352
  const refreshRepoContext = () => {
302
353
  const recordsByFolder = new Map;
303
354
  for (const record of records.values()) {
304
355
  if (!record.folder)
305
356
  continue;
357
+ if (record.state === STATES.EXITED)
358
+ continue;
306
359
  const grouped = recordsByFolder.get(record.folder);
307
360
  if (grouped) {
308
361
  grouped.push(record);
@@ -314,15 +367,14 @@ function createRegistry(options = {}) {
314
367
  for (const [folder, folderRecords] of recordsByFolder) {
315
368
  const repo2 = inferRepoContext(folder, gitRunner);
316
369
  for (const current of folderRecords) {
317
- if (current.repo === repo2.repo && current.repo_root === repo2.repo_root && current.branch === repo2.branch) {
370
+ if (current.repo === repo2.repo && current.branch === repo2.branch && current.project_root === repo2.project_root) {
318
371
  continue;
319
372
  }
320
373
  const next = {
321
374
  ...current,
322
375
  repo: repo2.repo,
323
- repo_root: repo2.repo_root,
324
376
  branch: repo2.branch,
325
- updated: now().toISOString()
377
+ project_root: repo2.project_root
326
378
  };
327
379
  records.set(next.id, next);
328
380
  changed.push(next);
@@ -335,6 +387,8 @@ function createRegistry(options = {}) {
335
387
  register,
336
388
  update,
337
389
  remove,
390
+ markExited,
391
+ claimResume,
338
392
  applyHook,
339
393
  refreshRepoContext,
340
394
  get(id) {
@@ -505,6 +559,30 @@ async function handle(request, registry, queue, onShutdown) {
505
559
  return text(404, "launch not found");
506
560
  return json({ ok: true });
507
561
  }
562
+ const launchResume = method === "POST" && path.match(/^\/launches\/(\d+)\/resume$/);
563
+ if (launchResume) {
564
+ const id = Number(launchResume[1]);
565
+ const patch = await readLaunchUpdate(request);
566
+ if (!patch)
567
+ return text(400, "invalid resume request");
568
+ const result = await queue.enqueue(id, () => registry.claimResume(id, patch));
569
+ if (!result)
570
+ return text(404, "launch not found");
571
+ if (result.type === "not_resumable") {
572
+ return json({ error: "not resumable", state: result.state }, { status: 409 });
573
+ }
574
+ return json(result.record);
575
+ }
576
+ const launchExit = method === "POST" && path.match(/^\/launches\/(\d+)\/exit$/);
577
+ if (launchExit) {
578
+ const id = Number(launchExit[1]);
579
+ const result = registry.markExited(id);
580
+ if (!result)
581
+ return text(404, "launch not found");
582
+ if (result.type === "removed")
583
+ return json({ ok: true });
584
+ return json(result.record);
585
+ }
508
586
  const hookMatch = method === "POST" && path.match(/^\/hooks\/(claude|codex|agent)$/);
509
587
  if (hookMatch) {
510
588
  const agent = hookMatch[1];
@@ -540,10 +618,7 @@ async function readLaunchRegistration(request) {
540
618
  agent: data.agent,
541
619
  folder: data.folder,
542
620
  title: typeof data.title === "string" ? data.title : null,
543
- nvim: {
544
- server: typeof data.nvim_server === "string" ? data.nvim_server : null,
545
- terminalBufnr: typeof data.nvim_terminal_bufnr === "string" ? data.nvim_terminal_bufnr : null
546
- }
621
+ client_ref: typeof data.client_ref === "string" ? data.client_ref : null
547
622
  };
548
623
  }
549
624
  async function readLaunchUpdate(request) {
@@ -560,6 +635,11 @@ async function readLaunchUpdate(request) {
560
635
  if ("title" in data) {
561
636
  patch.title = typeof data.title === "string" ? data.title : null;
562
637
  }
638
+ if (typeof data.folder === "string" && data.folder)
639
+ patch.folder = data.folder;
640
+ if ("client_ref" in data) {
641
+ patch.client_ref = typeof data.client_ref === "string" ? data.client_ref : null;
642
+ }
563
643
  return patch;
564
644
  }
565
645
  async function readJson(request) {
package/dist/aw.js CHANGED
@@ -47,9 +47,7 @@ var CLAUDE_STATE = {
47
47
  PostToolUse: STATES.WORKING,
48
48
  PostToolUseFailure: STATES.WORKING,
49
49
  PostToolBatch: STATES.WORKING,
50
- Notification: STATES.WAITING,
51
50
  SubagentStart: STATES.RUNNING_TOOL,
52
- SubagentStop: STATES.IDLE,
53
51
  TaskCreated: STATES.RUNNING_TOOL,
54
52
  TaskCompleted: STATES.WORKING,
55
53
  Stop: STATES.IDLE,
@@ -149,8 +147,7 @@ function createDaemonClient(url) {
149
147
  agent: reg.agent,
150
148
  folder: reg.folder,
151
149
  title: reg.title ?? null,
152
- nvim_server: reg.nvim?.server ?? null,
153
- nvim_terminal_bufnr: reg.nvim?.terminalBufnr ?? null
150
+ client_ref: reg.client_ref ?? null
154
151
  };
155
152
  return post("/launches", body);
156
153
  },
@@ -174,6 +171,40 @@ function createDaemonClient(url) {
174
171
  throw new Error(`DELETE /launches/${id} failed: ${response.status}`);
175
172
  return true;
176
173
  },
174
+ async getLaunch(id) {
175
+ const agents = await this.listAgents();
176
+ return agents.find((a) => a.id === id) ?? null;
177
+ },
178
+ async resumeLaunch(id, ctx) {
179
+ const response = await fetch(`${url}/launches/${id}/resume`, {
180
+ method: "POST",
181
+ headers: { "content-type": "application/json" },
182
+ body: JSON.stringify({
183
+ folder: ctx.folder,
184
+ client_ref: ctx.client_ref
185
+ })
186
+ });
187
+ if (response.status === 404)
188
+ return { type: "not_found" };
189
+ if (response.status === 409) {
190
+ const body = await response.json().catch(() => null);
191
+ return { type: "not_resumable", state: body?.state ?? "unknown" };
192
+ }
193
+ if (!response.ok)
194
+ throw new Error(`POST /launches/${id}/resume failed: ${response.status}`);
195
+ return { type: "resumed", record: await response.json() };
196
+ },
197
+ async reportExit(id) {
198
+ const response = await fetch(`${url}/launches/${id}/exit`, { method: "POST" });
199
+ if (response.status === 404)
200
+ return null;
201
+ if (!response.ok)
202
+ throw new Error(`POST /launches/${id}/exit failed: ${response.status}`);
203
+ const body = await response.json();
204
+ if ("ok" in body)
205
+ return null;
206
+ return body;
207
+ },
177
208
  async listAgents(filters) {
178
209
  const qs = filters ? new URLSearchParams(filters).toString() : "";
179
210
  const response = await fetch(`${url}/agents${qs ? `?${qs}` : ""}`);
@@ -260,26 +291,11 @@ function resolveDaemonEntry() {
260
291
  throw new Error("Could not locate agent-watchd entrypoint");
261
292
  }
262
293
 
263
- // packages/aw/src/nvim.ts
264
- function nvimContextFromEnv(env) {
265
- return {
266
- server: env.NVIM ?? null,
267
- terminalBufnr: null
268
- };
269
- }
270
- function mergeNvim(base, override) {
271
- return {
272
- server: override.server ?? base.server,
273
- terminalBufnr: override.terminalBufnr ?? base.terminalBufnr
274
- };
275
- }
276
-
277
294
  // packages/aw/src/commands/launch.ts
278
295
  function parseLaunchArgv(argv) {
279
296
  const rest = [];
280
297
  let title = null;
281
- let nvimServer = null;
282
- let nvimBufnr = null;
298
+ let clientRef = null;
283
299
  let separatorSeen = false;
284
300
  for (let i = 0;i < argv.length; i++) {
285
301
  const arg = argv[i] ?? "";
@@ -299,27 +315,19 @@ function parseLaunchArgv(argv) {
299
315
  title = arg.slice("--title=".length);
300
316
  continue;
301
317
  }
302
- if (arg === "--nvim-server") {
303
- nvimServer = argv[++i] ?? "";
304
- continue;
305
- }
306
- if (arg.startsWith("--nvim-server=")) {
307
- nvimServer = arg.slice("--nvim-server=".length);
318
+ if (arg === "--client-ref") {
319
+ clientRef = argv[++i] ?? "";
308
320
  continue;
309
321
  }
310
- if (arg === "--nvim-bufnr") {
311
- nvimBufnr = argv[++i] ?? "";
312
- continue;
313
- }
314
- if (arg.startsWith("--nvim-bufnr=")) {
315
- nvimBufnr = arg.slice("--nvim-bufnr=".length);
322
+ if (arg.startsWith("--client-ref=")) {
323
+ clientRef = arg.slice("--client-ref=".length);
316
324
  continue;
317
325
  }
318
326
  rest.push(arg);
319
327
  }
320
328
  return {
321
329
  title: title || null,
322
- nvim: { server: nvimServer, terminalBufnr: nvimBufnr },
330
+ client_ref: clientRef || null,
323
331
  rest
324
332
  };
325
333
  }
@@ -332,18 +340,19 @@ async function runLaunch(args) {
332
340
  return 1;
333
341
  }
334
342
  const client = await ensureDaemonRunning();
335
- const baseNvim = nvimContextFromEnv(process.env);
336
- const nvim = mergeNvim(baseNvim, parsed.nvim);
337
343
  const record = await client.registerLaunch({
338
344
  agent: args.agent,
339
- folder: process.cwd(),
345
+ folder: args.folder ?? process.cwd(),
340
346
  title: parsed.title,
341
- nvim
347
+ client_ref: parsed.client_ref
342
348
  });
349
+ return spawnAndWait(executable, record.id, parsed.rest, client);
350
+ }
351
+ async function spawnAndWait(executable, id, argv, client) {
343
352
  const env = { ...process.env };
344
- env[ENV.LAUNCH_ID] = String(record.id);
353
+ env[ENV.LAUNCH_ID] = String(id);
345
354
  env[ENV.DAEMON_URL] = client.url;
346
- const child = spawn2(executable, parsed.rest, { stdio: "inherit", env });
355
+ const child = spawn2(executable, argv, { stdio: "inherit", env });
347
356
  const cleanupSignals = ["SIGINT", "SIGTERM", "SIGHUP"];
348
357
  for (const signal of cleanupSignals) {
349
358
  process.on(signal, () => {
@@ -352,21 +361,21 @@ async function runLaunch(args) {
352
361
  });
353
362
  }
354
363
  if (typeof child.pid === "number") {
355
- client.updateLaunch(record.id, { agent_process_pid: child.pid }).catch(() => {
364
+ client.updateLaunch(id, { agent_process_pid: child.pid }).catch(() => {
356
365
  return;
357
366
  });
358
367
  }
359
368
  return await new Promise((resolve2) => {
360
369
  child.on("exit", (code, signal) => {
361
370
  const exitCode = signal ? 128 + signalNumber(signal) : code ?? 0;
362
- client.deleteLaunch(record.id).catch(() => {
371
+ client.reportExit(id).catch(() => {
363
372
  return;
364
373
  }).finally(() => resolve2(exitCode));
365
374
  });
366
375
  child.on("error", (err) => {
367
- process.stderr.write(`Could not launch ${args.agent}: ${err.message}
376
+ process.stderr.write(`Could not launch ${executable}: ${err.message}
368
377
  `);
369
- client.deleteLaunch(record.id).catch(() => {
378
+ client.deleteLaunch(id).catch(() => {
370
379
  return;
371
380
  }).finally(() => resolve2(1));
372
381
  });
@@ -603,15 +612,15 @@ ${END}
603
612
  writeFileSync3(file, content);
604
613
  }
605
614
  function ensureCodexFeatures(content) {
606
- if (/^\s*\[features\]/m.test(content)) {
607
- if (/^\s*codex_hooks\s*=/m.test(content)) {
608
- return content.replace(/^\s*codex_hooks\s*=.*$/m, "codex_hooks = true");
615
+ if (/^\s*\[features\]\s*$/m.test(content)) {
616
+ if (/^\s*hooks\s*=/m.test(content)) {
617
+ return content.replace(/^\s*hooks\s*=.*$/m, "hooks = true");
609
618
  }
610
619
  return content.replace(/^\s*\[features\]\s*$/m, `[features]
611
- codex_hooks = true`);
620
+ hooks = true`);
612
621
  }
613
622
  return `[features]
614
- codex_hooks = true
623
+ hooks = true
615
624
 
616
625
  ${content}`;
617
626
  }
@@ -689,6 +698,8 @@ function runHooksInstall(provider) {
689
698
  const file = join3(HOME2, ".codex", "config.toml");
690
699
  patchCodexConfig(file);
691
700
  process.stdout.write(`Patched ${file}
701
+ `);
702
+ process.stdout.write(`Open Codex and run /hooks to review the installed hooks.
692
703
  `);
693
704
  return 0;
694
705
  }
@@ -734,15 +745,84 @@ async function waitUntilStopped(client, timeoutMs) {
734
745
  return false;
735
746
  }
736
747
 
748
+ // packages/aw/src/commands/resume.ts
749
+ async function runResume(args) {
750
+ const [idStr, ...rest] = args;
751
+ if (!idStr) {
752
+ process.stderr.write(`Usage: aw resume <id> [--client-ref <token>] [-- agent args...]
753
+ `);
754
+ return 1;
755
+ }
756
+ const id = Number(idStr);
757
+ if (!Number.isInteger(id) || id <= 0) {
758
+ process.stderr.write(`Invalid launch id: ${idStr}
759
+ `);
760
+ return 1;
761
+ }
762
+ const parsed = parseLaunchArgv(rest);
763
+ const client = await ensureDaemonRunning();
764
+ const record = await client.getLaunch(id);
765
+ if (!record) {
766
+ process.stderr.write(`Launch ${id} not found
767
+ `);
768
+ return 1;
769
+ }
770
+ if (record.state !== STATES.EXITED) {
771
+ process.stderr.write(`Launch ${id} is not resumable (state: ${record.state})
772
+ `);
773
+ return 1;
774
+ }
775
+ if (!record.session_id) {
776
+ process.stderr.write(`Launch ${id} has no session_id
777
+ `);
778
+ return 1;
779
+ }
780
+ const executable = Bun.which(record.agent);
781
+ if (!executable) {
782
+ process.stderr.write(`Could not find ${record.agent} on PATH.
783
+ `);
784
+ return 1;
785
+ }
786
+ const argv = buildResumeArgs(record.agent, record.session_id);
787
+ if (parsed.rest.length)
788
+ argv.push("--", ...parsed.rest);
789
+ const claim = await client.resumeLaunch(id, {
790
+ folder: process.cwd(),
791
+ client_ref: parsed.client_ref
792
+ });
793
+ if (claim.type === "not_found") {
794
+ process.stderr.write(`Launch ${id} not found
795
+ `);
796
+ return 1;
797
+ }
798
+ if (claim.type === "not_resumable") {
799
+ process.stderr.write(`Launch ${id} is not resumable (state: ${claim.state})
800
+ `);
801
+ return 1;
802
+ }
803
+ return spawnAndWait(executable, id, argv, client);
804
+ }
805
+ function buildResumeArgs(agent, sessionId) {
806
+ switch (agent) {
807
+ case "claude":
808
+ return ["--resume", sessionId];
809
+ case "codex":
810
+ return ["resume", sessionId];
811
+ case "agent":
812
+ return ["--resume", sessionId];
813
+ }
814
+ }
815
+
737
816
  // packages/aw/src/index.ts
738
817
  var HELP = `aw \u2014 launcher and live view for terminal AI coding agents.
739
818
 
740
819
  Usage:
741
- aw claude [--title <title>] [--nvim-server <addr>] [--nvim-bufnr <nr>] [-- claude args...]
742
- aw codex [--title <title>] [--nvim-server <addr>] [--nvim-bufnr <nr>] [-- codex args...]
743
- aw agent [--title <title>] [--nvim-server <addr>] [--nvim-bufnr <nr>] [-- agent args...]
820
+ aw claude [--title <title>] [--client-ref <token>] [-- claude args...]
821
+ aw codex [--title <title>] [--client-ref <token>] [-- codex args...]
822
+ aw agent [--title <title>] [--client-ref <token>] [-- agent args...]
744
823
  aw stream
745
824
  aw stop
825
+ aw resume <id> [--client-ref <token>] [-- agent args...]
746
826
  aw hooks install <claude|codex|agent>
747
827
  aw help
748
828
  `;
@@ -761,6 +841,9 @@ async function main(argv) {
761
841
  if (command === "stop") {
762
842
  return runStop();
763
843
  }
844
+ if (command === "resume") {
845
+ return runResume(rest);
846
+ }
764
847
  if (command === "hooks") {
765
848
  const [sub, provider] = rest;
766
849
  if (sub !== "install" || !provider) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preziosiraffaele/agent-watch",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local observability layer for terminal-based AI coding agents.",
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "build": "bun build ./packages/aw/src/index.ts --outfile ./dist/aw.js --target bun && bun build ./packages/agent-watchd/src/index.ts --outfile ./dist/agent-watchd.js --target bun",
20
+ "link": "bun run build && bun link",
20
21
  "prepare": "bun run build",
21
22
  "test": "bun test",
22
23
  "lint": "eslint .",
@@ -36,6 +37,7 @@
36
37
  "@types/bun": "^1.1.0",
37
38
  "eslint": "^9.26.0",
38
39
  "eslint-config-prettier": "^10.1.8",
40
+ "eslint-plugin-jsdoc": "^62.9.0",
39
41
  "prettier": "^3.8.3",
40
42
  "typescript": "^5.6.0",
41
43
  "typescript-eslint": "^8.20.0"