squadrant 0.12.1 → 0.13.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.
package/dist/index.js CHANGED
@@ -2700,6 +2700,12 @@ var init_crew_spawn = __esm({
2700
2700
  }
2701
2701
  });
2702
2702
 
2703
+ // packages/core/dist/lifecycle-source.js
2704
+ var init_lifecycle_source = __esm({
2705
+ "packages/core/dist/lifecycle-source.js"() {
2706
+ }
2707
+ });
2708
+
2703
2709
  // packages/core/dist/index.js
2704
2710
  var init_dist2 = __esm({
2705
2711
  "packages/core/dist/index.js"() {
@@ -2732,6 +2738,7 @@ var init_dist2 = __esm({
2732
2738
  init_launch_workspace();
2733
2739
  init_side_session();
2734
2740
  init_crew_spawn();
2741
+ init_lifecycle_source();
2735
2742
  }
2736
2743
  });
2737
2744
 
@@ -3561,6 +3568,345 @@ var init_daemon_cmux = __esm({
3561
3568
  }
3562
3569
  });
3563
3570
 
3571
+ // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
3572
+ import { join as join13 } from "path";
3573
+ import { homedir as homedir8 } from "os";
3574
+ import { watch, readdirSync as readdirSync3, readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3575
+ function parseLifecycleState(s) {
3576
+ if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
3577
+ return s;
3578
+ }
3579
+ return "unknown";
3580
+ }
3581
+ function defaultIsPidAlive(pid) {
3582
+ try {
3583
+ process.kill(pid, 0);
3584
+ return true;
3585
+ } catch {
3586
+ return false;
3587
+ }
3588
+ }
3589
+ function defaultListFiles(dir) {
3590
+ try {
3591
+ return readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
3592
+ } catch {
3593
+ return [];
3594
+ }
3595
+ }
3596
+ function defaultReadFile(path29) {
3597
+ try {
3598
+ return readFileSync8(path29, "utf-8");
3599
+ } catch {
3600
+ return void 0;
3601
+ }
3602
+ }
3603
+ function defaultWatchDir(dir, cb) {
3604
+ const w = watch(dir, (_event, filename) => {
3605
+ if (typeof filename === "string" && filename.endsWith("-hook-sessions.json")) {
3606
+ cb();
3607
+ }
3608
+ });
3609
+ return () => w.close();
3610
+ }
3611
+ var CmuxStoreSource;
3612
+ var init_cmux_store_source = __esm({
3613
+ "packages/workspaces/dist/cmux-daemon/cmux-store-source.js"() {
3614
+ CmuxStoreSource = class {
3615
+ name = "cmux-store";
3616
+ stateDir;
3617
+ debounceMs;
3618
+ isPidAlive;
3619
+ listFiles;
3620
+ readFile;
3621
+ fileExists;
3622
+ watchDir;
3623
+ scheduleTimer;
3624
+ cancelTimer;
3625
+ log;
3626
+ deps;
3627
+ stopWatcher;
3628
+ debounceTimer;
3629
+ /** taskId → last reported snapshot (for snapshot() liveness floor). */
3630
+ cache = /* @__PURE__ */ new Map();
3631
+ constructor(opts = {}) {
3632
+ this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join13(homedir8(), ".cmuxterm");
3633
+ this.debounceMs = opts.debounceMs ?? 50;
3634
+ this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
3635
+ this.listFiles = opts.listFiles ?? defaultListFiles;
3636
+ this.readFile = opts.readFile ?? defaultReadFile;
3637
+ this.fileExists = opts.fileExists ?? existsSync10;
3638
+ this.watchDir = opts.watchDir ?? defaultWatchDir;
3639
+ this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
3640
+ this.cancelTimer = opts.cancelTimer ?? clearTimeout;
3641
+ this.log = opts.log ?? (() => {
3642
+ });
3643
+ }
3644
+ start(deps) {
3645
+ this.deps = deps;
3646
+ this.scan();
3647
+ try {
3648
+ this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced());
3649
+ } catch (e) {
3650
+ this.log(`cmux-store: failed to watch ${this.stateDir}: ${e.message}`);
3651
+ }
3652
+ }
3653
+ stop() {
3654
+ if (this.debounceTimer !== void 0) {
3655
+ this.cancelTimer(this.debounceTimer);
3656
+ this.debounceTimer = void 0;
3657
+ }
3658
+ this.stopWatcher?.();
3659
+ this.stopWatcher = void 0;
3660
+ this.deps = void 0;
3661
+ this.cache.clear();
3662
+ }
3663
+ /** Returns the last-reported snapshot for a known crew (liveness floor). */
3664
+ snapshot(taskId) {
3665
+ return this.cache.get(taskId);
3666
+ }
3667
+ // ── private ─────────────────────────────────────────────────────────────────
3668
+ scheduleDebounced() {
3669
+ if (this.debounceTimer !== void 0) {
3670
+ this.cancelTimer(this.debounceTimer);
3671
+ }
3672
+ this.debounceTimer = this.scheduleTimer(() => {
3673
+ this.debounceTimer = void 0;
3674
+ this.scan();
3675
+ }, this.debounceMs);
3676
+ }
3677
+ scan() {
3678
+ if (!this.deps)
3679
+ return;
3680
+ for (const filename of this.listFiles(this.stateDir)) {
3681
+ this.scanFile(filename);
3682
+ }
3683
+ }
3684
+ scanFile(filename) {
3685
+ const deps = this.deps;
3686
+ const filePath = join13(this.stateDir, filename);
3687
+ const lockPath = `${filePath}.lock`;
3688
+ if (this.fileExists(lockPath)) {
3689
+ this.log(`cmux-store: skipping ${filename} (locked)`);
3690
+ return;
3691
+ }
3692
+ const raw = this.readFile(filePath);
3693
+ if (!raw)
3694
+ return;
3695
+ let parsed;
3696
+ try {
3697
+ parsed = JSON.parse(raw);
3698
+ } catch {
3699
+ this.log(`cmux-store: failed to parse ${filename}`);
3700
+ return;
3701
+ }
3702
+ for (const session of Object.values(parsed.sessions ?? {})) {
3703
+ this.processSession(session, deps);
3704
+ }
3705
+ }
3706
+ processSession(session, deps) {
3707
+ if (!session.sessionId || !session.cwd || typeof session.pid !== "number")
3708
+ return;
3709
+ const hint = {
3710
+ cwd: session.cwd,
3711
+ pid: session.pid,
3712
+ sessionId: session.sessionId
3713
+ };
3714
+ const resolved = deps.resolve(hint);
3715
+ if (!resolved)
3716
+ return;
3717
+ let alive = this.isPidAlive(session.pid);
3718
+ if (!alive && session.isRestorable === true && session.agentLifecycle === "idle") {
3719
+ alive = true;
3720
+ }
3721
+ const snap = {
3722
+ taskId: resolved.id,
3723
+ state: parseLifecycleState(session.agentLifecycle),
3724
+ alive,
3725
+ // "agent": the store carries the agent's own reported lifecycle state,
3726
+ // not a scan inference. needsInput from the store is authoritative.
3727
+ origin: "agent",
3728
+ at: Math.floor((session.updatedAt ?? 0) * 1e3),
3729
+ pid: session.pid,
3730
+ ...session.lastBody ? { detail: { note: session.lastBody } } : {}
3731
+ };
3732
+ this.cache.set(resolved.id, snap);
3733
+ deps.report(snap);
3734
+ }
3735
+ };
3736
+ }
3737
+ });
3738
+
3739
+ // packages/workspaces/dist/native-hooks/native-hook-source.js
3740
+ import { join as join14 } from "path";
3741
+ import { homedir as homedir9 } from "os";
3742
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
3743
+ function installClaudeHooks(opts = {}) {
3744
+ const settingsPath = opts.settingsPath ?? join14(homedir9(), ".claude", "settings.json");
3745
+ const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
3746
+ const readFile6 = opts.readFile ?? defaultReadFile2;
3747
+ const writeFile5 = opts.writeFile ?? defaultWriteFile;
3748
+ const log = opts.log ?? (() => {
3749
+ });
3750
+ let settings = {};
3751
+ const raw = readFile6(settingsPath);
3752
+ if (raw) {
3753
+ try {
3754
+ settings = JSON.parse(raw);
3755
+ } catch {
3756
+ log(`native-hook: failed to parse ${settingsPath} \u2014 hooks section will be reset`);
3757
+ }
3758
+ }
3759
+ if (typeof settings.hooks !== "object" || settings.hooks === null || Array.isArray(settings.hooks)) {
3760
+ settings.hooks = {};
3761
+ }
3762
+ const hooks = settings.hooks;
3763
+ let changed = false;
3764
+ for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
3765
+ if (!Array.isArray(hooks[eventName])) {
3766
+ hooks[eventName] = [];
3767
+ }
3768
+ const entries = hooks[eventName];
3769
+ const command = `${hookCmd} claude ${sub}`;
3770
+ const hookMatcher = matcher ?? "";
3771
+ const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
3772
+ if (!alreadyPresent) {
3773
+ entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
3774
+ changed = true;
3775
+ }
3776
+ }
3777
+ if (changed) {
3778
+ writeFile5(settingsPath, JSON.stringify(settings, null, 2));
3779
+ }
3780
+ return settingsPath;
3781
+ }
3782
+ function mapSubToLifecycle(sub) {
3783
+ switch (sub) {
3784
+ case "session-start":
3785
+ return "running";
3786
+ case "prompt-submit":
3787
+ return "running";
3788
+ case "pre-tool-use":
3789
+ return "running";
3790
+ case "stop":
3791
+ return "idle";
3792
+ case "notification":
3793
+ return "needsInput";
3794
+ case "ask-question":
3795
+ return "needsInput";
3796
+ case "session-end":
3797
+ return "session-end";
3798
+ default:
3799
+ return null;
3800
+ }
3801
+ }
3802
+ function extractDetail(sub, payload) {
3803
+ if (!payload || typeof payload !== "object")
3804
+ return void 0;
3805
+ const p = payload;
3806
+ if (sub === "notification") {
3807
+ const note = typeof p.message === "string" ? p.message : void 0;
3808
+ return note ? { note } : void 0;
3809
+ }
3810
+ if (sub === "pre-tool-use") {
3811
+ const tool = typeof p.tool_name === "string" ? p.tool_name : void 0;
3812
+ return tool ? { tool } : void 0;
3813
+ }
3814
+ return void 0;
3815
+ }
3816
+ function defaultReadFile2(path29) {
3817
+ try {
3818
+ return readFileSync9(path29, "utf-8");
3819
+ } catch {
3820
+ return void 0;
3821
+ }
3822
+ }
3823
+ function defaultWriteFile(path29, content) {
3824
+ mkdirSync6(path29.replace(/\/[^/]+$/, ""), { recursive: true });
3825
+ writeFileSync7(path29, content, "utf-8");
3826
+ }
3827
+ var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
3828
+ var init_native_hook_source = __esm({
3829
+ "packages/workspaces/dist/native-hooks/native-hook-source.js"() {
3830
+ CLAUDE_HOOK_EVENTS = [
3831
+ ["SessionStart", "session-start"],
3832
+ ["UserPromptSubmit", "prompt-submit"],
3833
+ ["PreToolUse", "pre-tool-use"],
3834
+ ["Stop", "stop"],
3835
+ ["Notification", "notification"],
3836
+ ["PreToolUse", "ask-question", "AskUserQuestion"],
3837
+ ["SessionEnd", "session-end"]
3838
+ ];
3839
+ DEFAULT_HOOK_CMD = "squadrant hooks";
3840
+ NativeHookSource = class {
3841
+ name = "native-hook";
3842
+ hookInstall;
3843
+ log;
3844
+ deps;
3845
+ /** taskId → last-reported snapshot, for snapshot() liveness floor. */
3846
+ cache = /* @__PURE__ */ new Map();
3847
+ constructor(opts = {}) {
3848
+ this.hookInstall = opts.hookInstall ?? {};
3849
+ this.log = opts.log ?? (() => {
3850
+ });
3851
+ }
3852
+ start(deps) {
3853
+ this.deps = deps;
3854
+ }
3855
+ stop() {
3856
+ this.deps = void 0;
3857
+ this.cache.clear();
3858
+ }
3859
+ /** Returns the last-reported snapshot for a known crew (liveness floor poll). */
3860
+ snapshot(taskId) {
3861
+ return this.cache.get(taskId);
3862
+ }
3863
+ /**
3864
+ * Install squadrant-owned hooks into ~/.claude/settings.json.
3865
+ * Idempotent — safe to call on every project init or crew spawn.
3866
+ * Returns the path to the settings file.
3867
+ */
3868
+ install() {
3869
+ return installClaudeHooks(this.hookInstall);
3870
+ }
3871
+ /**
3872
+ * Receive a lifecycle hook event from the daemon and report a LifecycleSnapshot.
3873
+ *
3874
+ * The daemon's 'squadrant hooks claude <sub>' CLI subcommand calls this after
3875
+ * reading SQUADRANT_CREW_TASK_ID from the hook's process environment — the only
3876
+ * collision-proof correlation key (blueprint §2.2 priority 1).
3877
+ *
3878
+ * @param sub Sub-event alias: "session-start" | "prompt-submit" | "stop" | …
3879
+ * @param taskId SQUADRANT_CREW_TASK_ID extracted from the hook process env.
3880
+ * @param pid Optional: OS pid from the hook's process env or argv.
3881
+ * @param payload Optional: parsed JSON payload from hook stdin (best-effort detail).
3882
+ */
3883
+ handleHook(sub, taskId, pid, payload) {
3884
+ if (!this.deps)
3885
+ return;
3886
+ const mapped = mapSubToLifecycle(sub);
3887
+ if (mapped === null) {
3888
+ this.log(`native-hook: unknown sub '${sub}' for task ${taskId} \u2014 ignored`);
3889
+ return;
3890
+ }
3891
+ const isSessionEnd = mapped === "session-end";
3892
+ const state = isSessionEnd ? "unknown" : mapped;
3893
+ const detail = extractDetail(sub, payload);
3894
+ const snap = {
3895
+ taskId,
3896
+ state,
3897
+ alive: !isSessionEnd,
3898
+ origin: "agent",
3899
+ at: Date.now(),
3900
+ ...pid !== void 0 ? { pid } : {},
3901
+ ...detail ? { detail } : {}
3902
+ };
3903
+ this.cache.set(taskId, snap);
3904
+ this.deps.report(snap);
3905
+ }
3906
+ };
3907
+ }
3908
+ });
3909
+
3564
3910
  // packages/workspaces/dist/crew-pane.js
3565
3911
  import net from "net";
3566
3912
  async function settleInputBox(runtime, pane) {
@@ -3692,7 +4038,9 @@ var dist_exports = {};
3692
4038
  __export(dist_exports, {
3693
4039
  CMUX_TIMEOUT: () => CMUX_TIMEOUT,
3694
4040
  CmuxEventsBridge: () => CmuxEventsBridge,
4041
+ CmuxStoreSource: () => CmuxStoreSource,
3695
4042
  DaemonCmux: () => DaemonCmux,
4043
+ NativeHookSource: () => NativeHookSource,
3696
4044
  NotifierRegistry: () => NotifierRegistry,
3697
4045
  RuntimeRegistry: () => RuntimeRegistry,
3698
4046
  WorkspaceRegistry: () => WorkspaceRegistry,
@@ -3705,8 +4053,10 @@ __export(dist_exports, {
3705
4053
  deriveRunState: () => deriveRunState,
3706
4054
  findCrew: () => findCrew,
3707
4055
  getFreePort: () => getFreePort,
4056
+ installClaudeHooks: () => installClaudeHooks,
3708
4057
  isInsideCmux: () => isInsideCmux,
3709
4058
  listProjectCrews: () => listProjectCrews,
4059
+ mapSubToLifecycle: () => mapSubToLifecycle,
3710
4060
  resolveCaptainWorkspace: () => resolveCaptainWorkspace,
3711
4061
  sendFirstTurnWhenReady: () => sendFirstTurnWhenReady
3712
4062
  });
@@ -3717,6 +4067,8 @@ var init_dist3 = __esm({
3717
4067
  init_workspaces2();
3718
4068
  init_events_bridge();
3719
4069
  init_daemon_cmux();
4070
+ init_cmux_store_source();
4071
+ init_native_hook_source();
3720
4072
  init_crew_pane();
3721
4073
  }
3722
4074
  });
@@ -4646,13 +4998,99 @@ var init_app_server_client = __esm({
4646
4998
  }
4647
4999
  });
4648
5000
 
5001
+ // packages/agents/dist/codex/codex-app-server-source.js
5002
+ function toSnapshot(ev) {
5003
+ const now = Date.now();
5004
+ switch (ev.type) {
5005
+ // ── running: a turn is live ──────────────────────────────────────────────
5006
+ case "task.started":
5007
+ case "task.reattached":
5008
+ case "task.turn.started":
5009
+ case "task.delta":
5010
+ case "task.progress":
5011
+ return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
5012
+ // ── idle: turn ended, crew alive, awaiting next input ────────────────────
5013
+ // task.failed: the turn ended with an error, but the crew process is alive.
5014
+ // task.session.ended: process is gone (alive:false) — signals liveness loss.
5015
+ case "task.turn.completed":
5016
+ return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
5017
+ case "task.failed":
5018
+ return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
5019
+ case "task.session.ended":
5020
+ return { taskId: ev.id, state: "idle", alive: false, origin: "agent", at: now };
5021
+ // ── needsInput: crew is blocked on a human ───────────────────────────────
5022
+ case "task.approval.requested":
5023
+ return {
5024
+ taskId: ev.id,
5025
+ state: "needsInput",
5026
+ alive: true,
5027
+ origin: "agent",
5028
+ at: now,
5029
+ detail: { note: ev.question, reason: ev.kind }
5030
+ };
5031
+ case "task.input.requested":
5032
+ return {
5033
+ taskId: ev.id,
5034
+ state: "needsInput",
5035
+ alive: true,
5036
+ origin: "agent",
5037
+ at: now,
5038
+ detail: { note: ev.question }
5039
+ };
5040
+ // ── terminal / notify-only — ignored ────────────────────────────────────
5041
+ // task.done, task.blocked, task.cancelled: terminal state from crew signal only.
5042
+ // task.session, task.stalled, task.quiet, task.idle, task.timeout, etc.: no-op.
5043
+ default:
5044
+ return null;
5045
+ }
5046
+ }
5047
+ var CodexAppServerSource;
5048
+ var init_codex_app_server_source = __esm({
5049
+ "packages/agents/dist/codex/codex-app-server-source.js"() {
5050
+ CodexAppServerSource = class {
5051
+ name = "codex-appserver";
5052
+ deps;
5053
+ /** taskId → last reported snapshot (for snapshot() liveness floor). */
5054
+ cache = /* @__PURE__ */ new Map();
5055
+ start(deps) {
5056
+ this.deps = deps;
5057
+ }
5058
+ stop() {
5059
+ this.deps = void 0;
5060
+ this.cache.clear();
5061
+ }
5062
+ /** Returns the last-reported snapshot for a known crew (liveness floor). */
5063
+ snapshot(taskId) {
5064
+ return this.cache.get(taskId);
5065
+ }
5066
+ /**
5067
+ * Feed a ControlEvent from CodexInteractiveDriver into this source.
5068
+ * The daemon wires: emit = (ev) => { source.observe(ev); handle(ev); }
5069
+ *
5070
+ * All events that carry lifecycle meaning for a codex crew are mapped to a
5071
+ * LifecycleSnapshot and reported. Events that are terminal signals (task.done,
5072
+ * task.cancelled, task.blocked) or notify-only (task.stalled, task.quiet, etc.)
5073
+ * are ignored — terminal state still comes exclusively from `squadrant crew signal`
5074
+ * (anti-#2576 invariant).
5075
+ */
5076
+ observe(ev) {
5077
+ const snap = toSnapshot(ev);
5078
+ if (!snap || !this.deps)
5079
+ return;
5080
+ this.cache.set(snap.taskId, snap);
5081
+ this.deps.report(snap);
5082
+ }
5083
+ };
5084
+ }
5085
+ });
5086
+
4649
5087
  // packages/agents/dist/codex/config.js
4650
5088
  import { readFile as readFile5 } from "fs/promises";
4651
- import { homedir as homedir9 } from "os";
4652
- import { join as join14 } from "path";
5089
+ import { homedir as homedir11 } from "os";
5090
+ import { join as join16 } from "path";
4653
5091
  async function resolveCodexModel() {
4654
- const home = process.env["CODEX_HOME"] ?? join14(homedir9(), ".codex");
4655
- const configPath = join14(home, "config.toml");
5092
+ const home = process.env["CODEX_HOME"] ?? join16(homedir11(), ".codex");
5093
+ const configPath = join16(home, "config.toml");
4656
5094
  let text;
4657
5095
  try {
4658
5096
  text = await readFile5(configPath, "utf8");
@@ -5173,9 +5611,9 @@ var init_sse_bridge = __esm({
5173
5611
 
5174
5612
  // packages/agents/dist/interactive/claude.js
5175
5613
  import { execSync as execSync7 } from "child_process";
5176
- import { readFileSync as readFileSync8 } from "fs";
5177
- import { homedir as homedir10 } from "os";
5178
- import { join as join15 } from "path";
5614
+ import { readFileSync as readFileSync10 } from "fs";
5615
+ import { homedir as homedir12 } from "os";
5616
+ import { join as join17 } from "path";
5179
5617
  function probeClaudeSettingsFlag() {
5180
5618
  try {
5181
5619
  const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
@@ -5226,11 +5664,11 @@ function deriveTranscriptPath(sessionId, cwd) {
5226
5664
  if (!sessionId || !cwd)
5227
5665
  return null;
5228
5666
  const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5229
- return join15(homedir10(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
5667
+ return join17(homedir12(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
5230
5668
  }
5231
5669
  function readLastAssistantText(transcriptPath) {
5232
5670
  try {
5233
- const raw = readFileSync8(transcriptPath, "utf-8");
5671
+ const raw = readFileSync10(transcriptPath, "utf-8");
5234
5672
  const lines = raw.split(/\r?\n/);
5235
5673
  for (let i = lines.length - 1; i >= 0; i--) {
5236
5674
  const line = lines[i].trim();
@@ -5347,6 +5785,18 @@ function classifyPaneTail(tail) {
5347
5785
  }
5348
5786
  return { kind: "approval", text: "Crew is awaiting permission approval." };
5349
5787
  }
5788
+ const hasPickerFooter = cleaned.some((c) => c != null && PICKER_FOOTER_RE.test(c));
5789
+ if (options.length >= 2 && hasPickerFooter) {
5790
+ const firstOptCi = options[0].ci;
5791
+ for (let i = firstOptCi - 1; i >= 0; i--) {
5792
+ const c = cleaned[i];
5793
+ if (c == null)
5794
+ continue;
5795
+ if (c.endsWith("?"))
5796
+ return { kind: "question", text: c };
5797
+ }
5798
+ return { kind: "question", text: "Crew is awaiting a choice." };
5799
+ }
5350
5800
  const region = cleaned.filter((c) => c != null).join("\n");
5351
5801
  const q = detectTrailingQuestion(region);
5352
5802
  if (q)
@@ -5374,7 +5824,7 @@ function stripChrome(raw) {
5374
5824
  return null;
5375
5825
  return trimmed;
5376
5826
  }
5377
- var ERROR_BANNER_RE, OPTION_RE, PURE_CHROME_RE, STATUS_LINE_RE;
5827
+ var ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE;
5378
5828
  var init_pane_classifier = __esm({
5379
5829
  "packages/agents/dist/interactive/pane-classifier.js"() {
5380
5830
  init_claude2();
@@ -5388,6 +5838,7 @@ var init_pane_classifier = __esm({
5388
5838
  /\bmaximum\s+retries\b/i
5389
5839
  ];
5390
5840
  OPTION_RE = /^[❯>›]?\s*(\d+)\.\s+(.*\S)\s*$/;
5841
+ PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
5391
5842
  PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
5392
5843
  STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
5393
5844
  }
@@ -5618,6 +6069,7 @@ var dist_exports2 = {};
5618
6069
  __export(dist_exports2, {
5619
6070
  AppServerClient: () => AppServerClient,
5620
6071
  CapabilityRegistry: () => CapabilityRegistry,
6072
+ CodexAppServerSource: () => CodexAppServerSource,
5621
6073
  CodexInteractiveDriver: () => CodexInteractiveDriver,
5622
6074
  HEADLESS_ERROR_TAIL: () => HEADLESS_ERROR_TAIL,
5623
6075
  MARKER_END: () => MARKER_END,
@@ -5656,6 +6108,7 @@ var init_dist4 = __esm({
5656
6108
  init_drivers();
5657
6109
  init_projection2();
5658
6110
  init_app_server_client();
6111
+ init_codex_app_server_source();
5659
6112
  init_driver();
5660
6113
  init_normalize();
5661
6114
  init_sse_bridge();
@@ -5672,11 +6125,11 @@ var init_dist4 = __esm({
5672
6125
  // packages/cli/src/index.ts
5673
6126
  init_dist();
5674
6127
  init_dist2();
5675
- import { Command as Command28 } from "commander";
5676
- import { readFileSync as readFileSync11, existsSync as existsSync11, writeFileSync as writeFileSync9 } from "fs";
6128
+ import { Command as Command29 } from "commander";
6129
+ import { readFileSync as readFileSync13, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "fs";
5677
6130
  import { fileURLToPath as fileURLToPath6 } from "url";
5678
- import { dirname as dirname7, join as join23 } from "path";
5679
- import { homedir as homedir15 } from "os";
6131
+ import { dirname as dirname7, join as join26 } from "path";
6132
+ import { homedir as homedir18 } from "os";
5680
6133
 
5681
6134
  // packages/cli/src/commands/doctor.ts
5682
6135
  init_dist();
@@ -5693,10 +6146,10 @@ import chalk3 from "chalk";
5693
6146
  // packages/cli/src/commands/health-view.ts
5694
6147
  init_dist2();
5695
6148
  init_dist2();
5696
- import { homedir as homedir8 } from "os";
5697
- import { join as join13 } from "path";
6149
+ import { homedir as homedir10 } from "os";
6150
+ import { join as join15 } from "path";
5698
6151
  import chalk2 from "chalk";
5699
- var SOCK = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
6152
+ var SOCK = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5700
6153
  async function queryHealth(project) {
5701
6154
  try {
5702
6155
  const reply = await sendRequest(SOCK, { kind: "health", project });
@@ -6530,9 +6983,9 @@ init_dist4();
6530
6983
  import { Command as Command8 } from "commander";
6531
6984
  import { createConnection as createConnection3 } from "net";
6532
6985
  import { randomUUID as randomUUID4 } from "crypto";
6533
- import { homedir as homedir12 } from "os";
6534
- import { join as join17 } from "path";
6535
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
6986
+ import { homedir as homedir14 } from "os";
6987
+ import { join as join19 } from "path";
6988
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
6536
6989
 
6537
6990
  // packages/cli/src/commands/crew-output.ts
6538
6991
  function tailLines(text, maxLines = 40, maxBytes = 4096) {
@@ -6590,11 +7043,11 @@ init_dist2();
6590
7043
  import { Command as Command6 } from "commander";
6591
7044
  import chalk8 from "chalk";
6592
7045
  import { createConnection as createConnection2 } from "net";
6593
- import { homedir as homedir11 } from "os";
6594
- import { join as join16 } from "path";
7046
+ import { homedir as homedir13 } from "os";
7047
+ import { join as join18 } from "path";
6595
7048
  import { createInterface } from "readline";
6596
7049
  function socketPath() {
6597
- return process.env.SQUADRANTD_SOCK ?? join16(homedir11(), ".config", "squadrant", "squadrant.sock");
7050
+ return process.env.SQUADRANTD_SOCK ?? join18(homedir13(), ".config", "squadrant", "squadrant.sock");
6598
7051
  }
6599
7052
  function rule(width, ch = "\u2500") {
6600
7053
  return ch.repeat(Math.max(0, width));
@@ -6830,7 +7283,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
6830
7283
  });
6831
7284
 
6832
7285
  // packages/cli/src/commands/crew-control.ts
6833
- var SOCK2 = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
7286
+ var SOCK2 = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
6834
7287
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
6835
7288
  async function sendCodexFirstTurn(taskId, text) {
6836
7289
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -6935,10 +7388,10 @@ function buildSignalRequest(signal, o) {
6935
7388
  return { kind: "event", project, event };
6936
7389
  }
6937
7390
  function defaultWriteResult(id, payload) {
6938
- const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
6939
- mkdirSync6(dir, { recursive: true });
6940
- const file = join17(dir, `${id}.txt`);
6941
- writeFileSync7(file, payload);
7391
+ const dir = join19(homedir14(), ".config", "squadrant", "state", "_results");
7392
+ mkdirSync7(dir, { recursive: true });
7393
+ const file = join19(dir, `${id}.txt`);
7394
+ writeFileSync8(file, payload);
6942
7395
  return file;
6943
7396
  }
6944
7397
  function addControlPlaneCrewCommands(crew) {
@@ -7037,8 +7490,8 @@ addControlPlaneCrewCommands(crewControlCommand);
7037
7490
 
7038
7491
  // packages/cli/src/lib/per-crew-settings.ts
7039
7492
  init_dist4();
7040
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
7041
- import { join as join18 } from "path";
7493
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
7494
+ import { join as join20 } from "path";
7042
7495
  var CREW_PERMISSION_ALLOWLIST = [
7043
7496
  // git — read + safe mutations (reset/clean/config intentionally excluded)
7044
7497
  "Bash(git status:*)",
@@ -7129,24 +7582,24 @@ function mergeCrewPermissions(settings) {
7129
7582
  return next;
7130
7583
  }
7131
7584
  function writePerCrewSettingsLocal(o) {
7132
- const dir = join18(o.projectCwd, ".claude");
7133
- mkdirSync7(dir, { recursive: true });
7134
- const file = join18(dir, "settings.local.json");
7585
+ const dir = join20(o.projectCwd, ".claude");
7586
+ mkdirSync8(dir, { recursive: true });
7587
+ const file = join20(dir, "settings.local.json");
7135
7588
  let existing = {};
7136
7589
  try {
7137
- const raw = healStaleCockpitRefs(readFileSync9(file, "utf-8"));
7590
+ const raw = healStaleCockpitRefs(readFileSync11(file, "utf-8"));
7138
7591
  existing = JSON.parse(raw);
7139
7592
  } catch {
7140
7593
  }
7141
7594
  const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
7142
7595
  const merged = mergeCrewPermissions(withHooks);
7143
- writeFileSync8(file, JSON.stringify(merged, null, 2));
7596
+ writeFileSync9(file, JSON.stringify(merged, null, 2));
7144
7597
  return file;
7145
7598
  }
7146
7599
  function writePerCrewOpencodeConfig(o) {
7147
- const dir = join18(o.stateRoot, o.project, o.taskId);
7148
- mkdirSync7(dir, { recursive: true });
7149
- const file = join18(dir, "opencode.json");
7600
+ const dir = join20(o.stateRoot, o.project, o.taskId);
7601
+ mkdirSync8(dir, { recursive: true });
7602
+ const file = join20(dir, "opencode.json");
7150
7603
  const config = {
7151
7604
  permission: {
7152
7605
  read: "allow",
@@ -7161,7 +7614,7 @@ function writePerCrewOpencodeConfig(o) {
7161
7614
  external_directory: { "**": "allow" }
7162
7615
  }
7163
7616
  };
7164
- writeFileSync8(file, JSON.stringify(config, null, 2));
7617
+ writeFileSync9(file, JSON.stringify(config, null, 2));
7165
7618
  return file;
7166
7619
  }
7167
7620
 
@@ -7454,8 +7907,8 @@ init_dist();
7454
7907
  init_dist3();
7455
7908
  import { Command as Command11 } from "commander";
7456
7909
  import { execSync as execSync10 } from "child_process";
7457
- import { homedir as homedir14 } from "os";
7458
- import { join as join20 } from "path";
7910
+ import { homedir as homedir16 } from "os";
7911
+ import { join as join22 } from "path";
7459
7912
  import chalk12 from "chalk";
7460
7913
 
7461
7914
  // packages/web/dist/read-status.js
@@ -7651,9 +8104,9 @@ function mergeSnapshot(daemon, external, now) {
7651
8104
  // packages/web/dist/probes.js
7652
8105
  init_dist();
7653
8106
  init_dist();
7654
- import { join as join19 } from "path";
7655
- import { homedir as homedir13 } from "os";
7656
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
8107
+ import { join as join21 } from "path";
8108
+ import { homedir as homedir15 } from "os";
8109
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
7657
8110
  import { execFile as execFile3 } from "child_process";
7658
8111
  var DEFAULT_TIMEOUT_MS = 2e3;
7659
8112
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -7689,7 +8142,7 @@ function vaultProbe(run, dir) {
7689
8142
  return { state: "unknown", detail: "no vault configured" };
7690
8143
  if (!run.pathExists(dir))
7691
8144
  return { state: "gone", detail: "vault directory missing" };
7692
- if (!run.pathExists(join19(dir, ".obsidian")))
8145
+ if (!run.pathExists(join21(dir, ".obsidian")))
7693
8146
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
7694
8147
  return { state: "alive" };
7695
8148
  } catch {
@@ -7757,13 +8210,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
7757
8210
  const sessions = probeSessions(run);
7758
8211
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
7759
8212
  }
7760
- var SESSIONS_PATH = join19(homedir13(), ".config", "squadrant", "sessions.json");
8213
+ var SESSIONS_PATH = join21(homedir15(), ".config", "squadrant", "sessions.json");
7761
8214
  function onPath(cli) {
7762
8215
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
7763
- return dirs.some((d) => existsSync10(join19(d, cli)));
8216
+ return dirs.some((d) => existsSync11(join21(d, cli)));
7764
8217
  }
7765
8218
  function readSessionsHashes() {
7766
- const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
8219
+ const raw = JSON.parse(readFileSync12(SESSIONS_PATH, "utf-8"));
7767
8220
  const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
7768
8221
  return [...new Set(hashes)];
7769
8222
  }
@@ -7777,7 +8230,7 @@ function defaultProbeRunners() {
7777
8230
  }
7778
8231
  }),
7779
8232
  probeOnPath: async (cli) => onPath(cli),
7780
- pathExists: (p) => existsSync10(p),
8233
+ pathExists: (p) => existsSync11(p),
7781
8234
  loadConfig: () => loadConfig(),
7782
8235
  loadSessionsHashes: () => readSessionsHashes()
7783
8236
  };
@@ -8430,7 +8883,7 @@ async function startWebServer(opts) {
8430
8883
 
8431
8884
  // packages/cli/src/commands/dashboard.ts
8432
8885
  init_dist();
8433
- var SOCK3 = join20(homedir14(), ".config", "squadrant", "squadrant.sock");
8886
+ var SOCK3 = join22(homedir16(), ".config", "squadrant", "squadrant.sock");
8434
8887
  function detectCurrentWorkspace2() {
8435
8888
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
8436
8889
  const match = out.match(/workspace:\d+/);
@@ -8514,13 +8967,87 @@ init_dist();
8514
8967
  init_dist4();
8515
8968
  init_dist3();
8516
8969
  init_dist2();
8517
- init_dist2();
8518
8970
  import { Command as Command12 } from "commander";
8519
8971
  import { execSync as execSync11 } from "child_process";
8520
8972
  import fs20 from "fs";
8521
8973
  import path23 from "path";
8522
8974
  import os12 from "os";
8523
8975
  import chalk13 from "chalk";
8976
+
8977
+ // packages/cli/src/commands/launch-interactive.ts
8978
+ import checkbox, { Separator } from "@inquirer/checkbox";
8979
+ function getYesterday() {
8980
+ const d = /* @__PURE__ */ new Date();
8981
+ d.setDate(d.getDate() - 1);
8982
+ return d.toISOString().slice(0, 10);
8983
+ }
8984
+ function partitionByYesterday(entries, yesterday) {
8985
+ const y = [];
8986
+ const r = [];
8987
+ for (const e of entries) {
8988
+ if (e.lastLaunched === yesterday) {
8989
+ y.push(e);
8990
+ } else {
8991
+ r.push(e);
8992
+ }
8993
+ }
8994
+ return { yesterday: y, rest: r };
8995
+ }
8996
+ async function selectCaptainsInteractive(entries, yesterday = getYesterday()) {
8997
+ const { yesterday: yesterdayEntries, rest: restEntries } = partitionByYesterday(entries, yesterday);
8998
+ if (yesterdayEntries.length === 0) {
8999
+ const result2 = await checkbox({
9000
+ message: "Select captains to launch:",
9001
+ choices: entries.map((e) => ({
9002
+ name: `${e.captainName} (${e.projectName})`,
9003
+ value: e.projectName,
9004
+ checked: false
9005
+ })),
9006
+ pageSize: 20
9007
+ });
9008
+ return result2;
9009
+ }
9010
+ const initialChoices = [
9011
+ new Separator("\u2500\u2500 Opened yesterday \u2500\u2500"),
9012
+ ...yesterdayEntries.map((e) => ({
9013
+ name: `${e.captainName} (${e.projectName})`,
9014
+ value: e.projectName,
9015
+ checked: true
9016
+ })),
9017
+ new Separator(),
9018
+ { name: "Show all projects", value: "__show_all__", checked: false }
9019
+ ];
9020
+ const result = await checkbox({
9021
+ message: "Select captains to launch:",
9022
+ choices: initialChoices,
9023
+ pageSize: 20
9024
+ });
9025
+ if (result.includes("__show_all__")) {
9026
+ const allChecked = yesterdayEntries.map((e) => e.projectName);
9027
+ const result2 = await checkbox({
9028
+ message: "Select captains to launch (all projects):",
9029
+ choices: [
9030
+ new Separator("\u2500\u2500 Opened yesterday \u2500\u2500"),
9031
+ ...yesterdayEntries.map((e) => ({
9032
+ name: `${e.captainName} (${e.projectName})`,
9033
+ value: e.projectName,
9034
+ checked: allChecked.includes(e.projectName)
9035
+ })),
9036
+ ...restEntries.map((e) => ({
9037
+ name: `${e.captainName} (${e.projectName})`,
9038
+ value: e.projectName,
9039
+ checked: false
9040
+ }))
9041
+ ],
9042
+ pageSize: 30
9043
+ });
9044
+ return result2;
9045
+ }
9046
+ return result;
9047
+ }
9048
+
9049
+ // packages/cli/src/commands/launch.ts
9050
+ init_dist2();
8524
9051
  var CMUX_APP = "/Applications/cmux.app";
8525
9052
  var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
8526
9053
  var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
@@ -8604,12 +9131,43 @@ var launchCommand = new Command12("launch").description(
8604
9131
  }
8605
9132
  console.log("");
8606
9133
  } else if (!project) {
8607
- console.error(
8608
- chalk13.red(
8609
- "\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
8610
- )
8611
- );
8612
- process.exit(1);
9134
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
9135
+ console.error(
9136
+ chalk13.red(
9137
+ "\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
9138
+ )
9139
+ );
9140
+ process.exit(1);
9141
+ }
9142
+ ensureCmuxReady();
9143
+ const sessions = loadSessions(SESSIONS_PATH2);
9144
+ const entries = Object.entries(config.projects).map(([name, proj]) => ({
9145
+ projectName: name,
9146
+ captainName: proj.captainName,
9147
+ lastLaunched: sessions.workspaces[proj.captainName]?.lastLaunched ?? null
9148
+ }));
9149
+ const selected = await selectCaptainsInteractive(entries);
9150
+ if (selected.length === 0) {
9151
+ console.log(chalk13.yellow("\n No captains selected.\n"));
9152
+ return;
9153
+ }
9154
+ console.log(chalk13.bold(`
9155
+ Launching ${selected.length} captain workspace(s) in parallel
9156
+ `));
9157
+ await Promise.all(selected.map(async (name) => {
9158
+ const proj = config.projects[name];
9159
+ const projPath = resolveHome(proj.path);
9160
+ const spokePath = resolveHome(proj.spokeVault);
9161
+ if (!fs20.existsSync(spokePath)) {
9162
+ const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
9163
+ await ensureSpokeLayout(spokeDriver);
9164
+ console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
9165
+ }
9166
+ console.log(chalk13.bold(`
9167
+ Captain: ${proj.captainName} (${name})`));
9168
+ await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
9169
+ }));
9170
+ console.log("");
8613
9171
  } else {
8614
9172
  if (!config.projects[project]) {
8615
9173
  console.error(
@@ -9606,7 +10164,7 @@ init_dist2();
9606
10164
  import { Command as Command22 } from "commander";
9607
10165
  import fs25 from "fs";
9608
10166
  import { fileURLToPath as fileURLToPath5 } from "url";
9609
- import { dirname as dirname5, join as join21 } from "path";
10167
+ import { dirname as dirname5, join as join23 } from "path";
9610
10168
  import chalk22 from "chalk";
9611
10169
  function runConfigCheck(opts) {
9612
10170
  const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
@@ -9742,7 +10300,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
9742
10300
  }
9743
10301
  });
9744
10302
  function readPkgVersion2() {
9745
- const pkgPath = join21(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
10303
+ const pkgPath = join23(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
9746
10304
  return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
9747
10305
  }
9748
10306
 
@@ -10013,12 +10571,12 @@ var effortCommand = new Command26("effort").description("Get or set the global c
10013
10571
  // packages/cli/src/commands/telegram.ts
10014
10572
  init_dist();
10015
10573
  init_dist2();
10016
- import { join as join22, dirname as dirname6 } from "path";
10574
+ import { join as join24, dirname as dirname6 } from "path";
10017
10575
  import { emitKeypressEvents } from "readline";
10018
10576
  import { Command as Command27 } from "commander";
10019
10577
  import chalk27 from "chalk";
10020
10578
  function defaultStateRoot() {
10021
- return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
10579
+ return join24(dirname6(DEFAULT_CONFIG_PATH), "state");
10022
10580
  }
10023
10581
  async function questionMasked() {
10024
10582
  return new Promise((resolve3) => {
@@ -10339,25 +10897,88 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
10339
10897
  }
10340
10898
  });
10341
10899
 
10900
+ // packages/cli/src/commands/hooks.ts
10901
+ init_dist2();
10902
+ init_dist4();
10903
+ import { Command as Command28 } from "commander";
10904
+ import { join as join25 } from "path";
10905
+ import { homedir as homedir17 } from "os";
10906
+ var SOCK4 = join25(homedir17(), ".config", "squadrant", "squadrant.sock");
10907
+ async function sendToSock(req) {
10908
+ await sendRequest(SOCK4, req);
10909
+ }
10910
+ function mapHookSub(sub, payload, taskId) {
10911
+ switch (sub) {
10912
+ case "session-start":
10913
+ case "prompt-submit":
10914
+ case "pre-tool-use":
10915
+ return { type: "task.progress", id: taskId, note: sub };
10916
+ case "stop":
10917
+ return mapClaudeHookToEvent("Stop", payload, taskId);
10918
+ case "notification":
10919
+ return mapClaudeHookToEvent("Notification", payload, taskId);
10920
+ case "ask-question": {
10921
+ const q = typeof payload?.question === "string" ? payload.question : "awaiting input";
10922
+ return { type: "task.input.requested", id: taskId, requestId: 0, question: q };
10923
+ }
10924
+ case "session-end":
10925
+ return mapClaudeHookToEvent("SessionEnd", payload, taskId);
10926
+ default:
10927
+ return null;
10928
+ }
10929
+ }
10930
+ function hooksCommand() {
10931
+ const hooks = new Command28("hooks").description("(internal) receive lifecycle hook events from agent processes");
10932
+ hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
10933
+ const taskId = process.env.SQUADRANT_CREW_TASK_ID;
10934
+ const project = process.env.SQUADRANT_CREW_PROJECT;
10935
+ if (!taskId || !project) {
10936
+ process.exit(0);
10937
+ }
10938
+ let stdin = "";
10939
+ try {
10940
+ for await (const chunk of process.stdin) stdin += chunk;
10941
+ } catch {
10942
+ }
10943
+ let payload = void 0;
10944
+ if (stdin.trim()) {
10945
+ try {
10946
+ payload = JSON.parse(stdin);
10947
+ } catch {
10948
+ }
10949
+ }
10950
+ const ev = mapHookSub(sub, payload, taskId);
10951
+ if (!ev) {
10952
+ process.exit(0);
10953
+ }
10954
+ try {
10955
+ await sendToSock({ kind: "event", project, event: ev });
10956
+ } catch {
10957
+ }
10958
+ process.exit(0);
10959
+ });
10960
+ return hooks;
10961
+ }
10962
+
10342
10963
  // packages/cli/src/index.ts
10343
10964
  init_dist();
10344
10965
  init_dist();
10345
10966
  init_dist();
10346
10967
  var __dirname = dirname7(fileURLToPath6(import.meta.url));
10347
- var pkg = JSON.parse(readFileSync11(join23(__dirname, "..", "package.json"), "utf-8"));
10968
+ var pkg = JSON.parse(readFileSync13(join26(__dirname, "..", "package.json"), "utf-8"));
10348
10969
  ensureRuntimeSynced({
10349
- sourceRoot: join23(__dirname, ".."),
10350
- runtimeRoot: join23(homedir15(), ".config", "squadrant")
10970
+ sourceRoot: join26(__dirname, ".."),
10971
+ runtimeRoot: join26(homedir18(), ".config", "squadrant")
10351
10972
  });
10352
10973
  if (process.argv[2] !== "config") {
10353
10974
  try {
10354
- const cfgPath = join23(homedir15(), ".config", "squadrant", "config.json");
10355
- if (existsSync11(cfgPath)) {
10356
- const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
10975
+ const cfgPath = join26(homedir18(), ".config", "squadrant", "config.json");
10976
+ if (existsSync12(cfgPath)) {
10977
+ const cfg = JSON.parse(readFileSync13(cfgPath, "utf-8"));
10357
10978
  if (needsCheck(cfg, pkg.version)) {
10358
10979
  const items = detectDrift(cfg, getDefaultConfig());
10359
10980
  if (items.length === 0) {
10360
- writeFileSync9(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
10981
+ writeFileSync10(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
10361
10982
  } else {
10362
10983
  const from = cfg._squadrantVersion ?? "an earlier version";
10363
10984
  process.stderr.write(
@@ -10376,7 +10997,7 @@ if (process.argv[2] !== "config") {
10376
10997
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
10377
10998
  ensureDaemon();
10378
10999
  }
10379
- var program = new Command28();
11000
+ var program = new Command29();
10380
11001
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
10381
11002
  program.addCommand(doctorCommand);
10382
11003
  program.addCommand(initCommand);
@@ -10403,6 +11024,7 @@ program.addCommand(groupCommand);
10403
11024
  program.addCommand(cmuxCommand);
10404
11025
  program.addCommand(effortCommand);
10405
11026
  program.addCommand(telegramCommand);
11027
+ program.addCommand(hooksCommand());
10406
11028
  program.parseAsync().catch((e) => {
10407
11029
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
10408
11030
  `);