@taskless-app/swarm 0.4.9 → 0.5.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 +98 -16
- package/dist/cli.js +1762 -616
- package/dist/overmind.js +1022 -243
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,41 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/cliIo.ts
|
|
13
|
+
import { createInterface } from "readline/promises";
|
|
14
|
+
import { execa } from "execa";
|
|
15
|
+
async function askTty(question) {
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
try {
|
|
18
|
+
return await rl.question(question);
|
|
19
|
+
} finally {
|
|
20
|
+
rl.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function askYesNo(question) {
|
|
24
|
+
return YES.includes((await askTty(`${question} [y/N]: `)).trim().toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
async function runQuiet(command, opts = {}) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await execa(command, { shell: true, reject: false, timeout: opts.timeoutMs ?? PROBE_TIMEOUT_MS });
|
|
29
|
+
return { exitCode: res.exitCode ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { exitCode: 1, stdout: "", stderr: e instanceof Error ? e.message : String(e) };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function execLoud(command) {
|
|
35
|
+
const res = await execa(command, { shell: true, reject: false, stdio: "inherit" });
|
|
36
|
+
return res.exitCode ?? 1;
|
|
37
|
+
}
|
|
38
|
+
var YES, PROBE_TIMEOUT_MS;
|
|
39
|
+
var init_cliIo = __esm({
|
|
40
|
+
"src/cliIo.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
YES = ["y", "yes", "\u0434", "\u0434\u0430"];
|
|
43
|
+
PROBE_TIMEOUT_MS = 1e4;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
12
47
|
// ../swarm-core/src/types.ts
|
|
13
48
|
function isMilestoneKind(kind) {
|
|
14
49
|
return MILESTONE_KIND_SET.has(kind);
|
|
@@ -361,9 +396,14 @@ var init_windows = __esm({
|
|
|
361
396
|
});
|
|
362
397
|
|
|
363
398
|
// ../swarm-core/src/slug.ts
|
|
399
|
+
function isTaskSlug(s) {
|
|
400
|
+
return !!s && TASK_SLUG_RE.test(s);
|
|
401
|
+
}
|
|
402
|
+
var TASK_SLUG_RE;
|
|
364
403
|
var init_slug = __esm({
|
|
365
404
|
"../swarm-core/src/slug.ts"() {
|
|
366
405
|
"use strict";
|
|
406
|
+
TASK_SLUG_RE = /^(?:[A-Za-z0-9]{1,8}-\d+|#[A-Za-z0-9]{4,})$/;
|
|
367
407
|
}
|
|
368
408
|
});
|
|
369
409
|
|
|
@@ -371,14 +411,20 @@ var init_slug = __esm({
|
|
|
371
411
|
function sanitizeEventText(text) {
|
|
372
412
|
if (text == null) return null;
|
|
373
413
|
const cleaned = text.replace(ANSI_CSI_RE, "").replace(CONTROL_CHARS_RE, " ").replace(/ {2,}/g, " ").trim();
|
|
374
|
-
|
|
414
|
+
if (!cleaned.length) return null;
|
|
415
|
+
return cleaned.length <= EVENT_TEXT_MAX ? cleaned : clampEventText(cleaned);
|
|
416
|
+
}
|
|
417
|
+
function clampEventText(text) {
|
|
418
|
+
const note = ` \u2026 (\u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E, \u0431\u044B\u043B\u043E ${text.length} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)`;
|
|
419
|
+
return text.slice(0, Math.max(0, EVENT_TEXT_MAX - note.length)) + note;
|
|
375
420
|
}
|
|
376
|
-
var ANSI_CSI_RE, CONTROL_CHARS_RE;
|
|
421
|
+
var ANSI_CSI_RE, CONTROL_CHARS_RE, EVENT_TEXT_MAX;
|
|
377
422
|
var init_text = __esm({
|
|
378
423
|
"../swarm-core/src/text.ts"() {
|
|
379
424
|
"use strict";
|
|
380
425
|
ANSI_CSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
381
426
|
CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
|
|
427
|
+
EVENT_TEXT_MAX = 16e3;
|
|
382
428
|
}
|
|
383
429
|
});
|
|
384
430
|
|
|
@@ -557,6 +603,19 @@ var init_hosts = __esm({
|
|
|
557
603
|
}
|
|
558
604
|
});
|
|
559
605
|
|
|
606
|
+
// ../swarm-core/src/hostCommands.ts
|
|
607
|
+
var HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS;
|
|
608
|
+
var init_hostCommands = __esm({
|
|
609
|
+
"../swarm-core/src/hostCommands.ts"() {
|
|
610
|
+
"use strict";
|
|
611
|
+
HOST_COMMAND_STATUSES = ["pending", "delivered", "running", "done", "failed", "rejected", "expired"];
|
|
612
|
+
TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "rejected", "expired"]);
|
|
613
|
+
HOST_COMMAND_OPEN_STATUSES = HOST_COMMAND_STATUSES.filter((s) => !TERMINAL.has(s));
|
|
614
|
+
HOST_COMMAND_TTL_MS = 30 * 6e4;
|
|
615
|
+
HOST_COMMAND_KEEPALIVE_MS = 10 * 6e4;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
560
619
|
// ../swarm-core/src/relief.ts
|
|
561
620
|
var init_relief = __esm({
|
|
562
621
|
"../swarm-core/src/relief.ts"() {
|
|
@@ -581,6 +640,7 @@ var init_src = __esm({
|
|
|
581
640
|
init_names();
|
|
582
641
|
init_baseBranch();
|
|
583
642
|
init_hosts();
|
|
643
|
+
init_hostCommands();
|
|
584
644
|
init_relief();
|
|
585
645
|
}
|
|
586
646
|
});
|
|
@@ -625,7 +685,7 @@ function tokenFromFile() {
|
|
|
625
685
|
return "";
|
|
626
686
|
}
|
|
627
687
|
}
|
|
628
|
-
var SWARM_HOME, DB_PATH, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
|
|
688
|
+
var SWARM_HOME, DB_PATH, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
|
|
629
689
|
var init_config = __esm({
|
|
630
690
|
"src/config.ts"() {
|
|
631
691
|
"use strict";
|
|
@@ -641,6 +701,9 @@ var init_config = __esm({
|
|
|
641
701
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
642
702
|
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join(SWARM_HOME, "run-logs");
|
|
643
703
|
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join(SWARM_HOME, "overmind.log");
|
|
704
|
+
DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
|
|
705
|
+
DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
|
|
706
|
+
DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
|
|
644
707
|
DIST_BUILD = true;
|
|
645
708
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
646
709
|
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join(SWARM_HOME, "outbox.jsonl");
|
|
@@ -675,6 +738,9 @@ var init_config = __esm({
|
|
|
675
738
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
676
739
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
677
740
|
SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
|
|
741
|
+
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
742
|
+
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join(SWARM_HOME, "repo-map.json");
|
|
743
|
+
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
678
744
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
679
745
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
680
746
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -737,44 +803,579 @@ var init_names2 = __esm({
|
|
|
737
803
|
function encodeProbe(rec) {
|
|
738
804
|
return JSON.stringify(rec);
|
|
739
805
|
}
|
|
740
|
-
function decodeProbe(text) {
|
|
741
|
-
if (!text) return { probe: null, ext: null, reason: null, resetHint: null };
|
|
806
|
+
function decodeProbe(text) {
|
|
807
|
+
if (!text) return { probe: null, ext: null, reason: null, resetHint: null };
|
|
808
|
+
try {
|
|
809
|
+
const o = JSON.parse(text);
|
|
810
|
+
return {
|
|
811
|
+
probe: typeof o.probe === "boolean" ? o.probe : null,
|
|
812
|
+
ext: typeof o.ext === "boolean" ? o.ext : null,
|
|
813
|
+
reason: o.reason ?? null,
|
|
814
|
+
resetHint: o.resetHint ?? null
|
|
815
|
+
};
|
|
816
|
+
} catch {
|
|
817
|
+
return { probe: null, ext: null, reason: null, resetHint: null };
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function lastProbeReason(recs) {
|
|
821
|
+
for (let i = recs.length - 1; i >= 0; i--) {
|
|
822
|
+
if (recs[i].reason != null) return recs[i].reason;
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
var numEnv, DEFAULT_STORM_CONFIG;
|
|
827
|
+
var init_storm = __esm({
|
|
828
|
+
"src/storm.ts"() {
|
|
829
|
+
"use strict";
|
|
830
|
+
init_src();
|
|
831
|
+
numEnv = (name, def) => {
|
|
832
|
+
const v = Number(process.env[name]);
|
|
833
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
834
|
+
};
|
|
835
|
+
DEFAULT_STORM_CONFIG = {
|
|
836
|
+
deathThreshold: numEnv("SWARM_STORM_DEATHS", 2),
|
|
837
|
+
windowMs: numEnv("SWARM_STORM_WINDOW_MS", 5 * 60 * 1e3),
|
|
838
|
+
spawnFailThreshold: numEnv("SWARM_STORM_SPAWN_FAILS", 2),
|
|
839
|
+
probeIntervalMs: numEnv("SWARM_STORM_PROBE_INTERVAL_MS", 60 * 1e3),
|
|
840
|
+
probeSuccessesToClear: numEnv("SWARM_STORM_PROBE_CLEAR", 2),
|
|
841
|
+
probeTimeoutMs: numEnv("SWARM_STORM_PROBE_TIMEOUT_MS", 20 * 1e3),
|
|
842
|
+
restartCooldownMs: numEnv("SWARM_STORM_RESTART_COOLDOWN_MS", 3 * 60 * 1e3)
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// src/repoRegistry.ts
|
|
848
|
+
import { $ } from "execa";
|
|
849
|
+
function parseRepoFullName(remoteUrl) {
|
|
850
|
+
const m = remoteUrl.trim().match(/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(\.git)?\/?$/i);
|
|
851
|
+
if (!m) return null;
|
|
852
|
+
return `${m[1]}/${m[2]}`.toLowerCase();
|
|
853
|
+
}
|
|
854
|
+
async function resolveRepoFullName(path) {
|
|
855
|
+
const res = await $({ reject: false })`git -C ${path} remote get-url origin`;
|
|
856
|
+
if (res.exitCode !== 0) return null;
|
|
857
|
+
return parseRepoFullName(res.stdout);
|
|
858
|
+
}
|
|
859
|
+
async function buildRepoRegistry(paths) {
|
|
860
|
+
const resolved = await Promise.all(
|
|
861
|
+
paths.map(async (raw) => {
|
|
862
|
+
const path = normalizeRepo(raw);
|
|
863
|
+
if (!path) return null;
|
|
864
|
+
const repoFullName = await resolveRepoFullName(path);
|
|
865
|
+
return repoFullName ? { repoFullName, path } : null;
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
return resolved.filter((e) => e !== null);
|
|
869
|
+
}
|
|
870
|
+
var init_repoRegistry = __esm({
|
|
871
|
+
"src/repoRegistry.ts"() {
|
|
872
|
+
"use strict";
|
|
873
|
+
init_config();
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
// src/repoScan.ts
|
|
878
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
879
|
+
import { join as join3 } from "path";
|
|
880
|
+
var init_repoScan = __esm({
|
|
881
|
+
"src/repoScan.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
init_repoRegistry();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// src/repoDiscovery.ts
|
|
888
|
+
import { homedir as homedir2 } from "os";
|
|
889
|
+
import { readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
890
|
+
import { isAbsolute, join as join4, resolve as resolve2 } from "path";
|
|
891
|
+
function repoMapLines(rows) {
|
|
892
|
+
if (!rows?.length) return [];
|
|
893
|
+
const missing = rows.filter((r) => !r.path).length;
|
|
894
|
+
const head = `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F: ${rows.length}` + (missing ? `, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${missing}` : "");
|
|
895
|
+
const body = rows.map((r) => {
|
|
896
|
+
if (!r.path) return ` \u2717 ${r.repoFullName} \u2014 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u041D\u0415\u0422 (\u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E)`;
|
|
897
|
+
const how = r.source ? ` (${SOURCE_TEXT[r.source]})` : "";
|
|
898
|
+
const alt = r.alternatives.length ? ` \u26A0 \u0435\u0441\u0442\u044C \u0435\u0449\u0451 ${r.alternatives.length}: ${r.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C` : "";
|
|
899
|
+
return ` ${r.alternatives.length ? "\u26A0" : "\u2713"} ${r.repoFullName} \u2192 ${r.path}${how}${alt}`;
|
|
900
|
+
});
|
|
901
|
+
return [head, ...body];
|
|
902
|
+
}
|
|
903
|
+
function readRepoMapCache(path) {
|
|
904
|
+
try {
|
|
905
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
906
|
+
if (!Array.isArray(raw?.entries)) return [];
|
|
907
|
+
return raw.entries.filter(
|
|
908
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
909
|
+
);
|
|
910
|
+
} catch {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
var SOURCE_TEXT;
|
|
915
|
+
var init_repoDiscovery = __esm({
|
|
916
|
+
"src/repoDiscovery.ts"() {
|
|
917
|
+
"use strict";
|
|
918
|
+
init_src();
|
|
919
|
+
init_config();
|
|
920
|
+
init_repoScan();
|
|
921
|
+
SOURCE_TEXT = {
|
|
922
|
+
configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
923
|
+
cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
|
|
924
|
+
scan: "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C"
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// package.json
|
|
930
|
+
var package_default;
|
|
931
|
+
var init_package = __esm({
|
|
932
|
+
"package.json"() {
|
|
933
|
+
package_default = {
|
|
934
|
+
name: "@taskless-app/swarm",
|
|
935
|
+
version: "0.5.0",
|
|
936
|
+
type: "module",
|
|
937
|
+
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
938
|
+
keywords: [
|
|
939
|
+
"ai-agents",
|
|
940
|
+
"coding-agents",
|
|
941
|
+
"swarm",
|
|
942
|
+
"orchestration",
|
|
943
|
+
"autonomous-agents",
|
|
944
|
+
"mcp",
|
|
945
|
+
"taskless",
|
|
946
|
+
"cli"
|
|
947
|
+
],
|
|
948
|
+
license: "SEE LICENSE IN LICENSE",
|
|
949
|
+
author: "Ilya Smurov <ilya@smurov.com>",
|
|
950
|
+
homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
|
|
951
|
+
repository: {
|
|
952
|
+
type: "git",
|
|
953
|
+
url: "git+https://github.com/ilyasmurov/taskless.git",
|
|
954
|
+
directory: "packages/swarm-daemon"
|
|
955
|
+
},
|
|
956
|
+
engines: {
|
|
957
|
+
node: ">=22"
|
|
958
|
+
},
|
|
959
|
+
bin: {
|
|
960
|
+
swarm: "dist/cli.js"
|
|
961
|
+
},
|
|
962
|
+
files: [
|
|
963
|
+
"dist",
|
|
964
|
+
"docs/HERO.md",
|
|
965
|
+
"roles"
|
|
966
|
+
],
|
|
967
|
+
scripts: {
|
|
968
|
+
overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
|
|
969
|
+
swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
|
|
970
|
+
build: "tsup",
|
|
971
|
+
start: "node dist/overmind.js",
|
|
972
|
+
prepublishOnly: "pnpm run build",
|
|
973
|
+
test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
974
|
+
"test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
975
|
+
"test:watch": "vitest",
|
|
976
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
977
|
+
lint: "echo 'no lint in swarm-daemon yet'"
|
|
978
|
+
},
|
|
979
|
+
dependencies: {
|
|
980
|
+
"@hono/node-server": "^2.0.12",
|
|
981
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
982
|
+
execa: "^10.0.0",
|
|
983
|
+
zod: "3.25.76"
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
// src/version.ts
|
|
990
|
+
var PACKAGE_VERSION, MIN_NODE_MAJOR;
|
|
991
|
+
var init_version = __esm({
|
|
992
|
+
"src/version.ts"() {
|
|
993
|
+
"use strict";
|
|
994
|
+
init_package();
|
|
995
|
+
PACKAGE_VERSION = package_default.version;
|
|
996
|
+
MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// src/droneChannel.ts
|
|
1001
|
+
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
1002
|
+
import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
1003
|
+
import { dirname as dirname2 } from "path";
|
|
1004
|
+
import { serve } from "@hono/node-server";
|
|
1005
|
+
function loadChannelSecret(path) {
|
|
1006
|
+
try {
|
|
1007
|
+
const s = readFileSync8(path, "utf8").trim();
|
|
1008
|
+
if (s.length >= 32) return s;
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
const secret = randomBytes(32).toString("hex");
|
|
1012
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
1013
|
+
writeFileSync5(path, secret, { mode: 384 });
|
|
1014
|
+
try {
|
|
1015
|
+
chmodSync(path, 384);
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
1018
|
+
return secret;
|
|
1019
|
+
}
|
|
1020
|
+
function sign(runKey, secret) {
|
|
1021
|
+
return createHmac("sha256", secret).update(runKey).digest("base64url");
|
|
1022
|
+
}
|
|
1023
|
+
function mintChannelToken(runKey, secret) {
|
|
1024
|
+
return `${Buffer.from(runKey, "utf8").toString("base64url")}.${sign(runKey, secret)}`;
|
|
1025
|
+
}
|
|
1026
|
+
function verifyChannelToken(token, secret) {
|
|
1027
|
+
const dot = token.lastIndexOf(".");
|
|
1028
|
+
if (dot <= 0) return null;
|
|
1029
|
+
const runKey = Buffer.from(token.slice(0, dot), "base64url").toString("utf8");
|
|
1030
|
+
if (!runKey) return null;
|
|
1031
|
+
const got = Buffer.from(token.slice(dot + 1));
|
|
1032
|
+
const want = Buffer.from(sign(runKey, secret));
|
|
1033
|
+
if (got.length !== want.length || !timingSafeEqual(got, want)) return null;
|
|
1034
|
+
return runKey;
|
|
1035
|
+
}
|
|
1036
|
+
function mintLocalCliToken(secret) {
|
|
1037
|
+
return mintChannelToken(LOCAL_CLI_KEY, secret);
|
|
1038
|
+
}
|
|
1039
|
+
function verifyLocalCliToken(token, secret) {
|
|
1040
|
+
return verifyChannelToken(token, secret) === LOCAL_CLI_KEY;
|
|
1041
|
+
}
|
|
1042
|
+
var LOCAL_CLI_KEY, EVENT_KIND_SET;
|
|
1043
|
+
var init_droneChannel = __esm({
|
|
1044
|
+
"src/droneChannel.ts"() {
|
|
1045
|
+
"use strict";
|
|
1046
|
+
init_src();
|
|
1047
|
+
LOCAL_CLI_KEY = "cli";
|
|
1048
|
+
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// src/claudeBin.ts
|
|
1053
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
|
|
1054
|
+
import { win32 as winPath } from "path";
|
|
1055
|
+
function compareVersionDirs(a, b) {
|
|
1056
|
+
const parts = (s) => s.split(".").map((p) => Number.isFinite(Number(p)) ? Number(p) : -1);
|
|
1057
|
+
const pa = parts(a);
|
|
1058
|
+
const pb = parts(b);
|
|
1059
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1060
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1061
|
+
if (d !== 0) return d;
|
|
1062
|
+
}
|
|
1063
|
+
return 0;
|
|
1064
|
+
}
|
|
1065
|
+
function realListDir(dir) {
|
|
1066
|
+
try {
|
|
1067
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return [];
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function winVersionRoots(env, listDir) {
|
|
1073
|
+
const roots = [];
|
|
1074
|
+
const appData = env.APPDATA;
|
|
1075
|
+
if (appData) roots.push(winPath.join(appData, "Claude", "claude-code"));
|
|
1076
|
+
const localAppData = env.LOCALAPPDATA;
|
|
1077
|
+
if (localAppData) {
|
|
1078
|
+
const packages = winPath.join(localAppData, "Packages");
|
|
1079
|
+
for (const name of listDir(packages)) {
|
|
1080
|
+
if (!name.startsWith("Claude")) continue;
|
|
1081
|
+
roots.push(winPath.join(packages, name, "LocalCache", "Roaming", "Claude", "claude-code"));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return roots;
|
|
1085
|
+
}
|
|
1086
|
+
function findWindowsClaude(opts = {}) {
|
|
1087
|
+
const env = opts.env ?? process.env;
|
|
1088
|
+
const listDir = opts.listDir ?? realListDir;
|
|
1089
|
+
const fileExists = opts.fileExists ?? existsSync5;
|
|
1090
|
+
let best = null;
|
|
1091
|
+
for (const root of winVersionRoots(env, listDir)) {
|
|
1092
|
+
for (const version of listDir(root)) {
|
|
1093
|
+
const candidate = winPath.join(root, version, "claude.exe");
|
|
1094
|
+
if (!fileExists(candidate)) continue;
|
|
1095
|
+
if (!best || compareVersionDirs(version, best.version) > 0) best = { version, path: candidate };
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return best?.path ?? null;
|
|
1099
|
+
}
|
|
1100
|
+
function resolveClaudeBin(opts = {}) {
|
|
1101
|
+
const env = opts.env ?? process.env;
|
|
1102
|
+
const platform = opts.platform ?? process.platform;
|
|
1103
|
+
const explicit = (env.SWARM_CLAUDE_BIN ?? "").trim();
|
|
1104
|
+
if (explicit) return explicit;
|
|
1105
|
+
if (platform !== "win32") return CLAUDE_FALLBACK;
|
|
1106
|
+
if (windowsScanDone) return cachedWindowsBin ?? CLAUDE_FALLBACK;
|
|
1107
|
+
windowsScanDone = true;
|
|
1108
|
+
const found = findWindowsClaude({ ...opts, env });
|
|
1109
|
+
cachedWindowsBin = found;
|
|
1110
|
+
if (found) return found;
|
|
1111
|
+
if (!warnedNoWindowsClaude) {
|
|
1112
|
+
warnedNoWindowsClaude = true;
|
|
1113
|
+
console.warn(
|
|
1114
|
+
"[overmind] claude.exe \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043D\u0438 \u0432 %APPDATA%\\Claude\\claude-code, \u043D\u0438 \u0432 MSIX-\u043A\u043E\u043F\u0438\u0438 \u043F\u043E\u0434 %LOCALAPPDATA%\\Packages\\Claude* \u2014 \u0441\u043F\u0430\u0432\u043D\u044E \u0433\u043E\u043B\u043E\u0435 \xABclaude\xBB (\u043F\u043E\u0438\u0441\u043A \u043F\u043E PATH+PATHEXT). \u0423\u043F\u0430\u0434\u0451\u0442 ENOENT (bash-\u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u0431\u0435\u0437 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0432 PATH \u043D\u0435 \u0432\u0438\u0434\u043D\u0430 spawn'\u0443) \u2014 \u0437\u0430\u0434\u0430\u0439\u0442\u0435 SWARM_CLAUDE_BIN \u043F\u0443\u0442\u0451\u043C \u043A \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C\u0443 claude.exe."
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return CLAUDE_FALLBACK;
|
|
1118
|
+
}
|
|
1119
|
+
var CLAUDE_FALLBACK, warnedNoWindowsClaude, cachedWindowsBin, windowsScanDone;
|
|
1120
|
+
var init_claudeBin = __esm({
|
|
1121
|
+
"src/claudeBin.ts"() {
|
|
1122
|
+
"use strict";
|
|
1123
|
+
CLAUDE_FALLBACK = "claude";
|
|
1124
|
+
warnedNoWindowsClaude = false;
|
|
1125
|
+
cachedWindowsBin = null;
|
|
1126
|
+
windowsScanDone = false;
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// src/agentToken.ts
|
|
1131
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
1132
|
+
import { join as join11 } from "path";
|
|
1133
|
+
function loadAgentToken() {
|
|
1134
|
+
try {
|
|
1135
|
+
const s = JSON.parse(readFileSync10(STORE_PATH2, "utf8"));
|
|
1136
|
+
return s.token ? s : null;
|
|
1137
|
+
} catch {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function saveAgentToken(s) {
|
|
1142
|
+
writeFileSync8(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
1143
|
+
}
|
|
1144
|
+
function clearAgentToken() {
|
|
1145
|
+
try {
|
|
1146
|
+
unlinkSync3(STORE_PATH2);
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async function mintAgentToken(args, fetchFn = fetch) {
|
|
1151
|
+
const res = await fetchFn(`${base2(args.api)}/api/swarm/agent/token`, {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
|
|
1154
|
+
body: JSON.stringify(args.label ? { label: args.label } : {})
|
|
1155
|
+
});
|
|
1156
|
+
const text = await res.text();
|
|
1157
|
+
if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
1158
|
+
const j = JSON.parse(text);
|
|
1159
|
+
if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
|
|
1160
|
+
return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
|
|
1161
|
+
}
|
|
1162
|
+
async function probeAgentToken(args, fetchFn = fetch) {
|
|
1163
|
+
try {
|
|
1164
|
+
const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
|
|
1165
|
+
headers: { Authorization: `Bearer ${args.token}` }
|
|
1166
|
+
});
|
|
1167
|
+
if (res.status === 401 || res.status === 403) return "revoked";
|
|
1168
|
+
return res.ok ? "ok" : "unreachable";
|
|
1169
|
+
} catch {
|
|
1170
|
+
return "unreachable";
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
var STORE_PATH2, base2;
|
|
1174
|
+
var init_agentToken = __esm({
|
|
1175
|
+
"src/agentToken.ts"() {
|
|
1176
|
+
"use strict";
|
|
1177
|
+
init_config();
|
|
1178
|
+
STORE_PATH2 = join11(SWARM_HOME, "agent.json");
|
|
1179
|
+
base2 = (api) => api.replace(/\/$/, "");
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// src/screenshot.ts
|
|
1184
|
+
async function captureScreenshots(launchBrowser, opts) {
|
|
1185
|
+
const viewports = opts.viewports ?? DEFAULT_VIEWPORTS;
|
|
1186
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHOT_TIMEOUT_MS;
|
|
1187
|
+
const fullPage = opts.fullPage ?? true;
|
|
1188
|
+
const browser = await launchBrowser();
|
|
1189
|
+
try {
|
|
1190
|
+
const results = [];
|
|
1191
|
+
for (const vp of viewports) {
|
|
1192
|
+
const ctx = await browser.newContext({
|
|
1193
|
+
viewport: { width: vp.width, height: vp.height },
|
|
1194
|
+
...opts.storageStatePath ? { storageState: opts.storageStatePath } : {}
|
|
1195
|
+
});
|
|
1196
|
+
try {
|
|
1197
|
+
const page = await ctx.newPage();
|
|
1198
|
+
await page.goto(opts.url, { timeout: timeoutMs, waitUntil: "load" });
|
|
1199
|
+
await page.waitForNetworkSettle({
|
|
1200
|
+
quietMs: NETWORK_SETTLE_QUIET_MS,
|
|
1201
|
+
timeoutMs: Math.min(timeoutMs, NETWORK_SETTLE_TIMEOUT_MS)
|
|
1202
|
+
});
|
|
1203
|
+
const path = `${opts.outPrefix}-${vp.name}.png`;
|
|
1204
|
+
await page.screenshot({ path, fullPage, timeout: timeoutMs });
|
|
1205
|
+
results.push({ viewport: vp.name, path });
|
|
1206
|
+
} finally {
|
|
1207
|
+
await ctx.close();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return results;
|
|
1211
|
+
} finally {
|
|
1212
|
+
await browser.close();
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function isEventStream(headers2) {
|
|
1216
|
+
for (const [k, v] of Object.entries(headers2)) {
|
|
1217
|
+
if (k.toLowerCase() === "content-type") return v.toLowerCase().startsWith("text/event-stream");
|
|
1218
|
+
}
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
function createNetworkSettleTracker(target) {
|
|
1222
|
+
const pending2 = /* @__PURE__ */ new Set();
|
|
1223
|
+
const onChange = /* @__PURE__ */ new Set();
|
|
1224
|
+
const notify = () => {
|
|
1225
|
+
for (const cb of [...onChange]) cb();
|
|
1226
|
+
};
|
|
1227
|
+
target.on("request", ((req) => {
|
|
1228
|
+
pending2.add(req);
|
|
1229
|
+
notify();
|
|
1230
|
+
}));
|
|
1231
|
+
target.on("response", ((res) => {
|
|
1232
|
+
if (isEventStream(res.headers())) {
|
|
1233
|
+
pending2.delete(res.request());
|
|
1234
|
+
notify();
|
|
1235
|
+
}
|
|
1236
|
+
}));
|
|
1237
|
+
const onDone = ((req) => {
|
|
1238
|
+
pending2.delete(req);
|
|
1239
|
+
notify();
|
|
1240
|
+
});
|
|
1241
|
+
target.on("requestfinished", onDone);
|
|
1242
|
+
target.on("requestfailed", onDone);
|
|
1243
|
+
return {
|
|
1244
|
+
waitForSettle({ quietMs, timeoutMs }) {
|
|
1245
|
+
return new Promise((resolve4) => {
|
|
1246
|
+
let quietTimer;
|
|
1247
|
+
const hardTimer = setTimeout(finish, timeoutMs);
|
|
1248
|
+
function finish() {
|
|
1249
|
+
clearTimeout(quietTimer);
|
|
1250
|
+
clearTimeout(hardTimer);
|
|
1251
|
+
onChange.delete(check);
|
|
1252
|
+
resolve4();
|
|
1253
|
+
}
|
|
1254
|
+
function check() {
|
|
1255
|
+
clearTimeout(quietTimer);
|
|
1256
|
+
if (pending2.size === 0) quietTimer = setTimeout(finish, quietMs);
|
|
1257
|
+
}
|
|
1258
|
+
onChange.add(check);
|
|
1259
|
+
check();
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function wrapPage(page) {
|
|
1265
|
+
const tracker = createNetworkSettleTracker(page);
|
|
1266
|
+
return {
|
|
1267
|
+
goto: (url, gotoOpts) => page.goto(url, gotoOpts),
|
|
1268
|
+
screenshot: (shotOpts) => page.screenshot(shotOpts),
|
|
1269
|
+
waitForNetworkSettle: (settleOpts) => tracker.waitForSettle(settleOpts)
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function wrapContext(ctx) {
|
|
1273
|
+
return {
|
|
1274
|
+
newPage: async () => wrapPage(await ctx.newPage()),
|
|
1275
|
+
close: () => ctx.close()
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function wrapBrowser(browser) {
|
|
1279
|
+
return {
|
|
1280
|
+
newContext: async (ctxOpts) => wrapContext(await browser.newContext(ctxOpts)),
|
|
1281
|
+
close: () => browser.close()
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
async function launchChromium(opts = {}) {
|
|
1285
|
+
let mod;
|
|
1286
|
+
try {
|
|
1287
|
+
mod = await import("playwright-core");
|
|
1288
|
+
} catch {
|
|
1289
|
+
throw new Error(
|
|
1290
|
+
"playwright-core \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u043E\u0434\u0438\u043D \u0440\u0430\u0437: (\u0432 \u044D\u0442\u043E\u043C \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u043E) pnpm --filter @taskless-app/swarm add -D playwright-core; (\u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E) npm i -D playwright-core \u2014 \u0438 \u0432 \u043E\u0431\u043E\u0438\u0445 \u0441\u043B\u0443\u0447\u0430\u044F\u0445 npx playwright install chromium."
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
const browser = await mod.chromium.launch({ headless: true, ...opts.timeoutMs !== void 0 ? { timeout: opts.timeoutMs } : {} });
|
|
1294
|
+
return wrapBrowser(browser);
|
|
1295
|
+
}
|
|
1296
|
+
var DESKTOP_VIEWPORT, MOBILE_VIEWPORT, DEFAULT_VIEWPORTS, DEFAULT_SHOT_TIMEOUT_MS, NETWORK_SETTLE_QUIET_MS, NETWORK_SETTLE_TIMEOUT_MS;
|
|
1297
|
+
var init_screenshot = __esm({
|
|
1298
|
+
"src/screenshot.ts"() {
|
|
1299
|
+
"use strict";
|
|
1300
|
+
DESKTOP_VIEWPORT = { name: "desktop", width: 1360, height: 940 };
|
|
1301
|
+
MOBILE_VIEWPORT = { name: "mobile", width: 390, height: 844 };
|
|
1302
|
+
DEFAULT_VIEWPORTS = [DESKTOP_VIEWPORT, MOBILE_VIEWPORT];
|
|
1303
|
+
DEFAULT_SHOT_TIMEOUT_MS = 2e4;
|
|
1304
|
+
NETWORK_SETTLE_QUIET_MS = 500;
|
|
1305
|
+
NETWORK_SETTLE_TIMEOUT_MS = 8e3;
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
// src/daemonLink.ts
|
|
1310
|
+
function headers(extra = {}) {
|
|
1311
|
+
return { "X-Swarm-Local": mintLocalCliToken(loadChannelSecret(DRONE_CHANNEL_SECRET_PATH)), ...extra };
|
|
1312
|
+
}
|
|
1313
|
+
async function daemonRuns(status) {
|
|
1314
|
+
try {
|
|
1315
|
+
const qs = status ? `?status=${encodeURIComponent(status)}` : "";
|
|
1316
|
+
const res = await fetch(`${BASE}/runs${qs}`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
1317
|
+
if (!res.ok) return null;
|
|
1318
|
+
const body = await res.json();
|
|
1319
|
+
if (!Array.isArray(body.runs)) return null;
|
|
1320
|
+
return { runs: body.runs, backend: String(body.backend ?? ""), hostId: body.hostId ?? null };
|
|
1321
|
+
} catch {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async function post(path, form) {
|
|
742
1326
|
try {
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1327
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
1328
|
+
method: "POST",
|
|
1329
|
+
headers: headers({ "Content-Type": "application/x-www-form-urlencoded" }),
|
|
1330
|
+
body: new URLSearchParams(form),
|
|
1331
|
+
signal: AbortSignal.timeout(ACTION_TIMEOUT_MS)
|
|
1332
|
+
});
|
|
1333
|
+
if (res.status === 503) return null;
|
|
1334
|
+
if (res.status === 401) return { ok: false, why: "\u043C\u043E\u0440\u0434\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B\u0430 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u043B\u044E\u0447 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C ~/.taskless-swarm/drone-channel.key" };
|
|
1335
|
+
return await res.json();
|
|
750
1336
|
} catch {
|
|
751
|
-
return
|
|
1337
|
+
return null;
|
|
752
1338
|
}
|
|
753
1339
|
}
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
|
|
1340
|
+
function daemonCancel(slug, reason) {
|
|
1341
|
+
return post("/cancel", { slug, reason });
|
|
1342
|
+
}
|
|
1343
|
+
function daemonWake(runId) {
|
|
1344
|
+
return post("/wake", { runId: String(runId) });
|
|
1345
|
+
}
|
|
1346
|
+
function daemonPause(reason) {
|
|
1347
|
+
return post("/pause", { source: "cli", ...reason ? { reason } : {} });
|
|
1348
|
+
}
|
|
1349
|
+
function daemonResume() {
|
|
1350
|
+
return post("/resume", { source: "cli" });
|
|
1351
|
+
}
|
|
1352
|
+
async function daemonStop(opts) {
|
|
1353
|
+
const res = await post("/shutdown", { force: opts.force ? "1" : "0", wait: opts.wait ? "1" : "0" });
|
|
1354
|
+
if (!res) return null;
|
|
1355
|
+
const raw = res;
|
|
1356
|
+
if (raw.stopping === void 0 && raw.waiting === void 0) return null;
|
|
1357
|
+
return { stopping: !!raw.stopping, waiting: !!raw.waiting, sessions: Array.isArray(raw.sessions) ? raw.sessions : [] };
|
|
1358
|
+
}
|
|
1359
|
+
async function daemonStatus2() {
|
|
1360
|
+
try {
|
|
1361
|
+
const res = await fetch(`${BASE}/status`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
1362
|
+
if (!res.ok) return null;
|
|
1363
|
+
const body = await res.json();
|
|
1364
|
+
return typeof body?.paused === "boolean" ? body : null;
|
|
1365
|
+
} catch {
|
|
1366
|
+
return null;
|
|
757
1367
|
}
|
|
758
|
-
return null;
|
|
759
1368
|
}
|
|
760
|
-
var
|
|
761
|
-
var
|
|
762
|
-
"src/
|
|
1369
|
+
var TIMEOUT_MS, ACTION_TIMEOUT_MS, BASE, NO_DAEMON_NOTE;
|
|
1370
|
+
var init_daemonLink = __esm({
|
|
1371
|
+
"src/daemonLink.ts"() {
|
|
763
1372
|
"use strict";
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
deathThreshold: numEnv("SWARM_STORM_DEATHS", 2),
|
|
771
|
-
windowMs: numEnv("SWARM_STORM_WINDOW_MS", 5 * 60 * 1e3),
|
|
772
|
-
spawnFailThreshold: numEnv("SWARM_STORM_SPAWN_FAILS", 2),
|
|
773
|
-
probeIntervalMs: numEnv("SWARM_STORM_PROBE_INTERVAL_MS", 60 * 1e3),
|
|
774
|
-
probeSuccessesToClear: numEnv("SWARM_STORM_PROBE_CLEAR", 2),
|
|
775
|
-
probeTimeoutMs: numEnv("SWARM_STORM_PROBE_TIMEOUT_MS", 20 * 1e3),
|
|
776
|
-
restartCooldownMs: numEnv("SWARM_STORM_RESTART_COOLDOWN_MS", 3 * 60 * 1e3)
|
|
777
|
-
};
|
|
1373
|
+
init_config();
|
|
1374
|
+
init_droneChannel();
|
|
1375
|
+
TIMEOUT_MS = 5e3;
|
|
1376
|
+
ACTION_TIMEOUT_MS = 2e4;
|
|
1377
|
+
BASE = `http://127.0.0.1:${WEB_PORT}`;
|
|
1378
|
+
NO_DAEMON_NOTE = `\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT}) \u2014 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E \u041B\u041E\u041A\u0410\u041B\u042C\u041D\u042B\u0419 \u0441\u0442\u043E\u0440 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B, \u043E\u043D \u043C\u043E\u0436\u0435\u0442 \u0440\u0430\u0441\u0445\u043E\u0434\u0438\u0442\u044C\u0441\u044F \u0441 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u043E\u043C. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0434\u0435\u043C\u043E\u043D\u0430: swarm start`;
|
|
778
1379
|
}
|
|
779
1380
|
});
|
|
780
1381
|
|
|
@@ -784,8 +1385,8 @@ __export(db_exports, {
|
|
|
784
1385
|
SwarmDb: () => SwarmDb
|
|
785
1386
|
});
|
|
786
1387
|
import { createRequire as createRequire2 } from "module";
|
|
787
|
-
import { existsSync as
|
|
788
|
-
import { resolve as
|
|
1388
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1389
|
+
import { resolve as resolve3 } from "path";
|
|
789
1390
|
function rowToQuestion(row) {
|
|
790
1391
|
return {
|
|
791
1392
|
id: row.id,
|
|
@@ -1265,11 +1866,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
1265
1866
|
* транзакции: прервётся — маркер не встанет, повтор безопасен.
|
|
1266
1867
|
*/
|
|
1267
1868
|
importDb(sourcePath, repo) {
|
|
1268
|
-
const abs =
|
|
1869
|
+
const abs = resolve3(sourcePath);
|
|
1269
1870
|
const repoN = normalizeRepo(repo);
|
|
1270
1871
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
1271
1872
|
if (already) return { imported: false, runs: already.run_count };
|
|
1272
|
-
if (!
|
|
1873
|
+
if (!existsSync9(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
1273
1874
|
const src = new this.databaseCtor(abs);
|
|
1274
1875
|
try {
|
|
1275
1876
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -1577,28 +2178,904 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
1577
2178
|
const rows = this.db.prepare("SELECT text FROM events WHERE run_id IS NULL AND kind='storm_probe' AND at >= ? ORDER BY id").all(sinceIso);
|
|
1578
2179
|
return rows.map((r) => decodeProbe(r.text));
|
|
1579
2180
|
}
|
|
1580
|
-
/**
|
|
1581
|
-
* Последняя РЕАЛЬНАЯ health-probe (probe !== null; ext-only записи пропускаем) с её
|
|
1582
|
-
* временем. TASWM-137 п.д: подтверждающая проба перед входом в шторм по эвристике —
|
|
1583
|
-
* зелёная проба вето́ит ложный шторм; свежую (в пределах probeIntervalMs) переиспользуем,
|
|
1584
|
-
* не гоняя новую каждый тик. Смотрим только хвост (LIMIT) — старее нам всё равно не нужно.
|
|
1585
|
-
*/
|
|
1586
|
-
lastHealthProbe() {
|
|
1587
|
-
const rows = this.db.prepare("SELECT text, at FROM events WHERE run_id IS NULL AND kind='storm_probe' ORDER BY id DESC LIMIT 20").all();
|
|
1588
|
-
for (const r of rows) {
|
|
1589
|
-
const rec = decodeProbe(r.text);
|
|
1590
|
-
if (rec.probe !== null) return { ok: rec.probe, at: r.at };
|
|
2181
|
+
/**
|
|
2182
|
+
* Последняя РЕАЛЬНАЯ health-probe (probe !== null; ext-only записи пропускаем) с её
|
|
2183
|
+
* временем. TASWM-137 п.д: подтверждающая проба перед входом в шторм по эвристике —
|
|
2184
|
+
* зелёная проба вето́ит ложный шторм; свежую (в пределах probeIntervalMs) переиспользуем,
|
|
2185
|
+
* не гоняя новую каждый тик. Смотрим только хвост (LIMIT) — старее нам всё равно не нужно.
|
|
2186
|
+
*/
|
|
2187
|
+
lastHealthProbe() {
|
|
2188
|
+
const rows = this.db.prepare("SELECT text, at FROM events WHERE run_id IS NULL AND kind='storm_probe' ORDER BY id DESC LIMIT 20").all();
|
|
2189
|
+
for (const r of rows) {
|
|
2190
|
+
const rec = decodeProbe(r.text);
|
|
2191
|
+
if (rec.probe !== null) return { ok: rec.probe, at: r.at };
|
|
2192
|
+
}
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/platform.ts
|
|
2200
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
|
|
2201
|
+
import { delimiter, join as join16 } from "path";
|
|
2202
|
+
function safeReadText(path) {
|
|
2203
|
+
try {
|
|
2204
|
+
return readFileSync14(path, "utf8");
|
|
2205
|
+
} catch {
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
2210
|
+
if (name.includes("/") || name.includes("\\")) return existsSync10(name) ? name : null;
|
|
2211
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
2212
|
+
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
2213
|
+
for (const dir of dirs) {
|
|
2214
|
+
for (const ext of exts) {
|
|
2215
|
+
const candidate = join16(dir, name + ext);
|
|
2216
|
+
if (existsSync10(candidate)) return candidate;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
function hasCommandInPath(name, env = process.env, platform = process.platform) {
|
|
2222
|
+
return resolveCommandPath(name, env, platform) !== null;
|
|
2223
|
+
}
|
|
2224
|
+
function isWsl(io = {}) {
|
|
2225
|
+
const env = io.env ?? process.env;
|
|
2226
|
+
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) return true;
|
|
2227
|
+
const readText = io.readText ?? safeReadText;
|
|
2228
|
+
const version = readText("/proc/version") ?? "";
|
|
2229
|
+
return /microsoft/i.test(version);
|
|
2230
|
+
}
|
|
2231
|
+
function detectPlatformKind(io = {}) {
|
|
2232
|
+
const platform = io.platform ?? process.platform;
|
|
2233
|
+
if (platform === "darwin") return "macos";
|
|
2234
|
+
if (platform === "win32") return "windows";
|
|
2235
|
+
return isWsl(io) ? "wsl" : "linux";
|
|
2236
|
+
}
|
|
2237
|
+
function detectSystemPm(kind, io = {}) {
|
|
2238
|
+
const env = io.env ?? process.env;
|
|
2239
|
+
const platform = io.platform ?? process.platform;
|
|
2240
|
+
const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
|
|
2241
|
+
if (kind === "macos") return has("brew") ? "brew" : null;
|
|
2242
|
+
if (kind === "windows") return has("winget") ? "winget" : null;
|
|
2243
|
+
return LINUX_PMS.find((p) => has(p.cmd))?.pm ?? null;
|
|
2244
|
+
}
|
|
2245
|
+
function detectPlatform(io = {}) {
|
|
2246
|
+
const kind = detectPlatformKind(io);
|
|
2247
|
+
const pm = detectSystemPm(kind, io);
|
|
2248
|
+
const isRoot = io.isRoot ?? (() => typeof process.getuid === "function" && process.getuid() === 0);
|
|
2249
|
+
const needsSudo = pm !== null && pm !== "brew" && pm !== "winget" && !isRoot();
|
|
2250
|
+
return { kind, pm, needsSudo, label: PLATFORM_LABEL[kind] };
|
|
2251
|
+
}
|
|
2252
|
+
function systemInstallCommand(info, packages) {
|
|
2253
|
+
if (!info.pm || packages.length === 0) return null;
|
|
2254
|
+
const list = packages.join(" ");
|
|
2255
|
+
const sudo = info.needsSudo ? "sudo " : "";
|
|
2256
|
+
switch (info.pm) {
|
|
2257
|
+
case "brew":
|
|
2258
|
+
return `brew install ${list}`;
|
|
2259
|
+
case "apt":
|
|
2260
|
+
return `${sudo}apt-get update && ${sudo}apt-get install -y ${list}`;
|
|
2261
|
+
case "dnf":
|
|
2262
|
+
return `${sudo}dnf install -y ${list}`;
|
|
2263
|
+
case "pacman":
|
|
2264
|
+
return `${sudo}pacman -S --needed --noconfirm ${list}`;
|
|
2265
|
+
case "zypper":
|
|
2266
|
+
return `${sudo}zypper install -y ${list}`;
|
|
2267
|
+
case "apk":
|
|
2268
|
+
return `${sudo}apk add ${list}`;
|
|
2269
|
+
case "winget":
|
|
2270
|
+
return `winget install --silent ${list}`;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
function detectGlobalPm(entry, io = {}) {
|
|
2274
|
+
const p = (entry ?? "").replace(/\\/g, "/");
|
|
2275
|
+
if (!p) return "unknown";
|
|
2276
|
+
if (p.endsWith(".ts") || /\/packages\/swarm-daemon\/(src|dist)\//.test(p)) return "sources";
|
|
2277
|
+
if (/\/\.bun\/install\/global\//.test(p) || /\/\.bun\/bin\//.test(p)) return "bun";
|
|
2278
|
+
if (/\/pnpm\//.test(p) || /\/\.pnpm-global\//.test(p) || /\/Library\/pnpm\//.test(p)) return "pnpm";
|
|
2279
|
+
if (/\/\.yarn\//.test(p) || /\/Yarn\/Data\/global\//.test(p) || /\/yarn\/global\//.test(p)) return "yarn";
|
|
2280
|
+
if (/\/node_modules\//.test(p)) return "npm";
|
|
2281
|
+
const env = io.env ?? process.env;
|
|
2282
|
+
const platform = io.platform ?? process.platform;
|
|
2283
|
+
const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
|
|
2284
|
+
return has("npm") ? "npm" : "unknown";
|
|
2285
|
+
}
|
|
2286
|
+
function globalInstallCommand(pm, spec) {
|
|
2287
|
+
switch (pm) {
|
|
2288
|
+
case "pnpm":
|
|
2289
|
+
return `pnpm add -g ${spec}`;
|
|
2290
|
+
case "bun":
|
|
2291
|
+
return `bun add -g ${spec}`;
|
|
2292
|
+
case "yarn":
|
|
2293
|
+
return `yarn global add ${spec}`;
|
|
2294
|
+
default:
|
|
2295
|
+
return `npm i -g ${spec}`;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
var LINUX_PMS, PLATFORM_LABEL;
|
|
2299
|
+
var init_platform = __esm({
|
|
2300
|
+
"src/platform.ts"() {
|
|
2301
|
+
"use strict";
|
|
2302
|
+
LINUX_PMS = [
|
|
2303
|
+
{ cmd: "apt-get", pm: "apt" },
|
|
2304
|
+
{ cmd: "dnf", pm: "dnf" },
|
|
2305
|
+
{ cmd: "pacman", pm: "pacman" },
|
|
2306
|
+
{ cmd: "zypper", pm: "zypper" },
|
|
2307
|
+
{ cmd: "apk", pm: "apk" }
|
|
2308
|
+
];
|
|
2309
|
+
PLATFORM_LABEL = {
|
|
2310
|
+
macos: "macOS",
|
|
2311
|
+
linux: "Linux",
|
|
2312
|
+
wsl: "WSL2 (Linux \u0432\u043D\u0443\u0442\u0440\u0438 Windows)",
|
|
2313
|
+
windows: "Windows (\u043D\u0430\u0442\u0438\u0432\u043D\u0430\u044F)"
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
|
|
2318
|
+
// src/envGate.ts
|
|
2319
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2320
|
+
import { join as join17 } from "path";
|
|
2321
|
+
import { createConnection } from "net";
|
|
2322
|
+
import { execa as execa3 } from "execa";
|
|
2323
|
+
function detectRequirements(repo, io = {}) {
|
|
2324
|
+
const exists = io.exists ?? existsSync11;
|
|
2325
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join17(repo, f)));
|
|
2326
|
+
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
2327
|
+
}
|
|
2328
|
+
async function dockerAlive() {
|
|
2329
|
+
try {
|
|
2330
|
+
const res = await execa3("docker", ["version", "--format", "{{.Server.Version}}"], {
|
|
2331
|
+
timeout: PROBE_TIMEOUT_MS2,
|
|
2332
|
+
reject: false
|
|
2333
|
+
});
|
|
2334
|
+
return res.exitCode === 0;
|
|
2335
|
+
} catch {
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
var PROBE_TIMEOUT_MS2, COMPOSE_MARKERS;
|
|
2340
|
+
var init_envGate = __esm({
|
|
2341
|
+
"src/envGate.ts"() {
|
|
2342
|
+
"use strict";
|
|
2343
|
+
init_platform();
|
|
2344
|
+
PROBE_TIMEOUT_MS2 = 2500;
|
|
2345
|
+
COMPOSE_MARKERS = [
|
|
2346
|
+
"docker-compose.test.yml",
|
|
2347
|
+
"docker-compose.test.yaml",
|
|
2348
|
+
"docker-compose.yml",
|
|
2349
|
+
"docker-compose.yaml",
|
|
2350
|
+
"compose.yml",
|
|
2351
|
+
"compose.yaml"
|
|
2352
|
+
];
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
// src/release.ts
|
|
2357
|
+
async function fetchLatestVersion(opts = {}) {
|
|
2358
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
2359
|
+
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
2360
|
+
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
2361
|
+
const ctrl = new AbortController();
|
|
2362
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
2363
|
+
try {
|
|
2364
|
+
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
2365
|
+
signal: ctrl.signal,
|
|
2366
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
2367
|
+
});
|
|
2368
|
+
if (!res.ok) return null;
|
|
2369
|
+
const body = await res.json();
|
|
2370
|
+
return typeof body.version === "string" && body.version ? body.version : null;
|
|
2371
|
+
} catch {
|
|
2372
|
+
return null;
|
|
2373
|
+
} finally {
|
|
2374
|
+
clearTimeout(timer);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function isOutdated(current, latest) {
|
|
2378
|
+
if (!latest || !current) return false;
|
|
2379
|
+
return compareVersionDirs(latest, current) > 0;
|
|
2380
|
+
}
|
|
2381
|
+
var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
|
|
2382
|
+
var init_release = __esm({
|
|
2383
|
+
"src/release.ts"() {
|
|
2384
|
+
"use strict";
|
|
2385
|
+
init_claudeBin();
|
|
2386
|
+
SWARM_PACKAGE = "@taskless-app/swarm";
|
|
2387
|
+
LATEST_TIMEOUT_MS = 4e3;
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
|
|
2391
|
+
// src/doctor.ts
|
|
2392
|
+
import { isAbsolute as isAbsolute4, join as join18 } from "path";
|
|
2393
|
+
function ok(id, title, detail) {
|
|
2394
|
+
return { id, title, status: "ok", detail, remedies: [], critical: false };
|
|
2395
|
+
}
|
|
2396
|
+
function nodeMajor(version) {
|
|
2397
|
+
const m = /^v?(\d+)/.exec(version.trim());
|
|
2398
|
+
return m ? Number(m[1]) : 0;
|
|
2399
|
+
}
|
|
2400
|
+
function checkNode(io) {
|
|
2401
|
+
const major = nodeMajor(io.nodeVersion);
|
|
2402
|
+
if (major >= MIN_NODE_MAJOR) return ok("node", "Node.js", `${io.nodeVersion} (\u043D\u0443\u0436\u043D\u043E \u2265 ${MIN_NODE_MAJOR})`);
|
|
2403
|
+
const command = io.platform.kind === "macos" ? `brew install node@${MIN_NODE_MAJOR}` : io.platform.kind === "windows" ? `winget install OpenJS.NodeJS.LTS` : `curl -fsSL https://fnm.vercel.app/install | bash && fnm install ${MIN_NODE_MAJOR}`;
|
|
2404
|
+
return {
|
|
2405
|
+
id: "node",
|
|
2406
|
+
title: "Node.js",
|
|
2407
|
+
status: "fail",
|
|
2408
|
+
detail: `${io.nodeVersion} \u2014 \u0441\u0442\u0430\u0440\u0435\u0435 ${MIN_NODE_MAJOR}. \u0420\u043E\u0439 \u043D\u0430 \u043D\u0451\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442`,
|
|
2409
|
+
remedies: [{ what: `Node ${MIN_NODE_MAJOR}+`, command, sudo: false, auto: false }],
|
|
2410
|
+
critical: true
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
function checkClaude(io) {
|
|
2414
|
+
const bin = resolveClaudeBin();
|
|
2415
|
+
if (io.hasCommand(bin)) {
|
|
2416
|
+
return ok("claude", "Claude Code (claude)", bin === "claude" ? "\u043D\u0430\u0439\u0434\u0435\u043D \u0432 PATH" : `\u043D\u0430\u0439\u0434\u0435\u043D: ${bin}`);
|
|
2417
|
+
}
|
|
2418
|
+
const remedies = [
|
|
2419
|
+
{
|
|
2420
|
+
what: "Claude Code CLI \u2014 \u0438\u043C \u0434\u0440\u043E\u043D \u043F\u0438\u0448\u0435\u0442 \u043A\u043E\u0434",
|
|
2421
|
+
command: globalInstallCommand(io.globalPm === "sources" ? "npm" : io.globalPm, "@anthropic-ai/claude-code"),
|
|
2422
|
+
sudo: false,
|
|
2423
|
+
auto: true
|
|
2424
|
+
}
|
|
2425
|
+
];
|
|
2426
|
+
const detail = io.platform.kind === "windows" ? "\u0432 PATH \u043D\u0435\u0442 `claude`. \u041D\u0430 Windows spawn \u0438\u0449\u0435\u0442 \u0444\u0430\u0439\u043B \u043F\u043E PATHEXT \u2014 bash-\u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u0431\u0435\u0437 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0435\u043C\u0443 \u043D\u0435 \u0432\u0438\u0434\u043D\u0430; \u043D\u0435\u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442\u043D\u0430\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044F SWARM_CLAUDE_BIN" : "\u0432 PATH \u043D\u0435\u0442 `claude` \u2014 \u0434\u0440\u043E\u043D\u0443 \u043D\u0435\u0447\u0435\u043C \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434";
|
|
2427
|
+
return { id: "claude", title: "Claude Code (claude)", status: "fail", detail, remedies, critical: true };
|
|
2428
|
+
}
|
|
2429
|
+
async function checkGh(io) {
|
|
2430
|
+
if (!io.hasCommand("gh")) {
|
|
2431
|
+
const viaSystem = systemInstallCommand(io.platform, ["gh"]);
|
|
2432
|
+
return {
|
|
2433
|
+
id: "gh",
|
|
2434
|
+
title: "GitHub CLI (gh)",
|
|
2435
|
+
status: "fail",
|
|
2436
|
+
detail: "\u0432 PATH \u043D\u0435\u0442 `gh` \u2014 \u0434\u0440\u043E\u043D\u0443 \u043D\u0435\u0447\u0435\u043C \u043E\u0442\u043A\u0440\u044B\u0442\u044C pull request",
|
|
2437
|
+
remedies: [
|
|
2438
|
+
viaSystem ? { what: "GitHub CLI", command: viaSystem, sudo: io.platform.needsSudo, auto: true } : { what: "GitHub CLI \u2014 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C \u0441\u0432\u043E\u0435\u0439 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B", command: "\u0441\u043C. https://github.com/cli/cli#installation", sudo: false, auto: false },
|
|
2439
|
+
{ what: "\u0432\u0445\u043E\u0434 \u0432 GitHub (\u043F\u043E\u0441\u043B\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438)", command: "gh auth login", sudo: false, auto: false }
|
|
2440
|
+
],
|
|
2441
|
+
critical: true
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
const auth = await io.run("gh auth status");
|
|
2445
|
+
if (auth.exitCode === 0) return ok("gh", "GitHub CLI (gh)", "\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u0432\u0445\u043E\u0434 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D");
|
|
2446
|
+
return {
|
|
2447
|
+
id: "gh",
|
|
2448
|
+
title: "GitHub CLI (gh)",
|
|
2449
|
+
status: "fail",
|
|
2450
|
+
detail: "`gh` \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u043D\u043E \u0432\u0445\u043E\u0434 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u2014 `gh pr create` \u0443\u043F\u0430\u0434\u0451\u0442 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0440\u0430\u0431\u043E\u0442\u044B \u0434\u0440\u043E\u043D\u0430",
|
|
2451
|
+
// Интерактивный вход за человека не сделаешь: он идёт через браузер и код подтверждения.
|
|
2452
|
+
remedies: [{ what: "\u0432\u0445\u043E\u0434 \u0432 GitHub", command: "gh auth login", sudo: false, auto: false }],
|
|
2453
|
+
critical: true
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
function checkSwarmPackage(io) {
|
|
2457
|
+
const globalBin = io.globalSwarmBin();
|
|
2458
|
+
if (io.globalPm === "sources") {
|
|
2459
|
+
return {
|
|
2460
|
+
id: "package",
|
|
2461
|
+
title: "\u041F\u0430\u043A\u0435\u0442 \u0440\u043E\u044F",
|
|
2462
|
+
status: "warn",
|
|
2463
|
+
detail: `\u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u044B \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0418\u0417 \u0418\u0421\u0425\u041E\u0414\u041D\u0418\u041A\u041E\u0412 (${io.entry}), \u0430 \u043D\u0435 \u0438\u0437 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430. \u042D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E \u0434\u043B\u044F \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0440\u043E\u044F, \u043D\u043E \u0434\u0435\u043C\u043E\u043D, \u043F\u043E\u0434\u043D\u044F\u0442\u044B\u0439 \u0442\u0430\u043A, \u0436\u0438\u0432\u0451\u0442 \u043D\u0430 \u043A\u043E\u0434\u0435 \u0440\u0430\u0431\u043E\u0447\u0435\u0433\u043E \u0434\u0435\u0440\u0435\u0432\u0430 \u2014 \u043D\u0435 \u043D\u0430 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438` + (globalBin ? `. \u0413\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0430\u043A\u0435\u0442 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0435\u0441\u0442\u044C: ${globalBin}` : ", \u0438 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442"),
|
|
2464
|
+
remedies: globalBin ? [{ what: "\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u0440\u043E\u0439 \u0438\u0437 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430", command: "swarm start", sudo: false, auto: false }] : [{ what: `${SWARM_PACKAGE} \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E`, command: globalInstallCommand("npm", SWARM_PACKAGE), sudo: false, auto: false }],
|
|
2465
|
+
critical: false
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
if (!globalBin) {
|
|
2469
|
+
return {
|
|
2470
|
+
id: "package",
|
|
2471
|
+
title: "\u041F\u0430\u043A\u0435\u0442 \u0440\u043E\u044F",
|
|
2472
|
+
status: "warn",
|
|
2473
|
+
detail: `\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \`swarm\` \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 PATH \u2014 \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0440\u0430\u0437\u043E\u0432\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A (${io.entry}). \u041F\u043E\u0441\u043B\u0435 \u043D\u0435\u0433\u043E \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F`,
|
|
2474
|
+
remedies: [{ what: `${SWARM_PACKAGE} \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E`, command: globalInstallCommand(io.globalPm, SWARM_PACKAGE), sudo: false, auto: true }],
|
|
2475
|
+
critical: false
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
return ok("package", "\u041F\u0430\u043A\u0435\u0442 \u0440\u043E\u044F", `v${io.packageVersion}, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E: ${globalBin}`);
|
|
2479
|
+
}
|
|
2480
|
+
async function checkSwarmVersion(io) {
|
|
2481
|
+
const latest = await io.latestVersion();
|
|
2482
|
+
const cmp = latest === null ? 0 : compareVersionDirs(io.packageVersion, latest);
|
|
2483
|
+
if (latest === null) {
|
|
2484
|
+
return {
|
|
2485
|
+
id: "version",
|
|
2486
|
+
title: "\u0412\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F",
|
|
2487
|
+
status: "warn",
|
|
2488
|
+
detail: `v${io.packageVersion}; \u0434\u043E registry.npmjs.org \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u2014 \u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u043D\u0435\u0447\u0435\u043C`,
|
|
2489
|
+
remedies: [],
|
|
2490
|
+
critical: false
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
if (cmp >= 0) {
|
|
2494
|
+
return ok("version", "\u0412\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F", cmp > 0 ? `v${io.packageVersion} \u2014 \u043D\u043E\u0432\u0435\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 v${latest}` : `v${io.packageVersion} \u2014 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439`);
|
|
2495
|
+
}
|
|
2496
|
+
return {
|
|
2497
|
+
id: "version",
|
|
2498
|
+
title: "\u0412\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F",
|
|
2499
|
+
status: "warn",
|
|
2500
|
+
detail: `v${io.packageVersion} \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest}`,
|
|
2501
|
+
// auto:false СОЗНАТЕЛЬНО: обновление своей же программы из своего же процесса — это спавн
|
|
2502
|
+
// `swarm update` изнутри `swarm install`, а перепроверить результат нечем (версия ЭТОГО
|
|
2503
|
+
// процесса после обновления пакета не меняется, он живёт на старом коде до перезапуска).
|
|
2504
|
+
// Раньше на этом цикл доводки крутил `swarm update` трижды и всё равно оставлял пункт жёлтым.
|
|
2505
|
+
remedies: [{ what: `\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C ${SWARM_PACKAGE} \u0434\u043E v${latest}`, command: "swarm update", sudo: false, auto: false }],
|
|
2506
|
+
critical: false
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
function chromiumRemedies(kind, io) {
|
|
2510
|
+
if (kind === "no-module") {
|
|
2511
|
+
const pm = io.globalPm === "sources" || io.globalPm === "unknown" ? "npm" : io.globalPm;
|
|
2512
|
+
return [
|
|
2513
|
+
{ what: "playwright-core \u0440\u044F\u0434\u043E\u043C \u0441 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u043C CLI", command: globalInstallCommand(pm, "playwright-core"), sudo: false, auto: true },
|
|
2514
|
+
{ what: "\u0441\u0430\u043C \u0431\u0440\u0430\u0443\u0437\u0435\u0440 Chromium", command: "npx playwright install chromium", sudo: false, auto: true }
|
|
2515
|
+
];
|
|
2516
|
+
}
|
|
2517
|
+
if (kind === "no-browser") {
|
|
2518
|
+
return [{ what: "\u0441\u0430\u043C \u0431\u0440\u0430\u0443\u0437\u0435\u0440 Chromium (~150 \u041C\u0411)", command: "npx playwright install chromium", sudo: false, auto: true }];
|
|
2519
|
+
}
|
|
2520
|
+
if (kind === "no-libs") {
|
|
2521
|
+
if (io.platform.pm === "apt") {
|
|
2522
|
+
return [
|
|
2523
|
+
{
|
|
2524
|
+
what: "\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0435 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 Chromium (libnss3, libatk, libgbm \u0438 \u0434\u0440.) \u2014 \u0442\u043E\u0447\u043D\u044B\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u043D\u0430\u0435\u0442 \u0441\u0430\u043C Playwright",
|
|
2525
|
+
command: `${io.platform.needsSudo ? "sudo " : ""}npx playwright install-deps chromium`,
|
|
2526
|
+
sudo: io.platform.needsSudo,
|
|
2527
|
+
auto: true
|
|
2528
|
+
}
|
|
2529
|
+
];
|
|
2530
|
+
}
|
|
2531
|
+
const libs = io.platform.pm ? CHROMIUM_LIBS[io.platform.pm] : void 0;
|
|
2532
|
+
const command = libs ? systemInstallCommand(io.platform, libs) : null;
|
|
2533
|
+
if (command) {
|
|
2534
|
+
return [
|
|
2535
|
+
{
|
|
2536
|
+
what: `\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0435 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 Chromium (\u0441\u043F\u0438\u0441\u043E\u043A \u0434\u043B\u044F ${io.platform.pm} \u2014 \u043D\u0430\u0448, \u0443 Playwright \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u043A\u0438 \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430 \u043D\u0435\u0442; \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u043E\u043B\u043D\u044B\u043C)`,
|
|
2537
|
+
command,
|
|
2538
|
+
sudo: io.platform.needsSudo,
|
|
2539
|
+
auto: true
|
|
2540
|
+
}
|
|
2541
|
+
];
|
|
2542
|
+
}
|
|
2543
|
+
return [
|
|
2544
|
+
{
|
|
2545
|
+
what: "\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0435 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 Chromium \u2014 \u043F\u0430\u043A\u0435\u0442\u043D\u043E\u0433\u043E \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u0430 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435 \u043D\u0430\u0448\u043B\u0438, \u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043C\u0438 \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430",
|
|
2546
|
+
command: "nss nspr atk at-spi2-atk cups libdrm libXcomposite libXdamage libXfixes libXrandr libgbm alsa pango cairo",
|
|
2547
|
+
sudo: false,
|
|
2548
|
+
auto: false
|
|
2549
|
+
}
|
|
2550
|
+
];
|
|
2551
|
+
}
|
|
2552
|
+
return [];
|
|
2553
|
+
}
|
|
2554
|
+
async function checkScreenshots(io) {
|
|
2555
|
+
const probe = await io.probeChromium();
|
|
2556
|
+
if (probe.ok) return ok("screenshots", "\u0421\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B (headless Chromium)", "Chromium \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u2014 `swarm shot` \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442");
|
|
2557
|
+
return {
|
|
2558
|
+
id: "screenshots",
|
|
2559
|
+
title: "\u0421\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B (headless Chromium)",
|
|
2560
|
+
status: "fail",
|
|
2561
|
+
// Говорим ПОСЛЕДСТВИЕ, а не только причину: скрины — обязательная часть сдачи UI-задач,
|
|
2562
|
+
// и человек должен понимать, что именно он потеряет, если оставит пункт красным.
|
|
2563
|
+
detail: `Chromium \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u2014 \u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B, UI-\u0437\u0430\u0434\u0430\u0447\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u0434\u0430\u0432\u0430\u0442\u044C\u0441\u044F \u0431\u0435\u0437 \u043D\u0438\u0445. ${probe.message}`,
|
|
2564
|
+
remedies: probe.kind ? chromiumRemedies(probe.kind, io) : [],
|
|
2565
|
+
critical: false
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
async function checkDocker(io, repoPaths) {
|
|
2569
|
+
const needed = repoPaths.filter((repo) => detectRequirements(repo, { exists: io.exists }).some((r) => r.kind === "docker"));
|
|
2570
|
+
if (needed.length === 0) {
|
|
2571
|
+
return ok("docker", "Docker", "\u043D\u0438 \u043E\u0434\u043D\u043E\u043C\u0443 \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u043D\u0435 \u043D\u0443\u0436\u0435\u043D (\u043D\u0435\u0442 compose-\u0444\u0430\u0439\u043B\u0430) \u2014 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E \u0438 \u043D\u0435 \u0441\u0442\u0430\u0432\u043B\u044E");
|
|
2572
|
+
}
|
|
2573
|
+
if (await io.dockerAlive()) return ok("docker", "Docker", `\u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442; \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u043C: ${needed.join(", ")}`);
|
|
2574
|
+
const installed = io.hasCommand("docker");
|
|
2575
|
+
const remedies = installed ? [{ what: "\u0437\u0430\u043F\u0443\u0441\u0442\u0438 Docker (Desktop / `systemctl start docker`) \u2014 \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442", command: io.platform.kind === "macos" ? "open -a Docker" : "sudo systemctl start docker", sudo: io.platform.kind !== "macos", auto: false }] : [
|
|
2576
|
+
io.platform.kind === "macos" ? { what: "Docker Desktop", command: "brew install --cask docker", sudo: false, auto: false } : { what: "Docker Engine \u2014 \u0441\u043F\u043E\u0441\u043E\u0431 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430", command: "\u0441\u043C. https://docs.docker.com/engine/install/", sudo: false, auto: false }
|
|
2577
|
+
];
|
|
2578
|
+
return {
|
|
2579
|
+
id: "docker",
|
|
2580
|
+
title: "Docker",
|
|
2581
|
+
status: "warn",
|
|
2582
|
+
detail: `${installed ? "\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u043D\u043E \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442" : "\u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D"}; \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u043C: ${needed.join(", ")} \u2014 \u0438\u0445 \u0434\u0440\u043E\u043D\u044B \u0431\u0443\u0434\u0443\u0442 \u043F\u0430\u0434\u0430\u0442\u044C \u043D\u0430 \u0442\u0435\u0441\u0442\u0430\u0445 \u0432 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u0435`,
|
|
2583
|
+
remedies,
|
|
2584
|
+
critical: false
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
function checkRepos(io, snap) {
|
|
2588
|
+
const title = "\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0438 SWARM_REPO";
|
|
2589
|
+
const env = snap.envRepo.trim();
|
|
2590
|
+
if (env && !isAbsolute4(env)) {
|
|
2591
|
+
return {
|
|
2592
|
+
id: "repos",
|
|
2593
|
+
title,
|
|
2594
|
+
status: "fail",
|
|
2595
|
+
detail: `SWARM_REPO \u0437\u0430\u0434\u0430\u043D \u041E\u0422\u041D\u041E\u0421\u0418\u0422\u0415\u041B\u042C\u041D\u042B\u041C \u043F\u0443\u0442\u0451\u043C \xAB${env}\xBB \u2014 \u043E\u043D \u0441\u043E\u0431\u0435\u0440\u0451\u0442\u0441\u044F \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430, \u0438 \u0434\u0440\u043E\u043D \u0443\u043C\u0440\u0451\u0442 \u043D\u0430 \`cannot change to \u2026\` (\u0442\u0430\u043A 09.08 \u043F\u043E\u0434\u0440\u044F\u0434 \u043F\u043E\u0433\u0438\u0431\u043B\u0438 \u0432\u043E\u0441\u0435\u043C\u044C \u0434\u0440\u043E\u043D\u043E\u0432)`,
|
|
2596
|
+
remedies: [{ what: "\u0443\u0431\u0440\u0430\u0442\u044C \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0443\u044E \u0432\u043E\u0432\u0441\u0435 \u2014 \u0440\u043E\u0439 \u043D\u0430\u0445\u043E\u0434\u0438\u0442 \u043A\u043B\u043E\u043D\u044B \u0441\u0430\u043C (TASWM-336) \u2014 \u043B\u0438\u0431\u043E \u0437\u0430\u0434\u0430\u0442\u044C \u0410\u0411\u0421\u041E\u041B\u042E\u0422\u041D\u042B\u0419 \u043F\u0443\u0442\u044C", command: "unset SWARM_REPO", sudo: false, auto: false }],
|
|
2597
|
+
critical: true
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
if (env && !io.exists(join18(env, ".git"))) {
|
|
2601
|
+
return {
|
|
2602
|
+
id: "repos",
|
|
2603
|
+
title,
|
|
2604
|
+
status: "fail",
|
|
2605
|
+
detail: `SWARM_REPO \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043D\u0430 \xAB${env}\xBB, \u0430 git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u0442\u0430\u043C \u043D\u0435\u0442`,
|
|
2606
|
+
remedies: [{ what: "\u043F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043F\u0443\u0442\u044C \u0438\u043B\u0438 \u0443\u0431\u0440\u0430\u0442\u044C \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0443\u044E \u2014 \u0440\u043E\u0439 \u043D\u0430\u0439\u0434\u0451\u0442 \u043A\u043B\u043E\u043D\u044B \u0441\u0430\u043C", command: "unset SWARM_REPO", sudo: false, auto: false }],
|
|
2607
|
+
critical: true
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
const broken = snap.paths.filter((p) => !io.exists(join18(p, ".git")));
|
|
2611
|
+
if (broken.length) {
|
|
2612
|
+
return {
|
|
2613
|
+
id: "repos",
|
|
2614
|
+
title,
|
|
2615
|
+
status: "fail",
|
|
2616
|
+
detail: `\u0440\u043E\u0439 \u043F\u043E\u043C\u043D\u0438\u0442 \u043F\u0443\u0442\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u0443\u0436\u0435 \u043D\u0435\u0442: ${broken.join(", ")}`,
|
|
2617
|
+
remedies: [{ what: "\u0443\u043A\u0430\u0437\u0430\u0442\u044C \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C \u2014 \u043E\u043D \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u0441\u044F", command: "swarm start --repo <\u043F\u0443\u0442\u044C \u043A \u043A\u043B\u043E\u043D\u0443>", sudo: false, auto: false }],
|
|
2618
|
+
critical: false
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
if (snap.missing.length) {
|
|
2622
|
+
return {
|
|
2623
|
+
id: "repos",
|
|
2624
|
+
title,
|
|
2625
|
+
status: "warn",
|
|
2626
|
+
// Рой сознательно НЕ клонирует сам (решение владельца 10.08: диск чужой).
|
|
2627
|
+
detail: `\u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435: ${snap.missing.join(", ")} \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u044D\u0442\u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0431\u0443\u0434\u0443\u0442 \u0441\u0442\u043E\u044F\u0442\u044C. \u0420\u043E\u0439 \u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u0435\u0442`,
|
|
2628
|
+
remedies: [{ what: "\u0441\u043A\u043B\u043E\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u0443\u0436\u043D\u044B\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433", command: "git clone <url>", sudo: false, auto: false }],
|
|
2629
|
+
critical: false
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
if (snap.paths.length === 0) {
|
|
2633
|
+
return {
|
|
2634
|
+
id: "repos",
|
|
2635
|
+
title,
|
|
2636
|
+
status: "warn",
|
|
2637
|
+
detail: "\u0440\u043E\u0439 \u0435\u0449\u0451 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u043B Taskless, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0433\u043E \u2014 \u0443\u0437\u043D\u0430\u0435\u0442 \u043F\u0440\u0438 \u043F\u0435\u0440\u0432\u043E\u043C `swarm start` \u0438 \u043D\u0430\u0439\u0434\u0451\u0442 \u0438\u0445 \u043A\u043B\u043E\u043D\u044B \u0441\u0430\u043C",
|
|
2638
|
+
remedies: [],
|
|
2639
|
+
critical: false
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
return ok("repos", title, `${snap.paths.length} \u043A\u043B\u043E\u043D(\u043E\u0432) \u043D\u0430 \u043C\u0435\u0441\u0442\u0435: ${snap.paths.join(", ")}${env ? ` (SWARM_REPO=${env})` : ""}`);
|
|
2643
|
+
}
|
|
2644
|
+
function checkToken(state) {
|
|
2645
|
+
const title = "\u0412\u0445\u043E\u0434 \u0432 Taskless";
|
|
2646
|
+
if (state === "ok") return ok("token", title, "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u0438 \u043F\u0440\u0438\u043D\u044F\u0442 \u2014 \u043F\u0435\u0440\u0435\u043B\u043E\u0433\u0438\u043D \u043D\u0435 \u043D\u0443\u0436\u0435\u043D");
|
|
2647
|
+
if (state === "unreachable") {
|
|
2648
|
+
return { id: "token", title, status: "warn", detail: "\u0442\u043E\u043A\u0435\u043D \u0435\u0441\u0442\u044C, \u043D\u043E \u0434\u043E Taskless \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0447\u0435\u043C", remedies: [], critical: false };
|
|
2649
|
+
}
|
|
2650
|
+
if (state === "revoked") {
|
|
2651
|
+
return {
|
|
2652
|
+
id: "token",
|
|
2653
|
+
title,
|
|
2654
|
+
status: "fail",
|
|
2655
|
+
detail: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u041D\u0415 \u043F\u0440\u0438\u043D\u044F\u0442 (401): \u0441\u0431\u0440\u043E\u0448\u0435\u043D \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C \u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C. \u041F\u043E\u043A\u0430 \u044D\u0442\u043E \u0442\u0430\u043A, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F",
|
|
2656
|
+
remedies: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0437\u0430\u043D\u043E\u0432\u043E", command: "swarm login", sudo: false, auto: false }],
|
|
2657
|
+
critical: true
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
return {
|
|
2661
|
+
id: "token",
|
|
2662
|
+
title,
|
|
2663
|
+
status: "warn",
|
|
2664
|
+
detail: "\u0432\u0445\u043E\u0434 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u2014 \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F, \u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0443 \u0438\u0437 Taskless \u0431\u0440\u0430\u0442\u044C \u043D\u0435 \u0431\u0443\u0434\u0435\u0442",
|
|
2665
|
+
// Логин интерактивен (браузер + выбор воркспейса) — за человека его не сделаешь.
|
|
2666
|
+
remedies: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441", command: "swarm login", sudo: false, auto: false }],
|
|
2667
|
+
critical: false
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
function checkDaemon(snap, opts) {
|
|
2671
|
+
if (!snap.alive) return ok("daemon", "\u0414\u0435\u043C\u043E\u043D \u0440\u043E\u044F", "\u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 \u2014 \u043F\u043E\u0434\u043D\u044F\u0442\u044C: swarm start");
|
|
2672
|
+
const who = `v${snap.version ?? "?"}${snap.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${snap.hostName}\xBB` : ""}`;
|
|
2673
|
+
if (snap.version && compareVersionDirs(opts.packageVersion, snap.version) > 0) {
|
|
2674
|
+
return {
|
|
2675
|
+
id: "daemon",
|
|
2676
|
+
title: "\u0414\u0435\u043C\u043E\u043D \u0440\u043E\u044F",
|
|
2677
|
+
status: "warn",
|
|
2678
|
+
detail: `\u043F\u043E\u0434\u043D\u044F\u0442 \u043D\u0430 \u0421\u0422\u0410\u0420\u041E\u041C \u043A\u043E\u0434\u0435 (${who}), \u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u0430\u043A\u0435\u0442 v${opts.packageVersion}. Node \u0434\u0435\u0440\u0436\u0438\u0442 \u043C\u043E\u0434\u0443\u043B\u044C-\u0433\u0440\u0430\u0444 \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u0441\u0442\u0430\u0440\u0442\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u2014 \u0441\u0430\u043C \u043E\u043D \u043D\u0430 \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u0434 \u043D\u0435 \u043F\u0435\u0440\u0435\u0435\u0434\u0435\u0442`,
|
|
2679
|
+
// Перезапуск за человека не делаем: у демона могут быть живые дроны, и обрыв их работы —
|
|
2680
|
+
// его решение, а не наше (`swarm stop` сам покажет, кто работает).
|
|
2681
|
+
remedies: [{ what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u043D\u043E\u0432\u043E\u043C \u043A\u043E\u0434\u0435", command: "swarm stop && swarm start", sudo: false, auto: false }],
|
|
2682
|
+
critical: false
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
return ok(
|
|
2686
|
+
"daemon",
|
|
2687
|
+
"\u0414\u0435\u043C\u043E\u043D \u0440\u043E\u044F",
|
|
2688
|
+
opts.releaseOutdated ? `\u0443\u0436\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 (${who}) \u2014 \u0432\u0442\u043E\u0440\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u043D\u0430 \u0442\u043E\u0442 \u0436\u0435 \u043F\u043E\u0440\u0442 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E. \u041F\u043E\u0441\u043B\u0435 \`swarm update\` \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438: swarm stop && swarm start` : `\u0443\u0436\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 (${who}) \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E. \u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A: swarm stop && swarm start`
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
async function runCheck(id, io) {
|
|
2692
|
+
switch (id) {
|
|
2693
|
+
case "node":
|
|
2694
|
+
return checkNode(io);
|
|
2695
|
+
case "package":
|
|
2696
|
+
return checkSwarmPackage(io);
|
|
2697
|
+
case "version":
|
|
2698
|
+
return checkSwarmVersion(io);
|
|
2699
|
+
case "claude":
|
|
2700
|
+
return checkClaude(io);
|
|
2701
|
+
case "gh":
|
|
2702
|
+
return checkGh(io);
|
|
2703
|
+
case "screenshots":
|
|
2704
|
+
return checkScreenshots(io);
|
|
2705
|
+
case "docker":
|
|
2706
|
+
return checkDocker(io, (await io.repos()).paths);
|
|
2707
|
+
case "repos":
|
|
2708
|
+
return checkRepos(io, await io.repos());
|
|
2709
|
+
case "token":
|
|
2710
|
+
return checkToken(await io.token());
|
|
2711
|
+
default:
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
async function runDoctor(io) {
|
|
2716
|
+
const results = (await Promise.all(CHECK_ORDER.map((id) => runCheck(id, io)))).filter((r) => r !== null);
|
|
2717
|
+
results.push(
|
|
2718
|
+
checkDaemon(await io.daemon(), {
|
|
2719
|
+
packageVersion: io.packageVersion,
|
|
2720
|
+
// Спрашиваем ту же величину заново, а не ищем магическую строку в чужих remedies:
|
|
2721
|
+
// latestVersion мемоизирован, а переименование команды не должно молча ломать текст.
|
|
2722
|
+
releaseOutdated: isOutdated(io.packageVersion, await io.latestVersion())
|
|
2723
|
+
})
|
|
2724
|
+
);
|
|
2725
|
+
return results;
|
|
2726
|
+
}
|
|
2727
|
+
function renderReport(results) {
|
|
2728
|
+
const lines = [];
|
|
2729
|
+
for (const r of results) {
|
|
2730
|
+
lines.push(`${MARK[r.status]} ${r.title}: ${r.detail}`);
|
|
2731
|
+
if (r.status === "ok") continue;
|
|
2732
|
+
for (const rem of r.remedies) {
|
|
2733
|
+
lines.push(` ${rem.what}${rem.sudo ? " [\u043D\u0443\u0436\u0435\u043D sudo]" : ""}`);
|
|
2734
|
+
lines.push(` $ ${rem.command}`);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return lines;
|
|
2738
|
+
}
|
|
2739
|
+
function summarize(results) {
|
|
2740
|
+
const fails = results.filter((r) => r.status === "fail");
|
|
2741
|
+
const warns = results.filter((r) => r.status === "warn");
|
|
2742
|
+
if (fails.length === 0 && warns.length === 0) return { line: "\u0433\u043E\u0442\u043E\u0432\u043E: \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0432 \u0440\u0430\u0431\u043E\u0447\u0435\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438", exitCode: 0 };
|
|
2743
|
+
if (fails.length === 0) {
|
|
2744
|
+
return { line: `\u0440\u043E\u0439 \u043F\u043E\u0435\u0434\u0435\u0442, \u043D\u043E \u0435\u0441\u0442\u044C \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F (${warns.length}): ${warns.map((r) => r.title).join(", ")}`, exitCode: 0 };
|
|
2745
|
+
}
|
|
2746
|
+
const critical = fails.filter((r) => r.critical);
|
|
2747
|
+
return {
|
|
2748
|
+
line: critical.length ? `\u0440\u043E\u0439 \u041D\u0415 \u043F\u043E\u0435\u0434\u0435\u0442, \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0435\u043D\u043E: ${critical.map((r) => r.title).join(", ")}` + (fails.length > critical.length ? `; \u043A\u0440\u043E\u043C\u0435 \u0442\u043E\u0433\u043E \u0441\u043B\u043E\u043C\u0430\u043D\u043E: ${fails.filter((r) => !r.critical).map((r) => r.title).join(", ")}` : "") : `\u0440\u043E\u0439 \u043F\u043E\u0435\u0434\u0435\u0442, \u043D\u043E \u0441\u043B\u043E\u043C\u0430\u043D\u043E: ${fails.map((r) => r.title).join(", ")}`,
|
|
2749
|
+
exitCode: 1
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
var CHROMIUM_LIBS, CHECK_ORDER, MARK;
|
|
2753
|
+
var init_doctor = __esm({
|
|
2754
|
+
"src/doctor.ts"() {
|
|
2755
|
+
"use strict";
|
|
2756
|
+
init_claudeBin();
|
|
2757
|
+
init_envGate();
|
|
2758
|
+
init_platform();
|
|
2759
|
+
init_release();
|
|
2760
|
+
init_version();
|
|
2761
|
+
CHROMIUM_LIBS = {
|
|
2762
|
+
dnf: ["nss", "nspr", "atk", "at-spi2-atk", "cups-libs", "libdrm", "libXcomposite", "libXdamage", "libXfixes", "libXrandr", "mesa-libgbm", "alsa-lib", "pango", "cairo"],
|
|
2763
|
+
pacman: ["nss", "nspr", "atk", "at-spi2-atk", "libcups", "libdrm", "libxcomposite", "libxdamage", "libxfixes", "libxrandr", "mesa", "alsa-lib", "pango", "cairo"],
|
|
2764
|
+
zypper: ["mozilla-nss", "mozilla-nspr", "libatk-1_0-0", "at-spi2-atk", "cups-libs", "libdrm2", "libXcomposite1", "libXdamage1", "libXfixes3", "libXrandr2", "libgbm1", "alsa", "pango", "cairo"],
|
|
2765
|
+
apk: ["nss", "nspr", "atk", "at-spi2-atk", "cups-libs", "libdrm", "libxcomposite", "libxdamage", "libxfixes", "libxrandr", "mesa-gbm", "alsa-lib", "pango", "cairo"]
|
|
2766
|
+
};
|
|
2767
|
+
CHECK_ORDER = ["node", "package", "version", "claude", "gh", "screenshots", "docker", "repos", "token"];
|
|
2768
|
+
MARK = { ok: "\u2713", warn: "\u26A0", fail: "\u2715" };
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
// src/repoMemory.ts
|
|
2773
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
|
|
2774
|
+
import { join as join19 } from "path";
|
|
2775
|
+
function loadRememberedRepos(home) {
|
|
2776
|
+
let raw;
|
|
2777
|
+
try {
|
|
2778
|
+
raw = readFileSync15(join19(home, FILE), "utf8");
|
|
2779
|
+
} catch {
|
|
2780
|
+
return [];
|
|
2781
|
+
}
|
|
2782
|
+
try {
|
|
2783
|
+
const parsed = JSON.parse(raw);
|
|
2784
|
+
if (!Array.isArray(parsed.paths)) return [];
|
|
2785
|
+
return parsed.paths.filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
2786
|
+
} catch {
|
|
2787
|
+
return [];
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
var FILE;
|
|
2791
|
+
var init_repoMemory = __esm({
|
|
2792
|
+
"src/repoMemory.ts"() {
|
|
2793
|
+
"use strict";
|
|
2794
|
+
FILE = "repos.json";
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
|
|
2798
|
+
// src/installCli.ts
|
|
2799
|
+
var installCli_exports = {};
|
|
2800
|
+
__export(installCli_exports, {
|
|
2801
|
+
CHROMIUM_PROBE_MS: () => CHROMIUM_PROBE_MS,
|
|
2802
|
+
MAX_FIX_ROUNDS: () => MAX_FIX_ROUNDS,
|
|
2803
|
+
classifyChromiumError: () => classifyChromiumError,
|
|
2804
|
+
defaultDoctorIO: () => defaultDoctorIO,
|
|
2805
|
+
nextSteps: () => nextSteps,
|
|
2806
|
+
probeChromium: () => probeChromium,
|
|
2807
|
+
readReposSnapshot: () => readReposSnapshot,
|
|
2808
|
+
readTokenState: () => readTokenState,
|
|
2809
|
+
runDoctorCli: () => runDoctorCli,
|
|
2810
|
+
runInstallCli: () => runInstallCli,
|
|
2811
|
+
shortenError: () => shortenError
|
|
2812
|
+
});
|
|
2813
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2814
|
+
function classifyChromiumError(message) {
|
|
2815
|
+
if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
|
|
2816
|
+
if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
|
|
2817
|
+
if (/Executable doesn't exist|playwright install|browserType\.launch: Executable/i.test(message)) return "no-browser";
|
|
2818
|
+
return "other";
|
|
2819
|
+
}
|
|
2820
|
+
function shortenError(message, maxLines = 3) {
|
|
2821
|
+
return message.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, maxLines).join(" \xB7 ");
|
|
2822
|
+
}
|
|
2823
|
+
async function probeChromium(launch = () => launchChromium({ timeoutMs: CHROMIUM_PROBE_MS })) {
|
|
2824
|
+
try {
|
|
2825
|
+
const browser = await launch();
|
|
2826
|
+
await browser.close().catch(() => {
|
|
2827
|
+
});
|
|
2828
|
+
return { ok: true, kind: null, message: "" };
|
|
2829
|
+
} catch (e) {
|
|
2830
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
2831
|
+
return { ok: false, kind: classifyChromiumError(raw), message: shortenError(raw) };
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function readReposSnapshot(st) {
|
|
2835
|
+
const envRepo = SWARM_REPO;
|
|
2836
|
+
if (st?.repos?.length) {
|
|
2837
|
+
return {
|
|
2838
|
+
envRepo,
|
|
2839
|
+
paths: st.repos.flatMap((r) => r.path ? [r.path] : []),
|
|
2840
|
+
missing: st.repos.filter((r) => !r.path).map((r) => r.repoFullName)
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
const cached = readRepoMapCache(REPO_MAP_PATH).map((e) => e.path);
|
|
2844
|
+
const paths = [];
|
|
2845
|
+
for (const p of [...SWARM_REPO_PATHS, ...cached, ...loadRememberedRepos(SWARM_HOME)]) {
|
|
2846
|
+
if (p && !paths.includes(p)) paths.push(p);
|
|
2847
|
+
}
|
|
2848
|
+
return { envRepo, paths, missing: [] };
|
|
2849
|
+
}
|
|
2850
|
+
async function readTokenState() {
|
|
2851
|
+
const store = loadAgentToken();
|
|
2852
|
+
if (!store) return "none";
|
|
2853
|
+
return probeAgentToken({ api: TASKLESS_API, token: store.token });
|
|
2854
|
+
}
|
|
2855
|
+
function defaultDoctorIO(packageVersion) {
|
|
2856
|
+
const platform = detectPlatform();
|
|
2857
|
+
const entry = process.argv[1] ?? "";
|
|
2858
|
+
let statusMemo = null;
|
|
2859
|
+
const status = () => statusMemo ??= daemonStatus2();
|
|
2860
|
+
let latestMemo = null;
|
|
2861
|
+
return {
|
|
2862
|
+
platform,
|
|
2863
|
+
globalPm: detectGlobalPm(entry),
|
|
2864
|
+
nodeVersion: process.version,
|
|
2865
|
+
packageVersion,
|
|
2866
|
+
entry,
|
|
2867
|
+
hasCommand: (name) => hasCommandInPath(name),
|
|
2868
|
+
exists: existsSync12,
|
|
2869
|
+
run: (command) => runQuiet(command),
|
|
2870
|
+
latestVersion: () => latestMemo ??= fetchLatestVersion(),
|
|
2871
|
+
probeChromium: () => probeChromium(),
|
|
2872
|
+
dockerAlive,
|
|
2873
|
+
// та же проба, что у гейта окружения демона (envGate) — и с тем же таймаутом
|
|
2874
|
+
repos: async () => readReposSnapshot(await status()),
|
|
2875
|
+
token: readTokenState,
|
|
2876
|
+
daemon: async () => {
|
|
2877
|
+
const st = await status();
|
|
2878
|
+
return st ? { alive: true, version: st.version, hostName: st.hostName ?? void 0 } : { alive: false };
|
|
2879
|
+
},
|
|
2880
|
+
// Нас самих в PATH может не быть (npx-запуск), и это отдельный факт: после разового
|
|
2881
|
+
// запуска на машине ничего не останется. Возвращаем ПУТЬ, а не «да/нет»: на машине с
|
|
2882
|
+
// историей их бывает несколько, и человек должен видеть, какой именно нашёлся.
|
|
2883
|
+
globalSwarmBin: () => resolveCommandPath("swarm")
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function header(io) {
|
|
2887
|
+
const pm = io.platform.pm ? `\u043F\u0430\u043A\u0435\u0442\u043D\u044B\u0439 \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440: ${io.platform.pm}` : "\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u043D\u043E\u0433\u043E \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u0430 \u043D\u0435 \u043D\u0430\u0448\u043B\u0438";
|
|
2888
|
+
return `\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430: ${io.platform.label}; ${pm}${io.platform.needsSudo ? " (\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0435 \u043F\u0430\u043A\u0435\u0442\u044B \u2014 \u0447\u0435\u0440\u0435\u0437 sudo)" : ""}. Node ${io.nodeVersion}, \u0440\u043E\u0439 v${io.packageVersion}`;
|
|
2889
|
+
}
|
|
2890
|
+
function nextSteps(results, daemonAlive) {
|
|
2891
|
+
const steps = [];
|
|
2892
|
+
const token = results.find((r) => r.id === "token");
|
|
2893
|
+
if (token && token.status !== "ok") steps.push("\u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441: swarm login");
|
|
2894
|
+
if (daemonAlive) steps.push("\u0434\u0435\u043C\u043E\u043D \u0443\u0436\u0435 \u043F\u043E\u0434\u043D\u044F\u0442; \u0447\u0442\u043E\u0431\u044B \u043E\u043D \u043F\u043E\u0435\u0445\u0430\u043B \u043D\u0430 \u043D\u043E\u0432\u043E\u043C \u043A\u043E\u0434\u0435: swarm stop && swarm start");
|
|
2895
|
+
else steps.push("\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439: swarm start");
|
|
2896
|
+
steps.push("\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043F\u043E\u0442\u043E\u043C: swarm status \xB7 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0443: swarm doctor");
|
|
2897
|
+
return steps;
|
|
2898
|
+
}
|
|
2899
|
+
async function runDoctorCli(_argv, deps = {}) {
|
|
2900
|
+
const out = deps.out ?? ((l) => console.log(l));
|
|
2901
|
+
const io = deps.io ?? defaultDoctorIO(deps.packageVersion ?? "0.0.0");
|
|
2902
|
+
out(header(io));
|
|
2903
|
+
out("");
|
|
2904
|
+
const results = await runDoctor(io);
|
|
2905
|
+
renderReport(results).forEach(out);
|
|
2906
|
+
const { line, exitCode } = summarize(results);
|
|
2907
|
+
out("");
|
|
2908
|
+
out(line);
|
|
2909
|
+
if (exitCode !== 0 || results.some((r) => r.status === "warn")) out("\u043F\u043E\u0447\u0438\u043D\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 (\u0442\u043E, \u0447\u0442\u043E \u043C\u043E\u0436\u043D\u043E): swarm install");
|
|
2910
|
+
return exitCode;
|
|
2911
|
+
}
|
|
2912
|
+
async function fixCheck(check, io, opts) {
|
|
2913
|
+
let current = check;
|
|
2914
|
+
const manual = [];
|
|
2915
|
+
for (let round = 0; round < MAX_FIX_ROUNDS && current.status !== "ok"; round++) {
|
|
2916
|
+
let ranAny = false;
|
|
2917
|
+
for (const rem of current.remedies) {
|
|
2918
|
+
if (!rem.auto) {
|
|
2919
|
+
manual.push(`${rem.what}: ${rem.command}`);
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (rem.sudo) {
|
|
2923
|
+
opts.out(` \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u0430 root: ${rem.what}`);
|
|
2924
|
+
opts.out(` $ ${rem.command}`);
|
|
2925
|
+
if (!opts.yes && !opts.confirm) {
|
|
2926
|
+
opts.out(" \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043D\u0435\u0433\u0434\u0435 (\u043D\u0435 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A) \u2014 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u044E. \u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0437\u0430\u0440\u0430\u043D\u0435\u0435: swarm install --yes");
|
|
2927
|
+
manual.push(`${rem.what}: ${rem.command}`);
|
|
2928
|
+
continue;
|
|
1591
2929
|
}
|
|
1592
|
-
|
|
2930
|
+
const agreed = opts.yes || await opts.confirm(" \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u044D\u0442\u0443 \u043A\u043E\u043C\u0430\u043D\u0434\u0443?");
|
|
2931
|
+
if (!agreed) {
|
|
2932
|
+
opts.out(" \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E \u043F\u043E \u0442\u0432\u043E\u0435\u043C\u0443 \u0440\u0435\u0448\u0435\u043D\u0438\u044E");
|
|
2933
|
+
manual.push(`${rem.what}: ${rem.command}`);
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
} else {
|
|
2937
|
+
opts.out(` $ ${rem.command}`);
|
|
1593
2938
|
}
|
|
1594
|
-
|
|
2939
|
+
const code = await opts.exec(rem.command);
|
|
2940
|
+
ranAny = true;
|
|
2941
|
+
if (code !== 0) opts.out(` \u21B3 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 (\u043A\u043E\u0434 ${code}) \u2014 \u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E \u043F\u0443\u043D\u043A\u0442 \u043A\u0430\u043A \u0435\u0441\u0442\u044C`);
|
|
2942
|
+
}
|
|
2943
|
+
if (!ranAny) break;
|
|
2944
|
+
const rechecked = await runCheck(current.id, io);
|
|
2945
|
+
if (!rechecked) break;
|
|
2946
|
+
const stuck = rechecked.status === current.status && rechecked.detail === current.detail;
|
|
2947
|
+
current = rechecked;
|
|
2948
|
+
if (stuck) break;
|
|
2949
|
+
}
|
|
2950
|
+
return { result: current, manual: [...new Set(manual)] };
|
|
2951
|
+
}
|
|
2952
|
+
async function runInstallCli(argv, deps = {}) {
|
|
2953
|
+
const out = deps.out ?? ((l) => console.log(l));
|
|
2954
|
+
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
2955
|
+
const io = deps.io ?? defaultDoctorIO(deps.packageVersion ?? "0.0.0");
|
|
2956
|
+
const confirm = deps.confirm !== void 0 ? deps.confirm : process.stdin.isTTY ? askYesNo : null;
|
|
2957
|
+
const exec = deps.exec ?? execLoud;
|
|
2958
|
+
out(header(io));
|
|
2959
|
+
if (io.platform.kind === "windows") {
|
|
2960
|
+
out("\u041D\u0430\u0442\u0438\u0432\u043D\u0430\u044F Windows: \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F WSL2 \u2014 \u0442\u0430\u043C \u0440\u043E\u0439 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044F \u0440\u043E\u0432\u043D\u043E \u043A\u0430\u043A \u043D\u0430 macOS/Linux.");
|
|
2961
|
+
out("\u0417\u0434\u0435\u0441\u044C \u0434\u0435\u043B\u0430\u044E \u0441\u0432\u043E\u044E \u0447\u0430\u0441\u0442\u044C (\u043F\u0430\u043A\u0435\u0442\u044B npm), \u043D\u043E \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0445 \u043F\u0430\u043A\u0435\u0442\u043E\u0432 \u043D\u0435 \u0441\u0442\u0430\u0432\u043B\u044E: \u043E\u0441\u043E\u0431\u0435\u043D\u043D\u043E\u0441\u0442\u0438 Windows \u2014 \u0432 README \u0434\u0435\u043C\u043E\u043D\u0430.");
|
|
2962
|
+
}
|
|
2963
|
+
out("");
|
|
2964
|
+
const initial = await runDoctor(io);
|
|
2965
|
+
renderReport(initial).forEach(out);
|
|
2966
|
+
const broken = initial.filter((r) => r.status !== "ok" && r.id !== "daemon");
|
|
2967
|
+
if (broken.length === 0) {
|
|
2968
|
+
out("");
|
|
2969
|
+
out("\u0433\u043E\u0442\u043E\u0432\u043E: \u0432\u0441\u0451 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435, \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.");
|
|
2970
|
+
nextSteps(initial, (await io.daemon()).alive).forEach((s) => out(` ${s}`));
|
|
2971
|
+
return 0;
|
|
2972
|
+
}
|
|
2973
|
+
out("");
|
|
2974
|
+
out(`\u0434\u043E\u0432\u043E\u0436\u0443 \u0434\u043E \u0440\u0430\u0431\u043E\u0447\u0435\u0433\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F (${broken.length} \u043F\u0443\u043D\u043A\u0442(\u043E\u0432))\u2026`);
|
|
2975
|
+
const manual = [];
|
|
2976
|
+
const fixed = /* @__PURE__ */ new Map();
|
|
2977
|
+
for (const check of broken) {
|
|
2978
|
+
out("");
|
|
2979
|
+
out(`\u2014 ${check.title}`);
|
|
2980
|
+
const res = await fixCheck(check, io, { yes, out, confirm, exec });
|
|
2981
|
+
fixed.set(check.id, res.result);
|
|
2982
|
+
manual.push(...res.manual);
|
|
2983
|
+
}
|
|
2984
|
+
const final = initial.map((r) => fixed.get(r.id) ?? r);
|
|
2985
|
+
out("");
|
|
2986
|
+
out("\u2500\u2500 \u0438\u0442\u043E\u0433 \u2500\u2500");
|
|
2987
|
+
renderReport(final).forEach(out);
|
|
2988
|
+
const { line, exitCode } = summarize(final);
|
|
2989
|
+
out("");
|
|
2990
|
+
out(line);
|
|
2991
|
+
if (manual.length) {
|
|
2992
|
+
out("");
|
|
2993
|
+
out("\u0441\u0430\u043C \u044F \u044D\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043D\u0435 \u043C\u043E\u0433\u0443 \u2014 \u0441\u0434\u0435\u043B\u0430\u0439 \u0440\u0443\u043A\u0430\u043C\u0438:");
|
|
2994
|
+
for (const m of manual) out(` \u2022 ${m}`);
|
|
2995
|
+
}
|
|
2996
|
+
out("");
|
|
2997
|
+
out("\u0434\u0430\u043B\u044C\u0448\u0435:");
|
|
2998
|
+
nextSteps(final, (await io.daemon()).alive).forEach((s) => out(` ${s}`));
|
|
2999
|
+
return exitCode;
|
|
3000
|
+
}
|
|
3001
|
+
var CHROMIUM_PROBE_MS, MAX_FIX_ROUNDS;
|
|
3002
|
+
var init_installCli = __esm({
|
|
3003
|
+
"src/installCli.ts"() {
|
|
3004
|
+
"use strict";
|
|
3005
|
+
init_doctor();
|
|
3006
|
+
init_cliIo();
|
|
3007
|
+
init_platform();
|
|
3008
|
+
init_release();
|
|
3009
|
+
init_screenshot();
|
|
3010
|
+
init_envGate();
|
|
3011
|
+
init_daemonLink();
|
|
3012
|
+
init_agentToken();
|
|
3013
|
+
init_repoMemory();
|
|
3014
|
+
init_repoDiscovery();
|
|
3015
|
+
init_config();
|
|
3016
|
+
CHROMIUM_PROBE_MS = 2e4;
|
|
3017
|
+
MAX_FIX_ROUNDS = 3;
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
// src/updateCli.ts
|
|
3022
|
+
var updateCli_exports = {};
|
|
3023
|
+
__export(updateCli_exports, {
|
|
3024
|
+
runUpdateCli: () => runUpdateCli
|
|
3025
|
+
});
|
|
3026
|
+
async function runUpdateCli(argv, deps) {
|
|
3027
|
+
const out = deps.out ?? ((l) => console.log(l));
|
|
3028
|
+
const err = deps.err ?? ((l) => console.error(l));
|
|
3029
|
+
const pm = deps.globalPm ?? detectGlobalPm(process.argv[1]);
|
|
3030
|
+
const exec = deps.exec ?? execLoud;
|
|
3031
|
+
const latestVersion = deps.latestVersion ?? (() => fetchLatestVersion());
|
|
3032
|
+
const daemonAlive = deps.daemonAlive ?? (async () => await daemonStatus2() != null);
|
|
3033
|
+
const force = argv.includes("--force");
|
|
3034
|
+
if (pm === "sources") {
|
|
3035
|
+
err("\u0442\u044B \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0418\u0417 \u0418\u0421\u0425\u041E\u0414\u041D\u0418\u041A\u041E\u0412 \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u044B, \u0430 \u043D\u0435 \u0438\u0437 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C npm-\u043E\u043C \u0442\u0443\u0442 \u043D\u0435\u0447\u0435\u0433\u043E.");
|
|
3036
|
+
err(" \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: git pull && pnpm install && pnpm --filter @taskless-app/swarm... build");
|
|
3037
|
+
err(` \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0430\u043A\u0435\u0442 \u0440\u044F\u0434\u043E\u043C: ${globalInstallCommand("npm", SWARM_PACKAGE)}`);
|
|
3038
|
+
return 1;
|
|
3039
|
+
}
|
|
3040
|
+
const latest = await latestVersion();
|
|
3041
|
+
if (latest === null) {
|
|
3042
|
+
err("\u0434\u043E registry.npmjs.org \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u2014 \u043A\u0430\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430, \u0443\u0437\u043D\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C. \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0435\u0442\u044C \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438.");
|
|
3043
|
+
return 1;
|
|
3044
|
+
}
|
|
3045
|
+
if (!isOutdated(deps.packageVersion, latest) && !force) {
|
|
3046
|
+
out(`\u0443\u0436\u0435 \u0441\u0432\u0435\u0436\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F: v${deps.packageVersion} (\u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest}) \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
|
|
3047
|
+
return 0;
|
|
3048
|
+
}
|
|
3049
|
+
const command = globalInstallCommand(pm, `${SWARM_PACKAGE}@${latest}`);
|
|
3050
|
+
out(`\u043E\u0431\u043D\u043E\u0432\u043B\u044F\u044E \u0440\u043E\u0439: v${deps.packageVersion} \u2192 v${latest}`);
|
|
3051
|
+
out(` $ ${command}`);
|
|
3052
|
+
const code = await exec(command);
|
|
3053
|
+
if (code !== 0) {
|
|
3054
|
+
err(`\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u043E (\u043A\u043E\u0434 ${code}). \u0421\u0442\u0440\u043E\u043A\u0430 \u0432\u044B\u0448\u0435 \u2014 \u0442\u0430 \u0441\u0430\u043C\u0430\u044F, \u0447\u0442\u043E \u043D\u0443\u0436\u043D\u0430; \u0432\u044B\u043F\u043E\u043B\u043D\u0438 \u0435\u0451 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u043D\u0430 \u0447\u0451\u043C \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F.`);
|
|
3055
|
+
return code;
|
|
3056
|
+
}
|
|
3057
|
+
out(`\u0433\u043E\u0442\u043E\u0432\u043E: v${latest}`);
|
|
3058
|
+
if (await daemonAlive()) {
|
|
3059
|
+
out("\u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 \u0421\u0422\u0410\u0420\u041E\u041C \u043A\u043E\u0434\u0435 \u2014 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438, \u0447\u0442\u043E\u0431\u044B \u043E\u043D \u043F\u043E\u0435\u0445\u0430\u043B \u043D\u0430 \u043D\u043E\u0432\u043E\u043C: swarm stop && swarm start");
|
|
3060
|
+
} else {
|
|
3061
|
+
out("\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439: swarm start");
|
|
3062
|
+
}
|
|
3063
|
+
return 0;
|
|
3064
|
+
}
|
|
3065
|
+
var init_updateCli = __esm({
|
|
3066
|
+
"src/updateCli.ts"() {
|
|
3067
|
+
"use strict";
|
|
3068
|
+
init_cliIo();
|
|
3069
|
+
init_platform();
|
|
3070
|
+
init_release();
|
|
3071
|
+
init_daemonLink();
|
|
1595
3072
|
}
|
|
1596
3073
|
});
|
|
1597
3074
|
|
|
1598
3075
|
// src/cli.ts
|
|
3076
|
+
init_cliIo();
|
|
1599
3077
|
import { basename as basename4 } from "path";
|
|
1600
|
-
import { existsSync as
|
|
1601
|
-
import { createInterface } from "readline/promises";
|
|
3078
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7, statSync as statSync8, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
1602
3079
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
1603
3080
|
|
|
1604
3081
|
// src/swarmClient.ts
|
|
@@ -1784,6 +3261,11 @@ function runRef(runKey) {
|
|
|
1784
3261
|
function isRunRef(v) {
|
|
1785
3262
|
return typeof v === "object" && v !== null && "__runRef" in v;
|
|
1786
3263
|
}
|
|
3264
|
+
function isHostCommandShape(v) {
|
|
3265
|
+
const c = v;
|
|
3266
|
+
return !!c && typeof c === "object" && typeof c.id === "string" && !!c.id && typeof c.kind === "string";
|
|
3267
|
+
}
|
|
3268
|
+
var EMPTY_HEARTBEAT_REPLY = { commands: [], logRequests: [] };
|
|
1787
3269
|
function jsonFromTextContent(res) {
|
|
1788
3270
|
const text = res?.content?.find((c) => c.type === "text")?.text;
|
|
1789
3271
|
if (text) {
|
|
@@ -1963,6 +3445,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
1963
3445
|
runKeyFor(localId) {
|
|
1964
3446
|
return this.keyByLocal.get(localId);
|
|
1965
3447
|
}
|
|
3448
|
+
/**
|
|
3449
|
+
* TASWM-336: заменить репо-реестр на лету. До этой задачи реестр собирался ОДИН РАЗ на
|
|
3450
|
+
* старте из окружения и жил до рестарта демона; теперь машина сама находит клоны своих
|
|
3451
|
+
* репозиториев и переспрашивает прод по TTL — у владельца появился новый swarm-проект,
|
|
3452
|
+
* и машина обязана подхватить его без рестарта. Меняем обе стороны разом: и список для
|
|
3453
|
+
* claim/heartbeat, и обратную карту `owner/repo` → путь (иначе прогон нового репо
|
|
3454
|
+
* пришёл бы именем, которое не во что перевести).
|
|
3455
|
+
*/
|
|
3456
|
+
setRepoRegistry(registry) {
|
|
3457
|
+
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
3458
|
+
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
3459
|
+
}
|
|
1966
3460
|
// cuid прода → локальный id (по runKey гидрированного прогона `p:<cuid>`).
|
|
1967
3461
|
// Для маппинга прод heroId → локальный hero_run_id при гидрации. Резолвится, когда
|
|
1968
3462
|
// прогон-герой присутствует в зеркале; иначе null (уточнится следующим рефрешем).
|
|
@@ -2955,17 +4449,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
2955
4449
|
* прислал предыдущий (более свежий по коду) процесс демона.
|
|
2956
4450
|
*/
|
|
2957
4451
|
/**
|
|
2958
|
-
* TASWM-
|
|
2959
|
-
*
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
2962
|
-
|
|
2963
|
-
|
|
4452
|
+
* TASWM-321: ВОЗВРАЩАЕТ команды этой машине (пауза/рестарт/обновление/…), приехавшие
|
|
4453
|
+
* в ответе на пульс. Это единственный момент, когда прод может что-то сказать демону:
|
|
4454
|
+
* тот живёт за домашним роутером без белого адреса, и связь всегда инициирует он сам.
|
|
4455
|
+
* Прод, отдав команды, метит их доставленными — повторный пульс их не вернёт, поэтому
|
|
4456
|
+
* потерять ответ здесь значит потерять команду; парсим бережно и не бросаем.
|
|
4457
|
+
*
|
|
4458
|
+
* TASWM-322: тем же ответом приезжают запросы «пришли хвост лога» (logRequests) —
|
|
4459
|
+
* канал «прод → демон» один на оба вида поручений, поэтому и разбираем их вместе.
|
|
4460
|
+
* Возвращаем ОБА списка объектом: два разных потребителя в overmind, и слепить их в
|
|
4461
|
+
* один список значило бы заставить каждого фильтровать чужое.
|
|
4462
|
+
*
|
|
2964
4463
|
* TASWM-326: тем же пульсом уходит ПАУЗА этой машины (`pause` — durable-запись из
|
|
2965
4464
|
* pause.json либо null). В парке машин «на паузе» обязано отличаться от «нет работы»
|
|
2966
4465
|
* и от «молчит», а единственный канал «демон → прод» с нужной кадансностью — пульс.
|
|
2967
4466
|
* Шлём ВСЕГДА (в т.ч. `paused:false`): это состояние, а не команда, и молчание тут
|
|
2968
4467
|
* означало бы «прод помнит вчерашнюю паузу», когда рой давно работает.
|
|
4468
|
+
*
|
|
4469
|
+
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
2969
4470
|
*/
|
|
2970
4471
|
async heartbeat(daemonVersion, pause) {
|
|
2971
4472
|
try {
|
|
@@ -2980,9 +4481,33 @@ var SwarmClient = class _SwarmClient {
|
|
|
2980
4481
|
args.hostName = this.host.hostName;
|
|
2981
4482
|
}
|
|
2982
4483
|
const res = await this.call("swarm_heartbeat", args);
|
|
2983
|
-
|
|
4484
|
+
if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
|
|
4485
|
+
const data = res?.structuredContent ?? {};
|
|
4486
|
+
return {
|
|
4487
|
+
commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
|
|
4488
|
+
logRequests: parseLogRequests(data.logRequests)
|
|
4489
|
+
};
|
|
4490
|
+
} catch {
|
|
4491
|
+
return EMPTY_HEARTBEAT_REPLY;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
/**
|
|
4495
|
+
* TASWM-321: подтвердить исполнение команды (`swarm_command_ack`). ПРЯМОЙ вызов, не
|
|
4496
|
+
* outbox: подтверждение — «скажи СЕЙЧАС», ради него команду и заводили («без
|
|
4497
|
+
* подтверждения непонятно, обновился он или команда потерялась по дороге»), а
|
|
4498
|
+
* промежуточные шаги обновления, доехавшие после самого обновления, бессмысленны.
|
|
4499
|
+
* Не бросает: сорвавшийся ack не должен рушить тик демона — команда останется в
|
|
4500
|
+
* прошлом состоянии, следующий шаг/финал её всё равно подвинут.
|
|
4501
|
+
*/
|
|
4502
|
+
async ackCommand(commandId, status, patch = {}) {
|
|
4503
|
+
try {
|
|
4504
|
+
await this.call("swarm_command_ack", {
|
|
4505
|
+
commandId,
|
|
4506
|
+
status,
|
|
4507
|
+
...patch.step ? { step: patch.step } : {},
|
|
4508
|
+
...patch.result ? { result: patch.result.slice(0, 2e3) } : {}
|
|
4509
|
+
});
|
|
2984
4510
|
} catch {
|
|
2985
|
-
return [];
|
|
2986
4511
|
}
|
|
2987
4512
|
}
|
|
2988
4513
|
/**
|
|
@@ -3151,8 +4676,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
3151
4676
|
this.runs.set(id, local);
|
|
3152
4677
|
this.keyByLocal.set(id, runKey);
|
|
3153
4678
|
}
|
|
3154
|
-
const
|
|
3155
|
-
local.task_slug =
|
|
4679
|
+
const prodSlug = pr.taskSlug ?? pr.task?.slug ?? null;
|
|
4680
|
+
if (prodSlug) local.task_slug = prodSlug;
|
|
4681
|
+
else if (!isTaskSlug(local.task_slug)) local.task_slug = pr.taskId || local.task_slug;
|
|
3156
4682
|
local.task_title = pr.taskTitle ?? local.task_title;
|
|
3157
4683
|
local.task_desc = pr.taskDesc ?? local.task_desc;
|
|
3158
4684
|
local.name = pr.droneName ?? local.name;
|
|
@@ -3471,7 +4997,7 @@ function parseDecision(v) {
|
|
|
3471
4997
|
init_config();
|
|
3472
4998
|
|
|
3473
4999
|
// src/daemonLaunch.ts
|
|
3474
|
-
import { openSync as openSync2, closeSync as closeSync2,
|
|
5000
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync4 } from "fs";
|
|
3475
5001
|
import { spawn } from "child_process";
|
|
3476
5002
|
var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
|
|
3477
5003
|
function parseStartMode(argv) {
|
|
@@ -3481,18 +5007,6 @@ function parseStartMode(argv) {
|
|
|
3481
5007
|
function childStartArgv(rest) {
|
|
3482
5008
|
return ["start", ...rest.filter((a) => !FOREGROUND_FLAGS.has(a)), "--foreground"];
|
|
3483
5009
|
}
|
|
3484
|
-
function rotateDaemonLog(path, maxBytes) {
|
|
3485
|
-
let size;
|
|
3486
|
-
try {
|
|
3487
|
-
size = statSync2(path).size;
|
|
3488
|
-
} catch {
|
|
3489
|
-
return "missing";
|
|
3490
|
-
}
|
|
3491
|
-
if (size <= maxBytes) return "kept";
|
|
3492
|
-
renameSync2(path, `${path}.1`);
|
|
3493
|
-
writeFileSync3(path, "");
|
|
3494
|
-
return "rotated";
|
|
3495
|
-
}
|
|
3496
5010
|
function tailText(text, lines) {
|
|
3497
5011
|
const rows = text.split("\n");
|
|
3498
5012
|
if (rows[rows.length - 1] === "") rows.pop();
|
|
@@ -3549,6 +5063,42 @@ function tailFile(path, lines) {
|
|
|
3549
5063
|
}
|
|
3550
5064
|
}
|
|
3551
5065
|
|
|
5066
|
+
// src/logRotate.ts
|
|
5067
|
+
import { copyFileSync, renameSync as renameSync2, statSync as statSync2, fstatSync, truncateSync, unlinkSync, existsSync as existsSync3 } from "fs";
|
|
5068
|
+
var realIo = {
|
|
5069
|
+
size: (path) => {
|
|
5070
|
+
try {
|
|
5071
|
+
return statSync2(path).size;
|
|
5072
|
+
} catch {
|
|
5073
|
+
return null;
|
|
5074
|
+
}
|
|
5075
|
+
},
|
|
5076
|
+
copy: copyFileSync,
|
|
5077
|
+
rename: renameSync2,
|
|
5078
|
+
truncate: (path) => truncateSync(path, 0),
|
|
5079
|
+
unlink: unlinkSync,
|
|
5080
|
+
exists: existsSync3
|
|
5081
|
+
};
|
|
5082
|
+
var STRAY_GENERATIONS = 8;
|
|
5083
|
+
function rotateDaemonLog(path, opts, io = realIo) {
|
|
5084
|
+
const size = io.size(path);
|
|
5085
|
+
if (size == null) return "missing";
|
|
5086
|
+
if (size <= opts.maxBytes) return "kept";
|
|
5087
|
+
const keep = Math.max(0, Math.trunc(opts.keep));
|
|
5088
|
+
const gen = (n) => `${path}.${n}`;
|
|
5089
|
+
for (let i = keep; i >= 1; i--) {
|
|
5090
|
+
if (!io.exists(gen(i))) continue;
|
|
5091
|
+
if (i === keep) io.unlink(gen(i));
|
|
5092
|
+
else io.rename(gen(i), gen(i + 1));
|
|
5093
|
+
}
|
|
5094
|
+
for (let i = keep + 1; i <= keep + STRAY_GENERATIONS; i++) {
|
|
5095
|
+
if (io.exists(gen(i))) io.unlink(gen(i));
|
|
5096
|
+
}
|
|
5097
|
+
if (keep >= 1) io.copy(path, gen(1));
|
|
5098
|
+
io.truncate(path);
|
|
5099
|
+
return "rotated";
|
|
5100
|
+
}
|
|
5101
|
+
|
|
3552
5102
|
// src/procList.ts
|
|
3553
5103
|
function parsePsRows(out) {
|
|
3554
5104
|
const rows = [];
|
|
@@ -3625,7 +5175,7 @@ function descendantPids(rows, roots) {
|
|
|
3625
5175
|
|
|
3626
5176
|
// src/procCleanup.ts
|
|
3627
5177
|
function sleep(ms) {
|
|
3628
|
-
return new Promise((
|
|
5178
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3629
5179
|
}
|
|
3630
5180
|
function parseLsofCwd(output) {
|
|
3631
5181
|
const rows = [];
|
|
@@ -3780,45 +5330,24 @@ async function cleanupWorktreeProcesses(worktree, opts = {}) {
|
|
|
3780
5330
|
}
|
|
3781
5331
|
}
|
|
3782
5332
|
|
|
3783
|
-
// src/
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
function parseRepoFullName(remoteUrl) {
|
|
3787
|
-
const m = remoteUrl.trim().match(/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(\.git)?\/?$/i);
|
|
3788
|
-
if (!m) return null;
|
|
3789
|
-
return `${m[1]}/${m[2]}`.toLowerCase();
|
|
3790
|
-
}
|
|
3791
|
-
async function resolveRepoFullName(path) {
|
|
3792
|
-
const res = await $({ reject: false })`git -C ${path} remote get-url origin`;
|
|
3793
|
-
if (res.exitCode !== 0) return null;
|
|
3794
|
-
return parseRepoFullName(res.stdout);
|
|
3795
|
-
}
|
|
3796
|
-
async function buildRepoRegistry(paths) {
|
|
3797
|
-
const resolved = await Promise.all(
|
|
3798
|
-
paths.map(async (raw) => {
|
|
3799
|
-
const path = normalizeRepo(raw);
|
|
3800
|
-
if (!path) return null;
|
|
3801
|
-
const repoFullName = await resolveRepoFullName(path);
|
|
3802
|
-
return repoFullName ? { repoFullName, path } : null;
|
|
3803
|
-
})
|
|
3804
|
-
);
|
|
3805
|
-
return resolved.filter((e) => e !== null);
|
|
3806
|
-
}
|
|
5333
|
+
// src/cli.ts
|
|
5334
|
+
init_repoRegistry();
|
|
5335
|
+
init_repoDiscovery();
|
|
3807
5336
|
|
|
3808
5337
|
// src/hostIdentity.ts
|
|
3809
5338
|
import { hostname } from "os";
|
|
3810
|
-
import { existsSync as
|
|
3811
|
-
import { join as
|
|
5339
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
5340
|
+
import { join as join5 } from "path";
|
|
3812
5341
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3813
5342
|
function hostIdentityPath(home) {
|
|
3814
|
-
return
|
|
5343
|
+
return join5(home, "host.json");
|
|
3815
5344
|
}
|
|
3816
5345
|
function loadHostIdentity(deps) {
|
|
3817
|
-
const exists = deps.exists ??
|
|
3818
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
5346
|
+
const exists = deps.exists ?? existsSync4;
|
|
5347
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
|
|
3819
5348
|
const writeFile2 = deps.writeFile ?? ((p, data) => {
|
|
3820
5349
|
writeFileSync4(`${p}.tmp`, data);
|
|
3821
|
-
|
|
5350
|
+
renameSync4(`${p}.tmp`, p);
|
|
3822
5351
|
});
|
|
3823
5352
|
const host = deps.hostname ?? hostname;
|
|
3824
5353
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -3929,8 +5458,8 @@ import { basename as basename2 } from "path";
|
|
|
3929
5458
|
import { serve as serve2 } from "@hono/node-server";
|
|
3930
5459
|
|
|
3931
5460
|
// src/diff.ts
|
|
3932
|
-
import { statSync as
|
|
3933
|
-
import { join as
|
|
5461
|
+
import { statSync as statSync4 } from "fs";
|
|
5462
|
+
import { join as join6 } from "path";
|
|
3934
5463
|
import { $ as $2 } from "execa";
|
|
3935
5464
|
var MAX_FILES = 200;
|
|
3936
5465
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -4073,7 +5602,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
4073
5602
|
}
|
|
4074
5603
|
function mtime(path) {
|
|
4075
5604
|
try {
|
|
4076
|
-
return
|
|
5605
|
+
return statSync4(path).mtimeMs;
|
|
4077
5606
|
} catch {
|
|
4078
5607
|
return 0;
|
|
4079
5608
|
}
|
|
@@ -4098,7 +5627,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
4098
5627
|
files.push(pf);
|
|
4099
5628
|
}
|
|
4100
5629
|
}
|
|
4101
|
-
files.sort((a, b) => mtime(
|
|
5630
|
+
files.sort((a, b) => mtime(join6(worktree, b.path)) - mtime(join6(worktree, a.path)));
|
|
4102
5631
|
const truncatedFiles = files.length > MAX_FILES;
|
|
4103
5632
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
4104
5633
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -4114,62 +5643,8 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
4114
5643
|
};
|
|
4115
5644
|
}
|
|
4116
5645
|
|
|
4117
|
-
// package.json
|
|
4118
|
-
var package_default = {
|
|
4119
|
-
name: "@taskless-app/swarm",
|
|
4120
|
-
version: "0.4.9",
|
|
4121
|
-
type: "module",
|
|
4122
|
-
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
4123
|
-
keywords: [
|
|
4124
|
-
"ai-agents",
|
|
4125
|
-
"coding-agents",
|
|
4126
|
-
"swarm",
|
|
4127
|
-
"orchestration",
|
|
4128
|
-
"autonomous-agents",
|
|
4129
|
-
"mcp",
|
|
4130
|
-
"taskless",
|
|
4131
|
-
"cli"
|
|
4132
|
-
],
|
|
4133
|
-
license: "SEE LICENSE IN LICENSE",
|
|
4134
|
-
author: "Ilya Smurov <ilya@smurov.com>",
|
|
4135
|
-
homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
|
|
4136
|
-
repository: {
|
|
4137
|
-
type: "git",
|
|
4138
|
-
url: "git+https://github.com/ilyasmurov/taskless.git",
|
|
4139
|
-
directory: "packages/swarm-daemon"
|
|
4140
|
-
},
|
|
4141
|
-
engines: {
|
|
4142
|
-
node: ">=22"
|
|
4143
|
-
},
|
|
4144
|
-
bin: {
|
|
4145
|
-
swarm: "dist/cli.js"
|
|
4146
|
-
},
|
|
4147
|
-
files: [
|
|
4148
|
-
"dist",
|
|
4149
|
-
"docs/HERO.md",
|
|
4150
|
-
"roles"
|
|
4151
|
-
],
|
|
4152
|
-
scripts: {
|
|
4153
|
-
overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
|
|
4154
|
-
swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
|
|
4155
|
-
build: "tsup",
|
|
4156
|
-
start: "node dist/overmind.js",
|
|
4157
|
-
prepublishOnly: "pnpm run build",
|
|
4158
|
-
test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
4159
|
-
"test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
4160
|
-
"test:watch": "vitest",
|
|
4161
|
-
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
4162
|
-
lint: "echo 'no lint in swarm-daemon yet'"
|
|
4163
|
-
},
|
|
4164
|
-
dependencies: {
|
|
4165
|
-
"@hono/node-server": "^2.0.12",
|
|
4166
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
4167
|
-
execa: "^10.0.0",
|
|
4168
|
-
zod: "3.25.76"
|
|
4169
|
-
}
|
|
4170
|
-
};
|
|
4171
|
-
|
|
4172
5646
|
// src/web.ts
|
|
5647
|
+
init_version();
|
|
4173
5648
|
init_storm();
|
|
4174
5649
|
|
|
4175
5650
|
// src/gh.ts
|
|
@@ -4221,64 +5696,19 @@ async function ghChecks(pr, run = realChecksRunner) {
|
|
|
4221
5696
|
|
|
4222
5697
|
// src/web.ts
|
|
4223
5698
|
init_config();
|
|
4224
|
-
|
|
4225
|
-
// src/droneChannel.ts
|
|
4226
|
-
init_src();
|
|
4227
|
-
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
4228
|
-
import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4229
|
-
import { dirname as dirname2 } from "path";
|
|
4230
|
-
import { serve } from "@hono/node-server";
|
|
4231
|
-
function loadChannelSecret(path) {
|
|
4232
|
-
try {
|
|
4233
|
-
const s = readFileSync6(path, "utf8").trim();
|
|
4234
|
-
if (s.length >= 32) return s;
|
|
4235
|
-
} catch {
|
|
4236
|
-
}
|
|
4237
|
-
const secret = randomBytes(32).toString("hex");
|
|
4238
|
-
mkdirSync3(dirname2(path), { recursive: true });
|
|
4239
|
-
writeFileSync5(path, secret, { mode: 384 });
|
|
4240
|
-
try {
|
|
4241
|
-
chmodSync(path, 384);
|
|
4242
|
-
} catch {
|
|
4243
|
-
}
|
|
4244
|
-
return secret;
|
|
4245
|
-
}
|
|
4246
|
-
function sign(runKey, secret) {
|
|
4247
|
-
return createHmac("sha256", secret).update(runKey).digest("base64url");
|
|
4248
|
-
}
|
|
4249
|
-
function mintChannelToken(runKey, secret) {
|
|
4250
|
-
return `${Buffer.from(runKey, "utf8").toString("base64url")}.${sign(runKey, secret)}`;
|
|
4251
|
-
}
|
|
4252
|
-
function verifyChannelToken(token, secret) {
|
|
4253
|
-
const dot = token.lastIndexOf(".");
|
|
4254
|
-
if (dot <= 0) return null;
|
|
4255
|
-
const runKey = Buffer.from(token.slice(0, dot), "base64url").toString("utf8");
|
|
4256
|
-
if (!runKey) return null;
|
|
4257
|
-
const got = Buffer.from(token.slice(dot + 1));
|
|
4258
|
-
const want = Buffer.from(sign(runKey, secret));
|
|
4259
|
-
if (got.length !== want.length || !timingSafeEqual(got, want)) return null;
|
|
4260
|
-
return runKey;
|
|
4261
|
-
}
|
|
4262
|
-
var LOCAL_CLI_KEY = "cli";
|
|
4263
|
-
function mintLocalCliToken(secret) {
|
|
4264
|
-
return mintChannelToken(LOCAL_CLI_KEY, secret);
|
|
4265
|
-
}
|
|
4266
|
-
function verifyLocalCliToken(token, secret) {
|
|
4267
|
-
return verifyChannelToken(token, secret) === LOCAL_CLI_KEY;
|
|
4268
|
-
}
|
|
4269
|
-
var EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
5699
|
+
init_droneChannel();
|
|
4270
5700
|
|
|
4271
5701
|
// src/oauth.ts
|
|
4272
5702
|
init_config();
|
|
4273
5703
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
4274
|
-
import { readFileSync as
|
|
4275
|
-
import { join as
|
|
5704
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
5705
|
+
import { join as join7 } from "path";
|
|
4276
5706
|
var REGISTER = "/api/oauth/register";
|
|
4277
5707
|
var AUTHORIZE = "/api/oauth/authorize";
|
|
4278
5708
|
var TOKEN = "/api/oauth/token";
|
|
4279
5709
|
var SCOPE = "openid profile email taskless:full";
|
|
4280
|
-
var STORE_PATH =
|
|
4281
|
-
var CLIENT_PATH =
|
|
5710
|
+
var STORE_PATH = join7(SWARM_HOME, "oauth.json");
|
|
5711
|
+
var CLIENT_PATH = join7(SWARM_HOME, "oauth-client.json");
|
|
4282
5712
|
function makePkce() {
|
|
4283
5713
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4284
5714
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -4363,7 +5793,7 @@ function toStore(clientId, t, now) {
|
|
|
4363
5793
|
}
|
|
4364
5794
|
function loadStore() {
|
|
4365
5795
|
try {
|
|
4366
|
-
return JSON.parse(
|
|
5796
|
+
return JSON.parse(readFileSync9(STORE_PATH, "utf8"));
|
|
4367
5797
|
} catch {
|
|
4368
5798
|
return null;
|
|
4369
5799
|
}
|
|
@@ -4373,13 +5803,13 @@ function saveStore(s) {
|
|
|
4373
5803
|
}
|
|
4374
5804
|
function clearStore() {
|
|
4375
5805
|
try {
|
|
4376
|
-
|
|
5806
|
+
unlinkSync2(STORE_PATH);
|
|
4377
5807
|
} catch {
|
|
4378
5808
|
}
|
|
4379
5809
|
}
|
|
4380
5810
|
function loadClientId() {
|
|
4381
5811
|
try {
|
|
4382
|
-
return JSON.parse(
|
|
5812
|
+
return JSON.parse(readFileSync9(CLIENT_PATH, "utf8")).clientId;
|
|
4383
5813
|
} catch {
|
|
4384
5814
|
return null;
|
|
4385
5815
|
}
|
|
@@ -4407,7 +5837,6 @@ async function ensureAccess(store, opts = {}) {
|
|
|
4407
5837
|
}
|
|
4408
5838
|
|
|
4409
5839
|
// src/web.ts
|
|
4410
|
-
var PACKAGE_VERSION = package_default.version;
|
|
4411
5840
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
4412
5841
|
var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
|
|
4413
5842
|
var TASKLESS_MARK = `<svg viewBox="0 0 60 54" width="19" height="19" fill="none" aria-hidden="true" style="display:block"><g transform="matrix(1.0512,0,0,1.0512,0,-0.021)"><path fill="currentColor" fill-rule="nonzero" d="M46.92,0 L10.24,0 C4.59,0 0,4.59 0,10.24 L0,10.4 C0,16.05 4.59,20.64 10.24,20.64 L16.84,20.64 C19.56,20.64 22.12,19.59 24.05,17.67 L36.06,5.77 C37.25,4.6 38.82,3.95 40.49,3.95 L46.93,3.95 C50.4,3.95 53.22,6.77 53.22,10.24 L53.22,10.4 C53.22,13.87 50.4,16.69 46.93,16.69 L39.82,16.69 C37.05,16.69 34.36,17.83 32.44,19.83 L21.13,31.58 C19.28,33.5 18.27,36.02 18.27,38.68 L18.27,40.94 C18.27,46.59 22.86,51.18 28.51,51.18 C34.16,51.18 38.75,46.59 38.75,40.94 L38.75,20.74 C39.1,20.68 39.46,20.64 39.82,20.64 L46.93,20.64 C52.58,20.64 57.17,16.05 57.17,10.4 L57.17,10.24 C57.17,4.59 52.58,0 46.93,0 L46.92,0 Z M21.26,14.87 C20.07,16.04 18.5,16.69 16.83,16.69 L10.23,16.69 C6.76,16.69 3.94,13.87 3.94,10.4 L3.94,10.24 C3.94,6.77 6.76,3.95 10.23,3.95 L32.27,3.95 L21.25,14.87 L21.26,14.87 Z M34.79,40.94 C34.79,44.41 31.97,47.23 28.5,47.23 C25.03,47.23 22.21,44.41 22.21,40.94 L22.21,38.68 C22.21,37.05 22.83,35.5 23.97,34.32 L34.79,23.07 L34.79,40.94 L34.79,40.94 Z"/></g></svg>`;
|
|
@@ -5715,7 +7144,8 @@ function daemonStatus(deps) {
|
|
|
5715
7144
|
restart: deps.restart ? { phase: deps.restart.phase, intent: deps.restart.intent, sessions: live } : null,
|
|
5716
7145
|
counts,
|
|
5717
7146
|
live,
|
|
5718
|
-
version: PACKAGE_VERSION
|
|
7147
|
+
version: PACKAGE_VERSION,
|
|
7148
|
+
repos: deps.repoMap?.() ?? []
|
|
5719
7149
|
};
|
|
5720
7150
|
}
|
|
5721
7151
|
function eventsStream(db2, narratorLog, control, daemonVersion, restart, liveSessionIds, selfHost) {
|
|
@@ -5942,113 +7372,42 @@ function makeHandler(deps) {
|
|
|
5942
7372
|
if (p === "/restart/cancel" && req.method === "POST") {
|
|
5943
7373
|
if (!auth()) return unauthorized();
|
|
5944
7374
|
if (!deps.cancelRestartWait) return new Response("\u043E\u0442\u043C\u0435\u043D\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u043C\u043E\u0440\u0434\u0430 \u0431\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430", { status: 503 });
|
|
5945
|
-
deps.cancelRestartWait();
|
|
5946
|
-
return Response.json({ ok: true });
|
|
5947
|
-
}
|
|
5948
|
-
return new Response("not found", { status: 404 });
|
|
5949
|
-
};
|
|
5950
|
-
}
|
|
5951
|
-
function startWeb(deps, port = WEB_PORT) {
|
|
5952
|
-
const handle = makeHandler(deps);
|
|
5953
|
-
const server = serve2({ fetch: handle, port, hostname: "127.0.0.1" });
|
|
5954
|
-
server.on("error", (err) => {
|
|
5955
|
-
const e = err;
|
|
5956
|
-
if (e.code === "EADDRINUSE") {
|
|
5957
|
-
console.error(`\u043F\u043E\u0440\u0442 ${port} \u0437\u0430\u043D\u044F\u0442 \u2014 \u043F\u043E\u0445\u043E\u0436\u0435, overmind \u0443\u0436\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D (\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${port}).`);
|
|
5958
|
-
} else {
|
|
5959
|
-
console.error(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${e.message}`);
|
|
5960
|
-
}
|
|
5961
|
-
process.exit(1);
|
|
5962
|
-
});
|
|
5963
|
-
return { port, server };
|
|
5964
|
-
}
|
|
5965
|
-
|
|
5966
|
-
// src/cli.ts
|
|
5967
|
-
import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDroneProcesses } from "./overmind.js";
|
|
5968
|
-
|
|
5969
|
-
// src/launcher.ts
|
|
5970
|
-
import { spawn as spawn2 } from "child_process";
|
|
5971
|
-
import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync4, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
5972
|
-
import { createRequire } from "module";
|
|
5973
|
-
import { homedir as homedir2 } from "os";
|
|
5974
|
-
import { dirname as dirname3, join as join7 } from "path";
|
|
5975
|
-
import { fileURLToPath } from "url";
|
|
5976
|
-
|
|
5977
|
-
// src/claudeBin.ts
|
|
5978
|
-
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
5979
|
-
import { win32 as winPath } from "path";
|
|
5980
|
-
var CLAUDE_FALLBACK = "claude";
|
|
5981
|
-
function compareVersionDirs(a, b) {
|
|
5982
|
-
const parts = (s) => s.split(".").map((p) => Number.isFinite(Number(p)) ? Number(p) : -1);
|
|
5983
|
-
const pa = parts(a);
|
|
5984
|
-
const pb = parts(b);
|
|
5985
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
5986
|
-
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
5987
|
-
if (d !== 0) return d;
|
|
5988
|
-
}
|
|
5989
|
-
return 0;
|
|
5990
|
-
}
|
|
5991
|
-
function realListDir(dir) {
|
|
5992
|
-
try {
|
|
5993
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
5994
|
-
} catch {
|
|
5995
|
-
return [];
|
|
5996
|
-
}
|
|
5997
|
-
}
|
|
5998
|
-
function winVersionRoots(env, listDir) {
|
|
5999
|
-
const roots = [];
|
|
6000
|
-
const appData = env.APPDATA;
|
|
6001
|
-
if (appData) roots.push(winPath.join(appData, "Claude", "claude-code"));
|
|
6002
|
-
const localAppData = env.LOCALAPPDATA;
|
|
6003
|
-
if (localAppData) {
|
|
6004
|
-
const packages = winPath.join(localAppData, "Packages");
|
|
6005
|
-
for (const name of listDir(packages)) {
|
|
6006
|
-
if (!name.startsWith("Claude")) continue;
|
|
6007
|
-
roots.push(winPath.join(packages, name, "LocalCache", "Roaming", "Claude", "claude-code"));
|
|
6008
|
-
}
|
|
6009
|
-
}
|
|
6010
|
-
return roots;
|
|
6011
|
-
}
|
|
6012
|
-
function findWindowsClaude(opts = {}) {
|
|
6013
|
-
const env = opts.env ?? process.env;
|
|
6014
|
-
const listDir = opts.listDir ?? realListDir;
|
|
6015
|
-
const fileExists = opts.fileExists ?? existsSync4;
|
|
6016
|
-
let best = null;
|
|
6017
|
-
for (const root of winVersionRoots(env, listDir)) {
|
|
6018
|
-
for (const version of listDir(root)) {
|
|
6019
|
-
const candidate = winPath.join(root, version, "claude.exe");
|
|
6020
|
-
if (!fileExists(candidate)) continue;
|
|
6021
|
-
if (!best || compareVersionDirs(version, best.version) > 0) best = { version, path: candidate };
|
|
6022
|
-
}
|
|
6023
|
-
}
|
|
6024
|
-
return best?.path ?? null;
|
|
6025
|
-
}
|
|
6026
|
-
var warnedNoWindowsClaude = false;
|
|
6027
|
-
var cachedWindowsBin = null;
|
|
6028
|
-
var windowsScanDone = false;
|
|
6029
|
-
function resolveClaudeBin(opts = {}) {
|
|
6030
|
-
const env = opts.env ?? process.env;
|
|
6031
|
-
const platform = opts.platform ?? process.platform;
|
|
6032
|
-
const explicit = (env.SWARM_CLAUDE_BIN ?? "").trim();
|
|
6033
|
-
if (explicit) return explicit;
|
|
6034
|
-
if (platform !== "win32") return CLAUDE_FALLBACK;
|
|
6035
|
-
if (windowsScanDone) return cachedWindowsBin ?? CLAUDE_FALLBACK;
|
|
6036
|
-
windowsScanDone = true;
|
|
6037
|
-
const found = findWindowsClaude({ ...opts, env });
|
|
6038
|
-
cachedWindowsBin = found;
|
|
6039
|
-
if (found) return found;
|
|
6040
|
-
if (!warnedNoWindowsClaude) {
|
|
6041
|
-
warnedNoWindowsClaude = true;
|
|
6042
|
-
console.warn(
|
|
6043
|
-
"[overmind] claude.exe \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043D\u0438 \u0432 %APPDATA%\\Claude\\claude-code, \u043D\u0438 \u0432 MSIX-\u043A\u043E\u043F\u0438\u0438 \u043F\u043E\u0434 %LOCALAPPDATA%\\Packages\\Claude* \u2014 \u0441\u043F\u0430\u0432\u043D\u044E \u0433\u043E\u043B\u043E\u0435 \xABclaude\xBB (\u043F\u043E\u0438\u0441\u043A \u043F\u043E PATH+PATHEXT). \u0423\u043F\u0430\u0434\u0451\u0442 ENOENT (bash-\u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u0431\u0435\u0437 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0432 PATH \u043D\u0435 \u0432\u0438\u0434\u043D\u0430 spawn'\u0443) \u2014 \u0437\u0430\u0434\u0430\u0439\u0442\u0435 SWARM_CLAUDE_BIN \u043F\u0443\u0442\u0451\u043C \u043A \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C\u0443 claude.exe."
|
|
6044
|
-
);
|
|
6045
|
-
}
|
|
6046
|
-
return CLAUDE_FALLBACK;
|
|
7375
|
+
deps.cancelRestartWait();
|
|
7376
|
+
return Response.json({ ok: true });
|
|
7377
|
+
}
|
|
7378
|
+
return new Response("not found", { status: 404 });
|
|
7379
|
+
};
|
|
7380
|
+
}
|
|
7381
|
+
function startWeb(deps, port = WEB_PORT) {
|
|
7382
|
+
const handle = makeHandler(deps);
|
|
7383
|
+
const server = serve2({ fetch: handle, port, hostname: "127.0.0.1" });
|
|
7384
|
+
server.on("error", (err) => {
|
|
7385
|
+
const e = err;
|
|
7386
|
+
if (e.code === "EADDRINUSE") {
|
|
7387
|
+
console.error(`\u043F\u043E\u0440\u0442 ${port} \u0437\u0430\u043D\u044F\u0442 \u2014 \u043F\u043E\u0445\u043E\u0436\u0435, overmind \u0443\u0436\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D (\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${port}).`);
|
|
7388
|
+
} else {
|
|
7389
|
+
console.error(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${e.message}`);
|
|
7390
|
+
}
|
|
7391
|
+
process.exit(1);
|
|
7392
|
+
});
|
|
7393
|
+
return { port, server };
|
|
6047
7394
|
}
|
|
6048
7395
|
|
|
7396
|
+
// src/cli.ts
|
|
7397
|
+
import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDroneProcesses } from "./overmind.js";
|
|
7398
|
+
|
|
7399
|
+
// src/launcher.ts
|
|
7400
|
+
init_claudeBin();
|
|
7401
|
+
import { spawn as spawn2 } from "child_process";
|
|
7402
|
+
import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync5, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
7403
|
+
import { createRequire } from "module";
|
|
7404
|
+
import { homedir as homedir3 } from "os";
|
|
7405
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
7406
|
+
import { fileURLToPath } from "url";
|
|
7407
|
+
|
|
6049
7408
|
// src/droneHome.ts
|
|
6050
|
-
import { lstatSync, mkdirSync as mkdirSync4, readdirSync as
|
|
6051
|
-
import { join as
|
|
7409
|
+
import { lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync4, rmSync, statSync as statSync5, symlinkSync } from "fs";
|
|
7410
|
+
import { join as join8, relative, sep } from "path";
|
|
6052
7411
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
6053
7412
|
function droneHomeDeny(env = process.env) {
|
|
6054
7413
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -6067,28 +7426,28 @@ function buildDroneHome(opts) {
|
|
|
6067
7426
|
const deny = opts.deny ?? droneHomeDeny();
|
|
6068
7427
|
const platform = opts.platform ?? process.platform;
|
|
6069
7428
|
mkdirSync4(droneHome, { recursive: true, mode: 448 });
|
|
6070
|
-
const wanted = new Set(mirroredEntries(
|
|
7429
|
+
const wanted = new Set(mirroredEntries(readdirSync4(realHome), deny, platform));
|
|
6071
7430
|
const rel = relative(realHome, droneHome);
|
|
6072
7431
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
6073
|
-
for (const existing of
|
|
7432
|
+
for (const existing of readdirSync4(droneHome)) {
|
|
6074
7433
|
if (wanted.has(existing)) continue;
|
|
6075
|
-
const p =
|
|
7434
|
+
const p = join8(droneHome, existing);
|
|
6076
7435
|
try {
|
|
6077
7436
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
6078
7437
|
} catch {
|
|
6079
7438
|
}
|
|
6080
7439
|
}
|
|
6081
7440
|
for (const name of wanted) {
|
|
6082
|
-
const link =
|
|
7441
|
+
const link = join8(droneHome, name);
|
|
6083
7442
|
try {
|
|
6084
7443
|
lstatSync(link);
|
|
6085
7444
|
continue;
|
|
6086
7445
|
} catch {
|
|
6087
7446
|
}
|
|
6088
|
-
const target =
|
|
7447
|
+
const target = join8(realHome, name);
|
|
6089
7448
|
let isDir;
|
|
6090
7449
|
try {
|
|
6091
|
-
isDir =
|
|
7450
|
+
isDir = statSync5(target).isDirectory();
|
|
6092
7451
|
} catch {
|
|
6093
7452
|
continue;
|
|
6094
7453
|
}
|
|
@@ -6106,7 +7465,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
6106
7465
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
6107
7466
|
];
|
|
6108
7467
|
function realPathEnvPins(realHome) {
|
|
6109
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
7468
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join8(realHome, ...segments)]));
|
|
6110
7469
|
}
|
|
6111
7470
|
function droneDenyRules(realHome, swarmHome) {
|
|
6112
7471
|
const paths = [
|
|
@@ -6198,7 +7557,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
6198
7557
|
const env = { ...base3 };
|
|
6199
7558
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
6200
7559
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
6201
|
-
env.TURBO_CACHE_DIR =
|
|
7560
|
+
env.TURBO_CACHE_DIR = join9(worktree, ".turbo", "cache");
|
|
6202
7561
|
}
|
|
6203
7562
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
6204
7563
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -6226,20 +7585,20 @@ var DAEMON_STATE_ENV = [
|
|
|
6226
7585
|
"TASKLESS_TOKEN"
|
|
6227
7586
|
];
|
|
6228
7587
|
function bundledRoleDir(kind) {
|
|
6229
|
-
return
|
|
7588
|
+
return join9(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
6230
7589
|
}
|
|
6231
7590
|
function roleDir(worktree, kind) {
|
|
6232
|
-
return
|
|
7591
|
+
return join9(worktree, ".claude", "roles", kind);
|
|
6233
7592
|
}
|
|
6234
7593
|
function roleDirs(worktree, kind) {
|
|
6235
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
7594
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync6(dir));
|
|
6236
7595
|
}
|
|
6237
7596
|
function nextRunLogPath(runLogsDir, runId) {
|
|
6238
|
-
const dir =
|
|
7597
|
+
const dir = join9(runLogsDir, String(runId));
|
|
6239
7598
|
mkdirSync5(dir, { recursive: true });
|
|
6240
7599
|
let files = [];
|
|
6241
7600
|
try {
|
|
6242
|
-
files =
|
|
7601
|
+
files = readdirSync5(dir);
|
|
6243
7602
|
} catch {
|
|
6244
7603
|
}
|
|
6245
7604
|
let next = 0;
|
|
@@ -6247,7 +7606,7 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
6247
7606
|
const m = /^(\d+)\.json/.exec(f);
|
|
6248
7607
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
6249
7608
|
}
|
|
6250
|
-
return
|
|
7609
|
+
return join9(dir, `${next}.json`);
|
|
6251
7610
|
}
|
|
6252
7611
|
function openNextRunLog(runLogsDir, runId) {
|
|
6253
7612
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -6321,16 +7680,16 @@ function makeLauncher(cfg) {
|
|
|
6321
7680
|
}
|
|
6322
7681
|
}
|
|
6323
7682
|
};
|
|
6324
|
-
const cfgPath =
|
|
7683
|
+
const cfgPath = join9(worktree, ".swarm-mcp.json");
|
|
6325
7684
|
writeFileSync7(cfgPath, JSON.stringify(mcpConfig, null, 2));
|
|
6326
7685
|
const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
|
|
6327
7686
|
const pluginDirs = roleDirs(worktree, run.kind);
|
|
6328
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
7687
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
6329
7688
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
6330
7689
|
const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
|
|
6331
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
7690
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync6(join9(cfg.droneIsolation.swarmHome, "token"))) {
|
|
6332
7691
|
console.error(
|
|
6333
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
7692
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join9(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
6334
7693
|
);
|
|
6335
7694
|
}
|
|
6336
7695
|
let stdio = "ignore";
|
|
@@ -6360,14 +7719,17 @@ function makeLauncher(cfg) {
|
|
|
6360
7719
|
};
|
|
6361
7720
|
}
|
|
6362
7721
|
|
|
7722
|
+
// src/cli.ts
|
|
7723
|
+
init_droneChannel();
|
|
7724
|
+
|
|
6363
7725
|
// src/git.ts
|
|
6364
|
-
import { existsSync as
|
|
7726
|
+
import { existsSync as existsSync7, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
6365
7727
|
import { appendFile, mkdir, readFile, rm } from "fs/promises";
|
|
6366
|
-
import { isAbsolute, join as
|
|
7728
|
+
import { isAbsolute as isAbsolute2, join as join10 } from "path";
|
|
6367
7729
|
import { $ as $4 } from "execa";
|
|
6368
7730
|
function isLinkedWorktree(dir) {
|
|
6369
7731
|
try {
|
|
6370
|
-
return
|
|
7732
|
+
return statSync7(join10(dir, ".git")).isFile();
|
|
6371
7733
|
} catch {
|
|
6372
7734
|
return false;
|
|
6373
7735
|
}
|
|
@@ -6379,7 +7741,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
6379
7741
|
}
|
|
6380
7742
|
}
|
|
6381
7743
|
async function ensureWorktreeDirGone(worktree) {
|
|
6382
|
-
if (!
|
|
7744
|
+
if (!existsSync7(worktree)) return false;
|
|
6383
7745
|
if (!isLinkedWorktree(worktree)) {
|
|
6384
7746
|
console.error(
|
|
6385
7747
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -6394,7 +7756,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
6394
7756
|
);
|
|
6395
7757
|
return true;
|
|
6396
7758
|
}
|
|
6397
|
-
if (
|
|
7759
|
+
if (existsSync7(worktree)) {
|
|
6398
7760
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
6399
7761
|
}
|
|
6400
7762
|
return true;
|
|
@@ -6406,52 +7768,8 @@ async function removeWorktree(repo, path) {
|
|
|
6406
7768
|
if (branch) await $4({ reject: false })`git -C ${repo} branch -D ${branch}`;
|
|
6407
7769
|
}
|
|
6408
7770
|
|
|
6409
|
-
// src/
|
|
6410
|
-
|
|
6411
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
6412
|
-
import { join as join9 } from "path";
|
|
6413
|
-
var STORE_PATH2 = join9(SWARM_HOME, "agent.json");
|
|
6414
|
-
var base2 = (api) => api.replace(/\/$/, "");
|
|
6415
|
-
function loadAgentToken() {
|
|
6416
|
-
try {
|
|
6417
|
-
const s = JSON.parse(readFileSync8(STORE_PATH2, "utf8"));
|
|
6418
|
-
return s.token ? s : null;
|
|
6419
|
-
} catch {
|
|
6420
|
-
return null;
|
|
6421
|
-
}
|
|
6422
|
-
}
|
|
6423
|
-
function saveAgentToken(s) {
|
|
6424
|
-
writeFileSync8(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
6425
|
-
}
|
|
6426
|
-
function clearAgentToken() {
|
|
6427
|
-
try {
|
|
6428
|
-
unlinkSync2(STORE_PATH2);
|
|
6429
|
-
} catch {
|
|
6430
|
-
}
|
|
6431
|
-
}
|
|
6432
|
-
async function mintAgentToken(args, fetchFn = fetch) {
|
|
6433
|
-
const res = await fetchFn(`${base2(args.api)}/api/swarm/agent/token`, {
|
|
6434
|
-
method: "POST",
|
|
6435
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
|
|
6436
|
-
body: JSON.stringify(args.label ? { label: args.label } : {})
|
|
6437
|
-
});
|
|
6438
|
-
const text = await res.text();
|
|
6439
|
-
if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
6440
|
-
const j = JSON.parse(text);
|
|
6441
|
-
if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
|
|
6442
|
-
return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
|
|
6443
|
-
}
|
|
6444
|
-
async function probeAgentToken(args, fetchFn = fetch) {
|
|
6445
|
-
try {
|
|
6446
|
-
const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
|
|
6447
|
-
headers: { Authorization: `Bearer ${args.token}` }
|
|
6448
|
-
});
|
|
6449
|
-
if (res.status === 401 || res.status === 403) return "revoked";
|
|
6450
|
-
return res.ok ? "ok" : "unreachable";
|
|
6451
|
-
} catch {
|
|
6452
|
-
return "unreachable";
|
|
6453
|
-
}
|
|
6454
|
-
}
|
|
7771
|
+
// src/cli.ts
|
|
7772
|
+
init_agentToken();
|
|
6455
7773
|
|
|
6456
7774
|
// src/taskless.ts
|
|
6457
7775
|
init_src();
|
|
@@ -6460,7 +7778,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
6460
7778
|
|
|
6461
7779
|
// src/attachments.ts
|
|
6462
7780
|
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
6463
|
-
import { join as
|
|
7781
|
+
import { join as join12 } from "path";
|
|
6464
7782
|
var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
6465
7783
|
var ATTACH_MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
6466
7784
|
var IMAGE_EXT = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "heic"]);
|
|
@@ -6470,20 +7788,21 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
|
|
|
6470
7788
|
|
|
6471
7789
|
// src/shotUpload.ts
|
|
6472
7790
|
import { readFile as fsReadFile } from "fs/promises";
|
|
6473
|
-
import { basename as basename3, isAbsolute as
|
|
7791
|
+
import { basename as basename3, isAbsolute as isAbsolute3, join as join13 } from "path";
|
|
6474
7792
|
var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
6475
7793
|
|
|
6476
7794
|
// src/taskless.ts
|
|
6477
7795
|
init_config();
|
|
7796
|
+
init_agentToken();
|
|
6478
7797
|
|
|
6479
7798
|
// src/workspace.ts
|
|
6480
7799
|
init_config();
|
|
6481
|
-
import { readFileSync as
|
|
6482
|
-
import { join as
|
|
6483
|
-
var WORKSPACE_PATH =
|
|
7800
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
|
|
7801
|
+
import { join as join14 } from "path";
|
|
7802
|
+
var WORKSPACE_PATH = join14(SWARM_HOME, "workspace.json");
|
|
6484
7803
|
function loadWorkspace() {
|
|
6485
7804
|
try {
|
|
6486
|
-
const w = JSON.parse(
|
|
7805
|
+
const w = JSON.parse(readFileSync11(WORKSPACE_PATH, "utf8"));
|
|
6487
7806
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
6488
7807
|
} catch {
|
|
6489
7808
|
return null;
|
|
@@ -6494,7 +7813,7 @@ function saveWorkspace(w) {
|
|
|
6494
7813
|
}
|
|
6495
7814
|
function clearWorkspace() {
|
|
6496
7815
|
try {
|
|
6497
|
-
|
|
7816
|
+
unlinkSync4(WORKSPACE_PATH);
|
|
6498
7817
|
} catch {
|
|
6499
7818
|
}
|
|
6500
7819
|
}
|
|
@@ -6639,7 +7958,7 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
6639
7958
|
}
|
|
6640
7959
|
|
|
6641
7960
|
// src/login.ts
|
|
6642
|
-
import { execa } from "execa";
|
|
7961
|
+
import { execa as execa2 } from "execa";
|
|
6643
7962
|
async function waitForLogin(d) {
|
|
6644
7963
|
const deadline = d.now() + d.timeoutMs;
|
|
6645
7964
|
for (; ; ) {
|
|
@@ -6704,134 +8023,15 @@ function parseWhoAmI(res) {
|
|
|
6704
8023
|
async function openBrowser(url) {
|
|
6705
8024
|
const [cmd2, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
6706
8025
|
try {
|
|
6707
|
-
await
|
|
8026
|
+
await execa2(cmd2, args, { stdio: "ignore" });
|
|
6708
8027
|
return true;
|
|
6709
8028
|
} catch {
|
|
6710
8029
|
return false;
|
|
6711
8030
|
}
|
|
6712
8031
|
}
|
|
6713
8032
|
|
|
6714
|
-
// src/screenshot.ts
|
|
6715
|
-
var DESKTOP_VIEWPORT = { name: "desktop", width: 1360, height: 940 };
|
|
6716
|
-
var MOBILE_VIEWPORT = { name: "mobile", width: 390, height: 844 };
|
|
6717
|
-
var DEFAULT_VIEWPORTS = [DESKTOP_VIEWPORT, MOBILE_VIEWPORT];
|
|
6718
|
-
var DEFAULT_SHOT_TIMEOUT_MS = 2e4;
|
|
6719
|
-
var NETWORK_SETTLE_QUIET_MS = 500;
|
|
6720
|
-
var NETWORK_SETTLE_TIMEOUT_MS = 8e3;
|
|
6721
|
-
async function captureScreenshots(launchBrowser, opts) {
|
|
6722
|
-
const viewports = opts.viewports ?? DEFAULT_VIEWPORTS;
|
|
6723
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHOT_TIMEOUT_MS;
|
|
6724
|
-
const fullPage = opts.fullPage ?? true;
|
|
6725
|
-
const browser = await launchBrowser();
|
|
6726
|
-
try {
|
|
6727
|
-
const results = [];
|
|
6728
|
-
for (const vp of viewports) {
|
|
6729
|
-
const ctx = await browser.newContext({
|
|
6730
|
-
viewport: { width: vp.width, height: vp.height },
|
|
6731
|
-
...opts.storageStatePath ? { storageState: opts.storageStatePath } : {}
|
|
6732
|
-
});
|
|
6733
|
-
try {
|
|
6734
|
-
const page = await ctx.newPage();
|
|
6735
|
-
await page.goto(opts.url, { timeout: timeoutMs, waitUntil: "load" });
|
|
6736
|
-
await page.waitForNetworkSettle({
|
|
6737
|
-
quietMs: NETWORK_SETTLE_QUIET_MS,
|
|
6738
|
-
timeoutMs: Math.min(timeoutMs, NETWORK_SETTLE_TIMEOUT_MS)
|
|
6739
|
-
});
|
|
6740
|
-
const path = `${opts.outPrefix}-${vp.name}.png`;
|
|
6741
|
-
await page.screenshot({ path, fullPage, timeout: timeoutMs });
|
|
6742
|
-
results.push({ viewport: vp.name, path });
|
|
6743
|
-
} finally {
|
|
6744
|
-
await ctx.close();
|
|
6745
|
-
}
|
|
6746
|
-
}
|
|
6747
|
-
return results;
|
|
6748
|
-
} finally {
|
|
6749
|
-
await browser.close();
|
|
6750
|
-
}
|
|
6751
|
-
}
|
|
6752
|
-
function isEventStream(headers2) {
|
|
6753
|
-
for (const [k, v] of Object.entries(headers2)) {
|
|
6754
|
-
if (k.toLowerCase() === "content-type") return v.toLowerCase().startsWith("text/event-stream");
|
|
6755
|
-
}
|
|
6756
|
-
return false;
|
|
6757
|
-
}
|
|
6758
|
-
function createNetworkSettleTracker(target) {
|
|
6759
|
-
const pending2 = /* @__PURE__ */ new Set();
|
|
6760
|
-
const onChange = /* @__PURE__ */ new Set();
|
|
6761
|
-
const notify = () => {
|
|
6762
|
-
for (const cb of [...onChange]) cb();
|
|
6763
|
-
};
|
|
6764
|
-
target.on("request", ((req) => {
|
|
6765
|
-
pending2.add(req);
|
|
6766
|
-
notify();
|
|
6767
|
-
}));
|
|
6768
|
-
target.on("response", ((res) => {
|
|
6769
|
-
if (isEventStream(res.headers())) {
|
|
6770
|
-
pending2.delete(res.request());
|
|
6771
|
-
notify();
|
|
6772
|
-
}
|
|
6773
|
-
}));
|
|
6774
|
-
const onDone = ((req) => {
|
|
6775
|
-
pending2.delete(req);
|
|
6776
|
-
notify();
|
|
6777
|
-
});
|
|
6778
|
-
target.on("requestfinished", onDone);
|
|
6779
|
-
target.on("requestfailed", onDone);
|
|
6780
|
-
return {
|
|
6781
|
-
waitForSettle({ quietMs, timeoutMs }) {
|
|
6782
|
-
return new Promise((resolve3) => {
|
|
6783
|
-
let quietTimer;
|
|
6784
|
-
const hardTimer = setTimeout(finish, timeoutMs);
|
|
6785
|
-
function finish() {
|
|
6786
|
-
clearTimeout(quietTimer);
|
|
6787
|
-
clearTimeout(hardTimer);
|
|
6788
|
-
onChange.delete(check);
|
|
6789
|
-
resolve3();
|
|
6790
|
-
}
|
|
6791
|
-
function check() {
|
|
6792
|
-
clearTimeout(quietTimer);
|
|
6793
|
-
if (pending2.size === 0) quietTimer = setTimeout(finish, quietMs);
|
|
6794
|
-
}
|
|
6795
|
-
onChange.add(check);
|
|
6796
|
-
check();
|
|
6797
|
-
});
|
|
6798
|
-
}
|
|
6799
|
-
};
|
|
6800
|
-
}
|
|
6801
|
-
function wrapPage(page) {
|
|
6802
|
-
const tracker = createNetworkSettleTracker(page);
|
|
6803
|
-
return {
|
|
6804
|
-
goto: (url, gotoOpts) => page.goto(url, gotoOpts),
|
|
6805
|
-
screenshot: (shotOpts) => page.screenshot(shotOpts),
|
|
6806
|
-
waitForNetworkSettle: (settleOpts) => tracker.waitForSettle(settleOpts)
|
|
6807
|
-
};
|
|
6808
|
-
}
|
|
6809
|
-
function wrapContext(ctx) {
|
|
6810
|
-
return {
|
|
6811
|
-
newPage: async () => wrapPage(await ctx.newPage()),
|
|
6812
|
-
close: () => ctx.close()
|
|
6813
|
-
};
|
|
6814
|
-
}
|
|
6815
|
-
function wrapBrowser(browser) {
|
|
6816
|
-
return {
|
|
6817
|
-
newContext: async (ctxOpts) => wrapContext(await browser.newContext(ctxOpts)),
|
|
6818
|
-
close: () => browser.close()
|
|
6819
|
-
};
|
|
6820
|
-
}
|
|
6821
|
-
async function launchChromium(opts = {}) {
|
|
6822
|
-
let mod;
|
|
6823
|
-
try {
|
|
6824
|
-
mod = await import("playwright-core");
|
|
6825
|
-
} catch {
|
|
6826
|
-
throw new Error(
|
|
6827
|
-
"playwright-core \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u043E\u0434\u0438\u043D \u0440\u0430\u0437: (\u0432 \u044D\u0442\u043E\u043C \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u043E) pnpm --filter @taskless-app/swarm add -D playwright-core; (\u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E) npm i -D playwright-core \u2014 \u0438 \u0432 \u043E\u0431\u043E\u0438\u0445 \u0441\u043B\u0443\u0447\u0430\u044F\u0445 npx playwright install chromium."
|
|
6828
|
-
);
|
|
6829
|
-
}
|
|
6830
|
-
const browser = await mod.chromium.launch({ headless: true, ...opts.timeoutMs !== void 0 ? { timeout: opts.timeoutMs } : {} });
|
|
6831
|
-
return wrapBrowser(browser);
|
|
6832
|
-
}
|
|
6833
|
-
|
|
6834
8033
|
// src/shotCli.ts
|
|
8034
|
+
init_screenshot();
|
|
6835
8035
|
init_config();
|
|
6836
8036
|
function isFlagValue(v) {
|
|
6837
8037
|
return v !== void 0 && !v.startsWith("--");
|
|
@@ -6906,82 +8106,19 @@ async function runShotCli(argv, deps = {}) {
|
|
|
6906
8106
|
}
|
|
6907
8107
|
}
|
|
6908
8108
|
|
|
6909
|
-
// src/
|
|
6910
|
-
|
|
6911
|
-
var TIMEOUT_MS = 5e3;
|
|
6912
|
-
var ACTION_TIMEOUT_MS = 2e4;
|
|
6913
|
-
var BASE = `http://127.0.0.1:${WEB_PORT}`;
|
|
6914
|
-
function headers(extra = {}) {
|
|
6915
|
-
return { "X-Swarm-Local": mintLocalCliToken(loadChannelSecret(DRONE_CHANNEL_SECRET_PATH)), ...extra };
|
|
6916
|
-
}
|
|
6917
|
-
async function daemonRuns(status) {
|
|
6918
|
-
try {
|
|
6919
|
-
const qs = status ? `?status=${encodeURIComponent(status)}` : "";
|
|
6920
|
-
const res = await fetch(`${BASE}/runs${qs}`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
6921
|
-
if (!res.ok) return null;
|
|
6922
|
-
const body = await res.json();
|
|
6923
|
-
if (!Array.isArray(body.runs)) return null;
|
|
6924
|
-
return { runs: body.runs, backend: String(body.backend ?? ""), hostId: body.hostId ?? null };
|
|
6925
|
-
} catch {
|
|
6926
|
-
return null;
|
|
6927
|
-
}
|
|
6928
|
-
}
|
|
6929
|
-
async function post(path, form) {
|
|
6930
|
-
try {
|
|
6931
|
-
const res = await fetch(`${BASE}${path}`, {
|
|
6932
|
-
method: "POST",
|
|
6933
|
-
headers: headers({ "Content-Type": "application/x-www-form-urlencoded" }),
|
|
6934
|
-
body: new URLSearchParams(form),
|
|
6935
|
-
signal: AbortSignal.timeout(ACTION_TIMEOUT_MS)
|
|
6936
|
-
});
|
|
6937
|
-
if (res.status === 503) return null;
|
|
6938
|
-
if (res.status === 401) return { ok: false, why: "\u043C\u043E\u0440\u0434\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B\u0430 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u043B\u044E\u0447 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C ~/.taskless-swarm/drone-channel.key" };
|
|
6939
|
-
return await res.json();
|
|
6940
|
-
} catch {
|
|
6941
|
-
return null;
|
|
6942
|
-
}
|
|
6943
|
-
}
|
|
6944
|
-
function daemonCancel(slug, reason) {
|
|
6945
|
-
return post("/cancel", { slug, reason });
|
|
6946
|
-
}
|
|
6947
|
-
function daemonWake(runId) {
|
|
6948
|
-
return post("/wake", { runId: String(runId) });
|
|
6949
|
-
}
|
|
6950
|
-
function daemonPause(reason) {
|
|
6951
|
-
return post("/pause", { source: "cli", ...reason ? { reason } : {} });
|
|
6952
|
-
}
|
|
6953
|
-
function daemonResume() {
|
|
6954
|
-
return post("/resume", { source: "cli" });
|
|
6955
|
-
}
|
|
6956
|
-
async function daemonStop(opts) {
|
|
6957
|
-
const res = await post("/shutdown", { force: opts.force ? "1" : "0", wait: opts.wait ? "1" : "0" });
|
|
6958
|
-
if (!res) return null;
|
|
6959
|
-
const raw = res;
|
|
6960
|
-
if (raw.stopping === void 0 && raw.waiting === void 0) return null;
|
|
6961
|
-
return { stopping: !!raw.stopping, waiting: !!raw.waiting, sessions: Array.isArray(raw.sessions) ? raw.sessions : [] };
|
|
6962
|
-
}
|
|
6963
|
-
async function daemonStatus2() {
|
|
6964
|
-
try {
|
|
6965
|
-
const res = await fetch(`${BASE}/status`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
6966
|
-
if (!res.ok) return null;
|
|
6967
|
-
const body = await res.json();
|
|
6968
|
-
return typeof body?.paused === "boolean" ? body : null;
|
|
6969
|
-
} catch {
|
|
6970
|
-
return null;
|
|
6971
|
-
}
|
|
6972
|
-
}
|
|
6973
|
-
var NO_DAEMON_NOTE = `\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT}) \u2014 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E \u041B\u041E\u041A\u0410\u041B\u042C\u041D\u042B\u0419 \u0441\u0442\u043E\u0440 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B, \u043E\u043D \u043C\u043E\u0436\u0435\u0442 \u0440\u0430\u0441\u0445\u043E\u0434\u0438\u0442\u044C\u0441\u044F \u0441 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u043E\u043C. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0434\u0435\u043C\u043E\u043D\u0430: swarm start`;
|
|
8109
|
+
// src/cli.ts
|
|
8110
|
+
init_daemonLink();
|
|
6974
8111
|
|
|
6975
8112
|
// src/pauseState.ts
|
|
6976
|
-
import { existsSync as
|
|
6977
|
-
import { join as
|
|
8113
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
8114
|
+
import { join as join15 } from "path";
|
|
6978
8115
|
function pauseStatePath(home) {
|
|
6979
|
-
return
|
|
8116
|
+
return join15(home, "pause.json");
|
|
6980
8117
|
}
|
|
6981
8118
|
var ACTIVE = { paused: false, at: "", source: "cli" };
|
|
6982
8119
|
function loadPauseState(home, io = {}) {
|
|
6983
|
-
const exists = io.exists ??
|
|
6984
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
8120
|
+
const exists = io.exists ?? existsSync8;
|
|
8121
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
6985
8122
|
const path = pauseStatePath(home);
|
|
6986
8123
|
if (!exists(path)) return ACTIVE;
|
|
6987
8124
|
try {
|
|
@@ -7000,13 +8137,13 @@ function loadPauseState(home, io = {}) {
|
|
|
7000
8137
|
function savePauseState(home, state, io = {}) {
|
|
7001
8138
|
const path = pauseStatePath(home);
|
|
7002
8139
|
if (!state.paused) {
|
|
7003
|
-
const remove = io.remove ?? ((p) =>
|
|
8140
|
+
const remove = io.remove ?? ((p) => existsSync8(p) && unlinkSync5(p));
|
|
7004
8141
|
remove(path);
|
|
7005
8142
|
return;
|
|
7006
8143
|
}
|
|
7007
8144
|
const writeFile2 = io.writeFile ?? ((p, data) => {
|
|
7008
8145
|
writeFileSync10(`${p}.tmp`, data);
|
|
7009
|
-
|
|
8146
|
+
renameSync5(`${p}.tmp`, p);
|
|
7010
8147
|
});
|
|
7011
8148
|
writeFile2(path, JSON.stringify(state, null, 2));
|
|
7012
8149
|
}
|
|
@@ -7035,10 +8172,10 @@ function sinceText(at, now) {
|
|
|
7035
8172
|
}
|
|
7036
8173
|
|
|
7037
8174
|
// src/heartbeat.ts
|
|
7038
|
-
import { readFileSync as
|
|
8175
|
+
import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
7039
8176
|
function readHeartbeat(path) {
|
|
7040
8177
|
try {
|
|
7041
|
-
const ms = Number(
|
|
8178
|
+
const ms = Number(readFileSync13(path, "utf8").trim());
|
|
7042
8179
|
return Number.isFinite(ms) ? ms : null;
|
|
7043
8180
|
} catch {
|
|
7044
8181
|
return null;
|
|
@@ -7047,7 +8184,7 @@ function readHeartbeat(path) {
|
|
|
7047
8184
|
|
|
7048
8185
|
// src/cli.ts
|
|
7049
8186
|
init_src();
|
|
7050
|
-
|
|
8187
|
+
init_version();
|
|
7051
8188
|
function takeRepo(argv) {
|
|
7052
8189
|
const i = argv.indexOf("--repo");
|
|
7053
8190
|
if (i === -1) return { repo: null, rest: argv };
|
|
@@ -7101,14 +8238,6 @@ function takeWorkspace(argv) {
|
|
|
7101
8238
|
const workspace = argv[i + 1] ?? null;
|
|
7102
8239
|
return { workspace, rest: [...argv.slice(0, i), ...argv.slice(i + 2)] };
|
|
7103
8240
|
}
|
|
7104
|
-
async function askTty(q) {
|
|
7105
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7106
|
-
try {
|
|
7107
|
-
return await rl.question(q);
|
|
7108
|
-
} finally {
|
|
7109
|
-
rl.close();
|
|
7110
|
-
}
|
|
7111
|
-
}
|
|
7112
8241
|
async function runLogin(db2, opts) {
|
|
7113
8242
|
const web = startWeb({ db: db2 });
|
|
7114
8243
|
try {
|
|
@@ -7227,7 +8356,6 @@ async function waitDaemonDown(opts) {
|
|
|
7227
8356
|
await sleep2(pollMs);
|
|
7228
8357
|
}
|
|
7229
8358
|
}
|
|
7230
|
-
var MAX_DAEMON_LOG_BYTES = 16 * 1024 * 1024;
|
|
7231
8359
|
var START_READY_TIMEOUT_MS = 45e3;
|
|
7232
8360
|
async function runStartDetached(args) {
|
|
7233
8361
|
const already = await daemonStatus2();
|
|
@@ -7236,7 +8364,7 @@ async function runStartDetached(args) {
|
|
|
7236
8364
|
console.log("\u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435: swarm status \xB7 \u043B\u043E\u0433: swarm logs -f \xB7 \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C: swarm stop");
|
|
7237
8365
|
return 0;
|
|
7238
8366
|
}
|
|
7239
|
-
const rotated = rotateDaemonLog(DAEMON_LOG_PATH,
|
|
8367
|
+
const rotated = rotateDaemonLog(DAEMON_LOG_PATH, { maxBytes: DAEMON_LOG_MAX_BYTES, keep: DAEMON_LOG_KEEP });
|
|
7240
8368
|
const entry = process.argv[1];
|
|
7241
8369
|
if (!entry) {
|
|
7242
8370
|
console.error("\u043D\u0435\u0447\u0435\u043C \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0441\u0435\u0431\u044F \u0444\u043E\u043D\u043E\u043C (\u043F\u0443\u0441\u0442\u043E\u0439 argv[1]) \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435: swarm start --foreground");
|
|
@@ -7289,7 +8417,7 @@ async function runDaemonLogs(argv) {
|
|
|
7289
8417
|
console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
|
|
7290
8418
|
return 1;
|
|
7291
8419
|
}
|
|
7292
|
-
if (!
|
|
8420
|
+
if (!existsSync13(DAEMON_LOG_PATH)) {
|
|
7293
8421
|
console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
|
|
7294
8422
|
console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
|
|
7295
8423
|
return 1;
|
|
@@ -7297,12 +8425,12 @@ async function runDaemonLogs(argv) {
|
|
|
7297
8425
|
const head = tailFile(DAEMON_LOG_PATH, lines);
|
|
7298
8426
|
if (head) console.log(head);
|
|
7299
8427
|
if (!follow) return 0;
|
|
7300
|
-
let pos =
|
|
8428
|
+
let pos = statSync8(DAEMON_LOG_PATH).size;
|
|
7301
8429
|
for (; ; ) {
|
|
7302
8430
|
await sleep2(500);
|
|
7303
8431
|
let size;
|
|
7304
8432
|
try {
|
|
7305
|
-
size =
|
|
8433
|
+
size = statSync8(DAEMON_LOG_PATH).size;
|
|
7306
8434
|
} catch {
|
|
7307
8435
|
continue;
|
|
7308
8436
|
}
|
|
@@ -7463,16 +8591,24 @@ function printRuns(runs, view) {
|
|
|
7463
8591
|
}).forEach((l) => console.log(l));
|
|
7464
8592
|
}
|
|
7465
8593
|
function usage() {
|
|
7466
|
-
console.log(`swarm \u2014 \u0440\u043E\u0439 AI-\u0434\u0440\u043E\u043D\u043E\u0432 (v${
|
|
8594
|
+
console.log(`swarm \u2014 \u0440\u043E\u0439 AI-\u0434\u0440\u043E\u043D\u043E\u0432 (v${PACKAGE_VERSION})
|
|
7467
8595
|
|
|
7468
8596
|
\u041D\u0410\u0427\u0410\u0422\u042C
|
|
8597
|
+
swarm install [--yes] \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B \u0432 \u0440\u0430\u0431\u043E\u0447\u0435\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435: Node, claude, gh,
|
|
8598
|
+
\u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0430\u043A\u0435\u0442 \u0440\u043E\u044F, playwright-core \u0438 Chromium \u0434\u043B\u044F \u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u043E\u0432,
|
|
8599
|
+
Docker (\u0435\u0441\u043B\u0438 \u043D\u0443\u0436\u0435\u043D \u043F\u0440\u043E\u0435\u043A\u0442\u0443), \u043A\u043B\u043E\u043D \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F. \u041E\u0434\u0438\u043D\u0430\u043A\u043E\u0432\u043E \u043D\u0430
|
|
8600
|
+
macOS, Linux \u0438 WSL; sudo \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0432 \u0441\u0442\u0440\u043E\u043A\u0443 \u0438 \u0441\u043F\u0440\u043E\u0441\u0438\u0432
|
|
8601
|
+
swarm doctor \u0442\u0430 \u0436\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0411\u0415\u0417 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438: \u0447\u0442\u043E \u043D\u0435 \u0442\u0430\u043A \u0438 \u043A\u0430\u043A \u0447\u0438\u043D\u0438\u0442\u044C
|
|
8602
|
+
swarm update \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0440\u043E\u0439 \u0434\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 (\u0442\u0435\u043C \u0436\u0435 \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C,
|
|
8603
|
+
\u043A\u0430\u043A\u0438\u043C \u043E\u043D \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D)
|
|
7469
8604
|
swarm login [--workspace <slug|id>] [--no-open]
|
|
7470
8605
|
\u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441
|
|
7471
8606
|
swarm start [--repo <path>] [--foreground]
|
|
7472
8607
|
\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442 \u0438 \u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432.
|
|
7473
8608
|
\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0430\u0445\u043E\u0434\u0438\u0442 \u0441\u0430\u043C \u2014 \u043A\u043B\u043E\u043D\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 Taskless \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435;
|
|
7474
8609
|
--repo \u043D\u0443\u0436\u0435\u043D, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u043D\u0435\u043E\u0431\u044B\u0447\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435
|
|
7475
|
-
swarm status \u0436\u0438\u0432 \u043B\u0438 \u0434\u0435\u043C\u043E\u043D, \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u043B\u0438 \u0440\u043E\u0439, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435
|
|
8610
|
+
swarm status \u0436\u0438\u0432 \u043B\u0438 \u0434\u0435\u043C\u043E\u043D, \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u043B\u0438 \u0440\u043E\u0439, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435,
|
|
8611
|
+
\u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F \u0438 \u0433\u0434\u0435 \u043D\u0430\u0448\u043B\u0438\u0441\u044C \u0438\u0445 \u043A\u043B\u043E\u043D\u044B
|
|
7476
8612
|
|
|
7477
8613
|
\u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
|
|
7478
8614
|
swarm spawn <slug> [area] [\u0444\u043B\u0430\u0433\u0438 \u043D\u0438\u0436\u0435]
|
|
@@ -7530,10 +8666,10 @@ function effectiveWorktreesDir() {
|
|
|
7530
8666
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
7531
8667
|
targetDir: WORKTREES_DIR,
|
|
7532
8668
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
7533
|
-
exists: (d) =>
|
|
8669
|
+
exists: (d) => existsSync13(d),
|
|
7534
8670
|
isNonEmptyDir: (d) => {
|
|
7535
8671
|
try {
|
|
7536
|
-
return
|
|
8672
|
+
return readdirSync7(d).length > 0;
|
|
7537
8673
|
} catch {
|
|
7538
8674
|
return false;
|
|
7539
8675
|
}
|
|
@@ -7566,7 +8702,7 @@ async function finishClient(db2) {
|
|
|
7566
8702
|
}
|
|
7567
8703
|
var [cmd, ...rawArgs] = process.argv.slice(2);
|
|
7568
8704
|
if (cmd === "--version" || cmd === "-v") {
|
|
7569
|
-
console.log(
|
|
8705
|
+
console.log(PACKAGE_VERSION);
|
|
7570
8706
|
process.exit(0);
|
|
7571
8707
|
}
|
|
7572
8708
|
if (cmd === "logout") {
|
|
@@ -7688,6 +8824,7 @@ if (cmd === "status") {
|
|
|
7688
8824
|
const counts = Object.entries(st.counts).filter(([, n]) => n > 0);
|
|
7689
8825
|
console.log(counts.length ? `\u043F\u0440\u043E\u0433\u043E\u043D\u044B: ${counts.map(([s, n]) => `${s} ${n}`).join(", ")}` : "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u043D\u0435\u0442");
|
|
7690
8826
|
console.log(st.live.length ? `\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441: ${st.live.length} (#${st.live.join(", #")})` : "\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435\u0442");
|
|
8827
|
+
for (const line of repoMapLines(st.repos)) console.log(line);
|
|
7691
8828
|
process.exit(0);
|
|
7692
8829
|
}
|
|
7693
8830
|
if (cmd === "pause" || cmd === "resume") {
|
|
@@ -7718,6 +8855,15 @@ if (cmd === "remove") {
|
|
|
7718
8855
|
const code = await runRemove(rawArgs);
|
|
7719
8856
|
process.exit(code);
|
|
7720
8857
|
}
|
|
8858
|
+
if (cmd === "install" || cmd === "doctor") {
|
|
8859
|
+
const { runInstallCli: runInstallCli2, runDoctorCli: runDoctorCli2 } = await Promise.resolve().then(() => (init_installCli(), installCli_exports));
|
|
8860
|
+
const run = cmd === "install" ? runInstallCli2 : runDoctorCli2;
|
|
8861
|
+
process.exit(await run(rawArgs, { packageVersion: PACKAGE_VERSION }));
|
|
8862
|
+
}
|
|
8863
|
+
if (cmd === "update") {
|
|
8864
|
+
const { runUpdateCli: runUpdateCli2 } = await Promise.resolve().then(() => (init_updateCli(), updateCli_exports));
|
|
8865
|
+
process.exit(await runUpdateCli2(rawArgs, { packageVersion: PACKAGE_VERSION }));
|
|
8866
|
+
}
|
|
7721
8867
|
if (cmd === "shot") {
|
|
7722
8868
|
const code = await runShotCli(rawArgs);
|
|
7723
8869
|
process.exit(code);
|