@taskless-app/swarm 0.4.10 → 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.
Files changed (4) hide show
  1. package/README.md +98 -16
  2. package/dist/cli.js +1700 -610
  3. package/dist/overmind.js +603 -206
  4. 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
 
@@ -384,7 +424,7 @@ var init_text = __esm({
384
424
  "use strict";
385
425
  ANSI_CSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
386
426
  CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
387
- EVENT_TEXT_MAX = 4e3;
427
+ EVENT_TEXT_MAX = 16e3;
388
428
  }
389
429
  });
390
430
 
@@ -645,7 +685,7 @@ function tokenFromFile() {
645
685
  return "";
646
686
  }
647
687
  }
648
- 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;
649
689
  var init_config = __esm({
650
690
  "src/config.ts"() {
651
691
  "use strict";
@@ -661,6 +701,9 @@ var init_config = __esm({
661
701
  WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
662
702
  RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join(SWARM_HOME, "run-logs");
663
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);
664
707
  DIST_BUILD = true;
665
708
  SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
666
709
  SWARM_OUTBOX = process.env.SWARM_OUTBOX || join(SWARM_HOME, "outbox.jsonl");
@@ -695,6 +738,9 @@ var init_config = __esm({
695
738
  SWARM_REPO = process.env.SWARM_REPO || "";
696
739
  SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
697
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);
698
744
  TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
699
745
  SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
700
746
  TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
@@ -753,48 +799,583 @@ var init_names2 = __esm({
753
799
  }
754
800
  });
755
801
 
756
- // src/storm.ts
757
- function encodeProbe(rec) {
758
- return JSON.stringify(rec);
802
+ // src/storm.ts
803
+ function encodeProbe(rec) {
804
+ return JSON.stringify(rec);
805
+ }
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 };
759
1312
  }
760
- function decodeProbe(text) {
761
- if (!text) return { probe: null, ext: null, reason: null, resetHint: null };
1313
+ async function daemonRuns(status) {
762
1314
  try {
763
- const o = JSON.parse(text);
764
- return {
765
- probe: typeof o.probe === "boolean" ? o.probe : null,
766
- ext: typeof o.ext === "boolean" ? o.ext : null,
767
- reason: o.reason ?? null,
768
- resetHint: o.resetHint ?? null
769
- };
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 };
770
1321
  } catch {
771
- return { probe: null, ext: null, reason: null, resetHint: null };
1322
+ return null;
772
1323
  }
773
1324
  }
774
- function lastProbeReason(recs) {
775
- for (let i = recs.length - 1; i >= 0; i--) {
776
- if (recs[i].reason != null) return recs[i].reason;
1325
+ async function post(path, form) {
1326
+ try {
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();
1336
+ } catch {
1337
+ return null;
777
1338
  }
778
- return null;
779
1339
  }
780
- var numEnv, DEFAULT_STORM_CONFIG;
781
- var init_storm = __esm({
782
- "src/storm.ts"() {
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;
1367
+ }
1368
+ }
1369
+ var TIMEOUT_MS, ACTION_TIMEOUT_MS, BASE, NO_DAEMON_NOTE;
1370
+ var init_daemonLink = __esm({
1371
+ "src/daemonLink.ts"() {
783
1372
  "use strict";
784
- init_src();
785
- numEnv = (name, def) => {
786
- const v = Number(process.env[name]);
787
- return Number.isFinite(v) && v > 0 ? v : def;
788
- };
789
- DEFAULT_STORM_CONFIG = {
790
- deathThreshold: numEnv("SWARM_STORM_DEATHS", 2),
791
- windowMs: numEnv("SWARM_STORM_WINDOW_MS", 5 * 60 * 1e3),
792
- spawnFailThreshold: numEnv("SWARM_STORM_SPAWN_FAILS", 2),
793
- probeIntervalMs: numEnv("SWARM_STORM_PROBE_INTERVAL_MS", 60 * 1e3),
794
- probeSuccessesToClear: numEnv("SWARM_STORM_PROBE_CLEAR", 2),
795
- probeTimeoutMs: numEnv("SWARM_STORM_PROBE_TIMEOUT_MS", 20 * 1e3),
796
- restartCooldownMs: numEnv("SWARM_STORM_RESTART_COOLDOWN_MS", 3 * 60 * 1e3)
797
- };
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`;
798
1379
  }
799
1380
  });
800
1381
 
@@ -804,8 +1385,8 @@ __export(db_exports, {
804
1385
  SwarmDb: () => SwarmDb
805
1386
  });
806
1387
  import { createRequire as createRequire2 } from "module";
807
- import { existsSync as existsSync8 } from "fs";
808
- import { resolve as resolve2 } from "path";
1388
+ import { existsSync as existsSync9 } from "fs";
1389
+ import { resolve as resolve3 } from "path";
809
1390
  function rowToQuestion(row) {
810
1391
  return {
811
1392
  id: row.id,
@@ -1285,11 +1866,11 @@ CREATE TABLE IF NOT EXISTS imports (
1285
1866
  * транзакции: прервётся — маркер не встанет, повтор безопасен.
1286
1867
  */
1287
1868
  importDb(sourcePath, repo) {
1288
- const abs = resolve2(sourcePath);
1869
+ const abs = resolve3(sourcePath);
1289
1870
  const repoN = normalizeRepo(repo);
1290
1871
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
1291
1872
  if (already) return { imported: false, runs: already.run_count };
1292
- if (!existsSync8(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
1873
+ if (!existsSync9(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
1293
1874
  const src = new this.databaseCtor(abs);
1294
1875
  try {
1295
1876
  const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
@@ -1597,28 +2178,904 @@ CREATE TABLE IF NOT EXISTS imports (
1597
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);
1598
2179
  return rows.map((r) => decodeProbe(r.text));
1599
2180
  }
1600
- /**
1601
- * Последняя РЕАЛЬНАЯ health-probe (probe !== null; ext-only записи пропускаем) с её
1602
- * временем. TASWM-137 п.д: подтверждающая проба перед входом в шторм по эвристике —
1603
- * зелёная проба вето́ит ложный шторм; свежую (в пределах probeIntervalMs) переиспользуем,
1604
- * не гоняя новую каждый тик. Смотрим только хвост (LIMIT) — старее нам всё равно не нужно.
1605
- */
1606
- lastHealthProbe() {
1607
- 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();
1608
- for (const r of rows) {
1609
- const rec = decodeProbe(r.text);
1610
- 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;
1611
2929
  }
1612
- return null;
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}`);
1613
2938
  }
1614
- };
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();
1615
3072
  }
1616
3073
  });
1617
3074
 
1618
3075
  // src/cli.ts
3076
+ init_cliIo();
1619
3077
  import { basename as basename4 } from "path";
1620
- import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync7, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
1621
- 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";
1622
3079
  import { setTimeout as sleep2 } from "timers/promises";
1623
3080
 
1624
3081
  // src/swarmClient.ts
@@ -1988,6 +3445,18 @@ var SwarmClient = class _SwarmClient {
1988
3445
  runKeyFor(localId) {
1989
3446
  return this.keyByLocal.get(localId);
1990
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
+ }
1991
3460
  // cuid прода → локальный id (по runKey гидрированного прогона `p:<cuid>`).
1992
3461
  // Для маппинга прод heroId → локальный hero_run_id при гидрации. Резолвится, когда
1993
3462
  // прогон-герой присутствует в зеркале; иначе null (уточнится следующим рефрешем).
@@ -3207,8 +4676,9 @@ var SwarmClient = class _SwarmClient {
3207
4676
  this.runs.set(id, local);
3208
4677
  this.keyByLocal.set(id, runKey);
3209
4678
  }
3210
- const slug = pr.taskSlug ?? pr.task?.slug ?? pr.taskId ?? local.task_slug;
3211
- local.task_slug = slug || 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;
3212
4682
  local.task_title = pr.taskTitle ?? local.task_title;
3213
4683
  local.task_desc = pr.taskDesc ?? local.task_desc;
3214
4684
  local.name = pr.droneName ?? local.name;
@@ -3527,7 +4997,7 @@ function parseDecision(v) {
3527
4997
  init_config();
3528
4998
 
3529
4999
  // src/daemonLaunch.ts
3530
- import { openSync as openSync2, closeSync as closeSync2, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
5000
+ import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync4 } from "fs";
3531
5001
  import { spawn } from "child_process";
3532
5002
  var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
3533
5003
  function parseStartMode(argv) {
@@ -3537,18 +5007,6 @@ function parseStartMode(argv) {
3537
5007
  function childStartArgv(rest) {
3538
5008
  return ["start", ...rest.filter((a) => !FOREGROUND_FLAGS.has(a)), "--foreground"];
3539
5009
  }
3540
- function rotateDaemonLog(path, maxBytes) {
3541
- let size;
3542
- try {
3543
- size = statSync2(path).size;
3544
- } catch {
3545
- return "missing";
3546
- }
3547
- if (size <= maxBytes) return "kept";
3548
- renameSync2(path, `${path}.1`);
3549
- writeFileSync3(path, "");
3550
- return "rotated";
3551
- }
3552
5010
  function tailText(text, lines) {
3553
5011
  const rows = text.split("\n");
3554
5012
  if (rows[rows.length - 1] === "") rows.pop();
@@ -3605,6 +5063,42 @@ function tailFile(path, lines) {
3605
5063
  }
3606
5064
  }
3607
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
+
3608
5102
  // src/procList.ts
3609
5103
  function parsePsRows(out) {
3610
5104
  const rows = [];
@@ -3681,7 +5175,7 @@ function descendantPids(rows, roots) {
3681
5175
 
3682
5176
  // src/procCleanup.ts
3683
5177
  function sleep(ms) {
3684
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5178
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
3685
5179
  }
3686
5180
  function parseLsofCwd(output) {
3687
5181
  const rows = [];
@@ -3836,45 +5330,24 @@ async function cleanupWorktreeProcesses(worktree, opts = {}) {
3836
5330
  }
3837
5331
  }
3838
5332
 
3839
- // src/repoRegistry.ts
3840
- init_config();
3841
- import { $ } from "execa";
3842
- function parseRepoFullName(remoteUrl) {
3843
- const m = remoteUrl.trim().match(/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(\.git)?\/?$/i);
3844
- if (!m) return null;
3845
- return `${m[1]}/${m[2]}`.toLowerCase();
3846
- }
3847
- async function resolveRepoFullName(path) {
3848
- const res = await $({ reject: false })`git -C ${path} remote get-url origin`;
3849
- if (res.exitCode !== 0) return null;
3850
- return parseRepoFullName(res.stdout);
3851
- }
3852
- async function buildRepoRegistry(paths) {
3853
- const resolved = await Promise.all(
3854
- paths.map(async (raw) => {
3855
- const path = normalizeRepo(raw);
3856
- if (!path) return null;
3857
- const repoFullName = await resolveRepoFullName(path);
3858
- return repoFullName ? { repoFullName, path } : null;
3859
- })
3860
- );
3861
- return resolved.filter((e) => e !== null);
3862
- }
5333
+ // src/cli.ts
5334
+ init_repoRegistry();
5335
+ init_repoDiscovery();
3863
5336
 
3864
5337
  // src/hostIdentity.ts
3865
5338
  import { hostname } from "os";
3866
- import { existsSync as existsSync3, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
3867
- import { join as join3 } from "path";
5339
+ import { existsSync as existsSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
5340
+ import { join as join5 } from "path";
3868
5341
  import { randomUUID as randomUUID2 } from "crypto";
3869
5342
  function hostIdentityPath(home) {
3870
- return join3(home, "host.json");
5343
+ return join5(home, "host.json");
3871
5344
  }
3872
5345
  function loadHostIdentity(deps) {
3873
- const exists = deps.exists ?? existsSync3;
3874
- const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
5346
+ const exists = deps.exists ?? existsSync4;
5347
+ const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
3875
5348
  const writeFile2 = deps.writeFile ?? ((p, data) => {
3876
5349
  writeFileSync4(`${p}.tmp`, data);
3877
- renameSync3(`${p}.tmp`, p);
5350
+ renameSync4(`${p}.tmp`, p);
3878
5351
  });
3879
5352
  const host = deps.hostname ?? hostname;
3880
5353
  const newId = deps.newId ?? randomUUID2;
@@ -3985,8 +5458,8 @@ import { basename as basename2 } from "path";
3985
5458
  import { serve as serve2 } from "@hono/node-server";
3986
5459
 
3987
5460
  // src/diff.ts
3988
- import { statSync as statSync3 } from "fs";
3989
- import { join as join4 } from "path";
5461
+ import { statSync as statSync4 } from "fs";
5462
+ import { join as join6 } from "path";
3990
5463
  import { $ as $2 } from "execa";
3991
5464
  var MAX_FILES = 200;
3992
5465
  var MAX_LINES_PER_FILE = 1500;
@@ -4129,7 +5602,7 @@ async function resolveBase(wt, baseBranch) {
4129
5602
  }
4130
5603
  function mtime(path) {
4131
5604
  try {
4132
- return statSync3(path).mtimeMs;
5605
+ return statSync4(path).mtimeMs;
4133
5606
  } catch {
4134
5607
  return 0;
4135
5608
  }
@@ -4154,7 +5627,7 @@ async function collectRunDiff(worktree, opts = {}) {
4154
5627
  files.push(pf);
4155
5628
  }
4156
5629
  }
4157
- files.sort((a, b) => mtime(join4(worktree, b.path)) - mtime(join4(worktree, a.path)));
5630
+ files.sort((a, b) => mtime(join6(worktree, b.path)) - mtime(join6(worktree, a.path)));
4158
5631
  const truncatedFiles = files.length > MAX_FILES;
4159
5632
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
4160
5633
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -4170,62 +5643,8 @@ async function collectRunDiff(worktree, opts = {}) {
4170
5643
  };
4171
5644
  }
4172
5645
 
4173
- // package.json
4174
- var package_default = {
4175
- name: "@taskless-app/swarm",
4176
- version: "0.4.10",
4177
- type: "module",
4178
- description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
4179
- keywords: [
4180
- "ai-agents",
4181
- "coding-agents",
4182
- "swarm",
4183
- "orchestration",
4184
- "autonomous-agents",
4185
- "mcp",
4186
- "taskless",
4187
- "cli"
4188
- ],
4189
- license: "SEE LICENSE IN LICENSE",
4190
- author: "Ilya Smurov <ilya@smurov.com>",
4191
- homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
4192
- repository: {
4193
- type: "git",
4194
- url: "git+https://github.com/ilyasmurov/taskless.git",
4195
- directory: "packages/swarm-daemon"
4196
- },
4197
- engines: {
4198
- node: ">=22"
4199
- },
4200
- bin: {
4201
- swarm: "dist/cli.js"
4202
- },
4203
- files: [
4204
- "dist",
4205
- "docs/HERO.md",
4206
- "roles"
4207
- ],
4208
- scripts: {
4209
- overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
4210
- swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
4211
- build: "tsup",
4212
- start: "node dist/overmind.js",
4213
- prepublishOnly: "pnpm run build",
4214
- test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
4215
- "test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
4216
- "test:watch": "vitest",
4217
- typecheck: "tsc -p tsconfig.json --noEmit",
4218
- lint: "echo 'no lint in swarm-daemon yet'"
4219
- },
4220
- dependencies: {
4221
- "@hono/node-server": "^2.0.12",
4222
- "@modelcontextprotocol/sdk": "^1.29.0",
4223
- execa: "^10.0.0",
4224
- zod: "3.25.76"
4225
- }
4226
- };
4227
-
4228
5646
  // src/web.ts
5647
+ init_version();
4229
5648
  init_storm();
4230
5649
 
4231
5650
  // src/gh.ts
@@ -4277,64 +5696,19 @@ async function ghChecks(pr, run = realChecksRunner) {
4277
5696
 
4278
5697
  // src/web.ts
4279
5698
  init_config();
4280
-
4281
- // src/droneChannel.ts
4282
- init_src();
4283
- import { createHmac, randomBytes, timingSafeEqual } from "crypto";
4284
- import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4285
- import { dirname as dirname2 } from "path";
4286
- import { serve } from "@hono/node-server";
4287
- function loadChannelSecret(path) {
4288
- try {
4289
- const s = readFileSync6(path, "utf8").trim();
4290
- if (s.length >= 32) return s;
4291
- } catch {
4292
- }
4293
- const secret = randomBytes(32).toString("hex");
4294
- mkdirSync3(dirname2(path), { recursive: true });
4295
- writeFileSync5(path, secret, { mode: 384 });
4296
- try {
4297
- chmodSync(path, 384);
4298
- } catch {
4299
- }
4300
- return secret;
4301
- }
4302
- function sign(runKey, secret) {
4303
- return createHmac("sha256", secret).update(runKey).digest("base64url");
4304
- }
4305
- function mintChannelToken(runKey, secret) {
4306
- return `${Buffer.from(runKey, "utf8").toString("base64url")}.${sign(runKey, secret)}`;
4307
- }
4308
- function verifyChannelToken(token, secret) {
4309
- const dot = token.lastIndexOf(".");
4310
- if (dot <= 0) return null;
4311
- const runKey = Buffer.from(token.slice(0, dot), "base64url").toString("utf8");
4312
- if (!runKey) return null;
4313
- const got = Buffer.from(token.slice(dot + 1));
4314
- const want = Buffer.from(sign(runKey, secret));
4315
- if (got.length !== want.length || !timingSafeEqual(got, want)) return null;
4316
- return runKey;
4317
- }
4318
- var LOCAL_CLI_KEY = "cli";
4319
- function mintLocalCliToken(secret) {
4320
- return mintChannelToken(LOCAL_CLI_KEY, secret);
4321
- }
4322
- function verifyLocalCliToken(token, secret) {
4323
- return verifyChannelToken(token, secret) === LOCAL_CLI_KEY;
4324
- }
4325
- var EVENT_KIND_SET = new Set(EVENT_KINDS);
5699
+ init_droneChannel();
4326
5700
 
4327
5701
  // src/oauth.ts
4328
5702
  init_config();
4329
5703
  import { createHash, randomBytes as randomBytes2 } from "crypto";
4330
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync } from "fs";
4331
- import { join as join5 } from "path";
5704
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
5705
+ import { join as join7 } from "path";
4332
5706
  var REGISTER = "/api/oauth/register";
4333
5707
  var AUTHORIZE = "/api/oauth/authorize";
4334
5708
  var TOKEN = "/api/oauth/token";
4335
5709
  var SCOPE = "openid profile email taskless:full";
4336
- var STORE_PATH = join5(SWARM_HOME, "oauth.json");
4337
- var CLIENT_PATH = join5(SWARM_HOME, "oauth-client.json");
5710
+ var STORE_PATH = join7(SWARM_HOME, "oauth.json");
5711
+ var CLIENT_PATH = join7(SWARM_HOME, "oauth-client.json");
4338
5712
  function makePkce() {
4339
5713
  const verifier = randomBytes2(48).toString("base64url");
4340
5714
  const challenge = createHash("sha256").update(verifier).digest("base64url");
@@ -4419,7 +5793,7 @@ function toStore(clientId, t, now) {
4419
5793
  }
4420
5794
  function loadStore() {
4421
5795
  try {
4422
- return JSON.parse(readFileSync7(STORE_PATH, "utf8"));
5796
+ return JSON.parse(readFileSync9(STORE_PATH, "utf8"));
4423
5797
  } catch {
4424
5798
  return null;
4425
5799
  }
@@ -4429,13 +5803,13 @@ function saveStore(s) {
4429
5803
  }
4430
5804
  function clearStore() {
4431
5805
  try {
4432
- unlinkSync(STORE_PATH);
5806
+ unlinkSync2(STORE_PATH);
4433
5807
  } catch {
4434
5808
  }
4435
5809
  }
4436
5810
  function loadClientId() {
4437
5811
  try {
4438
- return JSON.parse(readFileSync7(CLIENT_PATH, "utf8")).clientId;
5812
+ return JSON.parse(readFileSync9(CLIENT_PATH, "utf8")).clientId;
4439
5813
  } catch {
4440
5814
  return null;
4441
5815
  }
@@ -4463,7 +5837,6 @@ async function ensureAccess(store, opts = {}) {
4463
5837
  }
4464
5838
 
4465
5839
  // src/web.ts
4466
- var PACKAGE_VERSION = package_default.version;
4467
5840
  var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4468
5841
  var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
4469
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>`;
@@ -5771,7 +7144,8 @@ function daemonStatus(deps) {
5771
7144
  restart: deps.restart ? { phase: deps.restart.phase, intent: deps.restart.intent, sessions: live } : null,
5772
7145
  counts,
5773
7146
  live,
5774
- version: PACKAGE_VERSION
7147
+ version: PACKAGE_VERSION,
7148
+ repos: deps.repoMap?.() ?? []
5775
7149
  };
5776
7150
  }
5777
7151
  function eventsStream(db2, narratorLog, control, daemonVersion, restart, liveSessionIds, selfHost) {
@@ -5998,113 +7372,42 @@ function makeHandler(deps) {
5998
7372
  if (p === "/restart/cancel" && req.method === "POST") {
5999
7373
  if (!auth()) return unauthorized();
6000
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 });
6001
- deps.cancelRestartWait();
6002
- return Response.json({ ok: true });
6003
- }
6004
- return new Response("not found", { status: 404 });
6005
- };
6006
- }
6007
- function startWeb(deps, port = WEB_PORT) {
6008
- const handle = makeHandler(deps);
6009
- const server = serve2({ fetch: handle, port, hostname: "127.0.0.1" });
6010
- server.on("error", (err) => {
6011
- const e = err;
6012
- if (e.code === "EADDRINUSE") {
6013
- 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}).`);
6014
- } else {
6015
- console.error(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${e.message}`);
6016
- }
6017
- process.exit(1);
6018
- });
6019
- return { port, server };
6020
- }
6021
-
6022
- // src/cli.ts
6023
- import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDroneProcesses } from "./overmind.js";
6024
-
6025
- // src/launcher.ts
6026
- import { spawn as spawn2 } from "child_process";
6027
- import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync4, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
6028
- import { createRequire } from "module";
6029
- import { homedir as homedir2 } from "os";
6030
- import { dirname as dirname3, join as join7 } from "path";
6031
- import { fileURLToPath } from "url";
6032
-
6033
- // src/claudeBin.ts
6034
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
6035
- import { win32 as winPath } from "path";
6036
- var CLAUDE_FALLBACK = "claude";
6037
- function compareVersionDirs(a, b) {
6038
- const parts = (s) => s.split(".").map((p) => Number.isFinite(Number(p)) ? Number(p) : -1);
6039
- const pa = parts(a);
6040
- const pb = parts(b);
6041
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
6042
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
6043
- if (d !== 0) return d;
6044
- }
6045
- return 0;
6046
- }
6047
- function realListDir(dir) {
6048
- try {
6049
- return readdirSync2(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6050
- } catch {
6051
- return [];
6052
- }
6053
- }
6054
- function winVersionRoots(env, listDir) {
6055
- const roots = [];
6056
- const appData = env.APPDATA;
6057
- if (appData) roots.push(winPath.join(appData, "Claude", "claude-code"));
6058
- const localAppData = env.LOCALAPPDATA;
6059
- if (localAppData) {
6060
- const packages = winPath.join(localAppData, "Packages");
6061
- for (const name of listDir(packages)) {
6062
- if (!name.startsWith("Claude")) continue;
6063
- roots.push(winPath.join(packages, name, "LocalCache", "Roaming", "Claude", "claude-code"));
6064
- }
6065
- }
6066
- return roots;
6067
- }
6068
- function findWindowsClaude(opts = {}) {
6069
- const env = opts.env ?? process.env;
6070
- const listDir = opts.listDir ?? realListDir;
6071
- const fileExists = opts.fileExists ?? existsSync4;
6072
- let best = null;
6073
- for (const root of winVersionRoots(env, listDir)) {
6074
- for (const version of listDir(root)) {
6075
- const candidate = winPath.join(root, version, "claude.exe");
6076
- if (!fileExists(candidate)) continue;
6077
- if (!best || compareVersionDirs(version, best.version) > 0) best = { version, path: candidate };
6078
- }
6079
- }
6080
- return best?.path ?? null;
6081
- }
6082
- var warnedNoWindowsClaude = false;
6083
- var cachedWindowsBin = null;
6084
- var windowsScanDone = false;
6085
- function resolveClaudeBin(opts = {}) {
6086
- const env = opts.env ?? process.env;
6087
- const platform = opts.platform ?? process.platform;
6088
- const explicit = (env.SWARM_CLAUDE_BIN ?? "").trim();
6089
- if (explicit) return explicit;
6090
- if (platform !== "win32") return CLAUDE_FALLBACK;
6091
- if (windowsScanDone) return cachedWindowsBin ?? CLAUDE_FALLBACK;
6092
- windowsScanDone = true;
6093
- const found = findWindowsClaude({ ...opts, env });
6094
- cachedWindowsBin = found;
6095
- if (found) return found;
6096
- if (!warnedNoWindowsClaude) {
6097
- warnedNoWindowsClaude = true;
6098
- console.warn(
6099
- "[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."
6100
- );
6101
- }
6102
- 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 };
6103
7394
  }
6104
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
+
6105
7408
  // src/droneHome.ts
6106
- import { lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync, statSync as statSync4, symlinkSync } from "fs";
6107
- import { join as join6, relative, sep } from "path";
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";
6108
7411
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
6109
7412
  function droneHomeDeny(env = process.env) {
6110
7413
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -6123,28 +7426,28 @@ function buildDroneHome(opts) {
6123
7426
  const deny = opts.deny ?? droneHomeDeny();
6124
7427
  const platform = opts.platform ?? process.platform;
6125
7428
  mkdirSync4(droneHome, { recursive: true, mode: 448 });
6126
- const wanted = new Set(mirroredEntries(readdirSync3(realHome), deny, platform));
7429
+ const wanted = new Set(mirroredEntries(readdirSync4(realHome), deny, platform));
6127
7430
  const rel = relative(realHome, droneHome);
6128
7431
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
6129
- for (const existing of readdirSync3(droneHome)) {
7432
+ for (const existing of readdirSync4(droneHome)) {
6130
7433
  if (wanted.has(existing)) continue;
6131
- const p = join6(droneHome, existing);
7434
+ const p = join8(droneHome, existing);
6132
7435
  try {
6133
7436
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
6134
7437
  } catch {
6135
7438
  }
6136
7439
  }
6137
7440
  for (const name of wanted) {
6138
- const link = join6(droneHome, name);
7441
+ const link = join8(droneHome, name);
6139
7442
  try {
6140
7443
  lstatSync(link);
6141
7444
  continue;
6142
7445
  } catch {
6143
7446
  }
6144
- const target = join6(realHome, name);
7447
+ const target = join8(realHome, name);
6145
7448
  let isDir;
6146
7449
  try {
6147
- isDir = statSync4(target).isDirectory();
7450
+ isDir = statSync5(target).isDirectory();
6148
7451
  } catch {
6149
7452
  continue;
6150
7453
  }
@@ -6162,7 +7465,7 @@ var REAL_PATH_ENV_PINS = [
6162
7465
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
6163
7466
  ];
6164
7467
  function realPathEnvPins(realHome) {
6165
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join6(realHome, ...segments)]));
7468
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join8(realHome, ...segments)]));
6166
7469
  }
6167
7470
  function droneDenyRules(realHome, swarmHome) {
6168
7471
  const paths = [
@@ -6254,7 +7557,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
6254
7557
  const env = { ...base3 };
6255
7558
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
6256
7559
  if (isolate && !base3.TURBO_CACHE_DIR) {
6257
- env.TURBO_CACHE_DIR = join7(worktree, ".turbo", "cache");
7560
+ env.TURBO_CACHE_DIR = join9(worktree, ".turbo", "cache");
6258
7561
  }
6259
7562
  Object.assign(env, droneTestEnv(droneCap, base3));
6260
7563
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -6282,20 +7585,20 @@ var DAEMON_STATE_ENV = [
6282
7585
  "TASKLESS_TOKEN"
6283
7586
  ];
6284
7587
  function bundledRoleDir(kind) {
6285
- return join7(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
7588
+ return join9(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
6286
7589
  }
6287
7590
  function roleDir(worktree, kind) {
6288
- return join7(worktree, ".claude", "roles", kind);
7591
+ return join9(worktree, ".claude", "roles", kind);
6289
7592
  }
6290
7593
  function roleDirs(worktree, kind) {
6291
- return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync5(dir));
7594
+ return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync6(dir));
6292
7595
  }
6293
7596
  function nextRunLogPath(runLogsDir, runId) {
6294
- const dir = join7(runLogsDir, String(runId));
7597
+ const dir = join9(runLogsDir, String(runId));
6295
7598
  mkdirSync5(dir, { recursive: true });
6296
7599
  let files = [];
6297
7600
  try {
6298
- files = readdirSync4(dir);
7601
+ files = readdirSync5(dir);
6299
7602
  } catch {
6300
7603
  }
6301
7604
  let next = 0;
@@ -6303,7 +7606,7 @@ function nextRunLogPath(runLogsDir, runId) {
6303
7606
  const m = /^(\d+)\.json/.exec(f);
6304
7607
  if (m) next = Math.max(next, Number(m[1]) + 1);
6305
7608
  }
6306
- return join7(dir, `${next}.json`);
7609
+ return join9(dir, `${next}.json`);
6307
7610
  }
6308
7611
  function openNextRunLog(runLogsDir, runId) {
6309
7612
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -6377,16 +7680,16 @@ function makeLauncher(cfg) {
6377
7680
  }
6378
7681
  }
6379
7682
  };
6380
- const cfgPath = join7(worktree, ".swarm-mcp.json");
7683
+ const cfgPath = join9(worktree, ".swarm-mcp.json");
6381
7684
  writeFileSync7(cfgPath, JSON.stringify(mcpConfig, null, 2));
6382
7685
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
6383
7686
  const pluginDirs = roleDirs(worktree, run.kind);
6384
- const realHome = cfg.droneIsolation?.realHome ?? homedir2();
7687
+ const realHome = cfg.droneIsolation?.realHome ?? homedir3();
6385
7688
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
6386
7689
  const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
6387
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync5(join7(cfg.droneIsolation.swarmHome, "token"))) {
7690
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync6(join9(cfg.droneIsolation.swarmHome, "token"))) {
6388
7691
  console.error(
6389
- `[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 ${join7(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.`
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.`
6390
7693
  );
6391
7694
  }
6392
7695
  let stdio = "ignore";
@@ -6416,14 +7719,17 @@ function makeLauncher(cfg) {
6416
7719
  };
6417
7720
  }
6418
7721
 
7722
+ // src/cli.ts
7723
+ init_droneChannel();
7724
+
6419
7725
  // src/git.ts
6420
- import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
7726
+ import { existsSync as existsSync7, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
6421
7727
  import { appendFile, mkdir, readFile, rm } from "fs/promises";
6422
- import { isAbsolute, join as join8 } from "path";
7728
+ import { isAbsolute as isAbsolute2, join as join10 } from "path";
6423
7729
  import { $ as $4 } from "execa";
6424
7730
  function isLinkedWorktree(dir) {
6425
7731
  try {
6426
- return statSync6(join8(dir, ".git")).isFile();
7732
+ return statSync7(join10(dir, ".git")).isFile();
6427
7733
  } catch {
6428
7734
  return false;
6429
7735
  }
@@ -6435,7 +7741,7 @@ async function detachWorktree(repo, worktree) {
6435
7741
  }
6436
7742
  }
6437
7743
  async function ensureWorktreeDirGone(worktree) {
6438
- if (!existsSync6(worktree)) return false;
7744
+ if (!existsSync7(worktree)) return false;
6439
7745
  if (!isLinkedWorktree(worktree)) {
6440
7746
  console.error(
6441
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.`
@@ -6450,7 +7756,7 @@ async function ensureWorktreeDirGone(worktree) {
6450
7756
  );
6451
7757
  return true;
6452
7758
  }
6453
- if (existsSync6(worktree)) {
7759
+ if (existsSync7(worktree)) {
6454
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.`);
6455
7761
  }
6456
7762
  return true;
@@ -6462,52 +7768,8 @@ async function removeWorktree(repo, path) {
6462
7768
  if (branch) await $4({ reject: false })`git -C ${repo} branch -D ${branch}`;
6463
7769
  }
6464
7770
 
6465
- // src/agentToken.ts
6466
- init_config();
6467
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2 } from "fs";
6468
- import { join as join9 } from "path";
6469
- var STORE_PATH2 = join9(SWARM_HOME, "agent.json");
6470
- var base2 = (api) => api.replace(/\/$/, "");
6471
- function loadAgentToken() {
6472
- try {
6473
- const s = JSON.parse(readFileSync8(STORE_PATH2, "utf8"));
6474
- return s.token ? s : null;
6475
- } catch {
6476
- return null;
6477
- }
6478
- }
6479
- function saveAgentToken(s) {
6480
- writeFileSync8(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
6481
- }
6482
- function clearAgentToken() {
6483
- try {
6484
- unlinkSync2(STORE_PATH2);
6485
- } catch {
6486
- }
6487
- }
6488
- async function mintAgentToken(args, fetchFn = fetch) {
6489
- const res = await fetchFn(`${base2(args.api)}/api/swarm/agent/token`, {
6490
- method: "POST",
6491
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
6492
- body: JSON.stringify(args.label ? { label: args.label } : {})
6493
- });
6494
- const text = await res.text();
6495
- if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
6496
- const j = JSON.parse(text);
6497
- if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
6498
- return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
6499
- }
6500
- async function probeAgentToken(args, fetchFn = fetch) {
6501
- try {
6502
- const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
6503
- headers: { Authorization: `Bearer ${args.token}` }
6504
- });
6505
- if (res.status === 401 || res.status === 403) return "revoked";
6506
- return res.ok ? "ok" : "unreachable";
6507
- } catch {
6508
- return "unreachable";
6509
- }
6510
- }
7771
+ // src/cli.ts
7772
+ init_agentToken();
6511
7773
 
6512
7774
  // src/taskless.ts
6513
7775
  init_src();
@@ -6516,7 +7778,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
6516
7778
 
6517
7779
  // src/attachments.ts
6518
7780
  import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
6519
- import { join as join10 } from "path";
7781
+ import { join as join12 } from "path";
6520
7782
  var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
6521
7783
  var ATTACH_MAX_FILE_BYTES = 8 * 1024 * 1024;
6522
7784
  var IMAGE_EXT = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "heic"]);
@@ -6526,20 +7788,21 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
6526
7788
 
6527
7789
  // src/shotUpload.ts
6528
7790
  import { readFile as fsReadFile } from "fs/promises";
6529
- import { basename as basename3, isAbsolute as isAbsolute2, join as join11 } from "path";
7791
+ import { basename as basename3, isAbsolute as isAbsolute3, join as join13 } from "path";
6530
7792
  var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
6531
7793
 
6532
7794
  // src/taskless.ts
6533
7795
  init_config();
7796
+ init_agentToken();
6534
7797
 
6535
7798
  // src/workspace.ts
6536
7799
  init_config();
6537
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3 } from "fs";
6538
- import { join as join12 } from "path";
6539
- var WORKSPACE_PATH = join12(SWARM_HOME, "workspace.json");
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");
6540
7803
  function loadWorkspace() {
6541
7804
  try {
6542
- const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
7805
+ const w = JSON.parse(readFileSync11(WORKSPACE_PATH, "utf8"));
6543
7806
  return w && typeof w.id === "string" && w.id ? w : null;
6544
7807
  } catch {
6545
7808
  return null;
@@ -6550,7 +7813,7 @@ function saveWorkspace(w) {
6550
7813
  }
6551
7814
  function clearWorkspace() {
6552
7815
  try {
6553
- unlinkSync3(WORKSPACE_PATH);
7816
+ unlinkSync4(WORKSPACE_PATH);
6554
7817
  } catch {
6555
7818
  }
6556
7819
  }
@@ -6695,7 +7958,7 @@ function resilientSwarmMcp(opts = {}) {
6695
7958
  }
6696
7959
 
6697
7960
  // src/login.ts
6698
- import { execa } from "execa";
7961
+ import { execa as execa2 } from "execa";
6699
7962
  async function waitForLogin(d) {
6700
7963
  const deadline = d.now() + d.timeoutMs;
6701
7964
  for (; ; ) {
@@ -6760,134 +8023,15 @@ function parseWhoAmI(res) {
6760
8023
  async function openBrowser(url) {
6761
8024
  const [cmd2, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
6762
8025
  try {
6763
- await execa(cmd2, args, { stdio: "ignore" });
8026
+ await execa2(cmd2, args, { stdio: "ignore" });
6764
8027
  return true;
6765
8028
  } catch {
6766
8029
  return false;
6767
8030
  }
6768
8031
  }
6769
8032
 
6770
- // src/screenshot.ts
6771
- var DESKTOP_VIEWPORT = { name: "desktop", width: 1360, height: 940 };
6772
- var MOBILE_VIEWPORT = { name: "mobile", width: 390, height: 844 };
6773
- var DEFAULT_VIEWPORTS = [DESKTOP_VIEWPORT, MOBILE_VIEWPORT];
6774
- var DEFAULT_SHOT_TIMEOUT_MS = 2e4;
6775
- var NETWORK_SETTLE_QUIET_MS = 500;
6776
- var NETWORK_SETTLE_TIMEOUT_MS = 8e3;
6777
- async function captureScreenshots(launchBrowser, opts) {
6778
- const viewports = opts.viewports ?? DEFAULT_VIEWPORTS;
6779
- const timeoutMs = opts.timeoutMs ?? DEFAULT_SHOT_TIMEOUT_MS;
6780
- const fullPage = opts.fullPage ?? true;
6781
- const browser = await launchBrowser();
6782
- try {
6783
- const results = [];
6784
- for (const vp of viewports) {
6785
- const ctx = await browser.newContext({
6786
- viewport: { width: vp.width, height: vp.height },
6787
- ...opts.storageStatePath ? { storageState: opts.storageStatePath } : {}
6788
- });
6789
- try {
6790
- const page = await ctx.newPage();
6791
- await page.goto(opts.url, { timeout: timeoutMs, waitUntil: "load" });
6792
- await page.waitForNetworkSettle({
6793
- quietMs: NETWORK_SETTLE_QUIET_MS,
6794
- timeoutMs: Math.min(timeoutMs, NETWORK_SETTLE_TIMEOUT_MS)
6795
- });
6796
- const path = `${opts.outPrefix}-${vp.name}.png`;
6797
- await page.screenshot({ path, fullPage, timeout: timeoutMs });
6798
- results.push({ viewport: vp.name, path });
6799
- } finally {
6800
- await ctx.close();
6801
- }
6802
- }
6803
- return results;
6804
- } finally {
6805
- await browser.close();
6806
- }
6807
- }
6808
- function isEventStream(headers2) {
6809
- for (const [k, v] of Object.entries(headers2)) {
6810
- if (k.toLowerCase() === "content-type") return v.toLowerCase().startsWith("text/event-stream");
6811
- }
6812
- return false;
6813
- }
6814
- function createNetworkSettleTracker(target) {
6815
- const pending2 = /* @__PURE__ */ new Set();
6816
- const onChange = /* @__PURE__ */ new Set();
6817
- const notify = () => {
6818
- for (const cb of [...onChange]) cb();
6819
- };
6820
- target.on("request", ((req) => {
6821
- pending2.add(req);
6822
- notify();
6823
- }));
6824
- target.on("response", ((res) => {
6825
- if (isEventStream(res.headers())) {
6826
- pending2.delete(res.request());
6827
- notify();
6828
- }
6829
- }));
6830
- const onDone = ((req) => {
6831
- pending2.delete(req);
6832
- notify();
6833
- });
6834
- target.on("requestfinished", onDone);
6835
- target.on("requestfailed", onDone);
6836
- return {
6837
- waitForSettle({ quietMs, timeoutMs }) {
6838
- return new Promise((resolve3) => {
6839
- let quietTimer;
6840
- const hardTimer = setTimeout(finish, timeoutMs);
6841
- function finish() {
6842
- clearTimeout(quietTimer);
6843
- clearTimeout(hardTimer);
6844
- onChange.delete(check);
6845
- resolve3();
6846
- }
6847
- function check() {
6848
- clearTimeout(quietTimer);
6849
- if (pending2.size === 0) quietTimer = setTimeout(finish, quietMs);
6850
- }
6851
- onChange.add(check);
6852
- check();
6853
- });
6854
- }
6855
- };
6856
- }
6857
- function wrapPage(page) {
6858
- const tracker = createNetworkSettleTracker(page);
6859
- return {
6860
- goto: (url, gotoOpts) => page.goto(url, gotoOpts),
6861
- screenshot: (shotOpts) => page.screenshot(shotOpts),
6862
- waitForNetworkSettle: (settleOpts) => tracker.waitForSettle(settleOpts)
6863
- };
6864
- }
6865
- function wrapContext(ctx) {
6866
- return {
6867
- newPage: async () => wrapPage(await ctx.newPage()),
6868
- close: () => ctx.close()
6869
- };
6870
- }
6871
- function wrapBrowser(browser) {
6872
- return {
6873
- newContext: async (ctxOpts) => wrapContext(await browser.newContext(ctxOpts)),
6874
- close: () => browser.close()
6875
- };
6876
- }
6877
- async function launchChromium(opts = {}) {
6878
- let mod;
6879
- try {
6880
- mod = await import("playwright-core");
6881
- } catch {
6882
- throw new Error(
6883
- "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."
6884
- );
6885
- }
6886
- const browser = await mod.chromium.launch({ headless: true, ...opts.timeoutMs !== void 0 ? { timeout: opts.timeoutMs } : {} });
6887
- return wrapBrowser(browser);
6888
- }
6889
-
6890
8033
  // src/shotCli.ts
8034
+ init_screenshot();
6891
8035
  init_config();
6892
8036
  function isFlagValue(v) {
6893
8037
  return v !== void 0 && !v.startsWith("--");
@@ -6962,82 +8106,19 @@ async function runShotCli(argv, deps = {}) {
6962
8106
  }
6963
8107
  }
6964
8108
 
6965
- // src/daemonLink.ts
6966
- init_config();
6967
- var TIMEOUT_MS = 5e3;
6968
- var ACTION_TIMEOUT_MS = 2e4;
6969
- var BASE = `http://127.0.0.1:${WEB_PORT}`;
6970
- function headers(extra = {}) {
6971
- return { "X-Swarm-Local": mintLocalCliToken(loadChannelSecret(DRONE_CHANNEL_SECRET_PATH)), ...extra };
6972
- }
6973
- async function daemonRuns(status) {
6974
- try {
6975
- const qs = status ? `?status=${encodeURIComponent(status)}` : "";
6976
- const res = await fetch(`${BASE}/runs${qs}`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
6977
- if (!res.ok) return null;
6978
- const body = await res.json();
6979
- if (!Array.isArray(body.runs)) return null;
6980
- return { runs: body.runs, backend: String(body.backend ?? ""), hostId: body.hostId ?? null };
6981
- } catch {
6982
- return null;
6983
- }
6984
- }
6985
- async function post(path, form) {
6986
- try {
6987
- const res = await fetch(`${BASE}${path}`, {
6988
- method: "POST",
6989
- headers: headers({ "Content-Type": "application/x-www-form-urlencoded" }),
6990
- body: new URLSearchParams(form),
6991
- signal: AbortSignal.timeout(ACTION_TIMEOUT_MS)
6992
- });
6993
- if (res.status === 503) return null;
6994
- 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" };
6995
- return await res.json();
6996
- } catch {
6997
- return null;
6998
- }
6999
- }
7000
- function daemonCancel(slug, reason) {
7001
- return post("/cancel", { slug, reason });
7002
- }
7003
- function daemonWake(runId) {
7004
- return post("/wake", { runId: String(runId) });
7005
- }
7006
- function daemonPause(reason) {
7007
- return post("/pause", { source: "cli", ...reason ? { reason } : {} });
7008
- }
7009
- function daemonResume() {
7010
- return post("/resume", { source: "cli" });
7011
- }
7012
- async function daemonStop(opts) {
7013
- const res = await post("/shutdown", { force: opts.force ? "1" : "0", wait: opts.wait ? "1" : "0" });
7014
- if (!res) return null;
7015
- const raw = res;
7016
- if (raw.stopping === void 0 && raw.waiting === void 0) return null;
7017
- return { stopping: !!raw.stopping, waiting: !!raw.waiting, sessions: Array.isArray(raw.sessions) ? raw.sessions : [] };
7018
- }
7019
- async function daemonStatus2() {
7020
- try {
7021
- const res = await fetch(`${BASE}/status`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
7022
- if (!res.ok) return null;
7023
- const body = await res.json();
7024
- return typeof body?.paused === "boolean" ? body : null;
7025
- } catch {
7026
- return null;
7027
- }
7028
- }
7029
- 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();
7030
8111
 
7031
8112
  // src/pauseState.ts
7032
- import { existsSync as existsSync7, readFileSync as readFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync10 } from "fs";
7033
- import { join as join13 } from "path";
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";
7034
8115
  function pauseStatePath(home) {
7035
- return join13(home, "pause.json");
8116
+ return join15(home, "pause.json");
7036
8117
  }
7037
8118
  var ACTIVE = { paused: false, at: "", source: "cli" };
7038
8119
  function loadPauseState(home, io = {}) {
7039
- const exists = io.exists ?? existsSync7;
7040
- const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
8120
+ const exists = io.exists ?? existsSync8;
8121
+ const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
7041
8122
  const path = pauseStatePath(home);
7042
8123
  if (!exists(path)) return ACTIVE;
7043
8124
  try {
@@ -7056,13 +8137,13 @@ function loadPauseState(home, io = {}) {
7056
8137
  function savePauseState(home, state, io = {}) {
7057
8138
  const path = pauseStatePath(home);
7058
8139
  if (!state.paused) {
7059
- const remove = io.remove ?? ((p) => existsSync7(p) && unlinkSync4(p));
8140
+ const remove = io.remove ?? ((p) => existsSync8(p) && unlinkSync5(p));
7060
8141
  remove(path);
7061
8142
  return;
7062
8143
  }
7063
8144
  const writeFile2 = io.writeFile ?? ((p, data) => {
7064
8145
  writeFileSync10(`${p}.tmp`, data);
7065
- renameSync4(`${p}.tmp`, p);
8146
+ renameSync5(`${p}.tmp`, p);
7066
8147
  });
7067
8148
  writeFile2(path, JSON.stringify(state, null, 2));
7068
8149
  }
@@ -7091,10 +8172,10 @@ function sinceText(at, now) {
7091
8172
  }
7092
8173
 
7093
8174
  // src/heartbeat.ts
7094
- import { readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync11 } from "fs";
8175
+ import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
7095
8176
  function readHeartbeat(path) {
7096
8177
  try {
7097
- const ms = Number(readFileSync11(path, "utf8").trim());
8178
+ const ms = Number(readFileSync13(path, "utf8").trim());
7098
8179
  return Number.isFinite(ms) ? ms : null;
7099
8180
  } catch {
7100
8181
  return null;
@@ -7103,7 +8184,7 @@ function readHeartbeat(path) {
7103
8184
 
7104
8185
  // src/cli.ts
7105
8186
  init_src();
7106
- var PACKAGE_VERSION2 = package_default.version;
8187
+ init_version();
7107
8188
  function takeRepo(argv) {
7108
8189
  const i = argv.indexOf("--repo");
7109
8190
  if (i === -1) return { repo: null, rest: argv };
@@ -7157,14 +8238,6 @@ function takeWorkspace(argv) {
7157
8238
  const workspace = argv[i + 1] ?? null;
7158
8239
  return { workspace, rest: [...argv.slice(0, i), ...argv.slice(i + 2)] };
7159
8240
  }
7160
- async function askTty(q) {
7161
- const rl = createInterface({ input: process.stdin, output: process.stdout });
7162
- try {
7163
- return await rl.question(q);
7164
- } finally {
7165
- rl.close();
7166
- }
7167
- }
7168
8241
  async function runLogin(db2, opts) {
7169
8242
  const web = startWeb({ db: db2 });
7170
8243
  try {
@@ -7283,7 +8356,6 @@ async function waitDaemonDown(opts) {
7283
8356
  await sleep2(pollMs);
7284
8357
  }
7285
8358
  }
7286
- var MAX_DAEMON_LOG_BYTES = 16 * 1024 * 1024;
7287
8359
  var START_READY_TIMEOUT_MS = 45e3;
7288
8360
  async function runStartDetached(args) {
7289
8361
  const already = await daemonStatus2();
@@ -7292,7 +8364,7 @@ async function runStartDetached(args) {
7292
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");
7293
8365
  return 0;
7294
8366
  }
7295
- const rotated = rotateDaemonLog(DAEMON_LOG_PATH, MAX_DAEMON_LOG_BYTES);
8367
+ const rotated = rotateDaemonLog(DAEMON_LOG_PATH, { maxBytes: DAEMON_LOG_MAX_BYTES, keep: DAEMON_LOG_KEEP });
7296
8368
  const entry = process.argv[1];
7297
8369
  if (!entry) {
7298
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");
@@ -7345,7 +8417,7 @@ async function runDaemonLogs(argv) {
7345
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.`);
7346
8418
  return 1;
7347
8419
  }
7348
- if (!existsSync9(DAEMON_LOG_PATH)) {
8420
+ if (!existsSync13(DAEMON_LOG_PATH)) {
7349
8421
  console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
7350
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.");
7351
8423
  return 1;
@@ -7353,12 +8425,12 @@ async function runDaemonLogs(argv) {
7353
8425
  const head = tailFile(DAEMON_LOG_PATH, lines);
7354
8426
  if (head) console.log(head);
7355
8427
  if (!follow) return 0;
7356
- let pos = statSync7(DAEMON_LOG_PATH).size;
8428
+ let pos = statSync8(DAEMON_LOG_PATH).size;
7357
8429
  for (; ; ) {
7358
8430
  await sleep2(500);
7359
8431
  let size;
7360
8432
  try {
7361
- size = statSync7(DAEMON_LOG_PATH).size;
8433
+ size = statSync8(DAEMON_LOG_PATH).size;
7362
8434
  } catch {
7363
8435
  continue;
7364
8436
  }
@@ -7519,16 +8591,24 @@ function printRuns(runs, view) {
7519
8591
  }).forEach((l) => console.log(l));
7520
8592
  }
7521
8593
  function usage() {
7522
- console.log(`swarm \u2014 \u0440\u043E\u0439 AI-\u0434\u0440\u043E\u043D\u043E\u0432 (v${PACKAGE_VERSION2})
8594
+ console.log(`swarm \u2014 \u0440\u043E\u0439 AI-\u0434\u0440\u043E\u043D\u043E\u0432 (v${PACKAGE_VERSION})
7523
8595
 
7524
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)
7525
8604
  swarm login [--workspace <slug|id>] [--no-open]
7526
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
7527
8606
  swarm start [--repo <path>] [--foreground]
7528
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.
7529
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;
7530
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
7531
- 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
7532
8612
 
7533
8613
  \u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
7534
8614
  swarm spawn <slug> [area] [\u0444\u043B\u0430\u0433\u0438 \u043D\u0438\u0436\u0435]
@@ -7586,10 +8666,10 @@ function effectiveWorktreesDir() {
7586
8666
  legacyDir: WORKTREES_LEGACY_DIR,
7587
8667
  targetDir: WORKTREES_DIR,
7588
8668
  envOverride: process.env.SWARM_WORKTREES || "",
7589
- exists: (d) => existsSync9(d),
8669
+ exists: (d) => existsSync13(d),
7590
8670
  isNonEmptyDir: (d) => {
7591
8671
  try {
7592
- return readdirSync6(d).length > 0;
8672
+ return readdirSync7(d).length > 0;
7593
8673
  } catch {
7594
8674
  return false;
7595
8675
  }
@@ -7622,7 +8702,7 @@ async function finishClient(db2) {
7622
8702
  }
7623
8703
  var [cmd, ...rawArgs] = process.argv.slice(2);
7624
8704
  if (cmd === "--version" || cmd === "-v") {
7625
- console.log(PACKAGE_VERSION2);
8705
+ console.log(PACKAGE_VERSION);
7626
8706
  process.exit(0);
7627
8707
  }
7628
8708
  if (cmd === "logout") {
@@ -7744,6 +8824,7 @@ if (cmd === "status") {
7744
8824
  const counts = Object.entries(st.counts).filter(([, n]) => n > 0);
7745
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");
7746
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);
7747
8828
  process.exit(0);
7748
8829
  }
7749
8830
  if (cmd === "pause" || cmd === "resume") {
@@ -7774,6 +8855,15 @@ if (cmd === "remove") {
7774
8855
  const code = await runRemove(rawArgs);
7775
8856
  process.exit(code);
7776
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
+ }
7777
8867
  if (cmd === "shot") {
7778
8868
  const code = await runShotCli(rawArgs);
7779
8869
  process.exit(code);