squadrant 0.17.1 → 0.18.1
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 +1261 -809
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +263 -139
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +1 -1
- package/plugin/skills/handback/SKILL.md +8 -0
- package/plugin/skills/takeover/SKILL.md +11 -0
- package/templates/captain.claude.md +1 -0
- package/templates/captain.generic.md +1 -0
package/dist/index.js
CHANGED
|
@@ -9,15 +9,87 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// packages/shared/dist/config.js
|
|
12
|
+
// packages/shared/dist/lib/config-io.js
|
|
13
13
|
import fs from "fs";
|
|
14
14
|
import path from "path";
|
|
15
|
+
function tightenModeSync(p, expectedMode) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(p))
|
|
18
|
+
return;
|
|
19
|
+
const stat2 = fs.statSync(p);
|
|
20
|
+
const currentMode = stat2.mode & 4095;
|
|
21
|
+
if ((currentMode & ~expectedMode) !== 0) {
|
|
22
|
+
fs.chmodSync(p, expectedMode);
|
|
23
|
+
if (!didLogMigration) {
|
|
24
|
+
console.warn(`\x1B[33m\u26A0\x1B[0m squadrant: tightened config file permissions (security fix #668)`);
|
|
25
|
+
didLogMigration = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function writeConfigFileSync(filePath, content) {
|
|
32
|
+
ensureDirSync(path.dirname(filePath));
|
|
33
|
+
fs.writeFileSync(filePath, content, { mode: 384 });
|
|
34
|
+
tightenModeSync(filePath, 384);
|
|
35
|
+
}
|
|
36
|
+
function ensureDirSync(dirPath) {
|
|
37
|
+
if (dirPath !== CONFIG_DIR && !dirPath.startsWith(CONFIG_DIR + path.sep)) {
|
|
38
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
42
|
+
let current = dirPath;
|
|
43
|
+
while (current === CONFIG_DIR || current.startsWith(CONFIG_DIR + path.sep)) {
|
|
44
|
+
tightenModeSync(current, 448);
|
|
45
|
+
if (current === CONFIG_DIR)
|
|
46
|
+
break;
|
|
47
|
+
const parent = path.dirname(current);
|
|
48
|
+
if (parent === current)
|
|
49
|
+
break;
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function readConfigFileSync(filePath) {
|
|
54
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
function migrateConfigPermsSync() {
|
|
57
|
+
if (migrationRun)
|
|
58
|
+
return;
|
|
59
|
+
migrationRun = true;
|
|
60
|
+
tightenModeSync(CONFIG_DIR, 448);
|
|
61
|
+
tightenModeSync(path.join(CONFIG_DIR, "config.json"), 384);
|
|
62
|
+
const projectsDir = path.join(CONFIG_DIR, "projects");
|
|
63
|
+
tightenModeSync(projectsDir, 448);
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(projectsDir)) {
|
|
66
|
+
const files = fs.readdirSync(projectsDir);
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
if (file.endsWith(".json")) {
|
|
69
|
+
tightenModeSync(path.join(projectsDir, file), 384);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
var didLogMigration, migrationRun;
|
|
77
|
+
var init_config_io = __esm({
|
|
78
|
+
"packages/shared/dist/lib/config-io.js"() {
|
|
79
|
+
init_config();
|
|
80
|
+
didLogMigration = false;
|
|
81
|
+
migrationRun = false;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// packages/shared/dist/config.js
|
|
86
|
+
import path2 from "path";
|
|
15
87
|
import os from "os";
|
|
16
88
|
import chalk from "chalk";
|
|
17
89
|
function getDefaultConfig() {
|
|
18
90
|
return {
|
|
19
91
|
commandName: "\u{1F3DB}\uFE0F command",
|
|
20
|
-
hubVault:
|
|
92
|
+
hubVault: path2.join(os.homedir(), "squadrant-hub"),
|
|
21
93
|
projects: {},
|
|
22
94
|
agents: {
|
|
23
95
|
claude: { cli: "claude", driver: "claude" }
|
|
@@ -61,13 +133,14 @@ function getDefaultConfig() {
|
|
|
61
133
|
},
|
|
62
134
|
metrics: {
|
|
63
135
|
enabled: true,
|
|
64
|
-
path:
|
|
136
|
+
path: path2.join(CONFIG_DIR, "metrics.json")
|
|
65
137
|
}
|
|
66
138
|
};
|
|
67
139
|
}
|
|
68
140
|
function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
141
|
+
migrateConfigPermsSync();
|
|
69
142
|
try {
|
|
70
|
-
const raw =
|
|
143
|
+
const raw = readConfigFileSync(configPath);
|
|
71
144
|
const config = JSON.parse(raw);
|
|
72
145
|
if (config.defaults.models && !config.defaults.roles) {
|
|
73
146
|
const m = config.defaults.models;
|
|
@@ -92,34 +165,32 @@ function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
|
92
165
|
}
|
|
93
166
|
}
|
|
94
167
|
function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
95
|
-
|
|
96
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
97
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
168
|
+
writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
98
169
|
}
|
|
99
170
|
function resolveHome(p) {
|
|
100
171
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
101
172
|
}
|
|
102
|
-
var
|
|
173
|
+
var DEFAULT_CONFIG_PATH, CONFIG_DIR;
|
|
103
174
|
var init_config = __esm({
|
|
104
175
|
"packages/shared/dist/config.js"() {
|
|
105
|
-
|
|
106
|
-
DEFAULT_CONFIG_PATH =
|
|
176
|
+
init_config_io();
|
|
177
|
+
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
178
|
+
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
107
179
|
}
|
|
108
180
|
});
|
|
109
181
|
|
|
110
182
|
// packages/shared/dist/project-config.js
|
|
111
|
-
import fs2 from "fs";
|
|
112
183
|
import os2 from "os";
|
|
113
|
-
import
|
|
184
|
+
import path3 from "path";
|
|
114
185
|
function defaultRoot() {
|
|
115
|
-
return
|
|
186
|
+
return path3.join(os2.homedir(), ".config", "squadrant");
|
|
116
187
|
}
|
|
117
188
|
function projectConfigPath(name, root = defaultRoot()) {
|
|
118
|
-
return
|
|
189
|
+
return path3.join(root, "projects", `${name}.json`);
|
|
119
190
|
}
|
|
120
191
|
function loadProjectOverride(name, root = defaultRoot()) {
|
|
121
192
|
try {
|
|
122
|
-
return JSON.parse(
|
|
193
|
+
return JSON.parse(readConfigFileSync(projectConfigPath(name, root)));
|
|
123
194
|
} catch {
|
|
124
195
|
return {};
|
|
125
196
|
}
|
|
@@ -136,8 +207,7 @@ function deepMerge(base, patch) {
|
|
|
136
207
|
function saveProjectOverride(name, patch, root = defaultRoot()) {
|
|
137
208
|
const merged = deepMerge(loadProjectOverride(name, root), patch);
|
|
138
209
|
const file = projectConfigPath(name, root);
|
|
139
|
-
|
|
140
|
-
fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
210
|
+
writeConfigFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
141
211
|
}
|
|
142
212
|
function crewRank(tier) {
|
|
143
213
|
return CREW_RANK[tier];
|
|
@@ -162,6 +232,7 @@ function resolveNotify(globalNotify, override) {
|
|
|
162
232
|
var DEFAULT_NOTIFY, CREW_RANK;
|
|
163
233
|
var init_project_config = __esm({
|
|
164
234
|
"packages/shared/dist/project-config.js"() {
|
|
235
|
+
init_config_io();
|
|
165
236
|
DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
|
|
166
237
|
CREW_RANK = { none: 0, done_only: 1, alert_only: 2, all: 3 };
|
|
167
238
|
}
|
|
@@ -234,22 +305,22 @@ function defaultCmuxConfigPath() {
|
|
|
234
305
|
return join(homedir(), ".config", "cmux", "cmux.json");
|
|
235
306
|
}
|
|
236
307
|
function ensureSocketAutomation(opts = {}) {
|
|
237
|
-
const
|
|
238
|
-
if (!existsSync(
|
|
239
|
-
mkdirSync(dirname(
|
|
240
|
-
writeFileSync(
|
|
241
|
-
return { path:
|
|
308
|
+
const path35 = opts.path ?? defaultCmuxConfigPath();
|
|
309
|
+
if (!existsSync(path35)) {
|
|
310
|
+
mkdirSync(dirname(path35), { recursive: true });
|
|
311
|
+
writeFileSync(path35, MINIMAL_TEMPLATE);
|
|
312
|
+
return { path: path35, changed: true, alreadySet: false };
|
|
242
313
|
}
|
|
243
|
-
const text = readFileSync(
|
|
314
|
+
const text = readFileSync(path35, "utf-8");
|
|
244
315
|
const current = parse(text)?.automation?.socketControlMode;
|
|
245
316
|
if (current === AUTOMATION_MODE) {
|
|
246
|
-
return { path:
|
|
317
|
+
return { path: path35, changed: false, alreadySet: true };
|
|
247
318
|
}
|
|
248
319
|
const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
|
|
249
320
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
250
321
|
});
|
|
251
|
-
writeFileSync(
|
|
252
|
-
return { path:
|
|
322
|
+
writeFileSync(path35, applyEdits(text, edits));
|
|
323
|
+
return { path: path35, changed: true, alreadySet: false };
|
|
253
324
|
}
|
|
254
325
|
var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
|
|
255
326
|
var init_cmux_config = __esm({
|
|
@@ -396,15 +467,15 @@ writeFileSync(resultFile, JSON.stringify(result));
|
|
|
396
467
|
});
|
|
397
468
|
|
|
398
469
|
// packages/shared/dist/lib/cmux-autoconfig.js
|
|
399
|
-
import { existsSync as existsSync4,
|
|
470
|
+
import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
|
|
400
471
|
import { homedir as homedir3 } from "os";
|
|
401
|
-
import {
|
|
472
|
+
import { join as join4 } from "path";
|
|
402
473
|
function defaultStatePath() {
|
|
403
474
|
return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
|
|
404
475
|
}
|
|
405
|
-
function readState(
|
|
476
|
+
function readState(path35) {
|
|
406
477
|
try {
|
|
407
|
-
return JSON.parse(
|
|
478
|
+
return JSON.parse(readConfigFileSync(path35));
|
|
408
479
|
} catch {
|
|
409
480
|
return {};
|
|
410
481
|
}
|
|
@@ -420,8 +491,7 @@ async function ensureCmuxAutoConfig(opts = {}) {
|
|
|
420
491
|
if (needsRestart) {
|
|
421
492
|
const already = readState(statePath2).promptedRestart === true;
|
|
422
493
|
if (!already) {
|
|
423
|
-
|
|
424
|
-
writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
|
|
494
|
+
writeConfigFileSync(statePath2, JSON.stringify({ promptedRestart: true }));
|
|
425
495
|
promptedThisRun = true;
|
|
426
496
|
}
|
|
427
497
|
} else if (verdict === "reachable") {
|
|
@@ -441,6 +511,7 @@ var init_cmux_autoconfig = __esm({
|
|
|
441
511
|
"packages/shared/dist/lib/cmux-autoconfig.js"() {
|
|
442
512
|
init_cmux_config();
|
|
443
513
|
init_cmux_probe();
|
|
514
|
+
init_config_io();
|
|
444
515
|
}
|
|
445
516
|
});
|
|
446
517
|
|
|
@@ -624,8 +695,8 @@ var init_config_version = __esm({
|
|
|
624
695
|
});
|
|
625
696
|
|
|
626
697
|
// packages/shared/dist/lib/update-check.js
|
|
627
|
-
import
|
|
628
|
-
import
|
|
698
|
+
import fs2 from "fs";
|
|
699
|
+
import path4 from "path";
|
|
629
700
|
import os3 from "os";
|
|
630
701
|
import https from "https";
|
|
631
702
|
function isUpdateCheckDisabled(config, env = process.env) {
|
|
@@ -648,8 +719,25 @@ function isNewerVersion(latest, current) {
|
|
|
648
719
|
return lb > cb;
|
|
649
720
|
return lc > cc;
|
|
650
721
|
}
|
|
651
|
-
function
|
|
652
|
-
|
|
722
|
+
function detectInstallManager(modulePath = import.meta.url) {
|
|
723
|
+
if (modulePath.includes("Library/pnpm/") || modulePath.includes("/pnpm/"))
|
|
724
|
+
return "pnpm";
|
|
725
|
+
if (modulePath.includes("/yarn/global/node_modules/"))
|
|
726
|
+
return "yarn";
|
|
727
|
+
return "npm";
|
|
728
|
+
}
|
|
729
|
+
function upgradeCommandFor(manager) {
|
|
730
|
+
switch (manager) {
|
|
731
|
+
case "pnpm":
|
|
732
|
+
return "pnpm add -g squadrant@latest";
|
|
733
|
+
case "yarn":
|
|
734
|
+
return "yarn global add squadrant@latest";
|
|
735
|
+
case "npm":
|
|
736
|
+
return "npm i -g squadrant@latest";
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function formatUpdateNotice(latest, current, modulePath = import.meta.url) {
|
|
740
|
+
return `\u2B06 squadrant ${latest} available (you have ${current}) \u2014 ${upgradeCommandFor(detectInstallManager(modulePath))}`;
|
|
653
741
|
}
|
|
654
742
|
async function fetchLatestVersion(requestFn = requestJson, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
655
743
|
const timeout = new Promise((resolve4) => {
|
|
@@ -681,15 +769,15 @@ async function checkForUpdate(opts) {
|
|
|
681
769
|
}
|
|
682
770
|
function readUpdateCheckState(statePath2 = UPDATE_CHECK_STATE_PATH) {
|
|
683
771
|
try {
|
|
684
|
-
return JSON.parse(
|
|
772
|
+
return JSON.parse(fs2.readFileSync(statePath2, "utf-8"));
|
|
685
773
|
} catch {
|
|
686
774
|
return void 0;
|
|
687
775
|
}
|
|
688
776
|
}
|
|
689
777
|
function writeUpdateCheckState(state, statePath2 = UPDATE_CHECK_STATE_PATH) {
|
|
690
778
|
try {
|
|
691
|
-
|
|
692
|
-
|
|
779
|
+
fs2.mkdirSync(path4.dirname(statePath2), { recursive: true });
|
|
780
|
+
fs2.writeFileSync(statePath2, JSON.stringify(state, null, 2) + "\n");
|
|
693
781
|
} catch {
|
|
694
782
|
}
|
|
695
783
|
}
|
|
@@ -720,7 +808,7 @@ ${line}
|
|
|
720
808
|
var UPDATE_CHECK_STATE_PATH, REGISTRY_URL, CHECK_INTERVAL_MS, FAILURE_RETRY_MS, FETCH_TIMEOUT_MS, requestJson;
|
|
721
809
|
var init_update_check = __esm({
|
|
722
810
|
"packages/shared/dist/lib/update-check.js"() {
|
|
723
|
-
UPDATE_CHECK_STATE_PATH =
|
|
811
|
+
UPDATE_CHECK_STATE_PATH = path4.join(os3.homedir(), ".config", "squadrant", "update-check.json");
|
|
724
812
|
REGISTRY_URL = "https://registry.npmjs.org/squadrant/latest";
|
|
725
813
|
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
726
814
|
FAILURE_RETRY_MS = 60 * 60 * 1e3;
|
|
@@ -752,10 +840,10 @@ var init_update_check = __esm({
|
|
|
752
840
|
|
|
753
841
|
// packages/shared/dist/lib/git-worktree.js
|
|
754
842
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
755
|
-
import
|
|
756
|
-
import
|
|
843
|
+
import fs3 from "fs";
|
|
844
|
+
import path5 from "path";
|
|
757
845
|
function worktreePath(repoRoot, worktreeDir, project, name) {
|
|
758
|
-
return
|
|
846
|
+
return path5.resolve(repoRoot, worktreeDir, `${project}-${name}`);
|
|
759
847
|
}
|
|
760
848
|
function crewBranch(name) {
|
|
761
849
|
return `crew/${name}`;
|
|
@@ -764,15 +852,21 @@ function ensureSpotlightExcluded(repoRoot, worktreeDir) {
|
|
|
764
852
|
if (process.platform !== "darwin")
|
|
765
853
|
return;
|
|
766
854
|
try {
|
|
767
|
-
const dir =
|
|
768
|
-
|
|
769
|
-
const marker =
|
|
770
|
-
if (!
|
|
771
|
-
|
|
855
|
+
const dir = path5.resolve(repoRoot, worktreeDir);
|
|
856
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
857
|
+
const marker = path5.join(dir, ".metadata_never_index");
|
|
858
|
+
if (!fs3.existsSync(marker))
|
|
859
|
+
fs3.writeFileSync(marker, "");
|
|
772
860
|
} catch {
|
|
773
861
|
}
|
|
774
862
|
}
|
|
775
863
|
function resolveWorktreeBase(repoRoot, fallback = "develop") {
|
|
864
|
+
try {
|
|
865
|
+
const head = execFileSync2("git", ["-C", repoRoot, "rev-parse", "--abbrev-ref", "HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
866
|
+
if (head && head !== "HEAD")
|
|
867
|
+
return head;
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
776
870
|
try {
|
|
777
871
|
const ref = execFileSync2("git", ["-C", repoRoot, "symbolic-ref", "refs/remotes/origin/HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
778
872
|
const m = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
@@ -824,35 +918,39 @@ function addWorktree(spec) {
|
|
|
824
918
|
return wt;
|
|
825
919
|
}
|
|
826
920
|
function installWorktreeDependencies(wt) {
|
|
827
|
-
if (!
|
|
921
|
+
if (!fs3.existsSync(path5.join(wt, "package.json")))
|
|
828
922
|
return;
|
|
829
|
-
if (
|
|
923
|
+
if (fs3.existsSync(path5.join(wt, "pnpm-lock.yaml"))) {
|
|
830
924
|
execFileSync2("pnpm", ["-C", wt, "install", "--frozen-lockfile"], { stdio: "pipe" });
|
|
831
|
-
} else if (
|
|
925
|
+
} else if (fs3.existsSync(path5.join(wt, "yarn.lock"))) {
|
|
832
926
|
execFileSync2("yarn", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
|
|
833
|
-
} else if (
|
|
927
|
+
} else if (fs3.existsSync(path5.join(wt, "package-lock.json"))) {
|
|
834
928
|
execFileSync2("npm", ["ci"], { cwd: wt, stdio: "pipe" });
|
|
835
|
-
} else if (
|
|
929
|
+
} else if (fs3.existsSync(path5.join(wt, "bun.lockb"))) {
|
|
836
930
|
execFileSync2("bun", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
|
|
837
931
|
} else {
|
|
838
932
|
process.stderr.write(`worktree ${wt}: package.json present but no lockfile \u2014 dependencies not installed; local typechecks/tests may resolve against the main checkout instead of this worktree.
|
|
839
933
|
`);
|
|
840
934
|
}
|
|
841
935
|
}
|
|
842
|
-
function
|
|
936
|
+
function worktreeDirtyFiles(wtPath) {
|
|
843
937
|
try {
|
|
844
|
-
execFileSync2("git", ["-C",
|
|
938
|
+
return execFileSync2("git", ["-C", wtPath, "status", "--porcelain", "--untracked-files=all"], { stdio: ["ignore", "pipe", "ignore"] }).toString().split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
|
|
845
939
|
} catch {
|
|
846
|
-
|
|
940
|
+
return [];
|
|
847
941
|
}
|
|
848
942
|
}
|
|
943
|
+
function removeWorktree(repoRoot, wtPath, opts) {
|
|
944
|
+
const args = ["-C", repoRoot, "worktree", "remove", ...opts?.force ? ["--force"] : [], wtPath];
|
|
945
|
+
execFileSync2("git", args, { stdio: "pipe" });
|
|
946
|
+
}
|
|
849
947
|
var init_git_worktree = __esm({
|
|
850
948
|
"packages/shared/dist/lib/git-worktree.js"() {
|
|
851
949
|
}
|
|
852
950
|
});
|
|
853
951
|
|
|
854
952
|
// packages/shared/dist/lib/resolve-text-input.js
|
|
855
|
-
import
|
|
953
|
+
import fs4 from "fs";
|
|
856
954
|
async function readAllStdin() {
|
|
857
955
|
const chunks = [];
|
|
858
956
|
for await (const chunk of process.stdin) {
|
|
@@ -864,7 +962,7 @@ function flagName(label) {
|
|
|
864
962
|
return label === "task" ? "--task-file" : "--message-file";
|
|
865
963
|
}
|
|
866
964
|
async function resolveTextInput(opts, deps) {
|
|
867
|
-
const readFile6 = deps?.readFile ?? ((p) =>
|
|
965
|
+
const readFile6 = deps?.readFile ?? ((p) => fs4.readFileSync(p, "utf8"));
|
|
868
966
|
const readStdin3 = deps?.readStdin ?? readAllStdin;
|
|
869
967
|
if (opts.filePath) {
|
|
870
968
|
if (opts.filePath === "-") {
|
|
@@ -892,75 +990,75 @@ var init_resolve_text_input = __esm({
|
|
|
892
990
|
});
|
|
893
991
|
|
|
894
992
|
// packages/shared/dist/lib/runtime-sync.js
|
|
895
|
-
import
|
|
896
|
-
import
|
|
993
|
+
import fs5 from "fs";
|
|
994
|
+
import path6 from "path";
|
|
897
995
|
function copyIfDifferent(src, dest) {
|
|
898
|
-
if (
|
|
899
|
-
if (
|
|
996
|
+
if (fs5.existsSync(dest)) {
|
|
997
|
+
if (fs5.readFileSync(src).equals(fs5.readFileSync(dest)))
|
|
900
998
|
return false;
|
|
901
999
|
}
|
|
902
|
-
|
|
1000
|
+
fs5.copyFileSync(src, dest);
|
|
903
1001
|
return true;
|
|
904
1002
|
}
|
|
905
1003
|
function mirrorDir(src, dest) {
|
|
906
|
-
|
|
907
|
-
const srcEntries =
|
|
1004
|
+
fs5.mkdirSync(dest, { recursive: true });
|
|
1005
|
+
const srcEntries = fs5.readdirSync(src, { withFileTypes: true });
|
|
908
1006
|
const srcNames = new Set(srcEntries.map((e) => e.name));
|
|
909
1007
|
for (const entry of srcEntries) {
|
|
910
|
-
const srcPath =
|
|
911
|
-
const destPath =
|
|
1008
|
+
const srcPath = path6.join(src, entry.name);
|
|
1009
|
+
const destPath = path6.join(dest, entry.name);
|
|
912
1010
|
if (entry.isDirectory()) {
|
|
913
1011
|
mirrorDir(srcPath, destPath);
|
|
914
1012
|
} else {
|
|
915
1013
|
copyIfDifferent(srcPath, destPath);
|
|
916
1014
|
}
|
|
917
1015
|
}
|
|
918
|
-
for (const entry of
|
|
1016
|
+
for (const entry of fs5.readdirSync(dest, { withFileTypes: true })) {
|
|
919
1017
|
if (!srcNames.has(entry.name)) {
|
|
920
|
-
|
|
1018
|
+
fs5.rmSync(path6.join(dest, entry.name), { recursive: true, force: true });
|
|
921
1019
|
}
|
|
922
1020
|
}
|
|
923
1021
|
}
|
|
924
1022
|
function mirrorFlat(src, dest, match, chmod) {
|
|
925
|
-
|
|
926
|
-
const matched =
|
|
1023
|
+
fs5.mkdirSync(dest, { recursive: true });
|
|
1024
|
+
const matched = fs5.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
|
|
927
1025
|
const matchedSet = new Set(matched);
|
|
928
1026
|
for (const name of matched) {
|
|
929
|
-
const destPath =
|
|
930
|
-
const copied = copyIfDifferent(
|
|
1027
|
+
const destPath = path6.join(dest, name);
|
|
1028
|
+
const copied = copyIfDifferent(path6.join(src, name), destPath);
|
|
931
1029
|
if (copied && chmod !== void 0)
|
|
932
|
-
|
|
1030
|
+
fs5.chmodSync(destPath, chmod);
|
|
933
1031
|
}
|
|
934
|
-
for (const entry of
|
|
1032
|
+
for (const entry of fs5.readdirSync(dest, { withFileTypes: true })) {
|
|
935
1033
|
if (!matchedSet.has(entry.name)) {
|
|
936
|
-
|
|
1034
|
+
fs5.rmSync(path6.join(dest, entry.name), { recursive: true, force: true });
|
|
937
1035
|
}
|
|
938
1036
|
}
|
|
939
1037
|
}
|
|
940
1038
|
function mirrorPluginSubset(src, dest, skills) {
|
|
941
|
-
|
|
942
|
-
mirrorDir(
|
|
943
|
-
const skillsDest =
|
|
944
|
-
|
|
1039
|
+
fs5.mkdirSync(dest, { recursive: true });
|
|
1040
|
+
mirrorDir(path6.join(src, ".claude-plugin"), path6.join(dest, ".claude-plugin"));
|
|
1041
|
+
const skillsDest = path6.join(dest, "skills");
|
|
1042
|
+
fs5.mkdirSync(skillsDest, { recursive: true });
|
|
945
1043
|
for (const name of skills) {
|
|
946
|
-
const skillSrc =
|
|
947
|
-
if (
|
|
948
|
-
mirrorDir(skillSrc,
|
|
1044
|
+
const skillSrc = path6.join(src, "skills", name);
|
|
1045
|
+
if (fs5.existsSync(skillSrc))
|
|
1046
|
+
mirrorDir(skillSrc, path6.join(skillsDest, name));
|
|
949
1047
|
}
|
|
950
|
-
for (const entry of
|
|
1048
|
+
for (const entry of fs5.readdirSync(skillsDest, { withFileTypes: true })) {
|
|
951
1049
|
if (!skills.includes(entry.name)) {
|
|
952
|
-
|
|
1050
|
+
fs5.rmSync(path6.join(skillsDest, entry.name), { recursive: true, force: true });
|
|
953
1051
|
}
|
|
954
1052
|
}
|
|
955
1053
|
}
|
|
956
1054
|
function ensureRuntimeSynced(opts) {
|
|
957
1055
|
const targets = opts.targets ?? MANAGED_TARGETS;
|
|
958
1056
|
for (const t of targets) {
|
|
959
|
-
const srcDir =
|
|
1057
|
+
const srcDir = path6.join(opts.sourceRoot, t.srcRel);
|
|
960
1058
|
try {
|
|
961
|
-
if (!
|
|
1059
|
+
if (!fs5.existsSync(srcDir))
|
|
962
1060
|
continue;
|
|
963
|
-
const destDir =
|
|
1061
|
+
const destDir = path6.join(opts.runtimeRoot, t.name);
|
|
964
1062
|
if (t.mode === "tree") {
|
|
965
1063
|
mirrorDir(srcDir, destDir);
|
|
966
1064
|
} else if (t.mode === "flat") {
|
|
@@ -977,7 +1075,7 @@ function ensureRuntimeSynced(opts) {
|
|
|
977
1075
|
var CREW_SKILLS, MANAGED_TARGETS;
|
|
978
1076
|
var init_runtime_sync = __esm({
|
|
979
1077
|
"packages/shared/dist/lib/runtime-sync.js"() {
|
|
980
|
-
CREW_SKILLS = ["karpathy-principles"];
|
|
1078
|
+
CREW_SKILLS = ["karpathy-principles", "takeover", "handback"];
|
|
981
1079
|
MANAGED_TARGETS = [
|
|
982
1080
|
{ name: "plugin", srcRel: "plugin", mode: "tree" },
|
|
983
1081
|
{ name: "plugin-crew", srcRel: "plugin", mode: "subset", skills: CREW_SKILLS },
|
|
@@ -1028,8 +1126,8 @@ var init_tool_compat = __esm({
|
|
|
1028
1126
|
});
|
|
1029
1127
|
|
|
1030
1128
|
// packages/shared/dist/lib/canonical-source.js
|
|
1031
|
-
import
|
|
1032
|
-
import
|
|
1129
|
+
import fs6 from "fs";
|
|
1130
|
+
import path7 from "path";
|
|
1033
1131
|
function parseSkill(raw) {
|
|
1034
1132
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1035
1133
|
if (!match)
|
|
@@ -1070,10 +1168,10 @@ async function readSkills(driver, skillsDir) {
|
|
|
1070
1168
|
function readRoleTemplates(opts) {
|
|
1071
1169
|
if (!opts.pkgRoot)
|
|
1072
1170
|
return "";
|
|
1073
|
-
const reader = opts.readFile ?? ((p) =>
|
|
1171
|
+
const reader = opts.readFile ?? ((p) => fs6.readFileSync(p, "utf-8"));
|
|
1074
1172
|
const sections = [];
|
|
1075
1173
|
for (const { file, heading } of ROLE_TEMPLATES) {
|
|
1076
|
-
const full =
|
|
1174
|
+
const full = path7.join(opts.pkgRoot, "templates", file);
|
|
1077
1175
|
let body = "";
|
|
1078
1176
|
try {
|
|
1079
1177
|
body = reader(full);
|
|
@@ -1110,8 +1208,8 @@ var init_canonical_source = __esm({
|
|
|
1110
1208
|
|
|
1111
1209
|
// packages/shared/dist/lib/daily-logs.js
|
|
1112
1210
|
import { execSync } from "child_process";
|
|
1113
|
-
import
|
|
1114
|
-
import
|
|
1211
|
+
import fs7 from "fs";
|
|
1212
|
+
import path8 from "path";
|
|
1115
1213
|
import matter from "gray-matter";
|
|
1116
1214
|
function iso(d) {
|
|
1117
1215
|
return d.toISOString().slice(0, 10);
|
|
@@ -1163,7 +1261,7 @@ function getGitCommits(projectPath, dateStr) {
|
|
|
1163
1261
|
}
|
|
1164
1262
|
function getGitCommitsInRange(projectPath, since, until) {
|
|
1165
1263
|
const resolved = resolveHome(projectPath);
|
|
1166
|
-
if (!
|
|
1264
|
+
if (!fs7.existsSync(path8.join(resolved, ".git")))
|
|
1167
1265
|
return [];
|
|
1168
1266
|
const untilArg = until ? ` --until="${until}"` : "";
|
|
1169
1267
|
try {
|
|
@@ -1177,7 +1275,7 @@ function getGitCommitsInRange(projectPath, since, until) {
|
|
|
1177
1275
|
}
|
|
1178
1276
|
function getMergedPRsInRange(projectPath, since, until) {
|
|
1179
1277
|
const resolved = resolveHome(projectPath);
|
|
1180
|
-
if (!
|
|
1278
|
+
if (!fs7.existsSync(path8.join(resolved, ".git")))
|
|
1181
1279
|
return [];
|
|
1182
1280
|
const untilArg = until ? ` --until="${until}"` : "";
|
|
1183
1281
|
try {
|
|
@@ -1237,6 +1335,7 @@ var init_daemon_keys = __esm({
|
|
|
1237
1335
|
var dist_exports = {};
|
|
1238
1336
|
__export(dist_exports, {
|
|
1239
1337
|
AUTOMATION_MODE: () => AUTOMATION_MODE,
|
|
1338
|
+
CONFIG_DIR: () => CONFIG_DIR,
|
|
1240
1339
|
CREW_SKILLS: () => CREW_SKILLS,
|
|
1241
1340
|
DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
|
|
1242
1341
|
DEFAULT_NOTIFY: () => DEFAULT_NOTIFY,
|
|
@@ -1260,7 +1359,9 @@ __export(dist_exports, {
|
|
|
1260
1359
|
defaultCmuxConfigPath: () => defaultCmuxConfigPath,
|
|
1261
1360
|
defaultStatePath: () => defaultStatePath,
|
|
1262
1361
|
detectDrift: () => detectDrift,
|
|
1362
|
+
detectInstallManager: () => detectInstallManager,
|
|
1263
1363
|
ensureCmuxAutoConfig: () => ensureCmuxAutoConfig,
|
|
1364
|
+
ensureDirSync: () => ensureDirSync,
|
|
1264
1365
|
ensureRuntimeSynced: () => ensureRuntimeSynced,
|
|
1265
1366
|
ensureSocketAutomation: () => ensureSocketAutomation,
|
|
1266
1367
|
ensureSpokeLayout: () => ensureSpokeLayout,
|
|
@@ -1279,6 +1380,7 @@ __export(dist_exports, {
|
|
|
1279
1380
|
iso: () => iso,
|
|
1280
1381
|
loadConfig: () => loadConfig,
|
|
1281
1382
|
loadProjectOverride: () => loadProjectOverride,
|
|
1383
|
+
migrateConfigPermsSync: () => migrateConfigPermsSync,
|
|
1282
1384
|
mirrorDir: () => mirrorDir,
|
|
1283
1385
|
mirrorFlat: () => mirrorFlat,
|
|
1284
1386
|
needsCheck: () => needsCheck,
|
|
@@ -1286,6 +1388,7 @@ __export(dist_exports, {
|
|
|
1286
1388
|
parseSection: () => parseSection,
|
|
1287
1389
|
probeCmuxDaemonDirect: () => probeCmuxDaemonDirect,
|
|
1288
1390
|
projectConfigPath: () => projectConfigPath,
|
|
1391
|
+
readConfigFileSync: () => readConfigFileSync,
|
|
1289
1392
|
readDailyLog: () => readDailyLog,
|
|
1290
1393
|
readProjectLevelSource: () => readProjectLevelSource,
|
|
1291
1394
|
readStamp: () => readStamp,
|
|
@@ -1302,7 +1405,9 @@ __export(dist_exports, {
|
|
|
1302
1405
|
saveConfig: () => saveConfig,
|
|
1303
1406
|
saveProjectOverride: () => saveProjectOverride,
|
|
1304
1407
|
withStamp: () => withStamp,
|
|
1408
|
+
worktreeDirtyFiles: () => worktreeDirtyFiles,
|
|
1305
1409
|
worktreePath: () => worktreePath,
|
|
1410
|
+
writeConfigFileSync: () => writeConfigFileSync,
|
|
1306
1411
|
writeUpdateCheckState: () => writeUpdateCheckState
|
|
1307
1412
|
});
|
|
1308
1413
|
var init_dist = __esm({
|
|
@@ -1324,6 +1429,7 @@ var init_dist = __esm({
|
|
|
1324
1429
|
init_config_drift();
|
|
1325
1430
|
init_config_version();
|
|
1326
1431
|
init_update_check();
|
|
1432
|
+
init_config_io();
|
|
1327
1433
|
init_git_worktree();
|
|
1328
1434
|
init_resolve_text_input();
|
|
1329
1435
|
init_runtime_sync();
|
|
@@ -1361,7 +1467,21 @@ function nextPendingMonitor(current, ev, now) {
|
|
|
1361
1467
|
}
|
|
1362
1468
|
function reduce(rec, ev, now) {
|
|
1363
1469
|
if (ev.type === "task.reopened") {
|
|
1364
|
-
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
|
|
1470
|
+
return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
|
|
1471
|
+
}
|
|
1472
|
+
if (ev.type === "crew.takeover.started") {
|
|
1473
|
+
if (rec.operatorHold)
|
|
1474
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
1475
|
+
return {
|
|
1476
|
+
...rec,
|
|
1477
|
+
operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
|
|
1478
|
+
lastHeartbeat: now,
|
|
1479
|
+
lastEvent: ev.type
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (ev.type === "crew.takeover.ended") {
|
|
1483
|
+
const { operatorHold: _dropped, ...rest } = rec;
|
|
1484
|
+
return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
|
|
1365
1485
|
}
|
|
1366
1486
|
if (TERMINAL_STATES.has(rec.state))
|
|
1367
1487
|
return rec;
|
|
@@ -1377,8 +1497,9 @@ function reduce(rec, ev, now) {
|
|
|
1377
1497
|
// resuming after a blocked→reply clears the question
|
|
1378
1498
|
pendingTool: void 0,
|
|
1379
1499
|
// #354: a new turn closes any prior tool window
|
|
1380
|
-
pendingMonitor: void 0
|
|
1500
|
+
pendingMonitor: void 0,
|
|
1381
1501
|
// #594a: same reset — a new turn moots any prior watch
|
|
1502
|
+
workingStretchStartedAt: now
|
|
1382
1503
|
};
|
|
1383
1504
|
case "task.progress": {
|
|
1384
1505
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
@@ -1549,6 +1670,8 @@ function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
|
|
|
1549
1670
|
return;
|
|
1550
1671
|
if (!ATTENTION_STATES.has(next.state))
|
|
1551
1672
|
return;
|
|
1673
|
+
if (next.operatorHold)
|
|
1674
|
+
return;
|
|
1552
1675
|
if (next.state === "awaiting-input" && lastCaptainTurnAt != null && deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS) {
|
|
1553
1676
|
return;
|
|
1554
1677
|
}
|
|
@@ -1757,9 +1880,32 @@ function createDaemon(deps) {
|
|
|
1757
1880
|
store.delete(r.project, r.id);
|
|
1758
1881
|
continue;
|
|
1759
1882
|
}
|
|
1883
|
+
if (r.operatorHold) {
|
|
1884
|
+
const threshold = (deps.takeoverNudgeHours ?? 6) * 36e5;
|
|
1885
|
+
const holdAge = t - r.operatorHold.since;
|
|
1886
|
+
if (holdAge > threshold) {
|
|
1887
|
+
const timeSinceLastNudge = t - (r.operatorHold.lastNudgeAt ?? 0);
|
|
1888
|
+
if (timeSinceLastNudge > threshold) {
|
|
1889
|
+
const hrs = Math.round(holdAge / 36e5);
|
|
1890
|
+
const tag = crewTag(r);
|
|
1891
|
+
const message = `CREW HELD-LONG ${tag} \u2014 held ${hrs}h. Ask the operator whether it is still in use. Do not release it yourself.`;
|
|
1892
|
+
store.put({ ...r, operatorHold: { ...r.operatorHold, lastNudgeAt: t } });
|
|
1893
|
+
if (deps.notify) {
|
|
1894
|
+
try {
|
|
1895
|
+
const p = deps.notify({ project: r.project, message, record: r, event: { type: "task.progress", id: r.id } });
|
|
1896
|
+
if (p && typeof p.catch === "function")
|
|
1897
|
+
p.catch(() => {
|
|
1898
|
+
});
|
|
1899
|
+
} catch {
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1760
1905
|
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1761
1906
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1762
|
-
|
|
1907
|
+
const refTime = r.workingStretchStartedAt ?? r.createdAt;
|
|
1908
|
+
if (t - refTime > ceiling) {
|
|
1763
1909
|
const prevState = r.state;
|
|
1764
1910
|
const tag = crewTag(r);
|
|
1765
1911
|
const hrs = Math.round(ceiling / 36e5);
|
|
@@ -1913,14 +2059,17 @@ var init_reduce = __esm({
|
|
|
1913
2059
|
"task.reconcile-failed",
|
|
1914
2060
|
"task.cancelled",
|
|
1915
2061
|
"task.session.ended",
|
|
1916
|
-
"task.first-turn.confirmed"
|
|
2062
|
+
"task.first-turn.confirmed",
|
|
1917
2063
|
// #466: delivery confirmation
|
|
2064
|
+
"crew.takeover.started",
|
|
2065
|
+
"crew.takeover.ended"
|
|
2066
|
+
// #649: operator takeover
|
|
1918
2067
|
]);
|
|
1919
2068
|
}
|
|
1920
2069
|
});
|
|
1921
2070
|
|
|
1922
2071
|
// packages/core/dist/mailbox.js
|
|
1923
|
-
import { promises as
|
|
2072
|
+
import { promises as fs8 } from "fs";
|
|
1924
2073
|
import { join as join5 } from "path";
|
|
1925
2074
|
import { randomUUID } from "crypto";
|
|
1926
2075
|
function inboxDir(stateRoot) {
|
|
@@ -1937,7 +2086,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
1937
2086
|
const dir = inboxDir(stateRoot);
|
|
1938
2087
|
let entries;
|
|
1939
2088
|
try {
|
|
1940
|
-
entries = await
|
|
2089
|
+
entries = await fs8.readdir(dir);
|
|
1941
2090
|
} catch {
|
|
1942
2091
|
return [];
|
|
1943
2092
|
}
|
|
@@ -1946,7 +2095,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
1946
2095
|
}
|
|
1947
2096
|
async function readMaxSeqFromFile(file) {
|
|
1948
2097
|
try {
|
|
1949
|
-
const buf = await
|
|
2098
|
+
const buf = await fs8.readFile(file, "utf-8");
|
|
1950
2099
|
if (!buf.trim())
|
|
1951
2100
|
return 0;
|
|
1952
2101
|
const lines = buf.trim().split("\n");
|
|
@@ -1987,12 +2136,12 @@ function withProjectLock(project, fn) {
|
|
|
1987
2136
|
function appendEntry(stateRoot, project, build) {
|
|
1988
2137
|
return withProjectLock(project, async () => {
|
|
1989
2138
|
const dir = inboxDir(stateRoot);
|
|
1990
|
-
await
|
|
2139
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
1991
2140
|
const file = logPath(stateRoot, project);
|
|
1992
2141
|
const lastSeq = await readMaxSeq(stateRoot, project);
|
|
1993
2142
|
const seq = lastSeq + 1;
|
|
1994
2143
|
const entry = build(seq);
|
|
1995
|
-
await
|
|
2144
|
+
await fs8.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
|
|
1996
2145
|
return seq;
|
|
1997
2146
|
});
|
|
1998
2147
|
}
|
|
@@ -2023,7 +2172,7 @@ function cursorPath(stateRoot, project, subscriber) {
|
|
|
2023
2172
|
async function readCursor(opts) {
|
|
2024
2173
|
let buf;
|
|
2025
2174
|
try {
|
|
2026
|
-
buf = await
|
|
2175
|
+
buf = await fs8.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
|
|
2027
2176
|
} catch (e) {
|
|
2028
2177
|
if (e.code === "ENOENT")
|
|
2029
2178
|
return null;
|
|
@@ -2050,7 +2199,7 @@ async function waitForCaptainDelivery(opts) {
|
|
|
2050
2199
|
}
|
|
2051
2200
|
}
|
|
2052
2201
|
async function writeCursor(opts) {
|
|
2053
|
-
await
|
|
2202
|
+
await fs8.mkdir(inboxDir(opts.stateRoot), { recursive: true });
|
|
2054
2203
|
const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
|
|
2055
2204
|
const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
|
|
2056
2205
|
const data = {
|
|
@@ -2058,7 +2207,7 @@ async function writeCursor(opts) {
|
|
|
2058
2207
|
subscriber: opts.subscriber,
|
|
2059
2208
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2060
2209
|
};
|
|
2061
|
-
const handle = await
|
|
2210
|
+
const handle = await fs8.open(tmp, "w");
|
|
2062
2211
|
try {
|
|
2063
2212
|
await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
|
|
2064
2213
|
await handle.sync();
|
|
@@ -2066,9 +2215,9 @@ async function writeCursor(opts) {
|
|
|
2066
2215
|
await handle.close();
|
|
2067
2216
|
}
|
|
2068
2217
|
try {
|
|
2069
|
-
await
|
|
2218
|
+
await fs8.rename(tmp, dest);
|
|
2070
2219
|
} catch (e) {
|
|
2071
|
-
await
|
|
2220
|
+
await fs8.unlink(tmp).catch(() => {
|
|
2072
2221
|
});
|
|
2073
2222
|
throw e;
|
|
2074
2223
|
}
|
|
@@ -2079,7 +2228,7 @@ async function* readFromCursor(opts) {
|
|
|
2079
2228
|
for (const file of files) {
|
|
2080
2229
|
let buf;
|
|
2081
2230
|
try {
|
|
2082
|
-
buf = await
|
|
2231
|
+
buf = await fs8.readFile(file, "utf-8");
|
|
2083
2232
|
} catch (e) {
|
|
2084
2233
|
if (e.code === "ENOENT")
|
|
2085
2234
|
continue;
|
|
@@ -2105,7 +2254,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
2105
2254
|
let sizeBytes = 0;
|
|
2106
2255
|
for (const f of [file, ...rotated]) {
|
|
2107
2256
|
try {
|
|
2108
|
-
sizeBytes += (await
|
|
2257
|
+
sizeBytes += (await fs8.stat(f)).size;
|
|
2109
2258
|
} catch (e) {
|
|
2110
2259
|
if (e.code !== "ENOENT")
|
|
2111
2260
|
throw e;
|
|
@@ -2121,7 +2270,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
2121
2270
|
}
|
|
2122
2271
|
async function oldestEntryAgeMs(file) {
|
|
2123
2272
|
try {
|
|
2124
|
-
const buf = await
|
|
2273
|
+
const buf = await fs8.readFile(file, "utf-8");
|
|
2125
2274
|
const firstLine2 = buf.split("\n").find((l) => l.trim());
|
|
2126
2275
|
if (!firstLine2)
|
|
2127
2276
|
return 0;
|
|
@@ -2136,7 +2285,7 @@ async function rotateIfNeeded(opts) {
|
|
|
2136
2285
|
const file = logPath(opts.stateRoot, opts.project);
|
|
2137
2286
|
let size = 0;
|
|
2138
2287
|
try {
|
|
2139
|
-
size = (await
|
|
2288
|
+
size = (await fs8.stat(file)).size;
|
|
2140
2289
|
} catch (e) {
|
|
2141
2290
|
if (e.code === "ENOENT")
|
|
2142
2291
|
return { rotated: false };
|
|
@@ -2152,22 +2301,22 @@ async function rotateIfNeeded(opts) {
|
|
|
2152
2301
|
const dst = `${file}.${n + 1}`;
|
|
2153
2302
|
if (n + 1 > opts.keepCount) {
|
|
2154
2303
|
try {
|
|
2155
|
-
await
|
|
2304
|
+
await fs8.unlink(src);
|
|
2156
2305
|
} catch (e) {
|
|
2157
2306
|
if (e.code !== "ENOENT")
|
|
2158
2307
|
throw e;
|
|
2159
2308
|
}
|
|
2160
2309
|
} else {
|
|
2161
2310
|
try {
|
|
2162
|
-
await
|
|
2311
|
+
await fs8.rename(src, dst);
|
|
2163
2312
|
} catch (e) {
|
|
2164
2313
|
if (e.code !== "ENOENT")
|
|
2165
2314
|
throw e;
|
|
2166
2315
|
}
|
|
2167
2316
|
}
|
|
2168
2317
|
}
|
|
2169
|
-
await
|
|
2170
|
-
await
|
|
2318
|
+
await fs8.rename(file, `${file}.1`);
|
|
2319
|
+
await fs8.writeFile(file, "", { encoding: "utf-8" });
|
|
2171
2320
|
return { rotated: true, from: file, to: `${file}.1` };
|
|
2172
2321
|
});
|
|
2173
2322
|
}
|
|
@@ -2180,7 +2329,7 @@ var init_mailbox = __esm({
|
|
|
2180
2329
|
|
|
2181
2330
|
// packages/core/dist/protocol.js
|
|
2182
2331
|
import { createServer, createConnection } from "net";
|
|
2183
|
-
import { existsSync as existsSync5, unlinkSync } from "fs";
|
|
2332
|
+
import { existsSync as existsSync5, unlinkSync, chmodSync } from "fs";
|
|
2184
2333
|
function encodeMsg(obj) {
|
|
2185
2334
|
return JSON.stringify(obj) + "\n";
|
|
2186
2335
|
}
|
|
@@ -2293,6 +2442,12 @@ function startServer(sockPath, handlerOrCallbacks, onListenError = defaultListen
|
|
|
2293
2442
|
});
|
|
2294
2443
|
});
|
|
2295
2444
|
server.on("error", onListenError);
|
|
2445
|
+
server.on("listening", () => {
|
|
2446
|
+
try {
|
|
2447
|
+
chmodSync(sockPath, 384);
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2296
2451
|
server.listen(sockPath);
|
|
2297
2452
|
return server;
|
|
2298
2453
|
}
|
|
@@ -2476,7 +2631,7 @@ var init_liveness2 = __esm({
|
|
|
2476
2631
|
});
|
|
2477
2632
|
|
|
2478
2633
|
// packages/core/dist/store.js
|
|
2479
|
-
import { mkdirSync as
|
|
2634
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, renameSync, writeFileSync as writeFileSync3, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
|
|
2480
2635
|
import { join as join6, resolve, sep } from "path";
|
|
2481
2636
|
function safeSegment(kind, s) {
|
|
2482
2637
|
if (typeof s !== "string" || s.length === 0) {
|
|
@@ -2502,10 +2657,10 @@ function createStore(root) {
|
|
|
2502
2657
|
const taskFile = (p, id) => assertUnderRoot(join6(projDir(p), `${safeSegment("id", id)}.json`));
|
|
2503
2658
|
return {
|
|
2504
2659
|
put(rec) {
|
|
2505
|
-
|
|
2660
|
+
mkdirSync2(projDir(rec.project), { recursive: true });
|
|
2506
2661
|
const dest = taskFile(rec.project, rec.id);
|
|
2507
2662
|
const tmp = `${dest}.tmp`;
|
|
2508
|
-
|
|
2663
|
+
writeFileSync3(tmp, JSON.stringify(rec, null, 2));
|
|
2509
2664
|
renameSync(tmp, dest);
|
|
2510
2665
|
},
|
|
2511
2666
|
get(project, id) {
|
|
@@ -2513,7 +2668,7 @@ function createStore(root) {
|
|
|
2513
2668
|
if (!existsSync6(f))
|
|
2514
2669
|
return void 0;
|
|
2515
2670
|
try {
|
|
2516
|
-
return JSON.parse(
|
|
2671
|
+
return JSON.parse(readFileSync4(f, "utf-8"));
|
|
2517
2672
|
} catch {
|
|
2518
2673
|
return void 0;
|
|
2519
2674
|
}
|
|
@@ -2524,7 +2679,7 @@ function createStore(root) {
|
|
|
2524
2679
|
return [];
|
|
2525
2680
|
return readdirSync(d).filter((n) => n.endsWith(".json")).map((n) => {
|
|
2526
2681
|
try {
|
|
2527
|
-
return JSON.parse(
|
|
2682
|
+
return JSON.parse(readFileSync4(join6(d, n), "utf-8"));
|
|
2528
2683
|
} catch {
|
|
2529
2684
|
return void 0;
|
|
2530
2685
|
}
|
|
@@ -2562,7 +2717,7 @@ var init_store = __esm({
|
|
|
2562
2717
|
import { homedir as homedir4 } from "os";
|
|
2563
2718
|
import { join as join7, resolve as resolve2, sep as sep2 } from "path";
|
|
2564
2719
|
import { randomBytes } from "crypto";
|
|
2565
|
-
import { mkdirSync as
|
|
2720
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync4, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
|
|
2566
2721
|
function defaultWorkRoot() {
|
|
2567
2722
|
return join7(homedir4(), ".config", "squadrant", "work");
|
|
2568
2723
|
}
|
|
@@ -2590,10 +2745,10 @@ function createWorkStore(root = defaultWorkRoot()) {
|
|
|
2590
2745
|
const itemFile = (p, id) => assertUnderRoot(join7(projDir(p), `${safeSegment2("id", id)}.json`));
|
|
2591
2746
|
return {
|
|
2592
2747
|
put(item) {
|
|
2593
|
-
|
|
2748
|
+
mkdirSync3(projDir(item.project), { recursive: true });
|
|
2594
2749
|
const dest = itemFile(item.project, item.id);
|
|
2595
2750
|
const tmp = `${dest}.tmp`;
|
|
2596
|
-
|
|
2751
|
+
writeFileSync4(tmp, JSON.stringify(item, null, 2));
|
|
2597
2752
|
renameSync2(tmp, dest);
|
|
2598
2753
|
},
|
|
2599
2754
|
get(project, id) {
|
|
@@ -2601,7 +2756,7 @@ function createWorkStore(root = defaultWorkRoot()) {
|
|
|
2601
2756
|
if (!existsSync7(f))
|
|
2602
2757
|
return void 0;
|
|
2603
2758
|
try {
|
|
2604
|
-
return JSON.parse(
|
|
2759
|
+
return JSON.parse(readFileSync5(f, "utf-8"));
|
|
2605
2760
|
} catch {
|
|
2606
2761
|
return void 0;
|
|
2607
2762
|
}
|
|
@@ -2612,7 +2767,7 @@ function createWorkStore(root = defaultWorkRoot()) {
|
|
|
2612
2767
|
return [];
|
|
2613
2768
|
return readdirSync2(d).filter((n) => n.endsWith(".json") && !n.endsWith(".json.tmp")).map((n) => {
|
|
2614
2769
|
try {
|
|
2615
|
-
return JSON.parse(
|
|
2770
|
+
return JSON.parse(readFileSync5(join7(d, n), "utf-8"));
|
|
2616
2771
|
} catch {
|
|
2617
2772
|
return void 0;
|
|
2618
2773
|
}
|
|
@@ -2756,15 +2911,15 @@ var init_snapshot = __esm({
|
|
|
2756
2911
|
|
|
2757
2912
|
// packages/core/dist/launchd.js
|
|
2758
2913
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2759
|
-
import { mkdirSync as
|
|
2914
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
|
|
2760
2915
|
import { homedir as homedir5 } from "os";
|
|
2761
|
-
import { dirname as
|
|
2916
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
2762
2917
|
import { fileURLToPath } from "url";
|
|
2763
2918
|
function plistPath() {
|
|
2764
2919
|
return join8(homedir5(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
2765
2920
|
}
|
|
2766
2921
|
function daemonEntryPath() {
|
|
2767
|
-
const p = join8(
|
|
2922
|
+
const p = join8(dirname2(fileURLToPath(import.meta.url)), "squadrantd.js");
|
|
2768
2923
|
if (!existsSync8(p)) {
|
|
2769
2924
|
throw new Error(`daemonEntryPath: compiled entry not found at '${p}'; run 'npm run build' \u2014 a src-tree or missing path in the launchd plist causes a MODULE_NOT_FOUND crash-loop (#259)`);
|
|
2770
2925
|
}
|
|
@@ -2773,10 +2928,28 @@ function daemonEntryPath() {
|
|
|
2773
2928
|
function xmlEscape(s) {
|
|
2774
2929
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2775
2930
|
}
|
|
2776
|
-
function
|
|
2931
|
+
function xmlUnescape(s) {
|
|
2932
|
+
return s.replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
2933
|
+
}
|
|
2934
|
+
function parseProgramArgs(plistXml) {
|
|
2935
|
+
const m = plistXml.match(/<key>ProgramArguments<\/key>\s*<array><string>([^<]*)<\/string><string>([^<]*)<\/string><\/array>/);
|
|
2936
|
+
if (!m)
|
|
2937
|
+
return null;
|
|
2938
|
+
return { nodeBin: xmlUnescape(m[1]), daemonEntry: xmlUnescape(m[2]) };
|
|
2939
|
+
}
|
|
2940
|
+
function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
|
|
2941
|
+
if (!parsed)
|
|
2942
|
+
return null;
|
|
2943
|
+
if (parsed.daemonEntry === thisEntry)
|
|
2944
|
+
return null;
|
|
2945
|
+
if (!registeredEntryExists)
|
|
2946
|
+
return null;
|
|
2947
|
+
return { registeredEntry: parsed.daemonEntry, thisEntry };
|
|
2948
|
+
}
|
|
2949
|
+
function sanitizePathForPlist(path35) {
|
|
2777
2950
|
const seen = /* @__PURE__ */ new Set();
|
|
2778
2951
|
const stable = [];
|
|
2779
|
-
for (const p of
|
|
2952
|
+
for (const p of path35.split(":")) {
|
|
2780
2953
|
if (!p)
|
|
2781
2954
|
continue;
|
|
2782
2955
|
if (p.includes("/.claude/plugins/"))
|
|
@@ -2795,7 +2968,7 @@ function resolveAgentBinDirs() {
|
|
|
2795
2968
|
const out = execFileSync3("which", [bin], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
2796
2969
|
const resolved = out.trim();
|
|
2797
2970
|
if (resolved)
|
|
2798
|
-
dirs.push(
|
|
2971
|
+
dirs.push(dirname2(resolved));
|
|
2799
2972
|
} catch {
|
|
2800
2973
|
}
|
|
2801
2974
|
}
|
|
@@ -2857,7 +3030,7 @@ function tryAcquireDaemonLock() {
|
|
|
2857
3030
|
const lp = daemonLockPath();
|
|
2858
3031
|
if (existsSync8(lp)) {
|
|
2859
3032
|
try {
|
|
2860
|
-
const pid = parseInt(
|
|
3033
|
+
const pid = parseInt(readFileSync6(lp, "utf-8").trim(), 10);
|
|
2861
3034
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2862
3035
|
unlinkSync2(lp);
|
|
2863
3036
|
} else {
|
|
@@ -2894,17 +3067,19 @@ function computeDaemonDrift(nodeBin) {
|
|
|
2894
3067
|
const p = plistPath();
|
|
2895
3068
|
const entry = daemonEntryPath();
|
|
2896
3069
|
const desired = renderPlist(nodeBin, entry, buildDaemonPath(process.env.PATH ?? ""));
|
|
2897
|
-
const current = existsSync8(p) ?
|
|
3070
|
+
const current = existsSync8(p) ? readFileSync6(p, "utf-8") : null;
|
|
2898
3071
|
const uid = process.getuid?.() ?? 0;
|
|
2899
3072
|
const target = `gui/${uid}/${LABEL}`;
|
|
2900
3073
|
const changed = current !== desired;
|
|
2901
3074
|
const programChanged = current !== null && changed && !current.includes(programArgsBlock(nodeBin, entry));
|
|
2902
|
-
|
|
3075
|
+
const parsedCurrent = current !== null ? parseProgramArgs(current) : null;
|
|
3076
|
+
const foreignInstall = detectForeignInstall(parsedCurrent, entry, parsedCurrent !== null && existsSync8(parsedCurrent.daemonEntry));
|
|
3077
|
+
return { plistPath: p, target, desired, current, changed, programChanged, foreignInstall };
|
|
2903
3078
|
}
|
|
2904
3079
|
function applyDaemonDrift(drift) {
|
|
2905
3080
|
if (drift.changed) {
|
|
2906
|
-
|
|
2907
|
-
|
|
3081
|
+
mkdirSync4(dirname2(drift.plistPath), { recursive: true });
|
|
3082
|
+
writeFileSync5(drift.plistPath, drift.desired);
|
|
2908
3083
|
}
|
|
2909
3084
|
if (drift.programChanged) {
|
|
2910
3085
|
try {
|
|
@@ -2940,7 +3115,12 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
|
2940
3115
|
return;
|
|
2941
3116
|
}
|
|
2942
3117
|
try {
|
|
2943
|
-
|
|
3118
|
+
const drift = computeDaemonDrift(nodeBin);
|
|
3119
|
+
if (drift.foreignInstall) {
|
|
3120
|
+
process.stderr.write(printForeignInstallError(drift.foreignInstall));
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
applyDaemonDrift(drift);
|
|
2944
3124
|
} catch (e) {
|
|
2945
3125
|
process.stderr.write(`[squadrant] warn: ensureDaemon failed (${e instanceof Error ? e.message : e})
|
|
2946
3126
|
`);
|
|
@@ -2948,6 +3128,13 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
|
2948
3128
|
releaseDaemonLock();
|
|
2949
3129
|
}
|
|
2950
3130
|
}
|
|
3131
|
+
function printForeignInstallError(foreign) {
|
|
3132
|
+
return `[squadrant] refusing to restart the daemon: the registered launchd config belongs to a DIFFERENT squadrant install than this one.
|
|
3133
|
+
registered install: ${foreign.registeredEntry}
|
|
3134
|
+
this install: ${foreign.thisEntry}
|
|
3135
|
+
Two squadrant installs on this machine will keep fighting over the daemon (#670). Uninstall the one you don't use, then run \`squadrant heal daemon\` to reconcile.
|
|
3136
|
+
`;
|
|
3137
|
+
}
|
|
2951
3138
|
function reregisterDaemon(nodeBin = process.execPath) {
|
|
2952
3139
|
if (!tryAcquireDaemonLock())
|
|
2953
3140
|
return;
|
|
@@ -3111,7 +3298,7 @@ var init_gate = __esm({
|
|
|
3111
3298
|
});
|
|
3112
3299
|
|
|
3113
3300
|
// packages/core/dist/daemon/liveness-registry.js
|
|
3114
|
-
import { writeFileSync as
|
|
3301
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync3 } from "fs";
|
|
3115
3302
|
var LivenessRegistry;
|
|
3116
3303
|
var init_liveness_registry = __esm({
|
|
3117
3304
|
"packages/core/dist/daemon/liveness-registry.js"() {
|
|
@@ -3125,13 +3312,13 @@ var init_liveness_registry = __esm({
|
|
|
3125
3312
|
this.path = opts.path;
|
|
3126
3313
|
this.readFile = opts.readFile ?? ((p) => {
|
|
3127
3314
|
try {
|
|
3128
|
-
return
|
|
3315
|
+
return readFileSync7(p, "utf-8");
|
|
3129
3316
|
} catch {
|
|
3130
3317
|
return void 0;
|
|
3131
3318
|
}
|
|
3132
3319
|
});
|
|
3133
3320
|
this.writeFile = opts.writeFile ?? ((p, c) => {
|
|
3134
|
-
|
|
3321
|
+
writeFileSync6(`${p}.tmp`, c);
|
|
3135
3322
|
renameSync3(`${p}.tmp`, p);
|
|
3136
3323
|
});
|
|
3137
3324
|
}
|
|
@@ -3184,7 +3371,7 @@ var init_liveness_registry = __esm({
|
|
|
3184
3371
|
import { homedir as homedir6 } from "os";
|
|
3185
3372
|
import { join as join9 } from "path";
|
|
3186
3373
|
import { spawn as realSpawn } from "child_process";
|
|
3187
|
-
import { writeFileSync as
|
|
3374
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
3188
3375
|
function defaultIsPidAlive(pid) {
|
|
3189
3376
|
try {
|
|
3190
3377
|
process.kill(pid, 0);
|
|
@@ -3198,14 +3385,16 @@ function buildContext(opts) {
|
|
|
3198
3385
|
const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
|
|
3199
3386
|
const store = createStore(stateRoot);
|
|
3200
3387
|
const bootedAt = Date.now();
|
|
3201
|
-
const
|
|
3388
|
+
const config = loadConfig();
|
|
3389
|
+
const taskTimeoutMs = config.defaults.taskTimeoutMs;
|
|
3390
|
+
const takeoverNudgeHours = config.defaults.takeoverNudgeHours;
|
|
3202
3391
|
const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
|
|
3203
3392
|
const spawn2 = opts.spawn ?? realSpawn;
|
|
3204
3393
|
const resultsDir = join9(stateRoot, "_results");
|
|
3205
|
-
|
|
3394
|
+
mkdirSync5(resultsDir, { recursive: true });
|
|
3206
3395
|
const writeResult = (id, payload) => {
|
|
3207
3396
|
const p = join9(resultsDir, `${id}.txt`);
|
|
3208
|
-
|
|
3397
|
+
writeFileSync7(p, payload);
|
|
3209
3398
|
return p;
|
|
3210
3399
|
};
|
|
3211
3400
|
const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
@@ -3218,6 +3407,7 @@ function buildContext(opts) {
|
|
|
3218
3407
|
bootedAt,
|
|
3219
3408
|
lastSweepAt: { value: null },
|
|
3220
3409
|
taskTimeoutMs,
|
|
3410
|
+
takeoverNudgeHours,
|
|
3221
3411
|
isPidAlive,
|
|
3222
3412
|
spawn: spawn2,
|
|
3223
3413
|
resultsDir,
|
|
@@ -3950,7 +4140,7 @@ var init_server = __esm({
|
|
|
3950
4140
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
3951
4141
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3952
4142
|
import { join as join10 } from "path";
|
|
3953
|
-
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as
|
|
4143
|
+
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
|
|
3954
4144
|
function distBuiltAt() {
|
|
3955
4145
|
try {
|
|
3956
4146
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -3958,10 +4148,10 @@ function distBuiltAt() {
|
|
|
3958
4148
|
return 0;
|
|
3959
4149
|
}
|
|
3960
4150
|
}
|
|
3961
|
-
function gatherLogStats(
|
|
4151
|
+
function gatherLogStats(path35, now, windowMs) {
|
|
3962
4152
|
let sizeBytes = 0;
|
|
3963
4153
|
try {
|
|
3964
|
-
sizeBytes = statSync3(
|
|
4154
|
+
sizeBytes = statSync3(path35).size;
|
|
3965
4155
|
} catch {
|
|
3966
4156
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
3967
4157
|
}
|
|
@@ -3972,7 +4162,7 @@ function gatherLogStats(path34, now, windowMs) {
|
|
|
3972
4162
|
const len = sizeBytes - start;
|
|
3973
4163
|
let text = "";
|
|
3974
4164
|
try {
|
|
3975
|
-
const fd = openSync2(
|
|
4165
|
+
const fd = openSync2(path35, "r");
|
|
3976
4166
|
try {
|
|
3977
4167
|
const buf = Buffer.alloc(len);
|
|
3978
4168
|
readSync(fd, buf, 0, len, start);
|
|
@@ -4013,7 +4203,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4013
4203
|
if (!n.endsWith(".json"))
|
|
4014
4204
|
continue;
|
|
4015
4205
|
try {
|
|
4016
|
-
JSON.parse(
|
|
4206
|
+
JSON.parse(readFileSync8(join10(dir, n), "utf-8"));
|
|
4017
4207
|
} catch {
|
|
4018
4208
|
corruptCount++;
|
|
4019
4209
|
}
|
|
@@ -4048,7 +4238,7 @@ var init_snapshot_gather = __esm({
|
|
|
4048
4238
|
});
|
|
4049
4239
|
|
|
4050
4240
|
// packages/core/dist/daemon/start.js
|
|
4051
|
-
import { join as join11, dirname as
|
|
4241
|
+
import { join as join11, dirname as dirname3 } from "path";
|
|
4052
4242
|
import { readdir } from "fs/promises";
|
|
4053
4243
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4054
4244
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
@@ -4068,6 +4258,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4068
4258
|
isPidAlive,
|
|
4069
4259
|
notify,
|
|
4070
4260
|
taskTimeoutMs,
|
|
4261
|
+
takeoverNudgeHours: ctx.takeoverNudgeHours,
|
|
4071
4262
|
isSurfaceAlive: surfaceProbe,
|
|
4072
4263
|
resendFirstTurn: ctx.resendFirstTurn,
|
|
4073
4264
|
launchHeadless: opts.launchHeadless,
|
|
@@ -4123,7 +4314,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4123
4314
|
return out;
|
|
4124
4315
|
}
|
|
4125
4316
|
async function gatherSnapshotInputs(now) {
|
|
4126
|
-
const logPath2 = join11(
|
|
4317
|
+
const logPath2 = join11(dirname3(stateRoot), "squadrantd.log");
|
|
4127
4318
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4128
4319
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4129
4320
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -4328,35 +4519,33 @@ var init_start = __esm({
|
|
|
4328
4519
|
|
|
4329
4520
|
// packages/core/dist/session-freshness.js
|
|
4330
4521
|
import crypto from "crypto";
|
|
4331
|
-
import
|
|
4332
|
-
import
|
|
4522
|
+
import fs9 from "fs";
|
|
4523
|
+
import path9 from "path";
|
|
4333
4524
|
function loadSessions(sessionsPath) {
|
|
4334
4525
|
try {
|
|
4335
|
-
return JSON.parse(
|
|
4526
|
+
return JSON.parse(readConfigFileSync(sessionsPath));
|
|
4336
4527
|
} catch {
|
|
4337
4528
|
return { workspaces: {} };
|
|
4338
4529
|
}
|
|
4339
4530
|
}
|
|
4340
4531
|
function saveSessions(sessionsPath, sessions) {
|
|
4341
|
-
|
|
4342
|
-
fs10.mkdirSync(dir, { recursive: true });
|
|
4343
|
-
fs10.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
|
|
4532
|
+
writeConfigFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
|
|
4344
4533
|
}
|
|
4345
4534
|
function computeTemplateHash(role, templatesDir) {
|
|
4346
4535
|
const hash = crypto.createHash("sha256");
|
|
4347
|
-
const roleFile =
|
|
4348
|
-
const legacyRoleFile =
|
|
4349
|
-
if (
|
|
4350
|
-
hash.update(
|
|
4351
|
-
} else if (
|
|
4352
|
-
hash.update(
|
|
4536
|
+
const roleFile = path9.join(templatesDir, `${role}.claude.md`);
|
|
4537
|
+
const legacyRoleFile = path9.join(templatesDir, `${role}.CLAUDE.md`);
|
|
4538
|
+
if (fs9.existsSync(roleFile)) {
|
|
4539
|
+
hash.update(fs9.readFileSync(roleFile, "utf-8"));
|
|
4540
|
+
} else if (fs9.existsSync(legacyRoleFile)) {
|
|
4541
|
+
hash.update(fs9.readFileSync(legacyRoleFile, "utf-8"));
|
|
4353
4542
|
}
|
|
4354
|
-
const pluginSkillsDir =
|
|
4355
|
-
if (
|
|
4356
|
-
for (const skill of
|
|
4357
|
-
const skillFile =
|
|
4358
|
-
if (
|
|
4359
|
-
hash.update(
|
|
4543
|
+
const pluginSkillsDir = path9.join(templatesDir, "..", "plugin", "skills");
|
|
4544
|
+
if (fs9.existsSync(pluginSkillsDir)) {
|
|
4545
|
+
for (const skill of fs9.readdirSync(pluginSkillsDir).sort()) {
|
|
4546
|
+
const skillFile = path9.join(pluginSkillsDir, skill, "SKILL.md");
|
|
4547
|
+
if (fs9.existsSync(skillFile)) {
|
|
4548
|
+
hash.update(fs9.readFileSync(skillFile, "utf-8"));
|
|
4360
4549
|
}
|
|
4361
4550
|
}
|
|
4362
4551
|
}
|
|
@@ -4388,6 +4577,7 @@ function recordSession(workspaceName, role, opts) {
|
|
|
4388
4577
|
}
|
|
4389
4578
|
var init_session_freshness = __esm({
|
|
4390
4579
|
"packages/core/dist/session-freshness.js"() {
|
|
4580
|
+
init_dist();
|
|
4391
4581
|
}
|
|
4392
4582
|
});
|
|
4393
4583
|
|
|
@@ -4777,17 +4967,16 @@ var init_format = __esm({
|
|
|
4777
4967
|
});
|
|
4778
4968
|
|
|
4779
4969
|
// packages/core/dist/telegram/state.js
|
|
4780
|
-
import
|
|
4781
|
-
import path9 from "path";
|
|
4970
|
+
import path10 from "path";
|
|
4782
4971
|
function statePath(stateRoot) {
|
|
4783
|
-
return
|
|
4972
|
+
return path10.join(stateRoot, "telegram-state.json");
|
|
4784
4973
|
}
|
|
4785
4974
|
function topicKey(project, scope = "project") {
|
|
4786
4975
|
return `${project}::${scope}`;
|
|
4787
4976
|
}
|
|
4788
4977
|
function loadState(stateRoot) {
|
|
4789
4978
|
try {
|
|
4790
|
-
const raw =
|
|
4979
|
+
const raw = readConfigFileSync(statePath(stateRoot));
|
|
4791
4980
|
const data = JSON.parse(raw);
|
|
4792
4981
|
const result = {
|
|
4793
4982
|
offset: typeof data.offset === "number" ? data.offset : 0,
|
|
@@ -4802,8 +4991,7 @@ function loadState(stateRoot) {
|
|
|
4802
4991
|
}
|
|
4803
4992
|
}
|
|
4804
4993
|
function saveState(stateRoot, s) {
|
|
4805
|
-
|
|
4806
|
-
fs11.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
4994
|
+
writeConfigFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
4807
4995
|
}
|
|
4808
4996
|
function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
4809
4997
|
const s = loadState(stateRoot);
|
|
@@ -4837,6 +5025,7 @@ function findProjectByThread(stateRoot, threadId) {
|
|
|
4837
5025
|
}
|
|
4838
5026
|
var init_state = __esm({
|
|
4839
5027
|
"packages/core/dist/telegram/state.js"() {
|
|
5028
|
+
init_dist();
|
|
4840
5029
|
}
|
|
4841
5030
|
});
|
|
4842
5031
|
|
|
@@ -4988,7 +5177,7 @@ var init_tiers = __esm({
|
|
|
4988
5177
|
|
|
4989
5178
|
// packages/core/dist/telegram/bridge.js
|
|
4990
5179
|
import os4 from "os";
|
|
4991
|
-
import
|
|
5180
|
+
import path11 from "path";
|
|
4992
5181
|
function parseNotifyPref(text) {
|
|
4993
5182
|
const parts = text.trim().split(/\s+/);
|
|
4994
5183
|
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
@@ -5015,7 +5204,7 @@ function notifyToggle(text) {
|
|
|
5015
5204
|
}
|
|
5016
5205
|
function createTelegramBridge(opts) {
|
|
5017
5206
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
5018
|
-
const configRoot = opts.configRoot ??
|
|
5207
|
+
const configRoot = opts.configRoot ?? path11.join(os4.homedir(), ".config", "squadrant");
|
|
5019
5208
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
5020
5209
|
let running = false;
|
|
5021
5210
|
let lastSuccessfulPollAt = null;
|
|
@@ -5146,14 +5335,14 @@ function createTelegramBridge(opts) {
|
|
|
5146
5335
|
}
|
|
5147
5336
|
function currentEffort() {
|
|
5148
5337
|
try {
|
|
5149
|
-
return loadConfig(
|
|
5338
|
+
return loadConfig(path11.join(configRoot, "config.json")).defaults.effort ?? "balance";
|
|
5150
5339
|
} catch {
|
|
5151
5340
|
return "balance";
|
|
5152
5341
|
}
|
|
5153
5342
|
}
|
|
5154
5343
|
function projectNames() {
|
|
5155
5344
|
try {
|
|
5156
|
-
return Object.keys(loadConfig(
|
|
5345
|
+
return Object.keys(loadConfig(path11.join(configRoot, "config.json")).projects);
|
|
5157
5346
|
} catch {
|
|
5158
5347
|
return [];
|
|
5159
5348
|
}
|
|
@@ -5421,7 +5610,7 @@ var init_restart_daemon = __esm({
|
|
|
5421
5610
|
});
|
|
5422
5611
|
|
|
5423
5612
|
// packages/core/dist/telegram/setup.js
|
|
5424
|
-
import
|
|
5613
|
+
import fs10 from "fs";
|
|
5425
5614
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
5426
5615
|
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
5427
5616
|
return "reuse";
|
|
@@ -5472,7 +5661,7 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
5472
5661
|
let config;
|
|
5473
5662
|
let raw = null;
|
|
5474
5663
|
try {
|
|
5475
|
-
raw =
|
|
5664
|
+
raw = fs10.readFileSync(configPath, "utf-8");
|
|
5476
5665
|
} catch (err) {
|
|
5477
5666
|
if (err.code !== "ENOENT") {
|
|
5478
5667
|
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
@@ -5500,10 +5689,11 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
5500
5689
|
if (remoteControl !== void 0)
|
|
5501
5690
|
next.remoteControl = remoteControl;
|
|
5502
5691
|
config.telegram = next;
|
|
5503
|
-
|
|
5692
|
+
writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
5504
5693
|
}
|
|
5505
5694
|
var init_setup = __esm({
|
|
5506
5695
|
"packages/core/dist/telegram/setup.js"() {
|
|
5696
|
+
init_dist();
|
|
5507
5697
|
init_state();
|
|
5508
5698
|
init_bot_commands();
|
|
5509
5699
|
init_restart_daemon();
|
|
@@ -5829,7 +6019,7 @@ var init_launch_workspace = __esm({
|
|
|
5829
6019
|
});
|
|
5830
6020
|
|
|
5831
6021
|
// packages/core/dist/side-session.js
|
|
5832
|
-
import
|
|
6022
|
+
import fs11 from "fs";
|
|
5833
6023
|
function sideTitleFor(project, name) {
|
|
5834
6024
|
return `\u{1F5D2} ${project}:${name}`;
|
|
5835
6025
|
}
|
|
@@ -5931,7 +6121,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
|
|
|
5931
6121
|
await runtime.closePane(pane);
|
|
5932
6122
|
if (projPath) {
|
|
5933
6123
|
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
5934
|
-
if (
|
|
6124
|
+
if (fs11.existsSync(wtPath)) {
|
|
5935
6125
|
try {
|
|
5936
6126
|
removeWorktree(projPath, wtPath);
|
|
5937
6127
|
} catch (e) {
|
|
@@ -5951,9 +6141,9 @@ var init_side_session = __esm({
|
|
|
5951
6141
|
});
|
|
5952
6142
|
|
|
5953
6143
|
// packages/core/dist/crew-spawn.js
|
|
5954
|
-
import
|
|
6144
|
+
import fs12 from "fs";
|
|
5955
6145
|
import os5 from "os";
|
|
5956
|
-
import
|
|
6146
|
+
import path12 from "path";
|
|
5957
6147
|
async function listCrewPanes(runtime, workspaceId, project) {
|
|
5958
6148
|
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
5959
6149
|
return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
|
|
@@ -6008,18 +6198,23 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
6008
6198
|
}
|
|
6009
6199
|
}
|
|
6010
6200
|
const name = input.name ?? nextAutoName(existingTitles, input.project);
|
|
6201
|
+
let base = "";
|
|
6202
|
+
if (!input.shared) {
|
|
6203
|
+
base = resolveWorktreeBase(proj.path);
|
|
6204
|
+
deps.onBaseResolved?.(base);
|
|
6205
|
+
}
|
|
6011
6206
|
const spawnCwd = !input.shared ? addWorktree({
|
|
6012
6207
|
repoRoot: proj.path,
|
|
6013
6208
|
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6014
6209
|
project: input.project,
|
|
6015
6210
|
name,
|
|
6016
|
-
base
|
|
6211
|
+
base
|
|
6017
6212
|
}) : proj.path;
|
|
6018
6213
|
let firstTurnTask = input.task;
|
|
6019
6214
|
if (input.taskFile && input.taskFile !== "-" && !input.shared) {
|
|
6020
|
-
const absTaskFile =
|
|
6021
|
-
const basename =
|
|
6022
|
-
|
|
6215
|
+
const absTaskFile = path12.resolve(input.taskFile);
|
|
6216
|
+
const basename = path12.basename(absTaskFile);
|
|
6217
|
+
fs12.copyFileSync(absTaskFile, path12.join(spawnCwd, basename));
|
|
6023
6218
|
firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;
|
|
6024
6219
|
}
|
|
6025
6220
|
const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
|
|
@@ -6032,8 +6227,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
6032
6227
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
6033
6228
|
}
|
|
6034
6229
|
if (agentName === "codex") {
|
|
6035
|
-
const codexRoleFile =
|
|
6036
|
-
const roleInstructions =
|
|
6230
|
+
const codexRoleFile = path12.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
6231
|
+
const roleInstructions = fs12.existsSync(codexRoleFile) ? fs12.readFileSync(codexRoleFile, "utf8") : void 0;
|
|
6037
6232
|
return runCodexInteractiveSpawn({
|
|
6038
6233
|
project: input.project,
|
|
6039
6234
|
task: input.task,
|
|
@@ -6049,7 +6244,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
6049
6244
|
sendCodexFirstTurn: deps.sendCodexFirstTurn
|
|
6050
6245
|
});
|
|
6051
6246
|
}
|
|
6052
|
-
const promptFile =
|
|
6247
|
+
const promptFile = path12.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
6053
6248
|
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
6054
6249
|
const crewRole = config.defaults.roles?.crew;
|
|
6055
6250
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
@@ -6181,7 +6376,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
6181
6376
|
function pickMostRecentTask(tasks) {
|
|
6182
6377
|
return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
6183
6378
|
}
|
|
6184
|
-
async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
6379
|
+
async function runCrewSend(project, name, message, runtime, workspaceId, deps, opts) {
|
|
6185
6380
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6186
6381
|
if (!crew) {
|
|
6187
6382
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
@@ -6190,9 +6385,17 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
6190
6385
|
if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
|
|
6191
6386
|
throw new Error(blockedByModalMessage());
|
|
6192
6387
|
}
|
|
6388
|
+
let task;
|
|
6193
6389
|
try {
|
|
6194
6390
|
const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6195
|
-
|
|
6391
|
+
task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
|
|
6392
|
+
} catch {
|
|
6393
|
+
}
|
|
6394
|
+
if (task && task.operatorHold && !opts?.force) {
|
|
6395
|
+
const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
|
|
6396
|
+
throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${task.operatorHold.note ? `: ${task.operatorHold.note}` : ""}). The operator is working in that tab \u2014 sending a message disrupts their conversation. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
|
|
6397
|
+
}
|
|
6398
|
+
try {
|
|
6196
6399
|
if (task) {
|
|
6197
6400
|
if (TERMINAL_STATES.has(task.state)) {
|
|
6198
6401
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
@@ -6218,23 +6421,60 @@ async function runCrewRead(project, name, runtime, workspaceId) {
|
|
|
6218
6421
|
}
|
|
6219
6422
|
return runtime.readPaneScreen(crew);
|
|
6220
6423
|
}
|
|
6221
|
-
|
|
6424
|
+
function buildRecoveryHint(sessId, provider, worktreeCwd) {
|
|
6425
|
+
if (!sessId || !worktreeCwd)
|
|
6426
|
+
return "";
|
|
6427
|
+
if (provider === "claude") {
|
|
6428
|
+
const escaped = worktreeCwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6429
|
+
const transcriptPath = path12.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
|
|
6430
|
+
return `
|
|
6431
|
+
transcript: ${transcriptPath}
|
|
6432
|
+
resume: claude --resume ${sessId} (run from the worktree path above)
|
|
6433
|
+
`;
|
|
6434
|
+
}
|
|
6435
|
+
return "";
|
|
6436
|
+
}
|
|
6437
|
+
async function runCrewClose(project, name, runtime, workspaceId, deps, opts) {
|
|
6222
6438
|
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6223
6439
|
const projRoot = loadConfig().projects[project]?.path;
|
|
6224
|
-
let
|
|
6225
|
-
let worktreeCwd;
|
|
6440
|
+
let matches = [];
|
|
6226
6441
|
try {
|
|
6227
|
-
|
|
6442
|
+
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6228
6443
|
for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {
|
|
6229
6444
|
await sleep3(CLOSE_LOOKUP_RETRY_DELAY_MS);
|
|
6230
6445
|
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
6231
6446
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6447
|
+
} catch {
|
|
6448
|
+
}
|
|
6449
|
+
let taskId;
|
|
6450
|
+
let worktreeCwd;
|
|
6451
|
+
let sessId;
|
|
6452
|
+
let provider;
|
|
6453
|
+
if (matches.length > 0) {
|
|
6454
|
+
const primary = pickMostRecentTask(matches);
|
|
6455
|
+
taskId = primary.id;
|
|
6456
|
+
sessId = primary.sessionId;
|
|
6457
|
+
provider = primary.provider;
|
|
6458
|
+
if (primary.cwd && projRoot && primary.cwd !== projRoot) {
|
|
6459
|
+
worktreeCwd = primary.cwd;
|
|
6460
|
+
}
|
|
6461
|
+
if (primary.operatorHold && !opts?.force) {
|
|
6462
|
+
const heldForMin = Math.round((Date.now() - primary.operatorHold.since) / 6e4);
|
|
6463
|
+
throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${primary.operatorHold.note ? `: ${primary.operatorHold.note}` : ""}). The operator is working in that tab \u2014 closing it kills their session and prunes the worktree. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
if (worktreeCwd && projRoot) {
|
|
6467
|
+
const dirty = worktreeDirtyFiles(worktreeCwd);
|
|
6468
|
+
if (dirty.length > 0 && !opts?.force) {
|
|
6469
|
+
const transcriptStr = buildRecoveryHint(sessId, provider, worktreeCwd);
|
|
6470
|
+
throw new Error(`Worktree '${worktreeCwd}' has uncommitted files:
|
|
6471
|
+
${dirty.map((f) => ` ${f}`).join("\n")}
|
|
6472
|
+
Why are they uncommitted? Commit them, or pass --force to destroy them.
|
|
6473
|
+
${transcriptStr}`);
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
if (matches.length > 0) {
|
|
6477
|
+
try {
|
|
6238
6478
|
for (const task of matches) {
|
|
6239
6479
|
if (!TERMINAL_STATES.has(task.state)) {
|
|
6240
6480
|
await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
|
|
@@ -6243,8 +6483,8 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
6243
6483
|
await deps.closeCodexThread(task.id);
|
|
6244
6484
|
}
|
|
6245
6485
|
}
|
|
6486
|
+
} catch {
|
|
6246
6487
|
}
|
|
6247
|
-
} catch {
|
|
6248
6488
|
}
|
|
6249
6489
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6250
6490
|
if (crew) {
|
|
@@ -6257,12 +6497,16 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
6257
6497
|
}
|
|
6258
6498
|
if (worktreeCwd && projRoot) {
|
|
6259
6499
|
try {
|
|
6260
|
-
removeWorktree(projRoot, worktreeCwd);
|
|
6500
|
+
removeWorktree(projRoot, worktreeCwd, opts);
|
|
6261
6501
|
} catch (e) {
|
|
6262
6502
|
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6263
6503
|
`);
|
|
6264
6504
|
}
|
|
6265
6505
|
}
|
|
6506
|
+
const hint = buildRecoveryHint(sessId, provider, worktreeCwd);
|
|
6507
|
+
if (hint) {
|
|
6508
|
+
process.stdout.write(hint);
|
|
6509
|
+
}
|
|
6266
6510
|
}
|
|
6267
6511
|
async function runCrewList(project, runtime, workspaceId) {
|
|
6268
6512
|
const crews = await listCrewPanes(runtime, workspaceId, project);
|
|
@@ -6278,8 +6522,8 @@ var init_crew_spawn = __esm({
|
|
|
6278
6522
|
init_crew_routing();
|
|
6279
6523
|
init_crew_protocol();
|
|
6280
6524
|
init_crew_lifecycle();
|
|
6281
|
-
TEMPLATES_DIR =
|
|
6282
|
-
STATE_ROOT =
|
|
6525
|
+
TEMPLATES_DIR = path12.join(os5.homedir(), ".config", "squadrant", "templates");
|
|
6526
|
+
STATE_ROOT = path12.join(os5.homedir(), ".config", "squadrant", "state");
|
|
6283
6527
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
6284
6528
|
CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
|
|
6285
6529
|
}
|
|
@@ -6377,6 +6621,7 @@ __export(dist_exports2, {
|
|
|
6377
6621
|
deliverStartupPrompt: () => deliverStartupPrompt,
|
|
6378
6622
|
deliverable: () => deliverable,
|
|
6379
6623
|
deriveCaptainState: () => deriveCaptainState,
|
|
6624
|
+
detectForeignInstall: () => detectForeignInstall,
|
|
6380
6625
|
detectGroupAndUser: () => detectGroupAndUser,
|
|
6381
6626
|
detectGroupId: () => detectGroupId,
|
|
6382
6627
|
discoverCaptainSurface: () => discoverCaptainSurface,
|
|
@@ -6416,7 +6661,9 @@ __export(dist_exports2, {
|
|
|
6416
6661
|
notifyToggle: () => notifyToggle,
|
|
6417
6662
|
parseCommand: () => parseCommand,
|
|
6418
6663
|
parseNotifyPref: () => parseNotifyPref,
|
|
6664
|
+
parseProgramArgs: () => parseProgramArgs,
|
|
6419
6665
|
plistPath: () => plistPath,
|
|
6666
|
+
printForeignInstallError: () => printForeignInstallError,
|
|
6420
6667
|
programArgsBlock: () => programArgsBlock,
|
|
6421
6668
|
projectHealth: () => projectHealth,
|
|
6422
6669
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
@@ -7168,13 +7415,13 @@ var init_notifiers = __esm({
|
|
|
7168
7415
|
});
|
|
7169
7416
|
|
|
7170
7417
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
7171
|
-
import
|
|
7418
|
+
import fs13 from "fs/promises";
|
|
7172
7419
|
import { existsSync as existsSync10 } from "fs";
|
|
7173
|
-
import
|
|
7420
|
+
import path13 from "path";
|
|
7174
7421
|
function resolveInRoot(root, relative) {
|
|
7175
|
-
const joined =
|
|
7176
|
-
const normalized =
|
|
7177
|
-
if (joined !==
|
|
7422
|
+
const joined = path13.resolve(root, relative);
|
|
7423
|
+
const normalized = path13.resolve(root) + path13.sep;
|
|
7424
|
+
if (joined !== path13.resolve(root) && !joined.startsWith(normalized)) {
|
|
7178
7425
|
throw new Error(`Path '${relative}' escapes workspace root`);
|
|
7179
7426
|
}
|
|
7180
7427
|
return joined;
|
|
@@ -7193,16 +7440,16 @@ function createObsidianDriver(scope) {
|
|
|
7193
7440
|
};
|
|
7194
7441
|
},
|
|
7195
7442
|
async read(rel) {
|
|
7196
|
-
return
|
|
7443
|
+
return fs13.readFile(resolveInRoot(root, rel), "utf-8");
|
|
7197
7444
|
},
|
|
7198
7445
|
async write(rel, content) {
|
|
7199
7446
|
const abs = resolveInRoot(root, rel);
|
|
7200
|
-
await
|
|
7201
|
-
await
|
|
7447
|
+
await fs13.mkdir(path13.dirname(abs), { recursive: true });
|
|
7448
|
+
await fs13.writeFile(abs, content);
|
|
7202
7449
|
},
|
|
7203
7450
|
async exists(rel) {
|
|
7204
7451
|
try {
|
|
7205
|
-
await
|
|
7452
|
+
await fs13.access(resolveInRoot(root, rel));
|
|
7206
7453
|
return true;
|
|
7207
7454
|
} catch {
|
|
7208
7455
|
return false;
|
|
@@ -7210,13 +7457,13 @@ function createObsidianDriver(scope) {
|
|
|
7210
7457
|
},
|
|
7211
7458
|
async list(rel) {
|
|
7212
7459
|
try {
|
|
7213
|
-
return await
|
|
7460
|
+
return await fs13.readdir(resolveInRoot(root, rel));
|
|
7214
7461
|
} catch {
|
|
7215
7462
|
return [];
|
|
7216
7463
|
}
|
|
7217
7464
|
},
|
|
7218
7465
|
async mkdir(rel) {
|
|
7219
|
-
await
|
|
7466
|
+
await fs13.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
7220
7467
|
}
|
|
7221
7468
|
};
|
|
7222
7469
|
}
|
|
@@ -7480,7 +7727,7 @@ var init_store_fingerprint = __esm({
|
|
|
7480
7727
|
});
|
|
7481
7728
|
|
|
7482
7729
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
7483
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
7730
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
7484
7731
|
import { join as join14 } from "path";
|
|
7485
7732
|
import { homedir as homedir9 } from "os";
|
|
7486
7733
|
var DaemonCmux;
|
|
@@ -7553,7 +7800,7 @@ var init_daemon_cmux = __esm({
|
|
|
7553
7800
|
} catch (e) {
|
|
7554
7801
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
7555
7802
|
}
|
|
7556
|
-
return readLivenessSnapshot(files, (f) =>
|
|
7803
|
+
return readLivenessSnapshot(files, (f) => readFileSync9(join14(dir, f), "utf-8"), projects);
|
|
7557
7804
|
}
|
|
7558
7805
|
};
|
|
7559
7806
|
}
|
|
@@ -7562,7 +7809,7 @@ var init_daemon_cmux = __esm({
|
|
|
7562
7809
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
7563
7810
|
import { join as join15 } from "path";
|
|
7564
7811
|
import { homedir as homedir10 } from "os";
|
|
7565
|
-
import { watch, readdirSync as readdirSync5, readFileSync as
|
|
7812
|
+
import { watch, readdirSync as readdirSync5, readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
|
|
7566
7813
|
function parseLifecycleState(s) {
|
|
7567
7814
|
if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
|
|
7568
7815
|
return s;
|
|
@@ -7584,9 +7831,9 @@ function defaultListFiles(dir) {
|
|
|
7584
7831
|
return [];
|
|
7585
7832
|
}
|
|
7586
7833
|
}
|
|
7587
|
-
function defaultReadFile(
|
|
7834
|
+
function defaultReadFile(path35) {
|
|
7588
7835
|
try {
|
|
7589
|
-
return
|
|
7836
|
+
return readFileSync10(path35, "utf-8");
|
|
7590
7837
|
} catch {
|
|
7591
7838
|
return void 0;
|
|
7592
7839
|
}
|
|
@@ -7740,7 +7987,7 @@ var init_cmux_store_source = __esm({
|
|
|
7740
7987
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
7741
7988
|
import { join as join16 } from "path";
|
|
7742
7989
|
import { homedir as homedir11 } from "os";
|
|
7743
|
-
import { mkdirSync as
|
|
7990
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
7744
7991
|
function installClaudeHooks(opts = {}) {
|
|
7745
7992
|
const settingsPath = opts.settingsPath ?? join16(homedir11(), ".claude", "settings.json");
|
|
7746
7993
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
@@ -7836,16 +8083,16 @@ function extractDetail(sub, payload) {
|
|
|
7836
8083
|
}
|
|
7837
8084
|
return void 0;
|
|
7838
8085
|
}
|
|
7839
|
-
function defaultReadFile2(
|
|
8086
|
+
function defaultReadFile2(path35) {
|
|
7840
8087
|
try {
|
|
7841
|
-
return
|
|
8088
|
+
return readFileSync11(path35, "utf-8");
|
|
7842
8089
|
} catch {
|
|
7843
8090
|
return void 0;
|
|
7844
8091
|
}
|
|
7845
8092
|
}
|
|
7846
|
-
function defaultWriteFile(
|
|
7847
|
-
|
|
7848
|
-
|
|
8093
|
+
function defaultWriteFile(path35, content) {
|
|
8094
|
+
mkdirSync6(path35.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
8095
|
+
writeFileSync8(path35, content, "utf-8");
|
|
7849
8096
|
}
|
|
7850
8097
|
var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
|
|
7851
8098
|
var init_native_hook_source = __esm({
|
|
@@ -8464,8 +8711,8 @@ var init_registry4 = __esm({
|
|
|
8464
8711
|
});
|
|
8465
8712
|
|
|
8466
8713
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
8467
|
-
import
|
|
8468
|
-
import
|
|
8714
|
+
import fs14 from "fs";
|
|
8715
|
+
import path14 from "path";
|
|
8469
8716
|
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
|
|
8470
8717
|
const driver = registry.getDriver(agentName);
|
|
8471
8718
|
if (driver.name === "claude") {
|
|
@@ -8481,27 +8728,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
8481
8728
|
cmd += ` --model ${model}`;
|
|
8482
8729
|
}
|
|
8483
8730
|
if (templatesDir) {
|
|
8484
|
-
const roleFile2 =
|
|
8485
|
-
const legacyRoleFile =
|
|
8486
|
-
const actualRoleFile =
|
|
8731
|
+
const roleFile2 = path14.join(templatesDir, `${role}.claude.md`);
|
|
8732
|
+
const legacyRoleFile = path14.join(templatesDir, `${role}.CLAUDE.md`);
|
|
8733
|
+
const actualRoleFile = fs14.existsSync(roleFile2) ? roleFile2 : fs14.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
8487
8734
|
if (actualRoleFile) {
|
|
8488
8735
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
8489
8736
|
}
|
|
8490
|
-
const pluginDir =
|
|
8491
|
-
if (
|
|
8737
|
+
const pluginDir = path14.join(templatesDir, "..", "plugin");
|
|
8738
|
+
if (fs14.existsSync(pluginDir)) {
|
|
8492
8739
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
8493
8740
|
}
|
|
8494
8741
|
}
|
|
8495
8742
|
return cmd;
|
|
8496
8743
|
}
|
|
8497
|
-
const roleFile = templatesDir ?
|
|
8744
|
+
const roleFile = templatesDir ? path14.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
|
|
8498
8745
|
return driver.buildCommand({
|
|
8499
8746
|
prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
|
|
8500
8747
|
workdir: process.cwd(),
|
|
8501
8748
|
role,
|
|
8502
8749
|
model,
|
|
8503
8750
|
autoApprove: true,
|
|
8504
|
-
promptFile: roleFile &&
|
|
8751
|
+
promptFile: roleFile && fs14.existsSync(roleFile) ? roleFile : void 0
|
|
8505
8752
|
});
|
|
8506
8753
|
}
|
|
8507
8754
|
var init_launch_cmd = __esm({
|
|
@@ -8524,7 +8771,7 @@ var init_drivers = __esm({
|
|
|
8524
8771
|
|
|
8525
8772
|
// packages/agents/dist/projection/cursor.js
|
|
8526
8773
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
8527
|
-
import
|
|
8774
|
+
import path15 from "path";
|
|
8528
8775
|
import os6 from "os";
|
|
8529
8776
|
function renderMdc(source) {
|
|
8530
8777
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
@@ -8573,7 +8820,7 @@ function createCursorEmitter() {
|
|
|
8573
8820
|
if (scope === "user") {
|
|
8574
8821
|
return [
|
|
8575
8822
|
{
|
|
8576
|
-
path:
|
|
8823
|
+
path: path15.join(os6.homedir(), ".cursor/rules/squadrant-global.mdc"),
|
|
8577
8824
|
shared: false,
|
|
8578
8825
|
format: "mdc"
|
|
8579
8826
|
}
|
|
@@ -8583,7 +8830,7 @@ function createCursorEmitter() {
|
|
|
8583
8830
|
return [];
|
|
8584
8831
|
return [
|
|
8585
8832
|
{
|
|
8586
|
-
path:
|
|
8833
|
+
path: path15.join(projectRoot, ".cursor/rules/squadrant.mdc"),
|
|
8587
8834
|
shared: false,
|
|
8588
8835
|
format: "mdc"
|
|
8589
8836
|
}
|
|
@@ -8600,7 +8847,7 @@ function createCursorEmitter() {
|
|
|
8600
8847
|
diff: buildDiff(existing, generated)
|
|
8601
8848
|
};
|
|
8602
8849
|
}
|
|
8603
|
-
await mkdir(
|
|
8850
|
+
await mkdir(path15.dirname(dest.path), { recursive: true });
|
|
8604
8851
|
await writeFile(dest.path, generated, "utf-8");
|
|
8605
8852
|
return {
|
|
8606
8853
|
written: true,
|
|
@@ -8651,7 +8898,7 @@ var init_marker = __esm({
|
|
|
8651
8898
|
|
|
8652
8899
|
// packages/agents/dist/projection/codex.js
|
|
8653
8900
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
8654
|
-
import
|
|
8901
|
+
import path16 from "path";
|
|
8655
8902
|
import os7 from "os";
|
|
8656
8903
|
function renderMarkdown(source) {
|
|
8657
8904
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
@@ -8676,7 +8923,7 @@ function createCodexEmitter() {
|
|
|
8676
8923
|
destinations(scope, projectRoot) {
|
|
8677
8924
|
if (scope === "user") {
|
|
8678
8925
|
return [{
|
|
8679
|
-
path:
|
|
8926
|
+
path: path16.join(os7.homedir(), ".codex/AGENTS.md"),
|
|
8680
8927
|
shared: true,
|
|
8681
8928
|
format: "markdown"
|
|
8682
8929
|
}];
|
|
@@ -8684,7 +8931,7 @@ function createCodexEmitter() {
|
|
|
8684
8931
|
if (!projectRoot)
|
|
8685
8932
|
return [];
|
|
8686
8933
|
return [{
|
|
8687
|
-
path:
|
|
8934
|
+
path: path16.join(projectRoot, "AGENTS.md"),
|
|
8688
8935
|
shared: true,
|
|
8689
8936
|
format: "markdown"
|
|
8690
8937
|
}];
|
|
@@ -8705,7 +8952,7 @@ ${existing ?? ""}
|
|
|
8705
8952
|
${generated}`
|
|
8706
8953
|
};
|
|
8707
8954
|
}
|
|
8708
|
-
await mkdir2(
|
|
8955
|
+
await mkdir2(path16.dirname(dest.path), { recursive: true });
|
|
8709
8956
|
await writeFile2(dest.path, generated, "utf-8");
|
|
8710
8957
|
return {
|
|
8711
8958
|
written: true,
|
|
@@ -8723,7 +8970,7 @@ var init_codex2 = __esm({
|
|
|
8723
8970
|
|
|
8724
8971
|
// packages/agents/dist/projection/gemini.js
|
|
8725
8972
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
8726
|
-
import
|
|
8973
|
+
import path17 from "path";
|
|
8727
8974
|
import os8 from "os";
|
|
8728
8975
|
function renderMarkdown2(source) {
|
|
8729
8976
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
@@ -8748,7 +8995,7 @@ function createGeminiEmitter() {
|
|
|
8748
8995
|
destinations(scope, projectRoot) {
|
|
8749
8996
|
if (scope === "user") {
|
|
8750
8997
|
return [{
|
|
8751
|
-
path:
|
|
8998
|
+
path: path17.join(os8.homedir(), ".gemini/GEMINI.md"),
|
|
8752
8999
|
shared: true,
|
|
8753
9000
|
format: "markdown"
|
|
8754
9001
|
}];
|
|
@@ -8756,7 +9003,7 @@ function createGeminiEmitter() {
|
|
|
8756
9003
|
if (!projectRoot)
|
|
8757
9004
|
return [];
|
|
8758
9005
|
return [{
|
|
8759
|
-
path:
|
|
9006
|
+
path: path17.join(projectRoot, "GEMINI.md"),
|
|
8760
9007
|
shared: true,
|
|
8761
9008
|
format: "markdown"
|
|
8762
9009
|
}];
|
|
@@ -8777,7 +9024,7 @@ ${existing ?? ""}
|
|
|
8777
9024
|
${generated}`
|
|
8778
9025
|
};
|
|
8779
9026
|
}
|
|
8780
|
-
await mkdir3(
|
|
9027
|
+
await mkdir3(path17.dirname(dest.path), { recursive: true });
|
|
8781
9028
|
await writeFile3(dest.path, generated, "utf-8");
|
|
8782
9029
|
return {
|
|
8783
9030
|
written: true,
|
|
@@ -8795,7 +9042,7 @@ var init_gemini2 = __esm({
|
|
|
8795
9042
|
|
|
8796
9043
|
// packages/agents/dist/projection/opencode.js
|
|
8797
9044
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
8798
|
-
import
|
|
9045
|
+
import path18 from "path";
|
|
8799
9046
|
import os9 from "os";
|
|
8800
9047
|
function renderMarkdown3(source) {
|
|
8801
9048
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
@@ -8820,7 +9067,7 @@ function createOpencodeEmitter() {
|
|
|
8820
9067
|
destinations(scope, projectRoot) {
|
|
8821
9068
|
if (scope === "user") {
|
|
8822
9069
|
return [{
|
|
8823
|
-
path:
|
|
9070
|
+
path: path18.join(os9.homedir(), ".config", "opencode", "AGENTS.md"),
|
|
8824
9071
|
shared: true,
|
|
8825
9072
|
format: "markdown"
|
|
8826
9073
|
}];
|
|
@@ -8828,7 +9075,7 @@ function createOpencodeEmitter() {
|
|
|
8828
9075
|
if (!projectRoot)
|
|
8829
9076
|
return [];
|
|
8830
9077
|
return [{
|
|
8831
|
-
path:
|
|
9078
|
+
path: path18.join(projectRoot, "AGENTS.md"),
|
|
8832
9079
|
shared: true,
|
|
8833
9080
|
format: "markdown"
|
|
8834
9081
|
}];
|
|
@@ -8849,7 +9096,7 @@ ${existing ?? ""}
|
|
|
8849
9096
|
${generated}`
|
|
8850
9097
|
};
|
|
8851
9098
|
}
|
|
8852
|
-
await mkdir4(
|
|
9099
|
+
await mkdir4(path18.dirname(dest.path), { recursive: true });
|
|
8853
9100
|
await writeFile4(dest.path, generated, "utf-8");
|
|
8854
9101
|
return {
|
|
8855
9102
|
written: true,
|
|
@@ -9708,7 +9955,7 @@ var init_sse_bridge = __esm({
|
|
|
9708
9955
|
|
|
9709
9956
|
// packages/agents/dist/interactive/claude.js
|
|
9710
9957
|
import { execSync as execSync7 } from "child_process";
|
|
9711
|
-
import { readFileSync as
|
|
9958
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
9712
9959
|
import { homedir as homedir14 } from "os";
|
|
9713
9960
|
import { join as join19 } from "path";
|
|
9714
9961
|
function probeClaudeSettingsFlag() {
|
|
@@ -9772,7 +10019,7 @@ function deriveTranscriptPath(sessionId, cwd) {
|
|
|
9772
10019
|
}
|
|
9773
10020
|
function readLastAssistantText(transcriptPath) {
|
|
9774
10021
|
try {
|
|
9775
|
-
const raw =
|
|
10022
|
+
const raw = readFileSync12(transcriptPath, "utf-8");
|
|
9776
10023
|
const lines = raw.split(/\r?\n/);
|
|
9777
10024
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
9778
10025
|
const line = lines[i].trim();
|
|
@@ -9814,8 +10061,8 @@ function resolveLastAssistantText(payload) {
|
|
|
9814
10061
|
const derived = deriveTranscriptPath(p?.session_id, cwd);
|
|
9815
10062
|
if (derived)
|
|
9816
10063
|
candidates.push(derived);
|
|
9817
|
-
for (const
|
|
9818
|
-
const text = readLastAssistantText(
|
|
10064
|
+
for (const path35 of candidates) {
|
|
10065
|
+
const text = readLastAssistantText(path35);
|
|
9819
10066
|
if (text != null)
|
|
9820
10067
|
return text;
|
|
9821
10068
|
}
|
|
@@ -10269,8 +10516,8 @@ var require_daemon_exports = {};
|
|
|
10269
10516
|
__export(require_daemon_exports, {
|
|
10270
10517
|
requireDaemon: () => requireDaemon
|
|
10271
10518
|
});
|
|
10272
|
-
import { join as
|
|
10273
|
-
import { homedir as
|
|
10519
|
+
import { join as join22 } from "path";
|
|
10520
|
+
import { homedir as homedir17 } from "os";
|
|
10274
10521
|
async function requireDaemon(sockPath = DEFAULT_SOCK_PATH3) {
|
|
10275
10522
|
const isLive = await isDaemonSocketLive(sockPath);
|
|
10276
10523
|
if (!isLive) {
|
|
@@ -10281,89 +10528,250 @@ var DEFAULT_SOCK_PATH3;
|
|
|
10281
10528
|
var init_require_daemon = __esm({
|
|
10282
10529
|
"packages/cli/src/lib/require-daemon.ts"() {
|
|
10283
10530
|
init_dist2();
|
|
10284
|
-
DEFAULT_SOCK_PATH3 =
|
|
10531
|
+
DEFAULT_SOCK_PATH3 = join22(homedir17(), ".config", "squadrant", "squadrant.sock");
|
|
10285
10532
|
}
|
|
10286
10533
|
});
|
|
10287
10534
|
|
|
10288
|
-
// packages/cli/src/
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
import { execSync as execSync8 } from "child_process";
|
|
10304
|
-
import fs17 from "fs";
|
|
10305
|
-
import { stat } from "fs/promises";
|
|
10306
|
-
import path18 from "path";
|
|
10307
|
-
import chalk3 from "chalk";
|
|
10308
|
-
|
|
10309
|
-
// packages/cli/src/commands/health-view.ts
|
|
10310
|
-
init_dist2();
|
|
10311
|
-
init_dist2();
|
|
10312
|
-
import { homedir as homedir12 } from "os";
|
|
10313
|
-
import { join as join17 } from "path";
|
|
10314
|
-
import chalk2 from "chalk";
|
|
10315
|
-
var SOCK = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
|
|
10316
|
-
async function queryHealth(project) {
|
|
10317
|
-
try {
|
|
10318
|
-
const reply = await sendRequest(SOCK, { kind: "health", project });
|
|
10319
|
-
return Array.isArray(reply) ? reply : [];
|
|
10320
|
-
} catch {
|
|
10321
|
-
return null;
|
|
10322
|
-
}
|
|
10535
|
+
// packages/cli/src/commands/runtime.ts
|
|
10536
|
+
var runtime_exports = {};
|
|
10537
|
+
__export(runtime_exports, {
|
|
10538
|
+
buildRegistry: () => buildRegistry,
|
|
10539
|
+
needRef: () => needRef,
|
|
10540
|
+
resolveTarget: () => resolveTarget,
|
|
10541
|
+
runRuntimeSend: () => runRuntimeSend,
|
|
10542
|
+
runtimeCommand: () => runtimeCommand
|
|
10543
|
+
});
|
|
10544
|
+
import { Command as Command8 } from "commander";
|
|
10545
|
+
import chalk9 from "chalk";
|
|
10546
|
+
function buildRegistry() {
|
|
10547
|
+
return new RuntimeRegistry({
|
|
10548
|
+
cmux: createCmuxDriver()
|
|
10549
|
+
});
|
|
10323
10550
|
}
|
|
10324
|
-
function
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
case "gone":
|
|
10331
|
-
return "\u2718";
|
|
10332
|
-
case "stopped":
|
|
10333
|
-
return "\u23FB";
|
|
10334
|
-
case "unknown":
|
|
10335
|
-
return "\u25CB";
|
|
10551
|
+
function resolveTarget(registry, config, target, useCommand) {
|
|
10552
|
+
if (useCommand) {
|
|
10553
|
+
return {
|
|
10554
|
+
driver: registry.global(config),
|
|
10555
|
+
workspaceName: config.commandName
|
|
10556
|
+
};
|
|
10336
10557
|
}
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
switch (state) {
|
|
10340
|
-
case "alive":
|
|
10341
|
-
return chalk2.green(state);
|
|
10342
|
-
case "stale":
|
|
10343
|
-
return chalk2.yellow(state);
|
|
10344
|
-
case "gone":
|
|
10345
|
-
return chalk2.red(state);
|
|
10346
|
-
// stopped = intentional shutdown, not a fault — magenta, never alarm-red.
|
|
10347
|
-
case "stopped":
|
|
10348
|
-
return chalk2.magenta(state);
|
|
10349
|
-
case "unknown":
|
|
10350
|
-
return chalk2.dim(state);
|
|
10558
|
+
if (!target) {
|
|
10559
|
+
throw new Error("Missing target: pass a project name or use --command");
|
|
10351
10560
|
}
|
|
10561
|
+
const proj = config.projects[target];
|
|
10562
|
+
if (!proj) {
|
|
10563
|
+
throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
|
|
10564
|
+
}
|
|
10565
|
+
return {
|
|
10566
|
+
driver: registry.forProject(target, config),
|
|
10567
|
+
workspaceName: proj.captainName
|
|
10568
|
+
};
|
|
10352
10569
|
}
|
|
10353
|
-
function
|
|
10354
|
-
const
|
|
10355
|
-
|
|
10356
|
-
}
|
|
10357
|
-
function printServiceHealth(rows, now = Date.now()) {
|
|
10358
|
-
console.log(chalk2.bold("\nService Health\n"));
|
|
10359
|
-
if (rows == null) {
|
|
10360
|
-
console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown`);
|
|
10361
|
-
console.log(` ${chalk2.cyan("\u2192")} ${chalk2.dim("Run: squadrant heal daemon")}`);
|
|
10362
|
-
return;
|
|
10570
|
+
async function needRef(resolved) {
|
|
10571
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10572
|
+
if (!ref) {
|
|
10573
|
+
throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
|
|
10363
10574
|
}
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10575
|
+
return ref.id;
|
|
10576
|
+
}
|
|
10577
|
+
async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
10578
|
+
const config = loadConfig();
|
|
10579
|
+
const registry = buildRegistry();
|
|
10580
|
+
if (opts.command && arg2 !== void 0) {
|
|
10581
|
+
throw new Error("With --command, pass only the message (not a project name)");
|
|
10582
|
+
}
|
|
10583
|
+
const target = opts.command ? void 0 : arg1;
|
|
10584
|
+
const message = opts.command ? arg1 : arg2;
|
|
10585
|
+
if (!message) throw new Error("Message is required");
|
|
10586
|
+
const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
|
|
10587
|
+
const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
10588
|
+
await requireDaemon2();
|
|
10589
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10590
|
+
await needRef(resolved);
|
|
10591
|
+
const finalProject = opts.command ? config.commandName : target;
|
|
10592
|
+
const { join: join31, dirname: dirname9 } = await import("path");
|
|
10593
|
+
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
10594
|
+
const stateRoot = join31(dirname9(DEFAULT_CONFIG_PATH2), "state");
|
|
10595
|
+
const seq = await appendCaptainMessage2({
|
|
10596
|
+
stateRoot,
|
|
10597
|
+
project: finalProject,
|
|
10598
|
+
text: message,
|
|
10599
|
+
source: "cli"
|
|
10600
|
+
});
|
|
10601
|
+
const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
|
|
10602
|
+
const delivered = await waitForCaptainDelivery2({
|
|
10603
|
+
stateRoot,
|
|
10604
|
+
project: finalProject,
|
|
10605
|
+
seq,
|
|
10606
|
+
timeoutMs,
|
|
10607
|
+
pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
|
|
10608
|
+
});
|
|
10609
|
+
if (!delivered) {
|
|
10610
|
+
throw new Error(
|
|
10611
|
+
`Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
|
|
10612
|
+
);
|
|
10613
|
+
}
|
|
10614
|
+
}
|
|
10615
|
+
var runtimeCommand, SEND_CONFIRM_TIMEOUT_MS, SEND_CONFIRM_POLL_MS;
|
|
10616
|
+
var init_runtime2 = __esm({
|
|
10617
|
+
"packages/cli/src/commands/runtime.ts"() {
|
|
10618
|
+
init_dist();
|
|
10619
|
+
init_dist3();
|
|
10620
|
+
runtimeCommand = new Command8("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
10621
|
+
runtimeCommand.command("status").description("Print 'running' or 'stopped' for a target; exit 0 if running, 1 if not").argument("[target]", "Project name").option("--command", "Target the command workspace instead of a project captain").action(async (target, opts) => {
|
|
10622
|
+
const config = loadConfig();
|
|
10623
|
+
const registry = buildRegistry();
|
|
10624
|
+
try {
|
|
10625
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10626
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10627
|
+
if (ref) {
|
|
10628
|
+
console.log("running");
|
|
10629
|
+
process.exit(0);
|
|
10630
|
+
} else {
|
|
10631
|
+
console.log("stopped");
|
|
10632
|
+
process.exit(1);
|
|
10633
|
+
}
|
|
10634
|
+
} catch (err) {
|
|
10635
|
+
console.error(chalk9.red(err.message));
|
|
10636
|
+
process.exit(2);
|
|
10637
|
+
}
|
|
10638
|
+
});
|
|
10639
|
+
SEND_CONFIRM_TIMEOUT_MS = 15e3;
|
|
10640
|
+
SEND_CONFIRM_POLL_MS = 500;
|
|
10641
|
+
runtimeCommand.command("send").description("Send a message to a target workspace AND commit with Enter. With --command, the first positional is the message.").argument("<arg1>", "Project name, or the message when --command is used").argument("[arg2]", "Message (when target is a project). Omit when using --command.").option("--command", "Target the command workspace").action(async (arg1, arg2, opts) => {
|
|
10642
|
+
try {
|
|
10643
|
+
await runRuntimeSend(arg1, arg2, opts);
|
|
10644
|
+
console.log(chalk9.green("\u2714 Delivered (confirmed)"));
|
|
10645
|
+
} catch (err) {
|
|
10646
|
+
console.error(chalk9.red(err.message));
|
|
10647
|
+
process.exit(1);
|
|
10648
|
+
}
|
|
10649
|
+
});
|
|
10650
|
+
runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
10651
|
+
const config = loadConfig();
|
|
10652
|
+
const registry = buildRegistry();
|
|
10653
|
+
const driver = registry.global(config);
|
|
10654
|
+
const refs = await driver.list();
|
|
10655
|
+
if (opts.json) {
|
|
10656
|
+
console.log(JSON.stringify(refs, null, 2));
|
|
10657
|
+
} else {
|
|
10658
|
+
for (const r of refs) {
|
|
10659
|
+
console.log(`${r.id} ${r.name} ${r.status}`);
|
|
10660
|
+
}
|
|
10661
|
+
}
|
|
10662
|
+
});
|
|
10663
|
+
runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
10664
|
+
const config = loadConfig();
|
|
10665
|
+
const registry = buildRegistry();
|
|
10666
|
+
try {
|
|
10667
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10668
|
+
const ref = await needRef(resolved);
|
|
10669
|
+
const screen = await resolved.driver.readScreen(ref);
|
|
10670
|
+
process.stdout.write(screen);
|
|
10671
|
+
} catch (err) {
|
|
10672
|
+
console.error(chalk9.red(err.message));
|
|
10673
|
+
process.exit(1);
|
|
10674
|
+
}
|
|
10675
|
+
});
|
|
10676
|
+
runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
10677
|
+
const config = loadConfig();
|
|
10678
|
+
const registry = buildRegistry();
|
|
10679
|
+
try {
|
|
10680
|
+
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
10681
|
+
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
10682
|
+
if (!ref) {
|
|
10683
|
+
console.log(chalk9.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
10684
|
+
return;
|
|
10685
|
+
}
|
|
10686
|
+
await resolved.driver.stop(ref.id);
|
|
10687
|
+
console.log(chalk9.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
10688
|
+
} catch (err) {
|
|
10689
|
+
console.error(chalk9.red(err.message));
|
|
10690
|
+
process.exit(1);
|
|
10691
|
+
}
|
|
10692
|
+
});
|
|
10693
|
+
}
|
|
10694
|
+
});
|
|
10695
|
+
|
|
10696
|
+
// packages/cli/src/index.ts
|
|
10697
|
+
init_dist();
|
|
10698
|
+
init_dist2();
|
|
10699
|
+
import { Command as Command35 } from "commander";
|
|
10700
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
10701
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
10702
|
+
import { dirname as dirname8, join as join30 } from "path";
|
|
10703
|
+
import { homedir as homedir22 } from "os";
|
|
10704
|
+
|
|
10705
|
+
// packages/cli/src/commands/doctor.ts
|
|
10706
|
+
init_dist();
|
|
10707
|
+
init_dist();
|
|
10708
|
+
init_dist();
|
|
10709
|
+
init_dist3();
|
|
10710
|
+
import { Command } from "commander";
|
|
10711
|
+
import { execSync as execSync8 } from "child_process";
|
|
10712
|
+
import fs15 from "fs";
|
|
10713
|
+
import { stat } from "fs/promises";
|
|
10714
|
+
import path19 from "path";
|
|
10715
|
+
import chalk3 from "chalk";
|
|
10716
|
+
|
|
10717
|
+
// packages/cli/src/commands/health-view.ts
|
|
10718
|
+
init_dist2();
|
|
10719
|
+
init_dist2();
|
|
10720
|
+
import { homedir as homedir12 } from "os";
|
|
10721
|
+
import { join as join17 } from "path";
|
|
10722
|
+
import chalk2 from "chalk";
|
|
10723
|
+
var SOCK = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
|
|
10724
|
+
async function queryHealth(project) {
|
|
10725
|
+
try {
|
|
10726
|
+
const reply = await sendRequest(SOCK, { kind: "health", project });
|
|
10727
|
+
return Array.isArray(reply) ? reply : [];
|
|
10728
|
+
} catch {
|
|
10729
|
+
return null;
|
|
10730
|
+
}
|
|
10731
|
+
}
|
|
10732
|
+
function healthIcon(state) {
|
|
10733
|
+
switch (state) {
|
|
10734
|
+
case "alive":
|
|
10735
|
+
return "\u2714";
|
|
10736
|
+
case "stale":
|
|
10737
|
+
return "\u2022";
|
|
10738
|
+
case "gone":
|
|
10739
|
+
return "\u2718";
|
|
10740
|
+
case "stopped":
|
|
10741
|
+
return "\u23FB";
|
|
10742
|
+
case "unknown":
|
|
10743
|
+
return "\u25CB";
|
|
10744
|
+
}
|
|
10745
|
+
}
|
|
10746
|
+
function colorState(state) {
|
|
10747
|
+
switch (state) {
|
|
10748
|
+
case "alive":
|
|
10749
|
+
return chalk2.green(state);
|
|
10750
|
+
case "stale":
|
|
10751
|
+
return chalk2.yellow(state);
|
|
10752
|
+
case "gone":
|
|
10753
|
+
return chalk2.red(state);
|
|
10754
|
+
// stopped = intentional shutdown, not a fault — magenta, never alarm-red.
|
|
10755
|
+
case "stopped":
|
|
10756
|
+
return chalk2.magenta(state);
|
|
10757
|
+
case "unknown":
|
|
10758
|
+
return chalk2.dim(state);
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10761
|
+
function healthRow(c, now) {
|
|
10762
|
+
const head = `${healthIcon(c.state)} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${c.state.padEnd(8)} ${ageText(c.lastSeenMs, now)}`;
|
|
10763
|
+
return c.detail ? `${head} ${c.detail}` : head;
|
|
10764
|
+
}
|
|
10765
|
+
function printServiceHealth(rows, now = Date.now()) {
|
|
10766
|
+
console.log(chalk2.bold("\nService Health\n"));
|
|
10767
|
+
if (rows == null) {
|
|
10768
|
+
console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown`);
|
|
10769
|
+
console.log(` ${chalk2.cyan("\u2192")} ${chalk2.dim("Run: squadrant heal daemon")}`);
|
|
10770
|
+
return;
|
|
10771
|
+
}
|
|
10772
|
+
if (rows.length === 0) {
|
|
10773
|
+
console.log(chalk2.dim(" no registered projects"));
|
|
10774
|
+
return;
|
|
10367
10775
|
}
|
|
10368
10776
|
const byProject = /* @__PURE__ */ new Map();
|
|
10369
10777
|
for (const c of rows) {
|
|
@@ -10406,7 +10814,7 @@ function settingsHaveAgentTeams() {
|
|
|
10406
10814
|
try {
|
|
10407
10815
|
const home = process.env.HOME || "";
|
|
10408
10816
|
const settings = JSON.parse(
|
|
10409
|
-
|
|
10817
|
+
fs15.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
10410
10818
|
);
|
|
10411
10819
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
10412
10820
|
} catch {
|
|
@@ -10417,7 +10825,7 @@ function pluginInstalled(pluginKey) {
|
|
|
10417
10825
|
try {
|
|
10418
10826
|
const home = process.env.HOME || "";
|
|
10419
10827
|
const plugins = JSON.parse(
|
|
10420
|
-
|
|
10828
|
+
fs15.readFileSync(
|
|
10421
10829
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
10422
10830
|
"utf-8"
|
|
10423
10831
|
)
|
|
@@ -10439,6 +10847,43 @@ function tryGetVersion(cmd) {
|
|
|
10439
10847
|
return "";
|
|
10440
10848
|
}
|
|
10441
10849
|
}
|
|
10850
|
+
function candidateGlobalInstalls(roots) {
|
|
10851
|
+
const out = [];
|
|
10852
|
+
if (roots.npm) out.push({ manager: "npm", packageJsonPath: path19.join(roots.npm, "squadrant", "package.json") });
|
|
10853
|
+
if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path19.join(roots.pnpm, "squadrant", "package.json") });
|
|
10854
|
+
if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path19.join(roots.yarn, "node_modules", "squadrant", "package.json") });
|
|
10855
|
+
return out;
|
|
10856
|
+
}
|
|
10857
|
+
function findInstalledSquadrants(candidates, readVersion) {
|
|
10858
|
+
const out = [];
|
|
10859
|
+
for (const c of candidates) {
|
|
10860
|
+
const version = readVersion(c.packageJsonPath);
|
|
10861
|
+
if (version) out.push({ manager: c.manager, packageJsonPath: c.packageJsonPath, version });
|
|
10862
|
+
}
|
|
10863
|
+
return out;
|
|
10864
|
+
}
|
|
10865
|
+
function formatDuplicateInstallWarning(installs) {
|
|
10866
|
+
if (installs.length <= 1) return null;
|
|
10867
|
+
const lines = installs.map((i) => ` ${i.manager}: ${i.packageJsonPath} (v${i.version})`);
|
|
10868
|
+
return `Multiple squadrant installs detected:
|
|
10869
|
+
${lines.join("\n")}
|
|
10870
|
+
Two installs fight over the daemon plist (#670) \u2014 uninstall the one you don't use, then run \`squadrant heal daemon\`.`;
|
|
10871
|
+
}
|
|
10872
|
+
function readPackageVersion(packageJsonPath) {
|
|
10873
|
+
try {
|
|
10874
|
+
const pkg2 = JSON.parse(fs15.readFileSync(packageJsonPath, "utf-8"));
|
|
10875
|
+
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
10876
|
+
} catch {
|
|
10877
|
+
return null;
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
function tryGetGlobalRoot(cmd, args) {
|
|
10881
|
+
try {
|
|
10882
|
+
return execSync8(`${cmd} ${args.join(" ")}`, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() || void 0;
|
|
10883
|
+
} catch {
|
|
10884
|
+
return void 0;
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10442
10887
|
function check(label, pass, hint) {
|
|
10443
10888
|
const icon = pass ? chalk3.green("\u2714 PASS") : chalk3.red("\u2718 FAIL");
|
|
10444
10889
|
console.log(` ${icon} ${label}`);
|
|
@@ -10462,7 +10907,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
10462
10907
|
));
|
|
10463
10908
|
results.push(check(
|
|
10464
10909
|
"Obsidian installed",
|
|
10465
|
-
commandExists("obsidian") ||
|
|
10910
|
+
commandExists("obsidian") || fs15.existsSync("/Applications/Obsidian.app"),
|
|
10466
10911
|
"Install from: https://obsidian.md"
|
|
10467
10912
|
));
|
|
10468
10913
|
results.push(check(
|
|
@@ -10556,7 +11001,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
10556
11001
|
const emitter = projectionRegistry.get(name);
|
|
10557
11002
|
const [userDest] = emitter.destinations("user");
|
|
10558
11003
|
if (!userDest) continue;
|
|
10559
|
-
const dir =
|
|
11004
|
+
const dir = path19.dirname(userDest.path);
|
|
10560
11005
|
let status;
|
|
10561
11006
|
try {
|
|
10562
11007
|
await stat(dir);
|
|
@@ -10569,7 +11014,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
10569
11014
|
results.push(
|
|
10570
11015
|
check(
|
|
10571
11016
|
"Squadrant config exists",
|
|
10572
|
-
|
|
11017
|
+
fs15.existsSync(
|
|
10573
11018
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
10574
11019
|
),
|
|
10575
11020
|
"Run: squadrant init"
|
|
@@ -10582,6 +11027,16 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
10582
11027
|
${passed === total ? chalk3.green("All checks passed") : chalk3.yellow(`${passed}/${total} checks passed`)}
|
|
10583
11028
|
`
|
|
10584
11029
|
);
|
|
11030
|
+
const installCandidates = candidateGlobalInstalls({
|
|
11031
|
+
npm: tryGetGlobalRoot("npm", ["root", "-g"]),
|
|
11032
|
+
pnpm: tryGetGlobalRoot("pnpm", ["root", "-g"]),
|
|
11033
|
+
yarn: tryGetGlobalRoot("yarn", ["global", "dir"])
|
|
11034
|
+
});
|
|
11035
|
+
const duplicateWarning = formatDuplicateInstallWarning(findInstalledSquadrants(installCandidates, readPackageVersion));
|
|
11036
|
+
if (duplicateWarning) {
|
|
11037
|
+
console.log(`
|
|
11038
|
+
${chalk3.yellow("\u26A0 WARN")} ${duplicateWarning}`);
|
|
11039
|
+
}
|
|
10585
11040
|
printServiceHealth(await queryHealth());
|
|
10586
11041
|
console.log(chalk3.bold("\nTool Version Compat\n"));
|
|
10587
11042
|
const toolVersionMap = {
|
|
@@ -10643,16 +11098,16 @@ init_dist();
|
|
|
10643
11098
|
init_dist3();
|
|
10644
11099
|
init_dist();
|
|
10645
11100
|
import { Command as Command2 } from "commander";
|
|
10646
|
-
import
|
|
10647
|
-
import
|
|
11101
|
+
import fs16 from "fs";
|
|
11102
|
+
import path20 from "path";
|
|
10648
11103
|
import os10 from "os";
|
|
10649
11104
|
import readline from "readline";
|
|
10650
11105
|
import chalk4 from "chalk";
|
|
10651
11106
|
|
|
10652
11107
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
10653
11108
|
init_dist4();
|
|
10654
|
-
import { mkdirSync as
|
|
10655
|
-
import { dirname as
|
|
11109
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
11110
|
+
import { dirname as dirname4, join as join20 } from "path";
|
|
10656
11111
|
import { homedir as homedir15 } from "os";
|
|
10657
11112
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
10658
11113
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
@@ -10745,28 +11200,28 @@ function mergeCrewPermissions(settings) {
|
|
|
10745
11200
|
}
|
|
10746
11201
|
function writePerCrewSettingsLocal(o) {
|
|
10747
11202
|
const dir = join20(o.projectCwd, ".claude");
|
|
10748
|
-
|
|
11203
|
+
mkdirSync7(dir, { recursive: true });
|
|
10749
11204
|
const file = join20(dir, "settings.local.json");
|
|
10750
11205
|
let existing = {};
|
|
10751
11206
|
try {
|
|
10752
|
-
const raw = healStaleCockpitRefs(
|
|
11207
|
+
const raw = healStaleCockpitRefs(readFileSync13(file, "utf-8"));
|
|
10753
11208
|
existing = JSON.parse(raw);
|
|
10754
11209
|
} catch {
|
|
10755
11210
|
}
|
|
10756
11211
|
const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
|
|
10757
11212
|
const merged = mergeCrewPermissions(withHooks);
|
|
10758
|
-
|
|
11213
|
+
writeFileSync9(file, JSON.stringify(merged, null, 2));
|
|
10759
11214
|
return file;
|
|
10760
11215
|
}
|
|
10761
11216
|
var DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH = join20(homedir15(), ".config", "opencode", "opencode.json");
|
|
10762
11217
|
function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH) {
|
|
10763
|
-
|
|
11218
|
+
mkdirSync7(dirname4(configPath), { recursive: true });
|
|
10764
11219
|
const defaultConfig = {
|
|
10765
11220
|
$schema: "https://opencode.ai/config.json",
|
|
10766
11221
|
model: "anthropic/claude-sonnet-4-5"
|
|
10767
11222
|
};
|
|
10768
11223
|
try {
|
|
10769
|
-
|
|
11224
|
+
writeFileSync9(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { flag: "wx" });
|
|
10770
11225
|
return configPath;
|
|
10771
11226
|
} catch (err) {
|
|
10772
11227
|
if (err.code === "EEXIST") return null;
|
|
@@ -10775,7 +11230,7 @@ function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_
|
|
|
10775
11230
|
}
|
|
10776
11231
|
function writePerCrewOpencodeConfig(o) {
|
|
10777
11232
|
const dir = join20(o.stateRoot, o.project, o.taskId);
|
|
10778
|
-
|
|
11233
|
+
mkdirSync7(dir, { recursive: true });
|
|
10779
11234
|
const file = join20(dir, "opencode.json");
|
|
10780
11235
|
const config = {
|
|
10781
11236
|
permission: {
|
|
@@ -10791,29 +11246,29 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
10791
11246
|
external_directory: { "**": "allow" }
|
|
10792
11247
|
}
|
|
10793
11248
|
};
|
|
10794
|
-
|
|
11249
|
+
writeFileSync9(file, JSON.stringify(config, null, 2));
|
|
10795
11250
|
return file;
|
|
10796
11251
|
}
|
|
10797
11252
|
|
|
10798
11253
|
// packages/cli/src/commands/init.ts
|
|
10799
11254
|
init_dist4();
|
|
10800
11255
|
function findPackageRoot() {
|
|
10801
|
-
let dir =
|
|
11256
|
+
let dir = path20.dirname(new URL(import.meta.url).pathname);
|
|
10802
11257
|
while (dir !== "/") {
|
|
10803
|
-
if (
|
|
10804
|
-
dir =
|
|
11258
|
+
if (fs16.existsSync(path20.join(dir, "package.json"))) return dir;
|
|
11259
|
+
dir = path20.dirname(dir);
|
|
10805
11260
|
}
|
|
10806
11261
|
return process.cwd();
|
|
10807
11262
|
}
|
|
10808
11263
|
function copyDirRecursive(src, dest) {
|
|
10809
|
-
|
|
10810
|
-
for (const entry of
|
|
10811
|
-
const srcPath =
|
|
10812
|
-
const destPath =
|
|
11264
|
+
fs16.mkdirSync(dest, { recursive: true });
|
|
11265
|
+
for (const entry of fs16.readdirSync(src, { withFileTypes: true })) {
|
|
11266
|
+
const srcPath = path20.join(src, entry.name);
|
|
11267
|
+
const destPath = path20.join(dest, entry.name);
|
|
10813
11268
|
if (entry.isDirectory()) {
|
|
10814
11269
|
copyDirRecursive(srcPath, destPath);
|
|
10815
11270
|
} else {
|
|
10816
|
-
|
|
11271
|
+
fs16.copyFileSync(srcPath, destPath);
|
|
10817
11272
|
}
|
|
10818
11273
|
}
|
|
10819
11274
|
}
|
|
@@ -10833,7 +11288,7 @@ function promptLine(question) {
|
|
|
10833
11288
|
var initCommand = new Command2("init").description("Guided first-time setup: hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").action(async (opts) => {
|
|
10834
11289
|
const hubPath = resolveHome(opts.hub);
|
|
10835
11290
|
const pkgRoot = findPackageRoot();
|
|
10836
|
-
const configDir =
|
|
11291
|
+
const configDir = path20.join(os10.homedir(), ".config", "squadrant");
|
|
10837
11292
|
const isTTY = process.stdin.isTTY === true;
|
|
10838
11293
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
10839
11294
|
if (!isTTY) {
|
|
@@ -10859,8 +11314,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
10859
11314
|
}
|
|
10860
11315
|
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
10861
11316
|
try {
|
|
10862
|
-
if (
|
|
10863
|
-
const existing = JSON.parse(
|
|
11317
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
11318
|
+
const existing = JSON.parse(fs16.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
10864
11319
|
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
10865
11320
|
}
|
|
10866
11321
|
} catch (err) {
|
|
@@ -10868,7 +11323,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
10868
11323
|
return;
|
|
10869
11324
|
}
|
|
10870
11325
|
stepHeader(1, 5, "Hub vault");
|
|
10871
|
-
if (
|
|
11326
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
10872
11327
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
10873
11328
|
} else {
|
|
10874
11329
|
const config = getDefaultConfig();
|
|
@@ -10876,37 +11331,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
10876
11331
|
saveConfig(config);
|
|
10877
11332
|
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
10878
11333
|
}
|
|
10879
|
-
const hubTemplate =
|
|
10880
|
-
if (
|
|
11334
|
+
const hubTemplate = path20.join(pkgRoot, "obsidian", "hub");
|
|
11335
|
+
if (fs16.existsSync(hubPath)) {
|
|
10881
11336
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
10882
|
-
} else if (
|
|
11337
|
+
} else if (fs16.existsSync(hubTemplate)) {
|
|
10883
11338
|
copyDirRecursive(hubTemplate, hubPath);
|
|
10884
11339
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
10885
11340
|
} else {
|
|
10886
|
-
|
|
11341
|
+
fs16.mkdirSync(hubPath, { recursive: true });
|
|
10887
11342
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
10888
11343
|
}
|
|
10889
|
-
const hubDashboardSrc =
|
|
10890
|
-
const hubDashboardDest =
|
|
10891
|
-
if (
|
|
10892
|
-
|
|
11344
|
+
const hubDashboardSrc = path20.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
11345
|
+
const hubDashboardDest = path20.join(hubPath, "dashboard.md");
|
|
11346
|
+
if (fs16.existsSync(hubDashboardSrc)) {
|
|
11347
|
+
fs16.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
10893
11348
|
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
10894
11349
|
}
|
|
10895
|
-
|
|
11350
|
+
fs16.mkdirSync(path20.join(hubPath, "projects"), { recursive: true });
|
|
10896
11351
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
10897
11352
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
10898
11353
|
stepHeader(2, 5, "Agent + projection setup");
|
|
10899
|
-
const settingsPath =
|
|
11354
|
+
const settingsPath = path20.join(os10.homedir(), ".claude", "settings.json");
|
|
10900
11355
|
try {
|
|
10901
11356
|
let settings = {};
|
|
10902
|
-
if (
|
|
10903
|
-
settings = JSON.parse(
|
|
11357
|
+
if (fs16.existsSync(settingsPath)) {
|
|
11358
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
10904
11359
|
}
|
|
10905
11360
|
const env = settings.env || {};
|
|
10906
11361
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
10907
11362
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
10908
|
-
|
|
10909
|
-
|
|
11363
|
+
fs16.mkdirSync(path20.dirname(settingsPath), { recursive: true });
|
|
11364
|
+
fs16.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
10910
11365
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
10911
11366
|
} else {
|
|
10912
11367
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -10958,7 +11413,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
10958
11413
|
chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
|
|
10959
11414
|
);
|
|
10960
11415
|
if (projectPath) {
|
|
10961
|
-
const projectName =
|
|
11416
|
+
const projectName = path20.basename(projectPath);
|
|
10962
11417
|
console.log(chalk4.bold(`
|
|
10963
11418
|
Run this to register it:`));
|
|
10964
11419
|
console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
|
|
@@ -10986,8 +11441,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
10986
11441
|
init_dist();
|
|
10987
11442
|
init_dist2();
|
|
10988
11443
|
import { Command as Command3 } from "commander";
|
|
10989
|
-
import
|
|
10990
|
-
import
|
|
11444
|
+
import fs17 from "fs";
|
|
11445
|
+
import path21 from "path";
|
|
10991
11446
|
import chalk5 from "chalk";
|
|
10992
11447
|
function restartAfterProjectsAdd(opts) {
|
|
10993
11448
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
@@ -10999,22 +11454,22 @@ function restartAfterProjectsAdd(opts) {
|
|
|
10999
11454
|
}
|
|
11000
11455
|
}
|
|
11001
11456
|
function findPackageRoot2() {
|
|
11002
|
-
let dir =
|
|
11457
|
+
let dir = path21.dirname(new URL(import.meta.url).pathname);
|
|
11003
11458
|
while (dir !== "/") {
|
|
11004
|
-
if (
|
|
11005
|
-
dir =
|
|
11459
|
+
if (fs17.existsSync(path21.join(dir, "package.json"))) return dir;
|
|
11460
|
+
dir = path21.dirname(dir);
|
|
11006
11461
|
}
|
|
11007
11462
|
return process.cwd();
|
|
11008
11463
|
}
|
|
11009
11464
|
function copyDirRecursive2(src, dest) {
|
|
11010
|
-
|
|
11011
|
-
for (const entry of
|
|
11012
|
-
const srcPath =
|
|
11013
|
-
const destPath =
|
|
11465
|
+
fs17.mkdirSync(dest, { recursive: true });
|
|
11466
|
+
for (const entry of fs17.readdirSync(src, { withFileTypes: true })) {
|
|
11467
|
+
const srcPath = path21.join(src, entry.name);
|
|
11468
|
+
const destPath = path21.join(dest, entry.name);
|
|
11014
11469
|
if (entry.isDirectory()) {
|
|
11015
11470
|
copyDirRecursive2(srcPath, destPath);
|
|
11016
11471
|
} else {
|
|
11017
|
-
|
|
11472
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
11018
11473
|
}
|
|
11019
11474
|
}
|
|
11020
11475
|
}
|
|
@@ -11050,7 +11505,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
11050
11505
|
process.exit(1);
|
|
11051
11506
|
}
|
|
11052
11507
|
const resolvedPath = resolveHome(projectPath);
|
|
11053
|
-
if (!
|
|
11508
|
+
if (!fs17.existsSync(path21.join(resolvedPath, ".git"))) {
|
|
11054
11509
|
console.log(chalk5.yellow(`
|
|
11055
11510
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
11056
11511
|
`));
|
|
@@ -11103,7 +11558,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
11103
11558
|
\u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
|
|
11104
11559
|
}
|
|
11105
11560
|
}
|
|
11106
|
-
const spokeVault = opts.spoke ? resolveHome(opts.spoke) :
|
|
11561
|
+
const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path21.join(config.hubVault, "spokes", name);
|
|
11107
11562
|
const project = {
|
|
11108
11563
|
path: resolvedPath,
|
|
11109
11564
|
captainName,
|
|
@@ -11118,20 +11573,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
11118
11573
|
\u2714 Project '${name}' registered`));
|
|
11119
11574
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
11120
11575
|
const pkgRoot = findPackageRoot2();
|
|
11121
|
-
const spokeTemplate =
|
|
11122
|
-
if (
|
|
11576
|
+
const spokeTemplate = path21.join(pkgRoot, "obsidian", "spoke");
|
|
11577
|
+
if (fs17.existsSync(spokeVault)) {
|
|
11123
11578
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
11124
|
-
} else if (
|
|
11579
|
+
} else if (fs17.existsSync(spokeTemplate)) {
|
|
11125
11580
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
11126
|
-
const statusPath =
|
|
11127
|
-
if (
|
|
11128
|
-
const content =
|
|
11581
|
+
const statusPath = path21.join(spokeVault, "status.md");
|
|
11582
|
+
if (fs17.existsSync(statusPath)) {
|
|
11583
|
+
const content = fs17.readFileSync(statusPath, "utf-8");
|
|
11129
11584
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
11130
|
-
|
|
11585
|
+
fs17.writeFileSync(statusPath, updated);
|
|
11131
11586
|
}
|
|
11132
11587
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
11133
11588
|
} else {
|
|
11134
|
-
|
|
11589
|
+
fs17.mkdirSync(spokeVault, { recursive: true });
|
|
11135
11590
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
11136
11591
|
}
|
|
11137
11592
|
console.log("");
|
|
@@ -11200,10 +11655,10 @@ init_dist4();
|
|
|
11200
11655
|
init_dist();
|
|
11201
11656
|
import { Command as Command5 } from "commander";
|
|
11202
11657
|
import { execSync as execSync9 } from "child_process";
|
|
11203
|
-
import
|
|
11658
|
+
import path22 from "path";
|
|
11204
11659
|
import os11 from "os";
|
|
11205
11660
|
import chalk7 from "chalk";
|
|
11206
|
-
var TEMPLATES_DIR2 =
|
|
11661
|
+
var TEMPLATES_DIR2 = path22.join(os11.homedir(), ".config", "squadrant", "templates");
|
|
11207
11662
|
var TASK_PROMPTS = {
|
|
11208
11663
|
briefing: "Run your daily briefing using the squadrant:command-ops skill. Read all spoke handoffs, yesterday's logs, current status; produce a concise cross-project briefing; save to {hubVault}/daily-logs/YYYY-MM-DD.md; then exit.",
|
|
11209
11664
|
"learnings-review": "Run a learnings review using the squadrant:command-ops skill. Scan {spokeVault}/learnings across all projects, identify cross-project patterns, propose captured-skill or fix actions, and exit when done.",
|
|
@@ -11236,7 +11691,7 @@ async function runCommandSpawn(input) {
|
|
|
11236
11691
|
if (!agent) {
|
|
11237
11692
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
11238
11693
|
}
|
|
11239
|
-
const promptFile =
|
|
11694
|
+
const promptFile = path22.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
|
|
11240
11695
|
const cliCommand = agent.buildCommand({
|
|
11241
11696
|
prompt,
|
|
11242
11697
|
workdir: process.cwd(),
|
|
@@ -11264,21 +11719,21 @@ init_dist();
|
|
|
11264
11719
|
init_dist3();
|
|
11265
11720
|
init_dist4();
|
|
11266
11721
|
init_dist2();
|
|
11267
|
-
import { Command as
|
|
11268
|
-
import
|
|
11722
|
+
import { Command as Command10 } from "commander";
|
|
11723
|
+
import chalk10 from "chalk";
|
|
11269
11724
|
|
|
11270
11725
|
// packages/cli/src/commands/crew-control.ts
|
|
11271
11726
|
init_dist2();
|
|
11272
11727
|
init_dist2();
|
|
11273
11728
|
init_dist();
|
|
11274
11729
|
init_dist4();
|
|
11275
|
-
import { Command as
|
|
11730
|
+
import { Command as Command9 } from "commander";
|
|
11276
11731
|
import { createConnection as createConnection3 } from "net";
|
|
11277
11732
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11278
11733
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
11279
|
-
import { homedir as
|
|
11280
|
-
import { join as
|
|
11281
|
-
import { mkdirSync as
|
|
11734
|
+
import { homedir as homedir18 } from "os";
|
|
11735
|
+
import { join as join23 } from "path";
|
|
11736
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
|
|
11282
11737
|
|
|
11283
11738
|
// packages/cli/src/commands/crew-output.ts
|
|
11284
11739
|
function tailLines(text, maxLines = 40, maxBytes = 4096) {
|
|
@@ -11328,7 +11783,26 @@ function formatCompactTasks(records, opts) {
|
|
|
11328
11783
|
if (opts.compact === false) {
|
|
11329
11784
|
return JSON.stringify(records, null, 2);
|
|
11330
11785
|
}
|
|
11331
|
-
|
|
11786
|
+
const active = records.filter((r) => !r.operatorHold);
|
|
11787
|
+
const held = records.filter((r) => r.operatorHold);
|
|
11788
|
+
let out = "";
|
|
11789
|
+
if (active.length > 0) {
|
|
11790
|
+
if (held.length > 0) out += `active (${active.length}):
|
|
11791
|
+
`;
|
|
11792
|
+
out += active.map((r) => (held.length > 0 ? " " : "") + formatTaskLine(r)).join("\n");
|
|
11793
|
+
}
|
|
11794
|
+
if (held.length > 0) {
|
|
11795
|
+
if (out) out += "\n";
|
|
11796
|
+
out += `HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:
|
|
11797
|
+
`;
|
|
11798
|
+
out += held.map((r) => {
|
|
11799
|
+
const m = Math.round((Date.now() - r.operatorHold.since) / 6e4);
|
|
11800
|
+
const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
|
|
11801
|
+
const note = r.operatorHold.note ? ` \xB7 "${r.operatorHold.note}"` : "";
|
|
11802
|
+
return ` ${formatTaskLine(r)} \xB7 held ${hm}${note}`;
|
|
11803
|
+
}).join("\n");
|
|
11804
|
+
}
|
|
11805
|
+
return out;
|
|
11332
11806
|
}
|
|
11333
11807
|
|
|
11334
11808
|
// packages/cli/src/commands/crew-attach.ts
|
|
@@ -11576,7 +12050,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
11576
12050
|
});
|
|
11577
12051
|
|
|
11578
12052
|
// packages/cli/src/commands/crew-control.ts
|
|
11579
|
-
var SOCK2 =
|
|
12053
|
+
var SOCK2 = join23(homedir18(), ".config", "squadrant", "squadrant.sock");
|
|
11580
12054
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
11581
12055
|
async function sendCodexFirstTurn(taskId, text) {
|
|
11582
12056
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -11640,6 +12114,40 @@ function buildGateResolveRequest(o) {
|
|
|
11640
12114
|
payload: { text: o.message, ...decision ? { decision } : {} }
|
|
11641
12115
|
};
|
|
11642
12116
|
}
|
|
12117
|
+
async function runCrewTakeover(mode, opts, deps) {
|
|
12118
|
+
const taskId = opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
|
|
12119
|
+
let project = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
|
|
12120
|
+
let target;
|
|
12121
|
+
if (taskId) {
|
|
12122
|
+
if (!project) {
|
|
12123
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
|
|
12124
|
+
}
|
|
12125
|
+
const tasks = await deps.listTasks(project);
|
|
12126
|
+
target = tasks.find((t) => t.id === taskId);
|
|
12127
|
+
if (!target) throw new Error(`Task '${taskId}' not found for project '${project}'`);
|
|
12128
|
+
} else if (project && opts.crew) {
|
|
12129
|
+
const tasks = await deps.listTasks(project);
|
|
12130
|
+
target = resolveApproveTarget(tasks, opts.crew) || void 0;
|
|
12131
|
+
if (!target) throw new Error(`Crew '${opts.crew}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
12132
|
+
} else {
|
|
12133
|
+
throw new Error("must provide either <project> <crew> or --task-id");
|
|
12134
|
+
}
|
|
12135
|
+
const nameDisp = project && opts.crew ? `${project}/${opts.crew}` : target.name ? `${target.project}/${target.name}` : `${target.project}/${target.id}`;
|
|
12136
|
+
if (mode === "start") {
|
|
12137
|
+
const event = { type: "crew.takeover.started", id: target.id, ...opts.note !== void 0 ? { note: opts.note } : {} };
|
|
12138
|
+
await deps.emitEvent(target.project, event);
|
|
12139
|
+
const msg = `CREW TAKEOVER [${nameDisp}] \u2014 operator is driving this tab. Do not send, do not close, do not act on its signals until handback.`;
|
|
12140
|
+
await deps.runtimeSend(target.project, msg);
|
|
12141
|
+
deps.printSuccess(msg);
|
|
12142
|
+
} else {
|
|
12143
|
+
const event = { type: "crew.takeover.ended", id: target.id };
|
|
12144
|
+
await deps.emitEvent(target.project, event);
|
|
12145
|
+
const msg = `CREW HANDBACK [${nameDisp}] \u2014 operator returned control. State: ${target.state}. Run 'squadrant crew read ${target.project} ${target.name || target.id}' before acting; the tab has history you did not see.`;
|
|
12146
|
+
await deps.runtimeSend(target.project, msg);
|
|
12147
|
+
deps.printSuccess(msg);
|
|
12148
|
+
}
|
|
12149
|
+
return target;
|
|
12150
|
+
}
|
|
11643
12151
|
async function squadrantdCall(req) {
|
|
11644
12152
|
try {
|
|
11645
12153
|
return await sendRequest(SOCK2, req);
|
|
@@ -11683,10 +12191,10 @@ function buildSignalRequest(signal, o) {
|
|
|
11683
12191
|
return { kind: "event", project, event };
|
|
11684
12192
|
}
|
|
11685
12193
|
function defaultWriteResult(id, payload) {
|
|
11686
|
-
const dir =
|
|
11687
|
-
|
|
11688
|
-
const file =
|
|
11689
|
-
|
|
12194
|
+
const dir = join23(homedir18(), ".config", "squadrant", "state", "_results");
|
|
12195
|
+
mkdirSync8(dir, { recursive: true });
|
|
12196
|
+
const file = join23(dir, `${id}.txt`);
|
|
12197
|
+
writeFileSync10(file, payload);
|
|
11690
12198
|
return file;
|
|
11691
12199
|
}
|
|
11692
12200
|
async function runCrewSignal(signal, o, deps) {
|
|
@@ -11865,6 +12373,60 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11865
12373
|
`);
|
|
11866
12374
|
} catch (e) {
|
|
11867
12375
|
process.stderr.write(`${e.message}
|
|
12376
|
+
`);
|
|
12377
|
+
process.exit(1);
|
|
12378
|
+
}
|
|
12379
|
+
});
|
|
12380
|
+
crew.command("takeover [project] [crewName]").description("Take over a crew's tab explicitly, suppressing captain action until handback (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").option("--note <note>", "Optional note explaining the takeover").action(async (project, crewName, opts) => {
|
|
12381
|
+
try {
|
|
12382
|
+
await runCrewTakeover("start", { project, crew: crewName, taskId: opts.taskId, note: opts.note }, {
|
|
12383
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
12384
|
+
emitEvent: async (p, event) => {
|
|
12385
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
12386
|
+
},
|
|
12387
|
+
runtimeSend: async (p, msg) => {
|
|
12388
|
+
const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
12389
|
+
await runRuntimeSend2(p, msg, {});
|
|
12390
|
+
},
|
|
12391
|
+
printError: (msg) => {
|
|
12392
|
+
process.stderr.write(`${msg}
|
|
12393
|
+
`);
|
|
12394
|
+
},
|
|
12395
|
+
printSuccess: (msg) => {
|
|
12396
|
+
process.stdout.write(`\u2714 ${msg}
|
|
12397
|
+
`);
|
|
12398
|
+
}
|
|
12399
|
+
});
|
|
12400
|
+
process.exit(0);
|
|
12401
|
+
} catch (e) {
|
|
12402
|
+
process.stderr.write(`${e.message}
|
|
12403
|
+
`);
|
|
12404
|
+
process.exit(1);
|
|
12405
|
+
}
|
|
12406
|
+
});
|
|
12407
|
+
crew.command("handback [project] [crewName]").description("Return control of a held crew tab to the captain (#649)").option("--task-id <id>", "Explicit task id (overrides SQUADRANT_CREW_TASK_ID env)").action(async (project, crewName, opts) => {
|
|
12408
|
+
try {
|
|
12409
|
+
await runCrewTakeover("end", { project, crew: crewName, taskId: opts.taskId }, {
|
|
12410
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
12411
|
+
emitEvent: async (p, event) => {
|
|
12412
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
12413
|
+
},
|
|
12414
|
+
runtimeSend: async (p, msg) => {
|
|
12415
|
+
const { runRuntimeSend: runRuntimeSend2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
12416
|
+
await runRuntimeSend2(p, msg, {});
|
|
12417
|
+
},
|
|
12418
|
+
printError: (msg) => {
|
|
12419
|
+
process.stderr.write(`${msg}
|
|
12420
|
+
`);
|
|
12421
|
+
},
|
|
12422
|
+
printSuccess: (msg) => {
|
|
12423
|
+
process.stdout.write(`\u2714 ${msg}
|
|
12424
|
+
`);
|
|
12425
|
+
}
|
|
12426
|
+
});
|
|
12427
|
+
process.exit(0);
|
|
12428
|
+
} catch (e) {
|
|
12429
|
+
process.stderr.write(`${e.message}
|
|
11868
12430
|
`);
|
|
11869
12431
|
process.exit(1);
|
|
11870
12432
|
}
|
|
@@ -11872,7 +12434,7 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11872
12434
|
crew.addCommand(crewAttachCommand);
|
|
11873
12435
|
crew.addCommand(crewChatCommand);
|
|
11874
12436
|
}
|
|
11875
|
-
var crewControlCommand = new
|
|
12437
|
+
var crewControlCommand = new Command9("crew").description("Dispatch and track crew via the squadrant control plane");
|
|
11876
12438
|
addControlPlaneCrewCommands(crewControlCommand);
|
|
11877
12439
|
|
|
11878
12440
|
// packages/cli/src/commands/crew.ts
|
|
@@ -11904,13 +12466,14 @@ async function runCrewSpawn2(input) {
|
|
|
11904
12466
|
await squadrantdCall({ kind: "event", project: p, event });
|
|
11905
12467
|
},
|
|
11906
12468
|
onRouted: (route) => console.log(
|
|
11907
|
-
|
|
12469
|
+
chalk10.dim(
|
|
11908
12470
|
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
11909
12471
|
)
|
|
11910
|
-
)
|
|
12472
|
+
),
|
|
12473
|
+
onBaseResolved: (base) => console.log(chalk10.dim(`base: ${base}`))
|
|
11911
12474
|
});
|
|
11912
12475
|
}
|
|
11913
|
-
async function runCrewSend2(project, name, message) {
|
|
12476
|
+
async function runCrewSend2(project, name, message, opts) {
|
|
11914
12477
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11915
12478
|
return runCrewSend(project, name, message, runtime, workspaceId, {
|
|
11916
12479
|
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
@@ -11922,13 +12485,13 @@ async function runCrewSend2(project, name, message) {
|
|
|
11922
12485
|
sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg),
|
|
11923
12486
|
// #516: side-effect-free modal precheck, run before any daemon-state emit.
|
|
11924
12487
|
isBlockedByModal: (pane) => paneHasOpenModal(runtime, pane)
|
|
11925
|
-
});
|
|
12488
|
+
}, opts);
|
|
11926
12489
|
}
|
|
11927
12490
|
async function runCrewRead2(project, name) {
|
|
11928
12491
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11929
12492
|
return runCrewRead(project, name, runtime, workspaceId);
|
|
11930
12493
|
}
|
|
11931
|
-
async function runCrewClose2(project, name) {
|
|
12494
|
+
async function runCrewClose2(project, name, opts) {
|
|
11932
12495
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11933
12496
|
return runCrewClose(project, name, runtime, workspaceId, {
|
|
11934
12497
|
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
@@ -11938,13 +12501,13 @@ async function runCrewClose2(project, name) {
|
|
|
11938
12501
|
closeCodexThread: async (taskId) => {
|
|
11939
12502
|
await squadrantdCall({ kind: "codex-close", taskId });
|
|
11940
12503
|
}
|
|
11941
|
-
});
|
|
12504
|
+
}, opts);
|
|
11942
12505
|
}
|
|
11943
12506
|
async function runCrewList2(project) {
|
|
11944
12507
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
11945
12508
|
return runCrewList(project, runtime, workspaceId);
|
|
11946
12509
|
}
|
|
11947
|
-
var crewCommand = new
|
|
12510
|
+
var crewCommand = new Command10("crew").description(
|
|
11948
12511
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
11949
12512
|
);
|
|
11950
12513
|
crewCommand.command("spawn").description(
|
|
@@ -11970,9 +12533,9 @@ crewCommand.command("spawn").description(
|
|
|
11970
12533
|
// into the isolated worktree root for relative-path access.
|
|
11971
12534
|
...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
|
|
11972
12535
|
});
|
|
11973
|
-
console.log(
|
|
12536
|
+
console.log(chalk10.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
|
|
11974
12537
|
} catch (err) {
|
|
11975
|
-
console.error(
|
|
12538
|
+
console.error(chalk10.red(err.message));
|
|
11976
12539
|
process.exit(1);
|
|
11977
12540
|
}
|
|
11978
12541
|
}
|
|
@@ -11981,24 +12544,48 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
11981
12544
|
try {
|
|
11982
12545
|
const crews = await runCrewList2(project);
|
|
11983
12546
|
if (crews.length === 0) {
|
|
11984
|
-
console.log(
|
|
12547
|
+
console.log(chalk10.yellow(`No live crew sessions for ${project}.`));
|
|
11985
12548
|
return;
|
|
11986
12549
|
}
|
|
12550
|
+
const tasks = await squadrantdCall({ kind: "list", project }).catch(() => []);
|
|
12551
|
+
const active = [];
|
|
12552
|
+
const held = [];
|
|
11987
12553
|
for (const c of crews) {
|
|
11988
|
-
|
|
12554
|
+
const task = tasks.find((t2) => t2.name === c.name);
|
|
12555
|
+
const t = task ? resolveApproveTarget(tasks, c.name) : void 0;
|
|
12556
|
+
if (t?.operatorHold) {
|
|
12557
|
+
held.push({ c, t });
|
|
12558
|
+
} else {
|
|
12559
|
+
active.push({ c, t });
|
|
12560
|
+
}
|
|
12561
|
+
}
|
|
12562
|
+
if (active.length > 0) {
|
|
12563
|
+
console.log(`active (${active.length}):`);
|
|
12564
|
+
for (const { c, t } of active) {
|
|
12565
|
+
console.log(` ${c.name.padEnd(10)} ${t ? t.state : "unknown"} (${c.surfaceId})`);
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
if (held.length > 0) {
|
|
12569
|
+
console.log(`HELD BY OPERATOR (${held.length}) \u2014 not counted toward maxCrew:`);
|
|
12570
|
+
for (const { c, t } of held) {
|
|
12571
|
+
const m = Math.round((Date.now() - t.operatorHold.since) / 6e4);
|
|
12572
|
+
const hm = m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${m}m`;
|
|
12573
|
+
const note = t.operatorHold.note ? ` \xB7 "${t.operatorHold.note}"` : "";
|
|
12574
|
+
console.log(` ${c.name.padEnd(10)} ${t.state} \xB7 held ${hm}${note} (${c.surfaceId})`);
|
|
12575
|
+
}
|
|
11989
12576
|
}
|
|
11990
12577
|
} catch (err) {
|
|
11991
|
-
console.error(
|
|
12578
|
+
console.error(chalk10.red(err.message));
|
|
11992
12579
|
process.exit(1);
|
|
11993
12580
|
}
|
|
11994
12581
|
});
|
|
11995
|
-
crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").action(async (project, name, message, opts) => {
|
|
12582
|
+
crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, message, opts) => {
|
|
11996
12583
|
try {
|
|
11997
12584
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
11998
|
-
await runCrewSend2(project, name, resolvedMessage);
|
|
11999
|
-
console.log(
|
|
12000
|
-
} catch (
|
|
12001
|
-
console.error(
|
|
12585
|
+
await runCrewSend2(project, name, resolvedMessage, opts);
|
|
12586
|
+
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
12587
|
+
} catch (e) {
|
|
12588
|
+
console.error(chalk10.red(e.message));
|
|
12002
12589
|
process.exit(1);
|
|
12003
12590
|
}
|
|
12004
12591
|
});
|
|
@@ -12008,16 +12595,16 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
|
|
|
12008
12595
|
const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
|
|
12009
12596
|
console.log(out);
|
|
12010
12597
|
} catch (err) {
|
|
12011
|
-
console.error(
|
|
12598
|
+
console.error(chalk10.red(err.message));
|
|
12012
12599
|
process.exit(1);
|
|
12013
12600
|
}
|
|
12014
12601
|
});
|
|
12015
|
-
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
|
|
12602
|
+
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, opts) => {
|
|
12016
12603
|
try {
|
|
12017
|
-
await runCrewClose2(project, name);
|
|
12018
|
-
console.log(
|
|
12604
|
+
await runCrewClose2(project, name, opts);
|
|
12605
|
+
console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
|
|
12019
12606
|
} catch (err) {
|
|
12020
|
-
console.error(
|
|
12607
|
+
console.error(chalk10.red(err.message));
|
|
12021
12608
|
process.exit(1);
|
|
12022
12609
|
}
|
|
12023
12610
|
});
|
|
@@ -12025,8 +12612,8 @@ crewCommand.command("close").description("Shutdown a crew session (closes its ta
|
|
|
12025
12612
|
// packages/cli/src/commands/diff.ts
|
|
12026
12613
|
init_dist();
|
|
12027
12614
|
init_dist3();
|
|
12028
|
-
import { Command as
|
|
12029
|
-
import
|
|
12615
|
+
import { Command as Command11 } from "commander";
|
|
12616
|
+
import chalk11 from "chalk";
|
|
12030
12617
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
12031
12618
|
import readline2 from "readline";
|
|
12032
12619
|
function resolveDiffTarget(tasks, crew, projectPath) {
|
|
@@ -12143,7 +12730,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
|
12143
12730
|
const label = sources.length > 1 ? "staged or unstaged" : sources[0];
|
|
12144
12731
|
console.log(`No ${label} changes on ${branchLabel}.`);
|
|
12145
12732
|
} else {
|
|
12146
|
-
console.log(
|
|
12733
|
+
console.log(chalk11.dim(`Opened ${opened} working-tree diff(s) (${sources.join(", ")}) for ${branchLabel}.`));
|
|
12147
12734
|
}
|
|
12148
12735
|
return;
|
|
12149
12736
|
}
|
|
@@ -12166,7 +12753,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
|
|
|
12166
12753
|
lastTurn: opts.lastTurn,
|
|
12167
12754
|
source: "branch"
|
|
12168
12755
|
});
|
|
12169
|
-
console.log(
|
|
12756
|
+
console.log(chalk11.dim(`Opened ${branchLabel} vs ${base} in cmux diff.`));
|
|
12170
12757
|
}
|
|
12171
12758
|
async function runDiff(project, crewArg, opts) {
|
|
12172
12759
|
const config = loadConfig();
|
|
@@ -12187,7 +12774,7 @@ async function runDiff(project, crewArg, opts) {
|
|
|
12187
12774
|
return;
|
|
12188
12775
|
}
|
|
12189
12776
|
await runtime.showPatch({ workspaceId, patch, title, layout: opts.layout, focus: opts.focus });
|
|
12190
|
-
console.log(
|
|
12777
|
+
console.log(chalk11.dim(`Opened ${title} in cmux diff.`));
|
|
12191
12778
|
return;
|
|
12192
12779
|
}
|
|
12193
12780
|
let crew;
|
|
@@ -12213,7 +12800,7 @@ async function runDiff(project, crewArg, opts) {
|
|
|
12213
12800
|
}
|
|
12214
12801
|
await openCrewDiff(project, proj, crew, opts, runtime, workspaceId);
|
|
12215
12802
|
}
|
|
12216
|
-
var diffCommand = new
|
|
12803
|
+
var diffCommand = new Command11("diff").description("Open a crew's branch diff, a PR, or a ref comparison in cmux's native diff viewer \u2014 no VSCode required (#596/#604)").argument("<project>", "Project name (must be registered)").argument("[crew]", "Crew name (e.g. crew-1); omit with no other flags to pick from live crews").option("--pr <n>", "Review a PR: wraps `gh pr diff <n>` (mutually exclusive with crew/--base/--head)").option("--base <ref>", "Base ref for a --head ref comparison (merge-base diff)").option("--head <ref>", "Head ref for a --base ref comparison").option("--against <ref>", "Alias: diff <ref>...HEAD (mutually exclusive with --base/--head)").option("--layout <mode>", "split (default) or unified", "split").option("--last-turn", "diff only changes since the crew's last agent turn", false).option("--no-focus", "open the diff pane without stealing focus").option("--staged", "show only staged (index) changes \u2014 VSCode's 'Staged Changes' panel (#599)", false).option("--unstaged", "show only unstaged working-tree changes \u2014 VSCode's 'Changes' panel (#599)", false).option("--working", "show both staged and unstaged changes (mid-task working-tree review, #599)", false).action(runDiff);
|
|
12217
12804
|
|
|
12218
12805
|
// packages/cli/src/commands/side.ts
|
|
12219
12806
|
init_dist();
|
|
@@ -12222,12 +12809,12 @@ init_dist4();
|
|
|
12222
12809
|
init_dist3();
|
|
12223
12810
|
init_dist();
|
|
12224
12811
|
init_dist2();
|
|
12225
|
-
import { Command as
|
|
12226
|
-
import
|
|
12227
|
-
import
|
|
12812
|
+
import { Command as Command12 } from "commander";
|
|
12813
|
+
import fs18 from "fs";
|
|
12814
|
+
import path23 from "path";
|
|
12228
12815
|
import os12 from "os";
|
|
12229
|
-
import
|
|
12230
|
-
var TEMPLATES_DIR3 =
|
|
12816
|
+
import chalk12 from "chalk";
|
|
12817
|
+
var TEMPLATES_DIR3 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
12231
12818
|
async function runSideSpawn2(input) {
|
|
12232
12819
|
const config = loadConfig();
|
|
12233
12820
|
const proj = config.projects[input.project];
|
|
@@ -12251,7 +12838,7 @@ async function runSideSpawn2(input) {
|
|
|
12251
12838
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
12252
12839
|
}
|
|
12253
12840
|
const sideModel = sideRole?.model;
|
|
12254
|
-
const promptFile =
|
|
12841
|
+
const promptFile = path23.join(
|
|
12255
12842
|
TEMPLATES_DIR3,
|
|
12256
12843
|
`side.${input.role}.${agent.templateSuffix}.md`
|
|
12257
12844
|
);
|
|
@@ -12259,7 +12846,7 @@ async function runSideSpawn2(input) {
|
|
|
12259
12846
|
prompt: input.topic,
|
|
12260
12847
|
workdir: spawnCwd,
|
|
12261
12848
|
role: "side",
|
|
12262
|
-
promptFile:
|
|
12849
|
+
promptFile: fs18.existsSync(promptFile) ? promptFile : void 0,
|
|
12263
12850
|
interactive: true,
|
|
12264
12851
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
12265
12852
|
...sideModel ? { model: sideModel } : {}
|
|
@@ -12288,7 +12875,7 @@ async function runSideClose2(project, name) {
|
|
|
12288
12875
|
config.defaults.worktreeDir ?? ".worktrees"
|
|
12289
12876
|
);
|
|
12290
12877
|
}
|
|
12291
|
-
var sideCommand = new
|
|
12878
|
+
var sideCommand = new Command12("side").description(
|
|
12292
12879
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
12293
12880
|
);
|
|
12294
12881
|
sideCommand.command("spawn").description(
|
|
@@ -12313,9 +12900,9 @@ sideCommand.command("spawn").description(
|
|
|
12313
12900
|
direction: opts.direction,
|
|
12314
12901
|
agent: opts.agent
|
|
12315
12902
|
});
|
|
12316
|
-
console.log(
|
|
12903
|
+
console.log(chalk12.green(`\u2714 Side session '${pane.title}' spawned (${pane.surfaceId})`));
|
|
12317
12904
|
} catch (err) {
|
|
12318
|
-
console.error(
|
|
12905
|
+
console.error(chalk12.red(err.message));
|
|
12319
12906
|
process.exit(1);
|
|
12320
12907
|
}
|
|
12321
12908
|
}
|
|
@@ -12324,14 +12911,14 @@ sideCommand.command("list").description("List live side-sessions for a project")
|
|
|
12324
12911
|
try {
|
|
12325
12912
|
const sessions = await runSideList2(project);
|
|
12326
12913
|
if (sessions.length === 0) {
|
|
12327
|
-
console.log(
|
|
12914
|
+
console.log(chalk12.yellow(`No live side-sessions for ${project}.`));
|
|
12328
12915
|
return;
|
|
12329
12916
|
}
|
|
12330
12917
|
for (const s of sessions) {
|
|
12331
12918
|
console.log(` ${s.name} (${s.surfaceId})`);
|
|
12332
12919
|
}
|
|
12333
12920
|
} catch (err) {
|
|
12334
|
-
console.error(
|
|
12921
|
+
console.error(chalk12.red(err.message));
|
|
12335
12922
|
process.exit(1);
|
|
12336
12923
|
}
|
|
12337
12924
|
});
|
|
@@ -12344,9 +12931,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
12344
12931
|
label: "message"
|
|
12345
12932
|
});
|
|
12346
12933
|
await runSideSend2(project, name, resolvedMessage);
|
|
12347
|
-
console.log(
|
|
12934
|
+
console.log(chalk12.green(`\u2714 Sent to ${project}:${name}`));
|
|
12348
12935
|
} catch (err) {
|
|
12349
|
-
console.error(
|
|
12936
|
+
console.error(chalk12.red(err.message));
|
|
12350
12937
|
process.exit(1);
|
|
12351
12938
|
}
|
|
12352
12939
|
}
|
|
@@ -12354,9 +12941,9 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
12354
12941
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
12355
12942
|
try {
|
|
12356
12943
|
await runSideClose2(project, name);
|
|
12357
|
-
console.log(
|
|
12944
|
+
console.log(chalk12.green(`\u2714 Closed ${project}:${name}`));
|
|
12358
12945
|
} catch (err) {
|
|
12359
|
-
console.error(
|
|
12946
|
+
console.error(chalk12.red(err.message));
|
|
12360
12947
|
process.exit(1);
|
|
12361
12948
|
}
|
|
12362
12949
|
});
|
|
@@ -12364,11 +12951,11 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
12364
12951
|
// packages/cli/src/commands/dashboard.ts
|
|
12365
12952
|
init_dist();
|
|
12366
12953
|
init_dist3();
|
|
12367
|
-
import { Command as
|
|
12954
|
+
import { Command as Command13 } from "commander";
|
|
12368
12955
|
import { execSync as execSync10 } from "child_process";
|
|
12369
|
-
import { homedir as
|
|
12370
|
-
import { join as
|
|
12371
|
-
import
|
|
12956
|
+
import { homedir as homedir20 } from "os";
|
|
12957
|
+
import { join as join25 } from "path";
|
|
12958
|
+
import chalk14 from "chalk";
|
|
12372
12959
|
|
|
12373
12960
|
// packages/web/dist/read-status.js
|
|
12374
12961
|
function deriveState(tasks) {
|
|
@@ -12445,14 +13032,14 @@ async function readAllStatuses(deps) {
|
|
|
12445
13032
|
}
|
|
12446
13033
|
|
|
12447
13034
|
// packages/web/dist/render.js
|
|
12448
|
-
import
|
|
13035
|
+
import chalk13 from "chalk";
|
|
12449
13036
|
var ICON = {
|
|
12450
|
-
idle:
|
|
12451
|
-
busy:
|
|
12452
|
-
blocked:
|
|
12453
|
-
errored:
|
|
12454
|
-
offline:
|
|
12455
|
-
unknown:
|
|
13037
|
+
idle: chalk13.green,
|
|
13038
|
+
busy: chalk13.cyan,
|
|
13039
|
+
blocked: chalk13.yellow,
|
|
13040
|
+
errored: chalk13.red,
|
|
13041
|
+
offline: chalk13.dim,
|
|
13042
|
+
unknown: chalk13.gray
|
|
12456
13043
|
};
|
|
12457
13044
|
var ICON_CHAR = {
|
|
12458
13045
|
idle: "\u25CF",
|
|
@@ -12495,10 +13082,10 @@ function renderDashboard(rows, opts) {
|
|
|
12495
13082
|
const width = opts.width ?? 100;
|
|
12496
13083
|
const lines = [];
|
|
12497
13084
|
lines.push("");
|
|
12498
|
-
lines.push(" " +
|
|
13085
|
+
lines.push(" " + chalk13.bold("\u{1F4CA} Squadrant Dashboard") + " " + chalk13.dim(opts.now));
|
|
12499
13086
|
lines.push("");
|
|
12500
13087
|
if (rows.length === 0) {
|
|
12501
|
-
lines.push(" " +
|
|
13088
|
+
lines.push(" " + chalk13.yellow("No projects registered. Add one with: squadrant projects add <name> <path>"));
|
|
12502
13089
|
lines.push("");
|
|
12503
13090
|
return lines.join("\n");
|
|
12504
13091
|
}
|
|
@@ -12509,22 +13096,22 @@ function renderDashboard(rows, opts) {
|
|
|
12509
13096
|
const excerptW = Math.max(20, width - FIXED);
|
|
12510
13097
|
for (const r of rows) {
|
|
12511
13098
|
const icon = ICON[r.state](ICON_CHAR[r.state]);
|
|
12512
|
-
const name =
|
|
13099
|
+
const name = chalk13.cyan(pad(r.project, NAME_W));
|
|
12513
13100
|
const state = ICON[r.state](pad(r.state, STATE_W));
|
|
12514
13101
|
const age = pad(formatAge(r.lastChecked, opts.now), AGE_W);
|
|
12515
|
-
const excerpt =
|
|
13102
|
+
const excerpt = chalk13.dim(truncate(firstLine(r.excerpt), excerptW));
|
|
12516
13103
|
lines.push(` ${icon} ${name} ${state} ${age} \u2502 ${excerpt}`);
|
|
12517
13104
|
}
|
|
12518
13105
|
lines.push("");
|
|
12519
|
-
lines.push(
|
|
13106
|
+
lines.push(chalk13.dim(" Refreshes every 10s \xB7 Ctrl+C to exit"));
|
|
12520
13107
|
lines.push("");
|
|
12521
13108
|
return lines.join("\n");
|
|
12522
13109
|
}
|
|
12523
13110
|
|
|
12524
13111
|
// packages/web/dist/sync-hub.js
|
|
12525
13112
|
init_dist();
|
|
12526
|
-
import
|
|
12527
|
-
import
|
|
13113
|
+
import fs19 from "fs";
|
|
13114
|
+
import path24 from "path";
|
|
12528
13115
|
function buildMirrorMarkdown(s) {
|
|
12529
13116
|
const fenced = "```";
|
|
12530
13117
|
return [
|
|
@@ -12550,15 +13137,15 @@ function buildMirrorMarkdown(s) {
|
|
|
12550
13137
|
function syncHub(deps) {
|
|
12551
13138
|
if (!deps.config.hubVault)
|
|
12552
13139
|
return [];
|
|
12553
|
-
const writeFile5 = deps.writeFile ?? ((p, c) =>
|
|
12554
|
-
const mkdir5 = deps.mkdir ?? ((p) =>
|
|
12555
|
-
const projectsDir =
|
|
13140
|
+
const writeFile5 = deps.writeFile ?? ((p, c) => fs19.writeFileSync(p, c));
|
|
13141
|
+
const mkdir5 = deps.mkdir ?? ((p) => fs19.mkdirSync(p, { recursive: true }));
|
|
13142
|
+
const projectsDir = path24.join(resolveHome(deps.config.hubVault), "projects");
|
|
12556
13143
|
mkdir5(projectsDir);
|
|
12557
13144
|
const out = [];
|
|
12558
13145
|
for (const s of deps.statuses) {
|
|
12559
13146
|
if (s.state === "unknown")
|
|
12560
13147
|
continue;
|
|
12561
|
-
const hubPath =
|
|
13148
|
+
const hubPath = path24.join(projectsDir, `${s.project}.md`);
|
|
12562
13149
|
try {
|
|
12563
13150
|
writeFile5(hubPath, buildMirrorMarkdown(s));
|
|
12564
13151
|
out.push({ project: s.project, hubPath });
|
|
@@ -12580,9 +13167,9 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
12580
13167
|
// packages/web/dist/probes.js
|
|
12581
13168
|
init_dist();
|
|
12582
13169
|
init_dist();
|
|
12583
|
-
import { join as
|
|
12584
|
-
import { homedir as
|
|
12585
|
-
import { existsSync as existsSync12, readFileSync as
|
|
13170
|
+
import { join as join24 } from "path";
|
|
13171
|
+
import { homedir as homedir19 } from "os";
|
|
13172
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
12586
13173
|
import { execFile as execFile4 } from "child_process";
|
|
12587
13174
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
12588
13175
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
@@ -12618,7 +13205,7 @@ function vaultProbe(run, dir) {
|
|
|
12618
13205
|
return { state: "unknown", detail: "no vault configured" };
|
|
12619
13206
|
if (!run.pathExists(dir))
|
|
12620
13207
|
return { state: "gone", detail: "vault directory missing" };
|
|
12621
|
-
if (!run.pathExists(
|
|
13208
|
+
if (!run.pathExists(join24(dir, ".obsidian")))
|
|
12622
13209
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
12623
13210
|
return { state: "alive" };
|
|
12624
13211
|
} catch {
|
|
@@ -12686,13 +13273,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
12686
13273
|
const sessions = probeSessions(run);
|
|
12687
13274
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
12688
13275
|
}
|
|
12689
|
-
var SESSIONS_PATH =
|
|
13276
|
+
var SESSIONS_PATH = join24(homedir19(), ".config", "squadrant", "sessions.json");
|
|
12690
13277
|
function onPath(cli) {
|
|
12691
13278
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
12692
|
-
return dirs.some((d) => existsSync12(
|
|
13279
|
+
return dirs.some((d) => existsSync12(join24(d, cli)));
|
|
12693
13280
|
}
|
|
12694
13281
|
function readSessionsHashes() {
|
|
12695
|
-
const raw = JSON.parse(
|
|
13282
|
+
const raw = JSON.parse(readFileSync14(SESSIONS_PATH, "utf-8"));
|
|
12696
13283
|
const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
|
|
12697
13284
|
return [...new Set(hashes)];
|
|
12698
13285
|
}
|
|
@@ -13597,7 +14184,7 @@ async function startWebServer(opts) {
|
|
|
13597
14184
|
|
|
13598
14185
|
// packages/cli/src/commands/dashboard.ts
|
|
13599
14186
|
init_dist();
|
|
13600
|
-
var SOCK3 =
|
|
14187
|
+
var SOCK3 = join25(homedir20(), ".config", "squadrant", "squadrant.sock");
|
|
13601
14188
|
function detectCurrentWorkspace2() {
|
|
13602
14189
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
13603
14190
|
const match = out.match(/workspace:\d+/);
|
|
@@ -13639,10 +14226,10 @@ async function runDashboardWeb(input) {
|
|
|
13639
14226
|
sockPath: SOCK3,
|
|
13640
14227
|
runners: defaultProbeRunners()
|
|
13641
14228
|
});
|
|
13642
|
-
console.log(
|
|
13643
|
-
console.log(
|
|
14229
|
+
console.log(chalk14.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
|
|
14230
|
+
console.log(chalk14.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
|
|
13644
14231
|
}
|
|
13645
|
-
var dashboardCommand = new
|
|
14232
|
+
var dashboardCommand = new Command13("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (v) => parseInt(v, 10), 7878).option("--direction <dir>", "Pane split direction (right|left|up|down)", "right").option("--interval <seconds>", "Daemon poll interval for --web (default 5); refresh interval for --pane (default 10)", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
13646
14233
|
try {
|
|
13647
14234
|
if (opts.web) {
|
|
13648
14235
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -13650,12 +14237,12 @@ var dashboardCommand = new Command12("dashboard").description("Live status grid
|
|
|
13650
14237
|
}
|
|
13651
14238
|
if (opts.pane) {
|
|
13652
14239
|
const pane = await runDashboardPane({ direction: opts.direction, interval: opts.interval ?? 10 });
|
|
13653
|
-
console.log(
|
|
14240
|
+
console.log(chalk14.green(`\u2714 Dashboard pane opened in ${pane.workspaceId} ${pane.surfaceId}`));
|
|
13654
14241
|
return;
|
|
13655
14242
|
}
|
|
13656
14243
|
await runDashboardOnce();
|
|
13657
14244
|
} catch (err) {
|
|
13658
|
-
console.error(
|
|
14245
|
+
console.error(chalk14.red(err.message));
|
|
13659
14246
|
process.exit(1);
|
|
13660
14247
|
}
|
|
13661
14248
|
});
|
|
@@ -13666,12 +14253,12 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
13666
14253
|
return;
|
|
13667
14254
|
}
|
|
13668
14255
|
if (results.length === 0) {
|
|
13669
|
-
console.log(
|
|
14256
|
+
console.log(chalk14.dim("\n No mirrors written (no projects with usable status.md, or hubVault unset).\n"));
|
|
13670
14257
|
return;
|
|
13671
14258
|
}
|
|
13672
|
-
console.log(
|
|
14259
|
+
console.log(chalk14.bold("\n \u{1F4CA} Hub mirror sync\n"));
|
|
13673
14260
|
for (const r of results) {
|
|
13674
|
-
console.log(` ${
|
|
14261
|
+
console.log(` ${chalk14.green("\u2714")} ${chalk14.cyan(r.project.padEnd(16))} \u2192 ${chalk14.dim(r.hubPath)}`);
|
|
13675
14262
|
}
|
|
13676
14263
|
console.log("");
|
|
13677
14264
|
});
|
|
@@ -13681,12 +14268,12 @@ init_dist();
|
|
|
13681
14268
|
init_dist4();
|
|
13682
14269
|
init_dist3();
|
|
13683
14270
|
init_dist2();
|
|
13684
|
-
import { Command as
|
|
14271
|
+
import { Command as Command14 } from "commander";
|
|
13685
14272
|
import { execSync as execSync11 } from "child_process";
|
|
13686
|
-
import
|
|
13687
|
-
import
|
|
14273
|
+
import fs20 from "fs";
|
|
14274
|
+
import path25 from "path";
|
|
13688
14275
|
import os13 from "os";
|
|
13689
|
-
import
|
|
14276
|
+
import chalk15 from "chalk";
|
|
13690
14277
|
|
|
13691
14278
|
// packages/cli/src/commands/launch-interactive.ts
|
|
13692
14279
|
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
@@ -13763,20 +14350,20 @@ async function selectCaptainsInteractive(entries, yesterday = getYesterday()) {
|
|
|
13763
14350
|
// packages/cli/src/commands/launch.ts
|
|
13764
14351
|
init_dist2();
|
|
13765
14352
|
var CMUX_APP = "/Applications/cmux.app";
|
|
13766
|
-
var TEMPLATES_DIR4 =
|
|
13767
|
-
var SESSIONS_PATH2 =
|
|
14353
|
+
var TEMPLATES_DIR4 = path25.join(os13.homedir(), ".config", "squadrant", "templates");
|
|
14354
|
+
var SESSIONS_PATH2 = path25.join(os13.homedir(), ".config", "squadrant", "sessions.json");
|
|
13768
14355
|
function ensureCmuxReady(headless) {
|
|
13769
14356
|
if (headless || isInsideCmux()) return;
|
|
13770
|
-
console.log(
|
|
14357
|
+
console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
13771
14358
|
execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
13772
|
-
console.log(
|
|
14359
|
+
console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
13773
14360
|
process.exit(0);
|
|
13774
14361
|
}
|
|
13775
|
-
var launchCommand = new
|
|
14362
|
+
var launchCommand = new Command14("launch").description(
|
|
13776
14363
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
13777
14364
|
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").action(async (project, opts) => {
|
|
13778
14365
|
if (opts.fresh && opts.keep) {
|
|
13779
|
-
console.error(
|
|
14366
|
+
console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
13780
14367
|
process.exit(1);
|
|
13781
14368
|
}
|
|
13782
14369
|
const config = loadConfig();
|
|
@@ -13824,29 +14411,29 @@ var launchCommand = new Command13("launch").description(
|
|
|
13824
14411
|
return null;
|
|
13825
14412
|
}
|
|
13826
14413
|
},
|
|
13827
|
-
onFreshReason: (reason) => console.log(
|
|
13828
|
-
onStoppingStale: (name) => console.log(
|
|
13829
|
-
onAlreadyExists: (name) => console.log(
|
|
13830
|
-
onCreated: (name) => console.log(
|
|
14414
|
+
onFreshReason: (reason) => console.log(chalk15.cyan(` \u21BB ${reason}`)),
|
|
14415
|
+
onStoppingStale: (name) => console.log(chalk15.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
14416
|
+
onAlreadyExists: (name) => console.log(chalk15.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
14417
|
+
onCreated: (name) => console.log(chalk15.green(` \u2714 Workspace '${name}' created`))
|
|
13831
14418
|
});
|
|
13832
14419
|
} catch (err) {
|
|
13833
|
-
console.error(
|
|
14420
|
+
console.error(chalk15.red(` \u2718 Failed: ${err.message}`));
|
|
13834
14421
|
hadFailure = true;
|
|
13835
14422
|
}
|
|
13836
14423
|
}
|
|
13837
14424
|
if (opts.all) {
|
|
13838
14425
|
const hubPath = resolveHome(config.hubVault);
|
|
13839
|
-
|
|
13840
|
-
console.log(
|
|
14426
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
14427
|
+
console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
|
|
13841
14428
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
13842
14429
|
const projPath = resolveHome(proj.path);
|
|
13843
14430
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13844
|
-
if (!
|
|
14431
|
+
if (!fs20.existsSync(spokePath)) {
|
|
13845
14432
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13846
14433
|
await ensureSpokeLayout(spokeDriver);
|
|
13847
|
-
console.log(
|
|
14434
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13848
14435
|
}
|
|
13849
|
-
console.log(
|
|
14436
|
+
console.log(chalk15.bold(`
|
|
13850
14437
|
Captain: ${proj.captainName} (${name})`));
|
|
13851
14438
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13852
14439
|
}
|
|
@@ -13854,7 +14441,7 @@ var launchCommand = new Command13("launch").description(
|
|
|
13854
14441
|
} else if (!project) {
|
|
13855
14442
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13856
14443
|
console.error(
|
|
13857
|
-
|
|
14444
|
+
chalk15.red(
|
|
13858
14445
|
"\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"
|
|
13859
14446
|
)
|
|
13860
14447
|
);
|
|
@@ -13869,22 +14456,22 @@ var launchCommand = new Command13("launch").description(
|
|
|
13869
14456
|
}));
|
|
13870
14457
|
const selected = await selectCaptainsInteractive(entries);
|
|
13871
14458
|
if (selected.length === 0) {
|
|
13872
|
-
console.log(
|
|
14459
|
+
console.log(chalk15.yellow("\n No captains selected.\n"));
|
|
13873
14460
|
return;
|
|
13874
14461
|
}
|
|
13875
|
-
console.log(
|
|
14462
|
+
console.log(chalk15.bold(`
|
|
13876
14463
|
Launching ${selected.length} captain workspace(s) in parallel
|
|
13877
14464
|
`));
|
|
13878
14465
|
await Promise.all(selected.map(async (name) => {
|
|
13879
14466
|
const proj = config.projects[name];
|
|
13880
14467
|
const projPath = resolveHome(proj.path);
|
|
13881
14468
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13882
|
-
if (!
|
|
14469
|
+
if (!fs20.existsSync(spokePath)) {
|
|
13883
14470
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
13884
14471
|
await ensureSpokeLayout(spokeDriver);
|
|
13885
|
-
console.log(
|
|
14472
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13886
14473
|
}
|
|
13887
|
-
console.log(
|
|
14474
|
+
console.log(chalk15.bold(`
|
|
13888
14475
|
Captain: ${proj.captainName} (${name})`));
|
|
13889
14476
|
await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
|
|
13890
14477
|
}));
|
|
@@ -13892,7 +14479,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13892
14479
|
} else {
|
|
13893
14480
|
if (!config.projects[project]) {
|
|
13894
14481
|
console.error(
|
|
13895
|
-
|
|
14482
|
+
chalk15.red(
|
|
13896
14483
|
`
|
|
13897
14484
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
13898
14485
|
`
|
|
@@ -13903,13 +14490,13 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
13903
14490
|
const proj = config.projects[project];
|
|
13904
14491
|
const projPath = resolveHome(proj.path);
|
|
13905
14492
|
const spokePath = resolveHome(proj.spokeVault);
|
|
13906
|
-
if (!
|
|
14493
|
+
if (!fs20.existsSync(spokePath)) {
|
|
13907
14494
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
13908
14495
|
await ensureSpokeLayout(spokeDriver);
|
|
13909
|
-
console.log(
|
|
14496
|
+
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
13910
14497
|
}
|
|
13911
14498
|
console.log(
|
|
13912
|
-
|
|
14499
|
+
chalk15.bold(
|
|
13913
14500
|
`
|
|
13914
14501
|
Launching captain workspace for '${project}' (${proj.captainName})
|
|
13915
14502
|
`
|
|
@@ -13923,8 +14510,8 @@ Launching captain workspace for '${project}' (${proj.captainName})
|
|
|
13923
14510
|
// packages/cli/src/commands/shutdown.ts
|
|
13924
14511
|
init_dist();
|
|
13925
14512
|
init_dist3();
|
|
13926
|
-
import { Command as
|
|
13927
|
-
import
|
|
14513
|
+
import { Command as Command15 } from "commander";
|
|
14514
|
+
import chalk16 from "chalk";
|
|
13928
14515
|
init_dist();
|
|
13929
14516
|
function nameVariants(name) {
|
|
13930
14517
|
const stripped = name.replace(/^⚓\s+/, "").trim();
|
|
@@ -13937,23 +14524,23 @@ async function closeMatching(runtime, variants, label) {
|
|
|
13937
14524
|
const failed = [];
|
|
13938
14525
|
if (matches.length === 0) {
|
|
13939
14526
|
console.log(
|
|
13940
|
-
|
|
14527
|
+
chalk16.yellow(` \u26A0 Workspace '${label}' not found \u2014 already closed?`)
|
|
13941
14528
|
);
|
|
13942
14529
|
return { closed, failed };
|
|
13943
14530
|
}
|
|
13944
14531
|
for (const ws of matches) {
|
|
13945
14532
|
try {
|
|
13946
14533
|
await runtime.stop(ws.id);
|
|
13947
|
-
console.log(
|
|
14534
|
+
console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
|
|
13948
14535
|
closed.push(ws.name);
|
|
13949
14536
|
} catch {
|
|
13950
|
-
console.log(
|
|
14537
|
+
console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13951
14538
|
failed.push(ws.name);
|
|
13952
14539
|
}
|
|
13953
14540
|
}
|
|
13954
14541
|
return { closed, failed };
|
|
13955
14542
|
}
|
|
13956
|
-
var shutdownCommand = new
|
|
14543
|
+
var shutdownCommand = new Command15("shutdown").description(
|
|
13957
14544
|
"Shutdown command + all captain workspaces (no args) or one captain workspace"
|
|
13958
14545
|
).argument("[project]", "Project name to shut down captain for").action(async (project) => {
|
|
13959
14546
|
const config = loadConfig();
|
|
@@ -13969,11 +14556,11 @@ var shutdownCommand = new Command14("shutdown").description(
|
|
|
13969
14556
|
const allVariants = /* @__PURE__ */ new Set([...captainVariants, ...commandVariants]);
|
|
13970
14557
|
const squadrantWorkspaces = workspaces.filter((w) => allVariants.has(w.name));
|
|
13971
14558
|
if (squadrantWorkspaces.length === 0) {
|
|
13972
|
-
console.log(
|
|
14559
|
+
console.log(chalk16.yellow("\nNo squadrant workspaces found to close.\n"));
|
|
13973
14560
|
return;
|
|
13974
14561
|
}
|
|
13975
14562
|
console.log(
|
|
13976
|
-
|
|
14563
|
+
chalk16.bold(
|
|
13977
14564
|
`
|
|
13978
14565
|
Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
13979
14566
|
`
|
|
@@ -13993,9 +14580,9 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
13993
14580
|
for (const ws of squadrantWorkspaces) {
|
|
13994
14581
|
try {
|
|
13995
14582
|
await globalRuntime.stop(ws.id);
|
|
13996
|
-
console.log(
|
|
14583
|
+
console.log(chalk16.green(` \u2714 Closed: ${ws.name}`));
|
|
13997
14584
|
} catch {
|
|
13998
|
-
console.log(
|
|
14585
|
+
console.log(chalk16.red(` \u2718 Failed to close: ${ws.name}`));
|
|
13999
14586
|
}
|
|
14000
14587
|
}
|
|
14001
14588
|
console.log("");
|
|
@@ -14003,7 +14590,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
14003
14590
|
}
|
|
14004
14591
|
if (!config.projects[project]) {
|
|
14005
14592
|
console.error(
|
|
14006
|
-
|
|
14593
|
+
chalk16.red(
|
|
14007
14594
|
`
|
|
14008
14595
|
\u2718 Project '${project}' not found. Run 'squadrant projects list' to see registered projects.
|
|
14009
14596
|
`
|
|
@@ -14014,7 +14601,7 @@ Shutting down ${squadrantWorkspaces.length} workspace(s)...
|
|
|
14014
14601
|
const captainName = config.projects[project].captainName;
|
|
14015
14602
|
const runtime = runtimes.forProject(project, config);
|
|
14016
14603
|
console.log(
|
|
14017
|
-
|
|
14604
|
+
chalk16.bold(`
|
|
14018
14605
|
Shutting down captain workspace for '${project}'...
|
|
14019
14606
|
`)
|
|
14020
14607
|
);
|
|
@@ -14038,25 +14625,25 @@ Shutting down captain workspace for '${project}'...
|
|
|
14038
14625
|
|
|
14039
14626
|
// packages/cli/src/commands/feedback.ts
|
|
14040
14627
|
init_dist();
|
|
14041
|
-
import { Command as
|
|
14042
|
-
import
|
|
14628
|
+
import { Command as Command16 } from "commander";
|
|
14629
|
+
import fs21 from "fs";
|
|
14043
14630
|
import os14 from "os";
|
|
14044
|
-
import
|
|
14631
|
+
import path26 from "path";
|
|
14045
14632
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14046
14633
|
import { execSync as execSync12 } from "child_process";
|
|
14047
|
-
import
|
|
14634
|
+
import chalk17 from "chalk";
|
|
14048
14635
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
14049
14636
|
function readPkgVersion() {
|
|
14050
14637
|
try {
|
|
14051
|
-
const pkgPath =
|
|
14052
|
-
return JSON.parse(
|
|
14638
|
+
const pkgPath = path26.join(path26.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
14639
|
+
return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
14053
14640
|
} catch {
|
|
14054
14641
|
return "unknown";
|
|
14055
14642
|
}
|
|
14056
14643
|
}
|
|
14057
14644
|
function readMetrics(metricsPath) {
|
|
14058
14645
|
try {
|
|
14059
|
-
return JSON.parse(
|
|
14646
|
+
return JSON.parse(fs21.readFileSync(metricsPath, "utf-8"));
|
|
14060
14647
|
} catch {
|
|
14061
14648
|
return {};
|
|
14062
14649
|
}
|
|
@@ -14092,21 +14679,21 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
14092
14679
|
});
|
|
14093
14680
|
return `${REPO_URL}/issues/new?${params.toString()}`;
|
|
14094
14681
|
}
|
|
14095
|
-
var feedbackCommand = new
|
|
14682
|
+
var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
14096
14683
|
const config = loadConfig();
|
|
14097
|
-
const metricsPath = config.metrics?.path ||
|
|
14684
|
+
const metricsPath = config.metrics?.path || path26.join(os14.homedir(), ".config", "squadrant", "metrics.json");
|
|
14098
14685
|
const metrics = readMetrics(metricsPath);
|
|
14099
14686
|
const version = readStamp(config) ?? readPkgVersion();
|
|
14100
14687
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
14101
|
-
console.log(
|
|
14102
|
-
console.log(
|
|
14688
|
+
console.log(chalk17.bold("\nOpening feedback issue in browser...\n"));
|
|
14689
|
+
console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
14103
14690
|
`));
|
|
14104
14691
|
try {
|
|
14105
14692
|
execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
14106
|
-
console.log(
|
|
14693
|
+
console.log(chalk17.green(" \u2714 Browser opened\n"));
|
|
14107
14694
|
} catch {
|
|
14108
|
-
console.log(
|
|
14109
|
-
console.log(` Open manually: ${
|
|
14695
|
+
console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
|
|
14696
|
+
console.log(` Open manually: ${chalk17.cyan(issueUrl)}
|
|
14110
14697
|
`);
|
|
14111
14698
|
}
|
|
14112
14699
|
});
|
|
@@ -14115,8 +14702,8 @@ var feedbackCommand = new Command15("feedback").description("Open a pre-filled G
|
|
|
14115
14702
|
init_dist();
|
|
14116
14703
|
init_dist();
|
|
14117
14704
|
init_dist3();
|
|
14118
|
-
import { Command as
|
|
14119
|
-
import
|
|
14705
|
+
import { Command as Command17 } from "commander";
|
|
14706
|
+
import chalk18 from "chalk";
|
|
14120
14707
|
function getDateStr(yesterday) {
|
|
14121
14708
|
return iso(daysAgo(yesterday ? 1 : 0));
|
|
14122
14709
|
}
|
|
@@ -14135,7 +14722,7 @@ function formatStandup(standups, dateStr, raw) {
|
|
|
14135
14722
|
const lines = [];
|
|
14136
14723
|
const header = `Standup \u2014 ${dateStr}`;
|
|
14137
14724
|
if (!raw) {
|
|
14138
|
-
lines.push(
|
|
14725
|
+
lines.push(chalk18.bold(`
|
|
14139
14726
|
${header}
|
|
14140
14727
|
`));
|
|
14141
14728
|
} else {
|
|
@@ -14145,12 +14732,12 @@ ${header}
|
|
|
14145
14732
|
let hasBlockers = false;
|
|
14146
14733
|
for (const s of standups) {
|
|
14147
14734
|
if (!raw) {
|
|
14148
|
-
lines.push(
|
|
14735
|
+
lines.push(chalk18.cyan.bold(`## ${s.name}`));
|
|
14149
14736
|
} else {
|
|
14150
14737
|
lines.push(`## ${s.name}`);
|
|
14151
14738
|
}
|
|
14152
14739
|
if (s.gitCommits.length > 0) {
|
|
14153
|
-
lines.push(!raw ?
|
|
14740
|
+
lines.push(!raw ? chalk18.green("Done:") : "**Done:**");
|
|
14154
14741
|
for (const commit of s.gitCommits) {
|
|
14155
14742
|
lines.push(` - ${commit}`);
|
|
14156
14743
|
}
|
|
@@ -14162,7 +14749,7 @@ ${header}
|
|
|
14162
14749
|
if (match) {
|
|
14163
14750
|
const items = match[1].trim().split("\n").filter((l) => l.trim().startsWith("-"));
|
|
14164
14751
|
if (items.length > 0 && section === "Tomorrow") {
|
|
14165
|
-
lines.push(!raw ?
|
|
14752
|
+
lines.push(!raw ? chalk18.blue("Next:") : "**Next:**");
|
|
14166
14753
|
for (const item of items) lines.push(` ${item.trim()}`);
|
|
14167
14754
|
}
|
|
14168
14755
|
}
|
|
@@ -14170,19 +14757,19 @@ ${header}
|
|
|
14170
14757
|
}
|
|
14171
14758
|
if (s.blockers.length > 0) {
|
|
14172
14759
|
hasBlockers = true;
|
|
14173
|
-
lines.push(!raw ?
|
|
14760
|
+
lines.push(!raw ? chalk18.red("Blocked:") : "**Blocked:**");
|
|
14174
14761
|
for (const b of s.blockers) {
|
|
14175
14762
|
lines.push(` - ${b}`);
|
|
14176
14763
|
}
|
|
14177
14764
|
}
|
|
14178
14765
|
if (s.gitCommits.length === 0 && !s.dailyLog) {
|
|
14179
|
-
lines.push(!raw ?
|
|
14766
|
+
lines.push(!raw ? chalk18.dim(" (no activity)") : " (no activity)");
|
|
14180
14767
|
}
|
|
14181
14768
|
lines.push("");
|
|
14182
14769
|
}
|
|
14183
14770
|
const totalCommits = standups.reduce((sum, s) => sum + s.gitCommits.length, 0);
|
|
14184
14771
|
if (!raw) {
|
|
14185
|
-
lines.push(
|
|
14772
|
+
lines.push(chalk18.dim(`--- ${totalCommits} commits${hasBlockers ? ", HAS BLOCKERS" : ""} (task tracking: no data source \u2014 #630) ---
|
|
14186
14773
|
`));
|
|
14187
14774
|
} else {
|
|
14188
14775
|
lines.push(`---
|
|
@@ -14191,12 +14778,12 @@ ${header}
|
|
|
14191
14778
|
}
|
|
14192
14779
|
return lines.join("\n");
|
|
14193
14780
|
}
|
|
14194
|
-
var standupCommand = new
|
|
14781
|
+
var standupCommand = new Command17("standup").description("Generate daily standup report from spoke vault data and git logs (zero tokens)").option("-p, --project <name>", "Show standup for a specific project only").option("-a, --all", "Show all projects (default)").option("-y, --yesterday", "Show yesterday's standup instead of today").option("-r, --raw", "Output raw markdown (for pasting into Slack/chat)").action(async (opts) => {
|
|
14195
14782
|
const config = loadConfig();
|
|
14196
14783
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
14197
14784
|
const projects = Object.entries(config.projects);
|
|
14198
14785
|
if (projects.length === 0) {
|
|
14199
|
-
console.log(
|
|
14786
|
+
console.log(chalk18.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
14200
14787
|
return;
|
|
14201
14788
|
}
|
|
14202
14789
|
const dateStr = getDateStr(!!opts.yesterday);
|
|
@@ -14205,7 +14792,7 @@ var standupCommand = new Command16("standup").description("Generate daily standu
|
|
|
14205
14792
|
if (opts.project) {
|
|
14206
14793
|
const match = projects.find(([name]) => name === opts.project);
|
|
14207
14794
|
if (!match) {
|
|
14208
|
-
console.error(
|
|
14795
|
+
console.error(chalk18.red(`Project "${opts.project}" not found.`));
|
|
14209
14796
|
process.exit(1);
|
|
14210
14797
|
}
|
|
14211
14798
|
targets = [match];
|
|
@@ -14223,8 +14810,8 @@ var standupCommand = new Command16("standup").description("Generate daily standu
|
|
|
14223
14810
|
init_dist();
|
|
14224
14811
|
init_dist();
|
|
14225
14812
|
init_dist3();
|
|
14226
|
-
import { Command as
|
|
14227
|
-
import
|
|
14813
|
+
import { Command as Command18 } from "commander";
|
|
14814
|
+
import chalk19 from "chalk";
|
|
14228
14815
|
function dedupe(items) {
|
|
14229
14816
|
const seen = /* @__PURE__ */ new Set();
|
|
14230
14817
|
const out = [];
|
|
@@ -14280,7 +14867,7 @@ function formatRetro(retros, fromStr, toStr, raw) {
|
|
|
14280
14867
|
const lines = [];
|
|
14281
14868
|
const header = `Retro \u2014 ${fromStr} \u2192 ${toStr}`;
|
|
14282
14869
|
lines.push(raw ? `# ${header}
|
|
14283
|
-
` :
|
|
14870
|
+
` : chalk19.bold(`
|
|
14284
14871
|
${header}
|
|
14285
14872
|
`));
|
|
14286
14873
|
let totalCommits = 0;
|
|
@@ -14290,39 +14877,39 @@ ${header}
|
|
|
14290
14877
|
totalCommits += r.commits.length;
|
|
14291
14878
|
totalPRs += r.mergedPRs.length;
|
|
14292
14879
|
totalShipped += r.shipped.length;
|
|
14293
|
-
lines.push(raw ? `## ${r.name}` :
|
|
14294
|
-
renderList(lines, r.shipped, raw, "Shipped",
|
|
14880
|
+
lines.push(raw ? `## ${r.name}` : chalk19.cyan.bold(`## ${r.name}`));
|
|
14881
|
+
renderList(lines, r.shipped, raw, "Shipped", chalk19.green);
|
|
14295
14882
|
if (r.mergedPRs.length > 0) {
|
|
14296
|
-
lines.push(raw ? `**PRs merged:**` :
|
|
14883
|
+
lines.push(raw ? `**PRs merged:**` : chalk19.green("PRs merged:"));
|
|
14297
14884
|
for (const pr of r.mergedPRs) lines.push(` - ${pr}`);
|
|
14298
14885
|
}
|
|
14299
|
-
renderList(lines, r.inProgress, raw, "In Progress",
|
|
14300
|
-
renderList(lines, r.blocked, raw, "Blocked",
|
|
14301
|
-
renderList(lines, r.decisions, raw, "Key Decisions",
|
|
14886
|
+
renderList(lines, r.inProgress, raw, "In Progress", chalk19.yellow);
|
|
14887
|
+
renderList(lines, r.blocked, raw, "Blocked", chalk19.red);
|
|
14888
|
+
renderList(lines, r.decisions, raw, "Key Decisions", chalk19.magenta);
|
|
14302
14889
|
const metricBits = [
|
|
14303
14890
|
`${r.commits.length} commits`,
|
|
14304
14891
|
`${r.mergedPRs.length} PRs merged`,
|
|
14305
14892
|
`${r.shipped.length} shipped`
|
|
14306
14893
|
];
|
|
14307
|
-
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` :
|
|
14894
|
+
lines.push(raw ? `*${metricBits.join(" \xB7 ")}*` : chalk19.dim(` ${metricBits.join(" \xB7 ")}`));
|
|
14308
14895
|
if (r.shipped.length === 0 && r.commits.length === 0 && r.mergedPRs.length === 0 && r.inProgress.length === 0 && r.blocked.length === 0) {
|
|
14309
|
-
lines.push(raw ? "_(no activity in this window)_" :
|
|
14896
|
+
lines.push(raw ? "_(no activity in this window)_" : chalk19.dim(" (no activity in this window)"));
|
|
14310
14897
|
}
|
|
14311
14898
|
lines.push("");
|
|
14312
14899
|
}
|
|
14313
14900
|
const summary = `${totalShipped} items shipped \xB7 ${totalCommits} commits \xB7 ${totalPRs} PRs merged`;
|
|
14314
14901
|
lines.push(raw ? `---
|
|
14315
14902
|
*${summary}*
|
|
14316
|
-
` :
|
|
14903
|
+
` : chalk19.dim(`--- ${summary} ---
|
|
14317
14904
|
`));
|
|
14318
14905
|
return lines.join("\n");
|
|
14319
14906
|
}
|
|
14320
|
-
var retroCommand = new
|
|
14907
|
+
var retroCommand = new Command18("retro").description("Generate a retro (weekly/sprint summary) from daily logs and git (zero tokens)").option("-w, --week", "Trailing 7 days (default)").option("-s, --sprint [days]", "Custom window of N days (default 14 if N omitted)").option("-p, --project <name>", "Retro for a single project").option("-a, --all", "All projects (default)").option("-r, --raw", "Raw markdown output (for pasting into Slack/Obsidian)").action(async (opts) => {
|
|
14321
14908
|
const config = loadConfig();
|
|
14322
14909
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
14323
14910
|
const projects = Object.entries(config.projects);
|
|
14324
14911
|
if (projects.length === 0) {
|
|
14325
|
-
console.log(
|
|
14912
|
+
console.log(chalk19.yellow("\nNo projects registered. Use: squadrant projects add <name> <path>\n"));
|
|
14326
14913
|
return;
|
|
14327
14914
|
}
|
|
14328
14915
|
let windowDays = 7;
|
|
@@ -14339,7 +14926,7 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
|
|
|
14339
14926
|
if (opts.project) {
|
|
14340
14927
|
const match = projects.find(([name]) => name === opts.project);
|
|
14341
14928
|
if (!match) {
|
|
14342
|
-
console.error(
|
|
14929
|
+
console.error(chalk19.red(`Project "${opts.project}" not found.`));
|
|
14343
14930
|
process.exit(1);
|
|
14344
14931
|
}
|
|
14345
14932
|
targets = [match];
|
|
@@ -14352,153 +14939,8 @@ var retroCommand = new Command17("retro").description("Generate a retro (weekly/
|
|
|
14352
14939
|
console.log(formatRetro(retros, fromStr, toStr, raw));
|
|
14353
14940
|
});
|
|
14354
14941
|
|
|
14355
|
-
// packages/cli/src/
|
|
14356
|
-
|
|
14357
|
-
init_dist3();
|
|
14358
|
-
import { Command as Command18 } from "commander";
|
|
14359
|
-
import chalk19 from "chalk";
|
|
14360
|
-
function buildRegistry() {
|
|
14361
|
-
return new RuntimeRegistry({
|
|
14362
|
-
cmux: createCmuxDriver()
|
|
14363
|
-
});
|
|
14364
|
-
}
|
|
14365
|
-
function resolveTarget(registry, config, target, useCommand) {
|
|
14366
|
-
if (useCommand) {
|
|
14367
|
-
return {
|
|
14368
|
-
driver: registry.global(config),
|
|
14369
|
-
workspaceName: config.commandName
|
|
14370
|
-
};
|
|
14371
|
-
}
|
|
14372
|
-
if (!target) {
|
|
14373
|
-
throw new Error("Missing target: pass a project name or use --command");
|
|
14374
|
-
}
|
|
14375
|
-
const proj = config.projects[target];
|
|
14376
|
-
if (!proj) {
|
|
14377
|
-
throw new Error(`Project '${target}' not found. Run 'squadrant projects list'.`);
|
|
14378
|
-
}
|
|
14379
|
-
return {
|
|
14380
|
-
driver: registry.forProject(target, config),
|
|
14381
|
-
workspaceName: proj.captainName
|
|
14382
|
-
};
|
|
14383
|
-
}
|
|
14384
|
-
async function needRef(resolved) {
|
|
14385
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14386
|
-
if (!ref) {
|
|
14387
|
-
throw new Error(`Workspace '${resolved.workspaceName}' is not running`);
|
|
14388
|
-
}
|
|
14389
|
-
return ref.id;
|
|
14390
|
-
}
|
|
14391
|
-
var runtimeCommand = new Command18("runtime").description("Interact with the runtime layer (workspaces). Bridges bash scripts to the RuntimeDriver.");
|
|
14392
|
-
runtimeCommand.command("status").description("Print 'running' or 'stopped' for a target; exit 0 if running, 1 if not").argument("[target]", "Project name").option("--command", "Target the command workspace instead of a project captain").action(async (target, opts) => {
|
|
14393
|
-
const config = loadConfig();
|
|
14394
|
-
const registry = buildRegistry();
|
|
14395
|
-
try {
|
|
14396
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14397
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14398
|
-
if (ref) {
|
|
14399
|
-
console.log("running");
|
|
14400
|
-
process.exit(0);
|
|
14401
|
-
} else {
|
|
14402
|
-
console.log("stopped");
|
|
14403
|
-
process.exit(1);
|
|
14404
|
-
}
|
|
14405
|
-
} catch (err) {
|
|
14406
|
-
console.error(chalk19.red(err.message));
|
|
14407
|
-
process.exit(2);
|
|
14408
|
-
}
|
|
14409
|
-
});
|
|
14410
|
-
var SEND_CONFIRM_TIMEOUT_MS = 15e3;
|
|
14411
|
-
var SEND_CONFIRM_POLL_MS = 500;
|
|
14412
|
-
async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
14413
|
-
const config = loadConfig();
|
|
14414
|
-
const registry = buildRegistry();
|
|
14415
|
-
if (opts.command && arg2 !== void 0) {
|
|
14416
|
-
throw new Error("With --command, pass only the message (not a project name)");
|
|
14417
|
-
}
|
|
14418
|
-
const target = opts.command ? void 0 : arg1;
|
|
14419
|
-
const message = opts.command ? arg1 : arg2;
|
|
14420
|
-
if (!message) throw new Error("Message is required");
|
|
14421
|
-
const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
|
|
14422
|
-
const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
14423
|
-
await requireDaemon2();
|
|
14424
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14425
|
-
await needRef(resolved);
|
|
14426
|
-
const finalProject = opts.command ? config.commandName : target;
|
|
14427
|
-
const { join: join31, dirname: dirname10 } = await import("path");
|
|
14428
|
-
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
14429
|
-
const stateRoot = join31(dirname10(DEFAULT_CONFIG_PATH2), "state");
|
|
14430
|
-
const seq = await appendCaptainMessage2({
|
|
14431
|
-
stateRoot,
|
|
14432
|
-
project: finalProject,
|
|
14433
|
-
text: message,
|
|
14434
|
-
source: "cli"
|
|
14435
|
-
});
|
|
14436
|
-
const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
|
|
14437
|
-
const delivered = await waitForCaptainDelivery2({
|
|
14438
|
-
stateRoot,
|
|
14439
|
-
project: finalProject,
|
|
14440
|
-
seq,
|
|
14441
|
-
timeoutMs,
|
|
14442
|
-
pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
|
|
14443
|
-
});
|
|
14444
|
-
if (!delivered) {
|
|
14445
|
-
throw new Error(
|
|
14446
|
-
`Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
|
|
14447
|
-
);
|
|
14448
|
-
}
|
|
14449
|
-
}
|
|
14450
|
-
runtimeCommand.command("send").description("Send a message to a target workspace AND commit with Enter. With --command, the first positional is the message.").argument("<arg1>", "Project name, or the message when --command is used").argument("[arg2]", "Message (when target is a project). Omit when using --command.").option("--command", "Target the command workspace").action(async (arg1, arg2, opts) => {
|
|
14451
|
-
try {
|
|
14452
|
-
await runRuntimeSend(arg1, arg2, opts);
|
|
14453
|
-
console.log(chalk19.green("\u2714 Delivered (confirmed)"));
|
|
14454
|
-
} catch (err) {
|
|
14455
|
-
console.error(chalk19.red(err.message));
|
|
14456
|
-
process.exit(1);
|
|
14457
|
-
}
|
|
14458
|
-
});
|
|
14459
|
-
runtimeCommand.command("list").description("List all workspaces from the global runtime").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
14460
|
-
const config = loadConfig();
|
|
14461
|
-
const registry = buildRegistry();
|
|
14462
|
-
const driver = registry.global(config);
|
|
14463
|
-
const refs = await driver.list();
|
|
14464
|
-
if (opts.json) {
|
|
14465
|
-
console.log(JSON.stringify(refs, null, 2));
|
|
14466
|
-
} else {
|
|
14467
|
-
for (const r of refs) {
|
|
14468
|
-
console.log(`${r.id} ${r.name} ${r.status}`);
|
|
14469
|
-
}
|
|
14470
|
-
}
|
|
14471
|
-
});
|
|
14472
|
-
runtimeCommand.command("read-screen").description("Print a terminal snapshot of a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
14473
|
-
const config = loadConfig();
|
|
14474
|
-
const registry = buildRegistry();
|
|
14475
|
-
try {
|
|
14476
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14477
|
-
const ref = await needRef(resolved);
|
|
14478
|
-
const screen = await resolved.driver.readScreen(ref);
|
|
14479
|
-
process.stdout.write(screen);
|
|
14480
|
-
} catch (err) {
|
|
14481
|
-
console.error(chalk19.red(err.message));
|
|
14482
|
-
process.exit(1);
|
|
14483
|
-
}
|
|
14484
|
-
});
|
|
14485
|
-
runtimeCommand.command("stop").description("Stop a target workspace").argument("[target]", "Project name").option("--command", "Target the command workspace").action(async (target, opts) => {
|
|
14486
|
-
const config = loadConfig();
|
|
14487
|
-
const registry = buildRegistry();
|
|
14488
|
-
try {
|
|
14489
|
-
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14490
|
-
const ref = await resolved.driver.status(resolved.workspaceName);
|
|
14491
|
-
if (!ref) {
|
|
14492
|
-
console.log(chalk19.yellow(`Workspace '${resolved.workspaceName}' already stopped`));
|
|
14493
|
-
return;
|
|
14494
|
-
}
|
|
14495
|
-
await resolved.driver.stop(ref.id);
|
|
14496
|
-
console.log(chalk19.green(`\u2714 Stopped ${resolved.workspaceName}`));
|
|
14497
|
-
} catch (err) {
|
|
14498
|
-
console.error(chalk19.red(err.message));
|
|
14499
|
-
process.exit(1);
|
|
14500
|
-
}
|
|
14501
|
-
});
|
|
14942
|
+
// packages/cli/src/index.ts
|
|
14943
|
+
init_runtime2();
|
|
14502
14944
|
|
|
14503
14945
|
// packages/cli/src/commands/workspace.ts
|
|
14504
14946
|
init_dist();
|
|
@@ -14539,9 +14981,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
|
|
|
14539
14981
|
const config = loadConfig();
|
|
14540
14982
|
const registry = buildRegistry2();
|
|
14541
14983
|
try {
|
|
14542
|
-
const { projectTarget, path:
|
|
14984
|
+
const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
14543
14985
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14544
|
-
const content = await driver.read(
|
|
14986
|
+
const content = await driver.read(path35);
|
|
14545
14987
|
process.stdout.write(content);
|
|
14546
14988
|
} catch (err) {
|
|
14547
14989
|
console.error(chalk20.red(err.message));
|
|
@@ -14553,26 +14995,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
|
|
|
14553
14995
|
const registry = buildRegistry2();
|
|
14554
14996
|
try {
|
|
14555
14997
|
let projectTarget;
|
|
14556
|
-
let
|
|
14998
|
+
let path35;
|
|
14557
14999
|
let rawContent;
|
|
14558
15000
|
if (opts.hub) {
|
|
14559
15001
|
if (arg3 !== void 0) {
|
|
14560
15002
|
throw new Error("With --hub, pass only the path and content");
|
|
14561
15003
|
}
|
|
14562
15004
|
projectTarget = void 0;
|
|
14563
|
-
|
|
15005
|
+
path35 = arg1;
|
|
14564
15006
|
rawContent = arg2;
|
|
14565
15007
|
} else {
|
|
14566
15008
|
if (arg3 === void 0) {
|
|
14567
15009
|
throw new Error("Missing content \u2014 usage: <project> <path> <content>");
|
|
14568
15010
|
}
|
|
14569
15011
|
projectTarget = arg1;
|
|
14570
|
-
|
|
15012
|
+
path35 = arg2;
|
|
14571
15013
|
rawContent = arg3;
|
|
14572
15014
|
}
|
|
14573
15015
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14574
15016
|
const payload = rawContent === "-" ? await readStdin() : rawContent;
|
|
14575
|
-
await driver.write(
|
|
15017
|
+
await driver.write(path35, payload);
|
|
14576
15018
|
} catch (err) {
|
|
14577
15019
|
console.error(chalk20.red(err.message));
|
|
14578
15020
|
process.exit(1);
|
|
@@ -14582,9 +15024,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
|
|
|
14582
15024
|
const config = loadConfig();
|
|
14583
15025
|
const registry = buildRegistry2();
|
|
14584
15026
|
try {
|
|
14585
|
-
const { projectTarget, path:
|
|
15027
|
+
const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
14586
15028
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14587
|
-
const entries = await driver.list(
|
|
15029
|
+
const entries = await driver.list(path35);
|
|
14588
15030
|
for (const entry of entries) console.log(entry);
|
|
14589
15031
|
} catch (err) {
|
|
14590
15032
|
console.error(chalk20.red(err.message));
|
|
@@ -14595,9 +15037,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
|
|
|
14595
15037
|
const config = loadConfig();
|
|
14596
15038
|
const registry = buildRegistry2();
|
|
14597
15039
|
try {
|
|
14598
|
-
const { projectTarget, path:
|
|
15040
|
+
const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
14599
15041
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14600
|
-
const ok2 = await driver.exists(
|
|
15042
|
+
const ok2 = await driver.exists(path35);
|
|
14601
15043
|
process.exit(ok2 ? 0 : 1);
|
|
14602
15044
|
} catch (err) {
|
|
14603
15045
|
console.error(chalk20.red(err.message));
|
|
@@ -14608,9 +15050,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
14608
15050
|
const config = loadConfig();
|
|
14609
15051
|
const registry = buildRegistry2();
|
|
14610
15052
|
try {
|
|
14611
|
-
const { projectTarget, path:
|
|
15053
|
+
const { projectTarget, path: path35 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
14612
15054
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
14613
|
-
await driver.mkdir(
|
|
15055
|
+
await driver.mkdir(path35);
|
|
14614
15056
|
} catch (err) {
|
|
14615
15057
|
console.error(chalk20.red(err.message));
|
|
14616
15058
|
process.exit(1);
|
|
@@ -14649,8 +15091,8 @@ init_dist3();
|
|
|
14649
15091
|
init_dist();
|
|
14650
15092
|
import { Command as Command21 } from "commander";
|
|
14651
15093
|
import chalk22 from "chalk";
|
|
14652
|
-
import
|
|
14653
|
-
import
|
|
15094
|
+
import fs22 from "fs";
|
|
15095
|
+
import path27 from "path";
|
|
14654
15096
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14655
15097
|
function parseScope(v) {
|
|
14656
15098
|
if (v !== "user" && v !== "project") {
|
|
@@ -14659,10 +15101,10 @@ function parseScope(v) {
|
|
|
14659
15101
|
return v;
|
|
14660
15102
|
}
|
|
14661
15103
|
function findPackageRoot3() {
|
|
14662
|
-
let dir =
|
|
15104
|
+
let dir = path27.dirname(fileURLToPath4(import.meta.url));
|
|
14663
15105
|
while (dir !== "/" && dir !== "") {
|
|
14664
|
-
if (
|
|
14665
|
-
dir =
|
|
15106
|
+
if (fs22.existsSync(path27.join(dir, "package.json"))) return dir;
|
|
15107
|
+
dir = path27.dirname(dir);
|
|
14666
15108
|
}
|
|
14667
15109
|
return process.cwd();
|
|
14668
15110
|
}
|
|
@@ -14857,12 +15299,12 @@ init_dist();
|
|
|
14857
15299
|
init_dist();
|
|
14858
15300
|
init_dist2();
|
|
14859
15301
|
import { Command as Command23 } from "commander";
|
|
14860
|
-
import
|
|
15302
|
+
import fs23 from "fs";
|
|
14861
15303
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14862
|
-
import { dirname as
|
|
15304
|
+
import { dirname as dirname5, join as join26 } from "path";
|
|
14863
15305
|
import chalk23 from "chalk";
|
|
14864
15306
|
function runConfigCheck(opts) {
|
|
14865
|
-
const raw = JSON.parse(
|
|
15307
|
+
const raw = JSON.parse(fs23.readFileSync(opts.configPath, "utf-8"));
|
|
14866
15308
|
const def = getDefaultConfig();
|
|
14867
15309
|
const items = detectDrift(raw, def);
|
|
14868
15310
|
let working = raw;
|
|
@@ -14879,7 +15321,7 @@ function runConfigCheck(opts) {
|
|
|
14879
15321
|
stamped = true;
|
|
14880
15322
|
}
|
|
14881
15323
|
if (opts.fix || opts.accept || stamped) {
|
|
14882
|
-
|
|
15324
|
+
writeConfigFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
|
|
14883
15325
|
}
|
|
14884
15326
|
return { items, applied, remaining, stamped };
|
|
14885
15327
|
}
|
|
@@ -14950,7 +15392,7 @@ function printItems(items) {
|
|
|
14950
15392
|
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
14951
15393
|
configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
|
|
14952
15394
|
const pkgVersion = readPkgVersion2();
|
|
14953
|
-
if (!
|
|
15395
|
+
if (!fs23.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
14954
15396
|
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
14955
15397
|
return;
|
|
14956
15398
|
}
|
|
@@ -14995,8 +15437,8 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
14995
15437
|
}
|
|
14996
15438
|
});
|
|
14997
15439
|
function readPkgVersion2() {
|
|
14998
|
-
const pkgPath = join26(
|
|
14999
|
-
return JSON.parse(
|
|
15440
|
+
const pkgPath = join26(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
15441
|
+
return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
|
|
15000
15442
|
}
|
|
15001
15443
|
|
|
15002
15444
|
// packages/cli/src/commands/heal.ts
|
|
@@ -15020,6 +15462,15 @@ function buildHealStatus(components) {
|
|
|
15020
15462
|
}
|
|
15021
15463
|
async function runHealStatus(opts) {
|
|
15022
15464
|
const { project, json, stdout, stderr } = opts;
|
|
15465
|
+
const isDaemonAlive = opts.isDaemonAlive ?? (() => isDaemonSocketLive(SOCK));
|
|
15466
|
+
if (!await isDaemonAlive()) {
|
|
15467
|
+
if (json) {
|
|
15468
|
+
stdout.write(JSON.stringify({ healthy: false, daemonUnreachable: true, components: [] }) + "\n");
|
|
15469
|
+
} else {
|
|
15470
|
+
stderr.write("daemon unreachable \u2014 start the daemon first (squadrant heal daemon)\n");
|
|
15471
|
+
}
|
|
15472
|
+
return 1;
|
|
15473
|
+
}
|
|
15023
15474
|
let rows;
|
|
15024
15475
|
try {
|
|
15025
15476
|
rows = await opts.queryHealth(project);
|
|
@@ -15149,17 +15600,18 @@ var groupCommand = new Command26("group").description("Cross-project intra-group
|
|
|
15149
15600
|
// packages/cli/src/commands/ping.ts
|
|
15150
15601
|
init_dist();
|
|
15151
15602
|
init_dist2();
|
|
15152
|
-
|
|
15603
|
+
init_runtime2();
|
|
15604
|
+
init_require_daemon();
|
|
15605
|
+
import { join as join27, dirname as dirname6 } from "path";
|
|
15153
15606
|
import { Command as Command27 } from "commander";
|
|
15154
15607
|
import chalk27 from "chalk";
|
|
15155
|
-
init_require_daemon();
|
|
15156
15608
|
async function runPing(project, message) {
|
|
15157
15609
|
const config = loadConfig();
|
|
15158
15610
|
const registry = buildRegistry();
|
|
15159
15611
|
const resolved = resolveTarget(registry, config, project, false);
|
|
15160
15612
|
await requireDaemon();
|
|
15161
15613
|
await needRef(resolved);
|
|
15162
|
-
const stateRoot = join27(
|
|
15614
|
+
const stateRoot = join27(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
15163
15615
|
await appendCaptainMessage({
|
|
15164
15616
|
stateRoot,
|
|
15165
15617
|
project,
|
|
@@ -15236,8 +15688,8 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
|
|
|
15236
15688
|
// packages/cli/src/commands/effort.ts
|
|
15237
15689
|
init_dist();
|
|
15238
15690
|
init_dist2();
|
|
15239
|
-
import
|
|
15240
|
-
import
|
|
15691
|
+
import fs24 from "fs";
|
|
15692
|
+
import path28 from "path";
|
|
15241
15693
|
import { Command as Command29 } from "commander";
|
|
15242
15694
|
import chalk29 from "chalk";
|
|
15243
15695
|
var VALID_EFFORTS = ["max", "balance", "low"];
|
|
@@ -15280,9 +15732,9 @@ function effortScopeLabel(projectName) {
|
|
|
15280
15732
|
}
|
|
15281
15733
|
function canonical(p) {
|
|
15282
15734
|
try {
|
|
15283
|
-
return
|
|
15735
|
+
return fs24.realpathSync(p);
|
|
15284
15736
|
} catch {
|
|
15285
|
-
return
|
|
15737
|
+
return path28.resolve(p);
|
|
15286
15738
|
}
|
|
15287
15739
|
}
|
|
15288
15740
|
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
|
|
@@ -15332,7 +15784,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
|
|
|
15332
15784
|
const config = loadConfig();
|
|
15333
15785
|
const registry = new RuntimeRegistry2({ cmux: createCmuxDriver2() });
|
|
15334
15786
|
const driver = registry.global(config);
|
|
15335
|
-
const stateRoot =
|
|
15787
|
+
const stateRoot = path28.join(path28.dirname(DEFAULT_CONFIG_PATH), "state");
|
|
15336
15788
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
15337
15789
|
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
15338
15790
|
} catch {
|
|
@@ -15342,13 +15794,13 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
|
|
|
15342
15794
|
|
|
15343
15795
|
// packages/cli/src/commands/tokens.ts
|
|
15344
15796
|
init_dist();
|
|
15345
|
-
import
|
|
15346
|
-
import
|
|
15797
|
+
import fs25 from "fs";
|
|
15798
|
+
import path29 from "path";
|
|
15347
15799
|
import os15 from "os";
|
|
15348
15800
|
import readline3 from "readline";
|
|
15349
15801
|
import { Command as Command30 } from "commander";
|
|
15350
15802
|
import chalk30 from "chalk";
|
|
15351
|
-
var CLAUDE_PROJECTS_DIR =
|
|
15803
|
+
var CLAUDE_PROJECTS_DIR = path29.join(os15.homedir(), ".claude", "projects");
|
|
15352
15804
|
function parseTranscriptLine(rawLine) {
|
|
15353
15805
|
const line = rawLine.trim();
|
|
15354
15806
|
if (!line) return { timestamp: null, usage: null };
|
|
@@ -15398,7 +15850,7 @@ function foldTranscriptLine(agg, rawLine, state) {
|
|
|
15398
15850
|
async function aggregateTranscriptFile(filePath) {
|
|
15399
15851
|
const agg = emptySessionAggregate();
|
|
15400
15852
|
const state = { lastCacheRead: null };
|
|
15401
|
-
const rl = readline3.createInterface({ input:
|
|
15853
|
+
const rl = readline3.createInterface({ input: fs25.createReadStream(filePath), crlfDelay: Infinity });
|
|
15402
15854
|
for await (const line of rl) {
|
|
15403
15855
|
foldTranscriptLine(agg, line, state);
|
|
15404
15856
|
}
|
|
@@ -15466,22 +15918,22 @@ function buildRoleReport(role, sessions) {
|
|
|
15466
15918
|
}
|
|
15467
15919
|
async function readdirSafe(dir) {
|
|
15468
15920
|
try {
|
|
15469
|
-
return await
|
|
15921
|
+
return await fs25.promises.readdir(dir);
|
|
15470
15922
|
} catch {
|
|
15471
15923
|
return [];
|
|
15472
15924
|
}
|
|
15473
15925
|
}
|
|
15474
15926
|
async function listJsonlFiles(dir) {
|
|
15475
15927
|
const entries = await readdirSafe(dir);
|
|
15476
|
-
return entries.filter((e) => e.endsWith(".jsonl")).map((e) =>
|
|
15928
|
+
return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path29.join(dir, e));
|
|
15477
15929
|
}
|
|
15478
15930
|
async function findTranscriptDirs(claudeProjectsDir, captainSlug) {
|
|
15479
15931
|
const entries = await readdirSafe(claudeProjectsDir);
|
|
15480
15932
|
const captainDirs = [];
|
|
15481
15933
|
const crewDirs = [];
|
|
15482
15934
|
for (const entry of entries) {
|
|
15483
|
-
if (entry === captainSlug) captainDirs.push(
|
|
15484
|
-
else if (isCrewDirName(entry, captainSlug)) crewDirs.push(
|
|
15935
|
+
if (entry === captainSlug) captainDirs.push(path29.join(claudeProjectsDir, entry));
|
|
15936
|
+
else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path29.join(claudeProjectsDir, entry));
|
|
15485
15937
|
}
|
|
15486
15938
|
return { captainDirs, crewDirs };
|
|
15487
15939
|
}
|
|
@@ -15633,12 +16085,12 @@ var tokensCommand = new Command30("tokens").description(
|
|
|
15633
16085
|
// packages/cli/src/commands/telegram.ts
|
|
15634
16086
|
init_dist();
|
|
15635
16087
|
init_dist2();
|
|
15636
|
-
import { join as join28, dirname as
|
|
16088
|
+
import { join as join28, dirname as dirname7 } from "path";
|
|
15637
16089
|
import { emitKeypressEvents } from "readline";
|
|
15638
16090
|
import { Command as Command31 } from "commander";
|
|
15639
16091
|
import chalk31 from "chalk";
|
|
15640
16092
|
function defaultStateRoot() {
|
|
15641
|
-
return join28(
|
|
16093
|
+
return join28(dirname7(DEFAULT_CONFIG_PATH), "state");
|
|
15642
16094
|
}
|
|
15643
16095
|
async function questionMasked() {
|
|
15644
16096
|
return new Promise((resolve4) => {
|
|
@@ -15968,8 +16420,8 @@ import { join as join29 } from "path";
|
|
|
15968
16420
|
import { homedir as homedir21 } from "os";
|
|
15969
16421
|
|
|
15970
16422
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
15971
|
-
import
|
|
15972
|
-
import
|
|
16423
|
+
import fs26 from "fs";
|
|
16424
|
+
import path30 from "path";
|
|
15973
16425
|
|
|
15974
16426
|
// packages/cli/src/lib/handoff-facts.ts
|
|
15975
16427
|
var STALE_FETCH_WARNING_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -16020,15 +16472,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
|
|
|
16020
16472
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
16021
16473
|
var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
|
|
16022
16474
|
function appendCaptainSession(spokeVault, record) {
|
|
16023
|
-
|
|
16024
|
-
const file =
|
|
16025
|
-
|
|
16475
|
+
fs26.mkdirSync(spokeVault, { recursive: true });
|
|
16476
|
+
const file = path30.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
16477
|
+
fs26.appendFileSync(file, JSON.stringify(record) + "\n");
|
|
16026
16478
|
}
|
|
16027
16479
|
function readCaptainSessionRegistry(spokeVault) {
|
|
16028
|
-
const file =
|
|
16029
|
-
if (!
|
|
16480
|
+
const file = path30.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
16481
|
+
if (!fs26.existsSync(file)) return [];
|
|
16030
16482
|
const records = [];
|
|
16031
|
-
for (const line of
|
|
16483
|
+
for (const line of fs26.readFileSync(file, "utf-8").split("\n")) {
|
|
16032
16484
|
if (!line.trim()) continue;
|
|
16033
16485
|
try {
|
|
16034
16486
|
records.push(JSON.parse(line));
|
|
@@ -16131,13 +16583,13 @@ function hooksCommand() {
|
|
|
16131
16583
|
// packages/cli/src/commands/work.ts
|
|
16132
16584
|
init_dist();
|
|
16133
16585
|
init_dist2();
|
|
16134
|
-
import
|
|
16586
|
+
import path31 from "path";
|
|
16135
16587
|
import { Command as Command33 } from "commander";
|
|
16136
16588
|
import chalk32 from "chalk";
|
|
16137
16589
|
function detectCurrentProject(config, cwd = process.cwd()) {
|
|
16138
16590
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
16139
16591
|
const projPath = resolveHome(proj.path);
|
|
16140
|
-
if (cwd === projPath || cwd.startsWith(projPath +
|
|
16592
|
+
if (cwd === projPath || cwd.startsWith(projPath + path31.sep)) return name;
|
|
16141
16593
|
}
|
|
16142
16594
|
return void 0;
|
|
16143
16595
|
}
|
|
@@ -16269,14 +16721,14 @@ var workCommand = new Command33("work").description("Track your own in-flight wo
|
|
|
16269
16721
|
// packages/cli/src/commands/handoff.ts
|
|
16270
16722
|
init_dist();
|
|
16271
16723
|
import { Command as Command34 } from "commander";
|
|
16272
|
-
import
|
|
16724
|
+
import path34 from "path";
|
|
16273
16725
|
import os16 from "os";
|
|
16274
16726
|
|
|
16275
16727
|
// packages/cli/src/lib/handoff-live-repo.ts
|
|
16276
16728
|
init_dist();
|
|
16277
16729
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
16278
|
-
import
|
|
16279
|
-
import
|
|
16730
|
+
import fs27 from "fs";
|
|
16731
|
+
import path32 from "path";
|
|
16280
16732
|
|
|
16281
16733
|
// packages/cli/src/lib/handoff-branch-state.ts
|
|
16282
16734
|
function tryRun(runner, cmd, args, cwd) {
|
|
@@ -16395,7 +16847,7 @@ function gatherOpenPRs(runner, projectPath) {
|
|
|
16395
16847
|
}
|
|
16396
16848
|
}
|
|
16397
16849
|
function gatherLiveCrews(tasks) {
|
|
16398
|
-
return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question }));
|
|
16850
|
+
return tasks.filter((t) => !TERMINAL_STATES.has(t.state)).map((t) => ({ name: t.name ?? t.id, state: t.state, task: t.task, question: t.question, operatorHold: t.operatorHold }));
|
|
16399
16851
|
}
|
|
16400
16852
|
function ghAheadOfBase(runner, projectPath, nameWithOwner, base, branch) {
|
|
16401
16853
|
return tryInt(
|
|
@@ -16415,7 +16867,7 @@ function localAheadOfBase(runner, projectPath, base) {
|
|
|
16415
16867
|
}
|
|
16416
16868
|
function readFetchAgeMs(projectPath, now) {
|
|
16417
16869
|
try {
|
|
16418
|
-
const stat2 =
|
|
16870
|
+
const stat2 = fs27.statSync(path32.join(projectPath, ".git", "FETCH_HEAD"));
|
|
16419
16871
|
return Math.max(0, now - stat2.mtime.getTime());
|
|
16420
16872
|
} catch {
|
|
16421
16873
|
return null;
|
|
@@ -16486,7 +16938,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
16486
16938
|
|
|
16487
16939
|
// packages/cli/src/lib/handoff-claude-mem.ts
|
|
16488
16940
|
import { createRequire } from "module";
|
|
16489
|
-
import
|
|
16941
|
+
import fs28 from "fs";
|
|
16490
16942
|
var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
|
|
16491
16943
|
var CLAUDE_MEM_RECENCY_LIMIT = 20;
|
|
16492
16944
|
function decisionText(row) {
|
|
@@ -16500,7 +16952,7 @@ function decisionText(row) {
|
|
|
16500
16952
|
return row.narrative ?? "";
|
|
16501
16953
|
}
|
|
16502
16954
|
function queryClaudeMem(dbPath, project) {
|
|
16503
|
-
if (!
|
|
16955
|
+
if (!fs28.existsSync(dbPath)) return null;
|
|
16504
16956
|
let db;
|
|
16505
16957
|
try {
|
|
16506
16958
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -16543,7 +16995,7 @@ function queryClaudeMem(dbPath, project) {
|
|
|
16543
16995
|
}
|
|
16544
16996
|
|
|
16545
16997
|
// packages/cli/src/lib/handoff-transcript.ts
|
|
16546
|
-
import
|
|
16998
|
+
import fs29 from "fs";
|
|
16547
16999
|
var TRANSCRIPT_BYTE_CAP = 2e5;
|
|
16548
17000
|
function tailOf(content, byteCap) {
|
|
16549
17001
|
const buf = Buffer.from(content, "utf-8");
|
|
@@ -16572,27 +17024,27 @@ function extractMessages(tailText) {
|
|
|
16572
17024
|
return { lastUserMessage, lastAssistantText };
|
|
16573
17025
|
}
|
|
16574
17026
|
function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
|
|
16575
|
-
if (!
|
|
16576
|
-
const content =
|
|
17027
|
+
if (!fs29.existsSync(transcriptPath)) return null;
|
|
17028
|
+
const content = fs29.readFileSync(transcriptPath, "utf-8");
|
|
16577
17029
|
const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
|
|
16578
|
-
const mtimeIso =
|
|
17030
|
+
const mtimeIso = fs29.statSync(transcriptPath).mtime.toISOString();
|
|
16579
17031
|
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
|
|
16580
17032
|
}
|
|
16581
17033
|
|
|
16582
17034
|
// packages/cli/src/lib/handoff-archive.ts
|
|
16583
|
-
import
|
|
16584
|
-
import
|
|
17035
|
+
import fs30 from "fs";
|
|
17036
|
+
import path33 from "path";
|
|
16585
17037
|
function readNewestArchivedHandoff(spokeVault, now) {
|
|
16586
|
-
const dir =
|
|
16587
|
-
if (!
|
|
16588
|
-
const candidates =
|
|
16589
|
-
const full =
|
|
16590
|
-
return { name: e.name, full, mtime:
|
|
17038
|
+
const dir = path33.join(spokeVault, "handoffs");
|
|
17039
|
+
if (!fs30.existsSync(dir)) return null;
|
|
17040
|
+
const candidates = fs30.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
|
|
17041
|
+
const full = path33.join(dir, e.name);
|
|
17042
|
+
return { name: e.name, full, mtime: fs30.statSync(full).mtime };
|
|
16591
17043
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
16592
17044
|
for (const candidate of candidates) {
|
|
16593
17045
|
let content;
|
|
16594
17046
|
try {
|
|
16595
|
-
content = JSON.parse(
|
|
17047
|
+
content = JSON.parse(fs30.readFileSync(candidate.full, "utf-8"));
|
|
16596
17048
|
} catch {
|
|
16597
17049
|
continue;
|
|
16598
17050
|
}
|
|
@@ -16602,7 +17054,7 @@ function readNewestArchivedHandoff(spokeVault, now) {
|
|
|
16602
17054
|
}
|
|
16603
17055
|
|
|
16604
17056
|
// packages/cli/src/commands/handoff.ts
|
|
16605
|
-
var CLAUDE_MEM_DB_PATH =
|
|
17057
|
+
var CLAUDE_MEM_DB_PATH = path34.join(os16.homedir(), ".claude-mem", "claude-mem.db");
|
|
16606
17058
|
async function defaultFetchTasks(project) {
|
|
16607
17059
|
return await squadrantdCall({ kind: "list", project });
|
|
16608
17060
|
}
|
|
@@ -16661,8 +17113,8 @@ init_dist();
|
|
|
16661
17113
|
init_dist();
|
|
16662
17114
|
init_dist();
|
|
16663
17115
|
init_dist();
|
|
16664
|
-
var __dirname =
|
|
16665
|
-
var pkg = JSON.parse(
|
|
17116
|
+
var __dirname = dirname8(fileURLToPath6(import.meta.url));
|
|
17117
|
+
var pkg = JSON.parse(readFileSync15(join30(__dirname, "..", "package.json"), "utf-8"));
|
|
16666
17118
|
ensureRuntimeSynced({
|
|
16667
17119
|
sourceRoot: join30(__dirname, ".."),
|
|
16668
17120
|
runtimeRoot: join30(homedir22(), ".config", "squadrant")
|
|
@@ -16671,11 +17123,11 @@ if (process.argv[2] !== "config") {
|
|
|
16671
17123
|
try {
|
|
16672
17124
|
const cfgPath = join30(homedir22(), ".config", "squadrant", "config.json");
|
|
16673
17125
|
if (existsSync13(cfgPath)) {
|
|
16674
|
-
const cfg = JSON.parse(
|
|
17126
|
+
const cfg = JSON.parse(readConfigFileSync(cfgPath));
|
|
16675
17127
|
if (needsCheck(cfg, pkg.version)) {
|
|
16676
17128
|
const items = detectDrift(cfg, getDefaultConfig());
|
|
16677
17129
|
if (items.length === 0) {
|
|
16678
|
-
|
|
17130
|
+
writeConfigFileSync(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
|
|
16679
17131
|
} else {
|
|
16680
17132
|
const from = cfg._squadrantVersion ?? "an earlier version";
|
|
16681
17133
|
process.stderr.write(
|