@agentsky/sky 2.16.0 → 2.18.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-MQECNHD2.js";
12
+ } from "./chunk-PL5KNPCX.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-5JHQH7WH.js";
23
+ } from "./chunk-MUHFB3LC.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -37,21 +37,22 @@ import {
37
37
  modelSubscriptionProviderFor,
38
38
  reasoningEffortDocLine,
39
39
  redactLine,
40
+ redactSecretValues,
40
41
  skillRefSchema,
41
42
  splitMcpServers,
42
43
  supersededBy
43
- } from "./chunk-RHRREQMF.js";
44
+ } from "./chunk-ZJVFWFOY.js";
44
45
  import "./chunk-MLKGABMK.js";
45
46
 
46
47
  // src/index.ts
47
- import { existsSync as existsSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
48
+ import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
48
49
  import { basename as basename8, resolve as resolve9 } from "path";
49
50
  import { createInterface as createInterface6 } from "readline";
50
51
  import { Command } from "commander";
51
52
 
52
53
  // src/files.ts
53
- import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
54
- import { basename as basename2, extname } from "path";
54
+ import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
55
+ import { basename as basename2, extname, join as join2 } from "path";
55
56
 
56
57
  // src/resumable-upload.ts
57
58
  import { createHash } from "crypto";
@@ -183,6 +184,58 @@ async function uploadLocalFile(path, expiresInSeconds, onProgress = () => {
183
184
  const bytes = readFileSync2(path);
184
185
  return uploadFile(new Uint8Array(bytes), basename2(path), mediaTypeFor(path), expiresInSeconds);
185
186
  }
187
+ var IGNORED_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
188
+ function filesUnder(paths) {
189
+ const found = [];
190
+ const walk = (path) => {
191
+ const stat = statSync2(path);
192
+ if (stat.isFile()) {
193
+ if (!IGNORED_NAMES.has(basename2(path))) found.push(path);
194
+ return;
195
+ }
196
+ if (!stat.isDirectory()) return;
197
+ for (const name of readdirSync(path).sort()) walk(join2(path, name));
198
+ };
199
+ for (const path of paths) walk(path);
200
+ return found;
201
+ }
202
+ function transient(error) {
203
+ return !(error instanceof ApiError) || error.status === 429 || error.status >= 500;
204
+ }
205
+ async function uploadLocalFiles(paths, {
206
+ concurrency = 4,
207
+ attempts = 4,
208
+ expiresInSeconds,
209
+ onUploaded,
210
+ onFailed,
211
+ wait = (ms) => new Promise((done) => setTimeout(done, ms))
212
+ }) {
213
+ let next = 0;
214
+ let uploaded = 0;
215
+ let failed = 0;
216
+ const worker = async () => {
217
+ while (next < paths.length) {
218
+ const path = paths[next++];
219
+ for (let attempt = 1; ; attempt++) {
220
+ try {
221
+ onUploaded(path, await uploadLocalFile(path, expiresInSeconds));
222
+ uploaded += 1;
223
+ break;
224
+ } catch (error) {
225
+ if (attempt < attempts && transient(error)) {
226
+ await wait(1e3 * 2 ** (attempt - 1));
227
+ continue;
228
+ }
229
+ onFailed(path, error);
230
+ failed += 1;
231
+ break;
232
+ }
233
+ }
234
+ }
235
+ };
236
+ await Promise.all(Array.from({ length: Math.min(concurrency, paths.length) }, worker));
237
+ return { uploaded, failed };
238
+ }
186
239
  function fileBlock(file) {
187
240
  const source = { type: "file", file_id: file.id };
188
241
  return file.mime_type.startsWith("image/") ? { type: "image", source } : { type: "document", source, title: file.filename };
@@ -564,12 +617,12 @@ function renderCatalogAgent(agent2) {
564
617
  import { spawn } from "child_process";
565
618
  import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
566
619
  import { homedir } from "os";
567
- import { basename as basename3, join as join2 } from "path";
620
+ import { basename as basename3, join as join3 } from "path";
568
621
  import { createInterface } from "readline";
569
622
  var PROMPT_CAP = 1e5;
570
623
  var HARNESSES = ["claude_code", "codex", "openclaw"];
571
624
  function claudeConfigRoot() {
572
- return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
625
+ return process.env.CLAUDE_CONFIG_DIR ?? join3(homedir(), ".claude");
573
626
  }
574
627
  function readIfExists(path) {
575
628
  try {
@@ -589,9 +642,9 @@ function readJson(path) {
589
642
  }
590
643
  function claudeModel(configRoot) {
591
644
  const candidates = [
592
- join2(process.cwd(), ".claude", "settings.local.json"),
593
- join2(process.cwd(), ".claude", "settings.json"),
594
- join2(configRoot, "settings.json")
645
+ join3(process.cwd(), ".claude", "settings.local.json"),
646
+ join3(process.cwd(), ".claude", "settings.json"),
647
+ join3(configRoot, "settings.json")
595
648
  ];
596
649
  for (const path of candidates) {
597
650
  const model = readJson(path)?.model;
@@ -601,11 +654,11 @@ function claudeModel(configRoot) {
601
654
  }
602
655
  function claudeMcpServers() {
603
656
  const merged = {};
604
- const userConfig = readJson(join2(homedir(), ".claude.json"));
657
+ const userConfig = readJson(join3(homedir(), ".claude.json"));
605
658
  Object.assign(merged, userConfig?.mcpServers ?? {});
606
659
  Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
607
660
  const projectConfig = readJson(
608
- join2(process.cwd(), ".mcp.json")
661
+ join3(process.cwd(), ".mcp.json")
609
662
  );
610
663
  Object.assign(merged, projectConfig?.mcpServers ?? {});
611
664
  return merged;
@@ -622,13 +675,13 @@ function readClaudeCode() {
622
675
  const configRoot = claudeConfigRoot();
623
676
  const parts = [];
624
677
  const sources = [];
625
- const global = readIfExists(join2(configRoot, "CLAUDE.md"));
678
+ const global = readIfExists(join3(configRoot, "CLAUDE.md"));
626
679
  if (global?.trim()) {
627
680
  parts.push({ label: "Global CLAUDE.md", content: global });
628
681
  sources.push("global CLAUDE.md");
629
682
  }
630
683
  for (const name of ["CLAUDE.md", "AGENTS.md"]) {
631
- const project = readIfExists(join2(process.cwd(), name));
684
+ const project = readIfExists(join3(process.cwd(), name));
632
685
  if (project?.trim()) {
633
686
  parts.push({ label: `Project ${name}`, content: project });
634
687
  sources.push(`project ${name}`);
@@ -669,8 +722,8 @@ function detectHarness(explicit) {
669
722
  return explicit;
670
723
  }
671
724
  if (existsSync2(claudeConfigRoot())) return "claude_code";
672
- if (existsSync2(join2(homedir(), ".codex"))) return "codex";
673
- if (existsSync2(join2(homedir(), ".openclaw"))) return "openclaw";
725
+ if (existsSync2(join3(homedir(), ".codex"))) return "codex";
726
+ if (existsSync2(join3(homedir(), ".openclaw"))) return "openclaw";
674
727
  throw new Error(
675
728
  "no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
676
729
  );
@@ -682,7 +735,7 @@ function readLocal(harness2) {
682
735
  );
683
736
  }
684
737
  function linkPath(configRoot) {
685
- return join2(configRoot, "agentsky-link.json");
738
+ return join3(configRoot, "agentsky-link.json");
686
739
  }
687
740
  function readLink(configRoot) {
688
741
  const link = readJson(linkPath(configRoot));
@@ -928,22 +981,22 @@ async function clone(opts) {
928
981
  }
929
982
 
930
983
  // src/import-local.ts
931
- import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync7, readSync as readSync2, statSync as statSync5 } from "fs";
984
+ import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
932
985
  import { homedir as homedir4 } from "os";
933
- import { join as join5 } from "path";
934
- import { createHash as createHash2 } from "crypto";
986
+ import { join as join7 } from "path";
987
+ import { createHash as createHash3 } from "crypto";
935
988
 
936
989
  // src/import-files.ts
937
990
  import { isUtf8 } from "buffer";
938
991
  import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
939
992
  import { homedir as homedir3 } from "os";
940
- import { dirname as dirname4, extname as extname2, isAbsolute, join as join4, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
993
+ import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
941
994
 
942
995
  // src/import-workspace.ts
943
996
  import { spawnSync } from "child_process";
944
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
997
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
945
998
  import { homedir as homedir2 } from "os";
946
- import { basename as basename4, dirname as dirname3, join as join3, relative, sep } from "path";
999
+ import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
947
1000
  import { parse as parseToml2 } from "smol-toml";
948
1001
  var MAX_TREE = 5e3;
949
1002
  var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
@@ -966,13 +1019,13 @@ var isDir = (path) => {
966
1019
  }
967
1020
  };
968
1021
  function sharedInstructionPaths(home = homedir2()) {
969
- const rules = join3(home, ".agents", "rules");
1022
+ const rules = join4(home, ".agents", "rules");
970
1023
  let ruleFiles = [];
971
1024
  try {
972
- ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join3(rules, name));
1025
+ ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
973
1026
  } catch {
974
1027
  }
975
- return [join3(home, "AGENTS.md"), join3(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
1028
+ return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
976
1029
  }
977
1030
  function strings(value, out, depth = 0) {
978
1031
  if (depth > 12) return;
@@ -981,30 +1034,30 @@ function strings(value, out, depth = 0) {
981
1034
  else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
982
1035
  }
983
1036
  function skillRoots(cwd, home) {
984
- const claude = process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude");
985
- const codex = process.env.CODEX_HOME ?? join3(home, ".codex");
1037
+ const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
1038
+ const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
986
1039
  return [
987
- ...cwd ? [join3(cwd, ".claude", "skills"), join3(cwd, ".agents", "skills")] : [],
988
- join3(home, ".agents", "skills"),
989
- join3(claude, "skills"),
990
- join3(codex, "skills"),
991
- join3(home, ".hermes", "skills")
1040
+ ...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
1041
+ join4(home, ".agents", "skills"),
1042
+ join4(claude, "skills"),
1043
+ join4(codex, "skills"),
1044
+ join4(home, ".hermes", "skills")
992
1045
  ];
993
1046
  }
994
1047
  function pluginSkill(plugin, skill, home) {
995
- const base = join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "plugins");
1048
+ const base = join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins");
996
1049
  const walk = (dir, depth) => {
997
1050
  if (depth > 7) return null;
998
1051
  let entries;
999
1052
  try {
1000
- entries = readdirSync(dir, { withFileTypes: true });
1053
+ entries = readdirSync2(dir, { withFileTypes: true });
1001
1054
  } catch {
1002
1055
  return null;
1003
1056
  }
1004
1057
  for (const entry2 of entries) {
1005
1058
  if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
1006
- const path = join3(dir, entry2.name);
1007
- if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join3(path, "SKILL.md"))) return path;
1059
+ const path = join4(dir, entry2.name);
1060
+ if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join4(path, "SKILL.md"))) return path;
1008
1061
  const found = walk(path, depth + 1);
1009
1062
  if (found) return found;
1010
1063
  }
@@ -1016,8 +1069,8 @@ function findSkill(name, cwd, home) {
1016
1069
  const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
1017
1070
  if (scope && skill) return pluginSkill(scope, skill, home);
1018
1071
  for (const root of skillRoots(cwd, home)) {
1019
- const direct = join3(root, name);
1020
- if (isFile(join3(direct, "SKILL.md"))) return realpathSync(direct);
1072
+ const direct = join4(root, name);
1073
+ if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
1021
1074
  }
1022
1075
  return null;
1023
1076
  }
@@ -1025,7 +1078,7 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
1025
1078
  const dirs = /* @__PURE__ */ new Map();
1026
1079
  const missing = /* @__PURE__ */ new Set();
1027
1080
  const add = (dir, name = basename4(dir)) => {
1028
- if (!isFile(join3(dir, "SKILL.md"))) return false;
1081
+ if (!isFile(join4(dir, "SKILL.md"))) return false;
1029
1082
  const real = realpathSync(dir);
1030
1083
  if (![...dirs.values()].includes(real)) dirs.set(name, real);
1031
1084
  return true;
@@ -1096,16 +1149,16 @@ function mcpServers(harness2, cwd, home = homedir2()) {
1096
1149
  }
1097
1150
  };
1098
1151
  if (harness2 === "claude_code") {
1099
- const config = readJson2(join3(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
1152
+ const config = readJson2(join4(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
1100
1153
  const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
1101
1154
  const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
1102
1155
  if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
1103
- if (root) push(readJson2(join3(root, ".mcp.json"))?.mcpServers, "project");
1156
+ if (root) push(readJson2(join4(root, ".mcp.json"))?.mcpServers, "project");
1104
1157
  push(config?.mcpServers, "user");
1105
1158
  }
1106
1159
  if (harness2 === "codex") {
1107
1160
  try {
1108
- const config = parseToml2(readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "config.toml"), "utf8"));
1161
+ const config = parseToml2(readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
1109
1162
  push(config.mcp_servers, "user");
1110
1163
  } catch {
1111
1164
  }
@@ -1159,13 +1212,13 @@ function skillFiles(skill) {
1159
1212
  if (depth > 8 || out.length > 400) return;
1160
1213
  let entries;
1161
1214
  try {
1162
- entries = readdirSync(dir, { withFileTypes: true });
1215
+ entries = readdirSync2(dir, { withFileTypes: true });
1163
1216
  } catch {
1164
1217
  return;
1165
1218
  }
1166
1219
  for (const entry2 of entries) {
1167
1220
  if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
1168
- const path = join3(dir, entry2.name);
1221
+ const path = join4(dir, entry2.name);
1169
1222
  if (entry2.isDirectory()) walk(path, depth + 1);
1170
1223
  else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
1171
1224
  }
@@ -1176,7 +1229,7 @@ function skillFiles(skill) {
1176
1229
  function codexThreadName(sessionId, home = homedir2()) {
1177
1230
  let name = null;
1178
1231
  try {
1179
- for (const line of readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
1232
+ for (const line of readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
1180
1233
  if (!line.includes(sessionId)) continue;
1181
1234
  try {
1182
1235
  const entry2 = JSON.parse(line);
@@ -1296,7 +1349,7 @@ function touchedPaths(records, cwd, home = homedir3()) {
1296
1349
  }
1297
1350
  function gitRoot(dir) {
1298
1351
  for (let d = dir; ; d = dirname4(d)) {
1299
- if (existsSync4(join4(d, ".git"))) return d;
1352
+ if (existsSync4(join5(d, ".git"))) return d;
1300
1353
  if (d === dirname4(d)) return null;
1301
1354
  }
1302
1355
  }
@@ -1305,12 +1358,12 @@ function instructionPaths(cwd, harness2, home = homedir3()) {
1305
1358
  if (cwd && existsSync4(cwd)) {
1306
1359
  const root = gitRoot(cwd) ?? cwd;
1307
1360
  for (let d = resolve4(cwd); ; d = dirname4(d)) {
1308
- for (const name of INSTRUCTION_FILES) out.push(join4(d, name));
1361
+ for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
1309
1362
  if (d === root || d === dirname4(d)) break;
1310
1363
  }
1311
1364
  }
1312
- if (harness2 === "claude_code") out.push(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "CLAUDE.md"));
1313
- if (harness2 === "codex") out.push(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "AGENTS.md"));
1365
+ if (harness2 === "claude_code") out.push(join5(process.env.CLAUDE_CONFIG_DIR ?? join5(home, ".claude"), "CLAUDE.md"));
1366
+ if (harness2 === "codex") out.push(join5(process.env.CODEX_HOME ?? join5(home, ".codex"), "AGENTS.md"));
1314
1367
  out.push(...sharedInstructionPaths(home));
1315
1368
  return out.filter((path) => {
1316
1369
  try {
@@ -1432,6 +1485,325 @@ function collectSessionFiles(records, opts) {
1432
1485
  return { lines, bytes, attached, leftOut, setup };
1433
1486
  }
1434
1487
 
1488
+ // src/import-environment.ts
1489
+ import { spawnSync as spawnSync2 } from "child_process";
1490
+ import { createHash as createHash2 } from "crypto";
1491
+ import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync as readSync2, rmSync as rmSync2 } from "fs";
1492
+ import { tmpdir } from "os";
1493
+ import { join as join6 } from "path";
1494
+ import { isUtf8 as isUtf82 } from "buffer";
1495
+ import { createGzip } from "zlib";
1496
+
1497
+ // ../../packages/spec/src/import-layout.ts
1498
+ import { gzipSync } from "zlib";
1499
+ function folderName(workspace2, sourceCwd) {
1500
+ const raw = workspace2?.project || (sourceCwd ? sourceCwd.split("/").filter(Boolean).pop() : "") || "";
1501
+ const cleaned = raw.replace(/[^A-Za-z0-9._ -]/g, "-").replace(/^[^A-Za-z0-9_]+/, "").replace(/\.\.+/g, ".").slice(0, 80).trim();
1502
+ return cleaned || "imported-session";
1503
+ }
1504
+ function envName(server, field) {
1505
+ return `${server}_${field}`.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1506
+ }
1507
+ function ownServer(server, platform) {
1508
+ try {
1509
+ return server.transport !== "stdio" && platform.ownHost.test(new URL(server.url ?? "").hostname);
1510
+ } catch {
1511
+ return false;
1512
+ }
1513
+ }
1514
+ function mcpJson(servers, platform) {
1515
+ const entries = servers.filter((server) => !ownServer(server, platform)).map((server) => {
1516
+ if (server.transport === "stdio") {
1517
+ return [server.name, {
1518
+ command: server.command,
1519
+ ...server.args?.length ? { args: server.args } : {},
1520
+ ...server.env?.length ? { env: Object.fromEntries(server.env.map((name) => [name, `\${${name}:-}`])) } : {}
1521
+ }];
1522
+ }
1523
+ const query = server.query?.length ? `?${server.query.map((name) => `${encodeURIComponent(name)}=\${${envName(server.name, name)}:-}`).join("&")}` : "";
1524
+ return [server.name, {
1525
+ type: server.transport,
1526
+ url: `${server.url}${query}`,
1527
+ ...server.headers?.length ? { headers: Object.fromEntries(server.headers.map((name) => [name, `\${${envName(server.name, name)}:-}`])) } : {}
1528
+ }];
1529
+ });
1530
+ return JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2) + "\n";
1531
+ }
1532
+ function bullet(items, none) {
1533
+ return items.length ? items.map((item) => `- ${item}`).join("\n") : `- ${none}`;
1534
+ }
1535
+ function readme(folder, workspace2, counts, skills2, mcpWritten, platform) {
1536
+ const git2 = workspace2?.git;
1537
+ const servers = workspace2?.mcp_servers ?? [];
1538
+ const missingSkills = (workspace2?.skills ?? []).filter((skill) => !skill.found).map((skill) => skill.name);
1539
+ const secrets2 = servers.filter((server) => !ownServer(server, platform)).flatMap((server) => [
1540
+ ...server.env ?? [],
1541
+ ...(server.headers ?? []).map((name) => envName(server.name, name)),
1542
+ ...(server.query ?? []).map((name) => envName(server.name, name))
1543
+ ]);
1544
+ const where = workspace2?.cwd ? ` (${workspace2.cwd})` : "";
1545
+ return [
1546
+ "# Imported working copy",
1547
+ "",
1548
+ `This folder (/workspace/${folder}) holds what the imported conversation worked with on the person's computer.`,
1549
+ "It is historical context: verify a path before acting on it, and follow the person's current message.",
1550
+ "",
1551
+ "## Files",
1552
+ counts.wholeFolder ? `- The whole working directory${where}, ${counts.project} files at the same relative paths: its tracked files and new files git does not ignore (every file when it is not a repository), without version-control data, dependency and build output, or credential files.` : `- ${counts.project} files from the original working directory${where}, at the same relative paths: the files the conversation read, wrote or edited and its instruction files. The rest of the working tree did not travel${workspace2?.tree?.length ? `; its full file list is in ${platform.notesDir}/workspace.json (\`tree\`)` : ""}.`,
1553
+ ...counts.outside ? [`- ${counts.outside} files from elsewhere on that computer (shared instructions such as ~/AGENTS.md, and files outside the working directory) under ${platform.notesDir}/files/, with ~ as home/.`] : [],
1554
+ ...counts.attachments ? [`- ${counts.attachments} attachments of the conversation under ${platform.notesDir}/attachments/.`] : [],
1555
+ "- Text was redacted on that computer before upload: a masked value (\u25CF\u25CF\u25CF\u25CF\u25CF) is a secret that was removed, not the real content.",
1556
+ "",
1557
+ "## Repository",
1558
+ ...git2 ? [
1559
+ `- Remote: ${git2.remote ?? "none"}`,
1560
+ `- Branch: ${git2.branch ?? "detached"} at commit ${git2.commit ?? "unknown"}${git2.ahead ? ` (${git2.ahead} commits not pushed)` : ""}`,
1561
+ ...git2.dirty?.length ? [`- Uncommitted changes in ${git2.dirty.length} paths${counts.wholeFolder ? " are already in these files" : ""}; the change against that commit is ${platform.notesDir}/uncommitted.patch (apply with \`git apply\` on a checkout of that commit).`] : ["- No uncommitted changes."],
1562
+ counts.wholeFolder ? "- This folder's own git history starts with this copy; the project's history is on the remote." : `- To get the whole repository, clone the remote at that commit (connect GitHub in ${platform.name} if it is private), apply the patch, then use the files here.`
1563
+ ] : ["- The working directory was not a git repository."],
1564
+ "",
1565
+ "## Skills",
1566
+ bullet(skills2.map((name) => `${name}: .claude/skills/${name}/SKILL.md (also .agents/skills/${name}/)`), "No skills were loaded."),
1567
+ ...missingSkills.length ? ["", `Loaded but no longer on that computer, so not carried: ${missingSkills.join(", ")}.`] : [],
1568
+ "",
1569
+ "## Tool servers (MCP)",
1570
+ bullet(servers.map((server) => `${server.name} (${server.transport}${server.scope ? `, ${server.scope}` : ""}): ${server.transport === "stdio" ? [server.command, ...server.args ?? []].join(" ") : server.url}`), "None were configured."),
1571
+ ...mcpWritten ? ["", `They are written to .mcp.json in this folder with every key as \${NAME:-}, except ${platform.name}'s own server, whose tools this session already has. Their values never leave the person's computer${secrets2.length ? `; these must be supplied to use them: ${[...new Set(secrets2)].join(", ")}` : ""}. A stdio server runs only if its command is installed here, and Claude Code connects a project server only once it is approved.`] : [],
1572
+ ""
1573
+ ].join("\n");
1574
+ }
1575
+ function parseWorkspace(file) {
1576
+ if (!file) return null;
1577
+ try {
1578
+ const value = JSON.parse(file.data.toString("utf8"));
1579
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1580
+ } catch {
1581
+ return null;
1582
+ }
1583
+ }
1584
+ function importLayout(files2, sourceCwd, platform, wholeFolder) {
1585
+ if (!files2.length && !wholeFolder) return null;
1586
+ const workspace2 = parseWorkspace(files2.find((file) => file.name === "agentsky/workspace.json"));
1587
+ const folder = folderName(workspace2, sourceCwd);
1588
+ const entries = /* @__PURE__ */ new Map();
1589
+ const skills2 = /* @__PURE__ */ new Set();
1590
+ const counts = { project: wholeFolder?.files ?? 0, outside: 0, attachments: 0, wholeFolder: Boolean(wholeFolder) };
1591
+ for (const file of files2) {
1592
+ const [kind, ...rest] = file.name.split("/");
1593
+ const path = rest.join("/");
1594
+ if (kind === "workspace") {
1595
+ if (wholeFolder) continue;
1596
+ entries.set(path, file.data);
1597
+ counts.project += 1;
1598
+ } else if (kind === "files") {
1599
+ entries.set(`${platform.notesDir}/files/${path.replace(/^~\//, "home/")}`, file.data);
1600
+ counts.outside += 1;
1601
+ } else if (kind === "imported-attachments") {
1602
+ entries.set(`${platform.notesDir}/attachments/${path}`, file.data);
1603
+ counts.attachments += 1;
1604
+ } else if (kind === "agentsky") entries.set(`${platform.notesDir}/${path}`, file.data);
1605
+ else if (kind === "skills" && rest.length > 1) {
1606
+ const skill = rest[0].split(":").pop();
1607
+ const inside = rest.slice(1).join("/");
1608
+ skills2.add(skill);
1609
+ entries.set(`.claude/skills/${skill}/${inside}`, file.data);
1610
+ entries.set(`.agents/skills/${skill}/${inside}`, file.data);
1611
+ }
1612
+ }
1613
+ const servers = (workspace2?.mcp_servers ?? []).filter((server) => server && typeof server.name === "string" && (server.transport === "stdio" ? typeof server.command === "string" : typeof server.url === "string"));
1614
+ const mcpWritten = servers.length > 0 && !entries.has(".mcp.json") && !wholeFolder?.paths.has(".mcp.json");
1615
+ if (mcpWritten) entries.set(".mcp.json", Buffer.from(mcpJson(servers, platform)));
1616
+ entries.set(`${platform.notesDir}/README.md`, Buffer.from(readme(folder, workspace2 ? { ...workspace2, mcp_servers: servers } : null, counts, [...skills2], mcpWritten, platform)));
1617
+ if (wholeFolder) for (const path of wholeFolder.paths) entries.delete(path);
1618
+ return { folder, entries, skills: [...skills2], mcpServers: servers.map((server) => server.name) };
1619
+ }
1620
+ var TAR_BLOCK = 512;
1621
+ var TAR_END = Buffer.alloc(TAR_BLOCK * 2);
1622
+ function header(name, size, type) {
1623
+ const block = Buffer.alloc(TAR_BLOCK);
1624
+ block.write(name, 0, 100, "utf8");
1625
+ block.write("0000644\0", 100);
1626
+ block.write("0000000\0", 108);
1627
+ block.write("0000000\0", 116);
1628
+ block.write(size.toString(8).padStart(11, "0") + "\0", 124);
1629
+ block.write("00000000000\0", 136);
1630
+ block.write(" ", 148);
1631
+ block.write(type, 156);
1632
+ block.write("ustar\x0000", 257);
1633
+ let sum = 0;
1634
+ for (const byte of block) sum += byte;
1635
+ block.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
1636
+ return block;
1637
+ }
1638
+ function paxRecord(key, value) {
1639
+ const body = ` ${key}=${value}
1640
+ `;
1641
+ let length = Buffer.byteLength(body);
1642
+ for (; ; ) {
1643
+ const total = String(length).length + length;
1644
+ if (String(total).length + length === total) return Buffer.from(`${total}${body}`);
1645
+ length += 1;
1646
+ }
1647
+ }
1648
+ function tarPadding(size) {
1649
+ const rest = size % TAR_BLOCK;
1650
+ return Buffer.alloc(rest ? TAR_BLOCK - rest : 0);
1651
+ }
1652
+ function tarHeader(path, size) {
1653
+ if (Buffer.byteLength(path) <= 100) return header(path, size, "0");
1654
+ const pax = paxRecord("path", path);
1655
+ return Buffer.concat([header("PaxHeader", pax.length, "x"), pax, tarPadding(pax.length), header(path, size, "0")]);
1656
+ }
1657
+
1658
+ // src/import-environment.ts
1659
+ var IMPORT_PLATFORM = { name: "AgentSky", ownHost: /(^|\.)(agentsky\.dev|asteroidbelt\.ai)$/i, notesDir: ".agentsky-import" };
1660
+ var MAX_ENVIRONMENT_FILES = 2e5;
1661
+ var MAX_ENVIRONMENT_BYTES = 2 * 1024 ** 3;
1662
+ var MAX_REDACTED_TEXT_BYTES = 10 * 1024 * 1024;
1663
+ var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", ".venv", "venv", "__pycache__", ".next", "dist", "build", "target", ".DS_Store", ".cache", ".turbo"]);
1664
+ var SECRET_FILE2 = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx))$|(^|\/)\.ssh\/|(^|\/)\.aws\/|(^|\/)\.gnupg\/|(^|\/)\.docker\/config\.json$/i;
1665
+ function listFiles(cwd) {
1666
+ const git2 = spawnSync2("git", ["-C", cwd, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8", maxBuffer: 512 * 1024 * 1024 });
1667
+ if (git2.status === 0) return git2.stdout.split("\0").filter(Boolean).filter((path) => !path.split("/").some((part) => part === ".git"));
1668
+ const out = [];
1669
+ const walk = (dir, prefix) => {
1670
+ let entries;
1671
+ try {
1672
+ entries = readdirSync3(dir, { withFileTypes: true });
1673
+ } catch {
1674
+ return;
1675
+ }
1676
+ for (const entry2 of entries) {
1677
+ if (SKIPPED.has(entry2.name)) continue;
1678
+ const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
1679
+ if (entry2.isDirectory()) walk(join6(dir, entry2.name), rel);
1680
+ else out.push(rel);
1681
+ if (out.length > MAX_ENVIRONMENT_FILES) return;
1682
+ }
1683
+ };
1684
+ walk(cwd, "");
1685
+ return out;
1686
+ }
1687
+ function textOf(path, size) {
1688
+ if (size > MAX_REDACTED_TEXT_BYTES) return null;
1689
+ const data = readFileSync7(path);
1690
+ if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
1691
+ return data;
1692
+ }
1693
+ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
1694
+ const listed = listFiles(cwd);
1695
+ if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
1696
+ const scratch = mkdtempSync(join6(tmpdir(), "sky-environment-"));
1697
+ const path = join6(scratch, "environment.tar.gz");
1698
+ const leftOut = [];
1699
+ const written = /* @__PURE__ */ new Set();
1700
+ const hash = createHash2("sha256");
1701
+ let bytes = 0;
1702
+ let redacted2 = 0;
1703
+ const gzip = createGzip({ level: 6 });
1704
+ const sink = createWriteStream(path);
1705
+ gzip.on("data", (chunk) => {
1706
+ bytes += chunk.length;
1707
+ hash.update(chunk);
1708
+ });
1709
+ gzip.pipe(sink);
1710
+ const done = new Promise((resolve10, reject) => {
1711
+ sink.on("finish", () => resolve10());
1712
+ sink.on("error", reject);
1713
+ gzip.on("error", reject);
1714
+ });
1715
+ const write = (chunk) => new Promise((resolve10, reject) => {
1716
+ if (bytes > MAX_ENVIRONMENT_BYTES) {
1717
+ reject(new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`));
1718
+ return;
1719
+ }
1720
+ if (gzip.write(chunk)) resolve10();
1721
+ else gzip.once("drain", resolve10);
1722
+ });
1723
+ try {
1724
+ for (const rel of listed) {
1725
+ const full = join6(cwd, ...rel.split("/"));
1726
+ if (SECRET_FILE2.test(rel)) {
1727
+ leftOut.push({ path: rel, reason: "credential file" });
1728
+ continue;
1729
+ }
1730
+ let stat;
1731
+ try {
1732
+ stat = lstatSync(full);
1733
+ } catch {
1734
+ continue;
1735
+ }
1736
+ if (!stat.isFile()) {
1737
+ leftOut.push({ path: rel, reason: "not a regular file" });
1738
+ continue;
1739
+ }
1740
+ const text = textOf(full, stat.size);
1741
+ if (text) {
1742
+ const masked = redactSecretValues(text.toString("utf8"));
1743
+ if (Object.keys(masked.hits).length) redacted2 += 1;
1744
+ const data = Buffer.from(masked.text, "utf8");
1745
+ await write(tarHeader(rel, data.length));
1746
+ await write(data);
1747
+ await write(tarPadding(data.length));
1748
+ } else {
1749
+ await write(tarHeader(rel, stat.size));
1750
+ const fd = openSync2(full, "r");
1751
+ try {
1752
+ const chunk = Buffer.alloc(1024 * 1024);
1753
+ let left = stat.size;
1754
+ for (let offset = 0; left > 0; ) {
1755
+ const read = readSync2(fd, chunk, 0, Math.min(chunk.length, left), offset);
1756
+ if (read <= 0) throw new Error(`${rel} changed while it was packed; import again.`);
1757
+ await write(Buffer.from(chunk.subarray(0, read)));
1758
+ offset += read;
1759
+ left -= read;
1760
+ }
1761
+ } finally {
1762
+ closeSync2(fd);
1763
+ }
1764
+ await write(tarPadding(stat.size));
1765
+ }
1766
+ written.add(rel);
1767
+ }
1768
+ const layout = importLayout(resources, cwd, platform, { files: written.size, paths: written });
1769
+ for (const [entry2, data] of layout?.entries ?? []) {
1770
+ await write(tarHeader(entry2, data.length));
1771
+ await write(data);
1772
+ await write(tarPadding(data.length));
1773
+ }
1774
+ await write(TAR_END);
1775
+ gzip.end();
1776
+ await done;
1777
+ if (bytes > MAX_ENVIRONMENT_BYTES) throw new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`);
1778
+ return {
1779
+ path,
1780
+ folder: layout?.folder ?? "imported-session",
1781
+ bytes,
1782
+ sha256: hash.digest("hex"),
1783
+ files: written.size + (layout?.entries.size ?? 0),
1784
+ redacted: redacted2,
1785
+ leftOut,
1786
+ dispose: () => rmSync2(scratch, { recursive: true, force: true })
1787
+ };
1788
+ } catch (error) {
1789
+ gzip.destroy();
1790
+ sink.destroy();
1791
+ rmSync2(scratch, { recursive: true, force: true });
1792
+ throw error;
1793
+ }
1794
+ }
1795
+ function resourcesOf(lines) {
1796
+ return lines.flatMap((line) => {
1797
+ try {
1798
+ const record = JSON.parse(line);
1799
+ if (record.type !== "import.resource" || typeof record.name !== "string" || typeof record.media_type !== "string" || typeof record.data !== "string") return [];
1800
+ return [{ name: record.name, mediaType: record.media_type, data: Buffer.from(record.data, "base64") }];
1801
+ } catch {
1802
+ return [];
1803
+ }
1804
+ });
1805
+ }
1806
+
1435
1807
  // src/import-local.ts
1436
1808
  function redactLine2(raw) {
1437
1809
  return redactLine(raw).line;
@@ -1444,12 +1816,12 @@ function decodeWorkspace(encoded) {
1444
1816
  }
1445
1817
  }
1446
1818
  function readPrefix(filePath) {
1447
- const fd = openSync2(filePath, "r");
1819
+ const fd = openSync3(filePath, "r");
1448
1820
  try {
1449
1821
  const bytes = Buffer.alloc(256 * 1024);
1450
- return bytes.subarray(0, readSync2(fd, bytes, 0, bytes.length, 0)).toString("utf8");
1822
+ return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
1451
1823
  } finally {
1452
- closeSync2(fd);
1824
+ closeSync3(fd);
1453
1825
  }
1454
1826
  }
1455
1827
  function sniffTitle(filePath) {
@@ -1504,16 +1876,16 @@ function sniffTitle(filePath) {
1504
1876
  }
1505
1877
  }
1506
1878
  function listClaudeCodeSessions(limit) {
1507
- const root = process.env.CLAUDE_CONFIG_DIR ?? join5(homedir4(), ".claude");
1508
- const projectsDir = join5(root, "projects");
1879
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
1880
+ const projectsDir = join7(root, "projects");
1509
1881
  if (!existsSync5(projectsDir)) return [];
1510
1882
  const sessions = [];
1511
1883
  try {
1512
- for (const encoded of readdirSync2(projectsDir)) {
1513
- const projectDir = join5(projectsDir, encoded);
1884
+ for (const encoded of readdirSync4(projectsDir)) {
1885
+ const projectDir = join7(projectsDir, encoded);
1514
1886
  let entries;
1515
1887
  try {
1516
- entries = readdirSync2(projectDir);
1888
+ entries = readdirSync4(projectDir);
1517
1889
  } catch {
1518
1890
  continue;
1519
1891
  }
@@ -1521,7 +1893,7 @@ function listClaudeCodeSessions(limit) {
1521
1893
  for (const entry2 of entries) {
1522
1894
  if (!entry2.endsWith(".jsonl")) continue;
1523
1895
  const sessionId = entry2.slice(0, -6);
1524
- const mainFile = join5(projectDir, entry2);
1896
+ const mainFile = join7(projectDir, entry2);
1525
1897
  let stat;
1526
1898
  try {
1527
1899
  stat = statSync5(mainFile);
@@ -1531,12 +1903,12 @@ function listClaudeCodeSessions(limit) {
1531
1903
  if (!stat.isFile()) continue;
1532
1904
  let discoveryError;
1533
1905
  const subagentFiles = [];
1534
- const subagentDir = join5(projectDir, sessionId, "subagents");
1906
+ const subagentDir = join7(projectDir, sessionId, "subagents");
1535
1907
  if (existsSync5(subagentDir)) {
1536
1908
  try {
1537
- const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1909
+ const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1538
1910
  for (const f of subFiles) {
1539
- subagentFiles.push(join5(subagentDir, f));
1911
+ subagentFiles.push(join7(subagentDir, f));
1540
1912
  }
1541
1913
  } catch {
1542
1914
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -1558,17 +1930,17 @@ function listClaudeCodeSessions(limit) {
1558
1930
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
1559
1931
  }
1560
1932
  function listCodexSessions(limit) {
1561
- const sessionsDir = join5(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join5(homedir4(), ".codex"), "sessions");
1933
+ const sessionsDir = join7(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join7(homedir4(), ".codex"), "sessions");
1562
1934
  const records = [];
1563
1935
  function walk(dir) {
1564
1936
  let entries;
1565
1937
  try {
1566
- entries = readdirSync2(dir, { withFileTypes: true });
1938
+ entries = readdirSync4(dir, { withFileTypes: true });
1567
1939
  } catch {
1568
1940
  return;
1569
1941
  }
1570
1942
  for (const entry2 of entries) {
1571
- const full = join5(dir, entry2.name);
1943
+ const full = join7(dir, entry2.name);
1572
1944
  if (entry2.isDirectory()) {
1573
1945
  walk(full);
1574
1946
  continue;
@@ -1655,7 +2027,7 @@ async function mintTrace(base, harness2) {
1655
2027
  function loadSummaryFile(summaryPath) {
1656
2028
  let raw;
1657
2029
  try {
1658
- raw = readFileSync7(summaryPath, "utf8");
2030
+ raw = readFileSync8(summaryPath, "utf8");
1659
2031
  } catch {
1660
2032
  throw new Error(`Cannot read summary file: ${summaryPath}`);
1661
2033
  }
@@ -1687,7 +2059,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1687
2059
  let inputBytes = 0;
1688
2060
  let outputBytes = 0;
1689
2061
  const lines = [];
1690
- const digest = createHash2("sha256");
2062
+ const digest = createHash3("sha256");
1691
2063
  let nativeId;
1692
2064
  let redactionCount = 0;
1693
2065
  let encryptedReasoningCount = 0;
@@ -1698,7 +2070,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1698
2070
  for (const path of session2.files) {
1699
2071
  const stat = statSync5(path);
1700
2072
  if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
1701
- const text = readFileSync7(path, "utf8");
2073
+ const text = readFileSync8(path, "utf8");
1702
2074
  for (const raw of text.split("\n")) {
1703
2075
  if (!raw.trim()) continue;
1704
2076
  let record;
@@ -1776,7 +2148,8 @@ function prepareImport(session2, summaryPath, options = {}) {
1776
2148
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1777
2149
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1778
2150
  if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
1779
- return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
2151
+ const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
2152
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
1780
2153
  }
1781
2154
  async function importSession(opts) {
1782
2155
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1785,7 +2158,7 @@ async function importSession(opts) {
1785
2158
  throw new Error("Import requires HTTPS, or a loopback development server.");
1786
2159
  }
1787
2160
  opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
1788
- const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
2161
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup, cwd, resourceLines } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1789
2162
  let traceId;
1790
2163
  let key;
1791
2164
  if (opts.importKey && opts.grantTraceId) {
@@ -1809,6 +2182,7 @@ async function importSession(opts) {
1809
2182
  }
1810
2183
  const result = await response.json();
1811
2184
  const finalTraceId = result.traceId ?? traceId;
2185
+ const environment = opts.files === false || opts.environment === false || !cwd || !existsSync5(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
1812
2186
  return {
1813
2187
  traceId: finalTraceId,
1814
2188
  url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
@@ -1820,9 +2194,42 @@ async function importSession(opts) {
1820
2194
  gaps,
1821
2195
  attachedFiles,
1822
2196
  leftOutFiles,
1823
- setup
2197
+ setup,
2198
+ environment
1824
2199
  };
1825
2200
  }
2201
+ async function grantCall(base, key, path, body) {
2202
+ const response = await fetch(`${base}${path}`, {
2203
+ method: "POST",
2204
+ redirect: "error",
2205
+ signal: AbortSignal.timeout(3e5),
2206
+ headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
2207
+ body: JSON.stringify(body)
2208
+ });
2209
+ const answer = await response.json().catch(() => null);
2210
+ if (!response.ok) throw new Error(typeof answer?.error === "string" ? answer.error : answer?.error?.message ?? `The environment upload failed (${response.status}).`);
2211
+ return answer;
2212
+ }
2213
+ async function uploadEnvironment(base, key, traceId, cwd, resourceLines, onProgress) {
2214
+ onProgress?.("Packing the working folder (secrets redacted locally)\u2026");
2215
+ const packed = await packEnvironment(cwd, resourcesOf(resourceLines));
2216
+ try {
2217
+ const path = `/api/traces/${encodeURIComponent(traceId)}/environment`;
2218
+ const { upload } = await grantCall(base, key, path, { name: packed.folder, size: packed.bytes, sha256: packed.sha256, files: packed.files });
2219
+ let shown = -1;
2220
+ await transfer(upload, packed.path, (sent, total) => {
2221
+ const percent = Math.floor(sent / total * 100);
2222
+ if (percent >= shown + 10 || sent === total) {
2223
+ shown = percent;
2224
+ onProgress?.(`Uploading the working folder\u2026 ${percent}% of ${(total / 1024 / 1024).toFixed(1)} MiB`);
2225
+ }
2226
+ });
2227
+ await grantCall(base, key, `${path}/complete`, {});
2228
+ return { folder: packed.folder, files: packed.files, bytes: packed.bytes, redacted: packed.redacted, leftOut: packed.leftOut.length };
2229
+ } finally {
2230
+ packed.dispose();
2231
+ }
2232
+ }
1826
2233
 
1827
2234
  // src/channels.ts
1828
2235
  function connectionLine(c) {
@@ -1876,7 +2283,7 @@ async function send(threadId, text) {
1876
2283
  import { createServer as createServer2 } from "http";
1877
2284
 
1878
2285
  // src/login.ts
1879
- import { createHash as createHash3, randomBytes } from "crypto";
2286
+ import { createHash as createHash4, randomBytes } from "crypto";
1880
2287
  import { createServer } from "http";
1881
2288
  import { hostname } from "os";
1882
2289
  import { spawn as spawn2 } from "child_process";
@@ -1892,7 +2299,7 @@ function openBrowser2(url) {
1892
2299
  }
1893
2300
  async function login(opts) {
1894
2301
  const verifier = b64url(randomBytes(32));
1895
- const challenge = b64url(createHash3("sha256").update(verifier).digest());
2302
+ const challenge = b64url(createHash4("sha256").update(verifier).digest());
1896
2303
  const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
1897
2304
  saveConfig({
1898
2305
  ...loadConfig(),
@@ -2083,15 +2490,15 @@ async function triggerRequest(id, resource, change) {
2083
2490
  }
2084
2491
 
2085
2492
  // src/session-workspace.ts
2086
- import { spawnSync as spawnSync2 } from "child_process";
2087
- import { createHash as createHash4, randomUUID } from "crypto";
2088
- import { readdirSync as readdirSync3, statSync as statSync6 } from "fs";
2089
- import { basename as basename6, join as join6, relative as relative3, resolve as resolve5 } from "path";
2493
+ import { spawnSync as spawnSync3 } from "child_process";
2494
+ import { createHash as createHash5, randomUUID } from "crypto";
2495
+ import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
2496
+ import { basename as basename6, join as join8, relative as relative3, resolve as resolve5 } from "path";
2090
2497
  var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
2091
2498
  var PART_SIZE = 8 * 1024 * 1024;
2092
- var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules"]);
2499
+ var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
2093
2500
  var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
2094
- async function listFiles(id, path, hidden) {
2501
+ async function listFiles2(id, path, hidden) {
2095
2502
  const query = new URLSearchParams();
2096
2503
  if (path) query.set("path", path);
2097
2504
  if (hidden) query.set("hidden", "true");
@@ -2117,16 +2524,16 @@ async function workspacePatch(id, repo) {
2117
2524
  return { meta, patch: Buffer.from(patch, "base64") };
2118
2525
  }
2119
2526
  function folderFiles(dir) {
2120
- const git2 = spawnSync2("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
2121
- const top = spawnSync2("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
2527
+ const git2 = spawnSync3("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
2528
+ const top = spawnSync3("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
2122
2529
  if (git2.status === 0 && top.status === 0) {
2123
- return git2.stdout.split("\0").filter((path) => path && statSync6(join6(dir, path), { throwIfNoEntry: false })?.isFile());
2530
+ return git2.stdout.split("\0").filter((path) => path && statSync6(join8(dir, path), { throwIfNoEntry: false })?.isFile());
2124
2531
  }
2125
2532
  const files2 = [];
2126
2533
  const walk = (at) => {
2127
- for (const entry2 of readdirSync3(at, { withFileTypes: true })) {
2128
- if (SKIPPED.has(entry2.name)) continue;
2129
- const full = join6(at, entry2.name);
2534
+ for (const entry2 of readdirSync5(at, { withFileTypes: true })) {
2535
+ if (SKIPPED2.has(entry2.name)) continue;
2536
+ const full = join8(at, entry2.name);
2130
2537
  if (entry2.isDirectory()) walk(full);
2131
2538
  else if (entry2.isFile()) files2.push(relative3(dir, full));
2132
2539
  }
@@ -2135,7 +2542,7 @@ function folderFiles(dir) {
2135
2542
  return files2;
2136
2543
  }
2137
2544
  function folderArchive(dir, files2) {
2138
- const tar = spawnSync2("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
2545
+ const tar = spawnSync3("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
2139
2546
  input: files2.join("\n"),
2140
2547
  env: { ...process.env, COPYFILE_DISABLE: "1" },
2141
2548
  maxBuffer: 2 * 1024 * 1024 * 1024
@@ -2151,7 +2558,7 @@ async function importFolder(id, dir, opts = {}) {
2151
2558
  const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
2152
2559
  return res.json();
2153
2560
  }
2154
- const sha256 = createHash4("sha256").update(archive).digest("hex");
2561
+ const sha256 = createHash5("sha256").update(archive).digest("hex");
2155
2562
  let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
2156
2563
  const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
2157
2564
  for (let part = 0; part < upload.parts; part += 1) {
@@ -2184,6 +2591,13 @@ function accessPatch(opts) {
2184
2591
  ...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
2185
2592
  };
2186
2593
  }
2594
+ function engineChange(model, thinking) {
2595
+ if (!model && !thinking) return null;
2596
+ return {
2597
+ ...model ? { llm: model } : {},
2598
+ ...thinking ? { reasoningEffort: thinking === "default" ? null : thinking } : {}
2599
+ };
2600
+ }
2187
2601
  var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
2188
2602
  async function handoff(id, targetAgent, opts = {}) {
2189
2603
  let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
@@ -2203,15 +2617,15 @@ async function handoff(id, targetAgent, opts = {}) {
2203
2617
  // src/local-agents.ts
2204
2618
  import { existsSync as existsSync6 } from "fs";
2205
2619
  import { homedir as homedir5 } from "os";
2206
- import { join as join7 } from "path";
2620
+ import { join as join9 } from "path";
2207
2621
  function localAgents(env = process.env, home = homedir5()) {
2208
- const claude = env.CLAUDE_CONFIG_DIR || join7(home, ".claude");
2209
- const codex = env.CODEX_HOME || join7(home, ".codex");
2210
- const cursor = join7(home, ".cursor");
2622
+ const claude = env.CLAUDE_CONFIG_DIR || join9(home, ".claude");
2623
+ const codex = env.CODEX_HOME || join9(home, ".codex");
2624
+ const cursor = join9(home, ".cursor");
2211
2625
  return [
2212
- { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join7(claude, "skills") },
2213
- { id: "codex", name: "Codex", root: codex, skillsDir: join7(codex, "skills") },
2214
- { id: "cursor", name: "Cursor", root: cursor, skillsDir: join7(cursor, "skills") }
2626
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join9(claude, "skills") },
2627
+ { id: "codex", name: "Codex", root: codex, skillsDir: join9(codex, "skills") },
2628
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join9(cursor, "skills") }
2215
2629
  ];
2216
2630
  }
2217
2631
  function detectLocalAgents(env = process.env, home = homedir5()) {
@@ -2316,16 +2730,16 @@ async function readTurn(sessionId, opts = {}) {
2316
2730
  }
2317
2731
 
2318
2732
  // src/setup.ts
2319
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
2320
- import { dirname as dirname6, join as join11 } from "path";
2733
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2734
+ import { dirname as dirname6, join as join13 } from "path";
2321
2735
  import { createInterface as createInterface3 } from "readline";
2322
2736
 
2323
2737
  // src/mcp-register.ts
2324
- import { spawnSync as spawnSync3 } from "child_process";
2738
+ import { spawnSync as spawnSync4 } from "child_process";
2325
2739
 
2326
2740
  // src/skill.ts
2327
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
2328
- import { join as join8 } from "path";
2741
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2742
+ import { join as join10 } from "path";
2329
2743
 
2330
2744
  // src/catalog-file.ts
2331
2745
  var CATALOG_FILE = "catalog.md";
@@ -2351,13 +2765,13 @@ function entry(agent2) {
2351
2765
  return lines.join("\n");
2352
2766
  }
2353
2767
  function renderCatalog(agents, generatedAt) {
2354
- const header = [
2768
+ const header2 = [
2355
2769
  "# AgentSky agent catalog",
2356
2770
  "",
2357
2771
  `Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
2358
2772
  "Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
2359
2773
  ];
2360
- if (!agents.length) return [...header, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
2774
+ if (!agents.length) return [...header2, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
2361
2775
  const own = agents.filter((agent2) => agent2.visibility === "workspace");
2362
2776
  const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
2363
2777
  const sections = [];
@@ -2372,7 +2786,7 @@ function renderCatalog(agents, generatedAt) {
2372
2786
  const title = category.charAt(0).toUpperCase() + category.slice(1);
2373
2787
  sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
2374
2788
  }
2375
- return [...header, "", sections.join("\n\n"), ""].join("\n");
2789
+ return [...header2, "", sections.join("\n\n"), ""].join("\n");
2376
2790
  }
2377
2791
 
2378
2792
  // src/skill.ts
@@ -2766,7 +3180,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
2766
3180
  }
2767
3181
  function installedVersion(dir) {
2768
3182
  try {
2769
- const marker = JSON.parse(readFileSync8(join8(dir, SKILL_MARKER), "utf8"));
3183
+ const marker = JSON.parse(readFileSync9(join10(dir, SKILL_MARKER), "utf8"));
2770
3184
  return typeof marker.version === "string" ? marker.version : null;
2771
3185
  } catch {
2772
3186
  return null;
@@ -2776,25 +3190,25 @@ function isInstalledSkill(dir) {
2776
3190
  return installedVersion(dir) !== null;
2777
3191
  }
2778
3192
  function installSkill(agent2, opts = {}) {
2779
- const dir = join8(agent2.skillsDir, SKILL_NAME);
3193
+ const dir = join10(agent2.skillsDir, SKILL_NAME);
2780
3194
  const previous = installedVersion(dir);
2781
3195
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
2782
3196
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
2783
3197
  if (outcome !== "current") {
2784
3198
  mkdirSync2(dir, { recursive: true });
2785
- writeFileSync4(join8(dir, "SKILL.md"), skillDocument());
2786
- writeFileSync4(join8(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
3199
+ writeFileSync4(join10(dir, "SKILL.md"), skillDocument());
3200
+ writeFileSync4(join10(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2787
3201
  `);
2788
3202
  }
2789
3203
  if (opts.catalog) {
2790
3204
  mkdirSync2(dir, { recursive: true });
2791
- writeFileSync4(join8(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
3205
+ writeFileSync4(join10(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2792
3206
  }
2793
- const refsDir = join8(dir, REFERENCES_DIR);
3207
+ const refsDir = join10(dir, REFERENCES_DIR);
2794
3208
  mkdirSync2(refsDir, { recursive: true });
2795
- writeFileSync4(join8(refsDir, "import.md"), importReferenceDoc());
2796
- writeFileSync4(join8(refsDir, "workspaces.md"), workspacesReferenceDoc());
2797
- writeFileSync4(join8(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
3209
+ writeFileSync4(join10(refsDir, "import.md"), importReferenceDoc());
3210
+ writeFileSync4(join10(refsDir, "workspaces.md"), workspacesReferenceDoc());
3211
+ writeFileSync4(join10(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
2798
3212
  return { agent: agent2.id, path: dir, outcome };
2799
3213
  }
2800
3214
 
@@ -2803,9 +3217,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
2803
3217
  var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
2804
3218
  function systemCommandRunner() {
2805
3219
  return {
2806
- has: (command) => spawnSync3(command, ["--version"], { stdio: "ignore" }).status === 0,
3220
+ has: (command) => spawnSync4(command, ["--version"], { stdio: "ignore" }).status === 0,
2807
3221
  run: (command, args) => {
2808
- const result = spawnSync3(command, args, { encoding: "utf8" });
3222
+ const result = spawnSync4(command, args, { encoding: "utf8" });
2809
3223
  return { status: result.status, stderr: result.stderr ?? "" };
2810
3224
  }
2811
3225
  };
@@ -2845,10 +3259,10 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
2845
3259
  }
2846
3260
 
2847
3261
  // src/setup-local.ts
2848
- import { isUtf8 as isUtf82 } from "buffer";
2849
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
3262
+ import { isUtf8 as isUtf83 } from "buffer";
3263
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
2850
3264
  import { homedir as homedir6 } from "os";
2851
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
3265
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
2852
3266
  import { parse as parseToml3 } from "smol-toml";
2853
3267
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
2854
3268
  var MAX_IMPORT_DEPTH = 5;
@@ -2862,7 +3276,7 @@ function sensitive(name) {
2862
3276
  return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
2863
3277
  }
2864
3278
  function redacted(data) {
2865
- if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
3279
+ if (data.includes(0) || !isUtf83(data)) return new Uint8Array(data);
2866
3280
  try {
2867
3281
  const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
2868
3282
  return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
@@ -2872,12 +3286,12 @@ function redacted(data) {
2872
3286
  }
2873
3287
  function skillFiles2(root, prefix) {
2874
3288
  const files2 = [];
2875
- for (const entry2 of readdirSync4(root, { withFileTypes: true })) {
2876
- const path = join9(root, entry2.name);
3289
+ for (const entry2 of readdirSync6(root, { withFileTypes: true })) {
3290
+ const path = join11(root, entry2.name);
2877
3291
  if (entry2.isDirectory()) {
2878
3292
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
2879
3293
  } else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
2880
- const bytes = redacted(readFileSync9(path));
3294
+ const bytes = redacted(readFileSync10(path));
2881
3295
  if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
2882
3296
  }
2883
3297
  }
@@ -2888,8 +3302,8 @@ function readLocalSkills(agents) {
2888
3302
  const ignored = [];
2889
3303
  for (const agent2 of agents) {
2890
3304
  if (!existsSync7(agent2.skillsDir)) continue;
2891
- for (const entry2 of readdirSync4(agent2.skillsDir, { withFileTypes: true })) {
2892
- const dir = join9(agent2.skillsDir, entry2.name);
3305
+ for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
3306
+ const dir = join11(agent2.skillsDir, entry2.name);
2893
3307
  let isDir2 = entry2.isDirectory();
2894
3308
  if (entry2.isSymbolicLink()) {
2895
3309
  try {
@@ -2898,8 +3312,8 @@ function readLocalSkills(agents) {
2898
3312
  isDir2 = false;
2899
3313
  }
2900
3314
  }
2901
- if (!isDir2 || !existsSync7(join9(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2902
- const name = frontmatterName(readFileSync9(join9(dir, "SKILL.md"), "utf8"));
3315
+ if (!isDir2 || !existsSync7(join11(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3316
+ const name = frontmatterName(readFileSync10(join11(dir, "SKILL.md"), "utf8"));
2903
3317
  if (!name) {
2904
3318
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
2905
3319
  continue;
@@ -2927,11 +3341,11 @@ function inlineImports(content, from, home, depth, seen) {
2927
3341
  }
2928
3342
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
2929
3343
  if (!target || depth >= MAX_IMPORT_DEPTH) return line;
2930
- const path = target.startsWith("~/") ? join9(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
3344
+ const path = target.startsWith("~/") ? join11(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
2931
3345
  if (seen.includes(path)) return "";
2932
3346
  let imported;
2933
3347
  try {
2934
- imported = readFileSync9(path, "utf8");
3348
+ imported = readFileSync10(path, "utf8");
2935
3349
  } catch {
2936
3350
  return line;
2937
3351
  }
@@ -2944,10 +3358,10 @@ function readGlobalInstructions(agents, home = homedir6()) {
2944
3358
  for (const agent2 of agents) {
2945
3359
  const file = INSTRUCTION_FILES2[agent2.id];
2946
3360
  if (!file) continue;
2947
- const path = join9(agent2.root, file);
3361
+ const path = join11(agent2.root, file);
2948
3362
  let raw;
2949
3363
  try {
2950
- raw = readFileSync9(path, "utf8");
3364
+ raw = readFileSync10(path, "utf8");
2951
3365
  } catch {
2952
3366
  continue;
2953
3367
  }
@@ -2966,7 +3380,7 @@ ${item.content.trim()}`).join("\n\n");
2966
3380
  }
2967
3381
  function readJson3(path) {
2968
3382
  try {
2969
- return JSON.parse(readFileSync9(path, "utf8"));
3383
+ return JSON.parse(readFileSync10(path, "utf8"));
2970
3384
  } catch {
2971
3385
  return void 0;
2972
3386
  }
@@ -2980,10 +3394,10 @@ function bearerOf(def) {
2980
3394
  return match?.[1] ?? null;
2981
3395
  }
2982
3396
  function serversOf(agent2, home) {
2983
- if (agent2.id === "claude_code") return readJson3(join9(home, ".claude.json"))?.mcpServers ?? {};
2984
- if (agent2.id === "cursor") return readJson3(join9(agent2.root, "mcp.json"))?.mcpServers ?? {};
3397
+ if (agent2.id === "claude_code") return readJson3(join11(home, ".claude.json"))?.mcpServers ?? {};
3398
+ if (agent2.id === "cursor") return readJson3(join11(agent2.root, "mcp.json"))?.mcpServers ?? {};
2985
3399
  try {
2986
- return parseToml3(readFileSync9(join9(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3400
+ return parseToml3(readFileSync10(join11(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2987
3401
  } catch {
2988
3402
  return {};
2989
3403
  }
@@ -3004,9 +3418,9 @@ function findLocalSecrets(agents, home = homedir6()) {
3004
3418
 
3005
3419
  // src/subscriptions.ts
3006
3420
  import { spawn as spawn3 } from "child_process";
3007
- import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
3421
+ import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
3008
3422
  import { homedir as homedir7 } from "os";
3009
- import { join as join10 } from "path";
3423
+ import { join as join12 } from "path";
3010
3424
  import { createInterface as createInterface2 } from "readline";
3011
3425
  var ALIASES = {
3012
3426
  claude: "anthropic",
@@ -3102,11 +3516,11 @@ async function connectClaude() {
3102
3516
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
3103
3517
  }
3104
3518
  function codexSignIn(env = process.env, home = homedir7()) {
3105
- const path = join10(env.CODEX_HOME || join10(home, ".codex"), "auth.json");
3519
+ const path = join12(env.CODEX_HOME || join12(home, ".codex"), "auth.json");
3106
3520
  if (!existsSync8(path)) {
3107
3521
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
3108
3522
  }
3109
- const raw = readFileSync10(path, "utf8");
3523
+ const raw = readFileSync11(path, "utf8");
3110
3524
  try {
3111
3525
  const parsed = JSON.parse(raw);
3112
3526
  return { path, raw, account: parsed.tokens?.account_id };
@@ -3115,11 +3529,11 @@ function codexSignIn(env = process.env, home = homedir7()) {
3115
3529
  }
3116
3530
  }
3117
3531
  function cursorSignIn(env = process.env, home = homedir7()) {
3118
- const path = join10(env.XDG_CONFIG_HOME || join10(home, ".config"), "cursor", "auth.json");
3532
+ const path = join12(env.XDG_CONFIG_HOME || join12(home, ".config"), "cursor", "auth.json");
3119
3533
  if (!existsSync8(path)) {
3120
3534
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
3121
3535
  }
3122
- const raw = readFileSync10(path, "utf8");
3536
+ const raw = readFileSync11(path, "utf8");
3123
3537
  try {
3124
3538
  const parsed = JSON.parse(raw);
3125
3539
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -3278,11 +3692,11 @@ function planProviders(names2) {
3278
3692
  }));
3279
3693
  }
3280
3694
  function reportPath() {
3281
- return join11(dirname6(configPath()), "setup.json");
3695
+ return join13(dirname6(configPath()), "setup.json");
3282
3696
  }
3283
3697
  function loadReport() {
3284
3698
  try {
3285
- const saved = JSON.parse(readFileSync11(reportPath(), "utf8"));
3699
+ const saved = JSON.parse(readFileSync12(reportPath(), "utf8"));
3286
3700
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
3287
3701
  } catch {
3288
3702
  return { agents: [], plans: [] };
@@ -3645,17 +4059,17 @@ function renderArtifact(artifact2) {
3645
4059
  }
3646
4060
 
3647
4061
  // src/skills-store.ts
3648
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
3649
- import { basename as basename7, join as join12, relative as relative4, resolve as resolve7 } from "path";
4062
+ import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
4063
+ import { basename as basename7, join as join14, relative as relative4, resolve as resolve7 } from "path";
3650
4064
  function skillBundle(dir) {
3651
4065
  const root = resolve7(dir);
3652
4066
  const top = basename7(root);
3653
4067
  const files2 = [];
3654
4068
  const walk = (path) => {
3655
- for (const name of readdirSync5(path).sort()) {
3656
- const full = join12(path, name);
4069
+ for (const name of readdirSync7(path).sort()) {
4070
+ const full = join14(path, name);
3657
4071
  if (statSync8(full).isDirectory()) walk(full);
3658
- else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync12(full) });
4072
+ else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync13(full) });
3659
4073
  }
3660
4074
  };
3661
4075
  walk(root);
@@ -3908,11 +4322,11 @@ function confirmPrompt(question) {
3908
4322
  }
3909
4323
 
3910
4324
  // src/harness.ts
3911
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
3912
- import { dirname as dirname7, join as join13, resolve as resolve8 } from "path";
4325
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
4326
+ import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
3913
4327
  var HARNESS_FILE = "harness.json";
3914
4328
  function harnessFilePath(dir) {
3915
- return join13(resolve8(dir), HARNESS_FILE);
4329
+ return join15(resolve8(dir), HARNESS_FILE);
3916
4330
  }
3917
4331
  function readHarnessFile(dir) {
3918
4332
  const path = harnessFilePath(dir);
@@ -3920,7 +4334,7 @@ function readHarnessFile(dir) {
3920
4334
  throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
3921
4335
  }
3922
4336
  try {
3923
- return JSON.parse(readFileSync13(path, "utf8"));
4337
+ return JSON.parse(readFileSync14(path, "utf8"));
3924
4338
  } catch (err) {
3925
4339
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
3926
4340
  }
@@ -4621,22 +5035,33 @@ session.command("shared").description("sessions other people shared with you to
4621
5035
  for (const s of sessions) console.log(`${s.id} ${s.agent_type.padEnd(12)} ${s.title ?? "(untitled)"} from ${s.owner.name ?? s.owner.email ?? "unknown"} ${s.shared_at.slice(0, 10)}`);
4622
5036
  })
4623
5037
  );
4624
- session.command("handoff <ref> <targetAgent>").description("hand the session's context to another agent (agent id or catalog handle); prints the new session").option("--from-message <id>", "carry the conversation up to this message only").option("--no-wait", "return once the handoff is accepted").action(
5038
+ session.command("model <ref> [model]").description("show the session's model and thinking level, or switch them; the next turn uses the new ones").option("--thinking <level>", `thinking level for the next turns; "default" returns to the agent's own`).action(
5039
+ run(async (ref, model, opts) => {
5040
+ const id = await resolveSessionRef(ref);
5041
+ const change = engineChange(model, opts.thinking);
5042
+ const { session: s } = change ? await api("PATCH", `/sessions/${encodeURIComponent(id)}`, change) : await api("GET", `/sessions/${encodeURIComponent(id)}`);
5043
+ console.log(`${id}: ${s.llm}${s.reasoningEffort ? ` \xB7 thinking ${s.reasoningEffort}` : ""}${change ? " (from the next turn)" : ""}`);
5044
+ })
5045
+ );
5046
+ session.command("handoff <ref> <targetAgent>").description("hand the session's context and workspace to another agent (agent id or catalog handle); prints the new session").option("--from-message <id>", "carry the conversation up to this message only").option("--model <id>", "the model the new session runs").option("--thinking <level>", "the new session's thinking level").option("--no-wait", "return once the handoff is accepted").action(
4625
5047
  run(async (ref, targetAgent, opts) => {
4626
5048
  const id = await resolveSessionRef(ref);
5049
+ const change = engineChange(opts.model, opts.thinking);
5050
+ if (change && opts.wait === false) throw new ApiError(400, "invalid_request", "--model and --thinking need the handoff to finish; drop --no-wait");
4627
5051
  const done = await handoff(id, targetAgent, {
4628
5052
  fromMessage: opts.fromMessage,
4629
5053
  wait: opts.wait !== false,
4630
5054
  onStatus: (h) => console.error(`${h.id}: ${h.status}`)
4631
5055
  });
4632
5056
  if (done.status === "failed") throw new ApiError(422, done.error?.code ?? "handoff_failed", done.error?.message ?? "the handoff failed");
5057
+ if (done.target_session && change) await api("PATCH", `/sessions/${encodeURIComponent(done.target_session)}`, change);
4633
5058
  if (done.target_session) console.log(`${done.target_session} (${done.resolved_mode === "native_fork" ? "forked" : "from a written summary"})`);
4634
5059
  })
4635
5060
  );
4636
5061
  var sessionWorkspace = session.command("workspace").description("the session's workspace: list and read files, see changes, take a patch, copy a folder in");
4637
5062
  sessionWorkspace.command("ls <ref> [path]").description("list a directory (default: the workspace root)").option("--hidden", "include dotfiles").action(
4638
5063
  run(async (ref, path, opts) => {
4639
- const listing = await listFiles(await resolveSessionRef(ref), path, Boolean(opts.hidden));
5064
+ const listing = await listFiles2(await resolveSessionRef(ref), path, Boolean(opts.hidden));
4640
5065
  for (const e of listing.entries) console.log(entryLine(e));
4641
5066
  if (listing.truncated) console.error(`(only the first ${listing.entries.length} entries)`);
4642
5067
  })
@@ -4909,7 +5334,7 @@ function formatRelative(msAgo) {
4909
5334
  if (hours < 48) return `${hours}h ago`;
4910
5335
  return `${Math.round(hours / 24)}d ago`;
4911
5336
  }
4912
- 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(
5337
+ 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").option("--no-environment", "keep the working folder on this computer: send only the files the conversation touched").action(
4913
5338
  run(
4914
5339
  async (opts) => {
4915
5340
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -5017,6 +5442,7 @@ Importing ${label} \u2026
5017
5442
  importKey: opts.key,
5018
5443
  grantTraceId: opts.trace,
5019
5444
  files: opts.files,
5445
+ environment: opts.environment,
5020
5446
  onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
5021
5447
  `)
5022
5448
  });
@@ -5036,11 +5462,14 @@ Importing ${label} \u2026
5036
5462
  skills: result.setup.skills,
5037
5463
  missingSkills: result.setup.missingSkills,
5038
5464
  mcpServers: result.setup.mcpServers,
5465
+ environment: result.environment,
5039
5466
  continueUrl: result.url
5040
5467
  }));
5041
5468
  } else {
5042
5469
  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).`);
5043
5470
  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)` : ""}.`);
5471
+ if (result.environment && "folder" in result.environment) console.log(` Uploaded the working folder ${result.environment.folder}: ${result.environment.files} files, ${(result.environment.bytes / 1024 / 1024).toFixed(1)} MiB packed${result.environment.leftOut ? `, ${result.environment.leftOut} credential or special files left on this computer` : ""}.`);
5472
+ else if (result.environment) console.log(` The working folder did not upload (${result.environment.error}); the conversation and the files it touched did.`);
5044
5473
  console.log(` \u2713 Imported. Continue here:
5045
5474
  ${result.url}
5046
5475
  `);
@@ -5050,10 +5479,40 @@ Importing ${label} \u2026
5050
5479
  )
5051
5480
  );
5052
5481
  var files = program.command("files").description("files: upload once, reference by id from any message");
5053
- files.command("upload <path>").description("upload a local file; prints the file id to reference from a message").option("--expires <seconds>", "expire the upload after this many seconds (3600\u20137776000)").action(
5054
- run(async (path, opts) => {
5055
- const file = await uploadLocalFile(path, opts.expires ? Number(opts.expires) : void 0, uploadProgress);
5056
- console.log(`${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`);
5482
+ files.command("upload <paths...>").description("upload local files and folders, up to 50 GiB each; prints each file id to reference from a message").option("--expires <seconds>", "expire the uploads after this many seconds (3600\u20137776000)").action(
5483
+ run(async (paths, opts) => {
5484
+ const expires = opts.expires ? Number(opts.expires) : void 0;
5485
+ const chosen = filesUnder(paths);
5486
+ if (chosen.length === 0) throw new Error("no files to upload");
5487
+ const line = (file) => `${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`;
5488
+ if (chosen.length === 1) {
5489
+ console.log(line(await uploadLocalFile(chosen[0], expires, uploadProgress)));
5490
+ return;
5491
+ }
5492
+ let done = 0;
5493
+ const tally = () => process.stderr.write(`${process.stderr.isTTY ? "\r" : ""}uploaded ${++done} / ${chosen.length}${process.stderr.isTTY && done < chosen.length ? "" : "\n"}`);
5494
+ const result = await uploadLocalFiles(chosen, {
5495
+ expiresInSeconds: expires,
5496
+ onUploaded: (_path, file) => {
5497
+ console.log(line(file));
5498
+ tally();
5499
+ },
5500
+ onFailed: (path, error) => {
5501
+ console.error(`sky: ${path}: ${error instanceof Error ? error.message : String(error)}`);
5502
+ tally();
5503
+ }
5504
+ });
5505
+ if (result.failed) {
5506
+ console.error(`sky: ${result.failed} of ${chosen.length} files failed; run the same command for those paths to retry`);
5507
+ process.exitCode = 1;
5508
+ }
5509
+ })
5510
+ );
5511
+ files.command("usage").description("how many files your workspace holds and the space they take (there is no storage limit)").action(
5512
+ run(async () => {
5513
+ const usage = await api("GET", "/files/usage");
5514
+ const gib = (bytes) => `${(bytes / 1024 ** 3).toFixed(2)} GiB`;
5515
+ console.log(`${usage.file_count} files ${gib(usage.size_bytes)} used ${usage.storage_limit_bytes === null ? "no storage limit" : `${gib(usage.storage_limit_bytes)} limit`} up to ${gib(usage.max_file_bytes)} per file`);
5057
5516
  })
5058
5517
  );
5059
5518
  files.command("list").description("list your uploads, or one session's files with --session").option("--session <ref>", "list the files of this session (its message copies and outputs)").option("--limit <n>", "page size (default 20)").option("--page <cursor>", "continue from a previous page").action(
@@ -5106,7 +5565,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
5106
5565
  );
5107
5566
  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(
5108
5567
  run(async (taskId, path, local) => {
5109
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync14(local), mediaTypeFor(local));
5568
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync15(local), mediaTypeFor(local));
5110
5569
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
5111
5570
  })
5112
5571
  );
@@ -5155,6 +5614,28 @@ skills.command("list").description("the workspace's stored skills").action(
5155
5614
  for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
5156
5615
  })
5157
5616
  );
5617
+ async function decideReflection(id, verb) {
5618
+ const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
5619
+ console.log(`${p.id} ${p.status}`);
5620
+ }
5621
+ var reflections = program.command("reflections").description("skill and lesson changes proposed after finished sessions; an owner or admin approves each one");
5622
+ reflections.command("list").description("the workspace's proposals, newest first (default: pending)").option("--status <status>", "pending, approved, rejected or superseded", "pending").option("--json", "print JSON").action(
5623
+ run(async (opts) => {
5624
+ const { reflection_proposals: list } = await api(
5625
+ "GET",
5626
+ `/reflection_proposals?status=${encodeURIComponent(opts.status)}`
5627
+ );
5628
+ if (opts.json) return void console.log(JSON.stringify(list, null, 2));
5629
+ if (!list.length) return void console.log(`no ${opts.status} proposals`);
5630
+ for (const p of list) {
5631
+ const target = p.kind === "save_lesson" ? p.path : p.kind === "create_skill" ? `new skill ${p.payload.name}` : `skill ${p.target_skill_id}`;
5632
+ console.log(`${p.id} ${p.kind} ${target} from ${p.session_id}
5633
+ ${p.payload.why ?? ""}`);
5634
+ }
5635
+ })
5636
+ );
5637
+ reflections.command("approve <id>").description("write the proposal through the skill or memory store").action(run(async (id) => decideReflection(id, "approve")));
5638
+ reflections.command("reject <id>").description("decline the proposal; nothing is written").action(run(async (id) => decideReflection(id, "reject")));
5158
5639
  var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
5159
5640
  artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
5160
5641
  run(async (opts) => {
@@ -5769,7 +6250,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
5769
6250
  }));
5770
6251
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
5771
6252
  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) => {
5772
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-IOARVWJ6.js");
6253
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-QLJH7VK6.js");
5773
6254
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
5774
6255
  const scopes = parseContentScopes(opts.capture);
5775
6256
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -5804,7 +6285,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
5804
6285
  }));
5805
6286
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
5806
6287
  try {
5807
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-IOARVWJ6.js");
6288
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-QLJH7VK6.js");
5808
6289
  const chunks = [];
5809
6290
  for await (const chunk of process.stdin) chunks.push(chunk);
5810
6291
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -5817,14 +6298,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
5817
6298
  }
5818
6299
  });
5819
6300
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
5820
- const { readState, flush, grantSender } = await import("./trace-IOARVWJ6.js");
6301
+ const { readState, flush, grantSender } = await import("./trace-QLJH7VK6.js");
5821
6302
  const state = readState();
5822
6303
  if (!state) throw new Error("no trace is running here");
5823
6304
  const outcome = await flush(grantSender(state), { attempts: 4 });
5824
6305
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
5825
6306
  }));
5826
6307
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
5827
- const { readState, spoolStatus } = await import("./trace-IOARVWJ6.js");
6308
+ const { readState, spoolStatus } = await import("./trace-QLJH7VK6.js");
5828
6309
  const state = readState();
5829
6310
  if (!state) {
5830
6311
  console.log("No trace is running here.");
@@ -5834,7 +6315,7 @@ traceCmd.command("status").description("the running trace and its spool").action
5834
6315
  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`);
5835
6316
  }));
5836
6317
  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) => {
5837
- const { readState } = await import("./trace-IOARVWJ6.js");
6318
+ const { readState } = await import("./trace-QLJH7VK6.js");
5838
6319
  const ref = id ?? readState()?.trace_id;
5839
6320
  if (!ref) throw new Error("name a trace, or start one here");
5840
6321
  const bundle = await api("GET", `/traces/${ref}`);
@@ -5861,7 +6342,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
5861
6342
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
5862
6343
  }));
5863
6344
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
5864
- const { readState, record, flush, grantSender, clearState } = await import("./trace-IOARVWJ6.js");
6345
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-QLJH7VK6.js");
5865
6346
  const state = readState();
5866
6347
  if (!state) throw new Error("no trace is running here");
5867
6348
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -5871,7 +6352,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
5871
6352
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
5872
6353
  }));
5873
6354
  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) => {
5874
- const lib = await import("./trace-library-K3NE26GH.js");
6355
+ const lib = await import("./trace-library-FS5PCBJC.js");
5875
6356
  if (opts.hook) {
5876
6357
  try {
5877
6358
  let raw = payload ?? "";
@@ -5919,10 +6400,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5919
6400
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
5920
6401
  }
5921
6402
  if (opts.installSchedule) {
5922
- const { join: join14 } = await import("path");
6403
+ const { join: join16 } = await import("path");
5923
6404
  const { homedir: homedir8 } = await import("os");
5924
6405
  const { execFileSync } = await import("child_process");
5925
- const log = join14(homedir8(), ".config", "sky", "trace-sync.log");
6406
+ const log = join16(homedir8(), ".config", "sky", "trace-sync.log");
5926
6407
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
5927
6408
  try {
5928
6409
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -5932,11 +6413,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5932
6413
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
5933
6414
  }
5934
6415
  if (opts.status) {
5935
- const { readFileSync: readFileSync15 } = await import("fs");
5936
- const { join: join14 } = await import("path");
6416
+ const { readFileSync: readFileSync16 } = await import("fs");
6417
+ const { join: join16 } = await import("path");
5937
6418
  let state = { sessions: {} };
5938
6419
  try {
5939
- state = JSON.parse(readFileSync15(join14(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
6420
+ state = JSON.parse(readFileSync16(join16(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
5940
6421
  } catch {
5941
6422
  }
5942
6423
  const counts = /* @__PURE__ */ new Map();
@@ -5969,7 +6450,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5969
6450
  })();
5970
6451
  });
5971
6452
  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) => {
5972
- const { libraryRequest } = await import("./trace-library-K3NE26GH.js");
6453
+ const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
5973
6454
  const workspace2 = opts.workspace ?? currentUniverse();
5974
6455
  if (!workspace2) throw new Error("name a workspace with --workspace");
5975
6456
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -5986,7 +6467,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
5986
6467
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
5987
6468
  }));
5988
6469
  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) => {
5989
- const { libraryRequest } = await import("./trace-library-K3NE26GH.js");
6470
+ const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
5990
6471
  const workspace2 = opts.workspace ?? currentUniverse();
5991
6472
  if (!workspace2) throw new Error("name a workspace with --workspace");
5992
6473
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);