@agentsky/sky 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ApiError,
4
+ api,
5
+ apiBytes,
6
+ apiForm,
7
+ downloadFile,
8
+ postEvents,
9
+ resolveSessionRef,
10
+ streamEvents,
11
+ uploadFile
12
+ } from "./chunk-XJ5JLLXM.js";
2
13
  import {
3
14
  CLI_VERSION,
4
15
  USER_AGENT,
@@ -9,12 +20,13 @@ import {
9
20
  currentUniverse,
10
21
  loadConfig,
11
22
  saveConfig
12
- } from "./chunk-RWPPIEHH.js";
23
+ } from "./chunk-Z3FPPGWX.js";
13
24
  import {
14
25
  AGENT_TYPES,
15
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
16
27
  MODEL_SUBSCRIPTION_PROVIDER_NAME,
17
28
  REASONING_EFFORT_VALUES,
29
+ SessionSourceCollector,
18
30
  capabilitiesSchema,
19
31
  customDataSchema,
20
32
  defaultLlm,
@@ -28,165 +40,15 @@ import {
28
40
  skillRefSchema,
29
41
  splitMcpServers,
30
42
  supersededBy
31
- } from "./chunk-WC7GJSAJ.js";
43
+ } from "./chunk-2IKEJ74C.js";
32
44
  import "./chunk-MLKGABMK.js";
33
45
 
34
46
  // src/index.ts
35
- import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
36
- import { basename as basename4, resolve as resolve6 } from "path";
47
+ import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
48
+ import { basename as basename5, resolve as resolve7 } from "path";
37
49
  import { createInterface as createInterface4 } from "readline";
38
50
  import { Command } from "commander";
39
51
 
40
- // src/api.ts
41
- var ApiError = class extends Error {
42
- constructor(status, code, message2) {
43
- super(message2);
44
- this.status = status;
45
- this.code = code;
46
- }
47
- status;
48
- code;
49
- };
50
- function authHeaders() {
51
- const token = apiToken();
52
- if (!token) {
53
- throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
54
- }
55
- const headers = { authorization: `Bearer ${token}` };
56
- const universe2 = currentUniverse();
57
- if (universe2) headers["x-universe"] = universe2;
58
- return headers;
59
- }
60
- async function parseError(res) {
61
- let code = "http_error";
62
- let message2 = `${res.status} ${res.statusText}`;
63
- try {
64
- const body = await res.json();
65
- if (body.error?.message) {
66
- code = body.error.code ?? code;
67
- message2 = body.error.message;
68
- }
69
- } catch {
70
- }
71
- throw new ApiError(res.status, code, message2);
72
- }
73
- async function api(method, path, body, opts = {}) {
74
- const res = await fetch(`${apiBase()}${path}`, {
75
- method,
76
- headers: {
77
- "user-agent": USER_AGENT,
78
- ...opts.auth === false ? {} : authHeaders(),
79
- ...body !== void 0 ? { "content-type": "application/json" } : {},
80
- ...opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {}
81
- },
82
- body: body !== void 0 ? JSON.stringify(body) : void 0
83
- });
84
- if (!res.ok) await parseError(res);
85
- if (res.status === 204) return void 0;
86
- return await res.json();
87
- }
88
- async function postEvents(sessionId, events, idempotencyKey) {
89
- const res = await fetch(`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/events`, {
90
- method: "POST",
91
- headers: {
92
- "user-agent": USER_AGENT,
93
- ...authHeaders(),
94
- "content-type": "application/json",
95
- ...idempotencyKey ? { "idempotency-key": idempotencyKey } : {}
96
- },
97
- body: JSON.stringify({ events })
98
- });
99
- if (!res.ok) await parseError(res);
100
- return await res.json();
101
- }
102
- async function uploadFile(bytes, filename, mediaType, expiresInSeconds) {
103
- const form = new FormData();
104
- form.append("file", new Blob([new Uint8Array(bytes)], { type: mediaType }), filename);
105
- if (expiresInSeconds) form.append("expires_in_seconds", String(expiresInSeconds));
106
- const res = await fetch(`${apiBase()}/files`, {
107
- method: "POST",
108
- headers: { "user-agent": USER_AGENT, ...authHeaders() },
109
- body: form
110
- });
111
- if (!res.ok) await parseError(res);
112
- return await res.json();
113
- }
114
- async function apiForm(method, path, form) {
115
- const res = await fetch(`${apiBase()}${path}`, {
116
- method,
117
- headers: { "user-agent": USER_AGENT, ...authHeaders() },
118
- body: form
119
- });
120
- if (!res.ok) await parseError(res);
121
- return await res.json();
122
- }
123
- async function downloadFile(fileId) {
124
- const res = await fetch(`${apiBase()}/files/${encodeURIComponent(fileId)}/content`, {
125
- headers: { "user-agent": USER_AGENT, ...authHeaders() }
126
- });
127
- if (!res.ok) await parseError(res);
128
- return { bytes: new Uint8Array(await res.arrayBuffer()), mediaType: res.headers.get("content-type") ?? "application/octet-stream" };
129
- }
130
- async function apiBytes(method, path, body) {
131
- const res = await fetch(`${apiBase()}${path}`, {
132
- method,
133
- headers: {
134
- "user-agent": USER_AGENT,
135
- ...authHeaders(),
136
- ...body ? { "content-type": body.mediaType } : {}
137
- },
138
- body: body ? new Uint8Array(body.bytes) : void 0
139
- });
140
- if (!res.ok) await parseError(res);
141
- const bytes = new Uint8Array(await res.arrayBuffer());
142
- return {
143
- bytes,
144
- mediaType: res.headers.get("content-type") ?? "application/octet-stream",
145
- json: () => JSON.parse(new TextDecoder().decode(bytes))
146
- };
147
- }
148
- async function resolveSessionRef(ref) {
149
- if (ref.startsWith("sess-")) return ref;
150
- const { agent: agent2 } = await api(
151
- "GET",
152
- `/agents/${encodeURIComponent(ref)}`
153
- ).catch(async () => {
154
- await api("GET", `/sessions/${encodeURIComponent(ref)}`);
155
- return { agent: { id: ref, defaultSessionId: ref } };
156
- });
157
- if (!agent2.defaultSessionId) {
158
- throw new ApiError(
159
- 404,
160
- "no_sessions",
161
- `agent ${agent2.id} has no sessions \u2014 create one: sky session create ${agent2.id}`
162
- );
163
- }
164
- return agent2.defaultSessionId;
165
- }
166
- async function* streamEvents(sessionId, signal) {
167
- const { parseSseStream } = await import("./src-HVCP3PPM.js");
168
- const res = await fetch(
169
- `${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
170
- {
171
- headers: { "user-agent": USER_AGENT, ...authHeaders(), accept: "text/event-stream" },
172
- signal
173
- }
174
- );
175
- if (!res.ok) await parseError(res);
176
- if (!res.body) throw new ApiError(502, "no_body", "stream returned no body");
177
- try {
178
- for await (const frame of parseSseStream(res.body)) {
179
- if (!frame.data) continue;
180
- try {
181
- yield { id: frame.id, event: frame.event, data: JSON.parse(frame.data) };
182
- } catch {
183
- }
184
- }
185
- } catch (error) {
186
- if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
187
- }
188
- }
189
-
190
52
  // src/files.ts
191
53
  import { readFileSync } from "fs";
192
54
  import { basename, extname } from "path";
@@ -306,6 +168,123 @@ function renderGoal(goal2) {
306
168
  return lines.join("\n");
307
169
  }
308
170
 
171
+ // src/account.ts
172
+ var DOLLARS = /^\d+(?:\.\d{1,2})?$/;
173
+ function dollarsToCents(flag, value) {
174
+ const trimmed = value.trim().replace(/^\$/, "");
175
+ if (!DOLLARS.test(trimmed)) throw new Error(`${flag} must be a dollar amount like 20 or 20.00, got ${JSON.stringify(value)}`);
176
+ const [whole, fraction = ""] = trimmed.split(".");
177
+ const cents = Number(whole) * 100 + Number(fraction.padEnd(2, "0"));
178
+ if (cents <= 0) throw new Error(`${flag} must be more than $0`);
179
+ return String(cents);
180
+ }
181
+ function wholeDollars(flag, value) {
182
+ const trimmed = value.trim().replace(/^\$/, "");
183
+ if (!/^\d+$/.test(trimmed)) throw new Error(`${flag} must be whole dollars, got ${JSON.stringify(value)}`);
184
+ return Number(trimmed);
185
+ }
186
+ function autoTopupBody(opts) {
187
+ if (opts.on && opts.off) throw new Error("pass --on or --off, not both");
188
+ const body = {};
189
+ if (opts.on) body.enabled = true;
190
+ if (opts.off) body.enabled = false;
191
+ if (opts.threshold !== void 0) body.threshold_usd = wholeDollars("--threshold", opts.threshold);
192
+ if (opts.amount !== void 0) body.amount_usd = wholeDollars("--amount", opts.amount);
193
+ return Object.keys(body).length ? body : null;
194
+ }
195
+ var usd = (value) => `$${value.toFixed(2)}`;
196
+ var centsUsd = (money) => `$${(Number(money.amount) / 100).toFixed(2)}`;
197
+ function renderAutoTopup(settings) {
198
+ const state = settings.enabled ? `on \u2014 below ${usd(settings.threshold_usd)}, the saved card adds ${usd(settings.amount_usd)}` : `off${settings.opted_out ? " (you turned it off)" : ""} \u2014 would add ${usd(settings.amount_usd)} below ${usd(settings.threshold_usd)}`;
199
+ const lines = [`auto top-up: ${state}`];
200
+ if (settings.card_on_file === false) lines.push(" no saved card \u2014 `sky billing card` adds one");
201
+ if (settings.failure) lines.push(` last charge failed${settings.failure.paused ? " (paused)" : ""}: ${settings.failure.reason}`);
202
+ return lines.join("\n");
203
+ }
204
+ function renderBalance(balance) {
205
+ return `balance: ${usd(balance.balance_usd)} card on file: ${balance.card_on_file ? "yes" : "no"}`;
206
+ }
207
+ function renderTopup(result) {
208
+ return result.charged ? `Charged your saved card. Balance: ${centsUsd(result.balance)}` : `Already bought with this request id \u2014 not charged again. Balance: ${centsUsd(result.balance)}`;
209
+ }
210
+ function measureLine(m) {
211
+ return `${String(m.calls).padStart(6)} calls ${String(m.inputTokens).padStart(10)} in ${String(m.outputTokens).padStart(9)} out ${`$${m.billedCostUsd.toFixed(4)}`.padStart(10)}`;
212
+ }
213
+ function renderUsage(summary) {
214
+ const lines = [`usage ${summary.range.from} \u2192 ${summary.range.to} (end exclusive), by ${summary.groupBy}`];
215
+ for (const group of summary.groups) {
216
+ lines.push(` ${group.label.padEnd(28).slice(0, 28)} ${group.source.padEnd(8)} ${measureLine(group)}`);
217
+ }
218
+ if (!summary.groups.length) lines.push(" no usage in this range");
219
+ lines.push(` ${"total".padEnd(37)} ${measureLine(summary.totals)}`);
220
+ return lines.join("\n");
221
+ }
222
+ function usageQuery(opts) {
223
+ const params = new URLSearchParams();
224
+ if (opts.from) params.set("from", opts.from);
225
+ if (opts.to) params.set("to", opts.to);
226
+ if (opts.groupBy) params.set("group_by", opts.groupBy);
227
+ if (opts.source) params.set("source", opts.source);
228
+ if (opts.key) params.set("api_key_id", opts.key);
229
+ if (opts.format) params.set("format", opts.format);
230
+ const query = params.toString();
231
+ return query ? `?${query}` : "";
232
+ }
233
+ function apiKeyBody(name, opts) {
234
+ const scopes = (opts.scopes ?? "read,write").split(",").map((scope) => scope.trim()).filter(Boolean);
235
+ const unknown = scopes.find((scope) => !["read", "write", "admin"].includes(scope));
236
+ if (unknown) throw new Error(`--scopes takes read, write and admin, got ${JSON.stringify(unknown)}`);
237
+ if (!scopes.length) throw new Error("--scopes needs at least one scope");
238
+ return {
239
+ name: name.trim(),
240
+ scopes,
241
+ ...opts.workspace !== void 0 ? { universe: opts.workspace === "personal" ? null : opts.workspace } : {},
242
+ ...opts.expires !== void 0 ? { expires_at: opts.expires } : {}
243
+ };
244
+ }
245
+ function renderApiKey(key) {
246
+ const where = key.universe ? `workspace ${key.universe}` : "any workspace";
247
+ const used = key.last_used_at ? `used ${key.last_used_at.slice(0, 10)}` : "never used";
248
+ return `${key.id} ${key.token_prefix}\u2026 ${key.status.padEnd(7)} ${key.scopes.join(",").padEnd(16)} ${where} ${used} ${key.name}${key.current ? " (this key)" : ""}`;
249
+ }
250
+
251
+ // src/context.ts
252
+ function memoryBody(operation, version) {
253
+ return { ...operation, expectedVersion: version };
254
+ }
255
+ function memoryIndex(value) {
256
+ if (!/^\d+$/.test(value.trim())) throw new Error(`the entry number must be a whole number, got ${JSON.stringify(value)}`);
257
+ return Number(value.trim());
258
+ }
259
+ function renderMemory(document) {
260
+ if (!document.entries.length) return "no saved memory \u2014 `sky memory save <text>` adds an entry";
261
+ return document.entries.map((entry2) => `${String(entry2.index).padStart(3)} ${entry2.text}`).join("\n");
262
+ }
263
+ function searchQuery(query, opts) {
264
+ const params = new URLSearchParams({ q: query });
265
+ params.set("scope", opts.scope ?? "sessions");
266
+ if (opts.limit) params.set("limit", opts.limit);
267
+ if (opts.cursor) params.set("cursor", opts.cursor);
268
+ if (opts.since) params.set("since", opts.since);
269
+ if (opts.until) params.set("until", opts.until);
270
+ return `?${params.toString()}`;
271
+ }
272
+ function refArgument(ref) {
273
+ return `'${JSON.stringify(ref).replace(/'/g, "'\\''")}'`;
274
+ }
275
+ function renderContextPage(page) {
276
+ const lines = [];
277
+ for (const { hit } of page.hits) {
278
+ lines.push(`${hit.source.padEnd(8)} ${hit.occurredAt?.slice(0, 10) ?? " "} ${hit.title}`);
279
+ lines.push(` ${hit.snippet.replace(/\s+/g, " ").slice(0, 160)}`);
280
+ lines.push(` sky context read ${refArgument(hit.ref)}`);
281
+ }
282
+ if (!page.hits.length) lines.push("no matches on this page");
283
+ if (page.partial) lines.push(`partial: ${page.coverage ?? "not every source was searched"}`);
284
+ if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
285
+ return lines.join("\n");
286
+ }
287
+
309
288
  // src/agent-file.ts
310
289
  import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
311
290
  import { dirname, resolve } from "path";
@@ -841,10 +820,222 @@ async function clone(opts) {
841
820
  }
842
821
 
843
822
  // src/import-local.ts
844
- import { closeSync, existsSync as existsSync3, openSync, readdirSync, readFileSync as readFileSync4, readSync, statSync } from "fs";
845
- import { homedir as homedir2 } from "os";
846
- import { join as join2 } from "path";
823
+ import { closeSync, existsSync as existsSync4, openSync, readdirSync, readFileSync as readFileSync5, readSync, statSync as statSync2 } from "fs";
824
+ import { homedir as homedir3 } from "os";
825
+ import { join as join3 } from "path";
847
826
  import { createHash } from "crypto";
827
+
828
+ // src/import-files.ts
829
+ import { isUtf8 } from "buffer";
830
+ import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
831
+ import { homedir as homedir2 } from "os";
832
+ import { dirname as dirname2, extname as extname2, isAbsolute, join as join2, relative, resolve as resolve3, sep, basename as basename3 } from "path";
833
+ var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
834
+ var SECRET_FILE = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx|sqlite|db))$|\/\.ssh\/|\/\.aws\/|\/\.gnupg\/|\/\.docker\/config\.json$|\/Keychains\//i;
835
+ var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
836
+ var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
837
+ var DOCUMENTS = {
838
+ ".pdf": "application/pdf",
839
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
840
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
841
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
842
+ ".doc": "application/msword",
843
+ ".rtf": "application/rtf",
844
+ ".odt": "application/vnd.oasis.opendocument.text"
845
+ };
846
+ var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
847
+ var TEXT = {
848
+ ".md": "text/markdown",
849
+ ".mdx": "text/markdown",
850
+ ".txt": "text/plain",
851
+ ".csv": "text/csv",
852
+ ".tsv": "text/tab-separated-values",
853
+ ".html": "text/html",
854
+ ".htm": "text/html",
855
+ ".css": "text/css",
856
+ ".scss": "text/css",
857
+ ".json": "application/json",
858
+ ".jsonl": "application/x-ndjson",
859
+ ".ndjson": "application/x-ndjson",
860
+ ".yaml": "application/yaml",
861
+ ".yml": "application/yaml",
862
+ ".toml": "application/toml",
863
+ ".xml": "application/xml",
864
+ ".sql": "application/sql",
865
+ ".svg": "image/svg+xml",
866
+ ".js": "application/javascript",
867
+ ".mjs": "application/javascript",
868
+ ".cjs": "application/javascript",
869
+ ".py": "text/x-python",
870
+ ".sh": "text/x-shellscript",
871
+ ".bash": "text/x-shellscript",
872
+ ".zsh": "text/x-shellscript"
873
+ };
874
+ var PLAIN_TEXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".c", ".h", ".cc", ".cpp", ".hpp", ".cs", ".scala", ".lua", ".r", ".m", ".tex", ".bib", ".ipynb", ".prisma", ".graphql", ".proto", ".ini", ".cfg", ".conf", ".log", ".rst", ".vue", ".svelte", ".astro", ".tf", ".hcl", ".gradle", ".lock", ".patch", ".diff"]);
875
+ var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
876
+ function classify(path) {
877
+ const ext = extname2(path).toLowerCase();
878
+ if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
879
+ if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
880
+ if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
881
+ if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename3(path))) return { kind: "text", mediaType: "text/plain" };
882
+ return null;
883
+ }
884
+ 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;
885
+ var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
886
+ var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
887
+ var str = (value) => typeof value === "string" ? value : "";
888
+ function shellText(input) {
889
+ if (typeof input === "string") return input;
890
+ const i = obj(input);
891
+ if (!i) return "";
892
+ const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
893
+ return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
894
+ }
895
+ function touchedPaths(records, cwd, home = homedir2()) {
896
+ const out = /* @__PURE__ */ new Set();
897
+ const add = (raw, base) => {
898
+ if (!raw || raw.length > 1e3) return;
899
+ const path = raw.replace(/^~(?=\/)/, home);
900
+ if (isAbsolute(path)) out.add(resolve3(path));
901
+ else if (base) out.add(resolve3(base, path));
902
+ };
903
+ const call = (name, input) => {
904
+ const i = obj(input);
905
+ const base = str(i?.workdir) || str(i?.cwd) || cwd;
906
+ if (i) for (const key of PATH_KEYS) add(str(i[key]), base);
907
+ const patch = typeof input === "string" ? input : str(i?.input) || str(i?.patch);
908
+ for (const match of patch.matchAll(/^\*\*\* (?:Add|Update) File: (.+)$/gm)) add(match[1].trim(), base);
909
+ if (!/bash|shell|exec|terminal|command|run/i.test(name)) return;
910
+ for (const match of shellText(input).replace(/\\n/g, "\n").matchAll(SHELL_READ)) add(match[1], base);
911
+ };
912
+ for (const raw of records) {
913
+ const record = obj(raw);
914
+ if (!record) continue;
915
+ const content = obj(record.message)?.content;
916
+ if (record.type === "assistant" && Array.isArray(content)) {
917
+ for (const block of content) {
918
+ const b = obj(block);
919
+ if (b?.type === "tool_use") call(str(b.name), b.input);
920
+ }
921
+ }
922
+ const payload = obj(record.payload);
923
+ if (record.type === "response_item" && (payload?.type === "function_call" || payload?.type === "custom_tool_call")) {
924
+ let input = payload.arguments ?? payload.input ?? "";
925
+ if (typeof input === "string") {
926
+ try {
927
+ input = JSON.parse(input);
928
+ } catch {
929
+ input = payload.arguments ?? payload.input;
930
+ }
931
+ }
932
+ call(str(payload.name), input);
933
+ }
934
+ }
935
+ return [...out];
936
+ }
937
+ function gitRoot(dir) {
938
+ for (let d = dir; ; d = dirname2(d)) {
939
+ if (existsSync3(join2(d, ".git"))) return d;
940
+ if (d === dirname2(d)) return null;
941
+ }
942
+ }
943
+ function instructionPaths(cwd, harness2, home = homedir2()) {
944
+ const out = [];
945
+ if (cwd && existsSync3(cwd)) {
946
+ const root = gitRoot(cwd) ?? cwd;
947
+ for (let d = resolve3(cwd); ; d = dirname2(d)) {
948
+ for (const name of INSTRUCTION_FILES) out.push(join2(d, name));
949
+ if (d === root || d === dirname2(d)) break;
950
+ }
951
+ }
952
+ if (harness2 === "claude_code") out.push(join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "CLAUDE.md"));
953
+ if (harness2 === "codex") out.push(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "AGENTS.md"));
954
+ return out.filter((path) => {
955
+ try {
956
+ return statSync(path).isFile();
957
+ } catch {
958
+ return false;
959
+ }
960
+ });
961
+ }
962
+ function resourceName(path, cwd, home = homedir2()) {
963
+ const inside = cwd ? relative(resolve3(cwd), path) : "";
964
+ if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep).join("/")}`;
965
+ const underHome = relative(home, path);
966
+ const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
967
+ return `files/${shown.split(sep).join("/")}`;
968
+ }
969
+ var ORDER = { text: 0, document: 1, image: 2 };
970
+ function collectSessionFiles(records, opts) {
971
+ const home = opts.home ?? homedir2();
972
+ const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
973
+ const leftOut = [];
974
+ const candidates = [];
975
+ const seen = /* @__PURE__ */ new Set();
976
+ for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touchedPaths(records, opts.cwd, home)]) {
977
+ if (seen.has(path) || exclude.has(path)) continue;
978
+ seen.add(path);
979
+ if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
980
+ leftOut.push({ path, reason: "credential or build file" });
981
+ continue;
982
+ }
983
+ let size;
984
+ try {
985
+ const stat = statSync(path);
986
+ if (!stat.isFile()) continue;
987
+ size = stat.size;
988
+ } catch {
989
+ leftOut.push({ path, reason: "missing" });
990
+ continue;
991
+ }
992
+ const type = classify(path);
993
+ if (!type) {
994
+ leftOut.push({ path, reason: "unsupported type" });
995
+ continue;
996
+ }
997
+ if (size > MAX_ATTACHED_FILE_BYTES) {
998
+ leftOut.push({ path, reason: "too large" });
999
+ continue;
1000
+ }
1001
+ candidates.push({ path, name: resourceName(path, opts.cwd, home), ...type, size });
1002
+ }
1003
+ candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
1004
+ const lines = [];
1005
+ const attached = [];
1006
+ let bytes = 0;
1007
+ for (const file of candidates) {
1008
+ let data;
1009
+ try {
1010
+ data = readFileSync4(file.path);
1011
+ } catch {
1012
+ leftOut.push({ path: file.path, reason: "missing" });
1013
+ continue;
1014
+ }
1015
+ if (file.kind === "text" && data.includes(0)) {
1016
+ leftOut.push({ path: file.path, reason: "not valid UTF-8 text" });
1017
+ continue;
1018
+ }
1019
+ let line;
1020
+ try {
1021
+ line = redactLine(JSON.stringify({ type: "import.resource", name: file.name, media_type: file.mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
1022
+ } catch {
1023
+ leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
1024
+ continue;
1025
+ }
1026
+ const size = Buffer.byteLength(line);
1027
+ if (bytes + size > opts.budget) {
1028
+ leftOut.push({ path: file.path, reason: "over the upload budget" });
1029
+ continue;
1030
+ }
1031
+ bytes += size;
1032
+ lines.push(line);
1033
+ attached.push({ name: file.name, kind: file.kind, bytes: data.length });
1034
+ }
1035
+ return { lines, bytes, attached, leftOut };
1036
+ }
1037
+
1038
+ // src/import-local.ts
848
1039
  function redactLine2(raw) {
849
1040
  return redactLine(raw).line;
850
1041
  }
@@ -916,13 +1107,13 @@ function sniffTitle(filePath) {
916
1107
  }
917
1108
  }
918
1109
  function listClaudeCodeSessions(limit) {
919
- const root = process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
920
- const projectsDir = join2(root, "projects");
921
- if (!existsSync3(projectsDir)) return [];
1110
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
1111
+ const projectsDir = join3(root, "projects");
1112
+ if (!existsSync4(projectsDir)) return [];
922
1113
  const sessions = [];
923
1114
  try {
924
1115
  for (const encoded of readdirSync(projectsDir)) {
925
- const projectDir = join2(projectsDir, encoded);
1116
+ const projectDir = join3(projectsDir, encoded);
926
1117
  let entries;
927
1118
  try {
928
1119
  entries = readdirSync(projectDir);
@@ -933,22 +1124,22 @@ function listClaudeCodeSessions(limit) {
933
1124
  for (const entry2 of entries) {
934
1125
  if (!entry2.endsWith(".jsonl")) continue;
935
1126
  const sessionId = entry2.slice(0, -6);
936
- const mainFile = join2(projectDir, entry2);
1127
+ const mainFile = join3(projectDir, entry2);
937
1128
  let stat;
938
1129
  try {
939
- stat = statSync(mainFile);
1130
+ stat = statSync2(mainFile);
940
1131
  } catch {
941
1132
  continue;
942
1133
  }
943
1134
  if (!stat.isFile()) continue;
944
1135
  let discoveryError;
945
1136
  const subagentFiles = [];
946
- const subagentDir = join2(projectDir, sessionId, "subagents");
947
- if (existsSync3(subagentDir)) {
1137
+ const subagentDir = join3(projectDir, sessionId, "subagents");
1138
+ if (existsSync4(subagentDir)) {
948
1139
  try {
949
1140
  const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
950
1141
  for (const f of subFiles) {
951
- subagentFiles.push(join2(subagentDir, f));
1142
+ subagentFiles.push(join3(subagentDir, f));
952
1143
  }
953
1144
  } catch {
954
1145
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -970,7 +1161,7 @@ function listClaudeCodeSessions(limit) {
970
1161
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
971
1162
  }
972
1163
  function listCodexSessions(limit) {
973
- const sessionsDir = join2(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join2(homedir2(), ".codex"), "sessions");
1164
+ const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
974
1165
  const records = [];
975
1166
  function walk(dir) {
976
1167
  let entries;
@@ -980,7 +1171,7 @@ function listCodexSessions(limit) {
980
1171
  return;
981
1172
  }
982
1173
  for (const entry2 of entries) {
983
- const full = join2(dir, entry2.name);
1174
+ const full = join3(dir, entry2.name);
984
1175
  if (entry2.isDirectory()) {
985
1176
  walk(full);
986
1177
  continue;
@@ -998,7 +1189,7 @@ function listCodexSessions(limit) {
998
1189
  sessionId: data.id,
999
1190
  workspace: typeof data.cwd === "string" ? data.cwd : dir,
1000
1191
  title: sniffTitle(full),
1001
- mtimeMs: statSync(full).mtimeMs,
1192
+ mtimeMs: statSync2(full).mtimeMs,
1002
1193
  files: [full]
1003
1194
  },
1004
1195
  ...typeof parent === "string" ? { parent } : {}
@@ -1067,7 +1258,7 @@ async function mintTrace(base, harness2) {
1067
1258
  function loadSummaryFile(summaryPath) {
1068
1259
  let raw;
1069
1260
  try {
1070
- raw = readFileSync4(summaryPath, "utf8");
1261
+ raw = readFileSync5(summaryPath, "utf8");
1071
1262
  } catch {
1072
1263
  throw new Error(`Cannot read summary file: ${summaryPath}`);
1073
1264
  }
@@ -1084,7 +1275,16 @@ function loadSummaryFile(summaryPath) {
1084
1275
  const redacted = redactLine2(raw.trimEnd());
1085
1276
  return redacted;
1086
1277
  }
1087
- function prepareImport(session2, summaryPath) {
1278
+ var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
1279
+ var SUMMARY_RESERVE_BYTES = 128 * 1024;
1280
+ var MAX_LISTED_ARTIFACTS = 99;
1281
+ function leftOutLine(left) {
1282
+ const counts = /* @__PURE__ */ new Map();
1283
+ for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
1284
+ const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
1285
+ return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
1286
+ }
1287
+ function prepareImport(session2, summaryPath, options = {}) {
1088
1288
  if (session2.discoveryError) throw new Error(session2.discoveryError);
1089
1289
  const maximum = 16 * 1024 * 1024;
1090
1290
  let inputBytes = 0;
@@ -1096,10 +1296,12 @@ function prepareImport(session2, summaryPath) {
1096
1296
  let encryptedReasoningCount = 0;
1097
1297
  let compactionCount = 0;
1098
1298
  let unreadableCount = 0;
1299
+ const source = new SessionSourceCollector();
1300
+ const records = [];
1099
1301
  for (const path of session2.files) {
1100
- const stat = statSync(path);
1302
+ const stat = statSync2(path);
1101
1303
  if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
1102
- const text = readFileSync4(path, "utf8");
1304
+ const text = readFileSync5(path, "utf8");
1103
1305
  for (const raw of text.split("\n")) {
1104
1306
  if (!raw.trim()) continue;
1105
1307
  let record;
@@ -1112,6 +1314,8 @@ function prepareImport(session2, summaryPath) {
1112
1314
  if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
1113
1315
  const rec = record;
1114
1316
  if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
1317
+ if (path === session2.files[0]) source.push(rec);
1318
+ records.push(rec);
1115
1319
  if (rec.type === "response_item") {
1116
1320
  const payload = rec.payload;
1117
1321
  if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
@@ -1135,6 +1339,9 @@ function prepareImport(session2, summaryPath) {
1135
1339
  if (!lines.length) throw new Error("The selected transcript is empty.");
1136
1340
  const sourceId = nativeId ?? session2.sessionId;
1137
1341
  if (!/^[A-Za-z0-9_-]{1,200}$/.test(sourceId) || redactLine2(JSON.stringify(sourceId)) !== JSON.stringify(sourceId)) throw new Error("The source conversation has an invalid identifier.");
1342
+ const meta = source.result();
1343
+ const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [] } : collectSessionFiles(records, { harness: session2.harness, cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""), budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES, exclude: session2.files });
1344
+ const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under are attached as well (listed in artifacts).` : "";
1138
1345
  let summaryJson;
1139
1346
  if (summaryPath) {
1140
1347
  summaryJson = loadSummaryFile(summaryPath);
@@ -1143,10 +1350,10 @@ function prepareImport(session2, summaryPath) {
1143
1350
  format: "asteroids.session-summary",
1144
1351
  format_version: 1,
1145
1352
  source_harness: session2.harness,
1146
- source_harness_version: null,
1353
+ source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
1147
1354
  source_session_id: sourceId,
1148
1355
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
1149
- summary: "The complete selected local conversation is attached as a transcript. Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.",
1356
+ summary: `The complete selected local conversation is attached as a transcript.${attachedNote} Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.`,
1150
1357
  context: {
1151
1358
  objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
1152
1359
  constraints: [],
@@ -1154,19 +1361,19 @@ function prepareImport(session2, summaryPath) {
1154
1361
  completed: [],
1155
1362
  pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
1156
1363
  verification: [],
1157
- blockers: ["This handoff contains the recorded conversation only. Referenced local files, runtime state, and unreadable reasoning are not transferred."],
1158
- artifacts: [],
1364
+ blockers: [leftOutLine(files2.leftOut)],
1365
+ artifacts: files2.attached.slice(0, MAX_LISTED_ARTIFACTS).map((file) => ({ path: file.name, description: `Attached from the local ${file.kind === "text" ? "workspace" : file.kind} the conversation used (${file.bytes} bytes).` })),
1159
1366
  provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
1160
1367
  }
1161
1368
  };
1162
1369
  summaryJson = JSON.stringify(summary);
1163
1370
  }
1164
- const body = lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + 1 }) + "\n";
1371
+ const body = lines.join("") + files2.lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + files2.lines.length + 1 }) + "\n";
1165
1372
  const gaps = [];
1166
1373
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1167
1374
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1168
1375
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1169
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
1376
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
1170
1377
  }
1171
1378
  async function importSession(opts) {
1172
1379
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1175,7 +1382,7 @@ async function importSession(opts) {
1175
1382
  throw new Error("Import requires HTTPS, or a loopback development server.");
1176
1383
  }
1177
1384
  opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
1178
- const { body, transcriptLines, outputBytes, redactionCount, gaps } = prepareImport(opts.session, opts.summaryPath);
1385
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1179
1386
  let traceId;
1180
1387
  let key;
1181
1388
  if (opts.importKey && opts.grantTraceId) {
@@ -1207,7 +1414,9 @@ async function importSession(opts) {
1207
1414
  transcriptFiles: opts.session.files.length,
1208
1415
  bytes: outputBytes,
1209
1416
  redactionCount,
1210
- gaps
1417
+ gaps,
1418
+ attachedFiles,
1419
+ leftOutFiles
1211
1420
  };
1212
1421
  }
1213
1422
 
@@ -1244,7 +1453,7 @@ async function login(opts) {
1244
1453
  }
1245
1454
  async function browserLogin(verifier, challenge) {
1246
1455
  const state = b64url(randomBytes(16));
1247
- const code = await new Promise((resolve7, reject) => {
1456
+ const code = await new Promise((resolve8, reject) => {
1248
1457
  const server = createServer((req, res) => {
1249
1458
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
1250
1459
  if (url.pathname !== "/callback") {
@@ -1257,7 +1466,7 @@ async function browserLogin(verifier, challenge) {
1257
1466
  if (url.searchParams.get("state") !== state || !got) {
1258
1467
  reject(new Error("login callback state mismatch"));
1259
1468
  } else {
1260
- resolve7(got);
1469
+ resolve8(got);
1261
1470
  }
1262
1471
  server.close();
1263
1472
  });
@@ -1311,23 +1520,23 @@ async function headlessLogin(verifier, challenge) {
1311
1520
  }
1312
1521
 
1313
1522
  // src/local-agents.ts
1314
- import { existsSync as existsSync4 } from "fs";
1315
- import { homedir as homedir3 } from "os";
1316
- import { join as join3 } from "path";
1317
- function localAgents(env = process.env, home = homedir3()) {
1318
- const claude = env.CLAUDE_CONFIG_DIR || join3(home, ".claude");
1319
- const codex = env.CODEX_HOME || join3(home, ".codex");
1320
- const cursor = join3(home, ".cursor");
1523
+ import { existsSync as existsSync5 } from "fs";
1524
+ import { homedir as homedir4 } from "os";
1525
+ import { join as join4 } from "path";
1526
+ function localAgents(env = process.env, home = homedir4()) {
1527
+ const claude = env.CLAUDE_CONFIG_DIR || join4(home, ".claude");
1528
+ const codex = env.CODEX_HOME || join4(home, ".codex");
1529
+ const cursor = join4(home, ".cursor");
1321
1530
  return [
1322
- { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join3(claude, "skills") },
1323
- { id: "codex", name: "Codex", root: codex, skillsDir: join3(codex, "skills") },
1324
- { id: "cursor", name: "Cursor", root: cursor, skillsDir: join3(cursor, "skills") }
1531
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join4(claude, "skills") },
1532
+ { id: "codex", name: "Codex", root: codex, skillsDir: join4(codex, "skills") },
1533
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join4(cursor, "skills") }
1325
1534
  ];
1326
1535
  }
1327
- function detectLocalAgents(env = process.env, home = homedir3()) {
1328
- return localAgents(env, home).filter((agent2) => existsSync4(agent2.root));
1536
+ function detectLocalAgents(env = process.env, home = homedir4()) {
1537
+ return localAgents(env, home).filter((agent2) => existsSync5(agent2.root));
1329
1538
  }
1330
- function selectLocalAgents(ids, env = process.env, home = homedir3()) {
1539
+ function selectLocalAgents(ids, env = process.env, home = homedir4()) {
1331
1540
  if (!ids?.length) return detectLocalAgents(env, home);
1332
1541
  const known = localAgents(env, home);
1333
1542
  return ids.map((id) => {
@@ -1421,21 +1630,21 @@ async function readTurn(sessionId, opts = {}) {
1421
1630
  for (; ; ) {
1422
1631
  const reading = latestTurn(await history(sessionId));
1423
1632
  if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
1424
- await new Promise((resolve7) => setTimeout(resolve7, opts.intervalMs ?? 3e3));
1633
+ await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
1425
1634
  }
1426
1635
  }
1427
1636
 
1428
1637
  // src/setup.ts
1429
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1430
- import { dirname as dirname3, join as join7 } from "path";
1638
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
1639
+ import { dirname as dirname4, join as join8 } from "path";
1431
1640
  import { createInterface as createInterface3 } from "readline";
1432
1641
 
1433
1642
  // src/mcp-register.ts
1434
1643
  import { spawnSync } from "child_process";
1435
1644
 
1436
1645
  // src/skill.ts
1437
- import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1438
- import { join as join4 } from "path";
1646
+ import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1647
+ import { join as join5 } from "path";
1439
1648
 
1440
1649
  // src/catalog-file.ts
1441
1650
  var CATALOG_FILE = "catalog.md";
@@ -1511,7 +1720,7 @@ at the end; they do the same things, one agent at a time.
1511
1720
 
1512
1721
  Use the workspace the person named. If they named none, check the git root for
1513
1722
  \`.agentsky.json\` \u2014 if it has a \`"workspace"\` key, use that. Otherwise use the
1514
- account default (personal). Never ask. Pass the chosen slug as \`workspace\` on
1723
+ account default workspace. Never ask. Pass the chosen slug as \`workspace\` on
1515
1724
  every MCP tool that accepts it, or as \`--workspace <slug>\` on sky commands.
1516
1725
  Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
1517
1726
 
@@ -1876,7 +2085,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
1876
2085
  }
1877
2086
  function installedVersion(dir) {
1878
2087
  try {
1879
- const marker = JSON.parse(readFileSync5(join4(dir, SKILL_MARKER), "utf8"));
2088
+ const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
1880
2089
  return typeof marker.version === "string" ? marker.version : null;
1881
2090
  } catch {
1882
2091
  return null;
@@ -1886,25 +2095,25 @@ function isInstalledSkill(dir) {
1886
2095
  return installedVersion(dir) !== null;
1887
2096
  }
1888
2097
  function installSkill(agent2, opts = {}) {
1889
- const dir = join4(agent2.skillsDir, SKILL_NAME);
2098
+ const dir = join5(agent2.skillsDir, SKILL_NAME);
1890
2099
  const previous = installedVersion(dir);
1891
2100
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
1892
2101
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
1893
2102
  if (outcome !== "current") {
1894
2103
  mkdirSync(dir, { recursive: true });
1895
- writeFileSync3(join4(dir, "SKILL.md"), skillDocument());
1896
- writeFileSync3(join4(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2104
+ writeFileSync3(join5(dir, "SKILL.md"), skillDocument());
2105
+ writeFileSync3(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
1897
2106
  `);
1898
2107
  }
1899
2108
  if (opts.catalog) {
1900
2109
  mkdirSync(dir, { recursive: true });
1901
- writeFileSync3(join4(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2110
+ writeFileSync3(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
1902
2111
  }
1903
- const refsDir = join4(dir, REFERENCES_DIR);
2112
+ const refsDir = join5(dir, REFERENCES_DIR);
1904
2113
  mkdirSync(refsDir, { recursive: true });
1905
- writeFileSync3(join4(refsDir, "import.md"), importReferenceDoc());
1906
- writeFileSync3(join4(refsDir, "workspaces.md"), workspacesReferenceDoc());
1907
- writeFileSync3(join4(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
2114
+ writeFileSync3(join5(refsDir, "import.md"), importReferenceDoc());
2115
+ writeFileSync3(join5(refsDir, "workspaces.md"), workspacesReferenceDoc());
2116
+ writeFileSync3(join5(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
1908
2117
  return { agent: agent2.id, path: dir, outcome };
1909
2118
  }
1910
2119
 
@@ -1955,9 +2164,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
1955
2164
  }
1956
2165
 
1957
2166
  // src/setup-local.ts
1958
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
1959
- import { homedir as homedir4 } from "os";
1960
- import { dirname as dirname2, isAbsolute, join as join5, resolve as resolve3 } from "path";
2167
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
2168
+ import { homedir as homedir5 } from "os";
2169
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
1961
2170
  import { parse as parseToml2 } from "smol-toml";
1962
2171
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
1963
2172
  var MAX_IMPORT_DEPTH = 5;
@@ -1973,11 +2182,11 @@ function sensitive(name) {
1973
2182
  function skillFiles(root, prefix) {
1974
2183
  const files2 = [];
1975
2184
  for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
1976
- const path = join5(root, entry2.name);
2185
+ const path = join6(root, entry2.name);
1977
2186
  if (entry2.isDirectory()) {
1978
2187
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
1979
- } else if (entry2.isFile() && !sensitive(entry2.name) && statSync2(path).size <= MAX_SKILL_FILE_BYTES) {
1980
- files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(readFileSync6(path)) });
2188
+ } else if (entry2.isFile() && !sensitive(entry2.name) && statSync3(path).size <= MAX_SKILL_FILE_BYTES) {
2189
+ files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(readFileSync7(path)) });
1981
2190
  }
1982
2191
  }
1983
2192
  return files2;
@@ -1986,19 +2195,19 @@ function readLocalSkills(agents) {
1986
2195
  const skills2 = [];
1987
2196
  const ignored = [];
1988
2197
  for (const agent2 of agents) {
1989
- if (!existsSync5(agent2.skillsDir)) continue;
2198
+ if (!existsSync6(agent2.skillsDir)) continue;
1990
2199
  for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
1991
- const dir = join5(agent2.skillsDir, entry2.name);
2200
+ const dir = join6(agent2.skillsDir, entry2.name);
1992
2201
  let isDir = entry2.isDirectory();
1993
2202
  if (entry2.isSymbolicLink()) {
1994
2203
  try {
1995
- isDir = statSync2(dir).isDirectory();
2204
+ isDir = statSync3(dir).isDirectory();
1996
2205
  } catch {
1997
2206
  isDir = false;
1998
2207
  }
1999
2208
  }
2000
- if (!isDir || !existsSync5(join5(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2001
- const name = frontmatterName(readFileSync6(join5(dir, "SKILL.md"), "utf8"));
2209
+ if (!isDir || !existsSync6(join6(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2210
+ const name = frontmatterName(readFileSync7(join6(dir, "SKILL.md"), "utf8"));
2002
2211
  if (!name) {
2003
2212
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
2004
2213
  continue;
@@ -2014,7 +2223,7 @@ function readLocalSkills(agents) {
2014
2223
  }
2015
2224
  return { skills: skills2, ignored };
2016
2225
  }
2017
- var INSTRUCTION_FILES = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
2226
+ var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
2018
2227
  function inlineImports(content, from, home, depth, seen) {
2019
2228
  let fence;
2020
2229
  return content.split(/\r?\n/).map((line) => {
@@ -2026,11 +2235,11 @@ function inlineImports(content, from, home, depth, seen) {
2026
2235
  }
2027
2236
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
2028
2237
  if (!target || depth >= MAX_IMPORT_DEPTH) return line;
2029
- const path = target.startsWith("~/") ? join5(home, target.slice(2)) : isAbsolute(target) ? target : resolve3(dirname2(from), target);
2238
+ const path = target.startsWith("~/") ? join6(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname3(from), target);
2030
2239
  if (seen.includes(path)) return "";
2031
2240
  let imported;
2032
2241
  try {
2033
- imported = readFileSync6(path, "utf8");
2242
+ imported = readFileSync7(path, "utf8");
2034
2243
  } catch {
2035
2244
  return line;
2036
2245
  }
@@ -2038,15 +2247,15 @@ function inlineImports(content, from, home, depth, seen) {
2038
2247
  return inlineImports(imported, path, home, depth + 1, seen).trim();
2039
2248
  }).join("\n");
2040
2249
  }
2041
- function readGlobalInstructions(agents, home = homedir4()) {
2250
+ function readGlobalInstructions(agents, home = homedir5()) {
2042
2251
  const found = [];
2043
2252
  for (const agent2 of agents) {
2044
- const file = INSTRUCTION_FILES[agent2.id];
2253
+ const file = INSTRUCTION_FILES2[agent2.id];
2045
2254
  if (!file) continue;
2046
- const path = join5(agent2.root, file);
2255
+ const path = join6(agent2.root, file);
2047
2256
  let raw;
2048
2257
  try {
2049
- raw = readFileSync6(path, "utf8");
2258
+ raw = readFileSync7(path, "utf8");
2050
2259
  } catch {
2051
2260
  continue;
2052
2261
  }
@@ -2065,7 +2274,7 @@ ${item.content.trim()}`).join("\n\n");
2065
2274
  }
2066
2275
  function readJson2(path) {
2067
2276
  try {
2068
- return JSON.parse(readFileSync6(path, "utf8"));
2277
+ return JSON.parse(readFileSync7(path, "utf8"));
2069
2278
  } catch {
2070
2279
  return void 0;
2071
2280
  }
@@ -2079,15 +2288,15 @@ function bearerOf(def) {
2079
2288
  return match?.[1] ?? null;
2080
2289
  }
2081
2290
  function serversOf(agent2, home) {
2082
- if (agent2.id === "claude_code") return readJson2(join5(home, ".claude.json"))?.mcpServers ?? {};
2083
- if (agent2.id === "cursor") return readJson2(join5(agent2.root, "mcp.json"))?.mcpServers ?? {};
2291
+ if (agent2.id === "claude_code") return readJson2(join6(home, ".claude.json"))?.mcpServers ?? {};
2292
+ if (agent2.id === "cursor") return readJson2(join6(agent2.root, "mcp.json"))?.mcpServers ?? {};
2084
2293
  try {
2085
- return parseToml2(readFileSync6(join5(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2294
+ return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2086
2295
  } catch {
2087
2296
  return {};
2088
2297
  }
2089
2298
  }
2090
- function findLocalSecrets(agents, home = homedir4()) {
2299
+ function findLocalSecrets(agents, home = homedir5()) {
2091
2300
  const secrets2 = [];
2092
2301
  for (const agent2 of agents) {
2093
2302
  for (const [name, def] of Object.entries(serversOf(agent2, home))) {
@@ -2103,9 +2312,9 @@ function findLocalSecrets(agents, home = homedir4()) {
2103
2312
 
2104
2313
  // src/subscriptions.ts
2105
2314
  import { spawn as spawn3 } from "child_process";
2106
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
2107
- import { homedir as homedir5 } from "os";
2108
- import { join as join6 } from "path";
2315
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2316
+ import { homedir as homedir6 } from "os";
2317
+ import { join as join7 } from "path";
2109
2318
  import { createInterface as createInterface2 } from "readline";
2110
2319
  var ALIASES = {
2111
2320
  claude: "anthropic",
@@ -2190,12 +2399,12 @@ async function connectClaude() {
2190
2399
  const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
2191
2400
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
2192
2401
  }
2193
- function codexSignIn(env = process.env, home = homedir5()) {
2194
- const path = join6(env.CODEX_HOME || join6(home, ".codex"), "auth.json");
2195
- if (!existsSync6(path)) {
2402
+ function codexSignIn(env = process.env, home = homedir6()) {
2403
+ const path = join7(env.CODEX_HOME || join7(home, ".codex"), "auth.json");
2404
+ if (!existsSync7(path)) {
2196
2405
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
2197
2406
  }
2198
- const raw = readFileSync7(path, "utf8");
2407
+ const raw = readFileSync8(path, "utf8");
2199
2408
  try {
2200
2409
  const parsed = JSON.parse(raw);
2201
2410
  return { path, raw, account: parsed.tokens?.account_id };
@@ -2203,12 +2412,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
2203
2412
  throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
2204
2413
  }
2205
2414
  }
2206
- function cursorSignIn(env = process.env, home = homedir5()) {
2207
- const path = join6(env.XDG_CONFIG_HOME || join6(home, ".config"), "cursor", "auth.json");
2208
- if (!existsSync6(path)) {
2415
+ function cursorSignIn(env = process.env, home = homedir6()) {
2416
+ const path = join7(env.XDG_CONFIG_HOME || join7(home, ".config"), "cursor", "auth.json");
2417
+ if (!existsSync7(path)) {
2209
2418
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
2210
2419
  }
2211
- const raw = readFileSync7(path, "utf8");
2420
+ const raw = readFileSync8(path, "utf8");
2212
2421
  try {
2213
2422
  const parsed = JSON.parse(raw);
2214
2423
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -2287,11 +2496,11 @@ function planProviders(names) {
2287
2496
  }));
2288
2497
  }
2289
2498
  function reportPath() {
2290
- return join7(dirname3(configPath()), "setup.json");
2499
+ return join8(dirname4(configPath()), "setup.json");
2291
2500
  }
2292
2501
  function loadReport() {
2293
2502
  try {
2294
- const saved = JSON.parse(readFileSync8(reportPath(), "utf8"));
2503
+ const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
2295
2504
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
2296
2505
  } catch {
2297
2506
  return { agents: [], plans: [] };
@@ -2304,7 +2513,7 @@ function mergeReport(base, next) {
2304
2513
  return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
2305
2514
  }
2306
2515
  function saveReport(report) {
2307
- mkdirSync2(dirname3(reportPath()), { recursive: true });
2516
+ mkdirSync2(dirname4(reportPath()), { recursive: true });
2308
2517
  writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
2309
2518
  `, { mode: 384 });
2310
2519
  }
@@ -2654,17 +2863,17 @@ function renderArtifact(artifact2) {
2654
2863
  }
2655
2864
 
2656
2865
  // src/skills-store.ts
2657
- import { readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
2658
- import { basename as basename3, join as join8, relative, resolve as resolve4 } from "path";
2866
+ import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
2867
+ import { basename as basename4, join as join9, relative as relative2, resolve as resolve5 } from "path";
2659
2868
  function skillBundle(dir) {
2660
- const root = resolve4(dir);
2661
- const top = basename3(root);
2869
+ const root = resolve5(dir);
2870
+ const top = basename4(root);
2662
2871
  const files2 = [];
2663
2872
  const walk = (path) => {
2664
2873
  for (const name of readdirSync3(path).sort()) {
2665
- const full = join8(path, name);
2666
- if (statSync3(full).isDirectory()) walk(full);
2667
- else files2.push({ path: `${top}/${relative(root, full)}`, bytes: readFileSync9(full) });
2874
+ const full = join9(path, name);
2875
+ if (statSync4(full).isDirectory()) walk(full);
2876
+ else files2.push({ path: `${top}/${relative2(root, full)}`, bytes: readFileSync10(full) });
2668
2877
  }
2669
2878
  };
2670
2879
  walk(root);
@@ -2674,7 +2883,7 @@ function skillBundle(dir) {
2674
2883
  async function uploadSkill(dir, displayName) {
2675
2884
  const form = new FormData();
2676
2885
  for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
2677
- form.append("displayName", displayName ?? basename3(resolve4(dir)));
2886
+ form.append("displayName", displayName ?? basename4(resolve5(dir)));
2678
2887
  return apiForm("POST", "/skills", form);
2679
2888
  }
2680
2889
  async function listSkills() {
@@ -2792,19 +3001,19 @@ function renderPolicy(rule) {
2792
3001
  }
2793
3002
 
2794
3003
  // src/harness.ts
2795
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
2796
- import { dirname as dirname4, join as join9, resolve as resolve5 } from "path";
3004
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
3005
+ import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
2797
3006
  var HARNESS_FILE = "harness.json";
2798
3007
  function harnessFilePath(dir) {
2799
- return join9(resolve5(dir), HARNESS_FILE);
3008
+ return join10(resolve6(dir), HARNESS_FILE);
2800
3009
  }
2801
3010
  function readHarnessFile(dir) {
2802
3011
  const path = harnessFilePath(dir);
2803
- if (!existsSync7(path)) {
2804
- throw new Error(`no ${HARNESS_FILE} found in ${resolve5(dir)} \u2014 run \`sky harness init\` first`);
3012
+ if (!existsSync8(path)) {
3013
+ throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
2805
3014
  }
2806
3015
  try {
2807
- return JSON.parse(readFileSync10(path, "utf8"));
3016
+ return JSON.parse(readFileSync11(path, "utf8"));
2808
3017
  } catch (err) {
2809
3018
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
2810
3019
  }
@@ -2831,10 +3040,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
2831
3040
  `;
2832
3041
  function harnessInit(dir) {
2833
3042
  const path = harnessFilePath(dir);
2834
- if (existsSync7(path)) {
3043
+ if (existsSync8(path)) {
2835
3044
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
2836
3045
  }
2837
- mkdirSync3(dirname4(path), { recursive: true });
3046
+ mkdirSync3(dirname5(path), { recursive: true });
2838
3047
  writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
2839
3048
  `);
2840
3049
  console.log(`Created ${path}`);
@@ -2990,7 +3199,7 @@ auth.command("whoami").description("who the current token acts as").action(
2990
3199
  run(async () => {
2991
3200
  const who = await api("GET", "/whoami");
2992
3201
  console.log(
2993
- `${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (personal)" : ""} \xB7 scopes ${who.scopes.join(",")}`
3202
+ `${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (default)" : ""} \xB7 scopes ${who.scopes.join(",")}`
2994
3203
  );
2995
3204
  })
2996
3205
  );
@@ -3010,7 +3219,7 @@ universe.command("list").action(
3010
3219
  const current = currentUniverse();
3011
3220
  for (const u of universes) {
3012
3221
  const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
3013
- console.log(`${marker} ${u.slug}${u.isPersonal ? " (personal)" : ""}`);
3222
+ console.log(`${marker} ${u.slug}${u.isPersonal ? " (default)" : ""}`);
3014
3223
  }
3015
3224
  })
3016
3225
  );
@@ -3036,7 +3245,7 @@ universe.command("show").action(
3036
3245
  console.log(`${u.slug} \u2014 ${u.name} \xB7 ${u.agents} agents \xB7 ${u.members} members`);
3037
3246
  } else {
3038
3247
  const who = await api("GET", path);
3039
- console.log(`${who.universe.slug} (personal)`);
3248
+ console.log(`${who.universe.slug} (default)`);
3040
3249
  }
3041
3250
  })
3042
3251
  );
@@ -3084,7 +3293,7 @@ workspace.command("list").action(
3084
3293
  const current = currentUniverse();
3085
3294
  for (const u of universes) {
3086
3295
  const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
3087
- console.log(`${marker} ${u.slug}${u.isPersonal ? " (personal)" : ""}`);
3296
+ console.log(`${marker} ${u.slug}${u.isPersonal ? " (default)" : ""}`);
3088
3297
  }
3089
3298
  })
3090
3299
  );
@@ -3339,6 +3548,38 @@ session.command("read <ref>").description("what the agent has said since the las
3339
3548
  if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
3340
3549
  })
3341
3550
  );
3551
+ session.command("share <ref>").description("print the session's share link, turning sharing on; --view makes it read-only, --off turns it off").option("--view", "a view-only link: holders read the conversation and can do nothing else").option("--rotate", "replace the link; the old one stops working").option("--off", "turn sharing off; the link stops working at once").action(
3552
+ run(async (ref, opts) => {
3553
+ const id = await resolveSessionRef(ref);
3554
+ const path = `/sessions/${encodeURIComponent(id)}/share`;
3555
+ if (opts.off) {
3556
+ await api("DELETE", path);
3557
+ console.log(`Sharing is off for ${id}`);
3558
+ return;
3559
+ }
3560
+ const link = await api("POST", path, {
3561
+ access: opts.view ? "view" : "prompt",
3562
+ ...opts.rotate ? { rotate: true } : {}
3563
+ });
3564
+ if (link.shareUrl) console.log(link.shareUrl);
3565
+ else console.log(`view-only token: ${link.token} \u2014 read with GET /links/${link.token}/events`);
3566
+ })
3567
+ );
3568
+ session.command("usage <ref>").description("what the session has cost so far, and its newest billed entries").option("--limit <n>", "entries to list (default 20)").option("--json", "print the API's answer as JSON").action(
3569
+ run(async (ref, opts) => {
3570
+ const id = await resolveSessionRef(ref);
3571
+ const usage = await api("GET", `/sessions/${encodeURIComponent(id)}/usage?limit=${encodeURIComponent(opts.limit ?? "20")}`);
3572
+ if (opts.json) {
3573
+ console.log(JSON.stringify(usage, null, 2));
3574
+ return;
3575
+ }
3576
+ const t = usage.totals;
3577
+ console.log(`${id}: $${t.billedCostUsd.toFixed(4)} billed, ${t.turns} model calls, ${t.inputTokens} in / ${t.outputTokens} out tokens`);
3578
+ for (const entry2 of usage.entries) {
3579
+ console.log(` ${entry2.at} ${entry2.kind.padEnd(16)} ${(entry2.model ?? "").padEnd(24)} $${entry2.billedCostUsd.toFixed(4)}`);
3580
+ }
3581
+ })
3582
+ );
3342
3583
  var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
3343
3584
  var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
3344
3585
  sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
@@ -3555,9 +3796,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
3555
3796
  );
3556
3797
  secrets.command("list <id>").action(
3557
3798
  run(async (id) => {
3558
- const { secrets: keys } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
3559
- if (!keys.length) console.log("no secrets set");
3560
- for (const s of keys) {
3799
+ const { secrets: keys2 } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
3800
+ if (!keys2.length) console.log("no secrets set");
3801
+ for (const s of keys2) {
3561
3802
  console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
3562
3803
  }
3563
3804
  })
@@ -3573,9 +3814,9 @@ secrets.command("unset <id> <key>").action(
3573
3814
  );
3574
3815
  program.command("launch").description("scaffold ./agent.toml if absent, create the agent, 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)").action(
3575
3816
  run(async (opts) => {
3576
- const path = resolve6(opts.file);
3577
- if (!existsSync8(path)) {
3578
- const name = basename4(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
3817
+ const path = resolve7(opts.file);
3818
+ if (!existsSync9(path)) {
3819
+ const name = basename5(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
3579
3820
  scaffoldAgentFile(path, name);
3580
3821
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
3581
3822
  }
@@ -3606,7 +3847,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
3606
3847
  );
3607
3848
  program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
3608
3849
  run(async (opts) => {
3609
- const path = resolve6(opts.file);
3850
+ const path = resolve7(opts.file);
3610
3851
  const { file, spec } = readAgentFile(path);
3611
3852
  if (!file.id) {
3612
3853
  console.log("No agent id in the file yet \u2014 running launch instead.");
@@ -3649,7 +3890,7 @@ function formatRelative(msAgo) {
3649
3890
  if (hours < 48) return `${hours}h ago`;
3650
3891
  return `${Math.round(hours / 24)}d ago`;
3651
3892
  }
3652
- program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").action(
3893
+ program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").option("--no-files", "send the conversation only, without the local files it read, wrote or ran under").action(
3653
3894
  run(
3654
3895
  async (opts) => {
3655
3896
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -3709,11 +3950,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
3709
3950
  console.log();
3710
3951
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
3711
3952
  const answer = await new Promise(
3712
- (resolve7) => rl.question(
3953
+ (resolve8) => rl.question(
3713
3954
  `Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
3714
3955
  (a) => {
3715
3956
  rl.close();
3716
- resolve7(a.trim());
3957
+ resolve8(a.trim());
3717
3958
  }
3718
3959
  )
3719
3960
  );
@@ -3731,14 +3972,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
3731
3972
  toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
3732
3973
  if (!opts.json) {
3733
3974
  console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
3734
- console.log("The recorded conversation is redacted locally. Local project files and login credentials are not transferred.");
3975
+ console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation and the local files it read, wrote or ran under are attached; text is redacted locally. Credential files and login credentials are never transferred. Use --no-files to send the conversation only.");
3735
3976
  }
3736
3977
  if (!autoConfirm) {
3737
3978
  if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
3738
3979
  const prompt = createInterface4({ input: process.stdin, output: process.stdout });
3739
- const answer = await new Promise((resolve7) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
3980
+ const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
3740
3981
  prompt.close();
3741
- resolve7(value);
3982
+ resolve8(value);
3742
3983
  }));
3743
3984
  if (!/^y(?:es)?$/i.test(answer.trim())) {
3744
3985
  console.log("Cancelled. Nothing uploaded.");
@@ -3756,6 +3997,7 @@ Importing ${label} \u2026
3756
3997
  summaryPath: opts.summary,
3757
3998
  importKey: opts.key,
3758
3999
  grantTraceId: opts.trace,
4000
+ files: opts.files,
3759
4001
  onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
3760
4002
  `)
3761
4003
  });
@@ -3770,9 +4012,12 @@ Importing ${label} \u2026
3770
4012
  bytes: result.bytes,
3771
4013
  redactionCount: result.redactionCount,
3772
4014
  gaps: result.gaps,
4015
+ attachedFiles: result.attachedFiles,
4016
+ leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
3773
4017
  continueUrl: result.url
3774
4018
  }));
3775
4019
  } else {
4020
+ if (result.attachedFiles.length || result.leftOutFiles.length) console.log(` Attached ${result.attachedFiles.length} local ${result.attachedFiles.length === 1 ? "file" : "files"}${result.leftOutFiles.length ? `; left out ${result.leftOutFiles.length} (credential, missing, unsupported or over the 30 MiB upload)` : ""}.`);
3776
4021
  console.log(` \u2713 Imported. Continue here:
3777
4022
  ${result.url}
3778
4023
  `);
@@ -3839,7 +4084,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
3839
4084
  );
3840
4085
  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(
3841
4086
  run(async (taskId, path, local) => {
3842
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync11(local), mediaTypeFor(local));
4087
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
3843
4088
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
3844
4089
  })
3845
4090
  );
@@ -4004,6 +4249,19 @@ for (const [verb, blurb] of [
4004
4249
  })
4005
4250
  );
4006
4251
  }
4252
+ routine.command("runs [id]").description("every fire attempt, newest first \u2014 of one routine, or all of them").option("--failed", "only runs that failed to start").option("--limit <n>", "how many (default 20)").action(
4253
+ run(async (id, opts) => {
4254
+ const params = new URLSearchParams({ limit: opts.limit ?? "20" });
4255
+ if (id) params.set("routine", id);
4256
+ if (opts.failed) params.set("has_error", "true");
4257
+ const { items } = await api("GET", `/routine-runs?${params}`);
4258
+ if (!items.length) console.log("no runs");
4259
+ for (const runRow of items) {
4260
+ const outcome = runRow.error ? `FAILED ${runRow.error.type}: ${runRow.error.message}` : runRow.session_id ?? "ok";
4261
+ console.log(`${runRow.id} ${runRow.created_at} ${runRow.trigger_context.type.padEnd(8)} ${outcome}`);
4262
+ }
4263
+ })
4264
+ );
4007
4265
  var collect = (value, previous = []) => [...previous, value];
4008
4266
  function goalPath(sessionId, suffix = "") {
4009
4267
  return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
@@ -4096,8 +4354,149 @@ program.command("config").description("show the CLI config location and current
4096
4354
  console.log(`config: ${configPath()}`);
4097
4355
  console.log(` apiBase: ${config.apiBase ?? "(default)"}`);
4098
4356
  console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
4099
- console.log(` universe: ${config.currentUniverse ?? "(personal)"}`);
4357
+ console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
4100
4358
  });
4359
+ var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
4360
+ billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
4361
+ run(async () => {
4362
+ const [balance, autoTopup] = await Promise.all([
4363
+ api("GET", "/billing"),
4364
+ api("GET", "/billing/auto-topup")
4365
+ ]);
4366
+ console.log(renderBalance(balance));
4367
+ console.log(renderAutoTopup(autoTopup));
4368
+ })
4369
+ );
4370
+ billing.command("card").description("print the address of a card form to save a card; saving moves no money").action(
4371
+ run(async () => {
4372
+ const { url } = await api("POST", "/billing/card-setup");
4373
+ console.log(url);
4374
+ })
4375
+ );
4376
+ billing.command("topup <dollars>").description("buy credit with your saved card: 10, 20, 50 or 100 dollars").option("--request-id <id>", "repeat a request without charging twice (default: a new id)").action(
4377
+ run(async (dollars2, opts) => {
4378
+ const result = await api("POST", "/billing/topups", {
4379
+ amount: { amount: dollarsToCents("<dollars>", dollars2), currency: "USD" },
4380
+ request_id: opts.requestId ?? `sky-${crypto.randomUUID()}`
4381
+ });
4382
+ console.log(renderTopup(result));
4383
+ })
4384
+ );
4385
+ billing.command("auto-topup").description("show auto top-up, or change it: --on/--off, --threshold <5|10|20>, --amount <10|20|50|100>").option("--on", "charge the saved card when the balance falls under the threshold").option("--off", "stop charging automatically").option("--threshold <dollars>", "the balance that triggers a charge").option("--amount <dollars>", "the credit each charge adds").action(
4386
+ run(async (opts) => {
4387
+ const body = autoTopupBody(opts);
4388
+ const settings = body ? await api("PATCH", "/billing/auto-topup", body) : await api("GET", "/billing/auto-topup");
4389
+ console.log(renderAutoTopup(settings));
4390
+ })
4391
+ );
4392
+ var usageOptions = (command) => command.option("--from <day>", "first UTC day, YYYY-MM-DD (default: 29 days ago)").option("--to <day>", "first UTC day not included (default: tomorrow)").option("--source <source>", "session or api_key (default: both)").option("--key <id>", "only this API key");
4393
+ usageOptions(
4394
+ billing.command("usage").description("what you spent over a range of days, across sessions and API keys").option("--by <dimension>", "day, kind, session or api_key (default day)").option("--json", "print the API's answer as JSON")
4395
+ ).action(
4396
+ run(async (opts) => {
4397
+ const summary = await api("GET", `/billing/usage${usageQuery({ ...opts, groupBy: opts.by })}`);
4398
+ console.log(opts.json ? JSON.stringify(summary, null, 2) : renderUsage(summary));
4399
+ })
4400
+ );
4401
+ usageOptions(
4402
+ billing.command("export").description("every usage record in the range, as CSV or JSONL").option("--format <format>", "csv or jsonl (default csv)").option("-o, --out <path>", "write to a file instead of printing")
4403
+ ).action(
4404
+ run(async (opts) => {
4405
+ const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
4406
+ if (opts.out) {
4407
+ writeFileSync6(opts.out, bytes);
4408
+ console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
4409
+ } else {
4410
+ process.stdout.write(bytes);
4411
+ }
4412
+ })
4413
+ );
4414
+ var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
4415
+ keys.command("list").action(
4416
+ run(async () => {
4417
+ const { api_keys: list } = await api("GET", "/api_keys");
4418
+ if (!list.length) console.log("no API keys");
4419
+ for (const key of list) console.log(renderApiKey(key));
4420
+ })
4421
+ );
4422
+ keys.command("create <name>").description("make a key; its secret is printed once (`sky keys reveal` reads it again where allowed)").option("--scopes <list>", "comma-separated: read, write, admin (default read,write)").option("--workspace <slug>", 'fix the key to one workspace, or "personal" for none').option("--expires <iso>", "when the key stops working, e.g. 2027-01-01T00:00:00Z").action(
4423
+ run(async (name, opts) => {
4424
+ const { api_key: key, secret } = await api("POST", "/api_keys", apiKeyBody(name, opts));
4425
+ console.error(renderApiKey(key));
4426
+ console.log(secret);
4427
+ })
4428
+ );
4429
+ keys.command("reveal <id>").description("print a key's secret again").action(
4430
+ run(async (id) => {
4431
+ const { secret } = await api("POST", `/api_keys/${encodeURIComponent(id)}/reveal`);
4432
+ console.log(secret);
4433
+ })
4434
+ );
4435
+ for (const [verb, blurb] of [
4436
+ ["revoke", "the key stops working at once"],
4437
+ ["restore", "a revoked key works again"]
4438
+ ]) {
4439
+ keys.command(`${verb} <id>`).description(blurb).action(
4440
+ run(async (id) => {
4441
+ const { api_key: key } = await api("POST", `/api_keys/${encodeURIComponent(id)}/${verb}`);
4442
+ console.log(renderApiKey(key));
4443
+ })
4444
+ );
4445
+ }
4446
+ var memory = program.command("memory").description("your personal memory: what every session you start is told about you");
4447
+ async function writeMemory(operation) {
4448
+ const current = await api("GET", "/memory");
4449
+ console.log(renderMemory(await api("POST", "/memory", memoryBody(operation, current.version))));
4450
+ }
4451
+ memory.command("list").action(run(async () => console.log(renderMemory(await api("GET", "/memory")))));
4452
+ memory.command("save <text...>").description("add an entry").action(run(async (text) => writeMemory({ op: "save", text: text.join(" ") })));
4453
+ memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
4454
+ memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
4455
+ memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
4456
+ var context = program.command("context").description("search your conversations, memory and the workspace trace library");
4457
+ context.command("search <words...>").description("literal matches, in source order; follow --cursor for more").option("--scope <scope>", "sessions, memory or traces (default sessions)").option("--limit <n>", "hits per page (default 20)").option("--cursor <cursor>", "continue a previous search").option("--since <iso>", "inclusive lower time bound").option("--until <iso>", "exclusive upper time bound").option("--json", "print the API's answer as JSON").action(
4458
+ run(async (words, opts) => {
4459
+ const page = await api("GET", `/context/search${searchQuery(words.join(" "), opts)}`);
4460
+ console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
4461
+ })
4462
+ );
4463
+ context.command("read <ref>").description("read a search hit in full: <ref> is the JSON reference the search printed").option("--offset <n>", "continue a long message from this offset").action(
4464
+ run(async (ref, opts) => {
4465
+ const params = new URLSearchParams({ ref });
4466
+ if (opts.offset) params.set("offset", opts.offset);
4467
+ const read = await api("GET", `/context/read?${params}`);
4468
+ console.log(read.text);
4469
+ if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
4470
+ })
4471
+ );
4472
+ var webhookLine = (w) => `${w.id} ${w.status.padEnd(8)} ${w.url} ${w.events.join(",")}${w.failure_count ? ` (${w.failure_count} failures)` : ""}`;
4473
+ var webhook = program.command("webhook").description("your HTTPS endpoints for platform events, signed with a whsec_ secret");
4474
+ webhook.command("list").action(
4475
+ run(async () => {
4476
+ const { webhooks } = await api("GET", "/webhooks");
4477
+ if (!webhooks.length) console.log("no webhooks");
4478
+ for (const w of webhooks) console.log(webhookLine(w));
4479
+ })
4480
+ );
4481
+ webhook.command("create <url>").description("register an endpoint; its signing secret is printed once").requiredOption("--event <type...>", "event types to deliver, e.g. goal.completed routine_run.failed").action(
4482
+ run(async (url, opts) => {
4483
+ const { webhook: created } = await api("POST", "/webhooks", { url, events: opts.event });
4484
+ console.error(webhookLine(created));
4485
+ if (created.secret) console.log(created.secret);
4486
+ })
4487
+ );
4488
+ webhook.command("enable <id>").description("re-enable an endpoint that was disabled after failed deliveries").action(
4489
+ run(async (id) => {
4490
+ const { webhook: updated } = await api("PATCH", `/webhooks/${encodeURIComponent(id)}`, { status: "active" });
4491
+ console.log(webhookLine(updated));
4492
+ })
4493
+ );
4494
+ webhook.command("rm <id>").description("delete an endpoint").action(
4495
+ run(async (id) => {
4496
+ await api("DELETE", `/webhooks/${encodeURIComponent(id)}`);
4497
+ console.log(`Deleted ${id}`);
4498
+ })
4499
+ );
4101
4500
  var harness = program.command("harness").description("workspace harnesses: init, verify, push");
4102
4501
  harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
4103
4502
  harnessInit(dir ?? ".");
@@ -4121,7 +4520,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
4121
4520
  }));
4122
4521
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
4123
4522
  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) => {
4124
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-MPGQYXNR.js");
4523
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DS35CFKV.js");
4125
4524
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
4126
4525
  const scopes = parseContentScopes(opts.capture);
4127
4526
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -4150,13 +4549,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
4150
4549
  ]);
4151
4550
  console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
4152
4551
  console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
4153
- if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve6(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
4552
+ if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve7(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
4154
4553
  console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
4155
4554
  console.log(` sky trace show ${trace.id}`);
4156
4555
  }));
4157
4556
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
4158
4557
  try {
4159
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-MPGQYXNR.js");
4558
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DS35CFKV.js");
4160
4559
  const chunks = [];
4161
4560
  for await (const chunk of process.stdin) chunks.push(chunk);
4162
4561
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -4169,14 +4568,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
4169
4568
  }
4170
4569
  });
4171
4570
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
4172
- const { readState, flush, grantSender } = await import("./trace-MPGQYXNR.js");
4571
+ const { readState, flush, grantSender } = await import("./trace-DS35CFKV.js");
4173
4572
  const state = readState();
4174
4573
  if (!state) throw new Error("no trace is running here");
4175
4574
  const outcome = await flush(grantSender(state), { attempts: 4 });
4176
4575
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
4177
4576
  }));
4178
4577
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
4179
- const { readState, spoolStatus } = await import("./trace-MPGQYXNR.js");
4578
+ const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
4180
4579
  const state = readState();
4181
4580
  if (!state) {
4182
4581
  console.log("No trace is running here.");
@@ -4186,7 +4585,7 @@ traceCmd.command("status").description("the running trace and its spool").action
4186
4585
  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`);
4187
4586
  }));
4188
4587
  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) => {
4189
- const { readState } = await import("./trace-MPGQYXNR.js");
4588
+ const { readState } = await import("./trace-DS35CFKV.js");
4190
4589
  const ref = id ?? readState()?.trace_id;
4191
4590
  if (!ref) throw new Error("name a trace, or start one here");
4192
4591
  const bundle = await api("GET", `/traces/${ref}`);
@@ -4213,7 +4612,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
4213
4612
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
4214
4613
  }));
4215
4614
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
4216
- const { readState, record, flush, grantSender, clearState } = await import("./trace-MPGQYXNR.js");
4615
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-DS35CFKV.js");
4217
4616
  const state = readState();
4218
4617
  if (!state) throw new Error("no trace is running here");
4219
4618
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -4222,6 +4621,138 @@ traceCmd.command("stop").description("end the root span, send what is left, and
4222
4621
  clearState();
4223
4622
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
4224
4623
  }));
4624
+ traceCmd.command("sync [payload]").description("upload this machine's Claude Code and Codex sessions 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("--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) => {
4625
+ const lib = await import("./trace-library-B3NZ4OQM.js");
4626
+ if (opts.hook) {
4627
+ try {
4628
+ let raw = payload ?? "";
4629
+ if (!raw && !process.stdin.isTTY) {
4630
+ const chunks = [];
4631
+ for await (const chunk of process.stdin) chunks.push(chunk);
4632
+ raw = Buffer.concat(chunks).toString("utf8");
4633
+ }
4634
+ if (!lib.loadSyncConfig().enabled) return;
4635
+ const ref = lib.hookSessionRef(raw);
4636
+ const { spawn: spawn4 } = await import("child_process");
4637
+ spawn4(process.execPath, [process.argv[1], "trace", "sync", "--quiet", ...ref ? ["--session", ref] : []], { detached: true, stdio: "ignore" }).unref();
4638
+ } catch {
4639
+ }
4640
+ return;
4641
+ }
4642
+ await run(async () => {
4643
+ const config = lib.loadSyncConfig();
4644
+ if (opts.disable) {
4645
+ lib.saveSyncConfig({ ...config, enabled: false });
4646
+ console.log("Trace sync is off on this machine. Uploaded traces stay in their workspaces until deleted there.");
4647
+ return;
4648
+ }
4649
+ const changedSettings = opts.enable || opts.route || opts.default || opts.keepLocal;
4650
+ if (changedSettings) {
4651
+ if (opts.enable && !apiToken()) throw new Error("not logged in \u2014 run `sky auth login` first");
4652
+ const rules = opts.route ? opts.route.map((spec) => {
4653
+ const at = spec.lastIndexOf("=");
4654
+ if (at <= 0 || at === spec.length - 1) throw new Error(`--route takes match=workspace, got "${spec}"`);
4655
+ return { match: spec.slice(0, at), workspace: spec.slice(at + 1) };
4656
+ }) : config.rules;
4657
+ const next = {
4658
+ ...config,
4659
+ rules,
4660
+ enabled: opts.enable ? true : config.enabled,
4661
+ enabledAt: opts.enable ? (/* @__PURE__ */ new Date()).toISOString() : config.enabledAt,
4662
+ fallback: opts.keepLocal ? void 0 : opts.default ?? config.fallback
4663
+ };
4664
+ lib.saveSyncConfig(next);
4665
+ console.log(`Trace sync is ${next.enabled ? "on" : "off"} on this machine. It reads only:`);
4666
+ for (const folder of lib.sourceFolders()) console.log(` ${folder}`);
4667
+ console.log("and sends each session, redacted here first, to:");
4668
+ for (const rule of next.rules) console.log(` folder path contains "${rule.match}" \u2192 ${rule.workspace}`);
4669
+ console.log(` anything else \u2192 ${next.fallback ?? "stays on this machine"}`);
4670
+ console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
4671
+ }
4672
+ if (opts.installSchedule) {
4673
+ const { join: join11 } = await import("path");
4674
+ const { homedir: homedir7 } = await import("os");
4675
+ const { execFileSync } = await import("child_process");
4676
+ const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
4677
+ const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
4678
+ try {
4679
+ execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
4680
+ } catch {
4681
+ }
4682
+ execFileSync("launchctl", ["load", plist]);
4683
+ console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
4684
+ }
4685
+ if (opts.status) {
4686
+ const { readFileSync: readFileSync13 } = await import("fs");
4687
+ const { join: join11 } = await import("path");
4688
+ let state = { sessions: {} };
4689
+ try {
4690
+ state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4691
+ } catch {
4692
+ }
4693
+ const counts = /* @__PURE__ */ new Map();
4694
+ for (const entry2 of Object.values(state.sessions)) {
4695
+ const label = entry2.status === "stored" ? `stored in ${entry2.workspace}` : entry2.status;
4696
+ counts.set(label, (counts.get(label) ?? 0) + 1);
4697
+ }
4698
+ console.log(`Trace sync is ${config.enabled ? "on" : "off"}${config.enabledAt ? ` (since ${config.enabledAt})` : ""}. Last full sweep: ${state.lastFullAt ?? "never"}.`);
4699
+ for (const [label, count] of [...counts].sort((a, b) => b[1] - a[1])) console.log(` ${count} ${label}`);
4700
+ return;
4701
+ }
4702
+ if (changedSettings && !opts.full && !opts.session) return;
4703
+ const report = await lib.syncTraces({
4704
+ full: opts.full,
4705
+ only: opts.session,
4706
+ dryRun: opts.dryRun,
4707
+ limit: opts.limit,
4708
+ onProgress: opts.quiet ? void 0 : (line) => console.log(line)
4709
+ });
4710
+ if (!report) {
4711
+ if (!opts.quiet) console.log("Another sync is running on this machine; it will pick these up.");
4712
+ return;
4713
+ }
4714
+ if (!opts.quiet || report.failed) {
4715
+ const where = Object.entries(report.byWorkspace).map(([workspace2, count]) => `${count} \u2192 ${workspace2}`).join(", ");
4716
+ console.log(`${opts.dryRun ? "Would upload" : "Uploaded"} ${report.uploaded}${where ? ` (${where})` : ""}; unchanged ${report.unchanged}; kept local ${report.local}; empty ${report.empty}; another member's ${report.owned}; failed ${report.failed}. Scanned ${report.scanned}, ${report.changed} changed.`);
4717
+ for (const error of report.errors.slice(0, 20)) console.error(` ${error}`);
4718
+ }
4719
+ if (report.failed) process.exitCode = 1;
4720
+ })();
4721
+ });
4722
+ traceCmd.command("search <query...>").description("search every session in a workspace's trace library (literal substring)").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", (v) => Number.parseInt(v, 10), 20).option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
4723
+ const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
4724
+ const workspace2 = opts.workspace ?? currentUniverse();
4725
+ if (!workspace2) throw new Error("name a workspace with --workspace");
4726
+ const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
4727
+ const page = await libraryRequest("GET", `/context/search?${params}`, workspace2);
4728
+ if (opts.json) {
4729
+ console.log(JSON.stringify(page, null, 2));
4730
+ return;
4731
+ }
4732
+ if (!page.hits.length) console.log(page.partial ? `No hits, but the search was cut short (${page.coverage}).` : "No hits.");
4733
+ for (const { hit } of page.hits) {
4734
+ console.log(`${hit.occurredAt?.slice(0, 16).replace("T", " ") ?? " "} ${hit.title}`);
4735
+ console.log(` ${hit.snippet.replace(/\s+/g, " ")}`);
4736
+ }
4737
+ if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
4738
+ }));
4739
+ 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) => {
4740
+ const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
4741
+ const workspace2 = opts.workspace ?? currentUniverse();
4742
+ if (!workspace2) throw new Error("name a workspace with --workspace");
4743
+ const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
4744
+ if (opts.json) {
4745
+ console.log(JSON.stringify(page, null, 2));
4746
+ return;
4747
+ }
4748
+ console.log(`${page.total} sessions in ${workspace2}'s trace library`);
4749
+ for (const trace of page.traces) {
4750
+ const models = trace.models?.length ? `${trace.models[0]}${trace.models.length > 1 ? ` +${trace.models.length - 1}` : ""}` : "";
4751
+ const output = trace.usage?.output_tokens ? `${trace.usage.output_tokens.toLocaleString("en-US")} out` : "";
4752
+ const source = [models, output].filter(Boolean).join(" \xB7 ");
4753
+ console.log(`${trace.updated_at.slice(0, 16).replace("T", " ")} ${trace.harness.padEnd(11)} ${(trace.project ?? "").padEnd(16)} ${String(trace.messages).padStart(5)} msgs ${trace.title ?? trace.session_id}${source ? ` (${source})` : ""}`);
4754
+ }
4755
+ }));
4225
4756
  var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");
4226
4757
  workerCmd.command("start <pool>").description("start a worker connected to the given pool (wpool_\u2026 id)").option("--token <token>", "bearer token (default: $SKY_WORKER_TOKEN)").option("--label <label>", "a human-readable name for this machine", "cli").action(async (poolId, options) => {
4227
4758
  const { startWorker } = await import("./worker-6Q4K4HZW.js");