@agentsky/sky 2.29.0 → 2.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QZLKHGX6.js → chunk-3SWL7YZD.js} +1 -1
- package/dist/{chunk-ISORBGQX.js → chunk-7T5NSWSW.js} +81 -45
- package/dist/{chunk-GEL5GWF2.js → chunk-T36VKZIJ.js} +2 -2
- package/dist/index.js +450 -308
- package/dist/{src-XQQXF3IG.js → src-PPHTLKQV.js} +1 -1
- package/dist/{trace-RUZF6QL6.js → trace-2SAGMFYJ.js} +2 -2
- package/dist/{trace-library-5Y63PZAD.js → trace-library-RDJEEW7O.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
streamEvents,
|
|
12
12
|
uploadFile,
|
|
13
13
|
writeResponseFile
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T36VKZIJ.js";
|
|
15
15
|
import {
|
|
16
16
|
CLI_VERSION,
|
|
17
17
|
USER_AGENT,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
currentUniverse,
|
|
23
23
|
loadConfig,
|
|
24
24
|
saveConfig
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-3SWL7YZD.js";
|
|
26
26
|
import {
|
|
27
27
|
AGENT_TYPES,
|
|
28
28
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -44,32 +44,132 @@ import {
|
|
|
44
44
|
skillRefSchema,
|
|
45
45
|
splitMcpServers,
|
|
46
46
|
supersededBy
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-7T5NSWSW.js";
|
|
48
48
|
import "./chunk-MLKGABMK.js";
|
|
49
49
|
|
|
50
|
+
// src/thread-experience.ts
|
|
51
|
+
import { randomUUID } from "crypto";
|
|
52
|
+
async function startExperienceThread(experience, text, options = {}) {
|
|
53
|
+
const requestId = options.requestId ?? randomUUID();
|
|
54
|
+
if (!/^[a-zA-Z0-9_-]{16,100}$/.test(requestId)) throw new Error("request-id must contain 16\u2013100 letters, digits, underscores or hyphens.");
|
|
55
|
+
if (!text.trim() || text.trim().length > 2e4) throw new Error("text must contain 1\u201320000 characters.");
|
|
56
|
+
return api("POST", "/threads/experiences", { experience, text, requestId, ...options.timezone === void 0 ? {} : { timezone: options.timezone } });
|
|
57
|
+
}
|
|
58
|
+
function registerExperienceThreadCommand(thread2) {
|
|
59
|
+
thread2.command("experience <experience> <text>").description("start a catalog experience through the same Mac/web thread owner").option("--request-id <id>", "reuse the same 16\u2013100 character id when retrying; defaults to a new UUID").option("--timezone <iana>", "the person's IANA timezone").action(async (experience, text, options) => {
|
|
60
|
+
const requestId = options.requestId ?? randomUUID();
|
|
61
|
+
console.error(`Request id: ${requestId}`);
|
|
62
|
+
console.log(JSON.stringify(await startExperienceThread(experience, text, { ...options, requestId }), null, 2));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/skills-store.ts
|
|
67
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
68
|
+
import { basename, join, relative, resolve } from "path";
|
|
69
|
+
function skillBundle(dir) {
|
|
70
|
+
const root = resolve(dir);
|
|
71
|
+
const top = basename(root);
|
|
72
|
+
const files2 = [];
|
|
73
|
+
const walk = (path) => {
|
|
74
|
+
for (const name of readdirSync(path).sort()) {
|
|
75
|
+
const full = join(path, name);
|
|
76
|
+
if (statSync(full).isDirectory()) walk(full);
|
|
77
|
+
else files2.push({ path: `${top}/${relative(root, full)}`, bytes: readFileSync(full) });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
walk(root);
|
|
81
|
+
if (!files2.some((file) => file.path === `${top}/SKILL.md`)) throw new Error(`${dir}: no SKILL.md at its root`);
|
|
82
|
+
return files2;
|
|
83
|
+
}
|
|
84
|
+
async function uploadSkill(dir, displayName) {
|
|
85
|
+
const form = new FormData();
|
|
86
|
+
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
87
|
+
form.append("displayName", displayName ?? basename(resolve(dir)));
|
|
88
|
+
return apiForm("POST", "/skills", form);
|
|
89
|
+
}
|
|
90
|
+
async function listSkills() {
|
|
91
|
+
return (await api("GET", "/skills")).skills;
|
|
92
|
+
}
|
|
93
|
+
async function getTask(task2) {
|
|
94
|
+
return (await api("GET", `/tasks/${encodeURIComponent(task2)}`)).task;
|
|
95
|
+
}
|
|
96
|
+
function answerBody(answer) {
|
|
97
|
+
if (!answer.option === !answer.text) throw new Error("give exactly one of --option or --text");
|
|
98
|
+
return { ...answer.option ? { option_id: answer.option } : { text: answer.text }, ...answer.note ? { note: answer.note } : {} };
|
|
99
|
+
}
|
|
100
|
+
async function answerRequest(task2, request, answer) {
|
|
101
|
+
return api("POST", `/tasks/${encodeURIComponent(task2)}/requests/${encodeURIComponent(request)}/answer`, answerBody(answer));
|
|
102
|
+
}
|
|
103
|
+
function renderRequest(request) {
|
|
104
|
+
const options = request.options.length ? `
|
|
105
|
+
options: ${request.options.map((option) => `${option.id} (${option.label})`).join(", ")}` : "";
|
|
106
|
+
return `${request.id} ${request.kind} from ${request.session_id}
|
|
107
|
+
${request.question}${options}`;
|
|
108
|
+
}
|
|
109
|
+
async function getSkillUsage(since) {
|
|
110
|
+
const qs = since ? `?since=${encodeURIComponent(since)}` : "";
|
|
111
|
+
return api("GET", `/skills/usage${qs}`);
|
|
112
|
+
}
|
|
113
|
+
function nextSkills(current, skillId, verb) {
|
|
114
|
+
const has = current.some((ref) => ref.type === "skill" && ref.skillId === skillId);
|
|
115
|
+
if (verb === "attach") return has ? null : [...current, { type: "skill", skillId, version: "latest" }];
|
|
116
|
+
return has ? current.filter((ref) => !(ref.type === "skill" && ref.skillId === skillId)) : null;
|
|
117
|
+
}
|
|
118
|
+
async function setAgentSkill(agentId, skillId, verb) {
|
|
119
|
+
const path = `/agents/${encodeURIComponent(agentId)}`;
|
|
120
|
+
const { agent: agent2 } = await api("GET", path);
|
|
121
|
+
const skills2 = nextSkills(agent2.skills ?? [], skillId, verb);
|
|
122
|
+
if (!skills2) return "unchanged";
|
|
123
|
+
await api("PATCH", path, { skills: skills2, expectedVersion: agent2.version });
|
|
124
|
+
return "changed";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/library-skills.ts
|
|
128
|
+
function listLibrarySkills() {
|
|
129
|
+
return api("GET", "/library/skills");
|
|
130
|
+
}
|
|
131
|
+
function writeLibrarySkill(directory, options = {}) {
|
|
132
|
+
const form = new FormData();
|
|
133
|
+
for (const file of skillBundle(directory)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
134
|
+
if (options.name !== void 0) form.append("displayName", options.name);
|
|
135
|
+
if (options.expectedLatestRevision !== void 0) form.append("expectedLatestRevision", options.expectedLatestRevision);
|
|
136
|
+
return apiForm("POST", options.id ? `/library/skills/${encodeURIComponent(options.id)}/revisions` : "/library/skills", form);
|
|
137
|
+
}
|
|
138
|
+
function deleteLibrarySkill(id) {
|
|
139
|
+
return api("DELETE", `/library/skills/${encodeURIComponent(id)}`);
|
|
140
|
+
}
|
|
141
|
+
function registerLibrarySkillCommands(library2) {
|
|
142
|
+
const librarySkills = library2.command("skills").description("Library skills available to new conversations; requires a personal write key");
|
|
143
|
+
const print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
144
|
+
librarySkills.command("list").description("list skills and make pending skills available to new conversations (write scope)").action(async () => print(await listLibrarySkills()));
|
|
145
|
+
librarySkills.command("upload <directory>").description("upload a skill folder and make it available to new workspace conversations").option("--name <name>", "display name").action(async (directory, options) => print(await writeLibrarySkill(directory, options)));
|
|
146
|
+
librarySkills.command("save <id> <directory>").description("save a complete skill revision").option("--expected-latest-revision <id>", "refuse if the current revision has changed").action(async (id, directory, options) => print(await writeLibrarySkill(directory, { ...options, id })));
|
|
147
|
+
librarySkills.command("rm <id>").description("remove a skill from the Library").action(async (id) => print(await deleteLibrarySkill(id)));
|
|
148
|
+
}
|
|
149
|
+
|
|
50
150
|
// src/index.ts
|
|
51
|
-
import { existsSync as
|
|
151
|
+
import { existsSync as existsSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
52
152
|
import { basename as basename9, resolve as resolve9 } from "path";
|
|
53
153
|
import { createInterface as createInterface6 } from "readline";
|
|
54
154
|
import { Command } from "commander";
|
|
55
155
|
|
|
56
156
|
// src/files.ts
|
|
57
|
-
import { existsSync, readdirSync, readFileSync as
|
|
58
|
-
import { basename as
|
|
157
|
+
import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
158
|
+
import { basename as basename3, extname, join as join3 } from "path";
|
|
59
159
|
|
|
60
160
|
// src/resumable-upload.ts
|
|
61
161
|
import { createHash } from "crypto";
|
|
62
|
-
import { mkdirSync, openSync, readFileSync, readSync, closeSync, rmSync, statSync, writeFileSync } from "fs";
|
|
63
|
-
import { basename, dirname, join, resolve } from "path";
|
|
162
|
+
import { mkdirSync, openSync, readFileSync as readFileSync2, readSync, closeSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
163
|
+
import { basename as basename2, dirname, join as join2, resolve as resolve2 } from "path";
|
|
64
164
|
var SINGLE_REQUEST_MAX_BYTES = 32 * 1024 * 1024;
|
|
65
165
|
var MAX_ATTEMPTS = 8;
|
|
66
166
|
function statePath(path, size, mtimeMs) {
|
|
67
|
-
const key = createHash("sha256").update(`${
|
|
68
|
-
return
|
|
167
|
+
const key = createHash("sha256").update(`${resolve2(path)}\0${size}\0${mtimeMs}`).digest("hex").slice(0, 32);
|
|
168
|
+
return join2(dirname(configPath()), "uploads", `${key}.json`);
|
|
69
169
|
}
|
|
70
170
|
function loadSession(file, expiresInSeconds) {
|
|
71
171
|
try {
|
|
72
|
-
const saved = JSON.parse(
|
|
172
|
+
const saved = JSON.parse(readFileSync2(file, "utf8"));
|
|
73
173
|
if (saved.expiresInSeconds !== expiresInSeconds) return null;
|
|
74
174
|
if (Date.parse(saved.expires_at) - Date.now() < 3600 * 1e3) return null;
|
|
75
175
|
return saved;
|
|
@@ -131,7 +231,7 @@ async function transfer(session2, path, onProgress) {
|
|
|
131
231
|
}
|
|
132
232
|
}
|
|
133
233
|
async function uploadLargeFile(path, mediaType, expiresInSeconds, onProgress) {
|
|
134
|
-
const { size, mtimeMs } =
|
|
234
|
+
const { size, mtimeMs } = statSync2(path);
|
|
135
235
|
const stateFile = statePath(path, size, mtimeMs);
|
|
136
236
|
let session2 = loadSession(stateFile, expiresInSeconds);
|
|
137
237
|
if (session2) {
|
|
@@ -144,13 +244,13 @@ async function uploadLargeFile(path, mediaType, expiresInSeconds, onProgress) {
|
|
|
144
244
|
}
|
|
145
245
|
if (!session2) {
|
|
146
246
|
session2 = await api("POST", "/files/uploads", {
|
|
147
|
-
filename:
|
|
247
|
+
filename: basename2(path),
|
|
148
248
|
size_bytes: size,
|
|
149
249
|
mime_type: mediaType,
|
|
150
250
|
...expiresInSeconds ? { expires_in_seconds: expiresInSeconds } : {}
|
|
151
251
|
});
|
|
152
252
|
mkdirSync(dirname(stateFile), { recursive: true });
|
|
153
|
-
const saved = { ...session2, path:
|
|
253
|
+
const saved = { ...session2, path: resolve2(path), mtimeMs, expiresInSeconds };
|
|
154
254
|
writeFileSync(stateFile, JSON.stringify(saved), { mode: 384 });
|
|
155
255
|
}
|
|
156
256
|
await transfer(session2, path, onProgress);
|
|
@@ -181,23 +281,23 @@ function mediaTypeFor(path) {
|
|
|
181
281
|
}
|
|
182
282
|
async function uploadLocalFile(path, expiresInSeconds, onProgress = () => {
|
|
183
283
|
}) {
|
|
184
|
-
if (
|
|
284
|
+
if (statSync3(path).size > SINGLE_REQUEST_MAX_BYTES) {
|
|
185
285
|
return uploadLargeFile(path, mediaTypeFor(path), expiresInSeconds, onProgress);
|
|
186
286
|
}
|
|
187
|
-
const bytes =
|
|
188
|
-
return uploadFile(new Uint8Array(bytes),
|
|
287
|
+
const bytes = readFileSync3(path);
|
|
288
|
+
return uploadFile(new Uint8Array(bytes), basename3(path), mediaTypeFor(path), expiresInSeconds);
|
|
189
289
|
}
|
|
190
290
|
var IGNORED_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
191
291
|
function filesUnder(paths) {
|
|
192
292
|
const found = [];
|
|
193
293
|
const walk = (path) => {
|
|
194
|
-
const stat =
|
|
294
|
+
const stat = statSync3(path);
|
|
195
295
|
if (stat.isFile()) {
|
|
196
|
-
if (!IGNORED_NAMES.has(
|
|
296
|
+
if (!IGNORED_NAMES.has(basename3(path))) found.push(path);
|
|
197
297
|
return;
|
|
198
298
|
}
|
|
199
299
|
if (!stat.isDirectory()) return;
|
|
200
|
-
for (const name of
|
|
300
|
+
for (const name of readdirSync2(path).sort()) walk(join3(path, name));
|
|
201
301
|
};
|
|
202
302
|
for (const path of paths) walk(path);
|
|
203
303
|
return found;
|
|
@@ -469,22 +569,22 @@ async function unsubscribeMarketing() {
|
|
|
469
569
|
}
|
|
470
570
|
|
|
471
571
|
// src/import-local.ts
|
|
472
|
-
import { closeSync as closeSync3, existsSync as existsSync4, openSync as openSync3, readdirSync as
|
|
572
|
+
import { closeSync as closeSync3, existsSync as existsSync4, openSync as openSync3, readdirSync as readdirSync5, readFileSync as readFileSync7, readSync as readSync3, statSync as statSync6 } from "fs";
|
|
473
573
|
import { homedir as homedir3 } from "os";
|
|
474
|
-
import { join as
|
|
574
|
+
import { join as join7 } from "path";
|
|
475
575
|
import { createHash as createHash3 } from "crypto";
|
|
476
576
|
|
|
477
577
|
// src/import-files.ts
|
|
478
578
|
import { isUtf8 } from "buffer";
|
|
479
|
-
import { existsSync as existsSync3, readFileSync as
|
|
579
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
480
580
|
import { homedir as homedir2 } from "os";
|
|
481
|
-
import { dirname as dirname3, extname as extname2, isAbsolute, join as
|
|
581
|
+
import { dirname as dirname3, extname as extname2, isAbsolute, join as join5, relative as relative3, resolve as resolve3, sep as sep2, basename as basename5 } from "path";
|
|
482
582
|
|
|
483
583
|
// src/import-workspace.ts
|
|
484
584
|
import { spawnSync } from "child_process";
|
|
485
|
-
import { existsSync as existsSync2, readdirSync as
|
|
585
|
+
import { existsSync as existsSync2, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "fs";
|
|
486
586
|
import { homedir } from "os";
|
|
487
|
-
import { basename as
|
|
587
|
+
import { basename as basename4, dirname as dirname2, join as join4, relative as relative2, sep } from "path";
|
|
488
588
|
import { parse as parseToml } from "smol-toml";
|
|
489
589
|
var MAX_TREE = 5e3;
|
|
490
590
|
var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
|
|
@@ -494,26 +594,26 @@ function git(cwd, ...args) {
|
|
|
494
594
|
}
|
|
495
595
|
var isFile = (path) => {
|
|
496
596
|
try {
|
|
497
|
-
return
|
|
597
|
+
return statSync4(path).isFile();
|
|
498
598
|
} catch {
|
|
499
599
|
return false;
|
|
500
600
|
}
|
|
501
601
|
};
|
|
502
602
|
var isDir = (path) => {
|
|
503
603
|
try {
|
|
504
|
-
return
|
|
604
|
+
return statSync4(path).isDirectory();
|
|
505
605
|
} catch {
|
|
506
606
|
return false;
|
|
507
607
|
}
|
|
508
608
|
};
|
|
509
609
|
function sharedInstructionPaths(home = homedir()) {
|
|
510
|
-
const rules =
|
|
610
|
+
const rules = join4(home, ".agents", "rules");
|
|
511
611
|
let ruleFiles = [];
|
|
512
612
|
try {
|
|
513
|
-
ruleFiles =
|
|
613
|
+
ruleFiles = readdirSync3(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
|
|
514
614
|
} catch {
|
|
515
615
|
}
|
|
516
|
-
return [
|
|
616
|
+
return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
517
617
|
}
|
|
518
618
|
function strings(value, out, depth = 0) {
|
|
519
619
|
if (depth > 12) return;
|
|
@@ -522,14 +622,14 @@ function strings(value, out, depth = 0) {
|
|
|
522
622
|
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
523
623
|
}
|
|
524
624
|
function skillRoots(cwd, home) {
|
|
525
|
-
const claude = process.env.CLAUDE_CONFIG_DIR ??
|
|
526
|
-
const codex = process.env.CODEX_HOME ??
|
|
625
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
|
|
626
|
+
const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
|
|
527
627
|
return [
|
|
528
|
-
...cwd ? [
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
628
|
+
...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
|
|
629
|
+
join4(home, ".agents", "skills"),
|
|
630
|
+
join4(claude, "skills"),
|
|
631
|
+
join4(codex, "skills"),
|
|
632
|
+
join4(home, ".hermes", "skills")
|
|
533
633
|
];
|
|
534
634
|
}
|
|
535
635
|
function pluginSkill(plugin, skill, home) {
|
|
@@ -537,35 +637,35 @@ function pluginSkill(plugin, skill, home) {
|
|
|
537
637
|
if (depth > 7) return null;
|
|
538
638
|
let entries;
|
|
539
639
|
try {
|
|
540
|
-
entries =
|
|
640
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
541
641
|
} catch {
|
|
542
642
|
return null;
|
|
543
643
|
}
|
|
544
644
|
for (const entry2 of entries) {
|
|
545
645
|
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
546
|
-
const path =
|
|
547
|
-
if (entry2.name === skill &&
|
|
646
|
+
const path = join4(dir, entry2.name);
|
|
647
|
+
if (entry2.name === skill && basename4(dir) === "skills" && (underPlugin || path.includes(`${sep}${plugin}${sep}`)) && isFile(join4(path, "SKILL.md"))) return realpathSync(path);
|
|
548
648
|
const found = walk(path, depth + 1, underPlugin);
|
|
549
649
|
if (found) return found;
|
|
550
650
|
}
|
|
551
651
|
return null;
|
|
552
652
|
};
|
|
553
|
-
return walk(
|
|
653
|
+
return walk(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins"), 0, false) ?? walk(join4(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions", "skills-plugin"), 0, true);
|
|
554
654
|
}
|
|
555
655
|
function findSkill(name, cwd, home) {
|
|
556
656
|
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
557
657
|
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
558
658
|
for (const root of skillRoots(cwd, home)) {
|
|
559
|
-
const direct =
|
|
560
|
-
if (isFile(
|
|
659
|
+
const direct = join4(root, name);
|
|
660
|
+
if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
|
|
561
661
|
}
|
|
562
662
|
return null;
|
|
563
663
|
}
|
|
564
664
|
function loadedSkills(records, touched, cwd, home = homedir()) {
|
|
565
665
|
const dirs = /* @__PURE__ */ new Map();
|
|
566
666
|
const missing = /* @__PURE__ */ new Set();
|
|
567
|
-
const add = (dir, name =
|
|
568
|
-
if (!isFile(
|
|
667
|
+
const add = (dir, name = basename4(dir)) => {
|
|
668
|
+
if (!isFile(join4(dir, "SKILL.md"))) return false;
|
|
569
669
|
const real = realpathSync(dir);
|
|
570
670
|
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
571
671
|
return true;
|
|
@@ -582,7 +682,7 @@ function loadedSkills(records, touched, cwd, home = homedir()) {
|
|
|
582
682
|
if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
|
|
583
683
|
}
|
|
584
684
|
}
|
|
585
|
-
for (const path of touched) if (
|
|
685
|
+
for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname2(path));
|
|
586
686
|
for (const name of called) {
|
|
587
687
|
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
588
688
|
const dir = findSkill(name, cwd, home);
|
|
@@ -592,7 +692,7 @@ function loadedSkills(records, touched, cwd, home = homedir()) {
|
|
|
592
692
|
}
|
|
593
693
|
function readJson(path) {
|
|
594
694
|
try {
|
|
595
|
-
const value = JSON.parse(
|
|
695
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
596
696
|
return value && typeof value === "object" ? value : null;
|
|
597
697
|
} catch {
|
|
598
698
|
return null;
|
|
@@ -637,16 +737,16 @@ function mcpServers(harness2, cwd, home = homedir()) {
|
|
|
637
737
|
}
|
|
638
738
|
};
|
|
639
739
|
if (harness2 === "claude_code") {
|
|
640
|
-
const config = readJson(
|
|
740
|
+
const config = readJson(join4(process.env.CLAUDE_CONFIG_DIR ? dirname2(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
641
741
|
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
642
742
|
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
643
743
|
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
644
|
-
if (root) push(readJson(
|
|
744
|
+
if (root) push(readJson(join4(root, ".mcp.json"))?.mcpServers, "project");
|
|
645
745
|
push(config?.mcpServers, "user");
|
|
646
746
|
}
|
|
647
747
|
if (harness2 === "codex") {
|
|
648
748
|
try {
|
|
649
|
-
const config = parseToml(
|
|
749
|
+
const config = parseToml(readFileSync4(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
|
|
650
750
|
push(config.mcp_servers, "user");
|
|
651
751
|
} catch {
|
|
652
752
|
}
|
|
@@ -684,7 +784,7 @@ function workspaceState(cwd, harness2, skills2, home = homedir(), title = null)
|
|
|
684
784
|
harness: harness2,
|
|
685
785
|
title: title?.trim().slice(0, 120) || null,
|
|
686
786
|
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
687
|
-
project: cwd ?
|
|
787
|
+
project: cwd ? basename4(root ?? cwd) : null,
|
|
688
788
|
git: gitState,
|
|
689
789
|
tree: tree.slice(0, MAX_TREE),
|
|
690
790
|
tree_truncated: tree.length > MAX_TREE,
|
|
@@ -700,15 +800,15 @@ function skillFiles(skill) {
|
|
|
700
800
|
if (depth > 8 || out.length > 400) return;
|
|
701
801
|
let entries;
|
|
702
802
|
try {
|
|
703
|
-
entries =
|
|
803
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
704
804
|
} catch {
|
|
705
805
|
return;
|
|
706
806
|
}
|
|
707
807
|
for (const entry2 of entries) {
|
|
708
808
|
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
709
|
-
const path =
|
|
809
|
+
const path = join4(dir, entry2.name);
|
|
710
810
|
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
711
|
-
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${
|
|
811
|
+
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative2(skill.dir, path).split(sep).join("/")}` });
|
|
712
812
|
}
|
|
713
813
|
};
|
|
714
814
|
if (existsSync2(skill.dir)) walk(skill.dir, 0);
|
|
@@ -717,7 +817,7 @@ function skillFiles(skill) {
|
|
|
717
817
|
function codexThreadName(sessionId, home = homedir()) {
|
|
718
818
|
let name = null;
|
|
719
819
|
try {
|
|
720
|
-
for (const line of
|
|
820
|
+
for (const line of readFileSync4(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
721
821
|
if (!line.includes(sessionId)) continue;
|
|
722
822
|
try {
|
|
723
823
|
const entry2 = JSON.parse(line);
|
|
@@ -779,7 +879,7 @@ function classify(path) {
|
|
|
779
879
|
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
780
880
|
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
781
881
|
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
782
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(
|
|
882
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename5(path))) return { kind: "text", mediaType: "text/plain" };
|
|
783
883
|
return null;
|
|
784
884
|
}
|
|
785
885
|
var SHELL_READ = /(?:^|[\s;&|(`"'])(?:cat|head|tail|nl|less|more|wc|bat|sed\s+-n\s+(?:'[^']*'|"[^"]*"|\S+))(?:\s+-[\w-]+(?:\s+\d+)?)*\s+((?:~|\.{0,2}\/)?[\w@%+./~-]*[\w-]\.[\w]{1,10}|\/[\w@%+./-]+)/g;
|
|
@@ -798,8 +898,8 @@ function touchedPaths(records, cwd, home = homedir2()) {
|
|
|
798
898
|
const add = (raw, base) => {
|
|
799
899
|
if (!raw || raw.length > 1e3) return;
|
|
800
900
|
const path = raw.replace(/^~(?=\/)/, home);
|
|
801
|
-
if (isAbsolute(path)) out.add(
|
|
802
|
-
else if (base) out.add(
|
|
901
|
+
if (isAbsolute(path)) out.add(resolve3(path));
|
|
902
|
+
else if (base) out.add(resolve3(base, path));
|
|
803
903
|
};
|
|
804
904
|
const call = (name, input) => {
|
|
805
905
|
const i = obj(input);
|
|
@@ -837,7 +937,7 @@ function touchedPaths(records, cwd, home = homedir2()) {
|
|
|
837
937
|
}
|
|
838
938
|
function gitRoot(dir) {
|
|
839
939
|
for (let d = dir; ; d = dirname3(d)) {
|
|
840
|
-
if (existsSync3(
|
|
940
|
+
if (existsSync3(join5(d, ".git"))) return d;
|
|
841
941
|
if (d === dirname3(d)) return null;
|
|
842
942
|
}
|
|
843
943
|
}
|
|
@@ -845,26 +945,26 @@ function instructionPaths(cwd, harness2, home = homedir2()) {
|
|
|
845
945
|
const out = [];
|
|
846
946
|
if (cwd && existsSync3(cwd)) {
|
|
847
947
|
const root = gitRoot(cwd) ?? cwd;
|
|
848
|
-
for (let d =
|
|
849
|
-
for (const name of INSTRUCTION_FILES) out.push(
|
|
948
|
+
for (let d = resolve3(cwd); ; d = dirname3(d)) {
|
|
949
|
+
for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
|
|
850
950
|
if (d === root || d === dirname3(d)) break;
|
|
851
951
|
}
|
|
852
952
|
}
|
|
853
|
-
if (harness2 === "claude_code") out.push(
|
|
854
|
-
if (harness2 === "codex") out.push(
|
|
953
|
+
if (harness2 === "claude_code") out.push(join5(process.env.CLAUDE_CONFIG_DIR ?? join5(home, ".claude"), "CLAUDE.md"));
|
|
954
|
+
if (harness2 === "codex") out.push(join5(process.env.CODEX_HOME ?? join5(home, ".codex"), "AGENTS.md"));
|
|
855
955
|
out.push(...sharedInstructionPaths(home));
|
|
856
956
|
return out.filter((path) => {
|
|
857
957
|
try {
|
|
858
|
-
return
|
|
958
|
+
return statSync5(path).isFile();
|
|
859
959
|
} catch {
|
|
860
960
|
return false;
|
|
861
961
|
}
|
|
862
962
|
});
|
|
863
963
|
}
|
|
864
964
|
function resourceName(path, cwd, home = homedir2()) {
|
|
865
|
-
const inside = cwd ?
|
|
965
|
+
const inside = cwd ? relative3(resolve3(cwd), path) : "";
|
|
866
966
|
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
867
|
-
const underHome =
|
|
967
|
+
const underHome = relative3(home, path);
|
|
868
968
|
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
869
969
|
return `files/${shown.split(sep2).join("/")}`;
|
|
870
970
|
}
|
|
@@ -874,7 +974,7 @@ function resourceLine(name, mediaType, data) {
|
|
|
874
974
|
}
|
|
875
975
|
function collectSessionFiles(records, opts) {
|
|
876
976
|
const home = opts.home ?? homedir2();
|
|
877
|
-
const exclude = new Set((opts.exclude ?? []).map((path) =>
|
|
977
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
|
|
878
978
|
const leftOut = [];
|
|
879
979
|
const candidates = [];
|
|
880
980
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -889,7 +989,7 @@ function collectSessionFiles(records, opts) {
|
|
|
889
989
|
}
|
|
890
990
|
let size;
|
|
891
991
|
try {
|
|
892
|
-
const stat =
|
|
992
|
+
const stat = statSync5(path);
|
|
893
993
|
if (!stat.isFile()) return;
|
|
894
994
|
size = stat.size;
|
|
895
995
|
} catch {
|
|
@@ -939,7 +1039,7 @@ function collectSessionFiles(records, opts) {
|
|
|
939
1039
|
for (const file of candidates) {
|
|
940
1040
|
let data;
|
|
941
1041
|
try {
|
|
942
|
-
data =
|
|
1042
|
+
data = readFileSync5(file.path);
|
|
943
1043
|
} catch {
|
|
944
1044
|
leftOut.push({ path: file.path, reason: "missing" });
|
|
945
1045
|
continue;
|
|
@@ -976,9 +1076,9 @@ function collectSessionFiles(records, opts) {
|
|
|
976
1076
|
// src/import-environment.ts
|
|
977
1077
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
978
1078
|
import { createHash as createHash2 } from "crypto";
|
|
979
|
-
import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as
|
|
1079
|
+
import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync6, readSync as readSync2, rmSync as rmSync2 } from "fs";
|
|
980
1080
|
import { tmpdir } from "os";
|
|
981
|
-
import { join as
|
|
1081
|
+
import { join as join6 } from "path";
|
|
982
1082
|
import { isUtf8 as isUtf82 } from "buffer";
|
|
983
1083
|
import { createGzip } from "zlib";
|
|
984
1084
|
|
|
@@ -1157,14 +1257,14 @@ function listFiles(cwd) {
|
|
|
1157
1257
|
const walk = (dir, prefix) => {
|
|
1158
1258
|
let entries;
|
|
1159
1259
|
try {
|
|
1160
|
-
entries =
|
|
1260
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1161
1261
|
} catch {
|
|
1162
1262
|
return;
|
|
1163
1263
|
}
|
|
1164
1264
|
for (const entry2 of entries) {
|
|
1165
1265
|
if (SKIPPED.has(entry2.name)) continue;
|
|
1166
1266
|
const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
1167
|
-
if (entry2.isDirectory()) walk(
|
|
1267
|
+
if (entry2.isDirectory()) walk(join6(dir, entry2.name), rel);
|
|
1168
1268
|
else out.push(rel);
|
|
1169
1269
|
if (out.length > MAX_ENVIRONMENT_FILES) return;
|
|
1170
1270
|
}
|
|
@@ -1174,15 +1274,15 @@ function listFiles(cwd) {
|
|
|
1174
1274
|
}
|
|
1175
1275
|
function textOf(path, size) {
|
|
1176
1276
|
if (size > MAX_REDACTED_TEXT_BYTES) return null;
|
|
1177
|
-
const data =
|
|
1277
|
+
const data = readFileSync6(path);
|
|
1178
1278
|
if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
|
|
1179
1279
|
return data;
|
|
1180
1280
|
}
|
|
1181
1281
|
async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
1182
1282
|
const listed = listFiles(cwd);
|
|
1183
1283
|
if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
|
|
1184
|
-
const scratch = mkdtempSync(
|
|
1185
|
-
const path =
|
|
1284
|
+
const scratch = mkdtempSync(join6(tmpdir(), "sky-environment-"));
|
|
1285
|
+
const path = join6(scratch, "environment.tar.gz");
|
|
1186
1286
|
const leftOut = [];
|
|
1187
1287
|
const written2 = /* @__PURE__ */ new Set();
|
|
1188
1288
|
const hash = createHash2("sha256");
|
|
@@ -1210,7 +1310,7 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
|
1210
1310
|
});
|
|
1211
1311
|
try {
|
|
1212
1312
|
for (const rel of listed) {
|
|
1213
|
-
const full =
|
|
1313
|
+
const full = join6(cwd, ...rel.split("/"));
|
|
1214
1314
|
if (SECRET_FILE2.test(rel)) {
|
|
1215
1315
|
leftOut.push({ path: rel, reason: "credential file" });
|
|
1216
1316
|
continue;
|
|
@@ -1364,16 +1464,16 @@ function sniffTitle(filePath) {
|
|
|
1364
1464
|
}
|
|
1365
1465
|
}
|
|
1366
1466
|
function listClaudeCodeSessions(limit) {
|
|
1367
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1368
|
-
const projectsDir =
|
|
1467
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir3(), ".claude");
|
|
1468
|
+
const projectsDir = join7(root, "projects");
|
|
1369
1469
|
if (!existsSync4(projectsDir)) return [];
|
|
1370
1470
|
const sessions = [];
|
|
1371
1471
|
try {
|
|
1372
|
-
for (const encoded of
|
|
1373
|
-
const projectDir =
|
|
1472
|
+
for (const encoded of readdirSync5(projectsDir)) {
|
|
1473
|
+
const projectDir = join7(projectsDir, encoded);
|
|
1374
1474
|
let entries;
|
|
1375
1475
|
try {
|
|
1376
|
-
entries =
|
|
1476
|
+
entries = readdirSync5(projectDir);
|
|
1377
1477
|
} catch {
|
|
1378
1478
|
continue;
|
|
1379
1479
|
}
|
|
@@ -1381,22 +1481,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
1381
1481
|
for (const entry2 of entries) {
|
|
1382
1482
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1383
1483
|
const sessionId = entry2.slice(0, -6);
|
|
1384
|
-
const mainFile =
|
|
1484
|
+
const mainFile = join7(projectDir, entry2);
|
|
1385
1485
|
let stat;
|
|
1386
1486
|
try {
|
|
1387
|
-
stat =
|
|
1487
|
+
stat = statSync6(mainFile);
|
|
1388
1488
|
} catch {
|
|
1389
1489
|
continue;
|
|
1390
1490
|
}
|
|
1391
1491
|
if (!stat.isFile()) continue;
|
|
1392
1492
|
let discoveryError;
|
|
1393
1493
|
const subagentFiles = [];
|
|
1394
|
-
const subagentDir =
|
|
1494
|
+
const subagentDir = join7(projectDir, sessionId, "subagents");
|
|
1395
1495
|
if (existsSync4(subagentDir)) {
|
|
1396
1496
|
try {
|
|
1397
|
-
const subFiles =
|
|
1497
|
+
const subFiles = readdirSync5(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1398
1498
|
for (const f of subFiles) {
|
|
1399
|
-
subagentFiles.push(
|
|
1499
|
+
subagentFiles.push(join7(subagentDir, f));
|
|
1400
1500
|
}
|
|
1401
1501
|
} catch {
|
|
1402
1502
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1418,17 +1518,17 @@ function listClaudeCodeSessions(limit) {
|
|
|
1418
1518
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1419
1519
|
}
|
|
1420
1520
|
function listCodexSessions(limit) {
|
|
1421
|
-
const sessionsDir =
|
|
1521
|
+
const sessionsDir = join7(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join7(homedir3(), ".codex"), "sessions");
|
|
1422
1522
|
const records = [];
|
|
1423
1523
|
function walk(dir) {
|
|
1424
1524
|
let entries;
|
|
1425
1525
|
try {
|
|
1426
|
-
entries =
|
|
1526
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
1427
1527
|
} catch {
|
|
1428
1528
|
return;
|
|
1429
1529
|
}
|
|
1430
1530
|
for (const entry2 of entries) {
|
|
1431
|
-
const full =
|
|
1531
|
+
const full = join7(dir, entry2.name);
|
|
1432
1532
|
if (entry2.isDirectory()) {
|
|
1433
1533
|
walk(full);
|
|
1434
1534
|
continue;
|
|
@@ -1446,7 +1546,7 @@ function listCodexSessions(limit) {
|
|
|
1446
1546
|
sessionId: data.id,
|
|
1447
1547
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1448
1548
|
title: sniffTitle(full),
|
|
1449
|
-
mtimeMs:
|
|
1549
|
+
mtimeMs: statSync6(full).mtimeMs,
|
|
1450
1550
|
files: [full]
|
|
1451
1551
|
},
|
|
1452
1552
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -1515,7 +1615,7 @@ async function mintTrace(base, harness2) {
|
|
|
1515
1615
|
function loadSummaryFile(summaryPath) {
|
|
1516
1616
|
let raw;
|
|
1517
1617
|
try {
|
|
1518
|
-
raw =
|
|
1618
|
+
raw = readFileSync7(summaryPath, "utf8");
|
|
1519
1619
|
} catch {
|
|
1520
1620
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1521
1621
|
}
|
|
@@ -1556,9 +1656,9 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1556
1656
|
const source = new SessionSourceCollector();
|
|
1557
1657
|
const records = [];
|
|
1558
1658
|
for (const path of session2.files) {
|
|
1559
|
-
const stat =
|
|
1659
|
+
const stat = statSync6(path);
|
|
1560
1660
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1561
|
-
const text =
|
|
1661
|
+
const text = readFileSync7(path, "utf8");
|
|
1562
1662
|
for (const raw of text.split("\n")) {
|
|
1563
1663
|
if (!raw.trim()) continue;
|
|
1564
1664
|
let record;
|
|
@@ -1769,8 +1869,8 @@ function renderContextPage(page) {
|
|
|
1769
1869
|
}
|
|
1770
1870
|
|
|
1771
1871
|
// src/agent-file.ts
|
|
1772
|
-
import { existsSync as existsSync5, readFileSync as
|
|
1773
|
-
import { dirname as dirname4, resolve as
|
|
1872
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
1873
|
+
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
1774
1874
|
import { parse as parseToml2 } from "smol-toml";
|
|
1775
1875
|
var fileInstructionSchema = external_exports.object({
|
|
1776
1876
|
name: external_exports.string(),
|
|
@@ -1803,7 +1903,7 @@ var agentFileSchema = external_exports.object({
|
|
|
1803
1903
|
memory_mb: external_exports.number().int().optional()
|
|
1804
1904
|
});
|
|
1805
1905
|
function readAgentFile(path) {
|
|
1806
|
-
const raw =
|
|
1906
|
+
const raw = readFileSync8(path, "utf8");
|
|
1807
1907
|
const parsed = agentFileSchema.safeParse(parseToml2(raw));
|
|
1808
1908
|
if (!parsed.success) {
|
|
1809
1909
|
const issue = parsed.error.issues[0];
|
|
@@ -1815,11 +1915,11 @@ function readAgentFile(path) {
|
|
|
1815
1915
|
`${path}: reasoning_effort '${file.reasoning_effort}' is not available for ${file.type} \u2014 ${reasoningEffortDocLine()}`
|
|
1816
1916
|
);
|
|
1817
1917
|
}
|
|
1818
|
-
const base = dirname4(
|
|
1918
|
+
const base = dirname4(resolve4(path));
|
|
1819
1919
|
const inline = (ref, inlineContent) => {
|
|
1820
1920
|
if (inlineContent !== void 0) return inlineContent;
|
|
1821
1921
|
if (!ref) return void 0;
|
|
1822
|
-
return
|
|
1922
|
+
return readFileSync8(resolve4(base, ref), "utf8");
|
|
1823
1923
|
};
|
|
1824
1924
|
const spec = {
|
|
1825
1925
|
name: file.name,
|
|
@@ -1845,7 +1945,7 @@ function readAgentFile(path) {
|
|
|
1845
1945
|
return { file, spec };
|
|
1846
1946
|
}
|
|
1847
1947
|
function stampId(path, id) {
|
|
1848
|
-
writeFileSync2(path, stampIdText(
|
|
1948
|
+
writeFileSync2(path, stampIdText(readFileSync8(path, "utf8"), id));
|
|
1849
1949
|
}
|
|
1850
1950
|
function stampIdText(text, id) {
|
|
1851
1951
|
const out = [];
|
|
@@ -1997,18 +2097,18 @@ function renderCatalogAgent(agent2) {
|
|
|
1997
2097
|
|
|
1998
2098
|
// src/clone.ts
|
|
1999
2099
|
import { spawn } from "child_process";
|
|
2000
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2100
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
2001
2101
|
import { homedir as homedir4 } from "os";
|
|
2002
|
-
import { basename as
|
|
2102
|
+
import { basename as basename6, join as join8 } from "path";
|
|
2003
2103
|
import { createInterface } from "readline";
|
|
2004
2104
|
var PROMPT_CAP = 1e5;
|
|
2005
2105
|
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
2006
2106
|
function claudeConfigRoot() {
|
|
2007
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
2107
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join8(homedir4(), ".claude");
|
|
2008
2108
|
}
|
|
2009
2109
|
function readIfExists(path) {
|
|
2010
2110
|
try {
|
|
2011
|
-
return
|
|
2111
|
+
return readFileSync9(path, "utf8");
|
|
2012
2112
|
} catch {
|
|
2013
2113
|
return void 0;
|
|
2014
2114
|
}
|
|
@@ -2024,9 +2124,9 @@ function readJson2(path) {
|
|
|
2024
2124
|
}
|
|
2025
2125
|
function claudeModel(configRoot) {
|
|
2026
2126
|
const candidates = [
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2127
|
+
join8(process.cwd(), ".claude", "settings.local.json"),
|
|
2128
|
+
join8(process.cwd(), ".claude", "settings.json"),
|
|
2129
|
+
join8(configRoot, "settings.json")
|
|
2030
2130
|
];
|
|
2031
2131
|
for (const path of candidates) {
|
|
2032
2132
|
const model = readJson2(path)?.model;
|
|
@@ -2036,11 +2136,11 @@ function claudeModel(configRoot) {
|
|
|
2036
2136
|
}
|
|
2037
2137
|
function claudeMcpServers() {
|
|
2038
2138
|
const merged = {};
|
|
2039
|
-
const userConfig = readJson2(
|
|
2139
|
+
const userConfig = readJson2(join8(homedir4(), ".claude.json"));
|
|
2040
2140
|
Object.assign(merged, userConfig?.mcpServers ?? {});
|
|
2041
2141
|
Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
|
|
2042
2142
|
const projectConfig = readJson2(
|
|
2043
|
-
|
|
2143
|
+
join8(process.cwd(), ".mcp.json")
|
|
2044
2144
|
);
|
|
2045
2145
|
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
2046
2146
|
return merged;
|
|
@@ -2057,13 +2157,13 @@ function readClaudeCode() {
|
|
|
2057
2157
|
const configRoot = claudeConfigRoot();
|
|
2058
2158
|
const parts = [];
|
|
2059
2159
|
const sources = [];
|
|
2060
|
-
const global = readIfExists(
|
|
2160
|
+
const global = readIfExists(join8(configRoot, "CLAUDE.md"));
|
|
2061
2161
|
if (global?.trim()) {
|
|
2062
2162
|
parts.push({ label: "Global CLAUDE.md", content: global });
|
|
2063
2163
|
sources.push("global CLAUDE.md");
|
|
2064
2164
|
}
|
|
2065
2165
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
2066
|
-
const project2 = readIfExists(
|
|
2166
|
+
const project2 = readIfExists(join8(process.cwd(), name));
|
|
2067
2167
|
if (project2?.trim()) {
|
|
2068
2168
|
parts.push({ label: `Project ${name}`, content: project2 });
|
|
2069
2169
|
sources.push(`project ${name}`);
|
|
@@ -2104,8 +2204,8 @@ function detectHarness(explicit) {
|
|
|
2104
2204
|
return explicit;
|
|
2105
2205
|
}
|
|
2106
2206
|
if (existsSync6(claudeConfigRoot())) return "claude_code";
|
|
2107
|
-
if (existsSync6(
|
|
2108
|
-
if (existsSync6(
|
|
2207
|
+
if (existsSync6(join8(homedir4(), ".codex"))) return "codex";
|
|
2208
|
+
if (existsSync6(join8(homedir4(), ".openclaw"))) return "openclaw";
|
|
2109
2209
|
throw new Error(
|
|
2110
2210
|
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
2111
2211
|
);
|
|
@@ -2117,7 +2217,7 @@ function readLocal(harness2) {
|
|
|
2117
2217
|
);
|
|
2118
2218
|
}
|
|
2119
2219
|
function linkPath(configRoot) {
|
|
2120
|
-
return
|
|
2220
|
+
return join8(configRoot, "agentsky-link.json");
|
|
2121
2221
|
}
|
|
2122
2222
|
function readLink(configRoot) {
|
|
2123
2223
|
const link2 = readJson2(linkPath(configRoot));
|
|
@@ -2226,7 +2326,7 @@ async function createClone(local, opts) {
|
|
|
2226
2326
|
`model ${local.model ?? "(harness default)"}`,
|
|
2227
2327
|
`${keptNames.length} MCP server${keptNames.length === 1 ? "" : "s"}${keptNames.length ? ` (${keptNames.join(", ")})` : ""}`
|
|
2228
2328
|
].join(" \xB7 ");
|
|
2229
|
-
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${
|
|
2329
|
+
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${basename6(process.cwd())})`);
|
|
2230
2330
|
console.log(`Will clone: ${summary}`);
|
|
2231
2331
|
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
2232
2332
|
if (local.droppedServers.length) {
|
|
@@ -2426,25 +2526,25 @@ import { hostname } from "os";
|
|
|
2426
2526
|
import { spawn as spawn2 } from "child_process";
|
|
2427
2527
|
|
|
2428
2528
|
// src/mcp-register.ts
|
|
2429
|
-
import { existsSync as existsSync8, readFileSync as
|
|
2529
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
2430
2530
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
2431
2531
|
import { homedir as homedir6 } from "os";
|
|
2432
|
-
import { join as
|
|
2532
|
+
import { join as join11 } from "path";
|
|
2433
2533
|
import { parse as parseToml3 } from "smol-toml";
|
|
2434
2534
|
import { isMap, parseDocument } from "yaml";
|
|
2435
2535
|
|
|
2436
2536
|
// src/local-agents.ts
|
|
2437
2537
|
import { existsSync as existsSync7 } from "fs";
|
|
2438
2538
|
import { homedir as homedir5 } from "os";
|
|
2439
|
-
import { join as
|
|
2539
|
+
import { join as join9 } from "path";
|
|
2440
2540
|
function localAgents(env = process.env, home = homedir5()) {
|
|
2441
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
2442
|
-
const codex = env.CODEX_HOME ||
|
|
2443
|
-
const cursor =
|
|
2541
|
+
const claude = env.CLAUDE_CONFIG_DIR || join9(home, ".claude");
|
|
2542
|
+
const codex = env.CODEX_HOME || join9(home, ".codex");
|
|
2543
|
+
const cursor = join9(home, ".cursor");
|
|
2444
2544
|
return [
|
|
2445
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
2446
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
2447
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
2545
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join9(claude, "skills") },
|
|
2546
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join9(codex, "skills") },
|
|
2547
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join9(cursor, "skills") }
|
|
2448
2548
|
];
|
|
2449
2549
|
}
|
|
2450
2550
|
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
@@ -2461,8 +2561,8 @@ function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
|
2461
2561
|
}
|
|
2462
2562
|
|
|
2463
2563
|
// src/skill.ts
|
|
2464
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
2465
|
-
import { join as
|
|
2564
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
2565
|
+
import { join as join10 } from "path";
|
|
2466
2566
|
|
|
2467
2567
|
// src/catalog-file.ts
|
|
2468
2568
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -2964,7 +3064,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
2964
3064
|
}
|
|
2965
3065
|
function installedVersion(dir) {
|
|
2966
3066
|
try {
|
|
2967
|
-
const marker = JSON.parse(
|
|
3067
|
+
const marker = JSON.parse(readFileSync10(join10(dir, SKILL_MARKER), "utf8"));
|
|
2968
3068
|
return typeof marker.version === "string" ? marker.version : null;
|
|
2969
3069
|
} catch {
|
|
2970
3070
|
return null;
|
|
@@ -2974,32 +3074,32 @@ function isInstalledSkill(dir) {
|
|
|
2974
3074
|
return installedVersion(dir) !== null;
|
|
2975
3075
|
}
|
|
2976
3076
|
function installSkill(agent2, opts = {}) {
|
|
2977
|
-
const dir =
|
|
3077
|
+
const dir = join10(agent2.skillsDir, SKILL_NAME);
|
|
2978
3078
|
const previous = installedVersion(dir);
|
|
2979
3079
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2980
3080
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2981
3081
|
if (outcome !== "current") {
|
|
2982
3082
|
mkdirSync2(dir, { recursive: true });
|
|
2983
|
-
writeFileSync4(
|
|
2984
|
-
writeFileSync4(
|
|
3083
|
+
writeFileSync4(join10(dir, "SKILL.md"), skillDocument());
|
|
3084
|
+
writeFileSync4(join10(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2985
3085
|
`);
|
|
2986
3086
|
}
|
|
2987
3087
|
if (opts.catalog) {
|
|
2988
3088
|
mkdirSync2(dir, { recursive: true });
|
|
2989
|
-
writeFileSync4(
|
|
3089
|
+
writeFileSync4(join10(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2990
3090
|
}
|
|
2991
|
-
const refsDir =
|
|
3091
|
+
const refsDir = join10(dir, REFERENCES_DIR);
|
|
2992
3092
|
mkdirSync2(refsDir, { recursive: true });
|
|
2993
|
-
writeFileSync4(
|
|
2994
|
-
writeFileSync4(
|
|
2995
|
-
writeFileSync4(
|
|
3093
|
+
writeFileSync4(join10(refsDir, "import.md"), importReferenceDoc());
|
|
3094
|
+
writeFileSync4(join10(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
3095
|
+
writeFileSync4(join10(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2996
3096
|
return { agent: agent2.id, path: dir, outcome };
|
|
2997
3097
|
}
|
|
2998
3098
|
|
|
2999
3099
|
// src/mcp-register.ts
|
|
3000
3100
|
var MCP_SERVER_NAME = SKILL_NAME;
|
|
3001
3101
|
function detectHermes(env = process.env, home = homedir6()) {
|
|
3002
|
-
const root = env.HERMES_HOME ||
|
|
3102
|
+
const root = env.HERMES_HOME || join11(home, ".hermes");
|
|
3003
3103
|
return existsSync8(root) ? { id: "hermes", name: "Hermes", root } : null;
|
|
3004
3104
|
}
|
|
3005
3105
|
function detectMcpClients(env = process.env, home = homedir6()) {
|
|
@@ -3047,18 +3147,18 @@ function registerMcpServer(client, opts, runner = systemCommandRunner()) {
|
|
|
3047
3147
|
function mcpConfigPath(client, env = process.env, home = homedir6()) {
|
|
3048
3148
|
switch (client.id) {
|
|
3049
3149
|
case "claude_code":
|
|
3050
|
-
return
|
|
3150
|
+
return join11(env.CLAUDE_CONFIG_DIR || home, ".claude.json");
|
|
3051
3151
|
case "codex":
|
|
3052
|
-
return
|
|
3152
|
+
return join11(client.root, "config.toml");
|
|
3053
3153
|
case "cursor":
|
|
3054
|
-
return
|
|
3154
|
+
return join11(client.root, "mcp.json");
|
|
3055
3155
|
case "hermes":
|
|
3056
|
-
return
|
|
3156
|
+
return join11(client.root, "config.yaml");
|
|
3057
3157
|
}
|
|
3058
3158
|
}
|
|
3059
3159
|
function readText(path) {
|
|
3060
3160
|
try {
|
|
3061
|
-
return
|
|
3161
|
+
return readFileSync11(path, "utf8");
|
|
3062
3162
|
} catch {
|
|
3063
3163
|
return null;
|
|
3064
3164
|
}
|
|
@@ -3160,29 +3260,29 @@ function refreshMcpRegistrations(opts, clients = detectMcpClients(), runner = sy
|
|
|
3160
3260
|
}
|
|
3161
3261
|
|
|
3162
3262
|
// src/shell-env.ts
|
|
3163
|
-
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as
|
|
3263
|
+
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
3164
3264
|
import { homedir as homedir7 } from "os";
|
|
3165
|
-
import { basename as
|
|
3265
|
+
import { basename as basename7, dirname as dirname5, join as join12 } from "path";
|
|
3166
3266
|
var TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
3167
3267
|
var HOOK_MARK = "# AgentSky API token, kept current by sky";
|
|
3168
3268
|
function shellEnvFiles() {
|
|
3169
3269
|
const dir = dirname5(configPath());
|
|
3170
|
-
return { sh:
|
|
3270
|
+
return { sh: join12(dir, "env.sh"), fish: join12(dir, "env.fish") };
|
|
3171
3271
|
}
|
|
3172
3272
|
function quoted(value) {
|
|
3173
3273
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3174
3274
|
}
|
|
3175
3275
|
function shellStartupFiles(env = process.env, home = homedir7(), platform = process.platform) {
|
|
3176
|
-
const shell =
|
|
3276
|
+
const shell = basename7(env.SHELL ?? "");
|
|
3177
3277
|
const files2 = [];
|
|
3178
|
-
const zshenv =
|
|
3278
|
+
const zshenv = join12(env.ZDOTDIR || home, ".zshenv");
|
|
3179
3279
|
if (shell === "zsh" || platform === "darwin" || existsSync9(zshenv)) files2.push({ path: zshenv, kind: "sh" });
|
|
3180
|
-
const bashrc =
|
|
3280
|
+
const bashrc = join12(home, ".bashrc");
|
|
3181
3281
|
if (shell === "bash" || existsSync9(bashrc)) files2.push({ path: bashrc, kind: "sh" });
|
|
3182
|
-
const bashProfile =
|
|
3282
|
+
const bashProfile = join12(home, ".bash_profile");
|
|
3183
3283
|
if (existsSync9(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
|
|
3184
|
-
const fish =
|
|
3185
|
-
if (shell === "fish" || existsSync9(fish)) files2.push({ path:
|
|
3284
|
+
const fish = join12(env.XDG_CONFIG_HOME || join12(home, ".config"), "fish");
|
|
3285
|
+
if (shell === "fish" || existsSync9(fish)) files2.push({ path: join12(fish, "conf.d", "agentsky.fish"), kind: "fish" });
|
|
3186
3286
|
return files2;
|
|
3187
3287
|
}
|
|
3188
3288
|
function writeShellEnv(token, opts = {}) {
|
|
@@ -3198,7 +3298,7 @@ function writeShellEnv(token, opts = {}) {
|
|
|
3198
3298
|
writeFileSync6(fish, `${header2}${token ? `set -gx ${TOKEN_ENV} ${quoted(token)}` : `set -e ${TOKEN_ENV}`}
|
|
3199
3299
|
`, { mode: 384 });
|
|
3200
3300
|
for (const file of startup) {
|
|
3201
|
-
const existing = existsSync9(file.path) ?
|
|
3301
|
+
const existing = existsSync9(file.path) ? readFileSync12(file.path, "utf8") : "";
|
|
3202
3302
|
if (existing.includes(HOOK_MARK)) continue;
|
|
3203
3303
|
const line = file.kind === "fish" ? `${HOOK_MARK}
|
|
3204
3304
|
test -f ${quoted(fish)}; and source ${quoted(fish)}
|
|
@@ -3439,9 +3539,11 @@ async function triggerRequest(id, resource, change) {
|
|
|
3439
3539
|
|
|
3440
3540
|
// src/session-workspace.ts
|
|
3441
3541
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3442
|
-
import { createHash as createHash5, randomUUID } from "crypto";
|
|
3443
|
-
import { readdirSync as
|
|
3444
|
-
import {
|
|
3542
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
|
|
3543
|
+
import { existsSync as existsSync10, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync13, renameSync, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
3544
|
+
import { homedir as homedir8, hostname as hostname2, tmpdir as tmpdir2 } from "os";
|
|
3545
|
+
import { basename as basename8, dirname as dirname6, join as join13, relative as relative4, resolve as resolve6 } from "path";
|
|
3546
|
+
import { inflateRawSync } from "zlib";
|
|
3445
3547
|
var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
|
|
3446
3548
|
var PART_SIZE = 8 * 1024 * 1024;
|
|
3447
3549
|
var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
@@ -3475,15 +3577,15 @@ function folderFiles(dir) {
|
|
|
3475
3577
|
const git2 = spawnSync4("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
|
|
3476
3578
|
const top = spawnSync4("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
|
|
3477
3579
|
if (git2.status === 0 && top.status === 0) {
|
|
3478
|
-
return git2.stdout.split("\0").filter((path) => path &&
|
|
3580
|
+
return git2.stdout.split("\0").filter((path) => path && statSync7(join13(dir, path), { throwIfNoEntry: false })?.isFile());
|
|
3479
3581
|
}
|
|
3480
3582
|
const files2 = [];
|
|
3481
3583
|
const walk = (at) => {
|
|
3482
|
-
for (const entry2 of
|
|
3584
|
+
for (const entry2 of readdirSync6(at, { withFileTypes: true })) {
|
|
3483
3585
|
if (SKIPPED2.has(entry2.name)) continue;
|
|
3484
|
-
const full =
|
|
3586
|
+
const full = join13(at, entry2.name);
|
|
3485
3587
|
if (entry2.isDirectory()) walk(full);
|
|
3486
|
-
else if (entry2.isFile()) files2.push(
|
|
3588
|
+
else if (entry2.isFile()) files2.push(relative4(dir, full));
|
|
3487
3589
|
}
|
|
3488
3590
|
};
|
|
3489
3591
|
walk(dir);
|
|
@@ -3499,8 +3601,8 @@ function folderArchive(dir, files2) {
|
|
|
3499
3601
|
return tar.stdout;
|
|
3500
3602
|
}
|
|
3501
3603
|
async function importFolder(id, dir, opts = {}) {
|
|
3502
|
-
const root =
|
|
3503
|
-
const name = opts.name ??
|
|
3604
|
+
const root = resolve6(dir);
|
|
3605
|
+
const name = opts.name ?? basename8(root);
|
|
3504
3606
|
const archive = opts.archive ?? folderArchive(root, folderFiles(root));
|
|
3505
3607
|
if (archive.length <= SINGLE_IMPORT_MAX) {
|
|
3506
3608
|
const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
|
|
@@ -3551,7 +3653,7 @@ async function handoff(id, targetAgent, opts = {}) {
|
|
|
3551
3653
|
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
|
|
3552
3654
|
target_agent: targetAgent,
|
|
3553
3655
|
...opts.fromMessage ? { last_message_id: opts.fromMessage } : {}
|
|
3554
|
-
}, { idempotencyKey: opts.idempotencyKey ??
|
|
3656
|
+
}, { idempotencyKey: opts.idempotencyKey ?? randomUUID2() });
|
|
3555
3657
|
opts.onStatus?.(h);
|
|
3556
3658
|
while (opts.wait && !TERMINAL.has(h.status)) {
|
|
3557
3659
|
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
@@ -3561,6 +3663,97 @@ async function handoff(id, targetAgent, opts = {}) {
|
|
|
3561
3663
|
}
|
|
3562
3664
|
return h;
|
|
3563
3665
|
}
|
|
3666
|
+
function zipEntries(archive) {
|
|
3667
|
+
let end = archive.length - 22;
|
|
3668
|
+
while (end >= 0 && archive.readUInt32LE(end) !== 101010256) end -= 1;
|
|
3669
|
+
if (end < 0) throw new ApiError(422, "checkpoint_invalid", "the downloaded conversation is not a readable archive");
|
|
3670
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3671
|
+
let at = archive.readUInt32LE(end + 16);
|
|
3672
|
+
for (let index = archive.readUInt16LE(end + 10); index > 0; index -= 1) {
|
|
3673
|
+
const method = archive.readUInt16LE(at + 10);
|
|
3674
|
+
const size = archive.readUInt32LE(at + 20);
|
|
3675
|
+
const nameLength = archive.readUInt16LE(at + 28);
|
|
3676
|
+
const local = archive.readUInt32LE(at + 42);
|
|
3677
|
+
const start = local + 30 + archive.readUInt16LE(local + 26) + archive.readUInt16LE(local + 28);
|
|
3678
|
+
const packed = archive.subarray(start, start + size);
|
|
3679
|
+
if (method !== 0 && method !== 8) throw new ApiError(422, "checkpoint_invalid", "the downloaded conversation uses an unsupported compression");
|
|
3680
|
+
entries.set(archive.subarray(at + 46, at + 46 + nameLength).toString("utf8"), method === 8 ? inflateRawSync(packed) : Buffer.from(packed));
|
|
3681
|
+
at += 46 + nameLength + archive.readUInt16LE(at + 30) + archive.readUInt16LE(at + 32);
|
|
3682
|
+
}
|
|
3683
|
+
return entries;
|
|
3684
|
+
}
|
|
3685
|
+
function conversationFiles(archive, harness2) {
|
|
3686
|
+
const entries = zipEntries(archive);
|
|
3687
|
+
const manifest = JSON.parse(entries.get("manifest.json")?.toString("utf8") ?? "null");
|
|
3688
|
+
const nativeId = manifest?.metadata?.native_session_id ?? "";
|
|
3689
|
+
if (manifest?.format !== "asteroids.native-checkpoint" || manifest.source_harness !== harness2 || !Array.isArray(manifest.files) || !/^[A-Za-z0-9_-]{1,200}$/.test(nativeId)) {
|
|
3690
|
+
throw new ApiError(422, "checkpoint_invalid", "the downloaded conversation is not one this computer can continue");
|
|
3691
|
+
}
|
|
3692
|
+
const ours = (path) => {
|
|
3693
|
+
const parts = path.split("/");
|
|
3694
|
+
if (parts.some((part) => !part || part === "." || part === "..")) return false;
|
|
3695
|
+
if (harness2 === "claude_code") return parts[0] === "projects" && parts.length >= 3 && (parts[2] === `${nativeId}.jsonl` || parts[2] === nativeId);
|
|
3696
|
+
return parts[0] === "sessions" && parts.at(-1).startsWith("rollout-") && parts.at(-1).endsWith(`${nativeId}.jsonl`);
|
|
3697
|
+
};
|
|
3698
|
+
const files2 = manifest.files.filter((file) => ours(file.path)).map((file) => {
|
|
3699
|
+
const data = entries.get(`files/${file.path}`);
|
|
3700
|
+
if (!data || data.length !== file.size || createHash5("sha256").update(data).digest("hex") !== file.sha256) {
|
|
3701
|
+
throw new ApiError(422, "checkpoint_invalid", "the downloaded conversation is incomplete; try again");
|
|
3702
|
+
}
|
|
3703
|
+
return { path: file.path, data };
|
|
3704
|
+
});
|
|
3705
|
+
if (!files2.length) throw new ApiError(422, "checkpoint_invalid", "the downloaded conversation has no transcript");
|
|
3706
|
+
return { nativeId, files: files2 };
|
|
3707
|
+
}
|
|
3708
|
+
async function verifiedDownload(path) {
|
|
3709
|
+
const res = await apiRaw("GET", path);
|
|
3710
|
+
if (!res.ok) {
|
|
3711
|
+
const body = await res.json().catch(() => null);
|
|
3712
|
+
throw new ApiError(res.status, body?.error?.code ?? "request_failed", body?.error?.message ?? `AgentSky answered ${res.status}`);
|
|
3713
|
+
}
|
|
3714
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
3715
|
+
const expected = res.headers.get("x-content-sha256");
|
|
3716
|
+
if (expected && createHash5("sha256").update(bytes).digest("hex") !== expected) throw new ApiError(502, "download_corrupt", "the download was damaged; try again");
|
|
3717
|
+
return bytes;
|
|
3718
|
+
}
|
|
3719
|
+
async function continueHere(id, dir, opts = {}) {
|
|
3720
|
+
const session2 = (await api("GET", `/sessions/${encodeURIComponent(id)}`)).session;
|
|
3721
|
+
const harness2 = session2.agentType;
|
|
3722
|
+
if (harness2 !== "claude_code" && harness2 !== "codex") throw new ApiError(409, "harness_unsupported", "only Claude Code and Codex sessions can continue on a computer");
|
|
3723
|
+
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/local-handoff`, { machine: hostname2().slice(0, 120) }, { idempotencyKey: opts.idempotencyKey ?? randomUUID2() });
|
|
3724
|
+
opts.onStatus?.(h);
|
|
3725
|
+
while (!TERMINAL.has(h.status)) {
|
|
3726
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
3727
|
+
const next = (await api("GET", `/session-handoffs/${encodeURIComponent(h.id)}`)).handoff;
|
|
3728
|
+
if (next.status !== h.status) opts.onStatus?.(next);
|
|
3729
|
+
h = next;
|
|
3730
|
+
}
|
|
3731
|
+
if (h.status !== "completed") throw new ApiError(422, h.error?.code ?? "handoff_failed", h.error?.message ?? "the session could not be moved; it stays in the cloud");
|
|
3732
|
+
const { nativeId, files: files2 } = conversationFiles(await verifiedDownload(`/session-handoffs/${encodeURIComponent(h.id)}/checkpoint`), harness2);
|
|
3733
|
+
const root = harness2 === "codex" ? process.env.CODEX_HOME || join13(homedir8(), ".codex") : process.env.CLAUDE_CONFIG_DIR || join13(homedir8(), ".claude");
|
|
3734
|
+
const stamp = Date.now();
|
|
3735
|
+
for (const file of files2) {
|
|
3736
|
+
const target = join13(root, file.path);
|
|
3737
|
+
mkdirSync4(dirname6(target), { recursive: true });
|
|
3738
|
+
if (existsSync10(target)) {
|
|
3739
|
+
if (!lstatSync2(target).isFile()) throw new ApiError(409, "restore_blocked", `something other than a file is in the way at ${target}`);
|
|
3740
|
+
if (readFileSync13(target).equals(file.data)) continue;
|
|
3741
|
+
renameSync(target, `${target}.before-agentsky-${stamp}`);
|
|
3742
|
+
}
|
|
3743
|
+
writeFileSync7(target, file.data, { mode: 384 });
|
|
3744
|
+
}
|
|
3745
|
+
const folder = resolve6(dir);
|
|
3746
|
+
mkdirSync4(folder, { recursive: true });
|
|
3747
|
+
const carried = h.workspace_carried === true;
|
|
3748
|
+
if (carried) {
|
|
3749
|
+
const archive = join13(tmpdir2(), `agentsky-workspace-${h.id}.tar.gz`);
|
|
3750
|
+
writeFileSync7(archive, await verifiedDownload(`/session-handoffs/${encodeURIComponent(h.id)}/workspace`), { mode: 384 });
|
|
3751
|
+
const tar = spawnSync4("tar", ["-xzf", archive, "-C", folder]);
|
|
3752
|
+
if (tar.status !== 0) throw new ApiError(422, "workspace_unpack_failed", `tar failed: ${tar.stderr.toString().trim()}`);
|
|
3753
|
+
}
|
|
3754
|
+
const resume = harness2 === "codex" ? `cd ${JSON.stringify(folder)} && codex resume ${nativeId}` : `cd ${JSON.stringify(folder)} && claude --resume ${nativeId}`;
|
|
3755
|
+
return { harness: harness2, nativeId, folder, workspaceCarried: carried, resume };
|
|
3756
|
+
}
|
|
3564
3757
|
|
|
3565
3758
|
// src/render.ts
|
|
3566
3759
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
@@ -3682,15 +3875,15 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
3682
3875
|
}
|
|
3683
3876
|
|
|
3684
3877
|
// src/setup.ts
|
|
3685
|
-
import { mkdirSync as
|
|
3686
|
-
import { dirname as
|
|
3878
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
|
|
3879
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
3687
3880
|
import { createInterface as createInterface3 } from "readline";
|
|
3688
3881
|
|
|
3689
3882
|
// src/setup-local.ts
|
|
3690
3883
|
import { isUtf8 as isUtf83 } from "buffer";
|
|
3691
|
-
import { existsSync as
|
|
3692
|
-
import { homedir as
|
|
3693
|
-
import { dirname as
|
|
3884
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync8 } from "fs";
|
|
3885
|
+
import { homedir as homedir9 } from "os";
|
|
3886
|
+
import { dirname as dirname7, isAbsolute as isAbsolute2, join as join14, resolve as resolve7 } from "path";
|
|
3694
3887
|
import { parse as parseToml4 } from "smol-toml";
|
|
3695
3888
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
3696
3889
|
var MAX_IMPORT_DEPTH = 5;
|
|
@@ -3714,12 +3907,12 @@ function redacted(data) {
|
|
|
3714
3907
|
}
|
|
3715
3908
|
function skillFiles2(root, prefix) {
|
|
3716
3909
|
const files2 = [];
|
|
3717
|
-
for (const entry2 of
|
|
3718
|
-
const path =
|
|
3910
|
+
for (const entry2 of readdirSync7(root, { withFileTypes: true })) {
|
|
3911
|
+
const path = join14(root, entry2.name);
|
|
3719
3912
|
if (entry2.isDirectory()) {
|
|
3720
3913
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
3721
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
3722
|
-
const bytes = redacted(
|
|
3914
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync8(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
3915
|
+
const bytes = redacted(readFileSync14(path));
|
|
3723
3916
|
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
3724
3917
|
}
|
|
3725
3918
|
}
|
|
@@ -3729,19 +3922,19 @@ function readLocalSkills(agents) {
|
|
|
3729
3922
|
const skills2 = [];
|
|
3730
3923
|
const ignored = [];
|
|
3731
3924
|
for (const agent2 of agents) {
|
|
3732
|
-
if (!
|
|
3733
|
-
for (const entry2 of
|
|
3734
|
-
const dir =
|
|
3925
|
+
if (!existsSync11(agent2.skillsDir)) continue;
|
|
3926
|
+
for (const entry2 of readdirSync7(agent2.skillsDir, { withFileTypes: true })) {
|
|
3927
|
+
const dir = join14(agent2.skillsDir, entry2.name);
|
|
3735
3928
|
let isDir2 = entry2.isDirectory();
|
|
3736
3929
|
if (entry2.isSymbolicLink()) {
|
|
3737
3930
|
try {
|
|
3738
|
-
isDir2 =
|
|
3931
|
+
isDir2 = statSync8(dir).isDirectory();
|
|
3739
3932
|
} catch {
|
|
3740
3933
|
isDir2 = false;
|
|
3741
3934
|
}
|
|
3742
3935
|
}
|
|
3743
|
-
if (!isDir2 || !
|
|
3744
|
-
const name = frontmatterName(
|
|
3936
|
+
if (!isDir2 || !existsSync11(join14(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
3937
|
+
const name = frontmatterName(readFileSync14(join14(dir, "SKILL.md"), "utf8"));
|
|
3745
3938
|
if (!name) {
|
|
3746
3939
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
3747
3940
|
continue;
|
|
@@ -3769,11 +3962,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
3769
3962
|
}
|
|
3770
3963
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
3771
3964
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
3772
|
-
const path = target.startsWith("~/") ?
|
|
3965
|
+
const path = target.startsWith("~/") ? join14(home, target.slice(2)) : isAbsolute2(target) ? target : resolve7(dirname7(from), target);
|
|
3773
3966
|
if (seen.includes(path)) return "";
|
|
3774
3967
|
let imported;
|
|
3775
3968
|
try {
|
|
3776
|
-
imported =
|
|
3969
|
+
imported = readFileSync14(path, "utf8");
|
|
3777
3970
|
} catch {
|
|
3778
3971
|
return line;
|
|
3779
3972
|
}
|
|
@@ -3781,15 +3974,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
3781
3974
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
3782
3975
|
}).join("\n");
|
|
3783
3976
|
}
|
|
3784
|
-
function readGlobalInstructions(agents, home =
|
|
3977
|
+
function readGlobalInstructions(agents, home = homedir9()) {
|
|
3785
3978
|
const found = [];
|
|
3786
3979
|
for (const agent2 of agents) {
|
|
3787
3980
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
3788
3981
|
if (!file) continue;
|
|
3789
|
-
const path =
|
|
3982
|
+
const path = join14(agent2.root, file);
|
|
3790
3983
|
let raw;
|
|
3791
3984
|
try {
|
|
3792
|
-
raw =
|
|
3985
|
+
raw = readFileSync14(path, "utf8");
|
|
3793
3986
|
} catch {
|
|
3794
3987
|
continue;
|
|
3795
3988
|
}
|
|
@@ -3808,7 +4001,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
3808
4001
|
}
|
|
3809
4002
|
function readJson3(path) {
|
|
3810
4003
|
try {
|
|
3811
|
-
return JSON.parse(
|
|
4004
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
3812
4005
|
} catch {
|
|
3813
4006
|
return void 0;
|
|
3814
4007
|
}
|
|
@@ -3822,15 +4015,15 @@ function bearerOf(def) {
|
|
|
3822
4015
|
return match?.[1] ?? null;
|
|
3823
4016
|
}
|
|
3824
4017
|
function serversOf(agent2, home) {
|
|
3825
|
-
if (agent2.id === "claude_code") return readJson3(
|
|
3826
|
-
if (agent2.id === "cursor") return readJson3(
|
|
4018
|
+
if (agent2.id === "claude_code") return readJson3(join14(home, ".claude.json"))?.mcpServers ?? {};
|
|
4019
|
+
if (agent2.id === "cursor") return readJson3(join14(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
3827
4020
|
try {
|
|
3828
|
-
return parseToml4(
|
|
4021
|
+
return parseToml4(readFileSync14(join14(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
3829
4022
|
} catch {
|
|
3830
4023
|
return {};
|
|
3831
4024
|
}
|
|
3832
4025
|
}
|
|
3833
|
-
function findLocalSecrets(agents, home =
|
|
4026
|
+
function findLocalSecrets(agents, home = homedir9()) {
|
|
3834
4027
|
const secrets2 = [];
|
|
3835
4028
|
for (const agent2 of agents) {
|
|
3836
4029
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -3847,9 +4040,9 @@ function findLocalSecrets(agents, home = homedir8()) {
|
|
|
3847
4040
|
|
|
3848
4041
|
// src/subscriptions.ts
|
|
3849
4042
|
import { spawn as spawn3 } from "child_process";
|
|
3850
|
-
import { existsSync as
|
|
3851
|
-
import { homedir as
|
|
3852
|
-
import { join as
|
|
4043
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
4044
|
+
import { homedir as homedir10 } from "os";
|
|
4045
|
+
import { join as join15 } from "path";
|
|
3853
4046
|
import { createInterface as createInterface2 } from "readline";
|
|
3854
4047
|
var ALIASES = {
|
|
3855
4048
|
claude: "anthropic",
|
|
@@ -3944,12 +4137,12 @@ async function connectClaude() {
|
|
|
3944
4137
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
3945
4138
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
3946
4139
|
}
|
|
3947
|
-
function codexSignIn(env = process.env, home =
|
|
3948
|
-
const path =
|
|
3949
|
-
if (!
|
|
4140
|
+
function codexSignIn(env = process.env, home = homedir10()) {
|
|
4141
|
+
const path = join15(env.CODEX_HOME || join15(home, ".codex"), "auth.json");
|
|
4142
|
+
if (!existsSync12(path)) {
|
|
3950
4143
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
3951
4144
|
}
|
|
3952
|
-
const raw =
|
|
4145
|
+
const raw = readFileSync15(path, "utf8");
|
|
3953
4146
|
try {
|
|
3954
4147
|
const parsed = JSON.parse(raw);
|
|
3955
4148
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -3957,12 +4150,12 @@ function codexSignIn(env = process.env, home = homedir9()) {
|
|
|
3957
4150
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
3958
4151
|
}
|
|
3959
4152
|
}
|
|
3960
|
-
function cursorSignIn(env = process.env, home =
|
|
3961
|
-
const path =
|
|
3962
|
-
if (!
|
|
4153
|
+
function cursorSignIn(env = process.env, home = homedir10()) {
|
|
4154
|
+
const path = join15(env.XDG_CONFIG_HOME || join15(home, ".config"), "cursor", "auth.json");
|
|
4155
|
+
if (!existsSync12(path)) {
|
|
3963
4156
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
3964
4157
|
}
|
|
3965
|
-
const raw =
|
|
4158
|
+
const raw = readFileSync15(path, "utf8");
|
|
3966
4159
|
try {
|
|
3967
4160
|
const parsed = JSON.parse(raw);
|
|
3968
4161
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -4123,11 +4316,11 @@ function planProviders(names2) {
|
|
|
4123
4316
|
}));
|
|
4124
4317
|
}
|
|
4125
4318
|
function reportPath() {
|
|
4126
|
-
return
|
|
4319
|
+
return join16(dirname8(configPath()), "setup.json");
|
|
4127
4320
|
}
|
|
4128
4321
|
function loadReport() {
|
|
4129
4322
|
try {
|
|
4130
|
-
const saved = JSON.parse(
|
|
4323
|
+
const saved = JSON.parse(readFileSync16(reportPath(), "utf8"));
|
|
4131
4324
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
4132
4325
|
} catch {
|
|
4133
4326
|
return { agents: [], plans: [] };
|
|
@@ -4140,8 +4333,8 @@ function mergeReport(base, next) {
|
|
|
4140
4333
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
4141
4334
|
}
|
|
4142
4335
|
function saveReport(report) {
|
|
4143
|
-
|
|
4144
|
-
|
|
4336
|
+
mkdirSync5(dirname8(reportPath()), { recursive: true });
|
|
4337
|
+
writeFileSync8(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
4145
4338
|
`, { mode: 384 });
|
|
4146
4339
|
}
|
|
4147
4340
|
function confirm3(question) {
|
|
@@ -4511,71 +4704,10 @@ function renderArtifact(artifact2) {
|
|
|
4511
4704
|
return `${artifact2.id} ${artifact2.name} v${artifact2.version} ${files2} entry ${artifact2.entry}${artifact2.share_url ? ` shared ${artifact2.share_url}` : ""}`;
|
|
4512
4705
|
}
|
|
4513
4706
|
|
|
4514
|
-
// src/skills-store.ts
|
|
4515
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync8 } from "fs";
|
|
4516
|
-
import { basename as basename8, join as join16, relative as relative4, resolve as resolve7 } from "path";
|
|
4517
|
-
function skillBundle(dir) {
|
|
4518
|
-
const root = resolve7(dir);
|
|
4519
|
-
const top = basename8(root);
|
|
4520
|
-
const files2 = [];
|
|
4521
|
-
const walk = (path) => {
|
|
4522
|
-
for (const name of readdirSync7(path).sort()) {
|
|
4523
|
-
const full = join16(path, name);
|
|
4524
|
-
if (statSync8(full).isDirectory()) walk(full);
|
|
4525
|
-
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync15(full) });
|
|
4526
|
-
}
|
|
4527
|
-
};
|
|
4528
|
-
walk(root);
|
|
4529
|
-
if (!files2.some((file) => file.path === `${top}/SKILL.md`)) throw new Error(`${dir}: no SKILL.md at its root`);
|
|
4530
|
-
return files2;
|
|
4531
|
-
}
|
|
4532
|
-
async function uploadSkill(dir, displayName) {
|
|
4533
|
-
const form = new FormData();
|
|
4534
|
-
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
4535
|
-
form.append("displayName", displayName ?? basename8(resolve7(dir)));
|
|
4536
|
-
return apiForm("POST", "/skills", form);
|
|
4537
|
-
}
|
|
4538
|
-
async function listSkills() {
|
|
4539
|
-
return (await api("GET", "/skills")).skills;
|
|
4540
|
-
}
|
|
4541
|
-
async function getTask(task2) {
|
|
4542
|
-
return (await api("GET", `/tasks/${encodeURIComponent(task2)}`)).task;
|
|
4543
|
-
}
|
|
4544
|
-
function answerBody(answer) {
|
|
4545
|
-
if (!answer.option === !answer.text) throw new Error("give exactly one of --option or --text");
|
|
4546
|
-
return { ...answer.option ? { option_id: answer.option } : { text: answer.text }, ...answer.note ? { note: answer.note } : {} };
|
|
4547
|
-
}
|
|
4548
|
-
async function answerRequest(task2, request, answer) {
|
|
4549
|
-
return api("POST", `/tasks/${encodeURIComponent(task2)}/requests/${encodeURIComponent(request)}/answer`, answerBody(answer));
|
|
4550
|
-
}
|
|
4551
|
-
function renderRequest(request) {
|
|
4552
|
-
const options = request.options.length ? `
|
|
4553
|
-
options: ${request.options.map((option) => `${option.id} (${option.label})`).join(", ")}` : "";
|
|
4554
|
-
return `${request.id} ${request.kind} from ${request.session_id}
|
|
4555
|
-
${request.question}${options}`;
|
|
4556
|
-
}
|
|
4557
|
-
async function getSkillUsage(since) {
|
|
4558
|
-
const qs = since ? `?since=${encodeURIComponent(since)}` : "";
|
|
4559
|
-
return api("GET", `/skills/usage${qs}`);
|
|
4560
|
-
}
|
|
4561
|
-
function nextSkills(current, skillId, verb) {
|
|
4562
|
-
const has = current.some((ref) => ref.type === "skill" && ref.skillId === skillId);
|
|
4563
|
-
if (verb === "attach") return has ? null : [...current, { type: "skill", skillId, version: "latest" }];
|
|
4564
|
-
return has ? current.filter((ref) => !(ref.type === "skill" && ref.skillId === skillId)) : null;
|
|
4565
|
-
}
|
|
4566
|
-
async function setAgentSkill(agentId, skillId, verb) {
|
|
4567
|
-
const path = `/agents/${encodeURIComponent(agentId)}`;
|
|
4568
|
-
const { agent: agent2 } = await api("GET", path);
|
|
4569
|
-
const skills2 = nextSkills(agent2.skills ?? [], skillId, verb);
|
|
4570
|
-
if (!skills2) return "unchanged";
|
|
4571
|
-
await api("PATCH", path, { skills: skills2, expectedVersion: agent2.version });
|
|
4572
|
-
return "changed";
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
4707
|
// src/task-export.ts
|
|
4576
4708
|
import { createHash as createHash6 } from "crypto";
|
|
4577
|
-
import { mkdirSync as
|
|
4578
|
-
import { dirname as
|
|
4709
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
4710
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
4579
4711
|
import { stringify as stringifyToml } from "smol-toml";
|
|
4580
4712
|
var TEXT_MAX = 2e4;
|
|
4581
4713
|
function scrub(text, names2) {
|
|
@@ -4866,8 +4998,8 @@ async function exportTaskHarbor(taskRef, outDir, opts) {
|
|
|
4866
4998
|
const written2 = [];
|
|
4867
4999
|
for (const [path, content] of files2) {
|
|
4868
5000
|
const target = join17(outDir, path);
|
|
4869
|
-
|
|
4870
|
-
|
|
5001
|
+
mkdirSync6(dirname9(target), { recursive: true });
|
|
5002
|
+
writeFileSync9(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
|
|
4871
5003
|
written2.push(path);
|
|
4872
5004
|
}
|
|
4873
5005
|
return { written: written2, record };
|
|
@@ -4881,7 +5013,7 @@ function parseRedactNames(values) {
|
|
|
4881
5013
|
}
|
|
4882
5014
|
|
|
4883
5015
|
// src/task-record.ts
|
|
4884
|
-
import { randomUUID as
|
|
5016
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4885
5017
|
var DECISION_KINDS = ["adopt", "rerun", "revise", "discard", "vote", "did_the_job", "approve"];
|
|
4886
5018
|
async function listThreads(taskRef) {
|
|
4887
5019
|
return (await api("GET", `/sessions/${encodeURIComponent(taskRef)}/threads`)).threads;
|
|
@@ -4894,7 +5026,7 @@ function renderThread(thread2) {
|
|
|
4894
5026
|
async function recordDecision(sessionRef, kind, opts = {}) {
|
|
4895
5027
|
if (!DECISION_KINDS.includes(kind)) throw new Error(`kind must be one of ${DECISION_KINDS.join(", ")}`);
|
|
4896
5028
|
if ((kind === "did_the_job" || kind === "approve") && typeof opts.value !== "boolean") throw new Error(`${kind} needs --yes or --no`);
|
|
4897
|
-
const key = opts.idempotencyKey ??
|
|
5029
|
+
const key = opts.idempotencyKey ?? randomUUID3();
|
|
4898
5030
|
const res = await fetchRecord(sessionRef, {
|
|
4899
5031
|
type: "human.decision",
|
|
4900
5032
|
payload: {
|
|
@@ -4910,7 +5042,7 @@ async function recordDecision(sessionRef, kind, opts = {}) {
|
|
|
4910
5042
|
var OUTCOME_KINDS = ["pr_merged", "deployed", "reopened", "later_failure", "shipped"];
|
|
4911
5043
|
async function recordOutcome(sessionRef, kind, opts = {}) {
|
|
4912
5044
|
if (!OUTCOME_KINDS.includes(kind)) throw new Error(`kind must be one of ${OUTCOME_KINDS.join(", ")}`);
|
|
4913
|
-
return fetchRecord(sessionRef, { type: "outcome.observed", payload: { kind, ...opts.ref ? { ref: opts.ref } : {}, observer: opts.observer ?? "cli" } }, opts.idempotencyKey ??
|
|
5045
|
+
return fetchRecord(sessionRef, { type: "outcome.observed", payload: { kind, ...opts.ref ? { ref: opts.ref } : {}, observer: opts.observer ?? "cli" } }, opts.idempotencyKey ?? randomUUID3());
|
|
4914
5046
|
}
|
|
4915
5047
|
async function fetchRecord(sessionRef, body, idempotencyKey) {
|
|
4916
5048
|
const result = await api("POST", `/sessions/${encodeURIComponent(sessionRef)}/record`, body, { idempotencyKey });
|
|
@@ -5359,14 +5491,14 @@ function renderThreadSummary(t) {
|
|
|
5359
5491
|
}
|
|
5360
5492
|
|
|
5361
5493
|
// src/library.ts
|
|
5362
|
-
import { readFileSync as
|
|
5494
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
5363
5495
|
|
|
5364
5496
|
// src/api-command.ts
|
|
5365
|
-
import { readFileSync as
|
|
5497
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
5366
5498
|
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
5367
|
-
function readData(data, stdin = () =>
|
|
5499
|
+
function readData(data, stdin = () => readFileSync17(0, "utf8")) {
|
|
5368
5500
|
if (data === void 0) return void 0;
|
|
5369
|
-
const raw = data === "-" ? stdin() : data.startsWith("@") ?
|
|
5501
|
+
const raw = data === "-" ? stdin() : data.startsWith("@") ? readFileSync17(data.slice(1), "utf8") : data;
|
|
5370
5502
|
try {
|
|
5371
5503
|
JSON.parse(raw);
|
|
5372
5504
|
} catch {
|
|
@@ -5572,7 +5704,7 @@ function registerLibraryLinkCommands(library2) {
|
|
|
5572
5704
|
});
|
|
5573
5705
|
libraryLinks.command("save").description("save through a live recipient edit grant; --revision is the opaque source revision from links read").requiredOption("--token <token>", "43-character token from the authenticated shared URL (public URLs have no writer)").requiredOption("--revision <revision>", "native source revision from links read; stale revisions fail without retry").option("--file <path>", "UTF-8 text file to save").option("--data <json>", "instead of --file, JSON/ @file / - containing exactly one text/html/office/docx/restoreRevision and optional requestId").action(async (opts) => {
|
|
5574
5706
|
if (opts.file !== void 0 === (opts.data !== void 0)) throw new Error("Choose exactly one of --file or --data.");
|
|
5575
|
-
const body = opts.file !== void 0 ? { text: new TextDecoder("utf-8", { fatal: true }).decode(
|
|
5707
|
+
const body = opts.file !== void 0 ? { text: new TextDecoder("utf-8", { fatal: true }).decode(readFileSync18(opts.file)) } : JSON.parse(readData(opts.data));
|
|
5576
5708
|
if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("--data must be a JSON object with one native save field.");
|
|
5577
5709
|
if ("revision" in body && body.revision !== opts.revision) throw new Error("--data revision must match --revision.");
|
|
5578
5710
|
console.log(JSON.stringify(await saveSharedLibraryFile(opts.token, sharedLibrarySaveSchema.parse({ ...body, revision: opts.revision })), null, 2));
|
|
@@ -5615,7 +5747,7 @@ function renderTodo(todo) {
|
|
|
5615
5747
|
}
|
|
5616
5748
|
|
|
5617
5749
|
// src/design-systems.ts
|
|
5618
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
5750
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
|
|
5619
5751
|
import { join as join18, relative as relative5, sep as sep3 } from "path";
|
|
5620
5752
|
var TEXT2 = /\.(mdx?|markdown|txt|json|m?js|cjs|jsx|tsx?|css|html?|svg|xml|csv|ya?ml|toml)$/i;
|
|
5621
5753
|
var MEDIA = {
|
|
@@ -5666,7 +5798,7 @@ function fileTableFrom(dir) {
|
|
|
5666
5798
|
continue;
|
|
5667
5799
|
}
|
|
5668
5800
|
const key = relative5(dir, path).split(sep3).join("/");
|
|
5669
|
-
const bytes =
|
|
5801
|
+
const bytes = readFileSync19(path);
|
|
5670
5802
|
files2[key] = TEXT2.test(name) ? bytes.toString("utf8") : `data:${MEDIA[name.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream"};base64,${bytes.toString("base64")}`;
|
|
5671
5803
|
}
|
|
5672
5804
|
};
|
|
@@ -5775,19 +5907,19 @@ function workspaceFlagIsCommands(argv, commandPath) {
|
|
|
5775
5907
|
}
|
|
5776
5908
|
|
|
5777
5909
|
// src/harness.ts
|
|
5778
|
-
import { existsSync as
|
|
5779
|
-
import { dirname as
|
|
5910
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
|
|
5911
|
+
import { dirname as dirname10, join as join19, resolve as resolve8 } from "path";
|
|
5780
5912
|
var HARNESS_FILE = "harness.json";
|
|
5781
5913
|
function harnessFilePath(dir) {
|
|
5782
5914
|
return join19(resolve8(dir), HARNESS_FILE);
|
|
5783
5915
|
}
|
|
5784
5916
|
function readHarnessFile(dir) {
|
|
5785
5917
|
const path = harnessFilePath(dir);
|
|
5786
|
-
if (!
|
|
5918
|
+
if (!existsSync13(path)) {
|
|
5787
5919
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
5788
5920
|
}
|
|
5789
5921
|
try {
|
|
5790
|
-
return JSON.parse(
|
|
5922
|
+
return JSON.parse(readFileSync20(path, "utf8"));
|
|
5791
5923
|
} catch (err) {
|
|
5792
5924
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5793
5925
|
}
|
|
@@ -5814,11 +5946,11 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
5814
5946
|
`;
|
|
5815
5947
|
function harnessInit(dir) {
|
|
5816
5948
|
const path = harnessFilePath(dir);
|
|
5817
|
-
if (
|
|
5949
|
+
if (existsSync13(path)) {
|
|
5818
5950
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
5819
5951
|
}
|
|
5820
|
-
|
|
5821
|
-
|
|
5952
|
+
mkdirSync7(dirname10(path), { recursive: true });
|
|
5953
|
+
writeFileSync10(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
5822
5954
|
`);
|
|
5823
5955
|
console.log(`Created ${path}`);
|
|
5824
5956
|
console.log(INIT_COMMENT);
|
|
@@ -6575,6 +6707,14 @@ session.command("handoff <ref> <targetAgent>").description("hand the session's c
|
|
|
6575
6707
|
if (done.target_session) console.log(`${done.target_session} (${done.resolved_mode === "native_fork" ? "forked" : "from a written summary"})`);
|
|
6576
6708
|
})
|
|
6577
6709
|
);
|
|
6710
|
+
session.command("continue-here <ref>").description("move an idle Claude Code or Codex session you own onto this computer and print how to resume it; the cloud session then takes no more turns").option("--dir <folder>", "where the session's workspace goes", ".").action(
|
|
6711
|
+
run(async (ref, opts) => {
|
|
6712
|
+
const id = await resolveSessionRef(ref);
|
|
6713
|
+
const moved = await continueHere(id, opts.dir, { onStatus: (h) => console.error(`${h.id}: ${h.status}`) });
|
|
6714
|
+
if (!moved.workspaceCarried) console.error("The workspace was not copied with this move; copy what you need with sky session workspace ls and cat.");
|
|
6715
|
+
console.log(moved.resume);
|
|
6716
|
+
})
|
|
6717
|
+
);
|
|
6578
6718
|
session.command("cloud-copy <ref>").description("what is kept in the cloud for a chat synced from your own computer: its working folder and native session").option("--json", "print the raw answer").action(
|
|
6579
6719
|
run(async (ref, opts) => {
|
|
6580
6720
|
const id = await resolveSessionRef(ref);
|
|
@@ -6608,7 +6748,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
|
|
|
6608
6748
|
run(async (ref, path, opts) => {
|
|
6609
6749
|
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
6610
6750
|
if (opts.out) {
|
|
6611
|
-
|
|
6751
|
+
writeFileSync11(opts.out, bytes);
|
|
6612
6752
|
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
6613
6753
|
} else process.stdout.write(bytes);
|
|
6614
6754
|
})
|
|
@@ -6625,7 +6765,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
|
|
|
6625
6765
|
console.error(`${meta.repo}: ${meta.commits.length} commit${meta.commits.length === 1 ? "" : "s"} since ${meta.base_branch || "the imported copy"} (${meta.base_commit.slice(0, 12)})`);
|
|
6626
6766
|
if (!patch.length) console.error("nothing changed");
|
|
6627
6767
|
else if (opts.out) {
|
|
6628
|
-
|
|
6768
|
+
writeFileSync11(opts.out, patch);
|
|
6629
6769
|
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
6630
6770
|
} else process.stdout.write(patch);
|
|
6631
6771
|
})
|
|
@@ -6859,7 +6999,7 @@ async function launchFromFile(path, spec, opts) {
|
|
|
6859
6999
|
program.command("launch").description("scaffold ./agent.toml if absent, create the agent and its first session (idle until the first message), print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").option("--start", "boot the first session now instead of on the first message").action(
|
|
6860
7000
|
run(async (opts) => {
|
|
6861
7001
|
const path = resolve9(opts.file);
|
|
6862
|
-
if (!
|
|
7002
|
+
if (!existsSync14(path)) {
|
|
6863
7003
|
const name = basename9(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
6864
7004
|
scaffoldAgentFile(path, name);
|
|
6865
7005
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
@@ -7130,14 +7270,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
7130
7270
|
run(async (taskId, path, opts) => {
|
|
7131
7271
|
const bytes = await readTaskFile(taskId, path);
|
|
7132
7272
|
if (opts.out) {
|
|
7133
|
-
|
|
7273
|
+
writeFileSync11(opts.out, bytes);
|
|
7134
7274
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
7135
7275
|
} else process.stdout.write(bytes);
|
|
7136
7276
|
})
|
|
7137
7277
|
);
|
|
7138
7278
|
task.command("put <task> <path> <local>").description("write a local file to <path> in the task's files (every session of the task sees it)").action(
|
|
7139
7279
|
run(async (taskId, path, local) => {
|
|
7140
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
7280
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync21(local), mediaTypeFor(local));
|
|
7141
7281
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
7142
7282
|
})
|
|
7143
7283
|
);
|
|
@@ -7375,7 +7515,7 @@ library.command("list").description("your Library, newest first").option("--sear
|
|
|
7375
7515
|
);
|
|
7376
7516
|
library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
|
|
7377
7517
|
run(async (path, opts) => {
|
|
7378
|
-
const bytes =
|
|
7518
|
+
const bytes = readFileSync21(path);
|
|
7379
7519
|
const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
|
|
7380
7520
|
console.log(renderLibraryArtifact(artifact2));
|
|
7381
7521
|
})
|
|
@@ -7406,11 +7546,12 @@ library.command("place <id> <workspace>").description('place your artifact in a
|
|
|
7406
7546
|
library.command("download <id> <out>").description("save one version's bytes to a file").option("--version <n>", "a saved version (the current one when omitted)").action(
|
|
7407
7547
|
run(async (id, out, opts) => {
|
|
7408
7548
|
const { bytes } = await downloadLibraryArtifact(id, opts.version ? Number(opts.version) : void 0);
|
|
7409
|
-
|
|
7549
|
+
writeFileSync11(out, bytes);
|
|
7410
7550
|
console.log(`wrote ${bytes.length} bytes to ${out}`);
|
|
7411
7551
|
})
|
|
7412
7552
|
);
|
|
7413
7553
|
registerLibraryLinkCommands(library);
|
|
7554
|
+
registerLibrarySkillCommands(library);
|
|
7414
7555
|
var todos = program.command("todos").description("to-dos: the checklist items of your Library documents (- [ ] text @who YYYY-MM-DD)");
|
|
7415
7556
|
todos.command("list").description("open to-dos assigned to you, or unassigned in your documents").option("--workspace", "every to-do in documents you can read, not only yours").option("--status <status>", "open, done or all", "open").option("--due <when>", "any, overdue, today, week or none", "any").option("--source <artifactId>", "only this document's to-dos").option("--json", "print as JSON").action(
|
|
7416
7557
|
run(async (opts) => {
|
|
@@ -7449,7 +7590,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
7449
7590
|
run(async (id, path, opts) => {
|
|
7450
7591
|
const bytes = await readArtifactFile(id, path);
|
|
7451
7592
|
if (opts.out) {
|
|
7452
|
-
|
|
7593
|
+
writeFileSync11(opts.out, bytes);
|
|
7453
7594
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
7454
7595
|
} else process.stdout.write(bytes);
|
|
7455
7596
|
})
|
|
@@ -7487,14 +7628,14 @@ designSystem.command("import <task> <folder>").description("import a local desig
|
|
|
7487
7628
|
);
|
|
7488
7629
|
designSystem.command("tokens <id> <file>").description("save a tokens.json file as the system's next version; --base is the version you edited").requiredOption("--base <version>", "the version the file was edited from", (value) => Number(value)).option("--note <note>", "one line saying what changed").action(
|
|
7489
7630
|
run(async (id, file, opts) => {
|
|
7490
|
-
const tokens = JSON.parse(
|
|
7631
|
+
const tokens = JSON.parse(readFileSync21(resolve9(file), "utf8"));
|
|
7491
7632
|
console.log(renderArtifact(await saveDesignTokens(id, opts.base, tokens, opts.note)));
|
|
7492
7633
|
})
|
|
7493
7634
|
);
|
|
7494
7635
|
designSystem.command("write <id> <path> [file]").description("save one of the system's editable files (path such as project/README.md) from a local file as its next version, or put back an earlier version's copy with --restore").requiredOption("--base <version>", "the version the file was edited from", (value) => Number(value)).option("--restore <version>", "restore the file as it was in this version instead of reading [file]", (value) => Number(value)).option("--note <note>", "one line saying what changed").action(
|
|
7495
7636
|
run(async (id, path, file, opts) => {
|
|
7496
7637
|
if (file === void 0 === (opts.restore === void 0)) throw Error("Give a local [file] to save, or --restore <version>.");
|
|
7497
|
-
const change = opts.restore === void 0 ? { content:
|
|
7638
|
+
const change = opts.restore === void 0 ? { content: readFileSync21(resolve9(file), "utf8") } : { restore_version: opts.restore };
|
|
7498
7639
|
console.log(renderArtifact(await saveDesignSystemFile(id, path, opts.base, change, opts.note)));
|
|
7499
7640
|
})
|
|
7500
7641
|
);
|
|
@@ -7807,7 +7948,7 @@ usageOptions(
|
|
|
7807
7948
|
run(async (opts) => {
|
|
7808
7949
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
7809
7950
|
if (opts.out) {
|
|
7810
|
-
|
|
7951
|
+
writeFileSync11(opts.out, bytes);
|
|
7811
7952
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
7812
7953
|
} else {
|
|
7813
7954
|
process.stdout.write(bytes);
|
|
@@ -8429,7 +8570,7 @@ notes.command("read <storeId> <noteId>").description("print one note").action(
|
|
|
8429
8570
|
notes.command("save <storeId> <file>").description("write a note from a local file: --path creates one, --note saves a new version of one").option("--path <path>", "path of a new note, e.g. /projects/launch.md").option("--note <noteId>", "the note to update").action(
|
|
8430
8571
|
run(async (storeId, file, opts) => {
|
|
8431
8572
|
if (!opts.path === !opts.note) throw new Error("pass exactly one of --path (new note) or --note (update)");
|
|
8432
|
-
const content =
|
|
8573
|
+
const content = readFileSync21(file, "utf8");
|
|
8433
8574
|
const target = `/memory_stores/${encodeURIComponent(storeId)}/memories${opts.note ? `/${encodeURIComponent(opts.note)}` : ""}`;
|
|
8434
8575
|
const { memory: memory2 } = await api("POST", target, { content, ...opts.path ? { path: opts.path } : {} });
|
|
8435
8576
|
console.log(`Saved ${memory2.path} (${memory2.id})`);
|
|
@@ -8529,7 +8670,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
8529
8670
|
}));
|
|
8530
8671
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
8531
8672
|
traceCmd.command("start").description("start a trace: a grant for this machine, a root span, and the spool the hooks write to").option("--name <name>", "a name for the task").option("--source <source>", "how this machine reports itself", "claude-code").option("--hooks [settings]", "also install the Claude Code hooks (default: ./.claude/settings.local.json)").option("--capture <scopes>", "also send content, redacted on this machine first: any of prompts,tool_output,files").option("--file <path>", "a file the files scope may send (repeatable)", collect).option("--dir <path>", "a folder whose files the files scope may send when a file tool touches them (repeatable)", collect).action(run(async (opts) => {
|
|
8532
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
8673
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-2SAGMFYJ.js");
|
|
8533
8674
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
8534
8675
|
const scopes = parseContentScopes(opts.capture);
|
|
8535
8676
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -8564,7 +8705,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
8564
8705
|
}));
|
|
8565
8706
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
8566
8707
|
try {
|
|
8567
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
8708
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-2SAGMFYJ.js");
|
|
8568
8709
|
const chunks = [];
|
|
8569
8710
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
8570
8711
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -8577,14 +8718,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
8577
8718
|
}
|
|
8578
8719
|
});
|
|
8579
8720
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
8580
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
8721
|
+
const { readState, flush, grantSender } = await import("./trace-2SAGMFYJ.js");
|
|
8581
8722
|
const state = readState();
|
|
8582
8723
|
if (!state) throw new Error("no trace is running here");
|
|
8583
8724
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
8584
8725
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
8585
8726
|
}));
|
|
8586
8727
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
8587
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
8728
|
+
const { readState, spoolStatus } = await import("./trace-2SAGMFYJ.js");
|
|
8588
8729
|
const state = readState();
|
|
8589
8730
|
if (!state) {
|
|
8590
8731
|
console.log("No trace is running here.");
|
|
@@ -8594,7 +8735,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
8594
8735
|
console.log(`${state.trace_id} as ${state.source}: ${spool.pending} pending, ${spool.dropped_full} dropped (spool full), ${spool.dropped_expired} given up, ${spool.refused} refused`);
|
|
8595
8736
|
}));
|
|
8596
8737
|
traceCmd.command("show [id]").description("the trace bundle: spans, the sessions they reach, cost counted once per session").option("--json", "print the bundle as JSON").action(run(async (id, opts) => {
|
|
8597
|
-
const { readState } = await import("./trace-
|
|
8738
|
+
const { readState } = await import("./trace-2SAGMFYJ.js");
|
|
8598
8739
|
const ref = id ?? readState()?.trace_id;
|
|
8599
8740
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
8600
8741
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -8621,7 +8762,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
8621
8762
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
8622
8763
|
}));
|
|
8623
8764
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
8624
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
8765
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-2SAGMFYJ.js");
|
|
8625
8766
|
const state = readState();
|
|
8626
8767
|
if (!state) throw new Error("no trace is running here");
|
|
8627
8768
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -8631,7 +8772,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
8631
8772
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
8632
8773
|
}));
|
|
8633
8774
|
traceCmd.command("sync [payload]").description("upload this machine's agent sessions (Claude Code, Codex, Cursor, Gemini CLI and every other agent `--agents` lists) to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--import <export>", "add a downloaded cloud export (claude.ai, ChatGPT, Gemini, Grok, DeepSeek \u2026: its .zip, folder or file) so sync uploads it (repeatable)", collect).option("--agents", "list the agents sync reads and which are on this machine").option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
|
|
8634
|
-
const lib = await import("./trace-library-
|
|
8775
|
+
const lib = await import("./trace-library-RDJEEW7O.js");
|
|
8635
8776
|
if (opts.hook) {
|
|
8636
8777
|
try {
|
|
8637
8778
|
let raw = payload ?? "";
|
|
@@ -8691,9 +8832,9 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
8691
8832
|
}
|
|
8692
8833
|
if (opts.installSchedule) {
|
|
8693
8834
|
const { join: join20 } = await import("path");
|
|
8694
|
-
const { homedir:
|
|
8835
|
+
const { homedir: homedir11 } = await import("os");
|
|
8695
8836
|
const { execFileSync } = await import("child_process");
|
|
8696
|
-
const log = join20(
|
|
8837
|
+
const log = join20(homedir11(), ".config", "sky", "trace-sync.log");
|
|
8697
8838
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
8698
8839
|
try {
|
|
8699
8840
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -8703,11 +8844,11 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
8703
8844
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
8704
8845
|
}
|
|
8705
8846
|
if (opts.status) {
|
|
8706
|
-
const { readFileSync:
|
|
8847
|
+
const { readFileSync: readFileSync22 } = await import("fs");
|
|
8707
8848
|
const { join: join20 } = await import("path");
|
|
8708
8849
|
let state = { sessions: {} };
|
|
8709
8850
|
try {
|
|
8710
|
-
state = JSON.parse(
|
|
8851
|
+
state = JSON.parse(readFileSync22(join20(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
8711
8852
|
} catch {
|
|
8712
8853
|
}
|
|
8713
8854
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -8746,7 +8887,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
8746
8887
|
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
8747
8888
|
}));
|
|
8748
8889
|
traceCmd.command("library").description("list the sessions in a workspace's trace library, most recently updated first").option("--workspace <slug>", "the workspace (default: the current one)").option("--limit <n>", "how many", (v) => Number.parseInt(v, 10), 20).option("--json", "print the page as JSON").action(run(async (opts) => {
|
|
8749
|
-
const { libraryRequest } = await import("./trace-library-
|
|
8890
|
+
const { libraryRequest } = await import("./trace-library-RDJEEW7O.js");
|
|
8750
8891
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
8751
8892
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
8752
8893
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
@@ -8850,6 +8991,7 @@ compare.command("pick <id>").description('record the winning column \u2014 or a
|
|
|
8850
8991
|
})
|
|
8851
8992
|
);
|
|
8852
8993
|
var thread = program.command("thread").description("workspace threads: the conversation history across all agents");
|
|
8994
|
+
registerExperienceThreadCommand(thread);
|
|
8853
8995
|
thread.command("list").description("your workspace threads, most recently updated first (includes comparisons)").option("--archived", "list archived threads only").option("--all", "include both active and archived").option("--json", "print as JSON").action(
|
|
8854
8996
|
run(async (opts) => {
|
|
8855
8997
|
const filter = opts.all ? "all" : opts.archived ? "archived" : "active";
|