@integrity-labs/agt-cli 0.28.1100 → 0.28.1102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/agt.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-BCIY67GX.js";
43
+ } from "../chunk-P5GRCGQ5.js";
44
44
  import {
45
45
  getProjectDir,
46
46
  isSessionResumeDisabled,
@@ -5474,7 +5474,7 @@ import { execFileSync, execSync } from "child_process";
5474
5474
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5475
5475
  import chalk18 from "chalk";
5476
5476
  import ora16 from "ora";
5477
- var cliVersion = true ? "0.28.1100" : "dev";
5477
+ var cliVersion = true ? "0.28.1102" : "dev";
5478
5478
  async function fetchLatestVersion() {
5479
5479
  const host2 = getHost();
5480
5480
  if (!host2) return null;
@@ -6734,7 +6734,7 @@ function handleError(err) {
6734
6734
  }
6735
6735
 
6736
6736
  // src/bin/agt.ts
6737
- var cliVersion2 = true ? "0.28.1100" : "dev";
6737
+ var cliVersion2 = true ? "0.28.1102" : "dev";
6738
6738
  var program = new Command();
6739
6739
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6740
6740
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -4214,6 +4214,402 @@ exit 0
4214
4214
  settings["hooks"] = hooks;
4215
4215
  writeFileSync4(settingsPath, JSON.stringify(settings, null, 2));
4216
4216
  }
4217
+ var LONG_COMMAND_BOUNDED_TIMEOUT_MS = 12e4;
4218
+ var LONG_COMMAND_HOOK_FILENAME = "agt-long-command-hook.sh";
4219
+ var LONG_COMMAND_HOOK_JS_FILENAME = "agt-long-command-hook.mjs";
4220
+ var LONG_COMMAND_HOOK_JS = String.raw`'use strict';
4221
+ // ENG-11393 long-command hook. Generated by provisionLongCommandHook; see index.ts.
4222
+ // Exit 2 + stderr refuses a foreground Bash call (Claude Code shows the reason);
4223
+ // exit 0 allows. Any internal error ALLOWS: a crashing guard must not block
4224
+ // every Bash call.
4225
+ const BOUND = __BOUND__;
4226
+ const SHELLS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh']);
4227
+ const TURBO_META = new Set(['login', 'logout', 'link', 'unlink', 'prune', 'daemon', 'telemetry', 'gen', 'generate', 'info', 'ls', 'query', 'bin', 'scan', 'completion', 'boundaries']);
4228
+ const PREFIX_CMDS = new Set(['sudo', 'nice', 'time', 'nohup', 'command', 'exec', 'stdbuf', 'ionice']);
4229
+ const KEYWORDS = new Set(['if', 'do', 'then', 'else', 'elif', '{', '!']);
4230
+ const FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?(:\d+)?$/;
4231
+
4232
+ let raw = '';
4233
+ process.stdin.setEncoding('utf8');
4234
+ process.stdin.on('data', (d) => { raw += d; });
4235
+ process.stdin.on('end', () => {
4236
+ let code = 0;
4237
+ try { code = main(raw); } catch (e) { code = 0; }
4238
+ process.exit(code);
4239
+ });
4240
+
4241
+ function main(text) {
4242
+ let input;
4243
+ try { input = JSON.parse(text); } catch (e) { return 0; }
4244
+ if (!input || input.tool_name !== 'Bash') return 0;
4245
+ const ti = input.tool_input || {};
4246
+ if (ti.run_in_background === true) return 0;
4247
+ const t = Number(ti.timeout);
4248
+ if (ti.timeout !== undefined && ti.timeout !== null && Number.isFinite(t) && t <= BOUND) return 0;
4249
+ const cmd = typeof ti.command === 'string' ? ti.command : '';
4250
+ if (!cmd) return 0;
4251
+ const reason = classify(cmd, 0);
4252
+ if (!reason) return 0;
4253
+ process.stderr.write('agt-long-command-hook: this looks like ' + reason + ', which can block you for minutes, and you cannot answer anyone while a foreground command runs. Re-run this exact command with the Bash tool parameter run_in_background: true. You are notified when it finishes, and you stay responsive meanwhile. Do NOT use a trailing & or nohup instead: that detaches it with no completion notification. If you are sure it is quick, pass an explicit timeout of ' + BOUND + ' ms or less.\n');
4254
+ return 2;
4255
+ }
4256
+
4257
+ // 1. Heredocs. A body is DATA unless the command that READS it is a shell
4258
+ // (Obi Wan 6159f6f97145 on #6845): each unquoted << operator is tied to the
4259
+ // command word of its own segment, not to the first word of the line, and a
4260
+ // "<<WORD" inside quotes opens nothing. A body piped into a shell
4261
+ // ("cat <<EOF | bash") is executed too (Obi Wan bf1bec45130a): every heredoc
4262
+ // in a pipeline becomes exec when a later stage of that pipeline is a shell.
4263
+ // The command word that reads a segment's stdin: shell keywords ("if", "do",
4264
+ // "!", ...) and prefixes (sudo, env, timeout) are stripped first, exactly as
4265
+ // classify does (Obi Wan 7a32094bc1df).
4266
+ function readerOf(seg) {
4267
+ let w = seg;
4268
+ while (w.length && KEYWORDS.has(w[0])) w = w.slice(1);
4269
+ return skipPrefixes(w)[0] || '';
4270
+ }
4271
+
4272
+ function heredocOps(line) {
4273
+ const ops = [];
4274
+ let q = null;
4275
+ let seg = [];
4276
+ let word = '';
4277
+ let pipeline = [];
4278
+ let piped = false;
4279
+ // Command substitutions opened INSIDE double quotes ("$(cat <<'EOF' ...").
4280
+ // In bash a quote inside $(...) does not close the outer one, and the heredoc
4281
+ // inside it is a real heredoc (Obi Wan 17d273c999ef: the usual shape of a
4282
+ // commit or PR body). Each entry saves the ENCLOSING segment and pipeline,
4283
+ // restored when the $( closes (CodeRabbit on #6845: in
4284
+ // cat <<EOF | sed "$(...)" | bash the substitution must not end the outer
4285
+ // pipeline, or the heredoc piped into bash reads as data), plus the
4286
+ // open-paren depth inside that $( .
4287
+ const subst = [];
4288
+ const endWord = () => { if (word) { seg.push(word); word = ''; } };
4289
+ const endSeg = (pipe) => {
4290
+ endWord();
4291
+ if (piped && SHELLS.has(base(readerOf(seg)))) for (const op of pipeline) op.exec = true;
4292
+ seg = [];
4293
+ piped = pipe;
4294
+ if (!pipe) pipeline = [];
4295
+ };
4296
+ for (let i = 0; i < line.length; i++) {
4297
+ const c = line[i];
4298
+ if (q === "'") { if (c === "'") q = null; else word += c; continue; }
4299
+ if (q === '"') {
4300
+ if (c === '\\' && i + 1 < line.length) { word += line[++i]; continue; }
4301
+ if (c === '$' && line[i + 1] === '(') {
4302
+ subst.push({ depth: 0, seg, word, pipeline, piped, q: '"' });
4303
+ seg = []; word = ''; pipeline = []; piped = false; q = null; i++;
4304
+ continue;
4305
+ }
4306
+ if (c === '"') q = null; else word += c;
4307
+ continue;
4308
+ }
4309
+ if (c === '\\' && i + 1 < line.length) { word += line[++i]; continue; }
4310
+ if (c === "'" || c === '"') { q = c; continue; }
4311
+ // An UNQUOTED $( keeps the enclosing pipeline too (CodeRabbit on #6845:
4312
+ // cat <<EOF | sed $(...) | bash), and returns to unquoted text on its ).
4313
+ if (c === '$' && line[i + 1] === '(') {
4314
+ subst.push({ depth: 0, seg, word, pipeline, piped, q: null });
4315
+ seg = []; word = ''; pipeline = []; piped = false; i++;
4316
+ continue;
4317
+ }
4318
+ if (subst.length && c === '(') { subst[subst.length - 1].depth++; endSeg(false); continue; }
4319
+ if (subst.length && c === ')') {
4320
+ endSeg(false);
4321
+ const top = subst[subst.length - 1];
4322
+ if (top.depth > 0) { top.depth--; continue; }
4323
+ subst.pop();
4324
+ // Back in the enclosing word, in the quote state it was opened from.
4325
+ seg = top.seg; word = top.word + '$()'; pipeline = top.pipeline; piped = top.piped; q = top.q;
4326
+ continue;
4327
+ }
4328
+ if (c === '<' && line[i + 1] === '<' && line[i + 2] !== '<' && line[i - 1] !== '<') {
4329
+ endWord();
4330
+ let j = i + 2;
4331
+ let dash = false;
4332
+ if (line[j] === '-') { dash = true; j++; }
4333
+ while (line[j] === ' ' || line[j] === '\t') j++;
4334
+ let qc = '';
4335
+ if (line[j] === "'" || line[j] === '"') { qc = line[j]; j++; }
4336
+ const m = /^[A-Za-z_][A-Za-z0-9_]*/.exec(line.slice(j));
4337
+ if (m) {
4338
+ j += m[0].length;
4339
+ if (qc && line[j] === qc) j++;
4340
+ const op = { word: m[0], dash, exec: SHELLS.has(base(readerOf(seg))) };
4341
+ ops.push(op);
4342
+ pipeline.push(op);
4343
+ i = j - 1;
4344
+ continue;
4345
+ }
4346
+ }
4347
+ if (c === '|') {
4348
+ if (line[i + 1] === '|') { i++; endSeg(false); continue; }
4349
+ if (line[i + 1] === '&') i++;
4350
+ endSeg(true);
4351
+ continue;
4352
+ }
4353
+ if (c === ';' || c === '&' || c === '(' || c === ')') { endSeg(false); continue; }
4354
+ if (c === ' ' || c === '\t') { endWord(); continue; }
4355
+ word += c;
4356
+ }
4357
+ endSeg(false);
4358
+ return ops;
4359
+ }
4360
+
4361
+ function splitHeredocs(cmd) {
4362
+ const lines = cmd.split('\n');
4363
+ const kept = [];
4364
+ const execBodies = [];
4365
+ let pending = [];
4366
+ let body = [];
4367
+ for (const line of lines) {
4368
+ if (pending.length > 0) {
4369
+ const cur = pending[0];
4370
+ const cmp = cur.dash ? line.replace(/^\t+/, '') : line;
4371
+ if (cmp === cur.word) {
4372
+ if (cur.exec) execBodies.push(body.join('\n'));
4373
+ body = [];
4374
+ pending.shift();
4375
+ } else {
4376
+ body.push(line);
4377
+ }
4378
+ continue;
4379
+ }
4380
+ kept.push(line);
4381
+ for (const op of heredocOps(line)) pending.push(op);
4382
+ }
4383
+ return { text: kept.join('\n'), execBodies };
4384
+ }
4385
+
4386
+ // 2. Segments: split on ; & | && || newline ( ) respecting quotes and escapes.
4387
+ function tokenize(text) {
4388
+ const segs = [];
4389
+ let words = [];
4390
+ let word = '';
4391
+ let inWord = false;
4392
+ let q = null;
4393
+ const endWord = () => { if (inWord) { words.push(word); word = ''; inWord = false; } };
4394
+ const endSeg = () => { endWord(); if (words.length) segs.push(words); words = []; };
4395
+ for (let i = 0; i < text.length; i++) {
4396
+ const c = text[i];
4397
+ if (q === "'") { if (c === "'") q = null; else word += c; continue; }
4398
+ if (q === '"') {
4399
+ if (c === '\\' && i + 1 < text.length) { word += text[++i]; continue; }
4400
+ if (c === '"') q = null; else word += c;
4401
+ continue;
4402
+ }
4403
+ if (c === '\\' && i + 1 < text.length) { if (text[i + 1] !== '\n') { word += text[i + 1]; inWord = true; } i++; continue; }
4404
+ if (c === "'" || c === '"') { q = c; inWord = true; continue; }
4405
+ if (c === '#' && !inWord) { while (i < text.length && text[i] !== '\n') i++; endSeg(); continue; }
4406
+ if (c === ';' || c === '&' || c === '|' || c === '\n' || c === '(' || c === ')') { endSeg(); continue; }
4407
+ if (c === ' ' || c === '\t') { endWord(); continue; }
4408
+ word += c; inWord = true;
4409
+ }
4410
+ endSeg();
4411
+ return segs;
4412
+ }
4413
+
4414
+ function base(w) { return String(w).split('/').pop(); }
4415
+
4416
+ // 3. Classify.
4417
+ function classify(cmd, depth) {
4418
+ if (depth > 4) return null;
4419
+ const { text, execBodies } = splitHeredocs(cmd);
4420
+ for (const b of execBodies) { const r = classify(b, depth + 1); if (r) return r; }
4421
+ let loop = 0;
4422
+ for (let words of tokenize(text)) {
4423
+ while (words.length && KEYWORDS.has(words[0])) words = words.slice(1);
4424
+ if (!words.length) continue;
4425
+ const head = words[0];
4426
+ if (head === 'for' || head === 'while' || head === 'until' || head === 'select') {
4427
+ loop++;
4428
+ if (head === 'for' || head === 'select') continue; // the rest is a word list, not a command
4429
+ words = words.slice(1);
4430
+ if (!words.length) continue;
4431
+ } else if (head === 'done') {
4432
+ if (loop > 0) loop--;
4433
+ continue;
4434
+ }
4435
+ const r = classifyCommand(words, loop, depth);
4436
+ if (r) return r;
4437
+ }
4438
+ return null;
4439
+ }
4440
+
4441
+ function skipPrefixes(words) {
4442
+ let w = words.slice();
4443
+ for (let guard = 0; guard < 20 && w.length; guard++) {
4444
+ const h = base(w[0]);
4445
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(w[0])) { w = w.slice(1); continue; }
4446
+ if (h === 'env') {
4447
+ // CodeRabbit on #6845: GNU env's -u/--unset NAME and -C/--chdir DIR take
4448
+ // a SEPARATE operand, which is not the command. The joined forms
4449
+ // (-uNAME, --unset=NAME, -CDIR, --chdir=DIR) carry their own; "--" ends
4450
+ // the options.
4451
+ w = w.slice(1);
4452
+ while (w.length) {
4453
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(w[0])) { w = w.slice(1); continue; }
4454
+ const f = w[0];
4455
+ if (!f.startsWith('-')) break;
4456
+ w = w.slice(1);
4457
+ if (f === '--') break;
4458
+ if ((f === '-u' || f === '--unset' || f === '-C' || f === '--chdir') && w.length) w = w.slice(1);
4459
+ }
4460
+ continue;
4461
+ }
4462
+ if (h === 'timeout') { w = w.slice(1); while (w.length && w[0].startsWith('-')) { const f = w[0]; w = w.slice(1); if ((f === '-s' || f === '-k' || f === '--signal' || f === '--kill-after') && w.length) w = w.slice(1); } if (w.length && /^[0-9.]+[smhd]?$/.test(w[0])) w = w.slice(1); continue; }
4463
+ // "command -v X" / "command -V X" only LOOKS X up (Obi Wan e9e75aab20fb).
4464
+ if (h === 'command' && (w[1] === '-v' || w[1] === '-V')) return [];
4465
+ if (PREFIX_CMDS.has(h)) { w = w.slice(1); while (w.length && w[0].startsWith('-')) { const f = w[0]; w = w.slice(1); if (h === 'nice' && f === '-n' && w.length) w = w.slice(1); } continue; }
4466
+ break;
4467
+ }
4468
+ return w;
4469
+ }
4470
+
4471
+ // GNU sleep also takes "inf"/"infinity" and float notation (1e3). Anything
4472
+ // it would accept that we cannot read is treated as unbounded rather than as
4473
+ // zero (Obi Wan 4d961cccc4ba); flags are ignored.
4474
+ function sleepSeconds(arg) {
4475
+ if (!arg || arg.startsWith('-')) return null;
4476
+ if (/^inf(inity)?[smhd]?$/i.test(arg)) return Infinity;
4477
+ const m = /^((?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?)([smhd]?)$/.exec(arg);
4478
+ if (!m) return Infinity;
4479
+ const n = Number(m[1]);
4480
+ return Number.isFinite(n) ? n * ({ '': 1, s: 1, m: 60, h: 3600, d: 86400 })[m[2]] : Infinity;
4481
+ }
4482
+
4483
+ function hasFileTarget(args) { return args.some((a) => FILE_RE.test(a)); }
4484
+
4485
+ function classifyCommand(words0, loop, depth) {
4486
+ let words = skipPrefixes(words0);
4487
+ if (!words.length) return null;
4488
+ let cw = base(words[0]);
4489
+ let args = words.slice(1);
4490
+
4491
+ // A shell given a command string: judge the string (bash -lc 'pnpm install').
4492
+ if (SHELLS.has(cw)) {
4493
+ const i = args.findIndex((a) => /^-[A-Za-z]*c[A-Za-z]*$/.test(a));
4494
+ if (i >= 0 && i + 1 < args.length) return classify(args[i + 1], depth + 1);
4495
+ return null;
4496
+ }
4497
+ if (cw === 'eval') return classify(args.join(' '), depth + 1);
4498
+
4499
+ // Runners that put the real command after them.
4500
+ if (cw === 'npx' || cw === 'bunx' || cw === 'corepack') {
4501
+ while (args.length && args[0].startsWith('-')) args = args.slice(1);
4502
+ if (!args.length) return null;
4503
+ return classifyCommand(args, loop, depth);
4504
+ }
4505
+
4506
+ if (cw === 'vitest' || cw === 'jest') {
4507
+ if (args.some((a) => a === '--version' || a === '-v' || a === '--help' || a === '-h')) return null;
4508
+ return hasFileTarget(args) ? null : 'a whole test suite';
4509
+ }
4510
+
4511
+ if (cw === 'pnpm' || cw === 'npm' || cw === 'yarn' || cw === 'bun') {
4512
+ let recursive = false;
4513
+ let firstAt = -1;
4514
+ const rest = [];
4515
+ for (let i = 0; i < args.length; i++) {
4516
+ const a = args[i];
4517
+ if (a === '-r' || a === '--recursive' || a === '--workspaces' || a === '-ws') { recursive = true; continue; }
4518
+ if (a === '--filter' || a === '-F' || a === '-C' || a === '--dir' || a === '--prefix' || a === '-w' && cw === 'npm' || a === '--workspace') { i++; continue; }
4519
+ if (a.startsWith('-')) continue;
4520
+ if (!rest.length) firstAt = i;
4521
+ rest.push(a);
4522
+ }
4523
+ let sub = rest[0];
4524
+ // "yarn --version" / "pnpm -v" / "npm --help": a quick lookup, not a bare
4525
+ // "yarn" install (Obi Wan e9e75aab20fb; the loop above dropped the flag).
4526
+ if (sub === undefined) {
4527
+ if (args.some((a) => a === '--version' || a === '-v' || a === '--help' || a === '-h')) return null;
4528
+ return cw === 'yarn' ? 'a dependency install' : null;
4529
+ }
4530
+ if ((sub === 'exec' || sub === 'dlx' || sub === 'x') && rest.length > 1) return classifyCommand(args.slice(args.indexOf(rest[1], firstAt + 1)), loop, depth);
4531
+ const viaRun = sub === 'run' || sub === 'run-script';
4532
+ if (viaRun) sub = rest[1] === undefined ? 'run' : rest[1];
4533
+ // A file after a package "test" SCRIPT does not narrow the run unless the
4534
+ // script forwards its args, and this repo's root one does not (it runs the
4535
+ // whole turbo suite). Only a direct runner (vitest/jest, exec/dlx, a
4536
+ // workspace bin) earns the single-file exemption (Obi Wan f471dc98a67b).
4537
+ if (sub === 'test' || sub === 't') return 'a whole test suite';
4538
+ if (sub === 'install' || sub === 'i' || sub === 'ci' || sub === 'add') return 'a dependency install';
4539
+ if (sub === 'vitest' || sub === 'jest') return hasFileTarget(rest.slice(1)) ? null : 'a whole test suite';
4540
+ // Obi Wan 444c7455aaad: in a turbo monorepo a root "pnpm build" /
4541
+ // "pnpm typecheck" is a workspace-wide build. A quick one in a single
4542
+ // package passes with an explicit short timeout.
4543
+ if (sub === 'build' || sub === 'typecheck' || sub === 'type-check') return 'a workspace-wide build or typecheck';
4544
+ if (recursive) return 'a workspace-wide build or typecheck';
4545
+ // Obi Wan c0646feacb29: "pnpm turbo test", "yarn tsc -b", "pnpm nx run-many"
4546
+ // run a workspace binary, so judge the binary as if it were run directly.
4547
+ // Its own flags must survive (rest drops them): re-read from the original args.
4548
+ if (!viaRun) return classifyCommand(args.slice(firstAt), loop, depth);
4549
+ return null;
4550
+ }
4551
+
4552
+ if (cw === 'tsc') return args.some((a) => a === '-b' || a === '--build') ? 'a workspace-wide build or typecheck' : null;
4553
+ if (cw === 'turbo') {
4554
+ // "turbo build" runs a task exactly like "turbo run build" (Obi Wan 444c7455aaad).
4555
+ const first = args.find((a) => !a.startsWith('-'));
4556
+ if (first === undefined || TURBO_META.has(first)) return null;
4557
+ return 'a workspace-wide build or typecheck';
4558
+ }
4559
+ if (cw === 'nx') return args.includes('run-many') ? 'a workspace-wide build or typecheck' : null;
4560
+
4561
+ if (cw === 'gh') {
4562
+ if (args[0] === 'run' && args[1] === 'watch') return 'a watch or poll loop';
4563
+ if (args[0] === 'pr' && args[1] === 'checks' && args.includes('--watch')) return 'a watch or poll loop';
4564
+ return null;
4565
+ }
4566
+ if (cw === 'watch') return 'a watch or poll loop';
4567
+ if (cw === 'tail' && args.some((a) => a === '-f' || a === '-F' || a === '--follow' || /^-[A-Za-z]*[fF]$/.test(a))) return 'a watch or poll loop';
4568
+ if (cw === 'inotifywait' && args.some((a) => a === '-m' || a === '--monitor')) return 'a watch or poll loop';
4569
+
4570
+ if (cw === 'sleep') {
4571
+ if (loop > 0) return 'a watch or poll loop';
4572
+ const s = args.reduce((acc, a) => { const v = sleepSeconds(a); return v === null ? acc : acc + v; }, 0);
4573
+ return s >= 30 ? 'a watch or poll loop' : null;
4574
+ }
4575
+ return null;
4576
+ }
4577
+ `;
4578
+ var LONG_COMMAND_MAX_FOREGROUND_TIMEOUT_MS = 18e4;
4579
+ function provisionLongCommandHook(codeName) {
4580
+ const projectDir = getProjectDir2(codeName);
4581
+ const claudeDir = join4(projectDir, ".claude");
4582
+ mkdirSync3(claudeDir, { recursive: true });
4583
+ const hookScriptPath = join4(claudeDir, LONG_COMMAND_HOOK_FILENAME);
4584
+ const jsPath = join4(claudeDir, LONG_COMMAND_HOOK_JS_FILENAME);
4585
+ writeFileSync4(jsPath, LONG_COMMAND_HOOK_JS.replace("__BOUND__", String(LONG_COMMAND_BOUNDED_TIMEOUT_MS)), { mode: 420 });
4586
+ const hookScript = [
4587
+ "#!/usr/bin/env bash",
4588
+ "# ENG-11393: refuse known-slow foreground shell commands; see index.ts.",
4589
+ "# Fail open: no node, or any error in the matcher, allows the call.",
4590
+ "command -v node >/dev/null 2>&1 || exit 0",
4591
+ `exec node '${jsPath.replace(/'/g, `'\\''`)}'`
4592
+ ].join("\n") + "\n";
4593
+ writeFileSync4(hookScriptPath, hookScript, { mode: 493 });
4594
+ const settingsPath = join4(claudeDir, "settings.local.json");
4595
+ let settings = {};
4596
+ try {
4597
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
4598
+ } catch {
4599
+ }
4600
+ const hooks = settings["hooks"] ?? {};
4601
+ const groups = Array.isArray(hooks["PreToolUse"]) ? hooks["PreToolUse"] : [];
4602
+ const already = groups.some((g) => (g.hooks ?? []).some((h) => h.command === hookScriptPath));
4603
+ if (!already) {
4604
+ groups.push({ matcher: "Bash", hooks: [{ type: "command", command: hookScriptPath }] });
4605
+ }
4606
+ hooks["PreToolUse"] = groups;
4607
+ settings["hooks"] = hooks;
4608
+ const env = settings["env"] ?? {};
4609
+ env["BASH_MAX_TIMEOUT_MS"] = String(LONG_COMMAND_MAX_FOREGROUND_TIMEOUT_MS);
4610
+ settings["env"] = env;
4611
+ writeFileSync4(settingsPath, JSON.stringify(settings, null, 2));
4612
+ }
4217
4613
  function provisionOrientHook(codeName) {
4218
4614
  const projectDir = getProjectDir2(codeName);
4219
4615
  const claudeDir = join4(projectDir, ".claude");
@@ -7122,7 +7518,7 @@ function exchangeFailureKind(err) {
7122
7518
  }
7123
7519
 
7124
7520
  // src/lib/api-client.ts
7125
- var agtCliVersion = true ? "0.28.1100" : "dev";
7521
+ var agtCliVersion = true ? "0.28.1102" : "dev";
7126
7522
  var lastConfigHash = null;
7127
7523
  function setConfigHash(hash) {
7128
7524
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -12008,6 +12404,7 @@ export {
12008
12404
  provisionIsolationHook,
12009
12405
  provisionAutoKanbanProgressHook,
12010
12406
  provisionChannelProgressHook,
12407
+ provisionLongCommandHook,
12011
12408
  provisionOrientHook,
12012
12409
  provisionPreCompactHook,
12013
12410
  provisionSessionStateHook,
@@ -12095,4 +12492,4 @@ export {
12095
12492
  managerInstallSystemUnitCommand,
12096
12493
  managerUninstallSystemUnitCommand
12097
12494
  };
12098
- //# sourceMappingURL=chunk-BCIY67GX.js.map
12495
+ //# sourceMappingURL=chunk-P5GRCGQ5.js.map