@agentsky/sky 2.13.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-XJ5JLLXM.js";
12
+ } from "./chunk-FOJP33RS.js";
13
13
  import {
14
14
  CLI_VERSION,
15
15
  USER_AGENT,
@@ -20,7 +20,7 @@ import {
20
20
  currentUniverse,
21
21
  loadConfig,
22
22
  saveConfig
23
- } from "./chunk-Z3FPPGWX.js";
23
+ } from "./chunk-VJEE2ALN.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -40,12 +40,12 @@ import {
40
40
  skillRefSchema,
41
41
  splitMcpServers,
42
42
  supersededBy
43
- } from "./chunk-2IKEJ74C.js";
43
+ } from "./chunk-7OM2B5SE.js";
44
44
  import "./chunk-MLKGABMK.js";
45
45
 
46
46
  // src/index.ts
47
- import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
48
- import { basename as basename5, resolve as resolve7 } 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";
49
49
  import { createInterface as createInterface4 } from "readline";
50
50
  import { Command } from "commander";
51
51
 
@@ -699,7 +699,7 @@ async function createClone(local, opts) {
699
699
  console.log("Aborted \u2014 nothing sent.");
700
700
  return;
701
701
  }
702
- const { servers: mcpServers, skipped } = mcpServersFromKept(local.keptServers);
702
+ const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
703
703
  if (skipped.length) {
704
704
  console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
705
705
  }
@@ -707,7 +707,7 @@ async function createClone(local, opts) {
707
707
  agentType: local.harness,
708
708
  ...local.model ? { llm: local.model } : {},
709
709
  ...local.prompt ? { prompt: local.prompt } : {},
710
- ...mcpServers.length ? { mcpServers } : {}
710
+ ...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
711
711
  };
712
712
  let sentLlm = local.model ?? null;
713
713
  let created;
@@ -820,16 +820,252 @@ async function clone(opts) {
820
820
  }
821
821
 
822
822
  // src/import-local.ts
823
- import { closeSync, existsSync as existsSync4, openSync, readdirSync, readFileSync as readFileSync5, readSync, statSync as statSync2 } from "fs";
824
- import { homedir as homedir3 } from "os";
825
- import { join as join3 } from "path";
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";
826
826
  import { createHash } from "crypto";
827
827
 
828
828
  // src/import-files.ts
829
829
  import { isUtf8 } from "buffer";
830
- import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
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";
831
837
  import { homedir as homedir2 } from "os";
832
- import { dirname as dirname2, extname as extname2, isAbsolute, join as join2, relative, resolve as resolve3, sep, basename as basename3 } from "path";
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
833
1069
  var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
834
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;
835
1071
  var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
@@ -878,7 +1114,7 @@ function classify(path) {
878
1114
  if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
879
1115
  if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
880
1116
  if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
881
- if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename3(path))) return { kind: "text", mediaType: "text/plain" };
1117
+ if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
882
1118
  return null;
883
1119
  }
884
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;
@@ -892,7 +1128,7 @@ function shellText(input) {
892
1128
  const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
893
1129
  return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
894
1130
  }
895
- function touchedPaths(records, cwd, home = homedir2()) {
1131
+ function touchedPaths(records, cwd, home = homedir3()) {
896
1132
  const out = /* @__PURE__ */ new Set();
897
1133
  const add = (raw, base) => {
898
1134
  if (!raw || raw.length > 1e3) return;
@@ -935,79 +1171,110 @@ function touchedPaths(records, cwd, home = homedir2()) {
935
1171
  return [...out];
936
1172
  }
937
1173
  function gitRoot(dir) {
938
- for (let d = dir; ; d = dirname2(d)) {
939
- if (existsSync3(join2(d, ".git"))) return d;
940
- if (d === dirname2(d)) return null;
1174
+ for (let d = dir; ; d = dirname3(d)) {
1175
+ if (existsSync4(join3(d, ".git"))) return d;
1176
+ if (d === dirname3(d)) return null;
941
1177
  }
942
1178
  }
943
- function instructionPaths(cwd, harness2, home = homedir2()) {
1179
+ function instructionPaths(cwd, harness2, home = homedir3()) {
944
1180
  const out = [];
945
- if (cwd && existsSync3(cwd)) {
1181
+ if (cwd && existsSync4(cwd)) {
946
1182
  const root = gitRoot(cwd) ?? cwd;
947
- for (let d = resolve3(cwd); ; d = dirname2(d)) {
948
- for (const name of INSTRUCTION_FILES) out.push(join2(d, name));
949
- if (d === root || d === dirname2(d)) break;
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;
950
1186
  }
951
1187
  }
952
- if (harness2 === "claude_code") out.push(join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "CLAUDE.md"));
953
- if (harness2 === "codex") out.push(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "AGENTS.md"));
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));
954
1191
  return out.filter((path) => {
955
1192
  try {
956
- return statSync(path).isFile();
1193
+ return statSync2(path).isFile();
957
1194
  } catch {
958
1195
  return false;
959
1196
  }
960
1197
  });
961
1198
  }
962
- function resourceName(path, cwd, home = homedir2()) {
963
- const inside = cwd ? relative(resolve3(cwd), path) : "";
964
- if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep).join("/")}`;
965
- const underHome = relative(home, path);
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);
966
1203
  const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
967
- return `files/${shown.split(sep).join("/")}`;
1204
+ return `files/${shown.split(sep2).join("/")}`;
968
1205
  }
969
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
+ }
970
1210
  function collectSessionFiles(records, opts) {
971
- const home = opts.home ?? homedir2();
1211
+ const home = opts.home ?? homedir3();
972
1212
  const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
973
1213
  const leftOut = [];
974
1214
  const candidates = [];
975
1215
  const seen = /* @__PURE__ */ new Set();
976
- for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touchedPaths(records, opts.cwd, home)]) {
977
- if (seen.has(path) || exclude.has(path)) continue;
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;
978
1220
  seen.add(path);
979
1221
  if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
980
1222
  leftOut.push({ path, reason: "credential or build file" });
981
- continue;
1223
+ return;
982
1224
  }
983
1225
  let size;
984
1226
  try {
985
- const stat = statSync(path);
986
- if (!stat.isFile()) continue;
1227
+ const stat = statSync2(path);
1228
+ if (!stat.isFile()) return;
987
1229
  size = stat.size;
988
1230
  } catch {
989
1231
  leftOut.push({ path, reason: "missing" });
990
- continue;
1232
+ return;
991
1233
  }
992
1234
  const type = classify(path);
993
1235
  if (!type) {
994
1236
  leftOut.push({ path, reason: "unsupported type" });
995
- continue;
1237
+ return;
996
1238
  }
997
1239
  if (size > MAX_ATTACHED_FILE_BYTES) {
998
1240
  leftOut.push({ path, reason: "too large" });
999
- continue;
1241
+ return;
1000
1242
  }
1001
- candidates.push({ path, name: resourceName(path, opts.cwd, home), ...type, size });
1002
- }
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);
1003
1247
  candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
1004
1248
  const lines = [];
1005
1249
  const attached = [];
1006
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
+ }
1007
1274
  for (const file of candidates) {
1008
1275
  let data;
1009
1276
  try {
1010
- data = readFileSync4(file.path);
1277
+ data = readFileSync5(file.path);
1011
1278
  } catch {
1012
1279
  leftOut.push({ path: file.path, reason: "missing" });
1013
1280
  continue;
@@ -1018,7 +1285,7 @@ function collectSessionFiles(records, opts) {
1018
1285
  }
1019
1286
  let line;
1020
1287
  try {
1021
- line = redactLine(JSON.stringify({ type: "import.resource", name: file.name, media_type: file.mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
1288
+ line = resourceLine(file.name, file.mediaType, data);
1022
1289
  } catch {
1023
1290
  leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
1024
1291
  continue;
@@ -1032,7 +1299,13 @@ function collectSessionFiles(records, opts) {
1032
1299
  lines.push(line);
1033
1300
  attached.push({ name: file.name, kind: file.kind, bytes: data.length });
1034
1301
  }
1035
- return { lines, bytes, attached, leftOut };
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 };
1036
1309
  }
1037
1310
 
1038
1311
  // src/import-local.ts
@@ -1107,16 +1380,16 @@ function sniffTitle(filePath) {
1107
1380
  }
1108
1381
  }
1109
1382
  function listClaudeCodeSessions(limit) {
1110
- const root = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
1111
- const projectsDir = join3(root, "projects");
1112
- if (!existsSync4(projectsDir)) return [];
1383
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
1384
+ const projectsDir = join4(root, "projects");
1385
+ if (!existsSync5(projectsDir)) return [];
1113
1386
  const sessions = [];
1114
1387
  try {
1115
- for (const encoded of readdirSync(projectsDir)) {
1116
- const projectDir = join3(projectsDir, encoded);
1388
+ for (const encoded of readdirSync2(projectsDir)) {
1389
+ const projectDir = join4(projectsDir, encoded);
1117
1390
  let entries;
1118
1391
  try {
1119
- entries = readdirSync(projectDir);
1392
+ entries = readdirSync2(projectDir);
1120
1393
  } catch {
1121
1394
  continue;
1122
1395
  }
@@ -1124,22 +1397,22 @@ function listClaudeCodeSessions(limit) {
1124
1397
  for (const entry2 of entries) {
1125
1398
  if (!entry2.endsWith(".jsonl")) continue;
1126
1399
  const sessionId = entry2.slice(0, -6);
1127
- const mainFile = join3(projectDir, entry2);
1400
+ const mainFile = join4(projectDir, entry2);
1128
1401
  let stat;
1129
1402
  try {
1130
- stat = statSync2(mainFile);
1403
+ stat = statSync3(mainFile);
1131
1404
  } catch {
1132
1405
  continue;
1133
1406
  }
1134
1407
  if (!stat.isFile()) continue;
1135
1408
  let discoveryError;
1136
1409
  const subagentFiles = [];
1137
- const subagentDir = join3(projectDir, sessionId, "subagents");
1138
- if (existsSync4(subagentDir)) {
1410
+ const subagentDir = join4(projectDir, sessionId, "subagents");
1411
+ if (existsSync5(subagentDir)) {
1139
1412
  try {
1140
- const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1413
+ const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1141
1414
  for (const f of subFiles) {
1142
- subagentFiles.push(join3(subagentDir, f));
1415
+ subagentFiles.push(join4(subagentDir, f));
1143
1416
  }
1144
1417
  } catch {
1145
1418
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -1161,17 +1434,17 @@ function listClaudeCodeSessions(limit) {
1161
1434
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
1162
1435
  }
1163
1436
  function listCodexSessions(limit) {
1164
- const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
1437
+ const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
1165
1438
  const records = [];
1166
1439
  function walk(dir) {
1167
1440
  let entries;
1168
1441
  try {
1169
- entries = readdirSync(dir, { withFileTypes: true });
1442
+ entries = readdirSync2(dir, { withFileTypes: true });
1170
1443
  } catch {
1171
1444
  return;
1172
1445
  }
1173
1446
  for (const entry2 of entries) {
1174
- const full = join3(dir, entry2.name);
1447
+ const full = join4(dir, entry2.name);
1175
1448
  if (entry2.isDirectory()) {
1176
1449
  walk(full);
1177
1450
  continue;
@@ -1189,7 +1462,7 @@ function listCodexSessions(limit) {
1189
1462
  sessionId: data.id,
1190
1463
  workspace: typeof data.cwd === "string" ? data.cwd : dir,
1191
1464
  title: sniffTitle(full),
1192
- mtimeMs: statSync2(full).mtimeMs,
1465
+ mtimeMs: statSync3(full).mtimeMs,
1193
1466
  files: [full]
1194
1467
  },
1195
1468
  ...typeof parent === "string" ? { parent } : {}
@@ -1258,7 +1531,7 @@ async function mintTrace(base, harness2) {
1258
1531
  function loadSummaryFile(summaryPath) {
1259
1532
  let raw;
1260
1533
  try {
1261
- raw = readFileSync5(summaryPath, "utf8");
1534
+ raw = readFileSync6(summaryPath, "utf8");
1262
1535
  } catch {
1263
1536
  throw new Error(`Cannot read summary file: ${summaryPath}`);
1264
1537
  }
@@ -1272,8 +1545,8 @@ function loadSummaryFile(summaryPath) {
1272
1545
  const p = pkg;
1273
1546
  if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
1274
1547
  if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
1275
- const redacted = redactLine2(raw.trimEnd());
1276
- return redacted;
1548
+ const redacted2 = redactLine2(raw.trimEnd());
1549
+ return redacted2;
1277
1550
  }
1278
1551
  var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
1279
1552
  var SUMMARY_RESERVE_BYTES = 128 * 1024;
@@ -1299,9 +1572,9 @@ function prepareImport(session2, summaryPath, options = {}) {
1299
1572
  const source = new SessionSourceCollector();
1300
1573
  const records = [];
1301
1574
  for (const path of session2.files) {
1302
- const stat = statSync2(path);
1575
+ const stat = statSync3(path);
1303
1576
  if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
1304
- const text = readFileSync5(path, "utf8");
1577
+ const text = readFileSync6(path, "utf8");
1305
1578
  for (const raw of text.split("\n")) {
1306
1579
  if (!raw.trim()) continue;
1307
1580
  let record;
@@ -1340,8 +1613,8 @@ function prepareImport(session2, summaryPath, options = {}) {
1340
1613
  const sourceId = nativeId ?? session2.sessionId;
1341
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.");
1342
1615
  const meta = source.result();
1343
- const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [] } : collectSessionFiles(records, { harness: session2.harness, cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""), budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES, exclude: session2.files });
1344
- const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under are attached as well (listed in artifacts).` : "";
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).` : "";
1345
1618
  let summaryJson;
1346
1619
  if (summaryPath) {
1347
1620
  summaryJson = loadSummaryFile(summaryPath);
@@ -1361,7 +1634,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1361
1634
  completed: [],
1362
1635
  pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
1363
1636
  verification: [],
1364
- blockers: [leftOutLine(files2.leftOut)],
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(", ")}.`] : []],
1365
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).` })),
1366
1639
  provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
1367
1640
  }
@@ -1373,7 +1646,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1373
1646
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1374
1647
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1375
1648
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1376
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
1649
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
1377
1650
  }
1378
1651
  async function importSession(opts) {
1379
1652
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1382,7 +1655,7 @@ async function importSession(opts) {
1382
1655
  throw new Error("Import requires HTTPS, or a loopback development server.");
1383
1656
  }
1384
1657
  opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
1385
- const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1658
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1386
1659
  let traceId;
1387
1660
  let key;
1388
1661
  if (opts.importKey && opts.grantTraceId) {
@@ -1416,7 +1689,8 @@ async function importSession(opts) {
1416
1689
  redactionCount,
1417
1690
  gaps,
1418
1691
  attachedFiles,
1419
- leftOutFiles
1692
+ leftOutFiles,
1693
+ setup
1420
1694
  };
1421
1695
  }
1422
1696
 
@@ -1520,23 +1794,23 @@ async function headlessLogin(verifier, challenge) {
1520
1794
  }
1521
1795
 
1522
1796
  // src/local-agents.ts
1523
- import { existsSync as existsSync5 } from "fs";
1524
- import { homedir as homedir4 } from "os";
1525
- import { join as join4 } from "path";
1526
- function localAgents(env = process.env, home = homedir4()) {
1527
- const claude = env.CLAUDE_CONFIG_DIR || join4(home, ".claude");
1528
- const codex = env.CODEX_HOME || join4(home, ".codex");
1529
- const cursor = join4(home, ".cursor");
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");
1530
1804
  return [
1531
- { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join4(claude, "skills") },
1532
- { id: "codex", name: "Codex", root: codex, skillsDir: join4(codex, "skills") },
1533
- { id: "cursor", name: "Cursor", root: cursor, skillsDir: join4(cursor, "skills") }
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") }
1534
1808
  ];
1535
1809
  }
1536
- function detectLocalAgents(env = process.env, home = homedir4()) {
1537
- return localAgents(env, home).filter((agent2) => existsSync5(agent2.root));
1810
+ function detectLocalAgents(env = process.env, home = homedir5()) {
1811
+ return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
1538
1812
  }
1539
- function selectLocalAgents(ids, env = process.env, home = homedir4()) {
1813
+ function selectLocalAgents(ids, env = process.env, home = homedir5()) {
1540
1814
  if (!ids?.length) return detectLocalAgents(env, home);
1541
1815
  const known = localAgents(env, home);
1542
1816
  return ids.map((id) => {
@@ -1635,16 +1909,16 @@ async function readTurn(sessionId, opts = {}) {
1635
1909
  }
1636
1910
 
1637
1911
  // src/setup.ts
1638
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
1639
- import { dirname as dirname4, join as join8 } 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";
1640
1914
  import { createInterface as createInterface3 } from "readline";
1641
1915
 
1642
1916
  // src/mcp-register.ts
1643
- import { spawnSync } from "child_process";
1917
+ import { spawnSync as spawnSync2 } from "child_process";
1644
1918
 
1645
1919
  // src/skill.ts
1646
- import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1647
- import { join as join5 } from "path";
1920
+ import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1921
+ import { join as join6 } from "path";
1648
1922
 
1649
1923
  // src/catalog-file.ts
1650
1924
  var CATALOG_FILE = "catalog.md";
@@ -1661,8 +1935,8 @@ function entry(agent2) {
1661
1935
  const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
1662
1936
  if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
1663
1937
  if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
1664
- const { mcp_servers: mcpServers, ...rest } = agent2.requirements;
1665
- 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.");
1666
1940
  const details = describe(rest);
1667
1941
  if (details) lines.push(`Details: ${details}`);
1668
1942
  const pricing = describe(agent2.pricing);
@@ -2085,7 +2359,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
2085
2359
  }
2086
2360
  function installedVersion(dir) {
2087
2361
  try {
2088
- const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
2362
+ const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
2089
2363
  return typeof marker.version === "string" ? marker.version : null;
2090
2364
  } catch {
2091
2365
  return null;
@@ -2095,25 +2369,25 @@ function isInstalledSkill(dir) {
2095
2369
  return installedVersion(dir) !== null;
2096
2370
  }
2097
2371
  function installSkill(agent2, opts = {}) {
2098
- const dir = join5(agent2.skillsDir, SKILL_NAME);
2372
+ const dir = join6(agent2.skillsDir, SKILL_NAME);
2099
2373
  const previous = installedVersion(dir);
2100
2374
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
2101
2375
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
2102
2376
  if (outcome !== "current") {
2103
2377
  mkdirSync(dir, { recursive: true });
2104
- writeFileSync3(join5(dir, "SKILL.md"), skillDocument());
2105
- writeFileSync3(join5(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)}
2106
2380
  `);
2107
2381
  }
2108
2382
  if (opts.catalog) {
2109
2383
  mkdirSync(dir, { recursive: true });
2110
- writeFileSync3(join5(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()));
2111
2385
  }
2112
- const refsDir = join5(dir, REFERENCES_DIR);
2386
+ const refsDir = join6(dir, REFERENCES_DIR);
2113
2387
  mkdirSync(refsDir, { recursive: true });
2114
- writeFileSync3(join5(refsDir, "import.md"), importReferenceDoc());
2115
- writeFileSync3(join5(refsDir, "workspaces.md"), workspacesReferenceDoc());
2116
- writeFileSync3(join5(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());
2117
2391
  return { agent: agent2.id, path: dir, outcome };
2118
2392
  }
2119
2393
 
@@ -2122,9 +2396,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
2122
2396
  var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
2123
2397
  function systemCommandRunner() {
2124
2398
  return {
2125
- has: (command) => spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0,
2399
+ has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
2126
2400
  run: (command, args) => {
2127
- const result = spawnSync(command, args, { encoding: "utf8" });
2401
+ const result = spawnSync2(command, args, { encoding: "utf8" });
2128
2402
  return { status: result.status, stderr: result.stderr ?? "" };
2129
2403
  }
2130
2404
  };
@@ -2164,10 +2438,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
2164
2438
  }
2165
2439
 
2166
2440
  // src/setup-local.ts
2167
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
2168
- import { homedir as homedir5 } from "os";
2169
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
2170
- 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";
2171
2446
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
2172
2447
  var MAX_IMPORT_DEPTH = 5;
2173
2448
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -2179,14 +2454,24 @@ function frontmatterName(skillMd) {
2179
2454
  function sensitive(name) {
2180
2455
  return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
2181
2456
  }
2182
- 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) {
2183
2467
  const files2 = [];
2184
- for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
2185
- const path = join6(root, entry2.name);
2468
+ for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
2469
+ const path = join7(root, entry2.name);
2186
2470
  if (entry2.isDirectory()) {
2187
- if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
2188
- } else if (entry2.isFile() && !sensitive(entry2.name) && statSync3(path).size <= MAX_SKILL_FILE_BYTES) {
2189
- files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(readFileSync7(path)) });
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 });
2190
2475
  }
2191
2476
  }
2192
2477
  return files2;
@@ -2195,19 +2480,19 @@ function readLocalSkills(agents) {
2195
2480
  const skills2 = [];
2196
2481
  const ignored = [];
2197
2482
  for (const agent2 of agents) {
2198
- if (!existsSync6(agent2.skillsDir)) continue;
2199
- for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
2200
- const dir = join6(agent2.skillsDir, entry2.name);
2201
- 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();
2202
2487
  if (entry2.isSymbolicLink()) {
2203
2488
  try {
2204
- isDir = statSync3(dir).isDirectory();
2489
+ isDir2 = statSync4(dir).isDirectory();
2205
2490
  } catch {
2206
- isDir = false;
2491
+ isDir2 = false;
2207
2492
  }
2208
2493
  }
2209
- if (!isDir || !existsSync6(join6(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2210
- const name = frontmatterName(readFileSync7(join6(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"));
2211
2496
  if (!name) {
2212
2497
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
2213
2498
  continue;
@@ -2217,7 +2502,7 @@ function readLocalSkills(agents) {
2217
2502
  ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
2218
2503
  continue;
2219
2504
  }
2220
- const files2 = skillFiles(dir, entry2.name);
2505
+ const files2 = skillFiles2(dir, entry2.name);
2221
2506
  skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
2222
2507
  }
2223
2508
  }
@@ -2235,11 +2520,11 @@ function inlineImports(content, from, home, depth, seen) {
2235
2520
  }
2236
2521
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
2237
2522
  if (!target || depth >= MAX_IMPORT_DEPTH) return line;
2238
- const path = target.startsWith("~/") ? join6(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname3(from), target);
2523
+ const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
2239
2524
  if (seen.includes(path)) return "";
2240
2525
  let imported;
2241
2526
  try {
2242
- imported = readFileSync7(path, "utf8");
2527
+ imported = readFileSync8(path, "utf8");
2243
2528
  } catch {
2244
2529
  return line;
2245
2530
  }
@@ -2247,15 +2532,15 @@ function inlineImports(content, from, home, depth, seen) {
2247
2532
  return inlineImports(imported, path, home, depth + 1, seen).trim();
2248
2533
  }).join("\n");
2249
2534
  }
2250
- function readGlobalInstructions(agents, home = homedir5()) {
2535
+ function readGlobalInstructions(agents, home = homedir6()) {
2251
2536
  const found = [];
2252
2537
  for (const agent2 of agents) {
2253
2538
  const file = INSTRUCTION_FILES2[agent2.id];
2254
2539
  if (!file) continue;
2255
- const path = join6(agent2.root, file);
2540
+ const path = join7(agent2.root, file);
2256
2541
  let raw;
2257
2542
  try {
2258
- raw = readFileSync7(path, "utf8");
2543
+ raw = readFileSync8(path, "utf8");
2259
2544
  } catch {
2260
2545
  continue;
2261
2546
  }
@@ -2272,9 +2557,9 @@ function combineInstructions(found) {
2272
2557
 
2273
2558
  ${item.content.trim()}`).join("\n\n");
2274
2559
  }
2275
- function readJson2(path) {
2560
+ function readJson3(path) {
2276
2561
  try {
2277
- return JSON.parse(readFileSync7(path, "utf8"));
2562
+ return JSON.parse(readFileSync8(path, "utf8"));
2278
2563
  } catch {
2279
2564
  return void 0;
2280
2565
  }
@@ -2288,15 +2573,15 @@ function bearerOf(def) {
2288
2573
  return match?.[1] ?? null;
2289
2574
  }
2290
2575
  function serversOf(agent2, home) {
2291
- if (agent2.id === "claude_code") return readJson2(join6(home, ".claude.json"))?.mcpServers ?? {};
2292
- if (agent2.id === "cursor") return readJson2(join6(agent2.root, "mcp.json"))?.mcpServers ?? {};
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 ?? {};
2293
2578
  try {
2294
- return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2579
+ return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2295
2580
  } catch {
2296
2581
  return {};
2297
2582
  }
2298
2583
  }
2299
- function findLocalSecrets(agents, home = homedir5()) {
2584
+ function findLocalSecrets(agents, home = homedir6()) {
2300
2585
  const secrets2 = [];
2301
2586
  for (const agent2 of agents) {
2302
2587
  for (const [name, def] of Object.entries(serversOf(agent2, home))) {
@@ -2312,9 +2597,9 @@ function findLocalSecrets(agents, home = homedir5()) {
2312
2597
 
2313
2598
  // src/subscriptions.ts
2314
2599
  import { spawn as spawn3 } from "child_process";
2315
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2316
- import { homedir as homedir6 } from "os";
2317
- import { join as join7 } 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";
2318
2603
  import { createInterface as createInterface2 } from "readline";
2319
2604
  var ALIASES = {
2320
2605
  claude: "anthropic",
@@ -2399,12 +2684,12 @@ async function connectClaude() {
2399
2684
  const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
2400
2685
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
2401
2686
  }
2402
- function codexSignIn(env = process.env, home = homedir6()) {
2403
- const path = join7(env.CODEX_HOME || join7(home, ".codex"), "auth.json");
2404
- if (!existsSync7(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)) {
2405
2690
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
2406
2691
  }
2407
- const raw = readFileSync8(path, "utf8");
2692
+ const raw = readFileSync9(path, "utf8");
2408
2693
  try {
2409
2694
  const parsed = JSON.parse(raw);
2410
2695
  return { path, raw, account: parsed.tokens?.account_id };
@@ -2412,12 +2697,12 @@ function codexSignIn(env = process.env, home = homedir6()) {
2412
2697
  throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
2413
2698
  }
2414
2699
  }
2415
- function cursorSignIn(env = process.env, home = homedir6()) {
2416
- const path = join7(env.XDG_CONFIG_HOME || join7(home, ".config"), "cursor", "auth.json");
2417
- if (!existsSync7(path)) {
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)) {
2418
2703
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
2419
2704
  }
2420
- const raw = readFileSync8(path, "utf8");
2705
+ const raw = readFileSync9(path, "utf8");
2421
2706
  try {
2422
2707
  const parsed = JSON.parse(raw);
2423
2708
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -2487,20 +2772,20 @@ async function disconnect(name, opts) {
2487
2772
  // src/setup.ts
2488
2773
  var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
2489
2774
  var PLAN_PROVIDERS = { claude: "anthropic", anthropic: "anthropic", chatgpt: "openai", codex: "openai", openai: "openai", cursor: "cursor" };
2490
- function planProviders(names) {
2491
- if (!names?.length) return null;
2492
- return new Set(names.map((name) => {
2775
+ function planProviders(names2) {
2776
+ if (!names2?.length) return null;
2777
+ return new Set(names2.map((name) => {
2493
2778
  const provider = PLAN_PROVIDERS[name.toLowerCase()];
2494
2779
  if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
2495
2780
  return provider;
2496
2781
  }));
2497
2782
  }
2498
2783
  function reportPath() {
2499
- return join8(dirname4(configPath()), "setup.json");
2784
+ return join9(dirname5(configPath()), "setup.json");
2500
2785
  }
2501
2786
  function loadReport() {
2502
2787
  try {
2503
- const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
2788
+ const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
2504
2789
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
2505
2790
  } catch {
2506
2791
  return { agents: [], plans: [] };
@@ -2513,7 +2798,7 @@ function mergeReport(base, next) {
2513
2798
  return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
2514
2799
  }
2515
2800
  function saveReport(report) {
2516
- mkdirSync2(dirname4(reportPath()), { recursive: true });
2801
+ mkdirSync2(dirname5(reportPath()), { recursive: true });
2517
2802
  writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
2518
2803
  `, { mode: 384 });
2519
2804
  }
@@ -2863,17 +3148,17 @@ function renderArtifact(artifact2) {
2863
3148
  }
2864
3149
 
2865
3150
  // src/skills-store.ts
2866
- import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
2867
- import { basename as basename4, join as join9, relative as relative2, resolve as resolve5 } from "path";
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";
2868
3153
  function skillBundle(dir) {
2869
3154
  const root = resolve5(dir);
2870
- const top = basename4(root);
3155
+ const top = basename5(root);
2871
3156
  const files2 = [];
2872
3157
  const walk = (path) => {
2873
- for (const name of readdirSync3(path).sort()) {
2874
- const full = join9(path, name);
2875
- if (statSync4(full).isDirectory()) walk(full);
2876
- else files2.push({ path: `${top}/${relative2(root, full)}`, bytes: readFileSync10(full) });
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) });
2877
3162
  }
2878
3163
  };
2879
3164
  walk(root);
@@ -2883,7 +3168,7 @@ function skillBundle(dir) {
2883
3168
  async function uploadSkill(dir, displayName) {
2884
3169
  const form = new FormData();
2885
3170
  for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
2886
- form.append("displayName", displayName ?? basename4(resolve5(dir)));
3171
+ form.append("displayName", displayName ?? basename5(resolve5(dir)));
2887
3172
  return apiForm("POST", "/skills", form);
2888
3173
  }
2889
3174
  async function listSkills() {
@@ -3001,19 +3286,19 @@ function renderPolicy(rule) {
3001
3286
  }
3002
3287
 
3003
3288
  // src/harness.ts
3004
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
3005
- import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
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";
3006
3291
  var HARNESS_FILE = "harness.json";
3007
3292
  function harnessFilePath(dir) {
3008
- return join10(resolve6(dir), HARNESS_FILE);
3293
+ return join11(resolve6(dir), HARNESS_FILE);
3009
3294
  }
3010
3295
  function readHarnessFile(dir) {
3011
3296
  const path = harnessFilePath(dir);
3012
- if (!existsSync8(path)) {
3297
+ if (!existsSync9(path)) {
3013
3298
  throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
3014
3299
  }
3015
3300
  try {
3016
- return JSON.parse(readFileSync11(path, "utf8"));
3301
+ return JSON.parse(readFileSync12(path, "utf8"));
3017
3302
  } catch (err) {
3018
3303
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
3019
3304
  }
@@ -3040,10 +3325,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
3040
3325
  `;
3041
3326
  function harnessInit(dir) {
3042
3327
  const path = harnessFilePath(dir);
3043
- if (existsSync8(path)) {
3328
+ if (existsSync9(path)) {
3044
3329
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
3045
3330
  }
3046
- mkdirSync3(dirname5(path), { recursive: true });
3331
+ mkdirSync3(dirname6(path), { recursive: true });
3047
3332
  writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
3048
3333
  `);
3049
3334
  console.log(`Created ${path}`);
@@ -3815,8 +4100,8 @@ secrets.command("unset <id> <key>").action(
3815
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(
3816
4101
  run(async (opts) => {
3817
4102
  const path = resolve7(opts.file);
3818
- if (!existsSync9(path)) {
3819
- const name = basename5(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
4103
+ if (!existsSync10(path)) {
4104
+ const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
3820
4105
  scaffoldAgentFile(path, name);
3821
4106
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
3822
4107
  }
@@ -3890,7 +4175,7 @@ function formatRelative(msAgo) {
3890
4175
  if (hours < 48) return `${hours}h ago`;
3891
4176
  return `${Math.round(hours / 24)}d ago`;
3892
4177
  }
3893
- program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").option("--no-files", "send the conversation only, without the local files it read, wrote or ran under").action(
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(
3894
4179
  run(
3895
4180
  async (opts) => {
3896
4181
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -3972,7 +4257,7 @@ program.command("import").description("import a local Claude Code or Codex sessi
3972
4257
  toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
3973
4258
  if (!opts.json) {
3974
4259
  console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
3975
- console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation and the local files it read, wrote or ran under are attached; text is redacted locally. Credential files and login credentials are never transferred. Use --no-files to send the conversation only.");
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.");
3976
4261
  }
3977
4262
  if (!autoConfirm) {
3978
4263
  if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
@@ -4014,9 +4299,13 @@ Importing ${label} \u2026
4014
4299
  gaps: result.gaps,
4015
4300
  attachedFiles: result.attachedFiles,
4016
4301
  leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
4302
+ skills: result.setup.skills,
4303
+ missingSkills: result.setup.missingSkills,
4304
+ mcpServers: result.setup.mcpServers,
4017
4305
  continueUrl: result.url
4018
4306
  }));
4019
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).`);
4020
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)` : ""}.`);
4021
4310
  console.log(` \u2713 Imported. Continue here:
4022
4311
  ${result.url}
@@ -4084,7 +4373,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
4084
4373
  );
4085
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(
4086
4375
  run(async (taskId, path, local) => {
4087
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
4376
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
4088
4377
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
4089
4378
  })
4090
4379
  );
@@ -4520,7 +4809,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
4520
4809
  }));
4521
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)");
4522
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) => {
4523
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DS35CFKV.js");
4812
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-YKEGRVJN.js");
4524
4813
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
4525
4814
  const scopes = parseContentScopes(opts.capture);
4526
4815
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -4555,7 +4844,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
4555
4844
  }));
4556
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 () => {
4557
4846
  try {
4558
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DS35CFKV.js");
4847
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-YKEGRVJN.js");
4559
4848
  const chunks = [];
4560
4849
  for await (const chunk of process.stdin) chunks.push(chunk);
4561
4850
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -4568,14 +4857,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
4568
4857
  }
4569
4858
  });
4570
4859
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
4571
- const { readState, flush, grantSender } = await import("./trace-DS35CFKV.js");
4860
+ const { readState, flush, grantSender } = await import("./trace-YKEGRVJN.js");
4572
4861
  const state = readState();
4573
4862
  if (!state) throw new Error("no trace is running here");
4574
4863
  const outcome = await flush(grantSender(state), { attempts: 4 });
4575
4864
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
4576
4865
  }));
4577
4866
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
4578
- const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
4867
+ const { readState, spoolStatus } = await import("./trace-YKEGRVJN.js");
4579
4868
  const state = readState();
4580
4869
  if (!state) {
4581
4870
  console.log("No trace is running here.");
@@ -4585,7 +4874,7 @@ traceCmd.command("status").description("the running trace and its spool").action
4585
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`);
4586
4875
  }));
4587
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) => {
4588
- const { readState } = await import("./trace-DS35CFKV.js");
4877
+ const { readState } = await import("./trace-YKEGRVJN.js");
4589
4878
  const ref = id ?? readState()?.trace_id;
4590
4879
  if (!ref) throw new Error("name a trace, or start one here");
4591
4880
  const bundle = await api("GET", `/traces/${ref}`);
@@ -4612,7 +4901,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
4612
4901
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
4613
4902
  }));
4614
4903
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
4615
- const { readState, record, flush, grantSender, clearState } = await import("./trace-DS35CFKV.js");
4904
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-YKEGRVJN.js");
4616
4905
  const state = readState();
4617
4906
  if (!state) throw new Error("no trace is running here");
4618
4907
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -4622,7 +4911,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
4622
4911
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
4623
4912
  }));
4624
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) => {
4625
- const lib = await import("./trace-library-B3NZ4OQM.js");
4914
+ const lib = await import("./trace-library-6BK5GJVD.js");
4626
4915
  if (opts.hook) {
4627
4916
  try {
4628
4917
  let raw = payload ?? "";
@@ -4670,10 +4959,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4670
4959
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
4671
4960
  }
4672
4961
  if (opts.installSchedule) {
4673
- const { join: join11 } = await import("path");
4674
- const { homedir: homedir7 } = await import("os");
4962
+ const { join: join12 } = await import("path");
4963
+ const { homedir: homedir8 } = await import("os");
4675
4964
  const { execFileSync } = await import("child_process");
4676
- const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
4965
+ const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
4677
4966
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
4678
4967
  try {
4679
4968
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -4683,11 +4972,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4683
4972
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
4684
4973
  }
4685
4974
  if (opts.status) {
4686
- const { readFileSync: readFileSync13 } = await import("fs");
4687
- const { join: join11 } = await import("path");
4975
+ const { readFileSync: readFileSync14 } = await import("fs");
4976
+ const { join: join12 } = await import("path");
4688
4977
  let state = { sessions: {} };
4689
4978
  try {
4690
- state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4979
+ state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4691
4980
  } catch {
4692
4981
  }
4693
4982
  const counts = /* @__PURE__ */ new Map();
@@ -4720,7 +5009,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4720
5009
  })();
4721
5010
  });
4722
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) => {
4723
- const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
5012
+ const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
4724
5013
  const workspace2 = opts.workspace ?? currentUniverse();
4725
5014
  if (!workspace2) throw new Error("name a workspace with --workspace");
4726
5015
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -4737,7 +5026,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
4737
5026
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
4738
5027
  }));
4739
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) => {
4740
- const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
5029
+ const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
4741
5030
  const workspace2 = opts.workspace ?? currentUniverse();
4742
5031
  if (!workspace2) throw new Error("name a workspace with --workspace");
4743
5032
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);