squadrant 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/index.js +1169 -289
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +969 -83
- package/dist/squadrantd.js.map +1 -1
- package/package.json +4 -3
- package/plugin/skills/captain-ops/SKILL.md +69 -0
- package/plugin/skills/telegram/SKILL.md +94 -0
package/dist/index.js
CHANGED
|
@@ -107,6 +107,66 @@ var init_config = __esm({
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
// packages/shared/dist/project-config.js
|
|
111
|
+
import fs2 from "fs";
|
|
112
|
+
import os2 from "os";
|
|
113
|
+
import path2 from "path";
|
|
114
|
+
function defaultRoot() {
|
|
115
|
+
return path2.join(os2.homedir(), ".config", "squadrant");
|
|
116
|
+
}
|
|
117
|
+
function projectConfigPath(name, root = defaultRoot()) {
|
|
118
|
+
return path2.join(root, "projects", `${name}.json`);
|
|
119
|
+
}
|
|
120
|
+
function loadProjectOverride(name, root = defaultRoot()) {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(fs2.readFileSync(projectConfigPath(name, root), "utf-8"));
|
|
123
|
+
} catch {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function deepMerge(base, patch) {
|
|
128
|
+
if (patch === null || typeof patch !== "object" || Array.isArray(patch))
|
|
129
|
+
return patch ?? base;
|
|
130
|
+
const out = { ...base };
|
|
131
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
132
|
+
out[k] = deepMerge(out[k], v);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
function saveProjectOverride(name, patch, root = defaultRoot()) {
|
|
137
|
+
const merged = deepMerge(loadProjectOverride(name, root), patch);
|
|
138
|
+
const file = projectConfigPath(name, root);
|
|
139
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
140
|
+
fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
141
|
+
}
|
|
142
|
+
function crewRank(tier) {
|
|
143
|
+
return CREW_RANK[tier];
|
|
144
|
+
}
|
|
145
|
+
function isQuieter(before, after) {
|
|
146
|
+
if (before.active && !after.active)
|
|
147
|
+
return { quieter: true, dim: "active" };
|
|
148
|
+
if (before.cap && !after.cap)
|
|
149
|
+
return { quieter: true, dim: "cap" };
|
|
150
|
+
if (crewRank(after.crew) < crewRank(before.crew))
|
|
151
|
+
return { quieter: true, dim: "crew" };
|
|
152
|
+
return { quieter: false, dim: null };
|
|
153
|
+
}
|
|
154
|
+
function resolveNotify(globalNotify, override) {
|
|
155
|
+
let n = { ...DEFAULT_NOTIFY };
|
|
156
|
+
if (globalNotify)
|
|
157
|
+
n = deepMerge(n, globalNotify);
|
|
158
|
+
if (override.telegram?.notify)
|
|
159
|
+
n = deepMerge(n, override.telegram.notify);
|
|
160
|
+
return n;
|
|
161
|
+
}
|
|
162
|
+
var DEFAULT_NOTIFY, CREW_RANK;
|
|
163
|
+
var init_project_config = __esm({
|
|
164
|
+
"packages/shared/dist/project-config.js"() {
|
|
165
|
+
DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
|
|
166
|
+
CREW_RANK = { none: 0, done_only: 1, alert_only: 2, all: 3 };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
110
170
|
// packages/shared/dist/effort.js
|
|
111
171
|
function resolveEffort(config) {
|
|
112
172
|
return config.defaults.effort ?? "balance";
|
|
@@ -155,22 +215,22 @@ function defaultCmuxConfigPath() {
|
|
|
155
215
|
return join(homedir(), ".config", "cmux", "cmux.json");
|
|
156
216
|
}
|
|
157
217
|
function ensureSocketAutomation(opts = {}) {
|
|
158
|
-
const
|
|
159
|
-
if (!existsSync(
|
|
160
|
-
mkdirSync(dirname(
|
|
161
|
-
writeFileSync(
|
|
162
|
-
return { path:
|
|
218
|
+
const path29 = opts.path ?? defaultCmuxConfigPath();
|
|
219
|
+
if (!existsSync(path29)) {
|
|
220
|
+
mkdirSync(dirname(path29), { recursive: true });
|
|
221
|
+
writeFileSync(path29, MINIMAL_TEMPLATE);
|
|
222
|
+
return { path: path29, changed: true, alreadySet: false };
|
|
163
223
|
}
|
|
164
|
-
const text = readFileSync(
|
|
224
|
+
const text = readFileSync(path29, "utf-8");
|
|
165
225
|
const current = parse(text)?.automation?.socketControlMode;
|
|
166
226
|
if (current === AUTOMATION_MODE) {
|
|
167
|
-
return { path:
|
|
227
|
+
return { path: path29, changed: false, alreadySet: true };
|
|
168
228
|
}
|
|
169
229
|
const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
|
|
170
230
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
171
231
|
});
|
|
172
|
-
writeFileSync(
|
|
173
|
-
return { path:
|
|
232
|
+
writeFileSync(path29, applyEdits(text, edits));
|
|
233
|
+
return { path: path29, changed: true, alreadySet: false };
|
|
174
234
|
}
|
|
175
235
|
var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
|
|
176
236
|
var init_cmux_config = __esm({
|
|
@@ -320,15 +380,15 @@ import { dirname as dirname2, join as join4 } from "path";
|
|
|
320
380
|
function defaultStatePath() {
|
|
321
381
|
return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
|
|
322
382
|
}
|
|
323
|
-
function readState(
|
|
383
|
+
function readState(path29) {
|
|
324
384
|
try {
|
|
325
|
-
return JSON.parse(readFileSync4(
|
|
385
|
+
return JSON.parse(readFileSync4(path29, "utf-8"));
|
|
326
386
|
} catch {
|
|
327
387
|
return {};
|
|
328
388
|
}
|
|
329
389
|
}
|
|
330
390
|
async function ensureCmuxAutoConfig(opts = {}) {
|
|
331
|
-
const
|
|
391
|
+
const statePath2 = opts.statePath ?? defaultStatePath();
|
|
332
392
|
const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;
|
|
333
393
|
const probe = opts.probe ?? probeCmuxDaemonDirect;
|
|
334
394
|
const cfg = ensureConfig({ path: opts.configPath });
|
|
@@ -336,15 +396,15 @@ async function ensureCmuxAutoConfig(opts = {}) {
|
|
|
336
396
|
const needsRestart = verdict === "denied";
|
|
337
397
|
let promptedThisRun = false;
|
|
338
398
|
if (needsRestart) {
|
|
339
|
-
const already = readState(
|
|
399
|
+
const already = readState(statePath2).promptedRestart === true;
|
|
340
400
|
if (!already) {
|
|
341
|
-
mkdirSync2(dirname2(
|
|
342
|
-
writeFileSync3(
|
|
401
|
+
mkdirSync2(dirname2(statePath2), { recursive: true });
|
|
402
|
+
writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
|
|
343
403
|
promptedThisRun = true;
|
|
344
404
|
}
|
|
345
405
|
} else if (verdict === "reachable") {
|
|
346
|
-
if (existsSync4(
|
|
347
|
-
rmSync2(
|
|
406
|
+
if (existsSync4(statePath2))
|
|
407
|
+
rmSync2(statePath2, { force: true });
|
|
348
408
|
}
|
|
349
409
|
return {
|
|
350
410
|
configPath: cfg.path,
|
|
@@ -543,9 +603,9 @@ var init_config_version = __esm({
|
|
|
543
603
|
|
|
544
604
|
// packages/shared/dist/lib/git-worktree.js
|
|
545
605
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
546
|
-
import
|
|
606
|
+
import path3 from "path";
|
|
547
607
|
function worktreePath(repoRoot, worktreeDir, project, name) {
|
|
548
|
-
return
|
|
608
|
+
return path3.resolve(repoRoot, worktreeDir, `${project}-${name}`);
|
|
549
609
|
}
|
|
550
610
|
function crewBranch(name) {
|
|
551
611
|
return `crew/${name}`;
|
|
@@ -579,7 +639,7 @@ var init_git_worktree = __esm({
|
|
|
579
639
|
});
|
|
580
640
|
|
|
581
641
|
// packages/shared/dist/lib/resolve-text-input.js
|
|
582
|
-
import
|
|
642
|
+
import fs3 from "fs";
|
|
583
643
|
async function readAllStdin() {
|
|
584
644
|
const chunks = [];
|
|
585
645
|
for await (const chunk of process.stdin) {
|
|
@@ -591,7 +651,7 @@ function flagName(label) {
|
|
|
591
651
|
return label === "task" ? "--task-file" : "--message-file";
|
|
592
652
|
}
|
|
593
653
|
async function resolveTextInput(opts, deps) {
|
|
594
|
-
const readFile6 = deps?.readFile ?? ((p) =>
|
|
654
|
+
const readFile6 = deps?.readFile ?? ((p) => fs3.readFileSync(p, "utf8"));
|
|
595
655
|
const readStdin3 = deps?.readStdin ?? readAllStdin;
|
|
596
656
|
if (opts.filePath) {
|
|
597
657
|
if (opts.filePath === "-") {
|
|
@@ -619,59 +679,59 @@ var init_resolve_text_input = __esm({
|
|
|
619
679
|
});
|
|
620
680
|
|
|
621
681
|
// packages/shared/dist/lib/runtime-sync.js
|
|
622
|
-
import
|
|
623
|
-
import
|
|
682
|
+
import fs4 from "fs";
|
|
683
|
+
import path4 from "path";
|
|
624
684
|
function copyIfDifferent(src, dest) {
|
|
625
|
-
if (
|
|
626
|
-
if (
|
|
685
|
+
if (fs4.existsSync(dest)) {
|
|
686
|
+
if (fs4.readFileSync(src).equals(fs4.readFileSync(dest)))
|
|
627
687
|
return false;
|
|
628
688
|
}
|
|
629
|
-
|
|
689
|
+
fs4.copyFileSync(src, dest);
|
|
630
690
|
return true;
|
|
631
691
|
}
|
|
632
692
|
function mirrorDir(src, dest) {
|
|
633
|
-
|
|
634
|
-
const srcEntries =
|
|
693
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
694
|
+
const srcEntries = fs4.readdirSync(src, { withFileTypes: true });
|
|
635
695
|
const srcNames = new Set(srcEntries.map((e) => e.name));
|
|
636
696
|
for (const entry of srcEntries) {
|
|
637
|
-
const srcPath =
|
|
638
|
-
const destPath =
|
|
697
|
+
const srcPath = path4.join(src, entry.name);
|
|
698
|
+
const destPath = path4.join(dest, entry.name);
|
|
639
699
|
if (entry.isDirectory()) {
|
|
640
700
|
mirrorDir(srcPath, destPath);
|
|
641
701
|
} else {
|
|
642
702
|
copyIfDifferent(srcPath, destPath);
|
|
643
703
|
}
|
|
644
704
|
}
|
|
645
|
-
for (const entry of
|
|
705
|
+
for (const entry of fs4.readdirSync(dest, { withFileTypes: true })) {
|
|
646
706
|
if (!srcNames.has(entry.name)) {
|
|
647
|
-
|
|
707
|
+
fs4.rmSync(path4.join(dest, entry.name), { recursive: true, force: true });
|
|
648
708
|
}
|
|
649
709
|
}
|
|
650
710
|
}
|
|
651
711
|
function mirrorFlat(src, dest, match, chmod) {
|
|
652
|
-
|
|
653
|
-
const matched =
|
|
712
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
713
|
+
const matched = fs4.readdirSync(src, { withFileTypes: true }).filter((e) => e.isFile() && match.test(e.name)).map((e) => e.name);
|
|
654
714
|
const matchedSet = new Set(matched);
|
|
655
715
|
for (const name of matched) {
|
|
656
|
-
const destPath =
|
|
657
|
-
const copied = copyIfDifferent(
|
|
716
|
+
const destPath = path4.join(dest, name);
|
|
717
|
+
const copied = copyIfDifferent(path4.join(src, name), destPath);
|
|
658
718
|
if (copied && chmod !== void 0)
|
|
659
|
-
|
|
719
|
+
fs4.chmodSync(destPath, chmod);
|
|
660
720
|
}
|
|
661
|
-
for (const entry of
|
|
721
|
+
for (const entry of fs4.readdirSync(dest, { withFileTypes: true })) {
|
|
662
722
|
if (!matchedSet.has(entry.name)) {
|
|
663
|
-
|
|
723
|
+
fs4.rmSync(path4.join(dest, entry.name), { recursive: true, force: true });
|
|
664
724
|
}
|
|
665
725
|
}
|
|
666
726
|
}
|
|
667
727
|
function ensureRuntimeSynced(opts) {
|
|
668
728
|
const targets = opts.targets ?? MANAGED_TARGETS;
|
|
669
729
|
for (const t of targets) {
|
|
670
|
-
const srcDir =
|
|
730
|
+
const srcDir = path4.join(opts.sourceRoot, t.srcRel);
|
|
671
731
|
try {
|
|
672
|
-
if (!
|
|
732
|
+
if (!fs4.existsSync(srcDir))
|
|
673
733
|
continue;
|
|
674
|
-
const destDir =
|
|
734
|
+
const destDir = path4.join(opts.runtimeRoot, t.name);
|
|
675
735
|
if (t.mode === "tree") {
|
|
676
736
|
mirrorDir(srcDir, destDir);
|
|
677
737
|
} else {
|
|
@@ -735,8 +795,8 @@ var init_tool_compat = __esm({
|
|
|
735
795
|
});
|
|
736
796
|
|
|
737
797
|
// packages/shared/dist/lib/canonical-source.js
|
|
738
|
-
import
|
|
739
|
-
import
|
|
798
|
+
import fs5 from "fs";
|
|
799
|
+
import path5 from "path";
|
|
740
800
|
function parseSkill(raw) {
|
|
741
801
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
742
802
|
if (!match)
|
|
@@ -777,10 +837,10 @@ async function readSkills(driver, skillsDir) {
|
|
|
777
837
|
function readRoleTemplates(opts) {
|
|
778
838
|
if (!opts.pkgRoot)
|
|
779
839
|
return "";
|
|
780
|
-
const reader = opts.readFile ?? ((p) =>
|
|
840
|
+
const reader = opts.readFile ?? ((p) => fs5.readFileSync(p, "utf-8"));
|
|
781
841
|
const sections = [];
|
|
782
842
|
for (const { file, heading } of ROLE_TEMPLATES) {
|
|
783
|
-
const full =
|
|
843
|
+
const full = path5.join(opts.pkgRoot, "templates", file);
|
|
784
844
|
let body = "";
|
|
785
845
|
try {
|
|
786
846
|
body = reader(full);
|
|
@@ -817,8 +877,8 @@ var init_canonical_source = __esm({
|
|
|
817
877
|
|
|
818
878
|
// packages/shared/dist/lib/daily-logs.js
|
|
819
879
|
import { execSync } from "child_process";
|
|
820
|
-
import
|
|
821
|
-
import
|
|
880
|
+
import fs6 from "fs";
|
|
881
|
+
import path6 from "path";
|
|
822
882
|
import matter from "gray-matter";
|
|
823
883
|
function iso(d) {
|
|
824
884
|
return d.toISOString().slice(0, 10);
|
|
@@ -870,7 +930,7 @@ function getGitCommits(projectPath, dateStr) {
|
|
|
870
930
|
}
|
|
871
931
|
function getGitCommitsInRange(projectPath, since, until) {
|
|
872
932
|
const resolved = resolveHome(projectPath);
|
|
873
|
-
if (!
|
|
933
|
+
if (!fs6.existsSync(path6.join(resolved, ".git")))
|
|
874
934
|
return [];
|
|
875
935
|
const untilArg = until ? ` --until="${until}"` : "";
|
|
876
936
|
try {
|
|
@@ -884,7 +944,7 @@ function getGitCommitsInRange(projectPath, since, until) {
|
|
|
884
944
|
}
|
|
885
945
|
function getMergedPRsInRange(projectPath, since, until) {
|
|
886
946
|
const resolved = resolveHome(projectPath);
|
|
887
|
-
if (!
|
|
947
|
+
if (!fs6.existsSync(path6.join(resolved, ".git")))
|
|
888
948
|
return [];
|
|
889
949
|
const untilArg = until ? ` --until="${until}"` : "";
|
|
890
950
|
try {
|
|
@@ -924,10 +984,27 @@ var init_vault_layout = __esm({
|
|
|
924
984
|
}
|
|
925
985
|
});
|
|
926
986
|
|
|
987
|
+
// packages/shared/dist/daemon-keys.js
|
|
988
|
+
function isDaemonCachedKey(dottedKey) {
|
|
989
|
+
return DAEMON_CACHED_PREFIXES.some((p) => dottedKey === p || dottedKey.startsWith(p));
|
|
990
|
+
}
|
|
991
|
+
var DAEMON_CACHED_PREFIXES;
|
|
992
|
+
var init_daemon_keys = __esm({
|
|
993
|
+
"packages/shared/dist/daemon-keys.js"() {
|
|
994
|
+
DAEMON_CACHED_PREFIXES = [
|
|
995
|
+
"telegram.",
|
|
996
|
+
"defaults.taskTimeoutMs",
|
|
997
|
+
"defaults.cmuxEventsBridge",
|
|
998
|
+
"projects."
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
|
|
927
1003
|
// packages/shared/dist/index.js
|
|
928
1004
|
var init_dist = __esm({
|
|
929
1005
|
"packages/shared/dist/index.js"() {
|
|
930
1006
|
init_config();
|
|
1007
|
+
init_project_config();
|
|
931
1008
|
init_effort();
|
|
932
1009
|
init_runtime();
|
|
933
1010
|
init_control();
|
|
@@ -947,6 +1024,7 @@ var init_dist = __esm({
|
|
|
947
1024
|
init_canonical_source();
|
|
948
1025
|
init_daily_logs();
|
|
949
1026
|
init_vault_layout();
|
|
1027
|
+
init_daemon_keys();
|
|
950
1028
|
}
|
|
951
1029
|
});
|
|
952
1030
|
|
|
@@ -978,7 +1056,7 @@ var init_daemon = __esm({
|
|
|
978
1056
|
});
|
|
979
1057
|
|
|
980
1058
|
// packages/core/dist/mailbox.js
|
|
981
|
-
import { promises as
|
|
1059
|
+
import { promises as fs7 } from "fs";
|
|
982
1060
|
import { join as join5 } from "path";
|
|
983
1061
|
import { randomUUID } from "crypto";
|
|
984
1062
|
var init_mailbox = __esm({
|
|
@@ -1131,10 +1209,10 @@ function daemonEntryPath() {
|
|
|
1131
1209
|
function xmlEscape(s) {
|
|
1132
1210
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1133
1211
|
}
|
|
1134
|
-
function sanitizePathForPlist(
|
|
1212
|
+
function sanitizePathForPlist(path29) {
|
|
1135
1213
|
const seen = /* @__PURE__ */ new Set();
|
|
1136
1214
|
const stable = [];
|
|
1137
|
-
for (const p of
|
|
1215
|
+
for (const p of path29.split(":")) {
|
|
1138
1216
|
if (!p)
|
|
1139
1217
|
continue;
|
|
1140
1218
|
if (p.includes("/.claude/plugins/"))
|
|
@@ -1202,6 +1280,9 @@ function renderPlist(nodeBin, daemonEntry, pathEnv = "") {
|
|
|
1202
1280
|
function programArgsBlock(nodeBin, daemonEntry) {
|
|
1203
1281
|
return `<array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>`;
|
|
1204
1282
|
}
|
|
1283
|
+
function kickstartArgv(target, plistChanged) {
|
|
1284
|
+
return plistChanged ? ["kickstart", "-k", target] : ["kickstart", target];
|
|
1285
|
+
}
|
|
1205
1286
|
function daemonLockPath() {
|
|
1206
1287
|
return join7(homedir4(), ".config", "squadrant", "daemon.lock");
|
|
1207
1288
|
}
|
|
@@ -1423,35 +1504,35 @@ var init_start = __esm({
|
|
|
1423
1504
|
|
|
1424
1505
|
// packages/core/dist/session-freshness.js
|
|
1425
1506
|
import crypto from "crypto";
|
|
1426
|
-
import
|
|
1427
|
-
import
|
|
1507
|
+
import fs8 from "fs";
|
|
1508
|
+
import path7 from "path";
|
|
1428
1509
|
function loadSessions(sessionsPath) {
|
|
1429
1510
|
try {
|
|
1430
|
-
return JSON.parse(
|
|
1511
|
+
return JSON.parse(fs8.readFileSync(sessionsPath, "utf-8"));
|
|
1431
1512
|
} catch {
|
|
1432
1513
|
return { workspaces: {} };
|
|
1433
1514
|
}
|
|
1434
1515
|
}
|
|
1435
1516
|
function saveSessions(sessionsPath, sessions) {
|
|
1436
|
-
const dir =
|
|
1437
|
-
|
|
1438
|
-
|
|
1517
|
+
const dir = path7.dirname(sessionsPath);
|
|
1518
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1519
|
+
fs8.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + "\n");
|
|
1439
1520
|
}
|
|
1440
1521
|
function computeTemplateHash(role, templatesDir) {
|
|
1441
1522
|
const hash = crypto.createHash("sha256");
|
|
1442
|
-
const roleFile =
|
|
1443
|
-
const legacyRoleFile =
|
|
1444
|
-
if (
|
|
1445
|
-
hash.update(
|
|
1446
|
-
} else if (
|
|
1447
|
-
hash.update(
|
|
1523
|
+
const roleFile = path7.join(templatesDir, `${role}.claude.md`);
|
|
1524
|
+
const legacyRoleFile = path7.join(templatesDir, `${role}.CLAUDE.md`);
|
|
1525
|
+
if (fs8.existsSync(roleFile)) {
|
|
1526
|
+
hash.update(fs8.readFileSync(roleFile, "utf-8"));
|
|
1527
|
+
} else if (fs8.existsSync(legacyRoleFile)) {
|
|
1528
|
+
hash.update(fs8.readFileSync(legacyRoleFile, "utf-8"));
|
|
1448
1529
|
}
|
|
1449
|
-
const pluginSkillsDir =
|
|
1450
|
-
if (
|
|
1451
|
-
for (const skill of
|
|
1452
|
-
const skillFile =
|
|
1453
|
-
if (
|
|
1454
|
-
hash.update(
|
|
1530
|
+
const pluginSkillsDir = path7.join(templatesDir, "..", "plugin", "skills");
|
|
1531
|
+
if (fs8.existsSync(pluginSkillsDir)) {
|
|
1532
|
+
for (const skill of fs8.readdirSync(pluginSkillsDir).sort()) {
|
|
1533
|
+
const skillFile = path7.join(pluginSkillsDir, skill, "SKILL.md");
|
|
1534
|
+
if (fs8.existsSync(skillFile)) {
|
|
1535
|
+
hash.update(fs8.readFileSync(skillFile, "utf-8"));
|
|
1455
1536
|
}
|
|
1456
1537
|
}
|
|
1457
1538
|
}
|
|
@@ -1571,6 +1652,276 @@ var init_crew_lifecycle = __esm({
|
|
|
1571
1652
|
}
|
|
1572
1653
|
});
|
|
1573
1654
|
|
|
1655
|
+
// packages/core/dist/telegram/bot-commands.js
|
|
1656
|
+
var BOT_COMMANDS;
|
|
1657
|
+
var init_bot_commands = __esm({
|
|
1658
|
+
"packages/core/dist/telegram/bot-commands.js"() {
|
|
1659
|
+
BOT_COMMANDS = [
|
|
1660
|
+
{ command: "status", description: "squadrant status" },
|
|
1661
|
+
{ command: "projects", description: "list registered projects" },
|
|
1662
|
+
{ command: "crews", description: "list crews for a project" },
|
|
1663
|
+
{ command: "launch", description: "launch a project's captain" },
|
|
1664
|
+
{ command: "effort", description: "set effort: max | balance | low" },
|
|
1665
|
+
{ command: "spawn", description: "spawn a crew (guided)" },
|
|
1666
|
+
{ command: "notify", description: "notification panel for a project topic" },
|
|
1667
|
+
{ command: "mute", description: "mute a project's topic" },
|
|
1668
|
+
{ command: "unmute", description: "unmute a project's topic" },
|
|
1669
|
+
{ command: "help", description: "list commands" }
|
|
1670
|
+
];
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
// packages/core/dist/telegram/auth.js
|
|
1675
|
+
var init_auth = __esm({
|
|
1676
|
+
"packages/core/dist/telegram/auth.js"() {
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
// packages/core/dist/telegram/commands.js
|
|
1681
|
+
var init_commands = __esm({
|
|
1682
|
+
"packages/core/dist/telegram/commands.js"() {
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
// packages/core/dist/telegram/ensure-captain.js
|
|
1687
|
+
var init_ensure_captain = __esm({
|
|
1688
|
+
"packages/core/dist/telegram/ensure-captain.js"() {
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
// packages/core/dist/telegram/format.js
|
|
1693
|
+
function topicName(project) {
|
|
1694
|
+
return project;
|
|
1695
|
+
}
|
|
1696
|
+
function maskToken(token) {
|
|
1697
|
+
if (token.length <= 4)
|
|
1698
|
+
return token;
|
|
1699
|
+
return "*".repeat(token.length - 4) + token.slice(-4);
|
|
1700
|
+
}
|
|
1701
|
+
var init_format = __esm({
|
|
1702
|
+
"packages/core/dist/telegram/format.js"() {
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// packages/core/dist/telegram/state.js
|
|
1707
|
+
import fs9 from "fs";
|
|
1708
|
+
import path8 from "path";
|
|
1709
|
+
function statePath(stateRoot) {
|
|
1710
|
+
return path8.join(stateRoot, "telegram-state.json");
|
|
1711
|
+
}
|
|
1712
|
+
function topicKey(project, scope = "project") {
|
|
1713
|
+
return `${project}::${scope}`;
|
|
1714
|
+
}
|
|
1715
|
+
function loadState(stateRoot) {
|
|
1716
|
+
try {
|
|
1717
|
+
const raw = fs9.readFileSync(statePath(stateRoot), "utf-8");
|
|
1718
|
+
const data = JSON.parse(raw);
|
|
1719
|
+
const result = {
|
|
1720
|
+
offset: typeof data.offset === "number" ? data.offset : 0,
|
|
1721
|
+
topics: data.topics ?? {},
|
|
1722
|
+
notify: data.notify ?? {}
|
|
1723
|
+
};
|
|
1724
|
+
if (typeof data.lastUserId === "number")
|
|
1725
|
+
result.lastUserId = data.lastUserId;
|
|
1726
|
+
return result;
|
|
1727
|
+
} catch {
|
|
1728
|
+
return { offset: 0, topics: {}, notify: {} };
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
function saveState(stateRoot, s) {
|
|
1732
|
+
fs9.mkdirSync(stateRoot, { recursive: true });
|
|
1733
|
+
fs9.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
1734
|
+
}
|
|
1735
|
+
function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
1736
|
+
const s = loadState(stateRoot);
|
|
1737
|
+
s.topics[topicKey(project, scope)] = topicId;
|
|
1738
|
+
saveState(stateRoot, s);
|
|
1739
|
+
}
|
|
1740
|
+
function isNotifyActive(stateRoot, project) {
|
|
1741
|
+
return loadState(stateRoot).notify[project] === true;
|
|
1742
|
+
}
|
|
1743
|
+
function setNotify(stateRoot, project, active) {
|
|
1744
|
+
const s = loadState(stateRoot);
|
|
1745
|
+
s.notify[project] = active;
|
|
1746
|
+
saveState(stateRoot, s);
|
|
1747
|
+
}
|
|
1748
|
+
var init_state = __esm({
|
|
1749
|
+
"packages/core/dist/telegram/state.js"() {
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// packages/core/dist/telegram/client.js
|
|
1754
|
+
function createTelegramClient(opts) {
|
|
1755
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
1756
|
+
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
1757
|
+
async function call(method, body) {
|
|
1758
|
+
const res = await fetchImpl(`${base}/${method}`, {
|
|
1759
|
+
method: "POST",
|
|
1760
|
+
headers: { "content-type": "application/json" },
|
|
1761
|
+
body: JSON.stringify(body)
|
|
1762
|
+
});
|
|
1763
|
+
const json = await res.json();
|
|
1764
|
+
if (!res.ok || !json.ok) {
|
|
1765
|
+
const code = json.error_code ?? res.status;
|
|
1766
|
+
const desc = json.description ?? "unknown error";
|
|
1767
|
+
throw new Error(`telegram ${method} failed (${code}): ${desc}`);
|
|
1768
|
+
}
|
|
1769
|
+
return json.result;
|
|
1770
|
+
}
|
|
1771
|
+
return {
|
|
1772
|
+
async getMe() {
|
|
1773
|
+
const r = await call("getMe", {});
|
|
1774
|
+
return { id: r.id, username: r.username };
|
|
1775
|
+
},
|
|
1776
|
+
getUpdates(offset, timeoutSec = 50) {
|
|
1777
|
+
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
1778
|
+
},
|
|
1779
|
+
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
1780
|
+
const body = { chat_id: chatId, text };
|
|
1781
|
+
if (threadId !== void 0)
|
|
1782
|
+
body.message_thread_id = threadId;
|
|
1783
|
+
if (replyMarkup !== void 0)
|
|
1784
|
+
body.reply_markup = replyMarkup;
|
|
1785
|
+
await call("sendMessage", body);
|
|
1786
|
+
},
|
|
1787
|
+
async answerCallbackQuery(callbackQueryId, text) {
|
|
1788
|
+
const body = { callback_query_id: callbackQueryId };
|
|
1789
|
+
if (text !== void 0)
|
|
1790
|
+
body.text = text;
|
|
1791
|
+
await call("answerCallbackQuery", body);
|
|
1792
|
+
},
|
|
1793
|
+
async editMessageReplyMarkup(chatId, messageId, replyMarkup) {
|
|
1794
|
+
await call("editMessageReplyMarkup", { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup });
|
|
1795
|
+
},
|
|
1796
|
+
async createForumTopic(chatId, name) {
|
|
1797
|
+
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
1798
|
+
return r.message_thread_id;
|
|
1799
|
+
},
|
|
1800
|
+
async setMyCommands(commands) {
|
|
1801
|
+
await call("setMyCommands", { commands });
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
var init_client = __esm({
|
|
1806
|
+
"packages/core/dist/telegram/client.js"() {
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
// packages/core/dist/telegram/panels.js
|
|
1811
|
+
var init_panels = __esm({
|
|
1812
|
+
"packages/core/dist/telegram/panels.js"() {
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
// packages/core/dist/telegram/tiers.js
|
|
1817
|
+
var DONE_ONLY, ALERTS;
|
|
1818
|
+
var init_tiers = __esm({
|
|
1819
|
+
"packages/core/dist/telegram/tiers.js"() {
|
|
1820
|
+
DONE_ONLY = /* @__PURE__ */ new Set(["task.done", "task.failed"]);
|
|
1821
|
+
ALERTS = /* @__PURE__ */ new Set([
|
|
1822
|
+
...DONE_ONLY,
|
|
1823
|
+
"task.blocked",
|
|
1824
|
+
"task.approval.requested",
|
|
1825
|
+
"task.input.requested",
|
|
1826
|
+
"task.timeout"
|
|
1827
|
+
]);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
// packages/core/dist/telegram/bridge.js
|
|
1832
|
+
import os3 from "os";
|
|
1833
|
+
import path9 from "path";
|
|
1834
|
+
var init_bridge = __esm({
|
|
1835
|
+
"packages/core/dist/telegram/bridge.js"() {
|
|
1836
|
+
init_dist();
|
|
1837
|
+
init_auth();
|
|
1838
|
+
init_commands();
|
|
1839
|
+
init_format();
|
|
1840
|
+
init_panels();
|
|
1841
|
+
init_state();
|
|
1842
|
+
init_tiers();
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
// packages/core/dist/telegram/setup.js
|
|
1847
|
+
import fs10 from "fs";
|
|
1848
|
+
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
1849
|
+
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
1850
|
+
return "reuse";
|
|
1851
|
+
return "detect";
|
|
1852
|
+
}
|
|
1853
|
+
async function detectGroupAndUser(client, opts = {}) {
|
|
1854
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1855
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1856
|
+
const deadline = Date.now() + timeoutMs;
|
|
1857
|
+
let offset = 0;
|
|
1858
|
+
while (Date.now() < deadline) {
|
|
1859
|
+
const updates = await client.getUpdates(offset, 10);
|
|
1860
|
+
for (const u of updates) {
|
|
1861
|
+
if (u.update_id >= offset)
|
|
1862
|
+
offset = u.update_id + 1;
|
|
1863
|
+
if (u.message?.chat?.type === "supergroup") {
|
|
1864
|
+
return { supergroupId: u.message.chat.id, userId: u.message.from?.id };
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
await sleep2(2e3);
|
|
1868
|
+
}
|
|
1869
|
+
throw new Error("Timed out waiting for the bot to receive a message in a supergroup");
|
|
1870
|
+
}
|
|
1871
|
+
function writeTelegramConfig(configPath, opts) {
|
|
1872
|
+
let config;
|
|
1873
|
+
let raw = null;
|
|
1874
|
+
try {
|
|
1875
|
+
raw = fs10.readFileSync(configPath, "utf-8");
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
if (err.code !== "ENOENT") {
|
|
1878
|
+
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (raw !== null) {
|
|
1882
|
+
try {
|
|
1883
|
+
config = JSON.parse(raw);
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);
|
|
1886
|
+
}
|
|
1887
|
+
} else {
|
|
1888
|
+
config = {};
|
|
1889
|
+
}
|
|
1890
|
+
const prev = config.telegram && typeof config.telegram === "object" ? config.telegram : {};
|
|
1891
|
+
const next = {
|
|
1892
|
+
botToken: opts.token,
|
|
1893
|
+
supergroupId: opts.supergroupId,
|
|
1894
|
+
chats: [opts.supergroupId]
|
|
1895
|
+
};
|
|
1896
|
+
const users = opts.users ?? prev.users;
|
|
1897
|
+
const remoteControl = opts.remoteControl ?? prev.remoteControl;
|
|
1898
|
+
if (users !== void 0)
|
|
1899
|
+
next.users = users;
|
|
1900
|
+
if (remoteControl !== void 0)
|
|
1901
|
+
next.remoteControl = remoteControl;
|
|
1902
|
+
config.telegram = next;
|
|
1903
|
+
fs10.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1904
|
+
}
|
|
1905
|
+
var init_setup = __esm({
|
|
1906
|
+
"packages/core/dist/telegram/setup.js"() {
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
// packages/core/dist/telegram/index.js
|
|
1911
|
+
var init_telegram = __esm({
|
|
1912
|
+
"packages/core/dist/telegram/index.js"() {
|
|
1913
|
+
init_bot_commands();
|
|
1914
|
+
init_auth();
|
|
1915
|
+
init_commands();
|
|
1916
|
+
init_ensure_captain();
|
|
1917
|
+
init_format();
|
|
1918
|
+
init_state();
|
|
1919
|
+
init_client();
|
|
1920
|
+
init_bridge();
|
|
1921
|
+
init_setup();
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1574
1925
|
// packages/core/dist/index.js
|
|
1575
1926
|
var init_dist2 = __esm({
|
|
1576
1927
|
"packages/core/dist/index.js"() {
|
|
@@ -1596,6 +1947,7 @@ var init_dist2 = __esm({
|
|
|
1596
1947
|
init_session_freshness();
|
|
1597
1948
|
init_crew_protocol();
|
|
1598
1949
|
init_crew_lifecycle();
|
|
1950
|
+
init_telegram();
|
|
1599
1951
|
}
|
|
1600
1952
|
});
|
|
1601
1953
|
|
|
@@ -2114,13 +2466,13 @@ var init_notifiers = __esm({
|
|
|
2114
2466
|
});
|
|
2115
2467
|
|
|
2116
2468
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
2117
|
-
import
|
|
2469
|
+
import fs11 from "fs/promises";
|
|
2118
2470
|
import { existsSync as existsSync8 } from "fs";
|
|
2119
|
-
import
|
|
2471
|
+
import path10 from "path";
|
|
2120
2472
|
function resolveInRoot(root, relative) {
|
|
2121
|
-
const joined =
|
|
2122
|
-
const normalized =
|
|
2123
|
-
if (joined !==
|
|
2473
|
+
const joined = path10.resolve(root, relative);
|
|
2474
|
+
const normalized = path10.resolve(root) + path10.sep;
|
|
2475
|
+
if (joined !== path10.resolve(root) && !joined.startsWith(normalized)) {
|
|
2124
2476
|
throw new Error(`Path '${relative}' escapes workspace root`);
|
|
2125
2477
|
}
|
|
2126
2478
|
return joined;
|
|
@@ -2139,16 +2491,16 @@ function createObsidianDriver(scope) {
|
|
|
2139
2491
|
};
|
|
2140
2492
|
},
|
|
2141
2493
|
async read(rel) {
|
|
2142
|
-
return
|
|
2494
|
+
return fs11.readFile(resolveInRoot(root, rel), "utf-8");
|
|
2143
2495
|
},
|
|
2144
2496
|
async write(rel, content) {
|
|
2145
2497
|
const abs = resolveInRoot(root, rel);
|
|
2146
|
-
await
|
|
2147
|
-
await
|
|
2498
|
+
await fs11.mkdir(path10.dirname(abs), { recursive: true });
|
|
2499
|
+
await fs11.writeFile(abs, content);
|
|
2148
2500
|
},
|
|
2149
2501
|
async exists(rel) {
|
|
2150
2502
|
try {
|
|
2151
|
-
await
|
|
2503
|
+
await fs11.access(resolveInRoot(root, rel));
|
|
2152
2504
|
return true;
|
|
2153
2505
|
} catch {
|
|
2154
2506
|
return false;
|
|
@@ -2156,13 +2508,13 @@ function createObsidianDriver(scope) {
|
|
|
2156
2508
|
},
|
|
2157
2509
|
async list(rel) {
|
|
2158
2510
|
try {
|
|
2159
|
-
return await
|
|
2511
|
+
return await fs11.readdir(resolveInRoot(root, rel));
|
|
2160
2512
|
} catch {
|
|
2161
2513
|
return [];
|
|
2162
2514
|
}
|
|
2163
2515
|
},
|
|
2164
2516
|
async mkdir(rel) {
|
|
2165
|
-
await
|
|
2517
|
+
await fs11.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
2166
2518
|
}
|
|
2167
2519
|
};
|
|
2168
2520
|
}
|
|
@@ -2831,8 +3183,8 @@ var init_registry4 = __esm({
|
|
|
2831
3183
|
});
|
|
2832
3184
|
|
|
2833
3185
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
2834
|
-
import
|
|
2835
|
-
import
|
|
3186
|
+
import fs12 from "fs";
|
|
3187
|
+
import path11 from "path";
|
|
2836
3188
|
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
|
|
2837
3189
|
const driver = registry.getDriver(agentName);
|
|
2838
3190
|
if (driver.name === "claude") {
|
|
@@ -2848,27 +3200,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
2848
3200
|
cmd += ` --model ${model}`;
|
|
2849
3201
|
}
|
|
2850
3202
|
if (templatesDir) {
|
|
2851
|
-
const roleFile2 =
|
|
2852
|
-
const legacyRoleFile =
|
|
2853
|
-
const actualRoleFile =
|
|
3203
|
+
const roleFile2 = path11.join(templatesDir, `${role}.claude.md`);
|
|
3204
|
+
const legacyRoleFile = path11.join(templatesDir, `${role}.CLAUDE.md`);
|
|
3205
|
+
const actualRoleFile = fs12.existsSync(roleFile2) ? roleFile2 : fs12.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
2854
3206
|
if (actualRoleFile) {
|
|
2855
3207
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
2856
3208
|
}
|
|
2857
|
-
const pluginDir =
|
|
2858
|
-
if (
|
|
3209
|
+
const pluginDir = path11.join(templatesDir, "..", "plugin");
|
|
3210
|
+
if (fs12.existsSync(pluginDir)) {
|
|
2859
3211
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
2860
3212
|
}
|
|
2861
3213
|
}
|
|
2862
3214
|
return cmd;
|
|
2863
3215
|
}
|
|
2864
|
-
const roleFile = templatesDir ?
|
|
3216
|
+
const roleFile = templatesDir ? path11.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
|
|
2865
3217
|
return driver.buildCommand({
|
|
2866
3218
|
prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
|
|
2867
3219
|
workdir: process.cwd(),
|
|
2868
3220
|
role,
|
|
2869
3221
|
model,
|
|
2870
3222
|
autoApprove: true,
|
|
2871
|
-
promptFile: roleFile &&
|
|
3223
|
+
promptFile: roleFile && fs12.existsSync(roleFile) ? roleFile : void 0
|
|
2872
3224
|
});
|
|
2873
3225
|
}
|
|
2874
3226
|
var init_launch_cmd = __esm({
|
|
@@ -2891,8 +3243,8 @@ var init_drivers = __esm({
|
|
|
2891
3243
|
|
|
2892
3244
|
// packages/agents/dist/projection/cursor.js
|
|
2893
3245
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2894
|
-
import
|
|
2895
|
-
import
|
|
3246
|
+
import path12 from "path";
|
|
3247
|
+
import os4 from "os";
|
|
2896
3248
|
function renderMdc(source) {
|
|
2897
3249
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
2898
3250
|
|
|
@@ -2940,7 +3292,7 @@ function createCursorEmitter() {
|
|
|
2940
3292
|
if (scope === "user") {
|
|
2941
3293
|
return [
|
|
2942
3294
|
{
|
|
2943
|
-
path:
|
|
3295
|
+
path: path12.join(os4.homedir(), ".cursor/rules/squadrant-global.mdc"),
|
|
2944
3296
|
shared: false,
|
|
2945
3297
|
format: "mdc"
|
|
2946
3298
|
}
|
|
@@ -2950,7 +3302,7 @@ function createCursorEmitter() {
|
|
|
2950
3302
|
return [];
|
|
2951
3303
|
return [
|
|
2952
3304
|
{
|
|
2953
|
-
path:
|
|
3305
|
+
path: path12.join(projectRoot, ".cursor/rules/squadrant.mdc"),
|
|
2954
3306
|
shared: false,
|
|
2955
3307
|
format: "mdc"
|
|
2956
3308
|
}
|
|
@@ -2967,7 +3319,7 @@ function createCursorEmitter() {
|
|
|
2967
3319
|
diff: buildDiff(existing, generated)
|
|
2968
3320
|
};
|
|
2969
3321
|
}
|
|
2970
|
-
await mkdir(
|
|
3322
|
+
await mkdir(path12.dirname(dest.path), { recursive: true });
|
|
2971
3323
|
await writeFile(dest.path, generated, "utf-8");
|
|
2972
3324
|
return {
|
|
2973
3325
|
written: true,
|
|
@@ -3018,8 +3370,8 @@ var init_marker = __esm({
|
|
|
3018
3370
|
|
|
3019
3371
|
// packages/agents/dist/projection/codex.js
|
|
3020
3372
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3021
|
-
import
|
|
3022
|
-
import
|
|
3373
|
+
import path13 from "path";
|
|
3374
|
+
import os5 from "os";
|
|
3023
3375
|
function renderMarkdown(source) {
|
|
3024
3376
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3025
3377
|
|
|
@@ -3043,7 +3395,7 @@ function createCodexEmitter() {
|
|
|
3043
3395
|
destinations(scope, projectRoot) {
|
|
3044
3396
|
if (scope === "user") {
|
|
3045
3397
|
return [{
|
|
3046
|
-
path:
|
|
3398
|
+
path: path13.join(os5.homedir(), ".codex/AGENTS.md"),
|
|
3047
3399
|
shared: true,
|
|
3048
3400
|
format: "markdown"
|
|
3049
3401
|
}];
|
|
@@ -3051,7 +3403,7 @@ function createCodexEmitter() {
|
|
|
3051
3403
|
if (!projectRoot)
|
|
3052
3404
|
return [];
|
|
3053
3405
|
return [{
|
|
3054
|
-
path:
|
|
3406
|
+
path: path13.join(projectRoot, "AGENTS.md"),
|
|
3055
3407
|
shared: true,
|
|
3056
3408
|
format: "markdown"
|
|
3057
3409
|
}];
|
|
@@ -3072,7 +3424,7 @@ ${existing ?? ""}
|
|
|
3072
3424
|
${generated}`
|
|
3073
3425
|
};
|
|
3074
3426
|
}
|
|
3075
|
-
await mkdir2(
|
|
3427
|
+
await mkdir2(path13.dirname(dest.path), { recursive: true });
|
|
3076
3428
|
await writeFile2(dest.path, generated, "utf-8");
|
|
3077
3429
|
return {
|
|
3078
3430
|
written: true,
|
|
@@ -3090,8 +3442,8 @@ var init_codex2 = __esm({
|
|
|
3090
3442
|
|
|
3091
3443
|
// packages/agents/dist/projection/gemini.js
|
|
3092
3444
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
3093
|
-
import
|
|
3094
|
-
import
|
|
3445
|
+
import path14 from "path";
|
|
3446
|
+
import os6 from "os";
|
|
3095
3447
|
function renderMarkdown2(source) {
|
|
3096
3448
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3097
3449
|
|
|
@@ -3115,7 +3467,7 @@ function createGeminiEmitter() {
|
|
|
3115
3467
|
destinations(scope, projectRoot) {
|
|
3116
3468
|
if (scope === "user") {
|
|
3117
3469
|
return [{
|
|
3118
|
-
path:
|
|
3470
|
+
path: path14.join(os6.homedir(), ".gemini/GEMINI.md"),
|
|
3119
3471
|
shared: true,
|
|
3120
3472
|
format: "markdown"
|
|
3121
3473
|
}];
|
|
@@ -3123,7 +3475,7 @@ function createGeminiEmitter() {
|
|
|
3123
3475
|
if (!projectRoot)
|
|
3124
3476
|
return [];
|
|
3125
3477
|
return [{
|
|
3126
|
-
path:
|
|
3478
|
+
path: path14.join(projectRoot, "GEMINI.md"),
|
|
3127
3479
|
shared: true,
|
|
3128
3480
|
format: "markdown"
|
|
3129
3481
|
}];
|
|
@@ -3144,7 +3496,7 @@ ${existing ?? ""}
|
|
|
3144
3496
|
${generated}`
|
|
3145
3497
|
};
|
|
3146
3498
|
}
|
|
3147
|
-
await mkdir3(
|
|
3499
|
+
await mkdir3(path14.dirname(dest.path), { recursive: true });
|
|
3148
3500
|
await writeFile3(dest.path, generated, "utf-8");
|
|
3149
3501
|
return {
|
|
3150
3502
|
written: true,
|
|
@@ -3162,8 +3514,8 @@ var init_gemini2 = __esm({
|
|
|
3162
3514
|
|
|
3163
3515
|
// packages/agents/dist/projection/opencode.js
|
|
3164
3516
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
3165
|
-
import
|
|
3166
|
-
import
|
|
3517
|
+
import path15 from "path";
|
|
3518
|
+
import os7 from "os";
|
|
3167
3519
|
function renderMarkdown3(source) {
|
|
3168
3520
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3169
3521
|
|
|
@@ -3187,7 +3539,7 @@ function createOpencodeEmitter() {
|
|
|
3187
3539
|
destinations(scope, projectRoot) {
|
|
3188
3540
|
if (scope === "user") {
|
|
3189
3541
|
return [{
|
|
3190
|
-
path:
|
|
3542
|
+
path: path15.join(os7.homedir(), ".config", "opencode", "AGENTS.md"),
|
|
3191
3543
|
shared: true,
|
|
3192
3544
|
format: "markdown"
|
|
3193
3545
|
}];
|
|
@@ -3195,7 +3547,7 @@ function createOpencodeEmitter() {
|
|
|
3195
3547
|
if (!projectRoot)
|
|
3196
3548
|
return [];
|
|
3197
3549
|
return [{
|
|
3198
|
-
path:
|
|
3550
|
+
path: path15.join(projectRoot, "AGENTS.md"),
|
|
3199
3551
|
shared: true,
|
|
3200
3552
|
format: "markdown"
|
|
3201
3553
|
}];
|
|
@@ -3216,7 +3568,7 @@ ${existing ?? ""}
|
|
|
3216
3568
|
${generated}`
|
|
3217
3569
|
};
|
|
3218
3570
|
}
|
|
3219
|
-
await mkdir4(
|
|
3571
|
+
await mkdir4(path15.dirname(dest.path), { recursive: true });
|
|
3220
3572
|
await writeFile4(dest.path, generated, "utf-8");
|
|
3221
3573
|
return {
|
|
3222
3574
|
written: true,
|
|
@@ -4081,8 +4433,8 @@ function resolveLastAssistantText(payload) {
|
|
|
4081
4433
|
const derived = deriveTranscriptPath(p?.session_id, cwd);
|
|
4082
4434
|
if (derived)
|
|
4083
4435
|
candidates.push(derived);
|
|
4084
|
-
for (const
|
|
4085
|
-
const text = readLastAssistantText(
|
|
4436
|
+
for (const path29 of candidates) {
|
|
4437
|
+
const text = readLastAssistantText(path29);
|
|
4086
4438
|
if (text != null)
|
|
4087
4439
|
return text;
|
|
4088
4440
|
}
|
|
@@ -4481,11 +4833,11 @@ var init_dist4 = __esm({
|
|
|
4481
4833
|
// packages/cli/src/index.ts
|
|
4482
4834
|
init_dist();
|
|
4483
4835
|
init_dist2();
|
|
4484
|
-
import { Command as
|
|
4485
|
-
import { readFileSync as readFileSync11, existsSync as
|
|
4836
|
+
import { Command as Command28 } from "commander";
|
|
4837
|
+
import { readFileSync as readFileSync11, existsSync as existsSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
4486
4838
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
4487
|
-
import { dirname as
|
|
4488
|
-
import { homedir as
|
|
4839
|
+
import { dirname as dirname7, join as join23 } from "path";
|
|
4840
|
+
import { homedir as homedir15 } from "os";
|
|
4489
4841
|
|
|
4490
4842
|
// packages/cli/src/commands/doctor.ts
|
|
4491
4843
|
init_dist();
|
|
@@ -4494,9 +4846,9 @@ init_dist();
|
|
|
4494
4846
|
init_dist3();
|
|
4495
4847
|
import { Command } from "commander";
|
|
4496
4848
|
import { execSync as execSync8 } from "child_process";
|
|
4497
|
-
import
|
|
4849
|
+
import fs13 from "fs";
|
|
4498
4850
|
import { stat } from "fs/promises";
|
|
4499
|
-
import
|
|
4851
|
+
import path16 from "path";
|
|
4500
4852
|
import chalk3 from "chalk";
|
|
4501
4853
|
|
|
4502
4854
|
// packages/cli/src/commands/health-view.ts
|
|
@@ -4598,7 +4950,7 @@ function settingsHaveAgentTeams() {
|
|
|
4598
4950
|
try {
|
|
4599
4951
|
const home = process.env.HOME || "";
|
|
4600
4952
|
const settings = JSON.parse(
|
|
4601
|
-
|
|
4953
|
+
fs13.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
4602
4954
|
);
|
|
4603
4955
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
4604
4956
|
} catch {
|
|
@@ -4609,7 +4961,7 @@ function pluginInstalled(pluginKey) {
|
|
|
4609
4961
|
try {
|
|
4610
4962
|
const home = process.env.HOME || "";
|
|
4611
4963
|
const plugins = JSON.parse(
|
|
4612
|
-
|
|
4964
|
+
fs13.readFileSync(
|
|
4613
4965
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
4614
4966
|
"utf-8"
|
|
4615
4967
|
)
|
|
@@ -4641,7 +4993,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
4641
4993
|
const results = [];
|
|
4642
4994
|
results.push(check("Claude Code installed", commandExists("claude")));
|
|
4643
4995
|
results.push(check(`Claude Code version >= ${compatManifest.tools.claude.min}`, claudeVersionOk()));
|
|
4644
|
-
results.push(check("Obsidian installed", commandExists("obsidian") ||
|
|
4996
|
+
results.push(check("Obsidian installed", commandExists("obsidian") || fs13.existsSync("/Applications/Obsidian.app")));
|
|
4645
4997
|
results.push(check("Node.js >= 18", nodeVersionOk()));
|
|
4646
4998
|
results.push(
|
|
4647
4999
|
check(
|
|
@@ -4714,7 +5066,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
4714
5066
|
const emitter = projectionRegistry.get(name);
|
|
4715
5067
|
const [userDest] = emitter.destinations("user");
|
|
4716
5068
|
if (!userDest) continue;
|
|
4717
|
-
const dir =
|
|
5069
|
+
const dir = path16.dirname(userDest.path);
|
|
4718
5070
|
let status;
|
|
4719
5071
|
try {
|
|
4720
5072
|
await stat(dir);
|
|
@@ -4727,7 +5079,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
4727
5079
|
results.push(
|
|
4728
5080
|
check(
|
|
4729
5081
|
"Squadrant config exists",
|
|
4730
|
-
|
|
5082
|
+
fs13.existsSync(
|
|
4731
5083
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
4732
5084
|
)
|
|
4733
5085
|
)
|
|
@@ -4800,39 +5152,39 @@ init_dist();
|
|
|
4800
5152
|
init_dist3();
|
|
4801
5153
|
init_dist();
|
|
4802
5154
|
import { Command as Command2 } from "commander";
|
|
4803
|
-
import
|
|
4804
|
-
import
|
|
4805
|
-
import
|
|
5155
|
+
import fs14 from "fs";
|
|
5156
|
+
import path17 from "path";
|
|
5157
|
+
import os8 from "os";
|
|
4806
5158
|
import chalk4 from "chalk";
|
|
4807
5159
|
function findPackageRoot() {
|
|
4808
|
-
let dir =
|
|
5160
|
+
let dir = path17.dirname(new URL(import.meta.url).pathname);
|
|
4809
5161
|
while (dir !== "/") {
|
|
4810
|
-
if (
|
|
4811
|
-
dir =
|
|
5162
|
+
if (fs14.existsSync(path17.join(dir, "package.json"))) return dir;
|
|
5163
|
+
dir = path17.dirname(dir);
|
|
4812
5164
|
}
|
|
4813
5165
|
return process.cwd();
|
|
4814
5166
|
}
|
|
4815
5167
|
function copyDirRecursive(src, dest) {
|
|
4816
|
-
|
|
4817
|
-
for (const entry of
|
|
4818
|
-
const srcPath =
|
|
4819
|
-
const destPath =
|
|
5168
|
+
fs14.mkdirSync(dest, { recursive: true });
|
|
5169
|
+
for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
|
|
5170
|
+
const srcPath = path17.join(src, entry.name);
|
|
5171
|
+
const destPath = path17.join(dest, entry.name);
|
|
4820
5172
|
if (entry.isDirectory()) {
|
|
4821
5173
|
copyDirRecursive(srcPath, destPath);
|
|
4822
5174
|
} else {
|
|
4823
|
-
|
|
5175
|
+
fs14.copyFileSync(srcPath, destPath);
|
|
4824
5176
|
}
|
|
4825
5177
|
}
|
|
4826
5178
|
}
|
|
4827
5179
|
var initCommand = new Command2("init").description("First-time setup: scaffold hub vault, scripts, and config").option("--hub <path>", "Hub vault path", "~/squadrant-hub").action((opts) => {
|
|
4828
5180
|
const hubPath = resolveHome(opts.hub);
|
|
4829
5181
|
const pkgRoot = findPackageRoot();
|
|
4830
|
-
const configDir =
|
|
5182
|
+
const configDir = path17.join(os8.homedir(), ".config", "squadrant");
|
|
4831
5183
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
4832
5184
|
const registry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
4833
5185
|
try {
|
|
4834
|
-
if (
|
|
4835
|
-
const existing = JSON.parse(
|
|
5186
|
+
if (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
5187
|
+
const existing = JSON.parse(fs14.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
4836
5188
|
const wsName = existing.workspace ?? "obsidian";
|
|
4837
5189
|
registry.get(wsName);
|
|
4838
5190
|
}
|
|
@@ -4840,7 +5192,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
|
|
|
4840
5192
|
console.log(chalk4.red(` \u2718 ${err.message}`));
|
|
4841
5193
|
return;
|
|
4842
5194
|
}
|
|
4843
|
-
if (
|
|
5195
|
+
if (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
4844
5196
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
4845
5197
|
} else {
|
|
4846
5198
|
const config = getDefaultConfig();
|
|
@@ -4848,37 +5200,37 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
|
|
|
4848
5200
|
saveConfig(config);
|
|
4849
5201
|
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
4850
5202
|
}
|
|
4851
|
-
const hubTemplate =
|
|
4852
|
-
if (
|
|
5203
|
+
const hubTemplate = path17.join(pkgRoot, "obsidian", "hub");
|
|
5204
|
+
if (fs14.existsSync(hubPath)) {
|
|
4853
5205
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}, skipping`));
|
|
4854
|
-
} else if (
|
|
5206
|
+
} else if (fs14.existsSync(hubTemplate)) {
|
|
4855
5207
|
copyDirRecursive(hubTemplate, hubPath);
|
|
4856
5208
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
4857
5209
|
} else {
|
|
4858
|
-
|
|
5210
|
+
fs14.mkdirSync(hubPath, { recursive: true });
|
|
4859
5211
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty dir at ${hubPath}`));
|
|
4860
5212
|
}
|
|
4861
|
-
const hubDashboardSrc =
|
|
4862
|
-
const hubDashboardDest =
|
|
4863
|
-
if (
|
|
4864
|
-
|
|
5213
|
+
const hubDashboardSrc = path17.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
5214
|
+
const hubDashboardDest = path17.join(hubPath, "dashboard.md");
|
|
5215
|
+
if (fs14.existsSync(hubDashboardSrc)) {
|
|
5216
|
+
fs14.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
4865
5217
|
console.log(chalk4.green(` \u2714 Dashboard page refreshed at ${hubDashboardDest}`));
|
|
4866
5218
|
}
|
|
4867
|
-
const projectsDir =
|
|
4868
|
-
|
|
5219
|
+
const projectsDir = path17.join(hubPath, "projects");
|
|
5220
|
+
fs14.mkdirSync(projectsDir, { recursive: true });
|
|
4869
5221
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
4870
5222
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
4871
|
-
const settingsPath =
|
|
5223
|
+
const settingsPath = path17.join(os8.homedir(), ".claude", "settings.json");
|
|
4872
5224
|
try {
|
|
4873
5225
|
let settings = {};
|
|
4874
|
-
if (
|
|
4875
|
-
settings = JSON.parse(
|
|
5226
|
+
if (fs14.existsSync(settingsPath)) {
|
|
5227
|
+
settings = JSON.parse(fs14.readFileSync(settingsPath, "utf-8"));
|
|
4876
5228
|
}
|
|
4877
5229
|
const env = settings.env || {};
|
|
4878
5230
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
4879
5231
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
4880
|
-
|
|
4881
|
-
|
|
5232
|
+
fs14.mkdirSync(path17.dirname(settingsPath), { recursive: true });
|
|
5233
|
+
fs14.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4882
5234
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
4883
5235
|
} else {
|
|
4884
5236
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -4899,7 +5251,7 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
|
|
|
4899
5251
|
console.log(chalk4.cyan(` ${hubPath}`));
|
|
4900
5252
|
console.log("");
|
|
4901
5253
|
console.log(" 4. Run " + chalk4.cyan("squadrant doctor") + " to verify setup\n");
|
|
4902
|
-
if (!
|
|
5254
|
+
if (!fs14.existsSync("/Applications/cmux.app")) {
|
|
4903
5255
|
console.log(chalk4.yellow(" \u26A0 cmux not found \u2014 download from https://cmux.dev\n"));
|
|
4904
5256
|
}
|
|
4905
5257
|
});
|
|
@@ -4907,26 +5259,70 @@ var initCommand = new Command2("init").description("First-time setup: scaffold h
|
|
|
4907
5259
|
// packages/cli/src/commands/projects.ts
|
|
4908
5260
|
init_dist();
|
|
4909
5261
|
import { Command as Command3 } from "commander";
|
|
4910
|
-
import
|
|
4911
|
-
import
|
|
5262
|
+
import fs15 from "fs";
|
|
5263
|
+
import path18 from "path";
|
|
4912
5264
|
import chalk5 from "chalk";
|
|
5265
|
+
|
|
5266
|
+
// packages/cli/src/control/restart-daemon.ts
|
|
5267
|
+
init_dist2();
|
|
5268
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5269
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5270
|
+
import { homedir as homedir9 } from "os";
|
|
5271
|
+
import { join as join14 } from "path";
|
|
5272
|
+
var DEFAULT_SOCK_PATH = join14(homedir9(), ".config", "squadrant", "squadrant.sock");
|
|
5273
|
+
function defaultIsRunning() {
|
|
5274
|
+
return existsSync9(DEFAULT_SOCK_PATH);
|
|
5275
|
+
}
|
|
5276
|
+
function defaultRunKickstart() {
|
|
5277
|
+
const uid = process.getuid?.() ?? 0;
|
|
5278
|
+
const target = `gui/${uid}/${LABEL}`;
|
|
5279
|
+
if (tryAcquireDaemonLock()) {
|
|
5280
|
+
try {
|
|
5281
|
+
execFileSync6("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
5282
|
+
} finally {
|
|
5283
|
+
releaseDaemonLock();
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
function restartDaemonIfRunning(opts) {
|
|
5288
|
+
const env = opts.env ?? process.env;
|
|
5289
|
+
if (env["VITEST"] || opts.noRestart) return "skipped-opt-out";
|
|
5290
|
+
const isRunning = opts.isRunning ?? defaultIsRunning;
|
|
5291
|
+
if (!isRunning()) return "skipped-not-running";
|
|
5292
|
+
const log = opts.log ?? console.log;
|
|
5293
|
+
log(`\u21BB restarting daemon to apply ${opts.reason}\u2026`);
|
|
5294
|
+
const runKickstart = opts.runKickstart ?? defaultRunKickstart;
|
|
5295
|
+
runKickstart();
|
|
5296
|
+
return "restarted";
|
|
5297
|
+
}
|
|
5298
|
+
|
|
5299
|
+
// packages/cli/src/commands/projects.ts
|
|
5300
|
+
function restartAfterProjectsAdd(opts) {
|
|
5301
|
+
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
5302
|
+
const outcome = doRestart({ reason: "project registration", noRestart: opts.noRestart });
|
|
5303
|
+
if (outcome === "skipped-not-running") {
|
|
5304
|
+
console.log(chalk5.dim(" (daemon not running \u2014 change applies on next start)"));
|
|
5305
|
+
} else if (outcome === "skipped-opt-out") {
|
|
5306
|
+
console.log(chalk5.dim(" (run 'squadrant heal daemon' to apply)"));
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
4913
5309
|
function findPackageRoot2() {
|
|
4914
|
-
let dir =
|
|
5310
|
+
let dir = path18.dirname(new URL(import.meta.url).pathname);
|
|
4915
5311
|
while (dir !== "/") {
|
|
4916
|
-
if (
|
|
4917
|
-
dir =
|
|
5312
|
+
if (fs15.existsSync(path18.join(dir, "package.json"))) return dir;
|
|
5313
|
+
dir = path18.dirname(dir);
|
|
4918
5314
|
}
|
|
4919
5315
|
return process.cwd();
|
|
4920
5316
|
}
|
|
4921
5317
|
function copyDirRecursive2(src, dest) {
|
|
4922
|
-
|
|
4923
|
-
for (const entry of
|
|
4924
|
-
const srcPath =
|
|
4925
|
-
const destPath =
|
|
5318
|
+
fs15.mkdirSync(dest, { recursive: true });
|
|
5319
|
+
for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
|
|
5320
|
+
const srcPath = path18.join(src, entry.name);
|
|
5321
|
+
const destPath = path18.join(dest, entry.name);
|
|
4926
5322
|
if (entry.isDirectory()) {
|
|
4927
5323
|
copyDirRecursive2(srcPath, destPath);
|
|
4928
5324
|
} else {
|
|
4929
|
-
|
|
5325
|
+
fs15.copyFileSync(srcPath, destPath);
|
|
4930
5326
|
}
|
|
4931
5327
|
}
|
|
4932
5328
|
}
|
|
@@ -4953,7 +5349,7 @@ var listCmd = new Command3("list").description("List registered projects").actio
|
|
|
4953
5349
|
}
|
|
4954
5350
|
console.log("");
|
|
4955
5351
|
});
|
|
4956
|
-
var addCmd = new Command3("add").description("Register a project").argument("<name>", "Project name").argument("<path>", "Path to project directory").option("--captain <name>", "Captain workspace name (default: <project>-captain)").option("--spoke <path>", "Spoke vault path (default: ~/squadrant-hub/spokes/<name>)").option("--group <name>", "Project group name (siblings share context)").option("--group-role <role>", "Role within the group (auto-set to 'primary' if first in group)").action((name, projectPath, opts) => {
|
|
5352
|
+
var addCmd = new Command3("add").description("Register a project").argument("<name>", "Project name").argument("<path>", "Path to project directory").option("--captain <name>", "Captain workspace name (default: <project>-captain)").option("--spoke <path>", "Spoke vault path (default: ~/squadrant-hub/spokes/<name>)").option("--group <name>", "Project group name (siblings share context)").option("--group-role <role>", "Role within the group (auto-set to 'primary' if first in group)").option("--no-restart", "skip daemon restart after registration").action((name, projectPath, opts) => {
|
|
4957
5353
|
const config = loadConfig();
|
|
4958
5354
|
if (config.projects[name]) {
|
|
4959
5355
|
console.log(chalk5.yellow(`
|
|
@@ -4962,7 +5358,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
4962
5358
|
process.exit(1);
|
|
4963
5359
|
}
|
|
4964
5360
|
const resolvedPath = resolveHome(projectPath);
|
|
4965
|
-
if (!
|
|
5361
|
+
if (!fs15.existsSync(path18.join(resolvedPath, ".git"))) {
|
|
4966
5362
|
console.log(chalk5.yellow(`
|
|
4967
5363
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
4968
5364
|
`));
|
|
@@ -5015,7 +5411,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5015
5411
|
\u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
|
|
5016
5412
|
}
|
|
5017
5413
|
}
|
|
5018
|
-
const spokeVault = opts.spoke ? resolveHome(opts.spoke) :
|
|
5414
|
+
const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path18.join(config.hubVault, "spokes", name);
|
|
5019
5415
|
const project = {
|
|
5020
5416
|
path: resolvedPath,
|
|
5021
5417
|
captainName,
|
|
@@ -5028,21 +5424,22 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5028
5424
|
saveConfig(config);
|
|
5029
5425
|
console.log(chalk5.green(`
|
|
5030
5426
|
\u2714 Project '${name}' registered`));
|
|
5427
|
+
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
5031
5428
|
const pkgRoot = findPackageRoot2();
|
|
5032
|
-
const spokeTemplate =
|
|
5033
|
-
if (
|
|
5429
|
+
const spokeTemplate = path18.join(pkgRoot, "obsidian", "spoke");
|
|
5430
|
+
if (fs15.existsSync(spokeVault)) {
|
|
5034
5431
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
5035
|
-
} else if (
|
|
5432
|
+
} else if (fs15.existsSync(spokeTemplate)) {
|
|
5036
5433
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
5037
|
-
const statusPath =
|
|
5038
|
-
if (
|
|
5039
|
-
const content =
|
|
5434
|
+
const statusPath = path18.join(spokeVault, "status.md");
|
|
5435
|
+
if (fs15.existsSync(statusPath)) {
|
|
5436
|
+
const content = fs15.readFileSync(statusPath, "utf-8");
|
|
5040
5437
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
5041
|
-
|
|
5438
|
+
fs15.writeFileSync(statusPath, updated);
|
|
5042
5439
|
}
|
|
5043
5440
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
5044
5441
|
} else {
|
|
5045
|
-
|
|
5442
|
+
fs15.mkdirSync(spokeVault, { recursive: true });
|
|
5046
5443
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
5047
5444
|
}
|
|
5048
5445
|
console.log("");
|
|
@@ -5143,10 +5540,10 @@ init_dist4();
|
|
|
5143
5540
|
init_dist();
|
|
5144
5541
|
import { Command as Command5 } from "commander";
|
|
5145
5542
|
import { execSync as execSync9 } from "child_process";
|
|
5146
|
-
import
|
|
5147
|
-
import
|
|
5543
|
+
import path19 from "path";
|
|
5544
|
+
import os9 from "os";
|
|
5148
5545
|
import chalk7 from "chalk";
|
|
5149
|
-
var TEMPLATES_DIR =
|
|
5546
|
+
var TEMPLATES_DIR = path19.join(os9.homedir(), ".config", "squadrant", "templates");
|
|
5150
5547
|
var TASK_PROMPTS = {
|
|
5151
5548
|
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.",
|
|
5152
5549
|
"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.",
|
|
@@ -5179,7 +5576,7 @@ async function runCommandSpawn(input) {
|
|
|
5179
5576
|
if (!agent) {
|
|
5180
5577
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
5181
5578
|
}
|
|
5182
|
-
const promptFile =
|
|
5579
|
+
const promptFile = path19.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
|
|
5183
5580
|
const cliCommand = agent.buildCommand({
|
|
5184
5581
|
prompt,
|
|
5185
5582
|
workdir: process.cwd(),
|
|
@@ -5206,9 +5603,9 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
|
|
|
5206
5603
|
init_dist();
|
|
5207
5604
|
init_dist();
|
|
5208
5605
|
import { Command as Command9 } from "commander";
|
|
5209
|
-
import
|
|
5210
|
-
import
|
|
5211
|
-
import
|
|
5606
|
+
import fs16 from "fs";
|
|
5607
|
+
import path20 from "path";
|
|
5608
|
+
import os10 from "os";
|
|
5212
5609
|
import chalk9 from "chalk";
|
|
5213
5610
|
|
|
5214
5611
|
// packages/cli/src/control/crew-routing.ts
|
|
@@ -5241,8 +5638,8 @@ init_dist4();
|
|
|
5241
5638
|
import { Command as Command8 } from "commander";
|
|
5242
5639
|
import { createConnection as createConnection3 } from "net";
|
|
5243
5640
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5244
|
-
import { homedir as
|
|
5245
|
-
import { join as
|
|
5641
|
+
import { homedir as homedir11 } from "os";
|
|
5642
|
+
import { join as join16 } from "path";
|
|
5246
5643
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5247
5644
|
|
|
5248
5645
|
// packages/cli/src/commands/crew-output.ts
|
|
@@ -5301,11 +5698,11 @@ init_dist2();
|
|
|
5301
5698
|
import { Command as Command6 } from "commander";
|
|
5302
5699
|
import chalk8 from "chalk";
|
|
5303
5700
|
import { createConnection as createConnection2 } from "net";
|
|
5304
|
-
import { homedir as
|
|
5305
|
-
import { join as
|
|
5701
|
+
import { homedir as homedir10 } from "os";
|
|
5702
|
+
import { join as join15 } from "path";
|
|
5306
5703
|
import { createInterface } from "readline";
|
|
5307
5704
|
function socketPath() {
|
|
5308
|
-
return process.env.SQUADRANTD_SOCK ??
|
|
5705
|
+
return process.env.SQUADRANTD_SOCK ?? join15(homedir10(), ".config", "squadrant", "squadrant.sock");
|
|
5309
5706
|
}
|
|
5310
5707
|
function rule(width, ch = "\u2500") {
|
|
5311
5708
|
return ch.repeat(Math.max(0, width));
|
|
@@ -5541,7 +5938,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
5541
5938
|
});
|
|
5542
5939
|
|
|
5543
5940
|
// packages/cli/src/commands/crew-control.ts
|
|
5544
|
-
var SOCK2 =
|
|
5941
|
+
var SOCK2 = join16(homedir11(), ".config", "squadrant", "squadrant.sock");
|
|
5545
5942
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
5546
5943
|
async function sendCodexFirstTurn(taskId, text) {
|
|
5547
5944
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -5646,9 +6043,9 @@ function buildSignalRequest(signal, o) {
|
|
|
5646
6043
|
return { kind: "event", project, event };
|
|
5647
6044
|
}
|
|
5648
6045
|
function defaultWriteResult(id, payload) {
|
|
5649
|
-
const dir =
|
|
6046
|
+
const dir = join16(homedir11(), ".config", "squadrant", "state", "_results");
|
|
5650
6047
|
mkdirSync6(dir, { recursive: true });
|
|
5651
|
-
const file =
|
|
6048
|
+
const file = join16(dir, `${id}.txt`);
|
|
5652
6049
|
writeFileSync7(file, payload);
|
|
5653
6050
|
return file;
|
|
5654
6051
|
}
|
|
@@ -5749,7 +6146,7 @@ addControlPlaneCrewCommands(crewControlCommand);
|
|
|
5749
6146
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
5750
6147
|
init_dist4();
|
|
5751
6148
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
5752
|
-
import { join as
|
|
6149
|
+
import { join as join17 } from "path";
|
|
5753
6150
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
5754
6151
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
5755
6152
|
"Bash(git status:*)",
|
|
@@ -5837,9 +6234,9 @@ function mergeCrewPermissions(settings) {
|
|
|
5837
6234
|
return next;
|
|
5838
6235
|
}
|
|
5839
6236
|
function writePerCrewSettingsLocal(o) {
|
|
5840
|
-
const dir =
|
|
6237
|
+
const dir = join17(o.projectCwd, ".claude");
|
|
5841
6238
|
mkdirSync7(dir, { recursive: true });
|
|
5842
|
-
const file =
|
|
6239
|
+
const file = join17(dir, "settings.local.json");
|
|
5843
6240
|
let existing = {};
|
|
5844
6241
|
try {
|
|
5845
6242
|
const raw = readFileSync9(file, "utf-8");
|
|
@@ -5852,9 +6249,9 @@ function writePerCrewSettingsLocal(o) {
|
|
|
5852
6249
|
return file;
|
|
5853
6250
|
}
|
|
5854
6251
|
function writePerCrewOpencodeConfig(o) {
|
|
5855
|
-
const dir =
|
|
6252
|
+
const dir = join17(o.stateRoot, o.project, o.taskId);
|
|
5856
6253
|
mkdirSync7(dir, { recursive: true });
|
|
5857
|
-
const file =
|
|
6254
|
+
const file = join17(dir, "opencode.json");
|
|
5858
6255
|
const config = {
|
|
5859
6256
|
permission: {
|
|
5860
6257
|
read: "allow",
|
|
@@ -5875,7 +6272,7 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
5875
6272
|
|
|
5876
6273
|
// packages/cli/src/commands/crew.ts
|
|
5877
6274
|
init_dist2();
|
|
5878
|
-
var TEMPLATES_DIR2 =
|
|
6275
|
+
var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
|
|
5879
6276
|
async function runCrewSpawn(input) {
|
|
5880
6277
|
const config = loadConfig();
|
|
5881
6278
|
const proj = config.projects[input.project];
|
|
@@ -5923,8 +6320,8 @@ async function runCrewSpawn(input) {
|
|
|
5923
6320
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
5924
6321
|
}
|
|
5925
6322
|
if (agentName === "codex") {
|
|
5926
|
-
const codexRoleFile =
|
|
5927
|
-
const roleInstructions =
|
|
6323
|
+
const codexRoleFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
|
|
6324
|
+
const roleInstructions = fs16.existsSync(codexRoleFile) ? fs16.readFileSync(codexRoleFile, "utf8") : void 0;
|
|
5928
6325
|
return runCodexInteractiveSpawn({
|
|
5929
6326
|
project: input.project,
|
|
5930
6327
|
task: input.task,
|
|
@@ -5937,7 +6334,7 @@ async function runCrewSpawn(input) {
|
|
|
5937
6334
|
roleInstructions
|
|
5938
6335
|
});
|
|
5939
6336
|
}
|
|
5940
|
-
const promptFile =
|
|
6337
|
+
const promptFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
|
|
5941
6338
|
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
5942
6339
|
const crewRole = config.defaults.roles?.crew;
|
|
5943
6340
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
@@ -5994,7 +6391,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
|
5994
6391
|
});
|
|
5995
6392
|
const rec = await squadrantdCall(req);
|
|
5996
6393
|
const opencodeConfigPath = writePerCrewOpencodeConfig({
|
|
5997
|
-
stateRoot:
|
|
6394
|
+
stateRoot: path20.join(os10.homedir(), ".config", "squadrant", "state"),
|
|
5998
6395
|
project: input.project,
|
|
5999
6396
|
taskId: rec.id,
|
|
6000
6397
|
// CP3 opt-in: --approval gates bash so the captain approves shell commands.
|
|
@@ -6234,11 +6631,11 @@ init_dist3();
|
|
|
6234
6631
|
init_dist();
|
|
6235
6632
|
init_dist();
|
|
6236
6633
|
import { Command as Command10 } from "commander";
|
|
6237
|
-
import
|
|
6238
|
-
import
|
|
6239
|
-
import
|
|
6634
|
+
import fs17 from "fs";
|
|
6635
|
+
import path21 from "path";
|
|
6636
|
+
import os11 from "os";
|
|
6240
6637
|
import chalk10 from "chalk";
|
|
6241
|
-
var TEMPLATES_DIR3 =
|
|
6638
|
+
var TEMPLATES_DIR3 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
|
|
6242
6639
|
var SIDE_ROLES = ["research", "debug"];
|
|
6243
6640
|
function shellQuote2(p) {
|
|
6244
6641
|
return "'" + p.replace(/'/g, "'\\''") + "'";
|
|
@@ -6329,7 +6726,7 @@ async function runSideSpawn(input) {
|
|
|
6329
6726
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
6330
6727
|
}
|
|
6331
6728
|
const sideModel = sideRole?.model;
|
|
6332
|
-
const promptFile =
|
|
6729
|
+
const promptFile = path21.join(
|
|
6333
6730
|
TEMPLATES_DIR3,
|
|
6334
6731
|
`side.${input.role}.${agent.templateSuffix}.md`
|
|
6335
6732
|
);
|
|
@@ -6340,7 +6737,7 @@ async function runSideSpawn(input) {
|
|
|
6340
6737
|
prompt: input.topic,
|
|
6341
6738
|
workdir: spawnCwd,
|
|
6342
6739
|
role: "side",
|
|
6343
|
-
promptFile:
|
|
6740
|
+
promptFile: fs17.existsSync(promptFile) ? promptFile : void 0,
|
|
6344
6741
|
interactive: true,
|
|
6345
6742
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
6346
6743
|
...sideModel ? { model: sideModel } : {}
|
|
@@ -6397,7 +6794,7 @@ async function runSideClose(project, name) {
|
|
|
6397
6794
|
project,
|
|
6398
6795
|
name
|
|
6399
6796
|
);
|
|
6400
|
-
if (
|
|
6797
|
+
if (fs17.existsSync(wtPath)) {
|
|
6401
6798
|
try {
|
|
6402
6799
|
removeWorktree(proj.path, wtPath);
|
|
6403
6800
|
} catch (e) {
|
|
@@ -6485,8 +6882,8 @@ init_dist();
|
|
|
6485
6882
|
init_dist3();
|
|
6486
6883
|
import { Command as Command11 } from "commander";
|
|
6487
6884
|
import { execSync as execSync10 } from "child_process";
|
|
6488
|
-
import { homedir as
|
|
6489
|
-
import { join as
|
|
6885
|
+
import { homedir as homedir13 } from "os";
|
|
6886
|
+
import { join as join19 } from "path";
|
|
6490
6887
|
import chalk12 from "chalk";
|
|
6491
6888
|
|
|
6492
6889
|
// packages/web/dist/read-status.js
|
|
@@ -6625,8 +7022,8 @@ function renderDashboard(rows, opts) {
|
|
|
6625
7022
|
|
|
6626
7023
|
// packages/web/dist/sync-hub.js
|
|
6627
7024
|
init_dist();
|
|
6628
|
-
import
|
|
6629
|
-
import
|
|
7025
|
+
import fs18 from "fs";
|
|
7026
|
+
import path22 from "path";
|
|
6630
7027
|
function buildMirrorMarkdown(s) {
|
|
6631
7028
|
const fenced = "```";
|
|
6632
7029
|
return [
|
|
@@ -6652,15 +7049,15 @@ function buildMirrorMarkdown(s) {
|
|
|
6652
7049
|
function syncHub(deps) {
|
|
6653
7050
|
if (!deps.config.hubVault)
|
|
6654
7051
|
return [];
|
|
6655
|
-
const writeFile5 = deps.writeFile ?? ((p, c) =>
|
|
6656
|
-
const mkdir5 = deps.mkdir ?? ((p) =>
|
|
6657
|
-
const projectsDir =
|
|
7052
|
+
const writeFile5 = deps.writeFile ?? ((p, c) => fs18.writeFileSync(p, c));
|
|
7053
|
+
const mkdir5 = deps.mkdir ?? ((p) => fs18.mkdirSync(p, { recursive: true }));
|
|
7054
|
+
const projectsDir = path22.join(resolveHome(deps.config.hubVault), "projects");
|
|
6658
7055
|
mkdir5(projectsDir);
|
|
6659
7056
|
const out = [];
|
|
6660
7057
|
for (const s of deps.statuses) {
|
|
6661
7058
|
if (s.state === "unknown")
|
|
6662
7059
|
continue;
|
|
6663
|
-
const hubPath =
|
|
7060
|
+
const hubPath = path22.join(projectsDir, `${s.project}.md`);
|
|
6664
7061
|
try {
|
|
6665
7062
|
writeFile5(hubPath, buildMirrorMarkdown(s));
|
|
6666
7063
|
out.push({ project: s.project, hubPath });
|
|
@@ -6682,9 +7079,9 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
6682
7079
|
// packages/web/dist/probes.js
|
|
6683
7080
|
init_dist();
|
|
6684
7081
|
init_dist();
|
|
6685
|
-
import { join as
|
|
6686
|
-
import { homedir as
|
|
6687
|
-
import { existsSync as
|
|
7082
|
+
import { join as join18 } from "path";
|
|
7083
|
+
import { homedir as homedir12 } from "os";
|
|
7084
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
6688
7085
|
import { execFile as execFile2 } from "child_process";
|
|
6689
7086
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
6690
7087
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
@@ -6720,7 +7117,7 @@ function vaultProbe(run, dir) {
|
|
|
6720
7117
|
return { state: "unknown", detail: "no vault configured" };
|
|
6721
7118
|
if (!run.pathExists(dir))
|
|
6722
7119
|
return { state: "gone", detail: "vault directory missing" };
|
|
6723
|
-
if (!run.pathExists(
|
|
7120
|
+
if (!run.pathExists(join18(dir, ".obsidian")))
|
|
6724
7121
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
6725
7122
|
return { state: "alive" };
|
|
6726
7123
|
} catch {
|
|
@@ -6788,10 +7185,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
6788
7185
|
const sessions = probeSessions(run);
|
|
6789
7186
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
6790
7187
|
}
|
|
6791
|
-
var SESSIONS_PATH =
|
|
7188
|
+
var SESSIONS_PATH = join18(homedir12(), ".config", "squadrant", "sessions.json");
|
|
6792
7189
|
function onPath(cli) {
|
|
6793
7190
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
6794
|
-
return dirs.some((d) =>
|
|
7191
|
+
return dirs.some((d) => existsSync10(join18(d, cli)));
|
|
6795
7192
|
}
|
|
6796
7193
|
function readSessionsHashes() {
|
|
6797
7194
|
const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
|
|
@@ -6808,7 +7205,7 @@ function defaultProbeRunners() {
|
|
|
6808
7205
|
}
|
|
6809
7206
|
}),
|
|
6810
7207
|
probeOnPath: async (cli) => onPath(cli),
|
|
6811
|
-
pathExists: (p) =>
|
|
7208
|
+
pathExists: (p) => existsSync10(p),
|
|
6812
7209
|
loadConfig: () => loadConfig(),
|
|
6813
7210
|
loadSessionsHashes: () => readSessionsHashes()
|
|
6814
7211
|
};
|
|
@@ -7461,7 +7858,7 @@ async function startWebServer(opts) {
|
|
|
7461
7858
|
|
|
7462
7859
|
// packages/cli/src/commands/dashboard.ts
|
|
7463
7860
|
init_dist();
|
|
7464
|
-
var SOCK3 =
|
|
7861
|
+
var SOCK3 = join19(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
7465
7862
|
function detectCurrentWorkspace2() {
|
|
7466
7863
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
7467
7864
|
const match = out.match(/workspace:\d+/);
|
|
@@ -7549,13 +7946,13 @@ init_dist3();
|
|
|
7549
7946
|
init_dist2();
|
|
7550
7947
|
import { Command as Command12 } from "commander";
|
|
7551
7948
|
import { execSync as execSync11 } from "child_process";
|
|
7552
|
-
import
|
|
7553
|
-
import
|
|
7554
|
-
import
|
|
7949
|
+
import fs19 from "fs";
|
|
7950
|
+
import path23 from "path";
|
|
7951
|
+
import os12 from "os";
|
|
7555
7952
|
import chalk13 from "chalk";
|
|
7556
7953
|
var CMUX_APP = "/Applications/cmux.app";
|
|
7557
|
-
var TEMPLATES_DIR4 =
|
|
7558
|
-
var SESSIONS_PATH2 =
|
|
7954
|
+
var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
7955
|
+
var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
|
|
7559
7956
|
function ensureCmuxReady() {
|
|
7560
7957
|
if (isInsideCmux()) return;
|
|
7561
7958
|
console.log(chalk13.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
@@ -7662,12 +8059,12 @@ var launchCommand = new Command12("launch").description(
|
|
|
7662
8059
|
}
|
|
7663
8060
|
if (opts.all) {
|
|
7664
8061
|
const hubPath = resolveHome(config.hubVault);
|
|
7665
|
-
|
|
8062
|
+
fs19.mkdirSync(hubPath, { recursive: true });
|
|
7666
8063
|
console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
|
|
7667
8064
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
7668
8065
|
const projPath = resolveHome(proj.path);
|
|
7669
8066
|
const spokePath = resolveHome(proj.spokeVault);
|
|
7670
|
-
if (!
|
|
8067
|
+
if (!fs19.existsSync(spokePath)) {
|
|
7671
8068
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
7672
8069
|
await ensureSpokeLayout(spokeDriver);
|
|
7673
8070
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -7698,7 +8095,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
7698
8095
|
const proj = config.projects[project];
|
|
7699
8096
|
const projPath = resolveHome(proj.path);
|
|
7700
8097
|
const spokePath = resolveHome(proj.spokeVault);
|
|
7701
|
-
if (!
|
|
8098
|
+
if (!fs19.existsSync(spokePath)) {
|
|
7702
8099
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
7703
8100
|
await ensureSpokeLayout(spokeDriver);
|
|
7704
8101
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -7833,24 +8230,24 @@ Shutting down captain workspace for '${project}'...
|
|
|
7833
8230
|
// packages/cli/src/commands/feedback.ts
|
|
7834
8231
|
init_dist();
|
|
7835
8232
|
import { Command as Command14 } from "commander";
|
|
7836
|
-
import
|
|
7837
|
-
import
|
|
7838
|
-
import
|
|
8233
|
+
import fs20 from "fs";
|
|
8234
|
+
import os13 from "os";
|
|
8235
|
+
import path24 from "path";
|
|
7839
8236
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7840
8237
|
import { execSync as execSync12 } from "child_process";
|
|
7841
8238
|
import chalk15 from "chalk";
|
|
7842
8239
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
7843
8240
|
function readPkgVersion() {
|
|
7844
8241
|
try {
|
|
7845
|
-
const pkgPath =
|
|
7846
|
-
return JSON.parse(
|
|
8242
|
+
const pkgPath = path24.join(path24.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8243
|
+
return JSON.parse(fs20.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
7847
8244
|
} catch {
|
|
7848
8245
|
return "unknown";
|
|
7849
8246
|
}
|
|
7850
8247
|
}
|
|
7851
8248
|
function readMetrics(metricsPath) {
|
|
7852
8249
|
try {
|
|
7853
|
-
return JSON.parse(
|
|
8250
|
+
return JSON.parse(fs20.readFileSync(metricsPath, "utf-8"));
|
|
7854
8251
|
} catch {
|
|
7855
8252
|
return {};
|
|
7856
8253
|
}
|
|
@@ -7888,7 +8285,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
|
|
|
7888
8285
|
}
|
|
7889
8286
|
var feedbackCommand = new Command14("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
|
|
7890
8287
|
const config = loadConfig();
|
|
7891
|
-
const metricsPath = config.metrics?.path ||
|
|
8288
|
+
const metricsPath = config.metrics?.path || path24.join(os13.homedir(), ".config", "squadrant", "metrics.json");
|
|
7892
8289
|
const metrics = readMetrics(metricsPath);
|
|
7893
8290
|
const version = readStamp(config) ?? readPkgVersion();
|
|
7894
8291
|
const issueUrl = buildIssueUrl(metrics, version);
|
|
@@ -7910,8 +8307,8 @@ init_dist();
|
|
|
7910
8307
|
init_dist();
|
|
7911
8308
|
init_dist3();
|
|
7912
8309
|
import { Command as Command15 } from "commander";
|
|
7913
|
-
import
|
|
7914
|
-
import
|
|
8310
|
+
import fs21 from "fs";
|
|
8311
|
+
import path25 from "path";
|
|
7915
8312
|
import chalk16 from "chalk";
|
|
7916
8313
|
import matter3 from "gray-matter";
|
|
7917
8314
|
function getDateStr(yesterday) {
|
|
@@ -7920,11 +8317,11 @@ function getDateStr(yesterday) {
|
|
|
7920
8317
|
async function getProjectStandup(name, project, dateStr, registry, config) {
|
|
7921
8318
|
const workspace = registry.forProject(name, config);
|
|
7922
8319
|
const spokeVault = resolveHome(project.spokeVault);
|
|
7923
|
-
const statusFile =
|
|
8320
|
+
const statusFile = path25.join(spokeVault, "status.md");
|
|
7924
8321
|
let status = {};
|
|
7925
|
-
if (
|
|
8322
|
+
if (fs21.existsSync(statusFile)) {
|
|
7926
8323
|
try {
|
|
7927
|
-
status = matter3(
|
|
8324
|
+
status = matter3(fs21.readFileSync(statusFile, "utf-8")).data;
|
|
7928
8325
|
} catch {
|
|
7929
8326
|
}
|
|
7930
8327
|
}
|
|
@@ -8042,15 +8439,15 @@ init_dist();
|
|
|
8042
8439
|
init_dist();
|
|
8043
8440
|
init_dist3();
|
|
8044
8441
|
import { Command as Command16 } from "commander";
|
|
8045
|
-
import
|
|
8046
|
-
import
|
|
8442
|
+
import fs22 from "fs";
|
|
8443
|
+
import path26 from "path";
|
|
8047
8444
|
import chalk17 from "chalk";
|
|
8048
8445
|
import matter4 from "gray-matter";
|
|
8049
8446
|
function readStatus(spokeVault) {
|
|
8050
|
-
const statusFile =
|
|
8051
|
-
if (!
|
|
8447
|
+
const statusFile = path26.join(spokeVault, "status.md");
|
|
8448
|
+
if (!fs22.existsSync(statusFile)) return {};
|
|
8052
8449
|
try {
|
|
8053
|
-
return matter4(
|
|
8450
|
+
return matter4(fs22.readFileSync(statusFile, "utf-8")).data;
|
|
8054
8451
|
} catch {
|
|
8055
8452
|
return {};
|
|
8056
8453
|
}
|
|
@@ -8360,9 +8757,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
|
|
|
8360
8757
|
const config = loadConfig();
|
|
8361
8758
|
const registry = buildRegistry2();
|
|
8362
8759
|
try {
|
|
8363
|
-
const { projectTarget, path:
|
|
8760
|
+
const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
8364
8761
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
8365
|
-
const content = await driver.read(
|
|
8762
|
+
const content = await driver.read(path29);
|
|
8366
8763
|
process.stdout.write(content);
|
|
8367
8764
|
} catch (err) {
|
|
8368
8765
|
console.error(chalk19.red(err.message));
|
|
@@ -8374,26 +8771,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
|
|
|
8374
8771
|
const registry = buildRegistry2();
|
|
8375
8772
|
try {
|
|
8376
8773
|
let projectTarget;
|
|
8377
|
-
let
|
|
8774
|
+
let path29;
|
|
8378
8775
|
let rawContent;
|
|
8379
8776
|
if (opts.hub) {
|
|
8380
8777
|
if (arg3 !== void 0) {
|
|
8381
8778
|
throw new Error("With --hub, pass only the path and content");
|
|
8382
8779
|
}
|
|
8383
8780
|
projectTarget = void 0;
|
|
8384
|
-
|
|
8781
|
+
path29 = arg1;
|
|
8385
8782
|
rawContent = arg2;
|
|
8386
8783
|
} else {
|
|
8387
8784
|
if (arg3 === void 0) {
|
|
8388
8785
|
throw new Error("Missing content \u2014 usage: <project> <path> <content>");
|
|
8389
8786
|
}
|
|
8390
8787
|
projectTarget = arg1;
|
|
8391
|
-
|
|
8788
|
+
path29 = arg2;
|
|
8392
8789
|
rawContent = arg3;
|
|
8393
8790
|
}
|
|
8394
8791
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
8395
8792
|
const payload = rawContent === "-" ? await readStdin() : rawContent;
|
|
8396
|
-
await driver.write(
|
|
8793
|
+
await driver.write(path29, payload);
|
|
8397
8794
|
} catch (err) {
|
|
8398
8795
|
console.error(chalk19.red(err.message));
|
|
8399
8796
|
process.exit(1);
|
|
@@ -8403,9 +8800,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
|
|
|
8403
8800
|
const config = loadConfig();
|
|
8404
8801
|
const registry = buildRegistry2();
|
|
8405
8802
|
try {
|
|
8406
|
-
const { projectTarget, path:
|
|
8803
|
+
const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
8407
8804
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
8408
|
-
const entries = await driver.list(
|
|
8805
|
+
const entries = await driver.list(path29);
|
|
8409
8806
|
for (const entry of entries) console.log(entry);
|
|
8410
8807
|
} catch (err) {
|
|
8411
8808
|
console.error(chalk19.red(err.message));
|
|
@@ -8416,9 +8813,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
|
|
|
8416
8813
|
const config = loadConfig();
|
|
8417
8814
|
const registry = buildRegistry2();
|
|
8418
8815
|
try {
|
|
8419
|
-
const { projectTarget, path:
|
|
8816
|
+
const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
8420
8817
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
8421
|
-
const ok = await driver.exists(
|
|
8818
|
+
const ok = await driver.exists(path29);
|
|
8422
8819
|
process.exit(ok ? 0 : 1);
|
|
8423
8820
|
} catch (err) {
|
|
8424
8821
|
console.error(chalk19.red(err.message));
|
|
@@ -8429,9 +8826,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
|
|
|
8429
8826
|
const config = loadConfig();
|
|
8430
8827
|
const registry = buildRegistry2();
|
|
8431
8828
|
try {
|
|
8432
|
-
const { projectTarget, path:
|
|
8829
|
+
const { projectTarget, path: path29 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
|
|
8433
8830
|
const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
|
|
8434
|
-
await driver.mkdir(
|
|
8831
|
+
await driver.mkdir(path29);
|
|
8435
8832
|
} catch (err) {
|
|
8436
8833
|
console.error(chalk19.red(err.message));
|
|
8437
8834
|
process.exit(1);
|
|
@@ -8470,8 +8867,8 @@ init_dist3();
|
|
|
8470
8867
|
init_dist();
|
|
8471
8868
|
import { Command as Command20 } from "commander";
|
|
8472
8869
|
import chalk21 from "chalk";
|
|
8473
|
-
import
|
|
8474
|
-
import
|
|
8870
|
+
import fs23 from "fs";
|
|
8871
|
+
import path27 from "path";
|
|
8475
8872
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8476
8873
|
function parseScope(v) {
|
|
8477
8874
|
if (v !== "user" && v !== "project") {
|
|
@@ -8480,10 +8877,10 @@ function parseScope(v) {
|
|
|
8480
8877
|
return v;
|
|
8481
8878
|
}
|
|
8482
8879
|
function findPackageRoot3() {
|
|
8483
|
-
let dir =
|
|
8880
|
+
let dir = path27.dirname(fileURLToPath4(import.meta.url));
|
|
8484
8881
|
while (dir !== "/" && dir !== "") {
|
|
8485
|
-
if (
|
|
8486
|
-
dir =
|
|
8882
|
+
if (fs23.existsSync(path27.join(dir, "package.json"))) return dir;
|
|
8883
|
+
dir = path27.dirname(dir);
|
|
8487
8884
|
}
|
|
8488
8885
|
return process.cwd();
|
|
8489
8886
|
}
|
|
@@ -8677,12 +9074,12 @@ init_dist();
|
|
|
8677
9074
|
init_dist();
|
|
8678
9075
|
init_dist();
|
|
8679
9076
|
import { Command as Command22 } from "commander";
|
|
8680
|
-
import
|
|
9077
|
+
import fs24 from "fs";
|
|
8681
9078
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
8682
|
-
import { dirname as dirname5, join as
|
|
9079
|
+
import { dirname as dirname5, join as join20 } from "path";
|
|
8683
9080
|
import chalk22 from "chalk";
|
|
8684
9081
|
function runConfigCheck(opts) {
|
|
8685
|
-
const raw = JSON.parse(
|
|
9082
|
+
const raw = JSON.parse(fs24.readFileSync(opts.configPath, "utf-8"));
|
|
8686
9083
|
const def = getDefaultConfig();
|
|
8687
9084
|
const items = detectDrift(raw, def);
|
|
8688
9085
|
let working = raw;
|
|
@@ -8699,10 +9096,56 @@ function runConfigCheck(opts) {
|
|
|
8699
9096
|
stamped = true;
|
|
8700
9097
|
}
|
|
8701
9098
|
if (opts.fix || opts.accept || stamped) {
|
|
8702
|
-
|
|
9099
|
+
fs24.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
|
|
8703
9100
|
}
|
|
8704
9101
|
return { items, applied, remaining, stamped };
|
|
8705
9102
|
}
|
|
9103
|
+
function runConfigGet(key, configPath = DEFAULT_CONFIG_PATH) {
|
|
9104
|
+
const config = loadConfig(configPath);
|
|
9105
|
+
const parts = key.split(".");
|
|
9106
|
+
let node = config;
|
|
9107
|
+
for (const p of parts) {
|
|
9108
|
+
if (node === null || typeof node !== "object" || !(p in node)) {
|
|
9109
|
+
throw new Error(`config key not found: ${key}`);
|
|
9110
|
+
}
|
|
9111
|
+
node = node[p];
|
|
9112
|
+
}
|
|
9113
|
+
return node;
|
|
9114
|
+
}
|
|
9115
|
+
function runConfigSet(key, value, configPath = DEFAULT_CONFIG_PATH) {
|
|
9116
|
+
let parsed;
|
|
9117
|
+
try {
|
|
9118
|
+
parsed = JSON.parse(value);
|
|
9119
|
+
} catch {
|
|
9120
|
+
parsed = value;
|
|
9121
|
+
}
|
|
9122
|
+
const config = loadConfig(configPath);
|
|
9123
|
+
const parts = key.split(".");
|
|
9124
|
+
let node = config;
|
|
9125
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
9126
|
+
const p = parts[i];
|
|
9127
|
+
if (node[p] === null || typeof node[p] !== "object") node[p] = {};
|
|
9128
|
+
node = node[p];
|
|
9129
|
+
}
|
|
9130
|
+
node[parts[parts.length - 1]] = parsed;
|
|
9131
|
+
saveConfig(config, configPath);
|
|
9132
|
+
}
|
|
9133
|
+
function printRestartOutcome(outcome) {
|
|
9134
|
+
if (outcome === "skipped-not-running") {
|
|
9135
|
+
console.log(chalk22.dim("(daemon not running \u2014 change applies on next start)"));
|
|
9136
|
+
} else if (outcome === "skipped-opt-out") {
|
|
9137
|
+
console.log(chalk22.dim("(run 'squadrant heal daemon' to apply)"));
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
function runConfigSetAction(opts) {
|
|
9141
|
+
runConfigSet(opts.key, opts.value, opts.configPath);
|
|
9142
|
+
console.log(chalk22.green(`\u2714 set ${opts.key} = ${opts.value}`));
|
|
9143
|
+
if (isDaemonCachedKey(opts.key)) {
|
|
9144
|
+
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
9145
|
+
const outcome = doRestart({ reason: `config ${opts.key}`, noRestart: opts.noRestart });
|
|
9146
|
+
printRestartOutcome(outcome);
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
8706
9149
|
var SEV_COLOR = {
|
|
8707
9150
|
info: chalk22.green,
|
|
8708
9151
|
advisory: chalk22.yellow,
|
|
@@ -8724,7 +9167,7 @@ function printItems(items) {
|
|
|
8724
9167
|
var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
|
|
8725
9168
|
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) => {
|
|
8726
9169
|
const pkgVersion = readPkgVersion2();
|
|
8727
|
-
if (!
|
|
9170
|
+
if (!fs24.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
8728
9171
|
console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
|
|
8729
9172
|
return;
|
|
8730
9173
|
}
|
|
@@ -8751,16 +9194,32 @@ ${judgment.length} item(s) need review \u2014 run the config-doctor skill, or \`
|
|
|
8751
9194
|
console.log(chalk22.green("\n\u2714 Config reconciled and stamped."));
|
|
8752
9195
|
}
|
|
8753
9196
|
});
|
|
9197
|
+
configCommand.command("get").description("Read a config value by dotted key (e.g. defaults.effort)").argument("<key>", "dotted config key").action((key) => {
|
|
9198
|
+
try {
|
|
9199
|
+
const value = runConfigGet(key);
|
|
9200
|
+
console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
9201
|
+
} catch (e) {
|
|
9202
|
+
console.error(chalk22.red(e.message));
|
|
9203
|
+
process.exit(1);
|
|
9204
|
+
}
|
|
9205
|
+
});
|
|
9206
|
+
configCommand.command("set").description("Write a config value by dotted key (e.g. defaults.effort low)").argument("<key>", "dotted config key").argument("<value>", "value (JSON-parsed when possible, else a bare string)").option("--no-restart", "skip daemon restart even if the key is daemon-cached").action((key, value, opts) => {
|
|
9207
|
+
try {
|
|
9208
|
+
runConfigSetAction({ key, value, noRestart: opts.restart === false });
|
|
9209
|
+
} catch (e) {
|
|
9210
|
+
console.error(chalk22.red(e.message));
|
|
9211
|
+
process.exit(1);
|
|
9212
|
+
}
|
|
9213
|
+
});
|
|
8754
9214
|
function readPkgVersion2() {
|
|
8755
|
-
const pkgPath =
|
|
8756
|
-
return JSON.parse(
|
|
9215
|
+
const pkgPath = join20(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
9216
|
+
return JSON.parse(fs24.readFileSync(pkgPath, "utf-8")).version;
|
|
8757
9217
|
}
|
|
8758
9218
|
|
|
8759
9219
|
// packages/cli/src/commands/heal.ts
|
|
8760
9220
|
import { Command as Command23 } from "commander";
|
|
8761
9221
|
import chalk23 from "chalk";
|
|
8762
9222
|
init_dist2();
|
|
8763
|
-
init_dist2();
|
|
8764
9223
|
function buildHealStatus(components) {
|
|
8765
9224
|
if (components === null) {
|
|
8766
9225
|
return { healthy: false, daemonUnreachable: true, components: [] };
|
|
@@ -8840,7 +9299,7 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
8840
9299
|
).addCommand(
|
|
8841
9300
|
new Command23("daemon").description("Restart squadrantd via the idempotent launchd kickstart path").action(async () => {
|
|
8842
9301
|
const code = await runHealDaemon({
|
|
8843
|
-
ensureDaemon,
|
|
9302
|
+
ensureDaemon: () => restartDaemonIfRunning({ reason: "heal", isRunning: () => true }),
|
|
8844
9303
|
stdout: process.stdout,
|
|
8845
9304
|
stderr: process.stderr
|
|
8846
9305
|
});
|
|
@@ -8854,10 +9313,10 @@ init_dist2();
|
|
|
8854
9313
|
import { Command as Command24 } from "commander";
|
|
8855
9314
|
import { execSync as execSync13 } from "child_process";
|
|
8856
9315
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8857
|
-
import { homedir as
|
|
8858
|
-
import { join as
|
|
9316
|
+
import { homedir as homedir14 } from "os";
|
|
9317
|
+
import { join as join21 } from "path";
|
|
8859
9318
|
import chalk24 from "chalk";
|
|
8860
|
-
var SOCK4 =
|
|
9319
|
+
var SOCK4 = join21(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
8861
9320
|
var WARMUP_TIMEOUT_MS = 12e4;
|
|
8862
9321
|
var WARMUP_POLL_MS = 1e3;
|
|
8863
9322
|
function resolveCurrentProject(config) {
|
|
@@ -9019,8 +9478,8 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
|
|
|
9019
9478
|
|
|
9020
9479
|
// packages/cli/src/commands/effort.ts
|
|
9021
9480
|
init_dist();
|
|
9022
|
-
import
|
|
9023
|
-
import
|
|
9481
|
+
import fs25 from "fs";
|
|
9482
|
+
import path28 from "path";
|
|
9024
9483
|
import { Command as Command26 } from "commander";
|
|
9025
9484
|
import chalk26 from "chalk";
|
|
9026
9485
|
var VALID_EFFORTS = ["max", "balance", "low"];
|
|
@@ -9047,9 +9506,9 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
9047
9506
|
}
|
|
9048
9507
|
function canonical(p) {
|
|
9049
9508
|
try {
|
|
9050
|
-
return
|
|
9509
|
+
return fs25.realpathSync(p);
|
|
9051
9510
|
} catch {
|
|
9052
|
-
return
|
|
9511
|
+
return path28.resolve(p);
|
|
9053
9512
|
}
|
|
9054
9513
|
}
|
|
9055
9514
|
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd()) {
|
|
@@ -9093,20 +9552,440 @@ var effortCommand = new Command26("effort").description("Get or set the global c
|
|
|
9093
9552
|
}
|
|
9094
9553
|
});
|
|
9095
9554
|
|
|
9555
|
+
// packages/cli/src/commands/telegram.ts
|
|
9556
|
+
init_dist();
|
|
9557
|
+
init_dist2();
|
|
9558
|
+
import { join as join22, dirname as dirname6 } from "path";
|
|
9559
|
+
import { emitKeypressEvents } from "readline";
|
|
9560
|
+
import { Command as Command27 } from "commander";
|
|
9561
|
+
import chalk27 from "chalk";
|
|
9562
|
+
function defaultStateRoot() {
|
|
9563
|
+
return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
9564
|
+
}
|
|
9565
|
+
function runTelegramStatus(opts) {
|
|
9566
|
+
const tg = opts.config.telegram;
|
|
9567
|
+
const env = opts.env ?? process.env;
|
|
9568
|
+
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
9569
|
+
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
9570
|
+
const sep2 = key.indexOf("::");
|
|
9571
|
+
return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
|
|
9572
|
+
});
|
|
9573
|
+
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
9574
|
+
}
|
|
9575
|
+
async function runTelegramSend(opts) {
|
|
9576
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9577
|
+
if (topicId === void 0) {
|
|
9578
|
+
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
9579
|
+
}
|
|
9580
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
9581
|
+
return { chatId: opts.cfg.supergroupId, topicId };
|
|
9582
|
+
}
|
|
9583
|
+
function runTelegramNotifySet(opts) {
|
|
9584
|
+
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
9585
|
+
}
|
|
9586
|
+
function runTelegramNotifyPref(args) {
|
|
9587
|
+
const { project, dimension, value, root } = args;
|
|
9588
|
+
if (dimension === "crew") {
|
|
9589
|
+
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
9590
|
+
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
9591
|
+
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
9592
|
+
return { ok: true };
|
|
9593
|
+
}
|
|
9594
|
+
if (value !== "on" && value !== "off") return { ok: false, message: "cap must be on|off" };
|
|
9595
|
+
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
9596
|
+
return { ok: true };
|
|
9597
|
+
}
|
|
9598
|
+
function capAllowed(project, globalNotify, root) {
|
|
9599
|
+
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
9600
|
+
}
|
|
9601
|
+
function runTelegramNotifyStatus(opts) {
|
|
9602
|
+
const s = loadState(opts.stateRoot);
|
|
9603
|
+
const projects = /* @__PURE__ */ new Set();
|
|
9604
|
+
for (const key of Object.keys(s.topics)) {
|
|
9605
|
+
const sep2 = key.indexOf("::");
|
|
9606
|
+
projects.add(sep2 === -1 ? key : key.slice(0, sep2));
|
|
9607
|
+
}
|
|
9608
|
+
for (const p of Object.keys(s.notify)) projects.add(p);
|
|
9609
|
+
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
9610
|
+
}
|
|
9611
|
+
async function runTelegramLink(opts) {
|
|
9612
|
+
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9613
|
+
if (existing !== void 0) return { topicId: existing, created: false };
|
|
9614
|
+
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
9615
|
+
setTopic(opts.stateRoot, opts.project, topicId);
|
|
9616
|
+
return { topicId, created: true };
|
|
9617
|
+
}
|
|
9618
|
+
async function questionMasked() {
|
|
9619
|
+
return new Promise((resolve3) => {
|
|
9620
|
+
emitKeypressEvents(process.stdin);
|
|
9621
|
+
process.stdin.setRawMode(true);
|
|
9622
|
+
process.stdin.resume();
|
|
9623
|
+
let answer = "";
|
|
9624
|
+
const onKeypress = (_str, key) => {
|
|
9625
|
+
if (key.ctrl && key.name === "c") {
|
|
9626
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
9627
|
+
process.stdin.setRawMode(false);
|
|
9628
|
+
process.stdin.pause();
|
|
9629
|
+
process.stdout.write("\n");
|
|
9630
|
+
process.exit(130);
|
|
9631
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
9632
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
9633
|
+
process.stdin.setRawMode(false);
|
|
9634
|
+
process.stdin.pause();
|
|
9635
|
+
process.stdout.write("\n");
|
|
9636
|
+
resolve3(answer);
|
|
9637
|
+
} else if (key.name === "backspace") {
|
|
9638
|
+
if (answer.length > 0) {
|
|
9639
|
+
answer = answer.slice(0, -1);
|
|
9640
|
+
process.stdout.write("\b \b");
|
|
9641
|
+
}
|
|
9642
|
+
} else if (!key.ctrl && !key.meta && key.sequence) {
|
|
9643
|
+
answer += key.sequence;
|
|
9644
|
+
process.stdout.write("*");
|
|
9645
|
+
}
|
|
9646
|
+
};
|
|
9647
|
+
process.stdin.on("keypress", onKeypress);
|
|
9648
|
+
});
|
|
9649
|
+
}
|
|
9650
|
+
async function questionYesNo(prompt) {
|
|
9651
|
+
const { createInterface: createInterface2 } = await import("readline");
|
|
9652
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
9653
|
+
return new Promise((resolve3) => {
|
|
9654
|
+
rl.question(prompt, (ans) => {
|
|
9655
|
+
rl.close();
|
|
9656
|
+
process.stdin.pause();
|
|
9657
|
+
resolve3(/^y(es)?$/i.test(ans.trim()));
|
|
9658
|
+
});
|
|
9659
|
+
});
|
|
9660
|
+
}
|
|
9661
|
+
function confirmationText(project, before, after, dim) {
|
|
9662
|
+
if (dim === "active") return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
9663
|
+
if (dim === "cap") return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
9664
|
+
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
9665
|
+
}
|
|
9666
|
+
async function runNotifyConfirmation(opts) {
|
|
9667
|
+
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
9668
|
+
if (!quieter || dim === null) return false;
|
|
9669
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9670
|
+
if (topicId === void 0) return false;
|
|
9671
|
+
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
9672
|
+
try {
|
|
9673
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
9674
|
+
return true;
|
|
9675
|
+
} catch {
|
|
9676
|
+
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
9677
|
+
return false;
|
|
9678
|
+
}
|
|
9679
|
+
}
|
|
9680
|
+
function resolveSetupToken(existingToken, opts) {
|
|
9681
|
+
if (opts.resetToken || !existingToken) return "prompt";
|
|
9682
|
+
return "try-reuse";
|
|
9683
|
+
}
|
|
9684
|
+
function resolveSetupUserId(flagUserId, detectedUserId, stateRoot) {
|
|
9685
|
+
return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;
|
|
9686
|
+
}
|
|
9687
|
+
async function runRegisterCommands(opts) {
|
|
9688
|
+
await opts.client.setMyCommands(BOT_COMMANDS);
|
|
9689
|
+
}
|
|
9690
|
+
function runTelegramPostSetup(opts) {
|
|
9691
|
+
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
9692
|
+
const outcome = doRestart({ reason: "telegram config" });
|
|
9693
|
+
if (outcome === "skipped-not-running") {
|
|
9694
|
+
console.log(chalk27.dim("(daemon not running \u2014 change applies on next start)"));
|
|
9695
|
+
} else if (outcome === "skipped-opt-out") {
|
|
9696
|
+
console.log(chalk27.dim("(run 'squadrant heal daemon' to apply)"));
|
|
9697
|
+
}
|
|
9698
|
+
}
|
|
9699
|
+
var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
9700
|
+
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
9701
|
+
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|
|
9702
|
+
console.log(`token: ${tokenSet ? chalk27.green("set") : chalk27.yellow("unset")}`);
|
|
9703
|
+
console.log(`supergroup: ${supergroupId ?? chalk27.yellow("unset")}`);
|
|
9704
|
+
if (links.length === 0) {
|
|
9705
|
+
console.log("no projects linked");
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
for (const l of links) console.log(` ${l.project} (${l.scope}) \u2192 topic ${l.topicId}`);
|
|
9709
|
+
});
|
|
9710
|
+
telegramCommand.command("link").argument("<project>", "project to bind to a Telegram topic").description("Create (or reuse) a forum topic for a project and bind it").action(async (project) => {
|
|
9711
|
+
const cfg = loadConfig().telegram;
|
|
9712
|
+
if (!cfg) {
|
|
9713
|
+
console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
9714
|
+
process.exit(1);
|
|
9715
|
+
}
|
|
9716
|
+
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
9717
|
+
if (!token) {
|
|
9718
|
+
console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
9719
|
+
process.exit(1);
|
|
9720
|
+
}
|
|
9721
|
+
const client = createTelegramClient({ token });
|
|
9722
|
+
const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
|
|
9723
|
+
console.log(chalk27.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
|
|
9724
|
+
});
|
|
9725
|
+
telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
9726
|
+
if (!process.stdin.isTTY) {
|
|
9727
|
+
console.error(chalk27.red("setup requires a TTY \u2014 pipe input is not supported"));
|
|
9728
|
+
process.exit(1);
|
|
9729
|
+
}
|
|
9730
|
+
console.log();
|
|
9731
|
+
console.log(chalk27.bold("Telegram setup") + " \u2014 connect squadrant to a Telegram bot for notifications + remote control");
|
|
9732
|
+
console.log();
|
|
9733
|
+
console.log("Before you start you need:");
|
|
9734
|
+
console.log(" 1. A bot token from @BotFather (send /newbot)");
|
|
9735
|
+
console.log(" 2. A forum supergroup with the bot added as an admin (Topics enabled)");
|
|
9736
|
+
console.log(" 3. Bot privacy mode set to OFF (@BotFather \u2192 /setprivacy \u2192 Disable)");
|
|
9737
|
+
console.log();
|
|
9738
|
+
console.log(chalk27.bold("Step 1/3 \u2014 Bot token"));
|
|
9739
|
+
const existingCfg = loadConfig().telegram;
|
|
9740
|
+
const existingToken = existingCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
9741
|
+
const decision = resolveSetupToken(existingToken, { resetToken: opts.resetToken ?? false });
|
|
9742
|
+
let token;
|
|
9743
|
+
let client;
|
|
9744
|
+
let botUser;
|
|
9745
|
+
if (decision === "try-reuse") {
|
|
9746
|
+
client = createTelegramClient({ token: existingToken });
|
|
9747
|
+
try {
|
|
9748
|
+
botUser = await client.getMe();
|
|
9749
|
+
token = existingToken;
|
|
9750
|
+
console.log(chalk27.green(`Using existing bot token (@${botUser.username})`));
|
|
9751
|
+
console.log();
|
|
9752
|
+
} catch {
|
|
9753
|
+
console.log(chalk27.yellow("Existing token is invalid \u2014 please enter a new one."));
|
|
9754
|
+
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
9755
|
+
token = await questionMasked();
|
|
9756
|
+
if (!token) {
|
|
9757
|
+
console.error(chalk27.red("token required"));
|
|
9758
|
+
process.exit(1);
|
|
9759
|
+
}
|
|
9760
|
+
client = createTelegramClient({ token });
|
|
9761
|
+
try {
|
|
9762
|
+
botUser = await client.getMe();
|
|
9763
|
+
} catch (e) {
|
|
9764
|
+
console.error(chalk27.red(`token rejected: ${e.message}`));
|
|
9765
|
+
process.exit(1);
|
|
9766
|
+
}
|
|
9767
|
+
console.log(chalk27.green(`Connected as @${botUser.username}`));
|
|
9768
|
+
console.log();
|
|
9769
|
+
}
|
|
9770
|
+
} else {
|
|
9771
|
+
console.log("Paste your bot token then press Enter (input is hidden):");
|
|
9772
|
+
token = await questionMasked();
|
|
9773
|
+
if (!token) {
|
|
9774
|
+
console.error(chalk27.red("token required"));
|
|
9775
|
+
process.exit(1);
|
|
9776
|
+
}
|
|
9777
|
+
client = createTelegramClient({ token });
|
|
9778
|
+
try {
|
|
9779
|
+
botUser = await client.getMe();
|
|
9780
|
+
} catch (e) {
|
|
9781
|
+
console.error(chalk27.red(`token rejected: ${e.message}`));
|
|
9782
|
+
process.exit(1);
|
|
9783
|
+
}
|
|
9784
|
+
console.log(chalk27.green(`Connected as @${botUser.username}`));
|
|
9785
|
+
console.log();
|
|
9786
|
+
}
|
|
9787
|
+
console.log(chalk27.bold("Step 2/3 \u2014 Supergroup"));
|
|
9788
|
+
const groupDecision = resolveSetupGroup(existingCfg?.supergroupId, { redetect: opts.redetect ?? false });
|
|
9789
|
+
let supergroupId;
|
|
9790
|
+
let detectedUserId;
|
|
9791
|
+
if (groupDecision === "reuse") {
|
|
9792
|
+
supergroupId = existingCfg.supergroupId;
|
|
9793
|
+
console.log(chalk27.green(`Using existing group: ${supergroupId}`));
|
|
9794
|
+
console.log();
|
|
9795
|
+
} else {
|
|
9796
|
+
console.log("Add the bot to your forum supergroup, then send any message in it.");
|
|
9797
|
+
console.log(chalk27.dim("Waiting for a message (up to 60s)\u2026"));
|
|
9798
|
+
try {
|
|
9799
|
+
({ supergroupId, userId: detectedUserId } = await detectGroupAndUser(client, { timeoutMs: 6e4 }));
|
|
9800
|
+
} catch {
|
|
9801
|
+
console.error(chalk27.red("Timed out \u2014 no supergroup message received within 60s."));
|
|
9802
|
+
console.error(chalk27.yellow("Check: bot is an admin in the group \xB7 privacy mode is OFF \xB7 Topics enabled"));
|
|
9803
|
+
process.exit(1);
|
|
9804
|
+
}
|
|
9805
|
+
console.log(chalk27.green(`Found group: ${supergroupId}`));
|
|
9806
|
+
console.log();
|
|
9807
|
+
}
|
|
9808
|
+
console.log(chalk27.bold("Step 3/3 \u2014 Remote control + Save"));
|
|
9809
|
+
console.log(chalk27.dim("Remote control enables auto-launching captains and the General command channel"));
|
|
9810
|
+
console.log(chalk27.dim("from your phone \u2014 gated to your Telegram user-id only (fail-closed)."));
|
|
9811
|
+
const finalUserId = resolveSetupUserId(opts.userId, detectedUserId, defaultStateRoot());
|
|
9812
|
+
let users;
|
|
9813
|
+
let remoteControl;
|
|
9814
|
+
let printedRemoteControlState = false;
|
|
9815
|
+
if (finalUserId !== void 0) {
|
|
9816
|
+
const enable = await questionYesNo(
|
|
9817
|
+
`Enable remote control for your user-id ${finalUserId}? [y/N] `
|
|
9818
|
+
);
|
|
9819
|
+
if (enable) {
|
|
9820
|
+
users = [finalUserId];
|
|
9821
|
+
remoteControl = true;
|
|
9822
|
+
}
|
|
9823
|
+
} else if (groupDecision === "detect") {
|
|
9824
|
+
console.log(chalk27.yellow("Could not read your user-id from that message \u2014 skipping remote control."));
|
|
9825
|
+
console.log(chalk27.yellow("Re-run with --user-id <id> to enable, or edit telegram.users in config manually."));
|
|
9826
|
+
printedRemoteControlState = true;
|
|
9827
|
+
} else {
|
|
9828
|
+
const existingUsers = existingCfg?.users;
|
|
9829
|
+
if (existingUsers && existingUsers.length > 0) {
|
|
9830
|
+
console.log(chalk27.dim(`Remote control: already configured (user-id ${existingUsers[0]}). Use --user-id to update.`));
|
|
9831
|
+
} else {
|
|
9832
|
+
console.log(chalk27.dim("Remote control: off. Re-run with --user-id <id> to enable."));
|
|
9833
|
+
}
|
|
9834
|
+
printedRemoteControlState = true;
|
|
9835
|
+
}
|
|
9836
|
+
writeTelegramConfig(DEFAULT_CONFIG_PATH, { token, supergroupId, users, remoteControl });
|
|
9837
|
+
console.log(chalk27.green(`Wrote telegram config \u2014 token: ${maskToken(token)} group: ${supergroupId}`));
|
|
9838
|
+
if (!printedRemoteControlState) {
|
|
9839
|
+
if (remoteControl) {
|
|
9840
|
+
console.log(chalk27.green(`Remote control: ON (allowlisted user-id ${users[0]})`));
|
|
9841
|
+
} else {
|
|
9842
|
+
console.log(chalk27.dim("Remote control: off (default). Re-run with --user-id <id> to enable."));
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
try {
|
|
9846
|
+
await runRegisterCommands({ client });
|
|
9847
|
+
console.log(chalk27.dim("Registered the /command menu."));
|
|
9848
|
+
} catch (e) {
|
|
9849
|
+
console.log(chalk27.yellow(`command-menu registration skipped: ${e.message}`));
|
|
9850
|
+
}
|
|
9851
|
+
const topics = loadState(defaultStateRoot()).topics;
|
|
9852
|
+
const topicEntries = Object.entries(topics);
|
|
9853
|
+
if (topicEntries.length > 0) {
|
|
9854
|
+
const summary = topicEntries.map(([key, id]) => {
|
|
9855
|
+
const project = key.slice(0, key.indexOf("::"));
|
|
9856
|
+
return `${project}\u2192${id}`;
|
|
9857
|
+
}).join(", ");
|
|
9858
|
+
console.log(chalk27.dim(`Existing topics: ${summary} (already created \u2014 not recreated)`));
|
|
9859
|
+
} else {
|
|
9860
|
+
console.log(chalk27.dim("No project topics yet \u2014 they're created on first delivery or via: squadrant telegram link <project>"));
|
|
9861
|
+
}
|
|
9862
|
+
runTelegramPostSetup({});
|
|
9863
|
+
console.log();
|
|
9864
|
+
console.log(`Next: ${chalk27.cyan("squadrant telegram link <project>")}`);
|
|
9865
|
+
});
|
|
9866
|
+
telegramCommand.command("register-commands").description("Register (or re-register) the bot's / command menu with Telegram").action(async () => {
|
|
9867
|
+
const cfg = loadConfig().telegram;
|
|
9868
|
+
if (!cfg) {
|
|
9869
|
+
console.error(chalk27.red("telegram config absent \u2014 run: squadrant telegram setup"));
|
|
9870
|
+
process.exit(1);
|
|
9871
|
+
}
|
|
9872
|
+
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
9873
|
+
if (!token) {
|
|
9874
|
+
console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
9875
|
+
process.exit(1);
|
|
9876
|
+
}
|
|
9877
|
+
const client = createTelegramClient({ token });
|
|
9878
|
+
await runRegisterCommands({ client });
|
|
9879
|
+
console.log(chalk27.green(`registered ${BOT_COMMANDS.length} bot commands`));
|
|
9880
|
+
});
|
|
9881
|
+
telegramCommand.command("notify").argument("[project]", "project to toggle").argument("[state]", "on | off | crew | cap").argument("[value]", "tier for crew (all|alert_only|done_only|none) or on|off for cap").option("--status", "list notification state for all projects").description("Live on|off (state), or crew <tier> / cap <on|off> preference (per-project config)").action(async (project, state, value, opts) => {
|
|
9882
|
+
const stateRoot = defaultStateRoot();
|
|
9883
|
+
if (opts.status || !project) {
|
|
9884
|
+
const rows = runTelegramNotifyStatus({ stateRoot });
|
|
9885
|
+
if (rows.length === 0) {
|
|
9886
|
+
console.log("no projects linked");
|
|
9887
|
+
return;
|
|
9888
|
+
}
|
|
9889
|
+
for (const r of rows) {
|
|
9890
|
+
console.log(` ${r.project}: ${r.active ? chalk27.green("on") : chalk27.dim("off (muted)")}`);
|
|
9891
|
+
}
|
|
9892
|
+
return;
|
|
9893
|
+
}
|
|
9894
|
+
const tgCfg = loadConfig().telegram;
|
|
9895
|
+
const globalNotify = tgCfg?.notify;
|
|
9896
|
+
const token = tgCfg?.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
9897
|
+
if (state === "crew" || state === "cap") {
|
|
9898
|
+
if (value === void 0) {
|
|
9899
|
+
console.error(chalk27.red(`usage: squadrant telegram notify <project> ${state} <value>`));
|
|
9900
|
+
process.exit(1);
|
|
9901
|
+
}
|
|
9902
|
+
const resolved2 = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
9903
|
+
const before2 = { ...resolved2, active: isNotifyActive(stateRoot, project) };
|
|
9904
|
+
const res = runTelegramNotifyPref({ project, dimension: state, value });
|
|
9905
|
+
if (!res.ok) {
|
|
9906
|
+
console.error(chalk27.red(res.message));
|
|
9907
|
+
process.exit(1);
|
|
9908
|
+
}
|
|
9909
|
+
console.log(chalk27.green(`${project} ${state} = ${value}`));
|
|
9910
|
+
const after2 = state === "crew" ? { ...before2, crew: value } : { ...before2, cap: value === "on" };
|
|
9911
|
+
if (tgCfg && token) {
|
|
9912
|
+
const client = createTelegramClient({ token });
|
|
9913
|
+
const sent = await runNotifyConfirmation({ project, before: before2, after: after2, cfg: tgCfg, client, stateRoot });
|
|
9914
|
+
if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
|
|
9915
|
+
}
|
|
9916
|
+
return;
|
|
9917
|
+
}
|
|
9918
|
+
if (state !== "on" && state !== "off") {
|
|
9919
|
+
console.error(chalk27.red("usage: squadrant telegram notify <project> <on|off|crew <tier>|cap <on|off>>"));
|
|
9920
|
+
process.exit(1);
|
|
9921
|
+
}
|
|
9922
|
+
const resolved = resolveNotify(globalNotify, loadProjectOverride(project));
|
|
9923
|
+
const before = { ...resolved, active: isNotifyActive(stateRoot, project) };
|
|
9924
|
+
const after = { ...before, active: state === "on" };
|
|
9925
|
+
runTelegramNotifySet({ project, active: state === "on", stateRoot });
|
|
9926
|
+
console.log(chalk27.green(`${project} notifications ${state === "on" ? "ON" : "OFF"}`));
|
|
9927
|
+
if (tgCfg && token) {
|
|
9928
|
+
const client = createTelegramClient({ token });
|
|
9929
|
+
const sent = await runNotifyConfirmation({ project, before, after, cfg: tgCfg, client, stateRoot });
|
|
9930
|
+
if (sent) console.log(chalk27.dim(`\u2192 notified ${project} topic`));
|
|
9931
|
+
}
|
|
9932
|
+
});
|
|
9933
|
+
telegramCommand.command("send").argument("<project>", "project whose topic receives the message").argument("[message...]", "message text (omit to read from stdin)").description("Send a message to a project's linked Telegram topic").action(async (project, messageParts) => {
|
|
9934
|
+
const cfg = loadConfig().telegram;
|
|
9935
|
+
if (!cfg) {
|
|
9936
|
+
console.error(chalk27.red("telegram config absent \u2014 add a `telegram` block to ~/.config/squadrant/config.json"));
|
|
9937
|
+
process.exit(1);
|
|
9938
|
+
}
|
|
9939
|
+
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
9940
|
+
if (!token) {
|
|
9941
|
+
console.error(chalk27.red("no botToken in config and TELEGRAM_BOT_TOKEN is unset"));
|
|
9942
|
+
process.exit(1);
|
|
9943
|
+
}
|
|
9944
|
+
if (!capAllowed(project, cfg.notify)) {
|
|
9945
|
+
console.log(chalk27.dim(`${project}: captain messages muted (cap=off) \u2014 not sent`));
|
|
9946
|
+
return;
|
|
9947
|
+
}
|
|
9948
|
+
let message;
|
|
9949
|
+
if (messageParts.length > 0) {
|
|
9950
|
+
message = messageParts.join(" ");
|
|
9951
|
+
} else if (!process.stdin.isTTY) {
|
|
9952
|
+
const { createInterface: createInterface2 } = await import("readline");
|
|
9953
|
+
const lines = [];
|
|
9954
|
+
const rl = createInterface2({ input: process.stdin });
|
|
9955
|
+
for await (const line of rl) lines.push(line);
|
|
9956
|
+
message = lines.join("\n").trimEnd();
|
|
9957
|
+
if (!message) {
|
|
9958
|
+
console.error(chalk27.red("no message provided (stdin was empty)"));
|
|
9959
|
+
process.exit(1);
|
|
9960
|
+
}
|
|
9961
|
+
} else {
|
|
9962
|
+
console.error(chalk27.red("message required \u2014 pass as argument or pipe via stdin"));
|
|
9963
|
+
process.exit(1);
|
|
9964
|
+
}
|
|
9965
|
+
const client = createTelegramClient({ token });
|
|
9966
|
+
try {
|
|
9967
|
+
const { chatId, topicId } = await runTelegramSend({ project, message, cfg, client, stateRoot: defaultStateRoot() });
|
|
9968
|
+
console.log(chalk27.green(`sent to group ${chatId} topic ${topicId}`));
|
|
9969
|
+
} catch (e) {
|
|
9970
|
+
console.error(chalk27.red(e.message));
|
|
9971
|
+
process.exit(1);
|
|
9972
|
+
}
|
|
9973
|
+
});
|
|
9974
|
+
|
|
9096
9975
|
// packages/cli/src/index.ts
|
|
9097
9976
|
init_dist();
|
|
9098
9977
|
init_dist();
|
|
9099
9978
|
init_dist();
|
|
9100
|
-
var __dirname =
|
|
9101
|
-
var pkg = JSON.parse(readFileSync11(
|
|
9979
|
+
var __dirname = dirname7(fileURLToPath6(import.meta.url));
|
|
9980
|
+
var pkg = JSON.parse(readFileSync11(join23(__dirname, "..", "package.json"), "utf-8"));
|
|
9102
9981
|
ensureRuntimeSynced({
|
|
9103
|
-
sourceRoot:
|
|
9104
|
-
runtimeRoot:
|
|
9982
|
+
sourceRoot: join23(__dirname, ".."),
|
|
9983
|
+
runtimeRoot: join23(homedir15(), ".config", "squadrant")
|
|
9105
9984
|
});
|
|
9106
9985
|
if (process.argv[2] !== "config") {
|
|
9107
9986
|
try {
|
|
9108
|
-
const cfgPath =
|
|
9109
|
-
if (
|
|
9987
|
+
const cfgPath = join23(homedir15(), ".config", "squadrant", "config.json");
|
|
9988
|
+
if (existsSync11(cfgPath)) {
|
|
9110
9989
|
const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
|
|
9111
9990
|
if (needsCheck(cfg, pkg.version)) {
|
|
9112
9991
|
const items = detectDrift(cfg, getDefaultConfig());
|
|
@@ -9130,7 +10009,7 @@ if (process.argv[2] !== "config") {
|
|
|
9130
10009
|
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
9131
10010
|
ensureDaemon();
|
|
9132
10011
|
}
|
|
9133
|
-
var program = new
|
|
10012
|
+
var program = new Command28();
|
|
9134
10013
|
program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
|
|
9135
10014
|
program.addCommand(doctorCommand);
|
|
9136
10015
|
program.addCommand(initCommand);
|
|
@@ -9156,6 +10035,7 @@ program.addCommand(healCommand);
|
|
|
9156
10035
|
program.addCommand(groupCommand);
|
|
9157
10036
|
program.addCommand(cmuxCommand);
|
|
9158
10037
|
program.addCommand(effortCommand);
|
|
10038
|
+
program.addCommand(telegramCommand);
|
|
9159
10039
|
program.parseAsync().catch((e) => {
|
|
9160
10040
|
process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
|
|
9161
10041
|
`);
|