@agentsky/sky 2.12.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
@@ -9,7 +9,7 @@ import {
9
9
  resolveSessionRef,
10
10
  streamEvents,
11
11
  uploadFile
12
- } from "./chunk-7LUCHBX6.js";
12
+ } from "./chunk-XJ5JLLXM.js";
13
13
  import {
14
14
  CLI_VERSION,
15
15
  USER_AGENT,
@@ -20,12 +20,13 @@ import {
20
20
  currentUniverse,
21
21
  loadConfig,
22
22
  saveConfig
23
- } from "./chunk-RF5JICNX.js";
23
+ } from "./chunk-Z3FPPGWX.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
27
27
  MODEL_SUBSCRIPTION_PROVIDER_NAME,
28
28
  REASONING_EFFORT_VALUES,
29
+ SessionSourceCollector,
29
30
  capabilitiesSchema,
30
31
  customDataSchema,
31
32
  defaultLlm,
@@ -39,12 +40,12 @@ import {
39
40
  skillRefSchema,
40
41
  splitMcpServers,
41
42
  supersededBy
42
- } from "./chunk-XXDQKVPS.js";
43
+ } from "./chunk-2IKEJ74C.js";
43
44
  import "./chunk-MLKGABMK.js";
44
45
 
45
46
  // src/index.ts
46
- import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
47
- 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";
48
49
  import { createInterface as createInterface4 } from "readline";
49
50
  import { Command } from "commander";
50
51
 
@@ -167,6 +168,123 @@ function renderGoal(goal2) {
167
168
  return lines.join("\n");
168
169
  }
169
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
+
170
288
  // src/agent-file.ts
171
289
  import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
172
290
  import { dirname, resolve } from "path";
@@ -702,10 +820,222 @@ async function clone(opts) {
702
820
  }
703
821
 
704
822
  // src/import-local.ts
705
- import { closeSync, existsSync as existsSync3, openSync, readdirSync, readFileSync as readFileSync4, readSync, statSync } from "fs";
706
- import { homedir as homedir2 } from "os";
707
- 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";
708
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
709
1039
  function redactLine2(raw) {
710
1040
  return redactLine(raw).line;
711
1041
  }
@@ -777,13 +1107,13 @@ function sniffTitle(filePath) {
777
1107
  }
778
1108
  }
779
1109
  function listClaudeCodeSessions(limit) {
780
- const root = process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
781
- const projectsDir = join2(root, "projects");
782
- 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 [];
783
1113
  const sessions = [];
784
1114
  try {
785
1115
  for (const encoded of readdirSync(projectsDir)) {
786
- const projectDir = join2(projectsDir, encoded);
1116
+ const projectDir = join3(projectsDir, encoded);
787
1117
  let entries;
788
1118
  try {
789
1119
  entries = readdirSync(projectDir);
@@ -794,22 +1124,22 @@ function listClaudeCodeSessions(limit) {
794
1124
  for (const entry2 of entries) {
795
1125
  if (!entry2.endsWith(".jsonl")) continue;
796
1126
  const sessionId = entry2.slice(0, -6);
797
- const mainFile = join2(projectDir, entry2);
1127
+ const mainFile = join3(projectDir, entry2);
798
1128
  let stat;
799
1129
  try {
800
- stat = statSync(mainFile);
1130
+ stat = statSync2(mainFile);
801
1131
  } catch {
802
1132
  continue;
803
1133
  }
804
1134
  if (!stat.isFile()) continue;
805
1135
  let discoveryError;
806
1136
  const subagentFiles = [];
807
- const subagentDir = join2(projectDir, sessionId, "subagents");
808
- if (existsSync3(subagentDir)) {
1137
+ const subagentDir = join3(projectDir, sessionId, "subagents");
1138
+ if (existsSync4(subagentDir)) {
809
1139
  try {
810
1140
  const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
811
1141
  for (const f of subFiles) {
812
- subagentFiles.push(join2(subagentDir, f));
1142
+ subagentFiles.push(join3(subagentDir, f));
813
1143
  }
814
1144
  } catch {
815
1145
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -831,7 +1161,7 @@ function listClaudeCodeSessions(limit) {
831
1161
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
832
1162
  }
833
1163
  function listCodexSessions(limit) {
834
- 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");
835
1165
  const records = [];
836
1166
  function walk(dir) {
837
1167
  let entries;
@@ -841,7 +1171,7 @@ function listCodexSessions(limit) {
841
1171
  return;
842
1172
  }
843
1173
  for (const entry2 of entries) {
844
- const full = join2(dir, entry2.name);
1174
+ const full = join3(dir, entry2.name);
845
1175
  if (entry2.isDirectory()) {
846
1176
  walk(full);
847
1177
  continue;
@@ -859,7 +1189,7 @@ function listCodexSessions(limit) {
859
1189
  sessionId: data.id,
860
1190
  workspace: typeof data.cwd === "string" ? data.cwd : dir,
861
1191
  title: sniffTitle(full),
862
- mtimeMs: statSync(full).mtimeMs,
1192
+ mtimeMs: statSync2(full).mtimeMs,
863
1193
  files: [full]
864
1194
  },
865
1195
  ...typeof parent === "string" ? { parent } : {}
@@ -928,7 +1258,7 @@ async function mintTrace(base, harness2) {
928
1258
  function loadSummaryFile(summaryPath) {
929
1259
  let raw;
930
1260
  try {
931
- raw = readFileSync4(summaryPath, "utf8");
1261
+ raw = readFileSync5(summaryPath, "utf8");
932
1262
  } catch {
933
1263
  throw new Error(`Cannot read summary file: ${summaryPath}`);
934
1264
  }
@@ -945,7 +1275,16 @@ function loadSummaryFile(summaryPath) {
945
1275
  const redacted = redactLine2(raw.trimEnd());
946
1276
  return redacted;
947
1277
  }
948
- 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 = {}) {
949
1288
  if (session2.discoveryError) throw new Error(session2.discoveryError);
950
1289
  const maximum = 16 * 1024 * 1024;
951
1290
  let inputBytes = 0;
@@ -957,10 +1296,12 @@ function prepareImport(session2, summaryPath) {
957
1296
  let encryptedReasoningCount = 0;
958
1297
  let compactionCount = 0;
959
1298
  let unreadableCount = 0;
1299
+ const source = new SessionSourceCollector();
1300
+ const records = [];
960
1301
  for (const path of session2.files) {
961
- const stat = statSync(path);
1302
+ const stat = statSync2(path);
962
1303
  if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
963
- const text = readFileSync4(path, "utf8");
1304
+ const text = readFileSync5(path, "utf8");
964
1305
  for (const raw of text.split("\n")) {
965
1306
  if (!raw.trim()) continue;
966
1307
  let record;
@@ -973,6 +1314,8 @@ function prepareImport(session2, summaryPath) {
973
1314
  if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
974
1315
  const rec = record;
975
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);
976
1319
  if (rec.type === "response_item") {
977
1320
  const payload = rec.payload;
978
1321
  if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
@@ -996,6 +1339,9 @@ function prepareImport(session2, summaryPath) {
996
1339
  if (!lines.length) throw new Error("The selected transcript is empty.");
997
1340
  const sourceId = nativeId ?? session2.sessionId;
998
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).` : "";
999
1345
  let summaryJson;
1000
1346
  if (summaryPath) {
1001
1347
  summaryJson = loadSummaryFile(summaryPath);
@@ -1004,10 +1350,10 @@ function prepareImport(session2, summaryPath) {
1004
1350
  format: "asteroids.session-summary",
1005
1351
  format_version: 1,
1006
1352
  source_harness: session2.harness,
1007
- source_harness_version: null,
1353
+ source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
1008
1354
  source_session_id: sourceId,
1009
1355
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
1010
- 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.`,
1011
1357
  context: {
1012
1358
  objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
1013
1359
  constraints: [],
@@ -1015,19 +1361,19 @@ function prepareImport(session2, summaryPath) {
1015
1361
  completed: [],
1016
1362
  pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
1017
1363
  verification: [],
1018
- blockers: ["This handoff contains the recorded conversation only. Referenced local files, runtime state, and unreadable reasoning are not transferred."],
1019
- 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).` })),
1020
1366
  provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
1021
1367
  }
1022
1368
  };
1023
1369
  summaryJson = JSON.stringify(summary);
1024
1370
  }
1025
- 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";
1026
1372
  const gaps = [];
1027
1373
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1028
1374
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1029
1375
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1030
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
1376
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
1031
1377
  }
1032
1378
  async function importSession(opts) {
1033
1379
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1036,7 +1382,7 @@ async function importSession(opts) {
1036
1382
  throw new Error("Import requires HTTPS, or a loopback development server.");
1037
1383
  }
1038
1384
  opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
1039
- 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 });
1040
1386
  let traceId;
1041
1387
  let key;
1042
1388
  if (opts.importKey && opts.grantTraceId) {
@@ -1068,7 +1414,9 @@ async function importSession(opts) {
1068
1414
  transcriptFiles: opts.session.files.length,
1069
1415
  bytes: outputBytes,
1070
1416
  redactionCount,
1071
- gaps
1417
+ gaps,
1418
+ attachedFiles,
1419
+ leftOutFiles
1072
1420
  };
1073
1421
  }
1074
1422
 
@@ -1105,7 +1453,7 @@ async function login(opts) {
1105
1453
  }
1106
1454
  async function browserLogin(verifier, challenge) {
1107
1455
  const state = b64url(randomBytes(16));
1108
- const code = await new Promise((resolve7, reject) => {
1456
+ const code = await new Promise((resolve8, reject) => {
1109
1457
  const server = createServer((req, res) => {
1110
1458
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
1111
1459
  if (url.pathname !== "/callback") {
@@ -1118,7 +1466,7 @@ async function browserLogin(verifier, challenge) {
1118
1466
  if (url.searchParams.get("state") !== state || !got) {
1119
1467
  reject(new Error("login callback state mismatch"));
1120
1468
  } else {
1121
- resolve7(got);
1469
+ resolve8(got);
1122
1470
  }
1123
1471
  server.close();
1124
1472
  });
@@ -1172,23 +1520,23 @@ async function headlessLogin(verifier, challenge) {
1172
1520
  }
1173
1521
 
1174
1522
  // src/local-agents.ts
1175
- import { existsSync as existsSync4 } from "fs";
1176
- import { homedir as homedir3 } from "os";
1177
- import { join as join3 } from "path";
1178
- function localAgents(env = process.env, home = homedir3()) {
1179
- const claude = env.CLAUDE_CONFIG_DIR || join3(home, ".claude");
1180
- const codex = env.CODEX_HOME || join3(home, ".codex");
1181
- 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");
1182
1530
  return [
1183
- { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join3(claude, "skills") },
1184
- { id: "codex", name: "Codex", root: codex, skillsDir: join3(codex, "skills") },
1185
- { 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") }
1186
1534
  ];
1187
1535
  }
1188
- function detectLocalAgents(env = process.env, home = homedir3()) {
1189
- 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));
1190
1538
  }
1191
- function selectLocalAgents(ids, env = process.env, home = homedir3()) {
1539
+ function selectLocalAgents(ids, env = process.env, home = homedir4()) {
1192
1540
  if (!ids?.length) return detectLocalAgents(env, home);
1193
1541
  const known = localAgents(env, home);
1194
1542
  return ids.map((id) => {
@@ -1282,21 +1630,21 @@ async function readTurn(sessionId, opts = {}) {
1282
1630
  for (; ; ) {
1283
1631
  const reading = latestTurn(await history(sessionId));
1284
1632
  if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
1285
- await new Promise((resolve7) => setTimeout(resolve7, opts.intervalMs ?? 3e3));
1633
+ await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
1286
1634
  }
1287
1635
  }
1288
1636
 
1289
1637
  // src/setup.ts
1290
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1291
- 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";
1292
1640
  import { createInterface as createInterface3 } from "readline";
1293
1641
 
1294
1642
  // src/mcp-register.ts
1295
1643
  import { spawnSync } from "child_process";
1296
1644
 
1297
1645
  // src/skill.ts
1298
- import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1299
- import { join as join4 } from "path";
1646
+ import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1647
+ import { join as join5 } from "path";
1300
1648
 
1301
1649
  // src/catalog-file.ts
1302
1650
  var CATALOG_FILE = "catalog.md";
@@ -1737,7 +2085,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
1737
2085
  }
1738
2086
  function installedVersion(dir) {
1739
2087
  try {
1740
- const marker = JSON.parse(readFileSync5(join4(dir, SKILL_MARKER), "utf8"));
2088
+ const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
1741
2089
  return typeof marker.version === "string" ? marker.version : null;
1742
2090
  } catch {
1743
2091
  return null;
@@ -1747,25 +2095,25 @@ function isInstalledSkill(dir) {
1747
2095
  return installedVersion(dir) !== null;
1748
2096
  }
1749
2097
  function installSkill(agent2, opts = {}) {
1750
- const dir = join4(agent2.skillsDir, SKILL_NAME);
2098
+ const dir = join5(agent2.skillsDir, SKILL_NAME);
1751
2099
  const previous = installedVersion(dir);
1752
2100
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
1753
2101
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
1754
2102
  if (outcome !== "current") {
1755
2103
  mkdirSync(dir, { recursive: true });
1756
- writeFileSync3(join4(dir, "SKILL.md"), skillDocument());
1757
- 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)}
1758
2106
  `);
1759
2107
  }
1760
2108
  if (opts.catalog) {
1761
2109
  mkdirSync(dir, { recursive: true });
1762
- 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()));
1763
2111
  }
1764
- const refsDir = join4(dir, REFERENCES_DIR);
2112
+ const refsDir = join5(dir, REFERENCES_DIR);
1765
2113
  mkdirSync(refsDir, { recursive: true });
1766
- writeFileSync3(join4(refsDir, "import.md"), importReferenceDoc());
1767
- writeFileSync3(join4(refsDir, "workspaces.md"), workspacesReferenceDoc());
1768
- 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());
1769
2117
  return { agent: agent2.id, path: dir, outcome };
1770
2118
  }
1771
2119
 
@@ -1816,9 +2164,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
1816
2164
  }
1817
2165
 
1818
2166
  // src/setup-local.ts
1819
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
1820
- import { homedir as homedir4 } from "os";
1821
- 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";
1822
2170
  import { parse as parseToml2 } from "smol-toml";
1823
2171
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
1824
2172
  var MAX_IMPORT_DEPTH = 5;
@@ -1834,11 +2182,11 @@ function sensitive(name) {
1834
2182
  function skillFiles(root, prefix) {
1835
2183
  const files2 = [];
1836
2184
  for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
1837
- const path = join5(root, entry2.name);
2185
+ const path = join6(root, entry2.name);
1838
2186
  if (entry2.isDirectory()) {
1839
2187
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
1840
- } else if (entry2.isFile() && !sensitive(entry2.name) && statSync2(path).size <= MAX_SKILL_FILE_BYTES) {
1841
- 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)) });
1842
2190
  }
1843
2191
  }
1844
2192
  return files2;
@@ -1847,19 +2195,19 @@ function readLocalSkills(agents) {
1847
2195
  const skills2 = [];
1848
2196
  const ignored = [];
1849
2197
  for (const agent2 of agents) {
1850
- if (!existsSync5(agent2.skillsDir)) continue;
2198
+ if (!existsSync6(agent2.skillsDir)) continue;
1851
2199
  for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
1852
- const dir = join5(agent2.skillsDir, entry2.name);
2200
+ const dir = join6(agent2.skillsDir, entry2.name);
1853
2201
  let isDir = entry2.isDirectory();
1854
2202
  if (entry2.isSymbolicLink()) {
1855
2203
  try {
1856
- isDir = statSync2(dir).isDirectory();
2204
+ isDir = statSync3(dir).isDirectory();
1857
2205
  } catch {
1858
2206
  isDir = false;
1859
2207
  }
1860
2208
  }
1861
- if (!isDir || !existsSync5(join5(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
1862
- 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"));
1863
2211
  if (!name) {
1864
2212
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
1865
2213
  continue;
@@ -1875,7 +2223,7 @@ function readLocalSkills(agents) {
1875
2223
  }
1876
2224
  return { skills: skills2, ignored };
1877
2225
  }
1878
- var INSTRUCTION_FILES = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
2226
+ var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
1879
2227
  function inlineImports(content, from, home, depth, seen) {
1880
2228
  let fence;
1881
2229
  return content.split(/\r?\n/).map((line) => {
@@ -1887,11 +2235,11 @@ function inlineImports(content, from, home, depth, seen) {
1887
2235
  }
1888
2236
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
1889
2237
  if (!target || depth >= MAX_IMPORT_DEPTH) return line;
1890
- 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);
1891
2239
  if (seen.includes(path)) return "";
1892
2240
  let imported;
1893
2241
  try {
1894
- imported = readFileSync6(path, "utf8");
2242
+ imported = readFileSync7(path, "utf8");
1895
2243
  } catch {
1896
2244
  return line;
1897
2245
  }
@@ -1899,15 +2247,15 @@ function inlineImports(content, from, home, depth, seen) {
1899
2247
  return inlineImports(imported, path, home, depth + 1, seen).trim();
1900
2248
  }).join("\n");
1901
2249
  }
1902
- function readGlobalInstructions(agents, home = homedir4()) {
2250
+ function readGlobalInstructions(agents, home = homedir5()) {
1903
2251
  const found = [];
1904
2252
  for (const agent2 of agents) {
1905
- const file = INSTRUCTION_FILES[agent2.id];
2253
+ const file = INSTRUCTION_FILES2[agent2.id];
1906
2254
  if (!file) continue;
1907
- const path = join5(agent2.root, file);
2255
+ const path = join6(agent2.root, file);
1908
2256
  let raw;
1909
2257
  try {
1910
- raw = readFileSync6(path, "utf8");
2258
+ raw = readFileSync7(path, "utf8");
1911
2259
  } catch {
1912
2260
  continue;
1913
2261
  }
@@ -1926,7 +2274,7 @@ ${item.content.trim()}`).join("\n\n");
1926
2274
  }
1927
2275
  function readJson2(path) {
1928
2276
  try {
1929
- return JSON.parse(readFileSync6(path, "utf8"));
2277
+ return JSON.parse(readFileSync7(path, "utf8"));
1930
2278
  } catch {
1931
2279
  return void 0;
1932
2280
  }
@@ -1940,15 +2288,15 @@ function bearerOf(def) {
1940
2288
  return match?.[1] ?? null;
1941
2289
  }
1942
2290
  function serversOf(agent2, home) {
1943
- if (agent2.id === "claude_code") return readJson2(join5(home, ".claude.json"))?.mcpServers ?? {};
1944
- 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 ?? {};
1945
2293
  try {
1946
- return parseToml2(readFileSync6(join5(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2294
+ return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
1947
2295
  } catch {
1948
2296
  return {};
1949
2297
  }
1950
2298
  }
1951
- function findLocalSecrets(agents, home = homedir4()) {
2299
+ function findLocalSecrets(agents, home = homedir5()) {
1952
2300
  const secrets2 = [];
1953
2301
  for (const agent2 of agents) {
1954
2302
  for (const [name, def] of Object.entries(serversOf(agent2, home))) {
@@ -1964,9 +2312,9 @@ function findLocalSecrets(agents, home = homedir4()) {
1964
2312
 
1965
2313
  // src/subscriptions.ts
1966
2314
  import { spawn as spawn3 } from "child_process";
1967
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1968
- import { homedir as homedir5 } from "os";
1969
- 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";
1970
2318
  import { createInterface as createInterface2 } from "readline";
1971
2319
  var ALIASES = {
1972
2320
  claude: "anthropic",
@@ -2051,12 +2399,12 @@ async function connectClaude() {
2051
2399
  const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
2052
2400
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
2053
2401
  }
2054
- function codexSignIn(env = process.env, home = homedir5()) {
2055
- const path = join6(env.CODEX_HOME || join6(home, ".codex"), "auth.json");
2056
- 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)) {
2057
2405
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
2058
2406
  }
2059
- const raw = readFileSync7(path, "utf8");
2407
+ const raw = readFileSync8(path, "utf8");
2060
2408
  try {
2061
2409
  const parsed = JSON.parse(raw);
2062
2410
  return { path, raw, account: parsed.tokens?.account_id };
@@ -2064,12 +2412,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
2064
2412
  throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
2065
2413
  }
2066
2414
  }
2067
- function cursorSignIn(env = process.env, home = homedir5()) {
2068
- const path = join6(env.XDG_CONFIG_HOME || join6(home, ".config"), "cursor", "auth.json");
2069
- 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)) {
2070
2418
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
2071
2419
  }
2072
- const raw = readFileSync7(path, "utf8");
2420
+ const raw = readFileSync8(path, "utf8");
2073
2421
  try {
2074
2422
  const parsed = JSON.parse(raw);
2075
2423
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -2148,11 +2496,11 @@ function planProviders(names) {
2148
2496
  }));
2149
2497
  }
2150
2498
  function reportPath() {
2151
- return join7(dirname3(configPath()), "setup.json");
2499
+ return join8(dirname4(configPath()), "setup.json");
2152
2500
  }
2153
2501
  function loadReport() {
2154
2502
  try {
2155
- const saved = JSON.parse(readFileSync8(reportPath(), "utf8"));
2503
+ const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
2156
2504
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
2157
2505
  } catch {
2158
2506
  return { agents: [], plans: [] };
@@ -2165,7 +2513,7 @@ function mergeReport(base, next) {
2165
2513
  return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
2166
2514
  }
2167
2515
  function saveReport(report) {
2168
- mkdirSync2(dirname3(reportPath()), { recursive: true });
2516
+ mkdirSync2(dirname4(reportPath()), { recursive: true });
2169
2517
  writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
2170
2518
  `, { mode: 384 });
2171
2519
  }
@@ -2515,17 +2863,17 @@ function renderArtifact(artifact2) {
2515
2863
  }
2516
2864
 
2517
2865
  // src/skills-store.ts
2518
- import { readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
2519
- 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";
2520
2868
  function skillBundle(dir) {
2521
- const root = resolve4(dir);
2522
- const top = basename3(root);
2869
+ const root = resolve5(dir);
2870
+ const top = basename4(root);
2523
2871
  const files2 = [];
2524
2872
  const walk = (path) => {
2525
2873
  for (const name of readdirSync3(path).sort()) {
2526
- const full = join8(path, name);
2527
- if (statSync3(full).isDirectory()) walk(full);
2528
- 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) });
2529
2877
  }
2530
2878
  };
2531
2879
  walk(root);
@@ -2535,7 +2883,7 @@ function skillBundle(dir) {
2535
2883
  async function uploadSkill(dir, displayName) {
2536
2884
  const form = new FormData();
2537
2885
  for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
2538
- form.append("displayName", displayName ?? basename3(resolve4(dir)));
2886
+ form.append("displayName", displayName ?? basename4(resolve5(dir)));
2539
2887
  return apiForm("POST", "/skills", form);
2540
2888
  }
2541
2889
  async function listSkills() {
@@ -2653,19 +3001,19 @@ function renderPolicy(rule) {
2653
3001
  }
2654
3002
 
2655
3003
  // src/harness.ts
2656
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
2657
- 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";
2658
3006
  var HARNESS_FILE = "harness.json";
2659
3007
  function harnessFilePath(dir) {
2660
- return join9(resolve5(dir), HARNESS_FILE);
3008
+ return join10(resolve6(dir), HARNESS_FILE);
2661
3009
  }
2662
3010
  function readHarnessFile(dir) {
2663
3011
  const path = harnessFilePath(dir);
2664
- if (!existsSync7(path)) {
2665
- 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`);
2666
3014
  }
2667
3015
  try {
2668
- return JSON.parse(readFileSync10(path, "utf8"));
3016
+ return JSON.parse(readFileSync11(path, "utf8"));
2669
3017
  } catch (err) {
2670
3018
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
2671
3019
  }
@@ -2692,10 +3040,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
2692
3040
  `;
2693
3041
  function harnessInit(dir) {
2694
3042
  const path = harnessFilePath(dir);
2695
- if (existsSync7(path)) {
3043
+ if (existsSync8(path)) {
2696
3044
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
2697
3045
  }
2698
- mkdirSync3(dirname4(path), { recursive: true });
3046
+ mkdirSync3(dirname5(path), { recursive: true });
2699
3047
  writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
2700
3048
  `);
2701
3049
  console.log(`Created ${path}`);
@@ -3200,6 +3548,38 @@ session.command("read <ref>").description("what the agent has said since the las
3200
3548
  if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
3201
3549
  })
3202
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
+ );
3203
3583
  var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
3204
3584
  var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
3205
3585
  sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
@@ -3416,9 +3796,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
3416
3796
  );
3417
3797
  secrets.command("list <id>").action(
3418
3798
  run(async (id) => {
3419
- const { secrets: keys } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
3420
- if (!keys.length) console.log("no secrets set");
3421
- 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) {
3422
3802
  console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
3423
3803
  }
3424
3804
  })
@@ -3434,9 +3814,9 @@ secrets.command("unset <id> <key>").action(
3434
3814
  );
3435
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(
3436
3816
  run(async (opts) => {
3437
- const path = resolve6(opts.file);
3438
- if (!existsSync8(path)) {
3439
- 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";
3440
3820
  scaffoldAgentFile(path, name);
3441
3821
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
3442
3822
  }
@@ -3467,7 +3847,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
3467
3847
  );
3468
3848
  program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
3469
3849
  run(async (opts) => {
3470
- const path = resolve6(opts.file);
3850
+ const path = resolve7(opts.file);
3471
3851
  const { file, spec } = readAgentFile(path);
3472
3852
  if (!file.id) {
3473
3853
  console.log("No agent id in the file yet \u2014 running launch instead.");
@@ -3510,7 +3890,7 @@ function formatRelative(msAgo) {
3510
3890
  if (hours < 48) return `${hours}h ago`;
3511
3891
  return `${Math.round(hours / 24)}d ago`;
3512
3892
  }
3513
- 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(
3514
3894
  run(
3515
3895
  async (opts) => {
3516
3896
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -3570,11 +3950,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
3570
3950
  console.log();
3571
3951
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
3572
3952
  const answer = await new Promise(
3573
- (resolve7) => rl.question(
3953
+ (resolve8) => rl.question(
3574
3954
  `Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
3575
3955
  (a) => {
3576
3956
  rl.close();
3577
- resolve7(a.trim());
3957
+ resolve8(a.trim());
3578
3958
  }
3579
3959
  )
3580
3960
  );
@@ -3592,14 +3972,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
3592
3972
  toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
3593
3973
  if (!opts.json) {
3594
3974
  console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
3595
- 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.");
3596
3976
  }
3597
3977
  if (!autoConfirm) {
3598
3978
  if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
3599
3979
  const prompt = createInterface4({ input: process.stdin, output: process.stdout });
3600
- 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) => {
3601
3981
  prompt.close();
3602
- resolve7(value);
3982
+ resolve8(value);
3603
3983
  }));
3604
3984
  if (!/^y(?:es)?$/i.test(answer.trim())) {
3605
3985
  console.log("Cancelled. Nothing uploaded.");
@@ -3617,6 +3997,7 @@ Importing ${label} \u2026
3617
3997
  summaryPath: opts.summary,
3618
3998
  importKey: opts.key,
3619
3999
  grantTraceId: opts.trace,
4000
+ files: opts.files,
3620
4001
  onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
3621
4002
  `)
3622
4003
  });
@@ -3631,9 +4012,12 @@ Importing ${label} \u2026
3631
4012
  bytes: result.bytes,
3632
4013
  redactionCount: result.redactionCount,
3633
4014
  gaps: result.gaps,
4015
+ attachedFiles: result.attachedFiles,
4016
+ leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
3634
4017
  continueUrl: result.url
3635
4018
  }));
3636
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)` : ""}.`);
3637
4021
  console.log(` \u2713 Imported. Continue here:
3638
4022
  ${result.url}
3639
4023
  `);
@@ -3700,7 +4084,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
3700
4084
  );
3701
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(
3702
4086
  run(async (taskId, path, local) => {
3703
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync11(local), mediaTypeFor(local));
4087
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
3704
4088
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
3705
4089
  })
3706
4090
  );
@@ -3865,6 +4249,19 @@ for (const [verb, blurb] of [
3865
4249
  })
3866
4250
  );
3867
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
+ );
3868
4265
  var collect = (value, previous = []) => [...previous, value];
3869
4266
  function goalPath(sessionId, suffix = "") {
3870
4267
  return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
@@ -3959,6 +4356,147 @@ program.command("config").description("show the CLI config location and current
3959
4356
  console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
3960
4357
  console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
3961
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
+ );
3962
4500
  var harness = program.command("harness").description("workspace harnesses: init, verify, push");
3963
4501
  harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
3964
4502
  harnessInit(dir ?? ".");
@@ -3982,7 +4520,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
3982
4520
  }));
3983
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)");
3984
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) => {
3985
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-UJTFSXNM.js");
4523
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DS35CFKV.js");
3986
4524
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
3987
4525
  const scopes = parseContentScopes(opts.capture);
3988
4526
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -4011,13 +4549,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
4011
4549
  ]);
4012
4550
  console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
4013
4551
  console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
4014
- 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"))}`);
4015
4553
  console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
4016
4554
  console.log(` sky trace show ${trace.id}`);
4017
4555
  }));
4018
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 () => {
4019
4557
  try {
4020
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-UJTFSXNM.js");
4558
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DS35CFKV.js");
4021
4559
  const chunks = [];
4022
4560
  for await (const chunk of process.stdin) chunks.push(chunk);
4023
4561
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -4030,14 +4568,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
4030
4568
  }
4031
4569
  });
4032
4570
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
4033
- const { readState, flush, grantSender } = await import("./trace-UJTFSXNM.js");
4571
+ const { readState, flush, grantSender } = await import("./trace-DS35CFKV.js");
4034
4572
  const state = readState();
4035
4573
  if (!state) throw new Error("no trace is running here");
4036
4574
  const outcome = await flush(grantSender(state), { attempts: 4 });
4037
4575
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
4038
4576
  }));
4039
4577
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
4040
- const { readState, spoolStatus } = await import("./trace-UJTFSXNM.js");
4578
+ const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
4041
4579
  const state = readState();
4042
4580
  if (!state) {
4043
4581
  console.log("No trace is running here.");
@@ -4047,7 +4585,7 @@ traceCmd.command("status").description("the running trace and its spool").action
4047
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`);
4048
4586
  }));
4049
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) => {
4050
- const { readState } = await import("./trace-UJTFSXNM.js");
4588
+ const { readState } = await import("./trace-DS35CFKV.js");
4051
4589
  const ref = id ?? readState()?.trace_id;
4052
4590
  if (!ref) throw new Error("name a trace, or start one here");
4053
4591
  const bundle = await api("GET", `/traces/${ref}`);
@@ -4074,7 +4612,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
4074
4612
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
4075
4613
  }));
4076
4614
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
4077
- const { readState, record, flush, grantSender, clearState } = await import("./trace-UJTFSXNM.js");
4615
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-DS35CFKV.js");
4078
4616
  const state = readState();
4079
4617
  if (!state) throw new Error("no trace is running here");
4080
4618
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -4084,7 +4622,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
4084
4622
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
4085
4623
  }));
4086
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) => {
4087
- const lib = await import("./trace-library-474QJTAZ.js");
4625
+ const lib = await import("./trace-library-B3NZ4OQM.js");
4088
4626
  if (opts.hook) {
4089
4627
  try {
4090
4628
  let raw = payload ?? "";
@@ -4132,10 +4670,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4132
4670
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
4133
4671
  }
4134
4672
  if (opts.installSchedule) {
4135
- const { join: join10 } = await import("path");
4136
- const { homedir: homedir6 } = await import("os");
4673
+ const { join: join11 } = await import("path");
4674
+ const { homedir: homedir7 } = await import("os");
4137
4675
  const { execFileSync } = await import("child_process");
4138
- const log = join10(homedir6(), ".config", "sky", "trace-sync.log");
4676
+ const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
4139
4677
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
4140
4678
  try {
4141
4679
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -4145,11 +4683,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4145
4683
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
4146
4684
  }
4147
4685
  if (opts.status) {
4148
- const { readFileSync: readFileSync12 } = await import("fs");
4149
- const { join: join10 } = await import("path");
4686
+ const { readFileSync: readFileSync13 } = await import("fs");
4687
+ const { join: join11 } = await import("path");
4150
4688
  let state = { sessions: {} };
4151
4689
  try {
4152
- state = JSON.parse(readFileSync12(join10(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4690
+ state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4153
4691
  } catch {
4154
4692
  }
4155
4693
  const counts = /* @__PURE__ */ new Map();
@@ -4182,7 +4720,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4182
4720
  })();
4183
4721
  });
4184
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) => {
4185
- const { libraryRequest } = await import("./trace-library-474QJTAZ.js");
4723
+ const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
4186
4724
  const workspace2 = opts.workspace ?? currentUniverse();
4187
4725
  if (!workspace2) throw new Error("name a workspace with --workspace");
4188
4726
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -4199,7 +4737,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
4199
4737
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
4200
4738
  }));
4201
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) => {
4202
- const { libraryRequest } = await import("./trace-library-474QJTAZ.js");
4740
+ const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
4203
4741
  const workspace2 = opts.workspace ?? currentUniverse();
4204
4742
  if (!workspace2) throw new Error("name a workspace with --workspace");
4205
4743
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
@@ -4209,7 +4747,10 @@ traceCmd.command("library").description("list the sessions in a workspace's trac
4209
4747
  }
4210
4748
  console.log(`${page.total} sessions in ${workspace2}'s trace library`);
4211
4749
  for (const trace of page.traces) {
4212
- 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}`);
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})` : ""}`);
4213
4754
  }
4214
4755
  }));
4215
4756
  var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");