@agentsky/sky 2.13.0 → 2.15.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-Y3NTKJLA.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-R5FKQQMG.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -40,13 +40,13 @@ import {
40
40
  skillRefSchema,
41
41
  splitMcpServers,
42
42
  supersededBy
43
- } from "./chunk-2IKEJ74C.js";
43
+ } from "./chunk-RHRREQMF.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";
49
- import { createInterface as createInterface4 } from "readline";
47
+ import { existsSync as existsSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
48
+ import { basename as basename6, resolve as resolve7 } from "path";
49
+ import { createInterface as createInterface5 } from "readline";
50
50
  import { Command } from "commander";
51
51
 
52
52
  // src/files.ts
@@ -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,268 @@ 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(), title = null) {
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
+ title: title?.trim().slice(0, 120) || null,
1037
+ cwd: cwd ? cwd.replace(home, "~") : null,
1038
+ project: cwd ? basename3(root ?? cwd) : null,
1039
+ git: gitState,
1040
+ tree: tree.slice(0, MAX_TREE),
1041
+ tree_truncated: tree.length > MAX_TREE,
1042
+ skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
1043
+ mcp_servers: mcpServers(harness2, cwd, home)
1044
+ },
1045
+ patch
1046
+ };
1047
+ }
1048
+ function skillFiles(skill) {
1049
+ const out = [];
1050
+ const walk = (dir, depth) => {
1051
+ if (depth > 8 || out.length > 400) return;
1052
+ let entries;
1053
+ try {
1054
+ entries = readdirSync(dir, { withFileTypes: true });
1055
+ } catch {
1056
+ return;
1057
+ }
1058
+ for (const entry2 of entries) {
1059
+ if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
1060
+ const path = join2(dir, entry2.name);
1061
+ if (entry2.isDirectory()) walk(path, depth + 1);
1062
+ else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
1063
+ }
1064
+ };
1065
+ if (existsSync3(skill.dir)) walk(skill.dir, 0);
1066
+ return out;
1067
+ }
1068
+ function codexThreadName(sessionId, home = homedir2()) {
1069
+ let name = null;
1070
+ try {
1071
+ for (const line of readFileSync4(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
1072
+ if (!line.includes(sessionId)) continue;
1073
+ try {
1074
+ const entry2 = JSON.parse(line);
1075
+ if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
1076
+ } catch {
1077
+ }
1078
+ }
1079
+ } catch {
1080
+ }
1081
+ return name;
1082
+ }
1083
+
1084
+ // src/import-files.ts
833
1085
  var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
834
1086
  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
1087
  var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
@@ -878,7 +1130,7 @@ function classify(path) {
878
1130
  if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
879
1131
  if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
880
1132
  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" };
1133
+ if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
882
1134
  return null;
883
1135
  }
884
1136
  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 +1144,7 @@ function shellText(input) {
892
1144
  const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
893
1145
  return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
894
1146
  }
895
- function touchedPaths(records, cwd, home = homedir2()) {
1147
+ function touchedPaths(records, cwd, home = homedir3()) {
896
1148
  const out = /* @__PURE__ */ new Set();
897
1149
  const add = (raw, base) => {
898
1150
  if (!raw || raw.length > 1e3) return;
@@ -935,79 +1187,110 @@ function touchedPaths(records, cwd, home = homedir2()) {
935
1187
  return [...out];
936
1188
  }
937
1189
  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;
1190
+ for (let d = dir; ; d = dirname3(d)) {
1191
+ if (existsSync4(join3(d, ".git"))) return d;
1192
+ if (d === dirname3(d)) return null;
941
1193
  }
942
1194
  }
943
- function instructionPaths(cwd, harness2, home = homedir2()) {
1195
+ function instructionPaths(cwd, harness2, home = homedir3()) {
944
1196
  const out = [];
945
- if (cwd && existsSync3(cwd)) {
1197
+ if (cwd && existsSync4(cwd)) {
946
1198
  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;
1199
+ for (let d = resolve3(cwd); ; d = dirname3(d)) {
1200
+ for (const name of INSTRUCTION_FILES) out.push(join3(d, name));
1201
+ if (d === root || d === dirname3(d)) break;
950
1202
  }
951
1203
  }
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"));
1204
+ if (harness2 === "claude_code") out.push(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "CLAUDE.md"));
1205
+ if (harness2 === "codex") out.push(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "AGENTS.md"));
1206
+ out.push(...sharedInstructionPaths(home));
954
1207
  return out.filter((path) => {
955
1208
  try {
956
- return statSync(path).isFile();
1209
+ return statSync2(path).isFile();
957
1210
  } catch {
958
1211
  return false;
959
1212
  }
960
1213
  });
961
1214
  }
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);
1215
+ function resourceName(path, cwd, home = homedir3()) {
1216
+ const inside = cwd ? relative2(resolve3(cwd), path) : "";
1217
+ if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
1218
+ const underHome = relative2(home, path);
966
1219
  const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
967
- return `files/${shown.split(sep).join("/")}`;
1220
+ return `files/${shown.split(sep2).join("/")}`;
968
1221
  }
969
1222
  var ORDER = { text: 0, document: 1, image: 2 };
1223
+ function resourceLine(name, mediaType, data) {
1224
+ return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
1225
+ }
970
1226
  function collectSessionFiles(records, opts) {
971
- const home = opts.home ?? homedir2();
1227
+ const home = opts.home ?? homedir3();
972
1228
  const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
973
1229
  const leftOut = [];
974
1230
  const candidates = [];
975
1231
  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;
1232
+ const touched = touchedPaths(records, opts.cwd, home);
1233
+ const skills2 = loadedSkills(records, touched, opts.cwd, home);
1234
+ const consider = (path, name) => {
1235
+ if (seen.has(path) || exclude.has(path)) return;
978
1236
  seen.add(path);
979
1237
  if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
980
1238
  leftOut.push({ path, reason: "credential or build file" });
981
- continue;
1239
+ return;
982
1240
  }
983
1241
  let size;
984
1242
  try {
985
- const stat = statSync(path);
986
- if (!stat.isFile()) continue;
1243
+ const stat = statSync2(path);
1244
+ if (!stat.isFile()) return;
987
1245
  size = stat.size;
988
1246
  } catch {
989
1247
  leftOut.push({ path, reason: "missing" });
990
- continue;
1248
+ return;
991
1249
  }
992
1250
  const type = classify(path);
993
1251
  if (!type) {
994
1252
  leftOut.push({ path, reason: "unsupported type" });
995
- continue;
1253
+ return;
996
1254
  }
997
1255
  if (size > MAX_ATTACHED_FILE_BYTES) {
998
1256
  leftOut.push({ path, reason: "too large" });
999
- continue;
1257
+ return;
1000
1258
  }
1001
- candidates.push({ path, name: resourceName(path, opts.cwd, home), ...type, size });
1002
- }
1259
+ candidates.push({ path, name, ...type, size });
1260
+ };
1261
+ for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
1262
+ for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
1003
1263
  candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
1004
1264
  const lines = [];
1005
1265
  const attached = [];
1006
1266
  let bytes = 0;
1267
+ const state = workspaceState(opts.cwd, opts.harness, skills2, home, opts.title ?? null);
1268
+ const described = !!opts.cwd || !!state.manifest.title || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
1269
+ const generated = [
1270
+ ...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
1271
+ ...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
1272
+ ];
1273
+ for (const file of generated) {
1274
+ let line;
1275
+ try {
1276
+ line = resourceLine(file.name, file.mediaType, file.data);
1277
+ } catch {
1278
+ leftOut.push({ path: file.name, reason: "could not be redacted safely" });
1279
+ continue;
1280
+ }
1281
+ const size = Buffer.byteLength(line);
1282
+ if (bytes + size > opts.budget) {
1283
+ leftOut.push({ path: file.name, reason: "over the upload budget" });
1284
+ continue;
1285
+ }
1286
+ bytes += size;
1287
+ lines.push(line);
1288
+ attached.push({ name: file.name, kind: "text", bytes: file.data.length });
1289
+ }
1007
1290
  for (const file of candidates) {
1008
1291
  let data;
1009
1292
  try {
1010
- data = readFileSync4(file.path);
1293
+ data = readFileSync5(file.path);
1011
1294
  } catch {
1012
1295
  leftOut.push({ path: file.path, reason: "missing" });
1013
1296
  continue;
@@ -1018,7 +1301,7 @@ function collectSessionFiles(records, opts) {
1018
1301
  }
1019
1302
  let line;
1020
1303
  try {
1021
- line = redactLine(JSON.stringify({ type: "import.resource", name: file.name, media_type: file.mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
1304
+ line = resourceLine(file.name, file.mediaType, data);
1022
1305
  } catch {
1023
1306
  leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
1024
1307
  continue;
@@ -1032,7 +1315,13 @@ function collectSessionFiles(records, opts) {
1032
1315
  lines.push(line);
1033
1316
  attached.push({ name: file.name, kind: file.kind, bytes: data.length });
1034
1317
  }
1035
- return { lines, bytes, attached, leftOut };
1318
+ const setup = {
1319
+ skills: skills2.found.map((skill) => skill.name),
1320
+ missingSkills: skills2.missing,
1321
+ mcpServers: state.manifest.mcp_servers.map((server) => server.name),
1322
+ git: state.manifest.git !== null
1323
+ };
1324
+ return { lines, bytes, attached, leftOut, setup };
1036
1325
  }
1037
1326
 
1038
1327
  // src/import-local.ts
@@ -1107,16 +1396,16 @@ function sniffTitle(filePath) {
1107
1396
  }
1108
1397
  }
1109
1398
  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 [];
1399
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
1400
+ const projectsDir = join4(root, "projects");
1401
+ if (!existsSync5(projectsDir)) return [];
1113
1402
  const sessions = [];
1114
1403
  try {
1115
- for (const encoded of readdirSync(projectsDir)) {
1116
- const projectDir = join3(projectsDir, encoded);
1404
+ for (const encoded of readdirSync2(projectsDir)) {
1405
+ const projectDir = join4(projectsDir, encoded);
1117
1406
  let entries;
1118
1407
  try {
1119
- entries = readdirSync(projectDir);
1408
+ entries = readdirSync2(projectDir);
1120
1409
  } catch {
1121
1410
  continue;
1122
1411
  }
@@ -1124,22 +1413,22 @@ function listClaudeCodeSessions(limit) {
1124
1413
  for (const entry2 of entries) {
1125
1414
  if (!entry2.endsWith(".jsonl")) continue;
1126
1415
  const sessionId = entry2.slice(0, -6);
1127
- const mainFile = join3(projectDir, entry2);
1416
+ const mainFile = join4(projectDir, entry2);
1128
1417
  let stat;
1129
1418
  try {
1130
- stat = statSync2(mainFile);
1419
+ stat = statSync3(mainFile);
1131
1420
  } catch {
1132
1421
  continue;
1133
1422
  }
1134
1423
  if (!stat.isFile()) continue;
1135
1424
  let discoveryError;
1136
1425
  const subagentFiles = [];
1137
- const subagentDir = join3(projectDir, sessionId, "subagents");
1138
- if (existsSync4(subagentDir)) {
1426
+ const subagentDir = join4(projectDir, sessionId, "subagents");
1427
+ if (existsSync5(subagentDir)) {
1139
1428
  try {
1140
- const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1429
+ const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1141
1430
  for (const f of subFiles) {
1142
- subagentFiles.push(join3(subagentDir, f));
1431
+ subagentFiles.push(join4(subagentDir, f));
1143
1432
  }
1144
1433
  } catch {
1145
1434
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -1161,17 +1450,17 @@ function listClaudeCodeSessions(limit) {
1161
1450
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
1162
1451
  }
1163
1452
  function listCodexSessions(limit) {
1164
- const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
1453
+ const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
1165
1454
  const records = [];
1166
1455
  function walk(dir) {
1167
1456
  let entries;
1168
1457
  try {
1169
- entries = readdirSync(dir, { withFileTypes: true });
1458
+ entries = readdirSync2(dir, { withFileTypes: true });
1170
1459
  } catch {
1171
1460
  return;
1172
1461
  }
1173
1462
  for (const entry2 of entries) {
1174
- const full = join3(dir, entry2.name);
1463
+ const full = join4(dir, entry2.name);
1175
1464
  if (entry2.isDirectory()) {
1176
1465
  walk(full);
1177
1466
  continue;
@@ -1189,7 +1478,7 @@ function listCodexSessions(limit) {
1189
1478
  sessionId: data.id,
1190
1479
  workspace: typeof data.cwd === "string" ? data.cwd : dir,
1191
1480
  title: sniffTitle(full),
1192
- mtimeMs: statSync2(full).mtimeMs,
1481
+ mtimeMs: statSync3(full).mtimeMs,
1193
1482
  files: [full]
1194
1483
  },
1195
1484
  ...typeof parent === "string" ? { parent } : {}
@@ -1258,7 +1547,7 @@ async function mintTrace(base, harness2) {
1258
1547
  function loadSummaryFile(summaryPath) {
1259
1548
  let raw;
1260
1549
  try {
1261
- raw = readFileSync5(summaryPath, "utf8");
1550
+ raw = readFileSync6(summaryPath, "utf8");
1262
1551
  } catch {
1263
1552
  throw new Error(`Cannot read summary file: ${summaryPath}`);
1264
1553
  }
@@ -1272,8 +1561,8 @@ function loadSummaryFile(summaryPath) {
1272
1561
  const p = pkg;
1273
1562
  if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
1274
1563
  if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
1275
- const redacted = redactLine2(raw.trimEnd());
1276
- return redacted;
1564
+ const redacted2 = redactLine2(raw.trimEnd());
1565
+ return redacted2;
1277
1566
  }
1278
1567
  var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
1279
1568
  var SUMMARY_RESERVE_BYTES = 128 * 1024;
@@ -1299,9 +1588,9 @@ function prepareImport(session2, summaryPath, options = {}) {
1299
1588
  const source = new SessionSourceCollector();
1300
1589
  const records = [];
1301
1590
  for (const path of session2.files) {
1302
- const stat = statSync2(path);
1591
+ const stat = statSync3(path);
1303
1592
  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");
1593
+ const text = readFileSync6(path, "utf8");
1305
1594
  for (const raw of text.split("\n")) {
1306
1595
  if (!raw.trim()) continue;
1307
1596
  let record;
@@ -1340,8 +1629,14 @@ function prepareImport(session2, summaryPath, options = {}) {
1340
1629
  const sourceId = nativeId ?? session2.sessionId;
1341
1630
  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
1631
  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).` : "";
1632
+ const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, {
1633
+ harness: session2.harness,
1634
+ cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""),
1635
+ budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES,
1636
+ exclude: session2.files,
1637
+ title: meta.title ?? (session2.harness === "codex" ? codexThreadName(session2.sessionId) : null)
1638
+ });
1639
+ 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
1640
  let summaryJson;
1346
1641
  if (summaryPath) {
1347
1642
  summaryJson = loadSummaryFile(summaryPath);
@@ -1361,7 +1656,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1361
1656
  completed: [],
1362
1657
  pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
1363
1658
  verification: [],
1364
- blockers: [leftOutLine(files2.leftOut)],
1659
+ 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
1660
  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
1661
  provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
1367
1662
  }
@@ -1373,7 +1668,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1373
1668
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1374
1669
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1375
1670
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1376
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
1671
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
1377
1672
  }
1378
1673
  async function importSession(opts) {
1379
1674
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1382,7 +1677,7 @@ async function importSession(opts) {
1382
1677
  throw new Error("Import requires HTTPS, or a loopback development server.");
1383
1678
  }
1384
1679
  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 });
1680
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1386
1681
  let traceId;
1387
1682
  let key;
1388
1683
  if (opts.importKey && opts.grantTraceId) {
@@ -1416,7 +1711,8 @@ async function importSession(opts) {
1416
1711
  redactionCount,
1417
1712
  gaps,
1418
1713
  attachedFiles,
1419
- leftOutFiles
1714
+ leftOutFiles,
1715
+ setup
1420
1716
  };
1421
1717
  }
1422
1718
 
@@ -1520,23 +1816,23 @@ async function headlessLogin(verifier, challenge) {
1520
1816
  }
1521
1817
 
1522
1818
  // 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");
1819
+ import { existsSync as existsSync6 } from "fs";
1820
+ import { homedir as homedir5 } from "os";
1821
+ import { join as join5 } from "path";
1822
+ function localAgents(env = process.env, home = homedir5()) {
1823
+ const claude = env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
1824
+ const codex = env.CODEX_HOME || join5(home, ".codex");
1825
+ const cursor = join5(home, ".cursor");
1530
1826
  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") }
1827
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join5(claude, "skills") },
1828
+ { id: "codex", name: "Codex", root: codex, skillsDir: join5(codex, "skills") },
1829
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join5(cursor, "skills") }
1534
1830
  ];
1535
1831
  }
1536
- function detectLocalAgents(env = process.env, home = homedir4()) {
1537
- return localAgents(env, home).filter((agent2) => existsSync5(agent2.root));
1832
+ function detectLocalAgents(env = process.env, home = homedir5()) {
1833
+ return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
1538
1834
  }
1539
- function selectLocalAgents(ids, env = process.env, home = homedir4()) {
1835
+ function selectLocalAgents(ids, env = process.env, home = homedir5()) {
1540
1836
  if (!ids?.length) return detectLocalAgents(env, home);
1541
1837
  const known = localAgents(env, home);
1542
1838
  return ids.map((id) => {
@@ -1635,16 +1931,16 @@ async function readTurn(sessionId, opts = {}) {
1635
1931
  }
1636
1932
 
1637
1933
  // 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";
1934
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
1935
+ import { dirname as dirname5, join as join9 } from "path";
1640
1936
  import { createInterface as createInterface3 } from "readline";
1641
1937
 
1642
1938
  // src/mcp-register.ts
1643
- import { spawnSync } from "child_process";
1939
+ import { spawnSync as spawnSync2 } from "child_process";
1644
1940
 
1645
1941
  // src/skill.ts
1646
- import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1647
- import { join as join5 } from "path";
1942
+ import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1943
+ import { join as join6 } from "path";
1648
1944
 
1649
1945
  // src/catalog-file.ts
1650
1946
  var CATALOG_FILE = "catalog.md";
@@ -1661,8 +1957,8 @@ function entry(agent2) {
1661
1957
  const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
1662
1958
  if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
1663
1959
  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.");
1960
+ const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
1961
+ if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
1666
1962
  const details = describe(rest);
1667
1963
  if (details) lines.push(`Details: ${details}`);
1668
1964
  const pricing = describe(agent2.pricing);
@@ -2085,7 +2381,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
2085
2381
  }
2086
2382
  function installedVersion(dir) {
2087
2383
  try {
2088
- const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
2384
+ const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
2089
2385
  return typeof marker.version === "string" ? marker.version : null;
2090
2386
  } catch {
2091
2387
  return null;
@@ -2095,25 +2391,25 @@ function isInstalledSkill(dir) {
2095
2391
  return installedVersion(dir) !== null;
2096
2392
  }
2097
2393
  function installSkill(agent2, opts = {}) {
2098
- const dir = join5(agent2.skillsDir, SKILL_NAME);
2394
+ const dir = join6(agent2.skillsDir, SKILL_NAME);
2099
2395
  const previous = installedVersion(dir);
2100
2396
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
2101
2397
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
2102
2398
  if (outcome !== "current") {
2103
2399
  mkdirSync(dir, { recursive: true });
2104
- writeFileSync3(join5(dir, "SKILL.md"), skillDocument());
2105
- writeFileSync3(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2400
+ writeFileSync3(join6(dir, "SKILL.md"), skillDocument());
2401
+ writeFileSync3(join6(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2106
2402
  `);
2107
2403
  }
2108
2404
  if (opts.catalog) {
2109
2405
  mkdirSync(dir, { recursive: true });
2110
- writeFileSync3(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2406
+ writeFileSync3(join6(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2111
2407
  }
2112
- const refsDir = join5(dir, REFERENCES_DIR);
2408
+ const refsDir = join6(dir, REFERENCES_DIR);
2113
2409
  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());
2410
+ writeFileSync3(join6(refsDir, "import.md"), importReferenceDoc());
2411
+ writeFileSync3(join6(refsDir, "workspaces.md"), workspacesReferenceDoc());
2412
+ writeFileSync3(join6(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
2117
2413
  return { agent: agent2.id, path: dir, outcome };
2118
2414
  }
2119
2415
 
@@ -2122,9 +2418,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
2122
2418
  var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
2123
2419
  function systemCommandRunner() {
2124
2420
  return {
2125
- has: (command) => spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0,
2421
+ has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
2126
2422
  run: (command, args) => {
2127
- const result = spawnSync(command, args, { encoding: "utf8" });
2423
+ const result = spawnSync2(command, args, { encoding: "utf8" });
2128
2424
  return { status: result.status, stderr: result.stderr ?? "" };
2129
2425
  }
2130
2426
  };
@@ -2164,10 +2460,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
2164
2460
  }
2165
2461
 
2166
2462
  // 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";
2463
+ import { isUtf8 as isUtf82 } from "buffer";
2464
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
2465
+ import { homedir as homedir6 } from "os";
2466
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
2467
+ import { parse as parseToml3 } from "smol-toml";
2171
2468
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
2172
2469
  var MAX_IMPORT_DEPTH = 5;
2173
2470
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -2179,14 +2476,24 @@ function frontmatterName(skillMd) {
2179
2476
  function sensitive(name) {
2180
2477
  return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
2181
2478
  }
2182
- function skillFiles(root, prefix) {
2479
+ function redacted(data) {
2480
+ if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
2481
+ try {
2482
+ const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
2483
+ return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
2484
+ } catch {
2485
+ return null;
2486
+ }
2487
+ }
2488
+ function skillFiles2(root, prefix) {
2183
2489
  const files2 = [];
2184
- for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
2185
- const path = join6(root, entry2.name);
2490
+ for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
2491
+ const path = join7(root, entry2.name);
2186
2492
  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)) });
2493
+ if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
2494
+ } else if (entry2.isFile() && !sensitive(entry2.name) && statSync4(path).size <= MAX_SKILL_FILE_BYTES) {
2495
+ const bytes = redacted(readFileSync8(path));
2496
+ if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
2190
2497
  }
2191
2498
  }
2192
2499
  return files2;
@@ -2195,19 +2502,19 @@ function readLocalSkills(agents) {
2195
2502
  const skills2 = [];
2196
2503
  const ignored = [];
2197
2504
  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();
2505
+ if (!existsSync7(agent2.skillsDir)) continue;
2506
+ for (const entry2 of readdirSync3(agent2.skillsDir, { withFileTypes: true })) {
2507
+ const dir = join7(agent2.skillsDir, entry2.name);
2508
+ let isDir2 = entry2.isDirectory();
2202
2509
  if (entry2.isSymbolicLink()) {
2203
2510
  try {
2204
- isDir = statSync3(dir).isDirectory();
2511
+ isDir2 = statSync4(dir).isDirectory();
2205
2512
  } catch {
2206
- isDir = false;
2513
+ isDir2 = false;
2207
2514
  }
2208
2515
  }
2209
- if (!isDir || !existsSync6(join6(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2210
- const name = frontmatterName(readFileSync7(join6(dir, "SKILL.md"), "utf8"));
2516
+ if (!isDir2 || !existsSync7(join7(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2517
+ const name = frontmatterName(readFileSync8(join7(dir, "SKILL.md"), "utf8"));
2211
2518
  if (!name) {
2212
2519
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
2213
2520
  continue;
@@ -2217,7 +2524,7 @@ function readLocalSkills(agents) {
2217
2524
  ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
2218
2525
  continue;
2219
2526
  }
2220
- const files2 = skillFiles(dir, entry2.name);
2527
+ const files2 = skillFiles2(dir, entry2.name);
2221
2528
  skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
2222
2529
  }
2223
2530
  }
@@ -2235,11 +2542,11 @@ function inlineImports(content, from, home, depth, seen) {
2235
2542
  }
2236
2543
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
2237
2544
  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);
2545
+ const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
2239
2546
  if (seen.includes(path)) return "";
2240
2547
  let imported;
2241
2548
  try {
2242
- imported = readFileSync7(path, "utf8");
2549
+ imported = readFileSync8(path, "utf8");
2243
2550
  } catch {
2244
2551
  return line;
2245
2552
  }
@@ -2247,15 +2554,15 @@ function inlineImports(content, from, home, depth, seen) {
2247
2554
  return inlineImports(imported, path, home, depth + 1, seen).trim();
2248
2555
  }).join("\n");
2249
2556
  }
2250
- function readGlobalInstructions(agents, home = homedir5()) {
2557
+ function readGlobalInstructions(agents, home = homedir6()) {
2251
2558
  const found = [];
2252
2559
  for (const agent2 of agents) {
2253
2560
  const file = INSTRUCTION_FILES2[agent2.id];
2254
2561
  if (!file) continue;
2255
- const path = join6(agent2.root, file);
2562
+ const path = join7(agent2.root, file);
2256
2563
  let raw;
2257
2564
  try {
2258
- raw = readFileSync7(path, "utf8");
2565
+ raw = readFileSync8(path, "utf8");
2259
2566
  } catch {
2260
2567
  continue;
2261
2568
  }
@@ -2272,9 +2579,9 @@ function combineInstructions(found) {
2272
2579
 
2273
2580
  ${item.content.trim()}`).join("\n\n");
2274
2581
  }
2275
- function readJson2(path) {
2582
+ function readJson3(path) {
2276
2583
  try {
2277
- return JSON.parse(readFileSync7(path, "utf8"));
2584
+ return JSON.parse(readFileSync8(path, "utf8"));
2278
2585
  } catch {
2279
2586
  return void 0;
2280
2587
  }
@@ -2288,15 +2595,15 @@ function bearerOf(def) {
2288
2595
  return match?.[1] ?? null;
2289
2596
  }
2290
2597
  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 ?? {};
2598
+ if (agent2.id === "claude_code") return readJson3(join7(home, ".claude.json"))?.mcpServers ?? {};
2599
+ if (agent2.id === "cursor") return readJson3(join7(agent2.root, "mcp.json"))?.mcpServers ?? {};
2293
2600
  try {
2294
- return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2601
+ return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2295
2602
  } catch {
2296
2603
  return {};
2297
2604
  }
2298
2605
  }
2299
- function findLocalSecrets(agents, home = homedir5()) {
2606
+ function findLocalSecrets(agents, home = homedir6()) {
2300
2607
  const secrets2 = [];
2301
2608
  for (const agent2 of agents) {
2302
2609
  for (const [name, def] of Object.entries(serversOf(agent2, home))) {
@@ -2312,9 +2619,9 @@ function findLocalSecrets(agents, home = homedir5()) {
2312
2619
 
2313
2620
  // src/subscriptions.ts
2314
2621
  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";
2622
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
2623
+ import { homedir as homedir7 } from "os";
2624
+ import { join as join8 } from "path";
2318
2625
  import { createInterface as createInterface2 } from "readline";
2319
2626
  var ALIASES = {
2320
2627
  claude: "anthropic",
@@ -2399,12 +2706,12 @@ async function connectClaude() {
2399
2706
  const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
2400
2707
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
2401
2708
  }
2402
- function codexSignIn(env = process.env, home = homedir6()) {
2403
- const path = join7(env.CODEX_HOME || join7(home, ".codex"), "auth.json");
2404
- if (!existsSync7(path)) {
2709
+ function codexSignIn(env = process.env, home = homedir7()) {
2710
+ const path = join8(env.CODEX_HOME || join8(home, ".codex"), "auth.json");
2711
+ if (!existsSync8(path)) {
2405
2712
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
2406
2713
  }
2407
- const raw = readFileSync8(path, "utf8");
2714
+ const raw = readFileSync9(path, "utf8");
2408
2715
  try {
2409
2716
  const parsed = JSON.parse(raw);
2410
2717
  return { path, raw, account: parsed.tokens?.account_id };
@@ -2412,12 +2719,12 @@ function codexSignIn(env = process.env, home = homedir6()) {
2412
2719
  throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
2413
2720
  }
2414
2721
  }
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)) {
2722
+ function cursorSignIn(env = process.env, home = homedir7()) {
2723
+ const path = join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "cursor", "auth.json");
2724
+ if (!existsSync8(path)) {
2418
2725
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
2419
2726
  }
2420
- const raw = readFileSync8(path, "utf8");
2727
+ const raw = readFileSync9(path, "utf8");
2421
2728
  try {
2422
2729
  const parsed = JSON.parse(raw);
2423
2730
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -2487,20 +2794,20 @@ async function disconnect(name, opts) {
2487
2794
  // src/setup.ts
2488
2795
  var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
2489
2796
  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) => {
2797
+ function planProviders(names2) {
2798
+ if (!names2?.length) return null;
2799
+ return new Set(names2.map((name) => {
2493
2800
  const provider = PLAN_PROVIDERS[name.toLowerCase()];
2494
2801
  if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
2495
2802
  return provider;
2496
2803
  }));
2497
2804
  }
2498
2805
  function reportPath() {
2499
- return join8(dirname4(configPath()), "setup.json");
2806
+ return join9(dirname5(configPath()), "setup.json");
2500
2807
  }
2501
2808
  function loadReport() {
2502
2809
  try {
2503
- const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
2810
+ const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
2504
2811
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
2505
2812
  } catch {
2506
2813
  return { agents: [], plans: [] };
@@ -2513,7 +2820,7 @@ function mergeReport(base, next) {
2513
2820
  return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
2514
2821
  }
2515
2822
  function saveReport(report) {
2516
- mkdirSync2(dirname4(reportPath()), { recursive: true });
2823
+ mkdirSync2(dirname5(reportPath()), { recursive: true });
2517
2824
  writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
2518
2825
  `, { mode: 384 });
2519
2826
  }
@@ -2863,17 +3170,17 @@ function renderArtifact(artifact2) {
2863
3170
  }
2864
3171
 
2865
3172
  // 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";
3173
+ import { readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
3174
+ import { basename as basename5, join as join10, relative as relative3, resolve as resolve5 } from "path";
2868
3175
  function skillBundle(dir) {
2869
3176
  const root = resolve5(dir);
2870
- const top = basename4(root);
3177
+ const top = basename5(root);
2871
3178
  const files2 = [];
2872
3179
  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) });
3180
+ for (const name of readdirSync4(path).sort()) {
3181
+ const full = join10(path, name);
3182
+ if (statSync5(full).isDirectory()) walk(full);
3183
+ else files2.push({ path: `${top}/${relative3(root, full)}`, bytes: readFileSync11(full) });
2877
3184
  }
2878
3185
  };
2879
3186
  walk(root);
@@ -2883,7 +3190,7 @@ function skillBundle(dir) {
2883
3190
  async function uploadSkill(dir, displayName) {
2884
3191
  const form = new FormData();
2885
3192
  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)));
3193
+ form.append("displayName", displayName ?? basename5(resolve5(dir)));
2887
3194
  return apiForm("POST", "/skills", form);
2888
3195
  }
2889
3196
  async function listSkills() {
@@ -2907,6 +3214,7 @@ function renderRequest(request) {
2907
3214
  }
2908
3215
 
2909
3216
  // src/workspace.ts
3217
+ import { createInterface as createInterface4 } from "readline";
2910
3218
  var KINDS = ["TOOL_CALL", "SPEND_CAP", "CALL_COUNT", "CAPABILITY"];
2911
3219
  async function workspaceSlug(explicit) {
2912
3220
  if (explicit) return explicit;
@@ -2999,21 +3307,66 @@ function renderPolicy(rule) {
2999
3307
  const state = rule.enabled ? "on " : "off";
3000
3308
  return `${state} ${rule.layer.padEnd(9)} ${rule.kind.padEnd(10)} ${rule.name} ${JSON.stringify(rule.config)} ${rule.id}`;
3001
3309
  }
3310
+ var REMOVED_KINDS = [
3311
+ ["sessions", "sessions and their transcripts"],
3312
+ ["agents", "agents"],
3313
+ ["artifacts", "artifacts"],
3314
+ ["files", "task files"],
3315
+ ["memory_stores", "memory stores"],
3316
+ ["routines", "routines"],
3317
+ ["channels", "channel connections"],
3318
+ ["webhooks", "webhooks"],
3319
+ ["api_keys", "API keys"],
3320
+ ["vaults", "vaults"],
3321
+ ["skills", "skills"],
3322
+ ["environments", "environments"],
3323
+ ["connections", "connected apps"],
3324
+ ["traces", "uploaded traces"],
3325
+ ["members", "other members"],
3326
+ ["invites", "pending invitations"]
3327
+ ];
3328
+ async function deletionPreview(slug) {
3329
+ return api("GET", `/universes/${encodeURIComponent(slug)}/deletion`);
3330
+ }
3331
+ function renderDeletionPreview(preview) {
3332
+ if (!preview.can_delete) return [`${preview.workspace.slug} cannot be deleted:`, ...preview.blockers.map((b) => ` ${b.message}`)];
3333
+ const lines = [`Deleting ${preview.workspace.slug} (${preview.workspace.name}) permanently removes:`];
3334
+ const removed = REMOVED_KINDS.filter(([key]) => (preview.removes[key] ?? 0) > 0);
3335
+ for (const [key, label] of removed) lines.push(` ${preview.removes[key]} ${label}`);
3336
+ if (!removed.length) lines.push(" nothing: the workspace is empty");
3337
+ const running = preview.removes.running_sessions ?? 0;
3338
+ if (running) lines.push(`${running} running session${running === 1 ? " is" : "s are"} stopped first.`);
3339
+ if (preview.wallet) lines.push(`$${(preview.wallet.balance_micros / 1e6).toFixed(2)} in its wallet goes back to ${preview.wallet.returns_to ?? "you"}.`);
3340
+ lines.push("This cannot be undone.");
3341
+ return lines;
3342
+ }
3343
+ async function deleteWorkspace(slug) {
3344
+ return (await api("DELETE", `/universes/${encodeURIComponent(slug)}`)).deleted;
3345
+ }
3346
+ function typedConfirmation(slug) {
3347
+ return new Promise((done) => {
3348
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
3349
+ rl.question(`Type ${slug} to delete it: `, (answer) => {
3350
+ rl.close();
3351
+ done(answer.trim() === slug);
3352
+ });
3353
+ });
3354
+ }
3002
3355
 
3003
3356
  // 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";
3357
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
3358
+ import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
3006
3359
  var HARNESS_FILE = "harness.json";
3007
3360
  function harnessFilePath(dir) {
3008
- return join10(resolve6(dir), HARNESS_FILE);
3361
+ return join11(resolve6(dir), HARNESS_FILE);
3009
3362
  }
3010
3363
  function readHarnessFile(dir) {
3011
3364
  const path = harnessFilePath(dir);
3012
- if (!existsSync8(path)) {
3365
+ if (!existsSync9(path)) {
3013
3366
  throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
3014
3367
  }
3015
3368
  try {
3016
- return JSON.parse(readFileSync11(path, "utf8"));
3369
+ return JSON.parse(readFileSync12(path, "utf8"));
3017
3370
  } catch (err) {
3018
3371
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
3019
3372
  }
@@ -3040,10 +3393,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
3040
3393
  `;
3041
3394
  function harnessInit(dir) {
3042
3395
  const path = harnessFilePath(dir);
3043
- if (existsSync8(path)) {
3396
+ if (existsSync9(path)) {
3044
3397
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
3045
3398
  }
3046
- mkdirSync3(dirname5(path), { recursive: true });
3399
+ mkdirSync3(dirname6(path), { recursive: true });
3047
3400
  writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
3048
3401
  `);
3049
3402
  console.log(`Created ${path}`);
@@ -3280,6 +3633,19 @@ universe.command("remove <member>").description("remove a member (user id or ema
3280
3633
  console.log(`Removed ${member.email ?? member.user_id}`);
3281
3634
  })
3282
3635
  );
3636
+ async function deleteWorkspaceCommand(slug, opts) {
3637
+ const preview = await deletionPreview(slug);
3638
+ for (const line of renderDeletionPreview(preview)) console.log(line);
3639
+ if (!preview.can_delete) process.exit(1);
3640
+ if (!opts.yes && !await typedConfirmation(slug)) {
3641
+ console.log("Aborted.");
3642
+ return;
3643
+ }
3644
+ const deleted = await deleteWorkspace(slug);
3645
+ if (currentUniverse() === slug) saveConfig({ ...loadConfig(), currentUniverse: void 0 });
3646
+ console.log(`Deleted ${deleted.slug}${deleted.sessions_stopped ? ` \xB7 stopped ${deleted.sessions_stopped} running session${deleted.sessions_stopped === 1 ? "" : "s"}` : ""}`);
3647
+ }
3648
+ universe.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
3283
3649
  universe.command("join <token>").description("accept an invitation sent to your verified email").action(
3284
3650
  run(async (token) => {
3285
3651
  const workspace2 = await joinWorkspace(token);
@@ -3297,6 +3663,7 @@ workspace.command("list").action(
3297
3663
  }
3298
3664
  })
3299
3665
  );
3666
+ workspace.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
3300
3667
  var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
3301
3668
  async function policyScope(opts) {
3302
3669
  return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
@@ -3700,7 +4067,7 @@ async function sendAndFollow(sessionId, text, opts) {
3700
4067
  async function chatLoop(sessionId, opts = {}) {
3701
4068
  console.log(`Chatting with ${sessionId} \u2014 Ctrl-C to leave.`);
3702
4069
  const controller = new AbortController();
3703
- const rl = createInterface4({ input: process.stdin, output: process.stdout, prompt: "> " });
4070
+ const rl = createInterface5({ input: process.stdin, output: process.stdout, prompt: "> " });
3704
4071
  let system = opts.system;
3705
4072
  void (async () => {
3706
4073
  try {
@@ -3815,8 +4182,8 @@ secrets.command("unset <id> <key>").action(
3815
4182
  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
4183
  run(async (opts) => {
3817
4184
  const path = resolve7(opts.file);
3818
- if (!existsSync9(path)) {
3819
- const name = basename5(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
4185
+ if (!existsSync10(path)) {
4186
+ const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
3820
4187
  scaffoldAgentFile(path, name);
3821
4188
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
3822
4189
  }
@@ -3890,7 +4257,7 @@ function formatRelative(msAgo) {
3890
4257
  if (hours < 48) return `${hours}h ago`;
3891
4258
  return `${Math.round(hours / 24)}d ago`;
3892
4259
  }
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(
4260
+ 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
4261
  run(
3895
4262
  async (opts) => {
3896
4263
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -3948,7 +4315,7 @@ program.command("import").description("import a local Claude Code or Codex sessi
3948
4315
  console.log(` ${ws}`);
3949
4316
  });
3950
4317
  console.log();
3951
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
4318
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
3952
4319
  const answer = await new Promise(
3953
4320
  (resolve8) => rl.question(
3954
4321
  `Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
@@ -3972,11 +4339,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
3972
4339
  toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
3973
4340
  if (!opts.json) {
3974
4341
  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.");
4342
+ 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
4343
  }
3977
4344
  if (!autoConfirm) {
3978
4345
  if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
3979
- const prompt = createInterface4({ input: process.stdin, output: process.stdout });
4346
+ const prompt = createInterface5({ input: process.stdin, output: process.stdout });
3980
4347
  const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
3981
4348
  prompt.close();
3982
4349
  resolve8(value);
@@ -4014,9 +4381,13 @@ Importing ${label} \u2026
4014
4381
  gaps: result.gaps,
4015
4382
  attachedFiles: result.attachedFiles,
4016
4383
  leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
4384
+ skills: result.setup.skills,
4385
+ missingSkills: result.setup.missingSkills,
4386
+ mcpServers: result.setup.mcpServers,
4017
4387
  continueUrl: result.url
4018
4388
  }));
4019
4389
  } else {
4390
+ 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
4391
  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
4392
  console.log(` \u2713 Imported. Continue here:
4022
4393
  ${result.url}
@@ -4084,7 +4455,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
4084
4455
  );
4085
4456
  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
4457
  run(async (taskId, path, local) => {
4087
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
4458
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
4088
4459
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
4089
4460
  })
4090
4461
  );
@@ -4520,7 +4891,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
4520
4891
  }));
4521
4892
  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
4893
  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");
4894
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-PMUETYJS.js");
4524
4895
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
4525
4896
  const scopes = parseContentScopes(opts.capture);
4526
4897
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -4555,7 +4926,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
4555
4926
  }));
4556
4927
  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
4928
  try {
4558
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DS35CFKV.js");
4929
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-PMUETYJS.js");
4559
4930
  const chunks = [];
4560
4931
  for await (const chunk of process.stdin) chunks.push(chunk);
4561
4932
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -4568,14 +4939,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
4568
4939
  }
4569
4940
  });
4570
4941
  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");
4942
+ const { readState, flush, grantSender } = await import("./trace-PMUETYJS.js");
4572
4943
  const state = readState();
4573
4944
  if (!state) throw new Error("no trace is running here");
4574
4945
  const outcome = await flush(grantSender(state), { attempts: 4 });
4575
4946
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
4576
4947
  }));
4577
4948
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
4578
- const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
4949
+ const { readState, spoolStatus } = await import("./trace-PMUETYJS.js");
4579
4950
  const state = readState();
4580
4951
  if (!state) {
4581
4952
  console.log("No trace is running here.");
@@ -4585,7 +4956,7 @@ traceCmd.command("status").description("the running trace and its spool").action
4585
4956
  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
4957
  }));
4587
4958
  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");
4959
+ const { readState } = await import("./trace-PMUETYJS.js");
4589
4960
  const ref = id ?? readState()?.trace_id;
4590
4961
  if (!ref) throw new Error("name a trace, or start one here");
4591
4962
  const bundle = await api("GET", `/traces/${ref}`);
@@ -4612,7 +4983,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
4612
4983
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
4613
4984
  }));
4614
4985
  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");
4986
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-PMUETYJS.js");
4616
4987
  const state = readState();
4617
4988
  if (!state) throw new Error("no trace is running here");
4618
4989
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -4622,7 +4993,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
4622
4993
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
4623
4994
  }));
4624
4995
  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");
4996
+ const lib = await import("./trace-library-FIMBRED6.js");
4626
4997
  if (opts.hook) {
4627
4998
  try {
4628
4999
  let raw = payload ?? "";
@@ -4670,10 +5041,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4670
5041
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
4671
5042
  }
4672
5043
  if (opts.installSchedule) {
4673
- const { join: join11 } = await import("path");
4674
- const { homedir: homedir7 } = await import("os");
5044
+ const { join: join12 } = await import("path");
5045
+ const { homedir: homedir8 } = await import("os");
4675
5046
  const { execFileSync } = await import("child_process");
4676
- const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
5047
+ const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
4677
5048
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
4678
5049
  try {
4679
5050
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -4683,11 +5054,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4683
5054
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
4684
5055
  }
4685
5056
  if (opts.status) {
4686
- const { readFileSync: readFileSync13 } = await import("fs");
4687
- const { join: join11 } = await import("path");
5057
+ const { readFileSync: readFileSync14 } = await import("fs");
5058
+ const { join: join12 } = await import("path");
4688
5059
  let state = { sessions: {} };
4689
5060
  try {
4690
- state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
5061
+ state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
4691
5062
  } catch {
4692
5063
  }
4693
5064
  const counts = /* @__PURE__ */ new Map();
@@ -4720,7 +5091,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
4720
5091
  })();
4721
5092
  });
4722
5093
  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");
5094
+ const { libraryRequest } = await import("./trace-library-FIMBRED6.js");
4724
5095
  const workspace2 = opts.workspace ?? currentUniverse();
4725
5096
  if (!workspace2) throw new Error("name a workspace with --workspace");
4726
5097
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -4737,7 +5108,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
4737
5108
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
4738
5109
  }));
4739
5110
  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");
5111
+ const { libraryRequest } = await import("./trace-library-FIMBRED6.js");
4741
5112
  const workspace2 = opts.workspace ?? currentUniverse();
4742
5113
  if (!workspace2) throw new Error("name a workspace with --workspace");
4743
5114
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);