@agentsky/sky 2.12.0 → 2.14.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-FOJP33RS.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-VJEE2ALN.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-7OM2B5SE.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 existsSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
48
+ import { basename as basename6, 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";
@@ -581,7 +699,7 @@ async function createClone(local, opts) {
581
699
  console.log("Aborted \u2014 nothing sent.");
582
700
  return;
583
701
  }
584
- const { servers: mcpServers, skipped } = mcpServersFromKept(local.keptServers);
702
+ const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
585
703
  if (skipped.length) {
586
704
  console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
587
705
  }
@@ -589,7 +707,7 @@ async function createClone(local, opts) {
589
707
  agentType: local.harness,
590
708
  ...local.model ? { llm: local.model } : {},
591
709
  ...local.prompt ? { prompt: local.prompt } : {},
592
- ...mcpServers.length ? { mcpServers } : {}
710
+ ...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
593
711
  };
594
712
  let sentLlm = local.model ?? null;
595
713
  let created;
@@ -702,10 +820,495 @@ 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 existsSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync6, readSync, statSync as statSync3 } from "fs";
824
+ import { homedir as homedir4 } from "os";
825
+ import { join as join4 } from "path";
708
826
  import { createHash } from "crypto";
827
+
828
+ // src/import-files.ts
829
+ import { isUtf8 } from "buffer";
830
+ import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
831
+ import { homedir as homedir3 } from "os";
832
+ import { dirname as dirname3, extname as extname2, isAbsolute, join as join3, relative as relative2, resolve as resolve3, sep as sep2, basename as basename4 } from "path";
833
+
834
+ // src/import-workspace.ts
835
+ import { spawnSync } from "child_process";
836
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, realpathSync, statSync } from "fs";
837
+ import { homedir as homedir2 } from "os";
838
+ import { basename as basename3, dirname as dirname2, join as join2, relative, sep } from "path";
839
+ import { parse as parseToml2 } from "smol-toml";
840
+ var MAX_TREE = 5e3;
841
+ var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
842
+ function git(cwd, ...args) {
843
+ const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
844
+ return run2.status === 0 ? run2.stdout : null;
845
+ }
846
+ var isFile = (path) => {
847
+ try {
848
+ return statSync(path).isFile();
849
+ } catch {
850
+ return false;
851
+ }
852
+ };
853
+ var isDir = (path) => {
854
+ try {
855
+ return statSync(path).isDirectory();
856
+ } catch {
857
+ return false;
858
+ }
859
+ };
860
+ function sharedInstructionPaths(home = homedir2()) {
861
+ const rules = join2(home, ".agents", "rules");
862
+ let ruleFiles = [];
863
+ try {
864
+ ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join2(rules, name));
865
+ } catch {
866
+ }
867
+ return [join2(home, "AGENTS.md"), join2(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
868
+ }
869
+ function strings(value, out, depth = 0) {
870
+ if (depth > 12) return;
871
+ if (typeof value === "string") out.push(value);
872
+ else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
873
+ else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
874
+ }
875
+ function skillRoots(cwd, home) {
876
+ const claude = process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude");
877
+ const codex = process.env.CODEX_HOME ?? join2(home, ".codex");
878
+ return [
879
+ ...cwd ? [join2(cwd, ".claude", "skills"), join2(cwd, ".agents", "skills")] : [],
880
+ join2(home, ".agents", "skills"),
881
+ join2(claude, "skills"),
882
+ join2(codex, "skills"),
883
+ join2(home, ".hermes", "skills")
884
+ ];
885
+ }
886
+ function pluginSkill(plugin, skill, home) {
887
+ const base = join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "plugins");
888
+ const walk = (dir, depth) => {
889
+ if (depth > 7) return null;
890
+ let entries;
891
+ try {
892
+ entries = readdirSync(dir, { withFileTypes: true });
893
+ } catch {
894
+ return null;
895
+ }
896
+ for (const entry2 of entries) {
897
+ if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
898
+ const path = join2(dir, entry2.name);
899
+ if (entry2.name === skill && basename3(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join2(path, "SKILL.md"))) return path;
900
+ const found = walk(path, depth + 1);
901
+ if (found) return found;
902
+ }
903
+ return null;
904
+ };
905
+ return walk(base, 0);
906
+ }
907
+ function findSkill(name, cwd, home) {
908
+ const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
909
+ if (scope && skill) return pluginSkill(scope, skill, home);
910
+ for (const root of skillRoots(cwd, home)) {
911
+ const direct = join2(root, name);
912
+ if (isFile(join2(direct, "SKILL.md"))) return realpathSync(direct);
913
+ }
914
+ return null;
915
+ }
916
+ function loadedSkills(records, touched, cwd, home = homedir2()) {
917
+ const dirs = /* @__PURE__ */ new Map();
918
+ const missing = /* @__PURE__ */ new Set();
919
+ const add = (dir, name = basename3(dir)) => {
920
+ if (!isFile(join2(dir, "SKILL.md"))) return false;
921
+ const real = realpathSync(dir);
922
+ if (![...dirs.values()].includes(real)) dirs.set(name, real);
923
+ return true;
924
+ };
925
+ for (const record of records) {
926
+ const texts = [];
927
+ strings(record, texts);
928
+ for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].replace(/[.,;:)]+$/, ""));
929
+ const content = record?.message?.content;
930
+ if (!Array.isArray(content)) continue;
931
+ for (const block of content) {
932
+ const b = block;
933
+ if (b?.type !== "tool_use" || b.name !== "Skill" || typeof b.input?.skill !== "string") continue;
934
+ const name = b.input.skill.replace(/^\//, "");
935
+ if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
936
+ const dir = findSkill(name, cwd, home);
937
+ if (!dir || !add(dir, name)) missing.add(name);
938
+ }
939
+ }
940
+ for (const path of touched) if (basename3(path) === "SKILL.md") add(dirname2(path));
941
+ for (const name of dirs.keys()) missing.delete(name);
942
+ return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
943
+ }
944
+ function readJson2(path) {
945
+ try {
946
+ const value = JSON.parse(readFileSync4(path, "utf8"));
947
+ return value && typeof value === "object" ? value : null;
948
+ } catch {
949
+ return null;
950
+ }
951
+ }
952
+ function names(value) {
953
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
954
+ const keys2 = Object.keys(value).sort();
955
+ return keys2.length ? keys2 : void 0;
956
+ }
957
+ function safeUrl(raw) {
958
+ try {
959
+ const url = new URL(raw);
960
+ if (!/^https?:$/.test(url.protocol)) return null;
961
+ const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
962
+ return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
963
+ } catch {
964
+ return null;
965
+ }
966
+ }
967
+ function serverEntry(name, def, scope) {
968
+ if (!def || typeof def !== "object") return null;
969
+ const env = names(def.env);
970
+ const headers = names(def.headers ?? def.http_headers);
971
+ if (typeof def.url === "string") {
972
+ const url = safeUrl(def.url);
973
+ if (!url) return null;
974
+ const type = def.type === "sse" ? "sse" : "http";
975
+ return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
976
+ }
977
+ if (typeof def.command !== "string" || !def.command) return null;
978
+ const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
979
+ return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
980
+ }
981
+ function mcpServers(harness2, cwd, home = homedir2()) {
982
+ const out = [];
983
+ const push = (servers, scope) => {
984
+ if (!servers || typeof servers !== "object") return;
985
+ for (const [name, def] of Object.entries(servers)) {
986
+ const entry2 = serverEntry(name, def, scope);
987
+ if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
988
+ }
989
+ };
990
+ if (harness2 === "claude_code") {
991
+ const config = readJson2(join2(process.env.CLAUDE_CONFIG_DIR ? dirname2(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
992
+ const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
993
+ const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
994
+ if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
995
+ if (root) push(readJson2(join2(root, ".mcp.json"))?.mcpServers, "project");
996
+ push(config?.mcpServers, "user");
997
+ }
998
+ if (harness2 === "codex") {
999
+ try {
1000
+ const config = parseToml2(readFileSync4(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "config.toml"), "utf8"));
1001
+ push(config.mcp_servers, "user");
1002
+ } catch {
1003
+ }
1004
+ }
1005
+ return out;
1006
+ }
1007
+ function remoteWithoutCredentials(remote) {
1008
+ return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
1009
+ }
1010
+ function workspaceState(cwd, harness2, skills2, home = homedir2()) {
1011
+ const present = !!cwd && isDir(cwd);
1012
+ const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
1013
+ let gitState = null;
1014
+ let tree = [];
1015
+ let patch = null;
1016
+ if (root) {
1017
+ const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
1018
+ const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
1019
+ const remote = git(root, "remote", "get-url", "origin");
1020
+ const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
1021
+ gitState = {
1022
+ remote: remote ? remoteWithoutCredentials(remote) : null,
1023
+ branch: branch === "HEAD" ? null : branch,
1024
+ commit: git(root, "rev-parse", "HEAD")?.trim() || null,
1025
+ dirty: dirty.slice(0, 500),
1026
+ ahead: ahead === null ? null : Number(ahead.trim()) || 0
1027
+ };
1028
+ tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
1029
+ if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
1030
+ }
1031
+ return {
1032
+ manifest: {
1033
+ format: "agentsky.import-workspace",
1034
+ format_version: 1,
1035
+ harness: harness2,
1036
+ cwd: cwd ? cwd.replace(home, "~") : null,
1037
+ project: cwd ? basename3(root ?? cwd) : null,
1038
+ git: gitState,
1039
+ tree: tree.slice(0, MAX_TREE),
1040
+ tree_truncated: tree.length > MAX_TREE,
1041
+ skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
1042
+ mcp_servers: mcpServers(harness2, cwd, home)
1043
+ },
1044
+ patch
1045
+ };
1046
+ }
1047
+ function skillFiles(skill) {
1048
+ const out = [];
1049
+ const walk = (dir, depth) => {
1050
+ if (depth > 8 || out.length > 400) return;
1051
+ let entries;
1052
+ try {
1053
+ entries = readdirSync(dir, { withFileTypes: true });
1054
+ } catch {
1055
+ return;
1056
+ }
1057
+ for (const entry2 of entries) {
1058
+ if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
1059
+ const path = join2(dir, entry2.name);
1060
+ if (entry2.isDirectory()) walk(path, depth + 1);
1061
+ else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
1062
+ }
1063
+ };
1064
+ if (existsSync3(skill.dir)) walk(skill.dir, 0);
1065
+ return out;
1066
+ }
1067
+
1068
+ // src/import-files.ts
1069
+ var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
1070
+ 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;
1071
+ var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
1072
+ var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
1073
+ var DOCUMENTS = {
1074
+ ".pdf": "application/pdf",
1075
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1076
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1077
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1078
+ ".doc": "application/msword",
1079
+ ".rtf": "application/rtf",
1080
+ ".odt": "application/vnd.oasis.opendocument.text"
1081
+ };
1082
+ var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
1083
+ var TEXT = {
1084
+ ".md": "text/markdown",
1085
+ ".mdx": "text/markdown",
1086
+ ".txt": "text/plain",
1087
+ ".csv": "text/csv",
1088
+ ".tsv": "text/tab-separated-values",
1089
+ ".html": "text/html",
1090
+ ".htm": "text/html",
1091
+ ".css": "text/css",
1092
+ ".scss": "text/css",
1093
+ ".json": "application/json",
1094
+ ".jsonl": "application/x-ndjson",
1095
+ ".ndjson": "application/x-ndjson",
1096
+ ".yaml": "application/yaml",
1097
+ ".yml": "application/yaml",
1098
+ ".toml": "application/toml",
1099
+ ".xml": "application/xml",
1100
+ ".sql": "application/sql",
1101
+ ".svg": "image/svg+xml",
1102
+ ".js": "application/javascript",
1103
+ ".mjs": "application/javascript",
1104
+ ".cjs": "application/javascript",
1105
+ ".py": "text/x-python",
1106
+ ".sh": "text/x-shellscript",
1107
+ ".bash": "text/x-shellscript",
1108
+ ".zsh": "text/x-shellscript"
1109
+ };
1110
+ 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"]);
1111
+ var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
1112
+ function classify(path) {
1113
+ const ext = extname2(path).toLowerCase();
1114
+ if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
1115
+ if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
1116
+ if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
1117
+ if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
1118
+ return null;
1119
+ }
1120
+ 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;
1121
+ var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
1122
+ var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1123
+ var str = (value) => typeof value === "string" ? value : "";
1124
+ function shellText(input) {
1125
+ if (typeof input === "string") return input;
1126
+ const i = obj(input);
1127
+ if (!i) return "";
1128
+ const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
1129
+ return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
1130
+ }
1131
+ function touchedPaths(records, cwd, home = homedir3()) {
1132
+ const out = /* @__PURE__ */ new Set();
1133
+ const add = (raw, base) => {
1134
+ if (!raw || raw.length > 1e3) return;
1135
+ const path = raw.replace(/^~(?=\/)/, home);
1136
+ if (isAbsolute(path)) out.add(resolve3(path));
1137
+ else if (base) out.add(resolve3(base, path));
1138
+ };
1139
+ const call = (name, input) => {
1140
+ const i = obj(input);
1141
+ const base = str(i?.workdir) || str(i?.cwd) || cwd;
1142
+ if (i) for (const key of PATH_KEYS) add(str(i[key]), base);
1143
+ const patch = typeof input === "string" ? input : str(i?.input) || str(i?.patch);
1144
+ for (const match of patch.matchAll(/^\*\*\* (?:Add|Update) File: (.+)$/gm)) add(match[1].trim(), base);
1145
+ if (!/bash|shell|exec|terminal|command|run/i.test(name)) return;
1146
+ for (const match of shellText(input).replace(/\\n/g, "\n").matchAll(SHELL_READ)) add(match[1], base);
1147
+ };
1148
+ for (const raw of records) {
1149
+ const record = obj(raw);
1150
+ if (!record) continue;
1151
+ const content = obj(record.message)?.content;
1152
+ if (record.type === "assistant" && Array.isArray(content)) {
1153
+ for (const block of content) {
1154
+ const b = obj(block);
1155
+ if (b?.type === "tool_use") call(str(b.name), b.input);
1156
+ }
1157
+ }
1158
+ const payload = obj(record.payload);
1159
+ if (record.type === "response_item" && (payload?.type === "function_call" || payload?.type === "custom_tool_call")) {
1160
+ let input = payload.arguments ?? payload.input ?? "";
1161
+ if (typeof input === "string") {
1162
+ try {
1163
+ input = JSON.parse(input);
1164
+ } catch {
1165
+ input = payload.arguments ?? payload.input;
1166
+ }
1167
+ }
1168
+ call(str(payload.name), input);
1169
+ }
1170
+ }
1171
+ return [...out];
1172
+ }
1173
+ function gitRoot(dir) {
1174
+ for (let d = dir; ; d = dirname3(d)) {
1175
+ if (existsSync4(join3(d, ".git"))) return d;
1176
+ if (d === dirname3(d)) return null;
1177
+ }
1178
+ }
1179
+ function instructionPaths(cwd, harness2, home = homedir3()) {
1180
+ const out = [];
1181
+ if (cwd && existsSync4(cwd)) {
1182
+ const root = gitRoot(cwd) ?? cwd;
1183
+ for (let d = resolve3(cwd); ; d = dirname3(d)) {
1184
+ for (const name of INSTRUCTION_FILES) out.push(join3(d, name));
1185
+ if (d === root || d === dirname3(d)) break;
1186
+ }
1187
+ }
1188
+ if (harness2 === "claude_code") out.push(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "CLAUDE.md"));
1189
+ if (harness2 === "codex") out.push(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "AGENTS.md"));
1190
+ out.push(...sharedInstructionPaths(home));
1191
+ return out.filter((path) => {
1192
+ try {
1193
+ return statSync2(path).isFile();
1194
+ } catch {
1195
+ return false;
1196
+ }
1197
+ });
1198
+ }
1199
+ function resourceName(path, cwd, home = homedir3()) {
1200
+ const inside = cwd ? relative2(resolve3(cwd), path) : "";
1201
+ if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
1202
+ const underHome = relative2(home, path);
1203
+ const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
1204
+ return `files/${shown.split(sep2).join("/")}`;
1205
+ }
1206
+ var ORDER = { text: 0, document: 1, image: 2 };
1207
+ function resourceLine(name, mediaType, data) {
1208
+ return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
1209
+ }
1210
+ function collectSessionFiles(records, opts) {
1211
+ const home = opts.home ?? homedir3();
1212
+ const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
1213
+ const leftOut = [];
1214
+ const candidates = [];
1215
+ const seen = /* @__PURE__ */ new Set();
1216
+ const touched = touchedPaths(records, opts.cwd, home);
1217
+ const skills2 = loadedSkills(records, touched, opts.cwd, home);
1218
+ const consider = (path, name) => {
1219
+ if (seen.has(path) || exclude.has(path)) return;
1220
+ seen.add(path);
1221
+ if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
1222
+ leftOut.push({ path, reason: "credential or build file" });
1223
+ return;
1224
+ }
1225
+ let size;
1226
+ try {
1227
+ const stat = statSync2(path);
1228
+ if (!stat.isFile()) return;
1229
+ size = stat.size;
1230
+ } catch {
1231
+ leftOut.push({ path, reason: "missing" });
1232
+ return;
1233
+ }
1234
+ const type = classify(path);
1235
+ if (!type) {
1236
+ leftOut.push({ path, reason: "unsupported type" });
1237
+ return;
1238
+ }
1239
+ if (size > MAX_ATTACHED_FILE_BYTES) {
1240
+ leftOut.push({ path, reason: "too large" });
1241
+ return;
1242
+ }
1243
+ candidates.push({ path, name, ...type, size });
1244
+ };
1245
+ for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
1246
+ for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
1247
+ candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
1248
+ const lines = [];
1249
+ const attached = [];
1250
+ let bytes = 0;
1251
+ const state = workspaceState(opts.cwd, opts.harness, skills2, home);
1252
+ const described = !!opts.cwd || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
1253
+ const generated = [
1254
+ ...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
1255
+ ...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
1256
+ ];
1257
+ for (const file of generated) {
1258
+ let line;
1259
+ try {
1260
+ line = resourceLine(file.name, file.mediaType, file.data);
1261
+ } catch {
1262
+ leftOut.push({ path: file.name, reason: "could not be redacted safely" });
1263
+ continue;
1264
+ }
1265
+ const size = Buffer.byteLength(line);
1266
+ if (bytes + size > opts.budget) {
1267
+ leftOut.push({ path: file.name, reason: "over the upload budget" });
1268
+ continue;
1269
+ }
1270
+ bytes += size;
1271
+ lines.push(line);
1272
+ attached.push({ name: file.name, kind: "text", bytes: file.data.length });
1273
+ }
1274
+ for (const file of candidates) {
1275
+ let data;
1276
+ try {
1277
+ data = readFileSync5(file.path);
1278
+ } catch {
1279
+ leftOut.push({ path: file.path, reason: "missing" });
1280
+ continue;
1281
+ }
1282
+ if (file.kind === "text" && data.includes(0)) {
1283
+ leftOut.push({ path: file.path, reason: "not valid UTF-8 text" });
1284
+ continue;
1285
+ }
1286
+ let line;
1287
+ try {
1288
+ line = resourceLine(file.name, file.mediaType, data);
1289
+ } catch {
1290
+ leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
1291
+ continue;
1292
+ }
1293
+ const size = Buffer.byteLength(line);
1294
+ if (bytes + size > opts.budget) {
1295
+ leftOut.push({ path: file.path, reason: "over the upload budget" });
1296
+ continue;
1297
+ }
1298
+ bytes += size;
1299
+ lines.push(line);
1300
+ attached.push({ name: file.name, kind: file.kind, bytes: data.length });
1301
+ }
1302
+ const setup = {
1303
+ skills: skills2.found.map((skill) => skill.name),
1304
+ missingSkills: skills2.missing,
1305
+ mcpServers: state.manifest.mcp_servers.map((server) => server.name),
1306
+ git: state.manifest.git !== null
1307
+ };
1308
+ return { lines, bytes, attached, leftOut, setup };
1309
+ }
1310
+
1311
+ // src/import-local.ts
709
1312
  function redactLine2(raw) {
710
1313
  return redactLine(raw).line;
711
1314
  }
@@ -777,16 +1380,16 @@ function sniffTitle(filePath) {
777
1380
  }
778
1381
  }
779
1382
  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 [];
1383
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
1384
+ const projectsDir = join4(root, "projects");
1385
+ if (!existsSync5(projectsDir)) return [];
783
1386
  const sessions = [];
784
1387
  try {
785
- for (const encoded of readdirSync(projectsDir)) {
786
- const projectDir = join2(projectsDir, encoded);
1388
+ for (const encoded of readdirSync2(projectsDir)) {
1389
+ const projectDir = join4(projectsDir, encoded);
787
1390
  let entries;
788
1391
  try {
789
- entries = readdirSync(projectDir);
1392
+ entries = readdirSync2(projectDir);
790
1393
  } catch {
791
1394
  continue;
792
1395
  }
@@ -794,22 +1397,22 @@ function listClaudeCodeSessions(limit) {
794
1397
  for (const entry2 of entries) {
795
1398
  if (!entry2.endsWith(".jsonl")) continue;
796
1399
  const sessionId = entry2.slice(0, -6);
797
- const mainFile = join2(projectDir, entry2);
1400
+ const mainFile = join4(projectDir, entry2);
798
1401
  let stat;
799
1402
  try {
800
- stat = statSync(mainFile);
1403
+ stat = statSync3(mainFile);
801
1404
  } catch {
802
1405
  continue;
803
1406
  }
804
1407
  if (!stat.isFile()) continue;
805
1408
  let discoveryError;
806
1409
  const subagentFiles = [];
807
- const subagentDir = join2(projectDir, sessionId, "subagents");
808
- if (existsSync3(subagentDir)) {
1410
+ const subagentDir = join4(projectDir, sessionId, "subagents");
1411
+ if (existsSync5(subagentDir)) {
809
1412
  try {
810
- const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1413
+ const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
811
1414
  for (const f of subFiles) {
812
- subagentFiles.push(join2(subagentDir, f));
1415
+ subagentFiles.push(join4(subagentDir, f));
813
1416
  }
814
1417
  } catch {
815
1418
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -831,17 +1434,17 @@ function listClaudeCodeSessions(limit) {
831
1434
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
832
1435
  }
833
1436
  function listCodexSessions(limit) {
834
- const sessionsDir = join2(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join2(homedir2(), ".codex"), "sessions");
1437
+ const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
835
1438
  const records = [];
836
1439
  function walk(dir) {
837
1440
  let entries;
838
1441
  try {
839
- entries = readdirSync(dir, { withFileTypes: true });
1442
+ entries = readdirSync2(dir, { withFileTypes: true });
840
1443
  } catch {
841
1444
  return;
842
1445
  }
843
1446
  for (const entry2 of entries) {
844
- const full = join2(dir, entry2.name);
1447
+ const full = join4(dir, entry2.name);
845
1448
  if (entry2.isDirectory()) {
846
1449
  walk(full);
847
1450
  continue;
@@ -859,7 +1462,7 @@ function listCodexSessions(limit) {
859
1462
  sessionId: data.id,
860
1463
  workspace: typeof data.cwd === "string" ? data.cwd : dir,
861
1464
  title: sniffTitle(full),
862
- mtimeMs: statSync(full).mtimeMs,
1465
+ mtimeMs: statSync3(full).mtimeMs,
863
1466
  files: [full]
864
1467
  },
865
1468
  ...typeof parent === "string" ? { parent } : {}
@@ -928,7 +1531,7 @@ async function mintTrace(base, harness2) {
928
1531
  function loadSummaryFile(summaryPath) {
929
1532
  let raw;
930
1533
  try {
931
- raw = readFileSync4(summaryPath, "utf8");
1534
+ raw = readFileSync6(summaryPath, "utf8");
932
1535
  } catch {
933
1536
  throw new Error(`Cannot read summary file: ${summaryPath}`);
934
1537
  }
@@ -942,10 +1545,19 @@ function loadSummaryFile(summaryPath) {
942
1545
  const p = pkg;
943
1546
  if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
944
1547
  if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
945
- const redacted = redactLine2(raw.trimEnd());
946
- return redacted;
947
- }
948
- function prepareImport(session2, summaryPath) {
1548
+ const redacted2 = redactLine2(raw.trimEnd());
1549
+ return redacted2;
1550
+ }
1551
+ var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
1552
+ var SUMMARY_RESERVE_BYTES = 128 * 1024;
1553
+ var MAX_LISTED_ARTIFACTS = 99;
1554
+ function leftOutLine(left) {
1555
+ const counts = /* @__PURE__ */ new Map();
1556
+ for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
1557
+ const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
1558
+ return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
1559
+ }
1560
+ function prepareImport(session2, summaryPath, options = {}) {
949
1561
  if (session2.discoveryError) throw new Error(session2.discoveryError);
950
1562
  const maximum = 16 * 1024 * 1024;
951
1563
  let inputBytes = 0;
@@ -957,10 +1569,12 @@ function prepareImport(session2, summaryPath) {
957
1569
  let encryptedReasoningCount = 0;
958
1570
  let compactionCount = 0;
959
1571
  let unreadableCount = 0;
1572
+ const source = new SessionSourceCollector();
1573
+ const records = [];
960
1574
  for (const path of session2.files) {
961
- const stat = statSync(path);
1575
+ const stat = statSync3(path);
962
1576
  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");
1577
+ const text = readFileSync6(path, "utf8");
964
1578
  for (const raw of text.split("\n")) {
965
1579
  if (!raw.trim()) continue;
966
1580
  let record;
@@ -973,6 +1587,8 @@ function prepareImport(session2, summaryPath) {
973
1587
  if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
974
1588
  const rec = record;
975
1589
  if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
1590
+ if (path === session2.files[0]) source.push(rec);
1591
+ records.push(rec);
976
1592
  if (rec.type === "response_item") {
977
1593
  const payload = rec.payload;
978
1594
  if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
@@ -996,6 +1612,9 @@ function prepareImport(session2, summaryPath) {
996
1612
  if (!lines.length) throw new Error("The selected transcript is empty.");
997
1613
  const sourceId = nativeId ?? session2.sessionId;
998
1614
  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.");
1615
+ const meta = source.result();
1616
+ const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : 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 });
1617
+ const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under, the skills it loaded and its workspace description (repository state and tool servers, agentsky/workspace.json) are attached as well (listed in artifacts).` : "";
999
1618
  let summaryJson;
1000
1619
  if (summaryPath) {
1001
1620
  summaryJson = loadSummaryFile(summaryPath);
@@ -1004,10 +1623,10 @@ function prepareImport(session2, summaryPath) {
1004
1623
  format: "asteroids.session-summary",
1005
1624
  format_version: 1,
1006
1625
  source_harness: session2.harness,
1007
- source_harness_version: null,
1626
+ source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
1008
1627
  source_session_id: sourceId,
1009
1628
  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.",
1629
+ 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
1630
  context: {
1012
1631
  objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
1013
1632
  constraints: [],
@@ -1015,19 +1634,19 @@ function prepareImport(session2, summaryPath) {
1015
1634
  completed: [],
1016
1635
  pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
1017
1636
  verification: [],
1018
- blockers: ["This handoff contains the recorded conversation only. Referenced local files, runtime state, and unreadable reasoning are not transferred."],
1019
- artifacts: [],
1637
+ blockers: [leftOutLine(files2.leftOut), ...files2.setup.missingSkills.length ? [`Skills it loaded that are not on this computer any more: ${files2.setup.missingSkills.slice(0, 20).join(", ")}.`] : []],
1638
+ 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
1639
  provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
1021
1640
  }
1022
1641
  };
1023
1642
  summaryJson = JSON.stringify(summary);
1024
1643
  }
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";
1644
+ 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
1645
  const gaps = [];
1027
1646
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1028
1647
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1029
1648
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1030
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
1649
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
1031
1650
  }
1032
1651
  async function importSession(opts) {
1033
1652
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1036,7 +1655,7 @@ async function importSession(opts) {
1036
1655
  throw new Error("Import requires HTTPS, or a loopback development server.");
1037
1656
  }
1038
1657
  opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
1039
- const { body, transcriptLines, outputBytes, redactionCount, gaps } = prepareImport(opts.session, opts.summaryPath);
1658
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1040
1659
  let traceId;
1041
1660
  let key;
1042
1661
  if (opts.importKey && opts.grantTraceId) {
@@ -1068,7 +1687,10 @@ async function importSession(opts) {
1068
1687
  transcriptFiles: opts.session.files.length,
1069
1688
  bytes: outputBytes,
1070
1689
  redactionCount,
1071
- gaps
1690
+ gaps,
1691
+ attachedFiles,
1692
+ leftOutFiles,
1693
+ setup
1072
1694
  };
1073
1695
  }
1074
1696
 
@@ -1105,7 +1727,7 @@ async function login(opts) {
1105
1727
  }
1106
1728
  async function browserLogin(verifier, challenge) {
1107
1729
  const state = b64url(randomBytes(16));
1108
- const code = await new Promise((resolve7, reject) => {
1730
+ const code = await new Promise((resolve8, reject) => {
1109
1731
  const server = createServer((req, res) => {
1110
1732
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
1111
1733
  if (url.pathname !== "/callback") {
@@ -1118,7 +1740,7 @@ async function browserLogin(verifier, challenge) {
1118
1740
  if (url.searchParams.get("state") !== state || !got) {
1119
1741
  reject(new Error("login callback state mismatch"));
1120
1742
  } else {
1121
- resolve7(got);
1743
+ resolve8(got);
1122
1744
  }
1123
1745
  server.close();
1124
1746
  });
@@ -1172,23 +1794,23 @@ async function headlessLogin(verifier, challenge) {
1172
1794
  }
1173
1795
 
1174
1796
  // 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");
1797
+ import { existsSync as existsSync6 } from "fs";
1798
+ import { homedir as homedir5 } from "os";
1799
+ import { join as join5 } from "path";
1800
+ function localAgents(env = process.env, home = homedir5()) {
1801
+ const claude = env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
1802
+ const codex = env.CODEX_HOME || join5(home, ".codex");
1803
+ const cursor = join5(home, ".cursor");
1182
1804
  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") }
1805
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join5(claude, "skills") },
1806
+ { id: "codex", name: "Codex", root: codex, skillsDir: join5(codex, "skills") },
1807
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join5(cursor, "skills") }
1186
1808
  ];
1187
1809
  }
1188
- function detectLocalAgents(env = process.env, home = homedir3()) {
1189
- return localAgents(env, home).filter((agent2) => existsSync4(agent2.root));
1810
+ function detectLocalAgents(env = process.env, home = homedir5()) {
1811
+ return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
1190
1812
  }
1191
- function selectLocalAgents(ids, env = process.env, home = homedir3()) {
1813
+ function selectLocalAgents(ids, env = process.env, home = homedir5()) {
1192
1814
  if (!ids?.length) return detectLocalAgents(env, home);
1193
1815
  const known = localAgents(env, home);
1194
1816
  return ids.map((id) => {
@@ -1282,21 +1904,21 @@ async function readTurn(sessionId, opts = {}) {
1282
1904
  for (; ; ) {
1283
1905
  const reading = latestTurn(await history(sessionId));
1284
1906
  if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
1285
- await new Promise((resolve7) => setTimeout(resolve7, opts.intervalMs ?? 3e3));
1907
+ await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
1286
1908
  }
1287
1909
  }
1288
1910
 
1289
1911
  // 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";
1912
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
1913
+ import { dirname as dirname5, join as join9 } from "path";
1292
1914
  import { createInterface as createInterface3 } from "readline";
1293
1915
 
1294
1916
  // src/mcp-register.ts
1295
- import { spawnSync } from "child_process";
1917
+ import { spawnSync as spawnSync2 } from "child_process";
1296
1918
 
1297
1919
  // src/skill.ts
1298
- import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1299
- import { join as join4 } from "path";
1920
+ import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1921
+ import { join as join6 } from "path";
1300
1922
 
1301
1923
  // src/catalog-file.ts
1302
1924
  var CATALOG_FILE = "catalog.md";
@@ -1313,8 +1935,8 @@ function entry(agent2) {
1313
1935
  const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
1314
1936
  if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
1315
1937
  if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
1316
- const { mcp_servers: mcpServers, ...rest } = agent2.requirements;
1317
- if (typeof mcpServers === "boolean") lines.push(mcpServers ? "Can use MCP servers." : "Cannot use MCP servers.");
1938
+ const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
1939
+ if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
1318
1940
  const details = describe(rest);
1319
1941
  if (details) lines.push(`Details: ${details}`);
1320
1942
  const pricing = describe(agent2.pricing);
@@ -1737,7 +2359,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
1737
2359
  }
1738
2360
  function installedVersion(dir) {
1739
2361
  try {
1740
- const marker = JSON.parse(readFileSync5(join4(dir, SKILL_MARKER), "utf8"));
2362
+ const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
1741
2363
  return typeof marker.version === "string" ? marker.version : null;
1742
2364
  } catch {
1743
2365
  return null;
@@ -1747,25 +2369,25 @@ function isInstalledSkill(dir) {
1747
2369
  return installedVersion(dir) !== null;
1748
2370
  }
1749
2371
  function installSkill(agent2, opts = {}) {
1750
- const dir = join4(agent2.skillsDir, SKILL_NAME);
2372
+ const dir = join6(agent2.skillsDir, SKILL_NAME);
1751
2373
  const previous = installedVersion(dir);
1752
2374
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
1753
2375
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
1754
2376
  if (outcome !== "current") {
1755
2377
  mkdirSync(dir, { recursive: true });
1756
- writeFileSync3(join4(dir, "SKILL.md"), skillDocument());
1757
- writeFileSync3(join4(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2378
+ writeFileSync3(join6(dir, "SKILL.md"), skillDocument());
2379
+ writeFileSync3(join6(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
1758
2380
  `);
1759
2381
  }
1760
2382
  if (opts.catalog) {
1761
2383
  mkdirSync(dir, { recursive: true });
1762
- writeFileSync3(join4(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2384
+ writeFileSync3(join6(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
1763
2385
  }
1764
- const refsDir = join4(dir, REFERENCES_DIR);
2386
+ const refsDir = join6(dir, REFERENCES_DIR);
1765
2387
  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());
2388
+ writeFileSync3(join6(refsDir, "import.md"), importReferenceDoc());
2389
+ writeFileSync3(join6(refsDir, "workspaces.md"), workspacesReferenceDoc());
2390
+ writeFileSync3(join6(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
1769
2391
  return { agent: agent2.id, path: dir, outcome };
1770
2392
  }
1771
2393
 
@@ -1774,9 +2396,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
1774
2396
  var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
1775
2397
  function systemCommandRunner() {
1776
2398
  return {
1777
- has: (command) => spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0,
2399
+ has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
1778
2400
  run: (command, args) => {
1779
- const result = spawnSync(command, args, { encoding: "utf8" });
2401
+ const result = spawnSync2(command, args, { encoding: "utf8" });
1780
2402
  return { status: result.status, stderr: result.stderr ?? "" };
1781
2403
  }
1782
2404
  };
@@ -1816,10 +2438,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
1816
2438
  }
1817
2439
 
1818
2440
  // 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";
1822
- import { parse as parseToml2 } from "smol-toml";
2441
+ import { isUtf8 as isUtf82 } from "buffer";
2442
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
2443
+ import { homedir as homedir6 } from "os";
2444
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
2445
+ import { parse as parseToml3 } from "smol-toml";
1823
2446
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
1824
2447
  var MAX_IMPORT_DEPTH = 5;
1825
2448
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -1831,14 +2454,24 @@ function frontmatterName(skillMd) {
1831
2454
  function sensitive(name) {
1832
2455
  return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
1833
2456
  }
1834
- function skillFiles(root, prefix) {
2457
+ function redacted(data) {
2458
+ if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
2459
+ try {
2460
+ const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
2461
+ return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
2462
+ } catch {
2463
+ return null;
2464
+ }
2465
+ }
2466
+ function skillFiles2(root, prefix) {
1835
2467
  const files2 = [];
1836
- for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
1837
- const path = join5(root, entry2.name);
2468
+ for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
2469
+ const path = join7(root, entry2.name);
1838
2470
  if (entry2.isDirectory()) {
1839
- 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)) });
2471
+ if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
2472
+ } else if (entry2.isFile() && !sensitive(entry2.name) && statSync4(path).size <= MAX_SKILL_FILE_BYTES) {
2473
+ const bytes = redacted(readFileSync8(path));
2474
+ if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
1842
2475
  }
1843
2476
  }
1844
2477
  return files2;
@@ -1847,19 +2480,19 @@ function readLocalSkills(agents) {
1847
2480
  const skills2 = [];
1848
2481
  const ignored = [];
1849
2482
  for (const agent2 of agents) {
1850
- if (!existsSync5(agent2.skillsDir)) continue;
1851
- for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
1852
- const dir = join5(agent2.skillsDir, entry2.name);
1853
- let isDir = entry2.isDirectory();
2483
+ if (!existsSync7(agent2.skillsDir)) continue;
2484
+ for (const entry2 of readdirSync3(agent2.skillsDir, { withFileTypes: true })) {
2485
+ const dir = join7(agent2.skillsDir, entry2.name);
2486
+ let isDir2 = entry2.isDirectory();
1854
2487
  if (entry2.isSymbolicLink()) {
1855
2488
  try {
1856
- isDir = statSync2(dir).isDirectory();
2489
+ isDir2 = statSync4(dir).isDirectory();
1857
2490
  } catch {
1858
- isDir = false;
2491
+ isDir2 = false;
1859
2492
  }
1860
2493
  }
1861
- if (!isDir || !existsSync5(join5(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
1862
- const name = frontmatterName(readFileSync6(join5(dir, "SKILL.md"), "utf8"));
2494
+ if (!isDir2 || !existsSync7(join7(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2495
+ const name = frontmatterName(readFileSync8(join7(dir, "SKILL.md"), "utf8"));
1863
2496
  if (!name) {
1864
2497
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
1865
2498
  continue;
@@ -1869,13 +2502,13 @@ function readLocalSkills(agents) {
1869
2502
  ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
1870
2503
  continue;
1871
2504
  }
1872
- const files2 = skillFiles(dir, entry2.name);
2505
+ const files2 = skillFiles2(dir, entry2.name);
1873
2506
  skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
1874
2507
  }
1875
2508
  }
1876
2509
  return { skills: skills2, ignored };
1877
2510
  }
1878
- var INSTRUCTION_FILES = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
2511
+ var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
1879
2512
  function inlineImports(content, from, home, depth, seen) {
1880
2513
  let fence;
1881
2514
  return content.split(/\r?\n/).map((line) => {
@@ -1887,11 +2520,11 @@ function inlineImports(content, from, home, depth, seen) {
1887
2520
  }
1888
2521
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
1889
2522
  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);
2523
+ const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
1891
2524
  if (seen.includes(path)) return "";
1892
2525
  let imported;
1893
2526
  try {
1894
- imported = readFileSync6(path, "utf8");
2527
+ imported = readFileSync8(path, "utf8");
1895
2528
  } catch {
1896
2529
  return line;
1897
2530
  }
@@ -1899,15 +2532,15 @@ function inlineImports(content, from, home, depth, seen) {
1899
2532
  return inlineImports(imported, path, home, depth + 1, seen).trim();
1900
2533
  }).join("\n");
1901
2534
  }
1902
- function readGlobalInstructions(agents, home = homedir4()) {
2535
+ function readGlobalInstructions(agents, home = homedir6()) {
1903
2536
  const found = [];
1904
2537
  for (const agent2 of agents) {
1905
- const file = INSTRUCTION_FILES[agent2.id];
2538
+ const file = INSTRUCTION_FILES2[agent2.id];
1906
2539
  if (!file) continue;
1907
- const path = join5(agent2.root, file);
2540
+ const path = join7(agent2.root, file);
1908
2541
  let raw;
1909
2542
  try {
1910
- raw = readFileSync6(path, "utf8");
2543
+ raw = readFileSync8(path, "utf8");
1911
2544
  } catch {
1912
2545
  continue;
1913
2546
  }
@@ -1924,9 +2557,9 @@ function combineInstructions(found) {
1924
2557
 
1925
2558
  ${item.content.trim()}`).join("\n\n");
1926
2559
  }
1927
- function readJson2(path) {
2560
+ function readJson3(path) {
1928
2561
  try {
1929
- return JSON.parse(readFileSync6(path, "utf8"));
2562
+ return JSON.parse(readFileSync8(path, "utf8"));
1930
2563
  } catch {
1931
2564
  return void 0;
1932
2565
  }
@@ -1940,15 +2573,15 @@ function bearerOf(def) {
1940
2573
  return match?.[1] ?? null;
1941
2574
  }
1942
2575
  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 ?? {};
2576
+ if (agent2.id === "claude_code") return readJson3(join7(home, ".claude.json"))?.mcpServers ?? {};
2577
+ if (agent2.id === "cursor") return readJson3(join7(agent2.root, "mcp.json"))?.mcpServers ?? {};
1945
2578
  try {
1946
- return parseToml2(readFileSync6(join5(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2579
+ return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
1947
2580
  } catch {
1948
2581
  return {};
1949
2582
  }
1950
2583
  }
1951
- function findLocalSecrets(agents, home = homedir4()) {
2584
+ function findLocalSecrets(agents, home = homedir6()) {
1952
2585
  const secrets2 = [];
1953
2586
  for (const agent2 of agents) {
1954
2587
  for (const [name, def] of Object.entries(serversOf(agent2, home))) {
@@ -1964,9 +2597,9 @@ function findLocalSecrets(agents, home = homedir4()) {
1964
2597
 
1965
2598
  // src/subscriptions.ts
1966
2599
  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";
2600
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
2601
+ import { homedir as homedir7 } from "os";
2602
+ import { join as join8 } from "path";
1970
2603
  import { createInterface as createInterface2 } from "readline";
1971
2604
  var ALIASES = {
1972
2605
  claude: "anthropic",
@@ -2051,12 +2684,12 @@ async function connectClaude() {
2051
2684
  const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
2052
2685
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
2053
2686
  }
2054
- function codexSignIn(env = process.env, home = homedir5()) {
2055
- const path = join6(env.CODEX_HOME || join6(home, ".codex"), "auth.json");
2056
- if (!existsSync6(path)) {
2687
+ function codexSignIn(env = process.env, home = homedir7()) {
2688
+ const path = join8(env.CODEX_HOME || join8(home, ".codex"), "auth.json");
2689
+ if (!existsSync8(path)) {
2057
2690
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
2058
2691
  }
2059
- const raw = readFileSync7(path, "utf8");
2692
+ const raw = readFileSync9(path, "utf8");
2060
2693
  try {
2061
2694
  const parsed = JSON.parse(raw);
2062
2695
  return { path, raw, account: parsed.tokens?.account_id };
@@ -2064,12 +2697,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
2064
2697
  throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
2065
2698
  }
2066
2699
  }
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)) {
2700
+ function cursorSignIn(env = process.env, home = homedir7()) {
2701
+ const path = join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "cursor", "auth.json");
2702
+ if (!existsSync8(path)) {
2070
2703
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
2071
2704
  }
2072
- const raw = readFileSync7(path, "utf8");
2705
+ const raw = readFileSync9(path, "utf8");
2073
2706
  try {
2074
2707
  const parsed = JSON.parse(raw);
2075
2708
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -2139,20 +2772,20 @@ async function disconnect(name, opts) {
2139
2772
  // src/setup.ts
2140
2773
  var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
2141
2774
  var PLAN_PROVIDERS = { claude: "anthropic", anthropic: "anthropic", chatgpt: "openai", codex: "openai", openai: "openai", cursor: "cursor" };
2142
- function planProviders(names) {
2143
- if (!names?.length) return null;
2144
- return new Set(names.map((name) => {
2775
+ function planProviders(names2) {
2776
+ if (!names2?.length) return null;
2777
+ return new Set(names2.map((name) => {
2145
2778
  const provider = PLAN_PROVIDERS[name.toLowerCase()];
2146
2779
  if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
2147
2780
  return provider;
2148
2781
  }));
2149
2782
  }
2150
2783
  function reportPath() {
2151
- return join7(dirname3(configPath()), "setup.json");
2784
+ return join9(dirname5(configPath()), "setup.json");
2152
2785
  }
2153
2786
  function loadReport() {
2154
2787
  try {
2155
- const saved = JSON.parse(readFileSync8(reportPath(), "utf8"));
2788
+ const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
2156
2789
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
2157
2790
  } catch {
2158
2791
  return { agents: [], plans: [] };
@@ -2165,7 +2798,7 @@ function mergeReport(base, next) {
2165
2798
  return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
2166
2799
  }
2167
2800
  function saveReport(report) {
2168
- mkdirSync2(dirname3(reportPath()), { recursive: true });
2801
+ mkdirSync2(dirname5(reportPath()), { recursive: true });
2169
2802
  writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
2170
2803
  `, { mode: 384 });
2171
2804
  }
@@ -2515,17 +3148,17 @@ function renderArtifact(artifact2) {
2515
3148
  }
2516
3149
 
2517
3150
  // 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";
3151
+ import { readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
3152
+ import { basename as basename5, join as join10, relative as relative3, resolve as resolve5 } from "path";
2520
3153
  function skillBundle(dir) {
2521
- const root = resolve4(dir);
2522
- const top = basename3(root);
3154
+ const root = resolve5(dir);
3155
+ const top = basename5(root);
2523
3156
  const files2 = [];
2524
3157
  const walk = (path) => {
2525
- 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) });
3158
+ for (const name of readdirSync4(path).sort()) {
3159
+ const full = join10(path, name);
3160
+ if (statSync5(full).isDirectory()) walk(full);
3161
+ else files2.push({ path: `${top}/${relative3(root, full)}`, bytes: readFileSync11(full) });
2529
3162
  }
2530
3163
  };
2531
3164
  walk(root);
@@ -2535,7 +3168,7 @@ function skillBundle(dir) {
2535
3168
  async function uploadSkill(dir, displayName) {
2536
3169
  const form = new FormData();
2537
3170
  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)));
3171
+ form.append("displayName", displayName ?? basename5(resolve5(dir)));
2539
3172
  return apiForm("POST", "/skills", form);
2540
3173
  }
2541
3174
  async function listSkills() {
@@ -2653,19 +3286,19 @@ function renderPolicy(rule) {
2653
3286
  }
2654
3287
 
2655
3288
  // 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";
3289
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
3290
+ import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
2658
3291
  var HARNESS_FILE = "harness.json";
2659
3292
  function harnessFilePath(dir) {
2660
- return join9(resolve5(dir), HARNESS_FILE);
3293
+ return join11(resolve6(dir), HARNESS_FILE);
2661
3294
  }
2662
3295
  function readHarnessFile(dir) {
2663
3296
  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`);
3297
+ if (!existsSync9(path)) {
3298
+ throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
2666
3299
  }
2667
3300
  try {
2668
- return JSON.parse(readFileSync10(path, "utf8"));
3301
+ return JSON.parse(readFileSync12(path, "utf8"));
2669
3302
  } catch (err) {
2670
3303
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
2671
3304
  }
@@ -2692,10 +3325,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
2692
3325
  `;
2693
3326
  function harnessInit(dir) {
2694
3327
  const path = harnessFilePath(dir);
2695
- if (existsSync7(path)) {
3328
+ if (existsSync9(path)) {
2696
3329
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
2697
3330
  }
2698
- mkdirSync3(dirname4(path), { recursive: true });
3331
+ mkdirSync3(dirname6(path), { recursive: true });
2699
3332
  writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
2700
3333
  `);
2701
3334
  console.log(`Created ${path}`);
@@ -3200,6 +3833,38 @@ session.command("read <ref>").description("what the agent has said since the las
3200
3833
  if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
3201
3834
  })
3202
3835
  );
3836
+ 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(
3837
+ run(async (ref, opts) => {
3838
+ const id = await resolveSessionRef(ref);
3839
+ const path = `/sessions/${encodeURIComponent(id)}/share`;
3840
+ if (opts.off) {
3841
+ await api("DELETE", path);
3842
+ console.log(`Sharing is off for ${id}`);
3843
+ return;
3844
+ }
3845
+ const link = await api("POST", path, {
3846
+ access: opts.view ? "view" : "prompt",
3847
+ ...opts.rotate ? { rotate: true } : {}
3848
+ });
3849
+ if (link.shareUrl) console.log(link.shareUrl);
3850
+ else console.log(`view-only token: ${link.token} \u2014 read with GET /links/${link.token}/events`);
3851
+ })
3852
+ );
3853
+ 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(
3854
+ run(async (ref, opts) => {
3855
+ const id = await resolveSessionRef(ref);
3856
+ const usage = await api("GET", `/sessions/${encodeURIComponent(id)}/usage?limit=${encodeURIComponent(opts.limit ?? "20")}`);
3857
+ if (opts.json) {
3858
+ console.log(JSON.stringify(usage, null, 2));
3859
+ return;
3860
+ }
3861
+ const t = usage.totals;
3862
+ console.log(`${id}: $${t.billedCostUsd.toFixed(4)} billed, ${t.turns} model calls, ${t.inputTokens} in / ${t.outputTokens} out tokens`);
3863
+ for (const entry2 of usage.entries) {
3864
+ console.log(` ${entry2.at} ${entry2.kind.padEnd(16)} ${(entry2.model ?? "").padEnd(24)} $${entry2.billedCostUsd.toFixed(4)}`);
3865
+ }
3866
+ })
3867
+ );
3203
3868
  var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
3204
3869
  var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
3205
3870
  sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
@@ -3416,9 +4081,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
3416
4081
  );
3417
4082
  secrets.command("list <id>").action(
3418
4083
  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) {
4084
+ const { secrets: keys2 } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
4085
+ if (!keys2.length) console.log("no secrets set");
4086
+ for (const s of keys2) {
3422
4087
  console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
3423
4088
  }
3424
4089
  })
@@ -3434,9 +4099,9 @@ secrets.command("unset <id> <key>").action(
3434
4099
  );
3435
4100
  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
4101
  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";
4102
+ const path = resolve7(opts.file);
4103
+ if (!existsSync10(path)) {
4104
+ const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
3440
4105
  scaffoldAgentFile(path, name);
3441
4106
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
3442
4107
  }
@@ -3467,7 +4132,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
3467
4132
  );
3468
4133
  program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
3469
4134
  run(async (opts) => {
3470
- const path = resolve6(opts.file);
4135
+ const path = resolve7(opts.file);
3471
4136
  const { file, spec } = readAgentFile(path);
3472
4137
  if (!file.id) {
3473
4138
  console.log("No agent id in the file yet \u2014 running launch instead.");
@@ -3510,7 +4175,7 @@ function formatRelative(msAgo) {
3510
4175
  if (hours < 48) return `${hours}h ago`;
3511
4176
  return `${Math.round(hours / 24)}d ago`;
3512
4177
  }
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(
4178
+ 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 its local files, skills, repository state or tool-server setup").action(
3514
4179
  run(
3515
4180
  async (opts) => {
3516
4181
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -3570,11 +4235,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
3570
4235
  console.log();
3571
4236
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
3572
4237
  const answer = await new Promise(
3573
- (resolve7) => rl.question(
4238
+ (resolve8) => rl.question(
3574
4239
  `Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
3575
4240
  (a) => {
3576
4241
  rl.close();
3577
- resolve7(a.trim());
4242
+ resolve8(a.trim());
3578
4243
  }
3579
4244
  )
3580
4245
  );
@@ -3592,14 +4257,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
3592
4257
  toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
3593
4258
  if (!opts.json) {
3594
4259
  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.");
4260
+ console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation, the local files it read, wrote or ran under, the skills it loaded, its repository state and the names of its tool servers are attached; text is redacted locally. Credential files, tool-server keys and login credentials are never transferred. Use --no-files to send the conversation only.");
3596
4261
  }
3597
4262
  if (!autoConfirm) {
3598
4263
  if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
3599
4264
  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) => {
4265
+ const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
3601
4266
  prompt.close();
3602
- resolve7(value);
4267
+ resolve8(value);
3603
4268
  }));
3604
4269
  if (!/^y(?:es)?$/i.test(answer.trim())) {
3605
4270
  console.log("Cancelled. Nothing uploaded.");
@@ -3617,6 +4282,7 @@ Importing ${label} \u2026
3617
4282
  summaryPath: opts.summary,
3618
4283
  importKey: opts.key,
3619
4284
  grantTraceId: opts.trace,
4285
+ files: opts.files,
3620
4286
  onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
3621
4287
  `)
3622
4288
  });
@@ -3631,9 +4297,16 @@ Importing ${label} \u2026
3631
4297
  bytes: result.bytes,
3632
4298
  redactionCount: result.redactionCount,
3633
4299
  gaps: result.gaps,
4300
+ attachedFiles: result.attachedFiles,
4301
+ leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
4302
+ skills: result.setup.skills,
4303
+ missingSkills: result.setup.missingSkills,
4304
+ mcpServers: result.setup.mcpServers,
3634
4305
  continueUrl: result.url
3635
4306
  }));
3636
4307
  } else {
4308
+ if (result.setup.skills.length || result.setup.mcpServers.length) console.log(` Carried ${result.setup.skills.length} ${result.setup.skills.length === 1 ? "skill" : "skills"} and the setup of ${result.setup.mcpServers.length} tool ${result.setup.mcpServers.length === 1 ? "server" : "servers"} (their keys stay on this computer).`);
4309
+ 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
4310
  console.log(` \u2713 Imported. Continue here:
3638
4311
  ${result.url}
3639
4312
  `);
@@ -3700,7 +4373,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
3700
4373
  );
3701
4374
  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
4375
  run(async (taskId, path, local) => {
3703
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync11(local), mediaTypeFor(local));
4376
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
3704
4377
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
3705
4378
  })
3706
4379
  );
@@ -3865,6 +4538,19 @@ for (const [verb, blurb] of [
3865
4538
  })
3866
4539
  );
3867
4540
  }
4541
+ 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(
4542
+ run(async (id, opts) => {
4543
+ const params = new URLSearchParams({ limit: opts.limit ?? "20" });
4544
+ if (id) params.set("routine", id);
4545
+ if (opts.failed) params.set("has_error", "true");
4546
+ const { items } = await api("GET", `/routine-runs?${params}`);
4547
+ if (!items.length) console.log("no runs");
4548
+ for (const runRow of items) {
4549
+ const outcome = runRow.error ? `FAILED ${runRow.error.type}: ${runRow.error.message}` : runRow.session_id ?? "ok";
4550
+ console.log(`${runRow.id} ${runRow.created_at} ${runRow.trigger_context.type.padEnd(8)} ${outcome}`);
4551
+ }
4552
+ })
4553
+ );
3868
4554
  var collect = (value, previous = []) => [...previous, value];
3869
4555
  function goalPath(sessionId, suffix = "") {
3870
4556
  return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
@@ -3959,6 +4645,147 @@ program.command("config").description("show the CLI config location and current
3959
4645
  console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
3960
4646
  console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
3961
4647
  });
4648
+ var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
4649
+ billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
4650
+ run(async () => {
4651
+ const [balance, autoTopup] = await Promise.all([
4652
+ api("GET", "/billing"),
4653
+ api("GET", "/billing/auto-topup")
4654
+ ]);
4655
+ console.log(renderBalance(balance));
4656
+ console.log(renderAutoTopup(autoTopup));
4657
+ })
4658
+ );
4659
+ billing.command("card").description("print the address of a card form to save a card; saving moves no money").action(
4660
+ run(async () => {
4661
+ const { url } = await api("POST", "/billing/card-setup");
4662
+ console.log(url);
4663
+ })
4664
+ );
4665
+ 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(
4666
+ run(async (dollars2, opts) => {
4667
+ const result = await api("POST", "/billing/topups", {
4668
+ amount: { amount: dollarsToCents("<dollars>", dollars2), currency: "USD" },
4669
+ request_id: opts.requestId ?? `sky-${crypto.randomUUID()}`
4670
+ });
4671
+ console.log(renderTopup(result));
4672
+ })
4673
+ );
4674
+ 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(
4675
+ run(async (opts) => {
4676
+ const body = autoTopupBody(opts);
4677
+ const settings = body ? await api("PATCH", "/billing/auto-topup", body) : await api("GET", "/billing/auto-topup");
4678
+ console.log(renderAutoTopup(settings));
4679
+ })
4680
+ );
4681
+ 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");
4682
+ usageOptions(
4683
+ 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")
4684
+ ).action(
4685
+ run(async (opts) => {
4686
+ const summary = await api("GET", `/billing/usage${usageQuery({ ...opts, groupBy: opts.by })}`);
4687
+ console.log(opts.json ? JSON.stringify(summary, null, 2) : renderUsage(summary));
4688
+ })
4689
+ );
4690
+ usageOptions(
4691
+ 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")
4692
+ ).action(
4693
+ run(async (opts) => {
4694
+ const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
4695
+ if (opts.out) {
4696
+ writeFileSync6(opts.out, bytes);
4697
+ console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
4698
+ } else {
4699
+ process.stdout.write(bytes);
4700
+ }
4701
+ })
4702
+ );
4703
+ var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
4704
+ keys.command("list").action(
4705
+ run(async () => {
4706
+ const { api_keys: list } = await api("GET", "/api_keys");
4707
+ if (!list.length) console.log("no API keys");
4708
+ for (const key of list) console.log(renderApiKey(key));
4709
+ })
4710
+ );
4711
+ 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(
4712
+ run(async (name, opts) => {
4713
+ const { api_key: key, secret } = await api("POST", "/api_keys", apiKeyBody(name, opts));
4714
+ console.error(renderApiKey(key));
4715
+ console.log(secret);
4716
+ })
4717
+ );
4718
+ keys.command("reveal <id>").description("print a key's secret again").action(
4719
+ run(async (id) => {
4720
+ const { secret } = await api("POST", `/api_keys/${encodeURIComponent(id)}/reveal`);
4721
+ console.log(secret);
4722
+ })
4723
+ );
4724
+ for (const [verb, blurb] of [
4725
+ ["revoke", "the key stops working at once"],
4726
+ ["restore", "a revoked key works again"]
4727
+ ]) {
4728
+ keys.command(`${verb} <id>`).description(blurb).action(
4729
+ run(async (id) => {
4730
+ const { api_key: key } = await api("POST", `/api_keys/${encodeURIComponent(id)}/${verb}`);
4731
+ console.log(renderApiKey(key));
4732
+ })
4733
+ );
4734
+ }
4735
+ var memory = program.command("memory").description("your personal memory: what every session you start is told about you");
4736
+ async function writeMemory(operation) {
4737
+ const current = await api("GET", "/memory");
4738
+ console.log(renderMemory(await api("POST", "/memory", memoryBody(operation, current.version))));
4739
+ }
4740
+ memory.command("list").action(run(async () => console.log(renderMemory(await api("GET", "/memory")))));
4741
+ memory.command("save <text...>").description("add an entry").action(run(async (text) => writeMemory({ op: "save", text: text.join(" ") })));
4742
+ memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
4743
+ memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
4744
+ memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
4745
+ var context = program.command("context").description("search your conversations, memory and the workspace trace library");
4746
+ 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(
4747
+ run(async (words, opts) => {
4748
+ const page = await api("GET", `/context/search${searchQuery(words.join(" "), opts)}`);
4749
+ console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
4750
+ })
4751
+ );
4752
+ 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(
4753
+ run(async (ref, opts) => {
4754
+ const params = new URLSearchParams({ ref });
4755
+ if (opts.offset) params.set("offset", opts.offset);
4756
+ const read = await api("GET", `/context/read?${params}`);
4757
+ console.log(read.text);
4758
+ if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
4759
+ })
4760
+ );
4761
+ var webhookLine = (w) => `${w.id} ${w.status.padEnd(8)} ${w.url} ${w.events.join(",")}${w.failure_count ? ` (${w.failure_count} failures)` : ""}`;
4762
+ var webhook = program.command("webhook").description("your HTTPS endpoints for platform events, signed with a whsec_ secret");
4763
+ webhook.command("list").action(
4764
+ run(async () => {
4765
+ const { webhooks } = await api("GET", "/webhooks");
4766
+ if (!webhooks.length) console.log("no webhooks");
4767
+ for (const w of webhooks) console.log(webhookLine(w));
4768
+ })
4769
+ );
4770
+ 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(
4771
+ run(async (url, opts) => {
4772
+ const { webhook: created } = await api("POST", "/webhooks", { url, events: opts.event });
4773
+ console.error(webhookLine(created));
4774
+ if (created.secret) console.log(created.secret);
4775
+ })
4776
+ );
4777
+ webhook.command("enable <id>").description("re-enable an endpoint that was disabled after failed deliveries").action(
4778
+ run(async (id) => {
4779
+ const { webhook: updated } = await api("PATCH", `/webhooks/${encodeURIComponent(id)}`, { status: "active" });
4780
+ console.log(webhookLine(updated));
4781
+ })
4782
+ );
4783
+ webhook.command("rm <id>").description("delete an endpoint").action(
4784
+ run(async (id) => {
4785
+ await api("DELETE", `/webhooks/${encodeURIComponent(id)}`);
4786
+ console.log(`Deleted ${id}`);
4787
+ })
4788
+ );
3962
4789
  var harness = program.command("harness").description("workspace harnesses: init, verify, push");
3963
4790
  harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
3964
4791
  harnessInit(dir ?? ".");
@@ -3982,7 +4809,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
3982
4809
  }));
3983
4810
  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
4811
  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");
4812
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-YKEGRVJN.js");
3986
4813
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
3987
4814
  const scopes = parseContentScopes(opts.capture);
3988
4815
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -4011,13 +4838,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
4011
4838
  ]);
4012
4839
  console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
4013
4840
  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"))}`);
4841
+ if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve7(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
4015
4842
  console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
4016
4843
  console.log(` sky trace show ${trace.id}`);
4017
4844
  }));
4018
4845
  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
4846
  try {
4020
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-UJTFSXNM.js");
4847
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-YKEGRVJN.js");
4021
4848
  const chunks = [];
4022
4849
  for await (const chunk of process.stdin) chunks.push(chunk);
4023
4850
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -4030,14 +4857,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
4030
4857
  }
4031
4858
  });
4032
4859
  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");
4860
+ const { readState, flush, grantSender } = await import("./trace-YKEGRVJN.js");
4034
4861
  const state = readState();
4035
4862
  if (!state) throw new Error("no trace is running here");
4036
4863
  const outcome = await flush(grantSender(state), { attempts: 4 });
4037
4864
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
4038
4865
  }));
4039
4866
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
4040
- const { readState, spoolStatus } = await import("./trace-UJTFSXNM.js");
4867
+ const { readState, spoolStatus } = await import("./trace-YKEGRVJN.js");
4041
4868
  const state = readState();
4042
4869
  if (!state) {
4043
4870
  console.log("No trace is running here.");
@@ -4047,7 +4874,7 @@ traceCmd.command("status").description("the running trace and its spool").action
4047
4874
  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
4875
  }));
4049
4876
  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");
4877
+ const { readState } = await import("./trace-YKEGRVJN.js");
4051
4878
  const ref = id ?? readState()?.trace_id;
4052
4879
  if (!ref) throw new Error("name a trace, or start one here");
4053
4880
  const bundle = await api("GET", `/traces/${ref}`);
@@ -4074,7 +4901,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
4074
4901
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
4075
4902
  }));
4076
4903
  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");
4904
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-YKEGRVJN.js");
4078
4905
  const state = readState();
4079
4906
  if (!state) throw new Error("no trace is running here");
4080
4907
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -4084,7 +4911,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
4084
4911
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
4085
4912
  }));
4086
4913
  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");
4914
+ const lib = await import("./trace-library-6BK5GJVD.js");
4088
4915
  if (opts.hook) {
4089
4916
  try {
4090
4917
  let raw = payload ?? "";
@@ -4132,10 +4959,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4132
4959
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
4133
4960
  }
4134
4961
  if (opts.installSchedule) {
4135
- const { join: join10 } = await import("path");
4136
- const { homedir: homedir6 } = await import("os");
4962
+ const { join: join12 } = await import("path");
4963
+ const { homedir: homedir8 } = await import("os");
4137
4964
  const { execFileSync } = await import("child_process");
4138
- const log = join10(homedir6(), ".config", "sky", "trace-sync.log");
4965
+ const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
4139
4966
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
4140
4967
  try {
4141
4968
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -4145,11 +4972,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4145
4972
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
4146
4973
  }
4147
4974
  if (opts.status) {
4148
- const { readFileSync: readFileSync12 } = await import("fs");
4149
- const { join: join10 } = await import("path");
4975
+ const { readFileSync: readFileSync14 } = await import("fs");
4976
+ const { join: join12 } = await import("path");
4150
4977
  let state = { sessions: {} };
4151
4978
  try {
4152
- state = JSON.parse(readFileSync12(join10(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4979
+ state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4153
4980
  } catch {
4154
4981
  }
4155
4982
  const counts = /* @__PURE__ */ new Map();
@@ -4182,7 +5009,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4182
5009
  })();
4183
5010
  });
4184
5011
  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");
5012
+ const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
4186
5013
  const workspace2 = opts.workspace ?? currentUniverse();
4187
5014
  if (!workspace2) throw new Error("name a workspace with --workspace");
4188
5015
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -4199,7 +5026,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
4199
5026
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
4200
5027
  }));
4201
5028
  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");
5029
+ const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
4203
5030
  const workspace2 = opts.workspace ?? currentUniverse();
4204
5031
  if (!workspace2) throw new Error("name a workspace with --workspace");
4205
5032
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
@@ -4209,7 +5036,10 @@ traceCmd.command("library").description("list the sessions in a workspace's trac
4209
5036
  }
4210
5037
  console.log(`${page.total} sessions in ${workspace2}'s trace library`);
4211
5038
  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}`);
5039
+ const models = trace.models?.length ? `${trace.models[0]}${trace.models.length > 1 ? ` +${trace.models.length - 1}` : ""}` : "";
5040
+ const output = trace.usage?.output_tokens ? `${trace.usage.output_tokens.toLocaleString("en-US")} out` : "";
5041
+ const source = [models, output].filter(Boolean).join(" \xB7 ");
5042
+ 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
5043
  }
4214
5044
  }));
4215
5045
  var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");