@agentsky/sky 2.16.0 → 2.17.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-FU43NICZ.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-H7SWXCH7.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -37,14 +37,15 @@ 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";
@@ -928,10 +929,10 @@ async function clone(opts) {
928
929
  }
929
930
 
930
931
  // 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";
932
+ import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
932
933
  import { homedir as homedir4 } from "os";
933
- import { join as join5 } from "path";
934
- import { createHash as createHash2 } from "crypto";
934
+ import { join as join6 } from "path";
935
+ import { createHash as createHash3 } from "crypto";
935
936
 
936
937
  // src/import-files.ts
937
938
  import { isUtf8 } from "buffer";
@@ -1432,6 +1433,325 @@ function collectSessionFiles(records, opts) {
1432
1433
  return { lines, bytes, attached, leftOut, setup };
1433
1434
  }
1434
1435
 
1436
+ // src/import-environment.ts
1437
+ import { spawnSync as spawnSync2 } from "child_process";
1438
+ import { createHash as createHash2 } from "crypto";
1439
+ import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync7, readSync as readSync2, rmSync as rmSync2 } from "fs";
1440
+ import { tmpdir } from "os";
1441
+ import { join as join5 } from "path";
1442
+ import { isUtf8 as isUtf82 } from "buffer";
1443
+ import { createGzip } from "zlib";
1444
+
1445
+ // ../../packages/spec/src/import-layout.ts
1446
+ import { gzipSync } from "zlib";
1447
+ function folderName(workspace2, sourceCwd) {
1448
+ const raw = workspace2?.project || (sourceCwd ? sourceCwd.split("/").filter(Boolean).pop() : "") || "";
1449
+ const cleaned = raw.replace(/[^A-Za-z0-9._ -]/g, "-").replace(/^[^A-Za-z0-9_]+/, "").replace(/\.\.+/g, ".").slice(0, 80).trim();
1450
+ return cleaned || "imported-session";
1451
+ }
1452
+ function envName(server, field) {
1453
+ return `${server}_${field}`.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1454
+ }
1455
+ function ownServer(server, platform) {
1456
+ try {
1457
+ return server.transport !== "stdio" && platform.ownHost.test(new URL(server.url ?? "").hostname);
1458
+ } catch {
1459
+ return false;
1460
+ }
1461
+ }
1462
+ function mcpJson(servers, platform) {
1463
+ const entries = servers.filter((server) => !ownServer(server, platform)).map((server) => {
1464
+ if (server.transport === "stdio") {
1465
+ return [server.name, {
1466
+ command: server.command,
1467
+ ...server.args?.length ? { args: server.args } : {},
1468
+ ...server.env?.length ? { env: Object.fromEntries(server.env.map((name) => [name, `\${${name}:-}`])) } : {}
1469
+ }];
1470
+ }
1471
+ const query = server.query?.length ? `?${server.query.map((name) => `${encodeURIComponent(name)}=\${${envName(server.name, name)}:-}`).join("&")}` : "";
1472
+ return [server.name, {
1473
+ type: server.transport,
1474
+ url: `${server.url}${query}`,
1475
+ ...server.headers?.length ? { headers: Object.fromEntries(server.headers.map((name) => [name, `\${${envName(server.name, name)}:-}`])) } : {}
1476
+ }];
1477
+ });
1478
+ return JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2) + "\n";
1479
+ }
1480
+ function bullet(items, none) {
1481
+ return items.length ? items.map((item) => `- ${item}`).join("\n") : `- ${none}`;
1482
+ }
1483
+ function readme(folder, workspace2, counts, skills2, mcpWritten, platform) {
1484
+ const git2 = workspace2?.git;
1485
+ const servers = workspace2?.mcp_servers ?? [];
1486
+ const missingSkills = (workspace2?.skills ?? []).filter((skill) => !skill.found).map((skill) => skill.name);
1487
+ const secrets2 = servers.filter((server) => !ownServer(server, platform)).flatMap((server) => [
1488
+ ...server.env ?? [],
1489
+ ...(server.headers ?? []).map((name) => envName(server.name, name)),
1490
+ ...(server.query ?? []).map((name) => envName(server.name, name))
1491
+ ]);
1492
+ const where = workspace2?.cwd ? ` (${workspace2.cwd})` : "";
1493
+ return [
1494
+ "# Imported working copy",
1495
+ "",
1496
+ `This folder (/workspace/${folder}) holds what the imported conversation worked with on the person's computer.`,
1497
+ "It is historical context: verify a path before acting on it, and follow the person's current message.",
1498
+ "",
1499
+ "## Files",
1500
+ 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\`)` : ""}.`,
1501
+ ...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/.`] : [],
1502
+ ...counts.attachments ? [`- ${counts.attachments} attachments of the conversation under ${platform.notesDir}/attachments/.`] : [],
1503
+ "- 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.",
1504
+ "",
1505
+ "## Repository",
1506
+ ...git2 ? [
1507
+ `- Remote: ${git2.remote ?? "none"}`,
1508
+ `- Branch: ${git2.branch ?? "detached"} at commit ${git2.commit ?? "unknown"}${git2.ahead ? ` (${git2.ahead} commits not pushed)` : ""}`,
1509
+ ...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."],
1510
+ 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.`
1511
+ ] : ["- The working directory was not a git repository."],
1512
+ "",
1513
+ "## Skills",
1514
+ bullet(skills2.map((name) => `${name}: .claude/skills/${name}/SKILL.md (also .agents/skills/${name}/)`), "No skills were loaded."),
1515
+ ...missingSkills.length ? ["", `Loaded but no longer on that computer, so not carried: ${missingSkills.join(", ")}.`] : [],
1516
+ "",
1517
+ "## Tool servers (MCP)",
1518
+ 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."),
1519
+ ...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.`] : [],
1520
+ ""
1521
+ ].join("\n");
1522
+ }
1523
+ function parseWorkspace(file) {
1524
+ if (!file) return null;
1525
+ try {
1526
+ const value = JSON.parse(file.data.toString("utf8"));
1527
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1528
+ } catch {
1529
+ return null;
1530
+ }
1531
+ }
1532
+ function importLayout(files2, sourceCwd, platform, wholeFolder) {
1533
+ if (!files2.length && !wholeFolder) return null;
1534
+ const workspace2 = parseWorkspace(files2.find((file) => file.name === "agentsky/workspace.json"));
1535
+ const folder = folderName(workspace2, sourceCwd);
1536
+ const entries = /* @__PURE__ */ new Map();
1537
+ const skills2 = /* @__PURE__ */ new Set();
1538
+ const counts = { project: wholeFolder?.files ?? 0, outside: 0, attachments: 0, wholeFolder: Boolean(wholeFolder) };
1539
+ for (const file of files2) {
1540
+ const [kind, ...rest] = file.name.split("/");
1541
+ const path = rest.join("/");
1542
+ if (kind === "workspace") {
1543
+ if (wholeFolder) continue;
1544
+ entries.set(path, file.data);
1545
+ counts.project += 1;
1546
+ } else if (kind === "files") {
1547
+ entries.set(`${platform.notesDir}/files/${path.replace(/^~\//, "home/")}`, file.data);
1548
+ counts.outside += 1;
1549
+ } else if (kind === "imported-attachments") {
1550
+ entries.set(`${platform.notesDir}/attachments/${path}`, file.data);
1551
+ counts.attachments += 1;
1552
+ } else if (kind === "agentsky") entries.set(`${platform.notesDir}/${path}`, file.data);
1553
+ else if (kind === "skills" && rest.length > 1) {
1554
+ const skill = rest[0].split(":").pop();
1555
+ const inside = rest.slice(1).join("/");
1556
+ skills2.add(skill);
1557
+ entries.set(`.claude/skills/${skill}/${inside}`, file.data);
1558
+ entries.set(`.agents/skills/${skill}/${inside}`, file.data);
1559
+ }
1560
+ }
1561
+ const servers = (workspace2?.mcp_servers ?? []).filter((server) => server && typeof server.name === "string" && (server.transport === "stdio" ? typeof server.command === "string" : typeof server.url === "string"));
1562
+ const mcpWritten = servers.length > 0 && !entries.has(".mcp.json") && !wholeFolder?.paths.has(".mcp.json");
1563
+ if (mcpWritten) entries.set(".mcp.json", Buffer.from(mcpJson(servers, platform)));
1564
+ entries.set(`${platform.notesDir}/README.md`, Buffer.from(readme(folder, workspace2 ? { ...workspace2, mcp_servers: servers } : null, counts, [...skills2], mcpWritten, platform)));
1565
+ if (wholeFolder) for (const path of wholeFolder.paths) entries.delete(path);
1566
+ return { folder, entries, skills: [...skills2], mcpServers: servers.map((server) => server.name) };
1567
+ }
1568
+ var TAR_BLOCK = 512;
1569
+ var TAR_END = Buffer.alloc(TAR_BLOCK * 2);
1570
+ function header(name, size, type) {
1571
+ const block = Buffer.alloc(TAR_BLOCK);
1572
+ block.write(name, 0, 100, "utf8");
1573
+ block.write("0000644\0", 100);
1574
+ block.write("0000000\0", 108);
1575
+ block.write("0000000\0", 116);
1576
+ block.write(size.toString(8).padStart(11, "0") + "\0", 124);
1577
+ block.write("00000000000\0", 136);
1578
+ block.write(" ", 148);
1579
+ block.write(type, 156);
1580
+ block.write("ustar\x0000", 257);
1581
+ let sum = 0;
1582
+ for (const byte of block) sum += byte;
1583
+ block.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
1584
+ return block;
1585
+ }
1586
+ function paxRecord(key, value) {
1587
+ const body = ` ${key}=${value}
1588
+ `;
1589
+ let length = Buffer.byteLength(body);
1590
+ for (; ; ) {
1591
+ const total = String(length).length + length;
1592
+ if (String(total).length + length === total) return Buffer.from(`${total}${body}`);
1593
+ length += 1;
1594
+ }
1595
+ }
1596
+ function tarPadding(size) {
1597
+ const rest = size % TAR_BLOCK;
1598
+ return Buffer.alloc(rest ? TAR_BLOCK - rest : 0);
1599
+ }
1600
+ function tarHeader(path, size) {
1601
+ if (Buffer.byteLength(path) <= 100) return header(path, size, "0");
1602
+ const pax = paxRecord("path", path);
1603
+ return Buffer.concat([header("PaxHeader", pax.length, "x"), pax, tarPadding(pax.length), header(path, size, "0")]);
1604
+ }
1605
+
1606
+ // src/import-environment.ts
1607
+ var IMPORT_PLATFORM = { name: "AgentSky", ownHost: /(^|\.)(agentsky\.dev|asteroidbelt\.ai)$/i, notesDir: ".agentsky-import" };
1608
+ var MAX_ENVIRONMENT_FILES = 2e5;
1609
+ var MAX_ENVIRONMENT_BYTES = 2 * 1024 ** 3;
1610
+ var MAX_REDACTED_TEXT_BYTES = 10 * 1024 * 1024;
1611
+ var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", ".venv", "venv", "__pycache__", ".next", "dist", "build", "target", ".DS_Store", ".cache", ".turbo"]);
1612
+ 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;
1613
+ function listFiles(cwd) {
1614
+ const git2 = spawnSync2("git", ["-C", cwd, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8", maxBuffer: 512 * 1024 * 1024 });
1615
+ if (git2.status === 0) return git2.stdout.split("\0").filter(Boolean).filter((path) => !path.split("/").some((part) => part === ".git"));
1616
+ const out = [];
1617
+ const walk = (dir, prefix) => {
1618
+ let entries;
1619
+ try {
1620
+ entries = readdirSync2(dir, { withFileTypes: true });
1621
+ } catch {
1622
+ return;
1623
+ }
1624
+ for (const entry2 of entries) {
1625
+ if (SKIPPED.has(entry2.name)) continue;
1626
+ const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
1627
+ if (entry2.isDirectory()) walk(join5(dir, entry2.name), rel);
1628
+ else out.push(rel);
1629
+ if (out.length > MAX_ENVIRONMENT_FILES) return;
1630
+ }
1631
+ };
1632
+ walk(cwd, "");
1633
+ return out;
1634
+ }
1635
+ function textOf(path, size) {
1636
+ if (size > MAX_REDACTED_TEXT_BYTES) return null;
1637
+ const data = readFileSync7(path);
1638
+ if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
1639
+ return data;
1640
+ }
1641
+ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
1642
+ const listed = listFiles(cwd);
1643
+ if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
1644
+ const scratch = mkdtempSync(join5(tmpdir(), "sky-environment-"));
1645
+ const path = join5(scratch, "environment.tar.gz");
1646
+ const leftOut = [];
1647
+ const written = /* @__PURE__ */ new Set();
1648
+ const hash = createHash2("sha256");
1649
+ let bytes = 0;
1650
+ let redacted2 = 0;
1651
+ const gzip = createGzip({ level: 6 });
1652
+ const sink = createWriteStream(path);
1653
+ gzip.on("data", (chunk) => {
1654
+ bytes += chunk.length;
1655
+ hash.update(chunk);
1656
+ });
1657
+ gzip.pipe(sink);
1658
+ const done = new Promise((resolve10, reject) => {
1659
+ sink.on("finish", () => resolve10());
1660
+ sink.on("error", reject);
1661
+ gzip.on("error", reject);
1662
+ });
1663
+ const write = (chunk) => new Promise((resolve10, reject) => {
1664
+ if (bytes > MAX_ENVIRONMENT_BYTES) {
1665
+ reject(new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`));
1666
+ return;
1667
+ }
1668
+ if (gzip.write(chunk)) resolve10();
1669
+ else gzip.once("drain", resolve10);
1670
+ });
1671
+ try {
1672
+ for (const rel of listed) {
1673
+ const full = join5(cwd, ...rel.split("/"));
1674
+ if (SECRET_FILE2.test(rel)) {
1675
+ leftOut.push({ path: rel, reason: "credential file" });
1676
+ continue;
1677
+ }
1678
+ let stat;
1679
+ try {
1680
+ stat = lstatSync(full);
1681
+ } catch {
1682
+ continue;
1683
+ }
1684
+ if (!stat.isFile()) {
1685
+ leftOut.push({ path: rel, reason: "not a regular file" });
1686
+ continue;
1687
+ }
1688
+ const text = textOf(full, stat.size);
1689
+ if (text) {
1690
+ const masked = redactSecretValues(text.toString("utf8"));
1691
+ if (Object.keys(masked.hits).length) redacted2 += 1;
1692
+ const data = Buffer.from(masked.text, "utf8");
1693
+ await write(tarHeader(rel, data.length));
1694
+ await write(data);
1695
+ await write(tarPadding(data.length));
1696
+ } else {
1697
+ await write(tarHeader(rel, stat.size));
1698
+ const fd = openSync2(full, "r");
1699
+ try {
1700
+ const chunk = Buffer.alloc(1024 * 1024);
1701
+ let left = stat.size;
1702
+ for (let offset = 0; left > 0; ) {
1703
+ const read = readSync2(fd, chunk, 0, Math.min(chunk.length, left), offset);
1704
+ if (read <= 0) throw new Error(`${rel} changed while it was packed; import again.`);
1705
+ await write(Buffer.from(chunk.subarray(0, read)));
1706
+ offset += read;
1707
+ left -= read;
1708
+ }
1709
+ } finally {
1710
+ closeSync2(fd);
1711
+ }
1712
+ await write(tarPadding(stat.size));
1713
+ }
1714
+ written.add(rel);
1715
+ }
1716
+ const layout = importLayout(resources, cwd, platform, { files: written.size, paths: written });
1717
+ for (const [entry2, data] of layout?.entries ?? []) {
1718
+ await write(tarHeader(entry2, data.length));
1719
+ await write(data);
1720
+ await write(tarPadding(data.length));
1721
+ }
1722
+ await write(TAR_END);
1723
+ gzip.end();
1724
+ await done;
1725
+ 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.`);
1726
+ return {
1727
+ path,
1728
+ folder: layout?.folder ?? "imported-session",
1729
+ bytes,
1730
+ sha256: hash.digest("hex"),
1731
+ files: written.size + (layout?.entries.size ?? 0),
1732
+ redacted: redacted2,
1733
+ leftOut,
1734
+ dispose: () => rmSync2(scratch, { recursive: true, force: true })
1735
+ };
1736
+ } catch (error) {
1737
+ gzip.destroy();
1738
+ sink.destroy();
1739
+ rmSync2(scratch, { recursive: true, force: true });
1740
+ throw error;
1741
+ }
1742
+ }
1743
+ function resourcesOf(lines) {
1744
+ return lines.flatMap((line) => {
1745
+ try {
1746
+ const record = JSON.parse(line);
1747
+ if (record.type !== "import.resource" || typeof record.name !== "string" || typeof record.media_type !== "string" || typeof record.data !== "string") return [];
1748
+ return [{ name: record.name, mediaType: record.media_type, data: Buffer.from(record.data, "base64") }];
1749
+ } catch {
1750
+ return [];
1751
+ }
1752
+ });
1753
+ }
1754
+
1435
1755
  // src/import-local.ts
1436
1756
  function redactLine2(raw) {
1437
1757
  return redactLine(raw).line;
@@ -1444,12 +1764,12 @@ function decodeWorkspace(encoded) {
1444
1764
  }
1445
1765
  }
1446
1766
  function readPrefix(filePath) {
1447
- const fd = openSync2(filePath, "r");
1767
+ const fd = openSync3(filePath, "r");
1448
1768
  try {
1449
1769
  const bytes = Buffer.alloc(256 * 1024);
1450
- return bytes.subarray(0, readSync2(fd, bytes, 0, bytes.length, 0)).toString("utf8");
1770
+ return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
1451
1771
  } finally {
1452
- closeSync2(fd);
1772
+ closeSync3(fd);
1453
1773
  }
1454
1774
  }
1455
1775
  function sniffTitle(filePath) {
@@ -1504,16 +1824,16 @@ function sniffTitle(filePath) {
1504
1824
  }
1505
1825
  }
1506
1826
  function listClaudeCodeSessions(limit) {
1507
- const root = process.env.CLAUDE_CONFIG_DIR ?? join5(homedir4(), ".claude");
1508
- const projectsDir = join5(root, "projects");
1827
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join6(homedir4(), ".claude");
1828
+ const projectsDir = join6(root, "projects");
1509
1829
  if (!existsSync5(projectsDir)) return [];
1510
1830
  const sessions = [];
1511
1831
  try {
1512
- for (const encoded of readdirSync2(projectsDir)) {
1513
- const projectDir = join5(projectsDir, encoded);
1832
+ for (const encoded of readdirSync3(projectsDir)) {
1833
+ const projectDir = join6(projectsDir, encoded);
1514
1834
  let entries;
1515
1835
  try {
1516
- entries = readdirSync2(projectDir);
1836
+ entries = readdirSync3(projectDir);
1517
1837
  } catch {
1518
1838
  continue;
1519
1839
  }
@@ -1521,7 +1841,7 @@ function listClaudeCodeSessions(limit) {
1521
1841
  for (const entry2 of entries) {
1522
1842
  if (!entry2.endsWith(".jsonl")) continue;
1523
1843
  const sessionId = entry2.slice(0, -6);
1524
- const mainFile = join5(projectDir, entry2);
1844
+ const mainFile = join6(projectDir, entry2);
1525
1845
  let stat;
1526
1846
  try {
1527
1847
  stat = statSync5(mainFile);
@@ -1531,12 +1851,12 @@ function listClaudeCodeSessions(limit) {
1531
1851
  if (!stat.isFile()) continue;
1532
1852
  let discoveryError;
1533
1853
  const subagentFiles = [];
1534
- const subagentDir = join5(projectDir, sessionId, "subagents");
1854
+ const subagentDir = join6(projectDir, sessionId, "subagents");
1535
1855
  if (existsSync5(subagentDir)) {
1536
1856
  try {
1537
- const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1857
+ const subFiles = readdirSync3(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1538
1858
  for (const f of subFiles) {
1539
- subagentFiles.push(join5(subagentDir, f));
1859
+ subagentFiles.push(join6(subagentDir, f));
1540
1860
  }
1541
1861
  } catch {
1542
1862
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -1558,17 +1878,17 @@ function listClaudeCodeSessions(limit) {
1558
1878
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
1559
1879
  }
1560
1880
  function listCodexSessions(limit) {
1561
- const sessionsDir = join5(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join5(homedir4(), ".codex"), "sessions");
1881
+ const sessionsDir = join6(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join6(homedir4(), ".codex"), "sessions");
1562
1882
  const records = [];
1563
1883
  function walk(dir) {
1564
1884
  let entries;
1565
1885
  try {
1566
- entries = readdirSync2(dir, { withFileTypes: true });
1886
+ entries = readdirSync3(dir, { withFileTypes: true });
1567
1887
  } catch {
1568
1888
  return;
1569
1889
  }
1570
1890
  for (const entry2 of entries) {
1571
- const full = join5(dir, entry2.name);
1891
+ const full = join6(dir, entry2.name);
1572
1892
  if (entry2.isDirectory()) {
1573
1893
  walk(full);
1574
1894
  continue;
@@ -1655,7 +1975,7 @@ async function mintTrace(base, harness2) {
1655
1975
  function loadSummaryFile(summaryPath) {
1656
1976
  let raw;
1657
1977
  try {
1658
- raw = readFileSync7(summaryPath, "utf8");
1978
+ raw = readFileSync8(summaryPath, "utf8");
1659
1979
  } catch {
1660
1980
  throw new Error(`Cannot read summary file: ${summaryPath}`);
1661
1981
  }
@@ -1687,7 +2007,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1687
2007
  let inputBytes = 0;
1688
2008
  let outputBytes = 0;
1689
2009
  const lines = [];
1690
- const digest = createHash2("sha256");
2010
+ const digest = createHash3("sha256");
1691
2011
  let nativeId;
1692
2012
  let redactionCount = 0;
1693
2013
  let encryptedReasoningCount = 0;
@@ -1698,7 +2018,7 @@ function prepareImport(session2, summaryPath, options = {}) {
1698
2018
  for (const path of session2.files) {
1699
2019
  const stat = statSync5(path);
1700
2020
  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");
2021
+ const text = readFileSync8(path, "utf8");
1702
2022
  for (const raw of text.split("\n")) {
1703
2023
  if (!raw.trim()) continue;
1704
2024
  let record;
@@ -1776,7 +2096,8 @@ function prepareImport(session2, summaryPath, options = {}) {
1776
2096
  if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
1777
2097
  if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
1778
2098
  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 };
2099
+ const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
2100
+ return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
1780
2101
  }
1781
2102
  async function importSession(opts) {
1782
2103
  const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
@@ -1785,7 +2106,7 @@ async function importSession(opts) {
1785
2106
  throw new Error("Import requires HTTPS, or a loopback development server.");
1786
2107
  }
1787
2108
  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 });
2109
+ const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup, cwd, resourceLines } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
1789
2110
  let traceId;
1790
2111
  let key;
1791
2112
  if (opts.importKey && opts.grantTraceId) {
@@ -1809,6 +2130,7 @@ async function importSession(opts) {
1809
2130
  }
1810
2131
  const result = await response.json();
1811
2132
  const finalTraceId = result.traceId ?? traceId;
2133
+ 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
2134
  return {
1813
2135
  traceId: finalTraceId,
1814
2136
  url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
@@ -1820,9 +2142,42 @@ async function importSession(opts) {
1820
2142
  gaps,
1821
2143
  attachedFiles,
1822
2144
  leftOutFiles,
1823
- setup
2145
+ setup,
2146
+ environment
1824
2147
  };
1825
2148
  }
2149
+ async function grantCall(base, key, path, body) {
2150
+ const response = await fetch(`${base}${path}`, {
2151
+ method: "POST",
2152
+ redirect: "error",
2153
+ signal: AbortSignal.timeout(3e5),
2154
+ headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
2155
+ body: JSON.stringify(body)
2156
+ });
2157
+ const answer = await response.json().catch(() => null);
2158
+ if (!response.ok) throw new Error(typeof answer?.error === "string" ? answer.error : answer?.error?.message ?? `The environment upload failed (${response.status}).`);
2159
+ return answer;
2160
+ }
2161
+ async function uploadEnvironment(base, key, traceId, cwd, resourceLines, onProgress) {
2162
+ onProgress?.("Packing the working folder (secrets redacted locally)\u2026");
2163
+ const packed = await packEnvironment(cwd, resourcesOf(resourceLines));
2164
+ try {
2165
+ const path = `/api/traces/${encodeURIComponent(traceId)}/environment`;
2166
+ const { upload } = await grantCall(base, key, path, { name: packed.folder, size: packed.bytes, sha256: packed.sha256, files: packed.files });
2167
+ let shown = -1;
2168
+ await transfer(upload, packed.path, (sent, total) => {
2169
+ const percent = Math.floor(sent / total * 100);
2170
+ if (percent >= shown + 10 || sent === total) {
2171
+ shown = percent;
2172
+ onProgress?.(`Uploading the working folder\u2026 ${percent}% of ${(total / 1024 / 1024).toFixed(1)} MiB`);
2173
+ }
2174
+ });
2175
+ await grantCall(base, key, `${path}/complete`, {});
2176
+ return { folder: packed.folder, files: packed.files, bytes: packed.bytes, redacted: packed.redacted, leftOut: packed.leftOut.length };
2177
+ } finally {
2178
+ packed.dispose();
2179
+ }
2180
+ }
1826
2181
 
1827
2182
  // src/channels.ts
1828
2183
  function connectionLine(c) {
@@ -1876,7 +2231,7 @@ async function send(threadId, text) {
1876
2231
  import { createServer as createServer2 } from "http";
1877
2232
 
1878
2233
  // src/login.ts
1879
- import { createHash as createHash3, randomBytes } from "crypto";
2234
+ import { createHash as createHash4, randomBytes } from "crypto";
1880
2235
  import { createServer } from "http";
1881
2236
  import { hostname } from "os";
1882
2237
  import { spawn as spawn2 } from "child_process";
@@ -1892,7 +2247,7 @@ function openBrowser2(url) {
1892
2247
  }
1893
2248
  async function login(opts) {
1894
2249
  const verifier = b64url(randomBytes(32));
1895
- const challenge = b64url(createHash3("sha256").update(verifier).digest());
2250
+ const challenge = b64url(createHash4("sha256").update(verifier).digest());
1896
2251
  const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
1897
2252
  saveConfig({
1898
2253
  ...loadConfig(),
@@ -2083,15 +2438,15 @@ async function triggerRequest(id, resource, change) {
2083
2438
  }
2084
2439
 
2085
2440
  // 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";
2441
+ import { spawnSync as spawnSync3 } from "child_process";
2442
+ import { createHash as createHash5, randomUUID } from "crypto";
2443
+ import { readdirSync as readdirSync4, statSync as statSync6 } from "fs";
2444
+ import { basename as basename6, join as join7, relative as relative3, resolve as resolve5 } from "path";
2090
2445
  var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
2091
2446
  var PART_SIZE = 8 * 1024 * 1024;
2092
- var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules"]);
2447
+ var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
2093
2448
  var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
2094
- async function listFiles(id, path, hidden) {
2449
+ async function listFiles2(id, path, hidden) {
2095
2450
  const query = new URLSearchParams();
2096
2451
  if (path) query.set("path", path);
2097
2452
  if (hidden) query.set("hidden", "true");
@@ -2117,16 +2472,16 @@ async function workspacePatch(id, repo) {
2117
2472
  return { meta, patch: Buffer.from(patch, "base64") };
2118
2473
  }
2119
2474
  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" });
2475
+ const git2 = spawnSync3("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
2476
+ const top = spawnSync3("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
2122
2477
  if (git2.status === 0 && top.status === 0) {
2123
- return git2.stdout.split("\0").filter((path) => path && statSync6(join6(dir, path), { throwIfNoEntry: false })?.isFile());
2478
+ return git2.stdout.split("\0").filter((path) => path && statSync6(join7(dir, path), { throwIfNoEntry: false })?.isFile());
2124
2479
  }
2125
2480
  const files2 = [];
2126
2481
  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);
2482
+ for (const entry2 of readdirSync4(at, { withFileTypes: true })) {
2483
+ if (SKIPPED2.has(entry2.name)) continue;
2484
+ const full = join7(at, entry2.name);
2130
2485
  if (entry2.isDirectory()) walk(full);
2131
2486
  else if (entry2.isFile()) files2.push(relative3(dir, full));
2132
2487
  }
@@ -2135,7 +2490,7 @@ function folderFiles(dir) {
2135
2490
  return files2;
2136
2491
  }
2137
2492
  function folderArchive(dir, files2) {
2138
- const tar = spawnSync2("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
2493
+ const tar = spawnSync3("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
2139
2494
  input: files2.join("\n"),
2140
2495
  env: { ...process.env, COPYFILE_DISABLE: "1" },
2141
2496
  maxBuffer: 2 * 1024 * 1024 * 1024
@@ -2151,7 +2506,7 @@ async function importFolder(id, dir, opts = {}) {
2151
2506
  const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
2152
2507
  return res.json();
2153
2508
  }
2154
- const sha256 = createHash4("sha256").update(archive).digest("hex");
2509
+ const sha256 = createHash5("sha256").update(archive).digest("hex");
2155
2510
  let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
2156
2511
  const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
2157
2512
  for (let part = 0; part < upload.parts; part += 1) {
@@ -2184,6 +2539,13 @@ function accessPatch(opts) {
2184
2539
  ...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
2185
2540
  };
2186
2541
  }
2542
+ function engineChange(model, thinking) {
2543
+ if (!model && !thinking) return null;
2544
+ return {
2545
+ ...model ? { llm: model } : {},
2546
+ ...thinking ? { reasoningEffort: thinking === "default" ? null : thinking } : {}
2547
+ };
2548
+ }
2187
2549
  var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
2188
2550
  async function handoff(id, targetAgent, opts = {}) {
2189
2551
  let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
@@ -2203,15 +2565,15 @@ async function handoff(id, targetAgent, opts = {}) {
2203
2565
  // src/local-agents.ts
2204
2566
  import { existsSync as existsSync6 } from "fs";
2205
2567
  import { homedir as homedir5 } from "os";
2206
- import { join as join7 } from "path";
2568
+ import { join as join8 } from "path";
2207
2569
  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");
2570
+ const claude = env.CLAUDE_CONFIG_DIR || join8(home, ".claude");
2571
+ const codex = env.CODEX_HOME || join8(home, ".codex");
2572
+ const cursor = join8(home, ".cursor");
2211
2573
  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") }
2574
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join8(claude, "skills") },
2575
+ { id: "codex", name: "Codex", root: codex, skillsDir: join8(codex, "skills") },
2576
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join8(cursor, "skills") }
2215
2577
  ];
2216
2578
  }
2217
2579
  function detectLocalAgents(env = process.env, home = homedir5()) {
@@ -2316,16 +2678,16 @@ async function readTurn(sessionId, opts = {}) {
2316
2678
  }
2317
2679
 
2318
2680
  // 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";
2681
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2682
+ import { dirname as dirname6, join as join12 } from "path";
2321
2683
  import { createInterface as createInterface3 } from "readline";
2322
2684
 
2323
2685
  // src/mcp-register.ts
2324
- import { spawnSync as spawnSync3 } from "child_process";
2686
+ import { spawnSync as spawnSync4 } from "child_process";
2325
2687
 
2326
2688
  // src/skill.ts
2327
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
2328
- import { join as join8 } from "path";
2689
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2690
+ import { join as join9 } from "path";
2329
2691
 
2330
2692
  // src/catalog-file.ts
2331
2693
  var CATALOG_FILE = "catalog.md";
@@ -2351,13 +2713,13 @@ function entry(agent2) {
2351
2713
  return lines.join("\n");
2352
2714
  }
2353
2715
  function renderCatalog(agents, generatedAt) {
2354
- const header = [
2716
+ const header2 = [
2355
2717
  "# AgentSky agent catalog",
2356
2718
  "",
2357
2719
  `Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
2358
2720
  "Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
2359
2721
  ];
2360
- if (!agents.length) return [...header, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
2722
+ if (!agents.length) return [...header2, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
2361
2723
  const own = agents.filter((agent2) => agent2.visibility === "workspace");
2362
2724
  const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
2363
2725
  const sections = [];
@@ -2372,7 +2734,7 @@ function renderCatalog(agents, generatedAt) {
2372
2734
  const title = category.charAt(0).toUpperCase() + category.slice(1);
2373
2735
  sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
2374
2736
  }
2375
- return [...header, "", sections.join("\n\n"), ""].join("\n");
2737
+ return [...header2, "", sections.join("\n\n"), ""].join("\n");
2376
2738
  }
2377
2739
 
2378
2740
  // src/skill.ts
@@ -2766,7 +3128,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
2766
3128
  }
2767
3129
  function installedVersion(dir) {
2768
3130
  try {
2769
- const marker = JSON.parse(readFileSync8(join8(dir, SKILL_MARKER), "utf8"));
3131
+ const marker = JSON.parse(readFileSync9(join9(dir, SKILL_MARKER), "utf8"));
2770
3132
  return typeof marker.version === "string" ? marker.version : null;
2771
3133
  } catch {
2772
3134
  return null;
@@ -2776,25 +3138,25 @@ function isInstalledSkill(dir) {
2776
3138
  return installedVersion(dir) !== null;
2777
3139
  }
2778
3140
  function installSkill(agent2, opts = {}) {
2779
- const dir = join8(agent2.skillsDir, SKILL_NAME);
3141
+ const dir = join9(agent2.skillsDir, SKILL_NAME);
2780
3142
  const previous = installedVersion(dir);
2781
3143
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
2782
3144
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
2783
3145
  if (outcome !== "current") {
2784
3146
  mkdirSync2(dir, { recursive: true });
2785
- writeFileSync4(join8(dir, "SKILL.md"), skillDocument());
2786
- writeFileSync4(join8(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
3147
+ writeFileSync4(join9(dir, "SKILL.md"), skillDocument());
3148
+ writeFileSync4(join9(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
2787
3149
  `);
2788
3150
  }
2789
3151
  if (opts.catalog) {
2790
3152
  mkdirSync2(dir, { recursive: true });
2791
- writeFileSync4(join8(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
3153
+ writeFileSync4(join9(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
2792
3154
  }
2793
- const refsDir = join8(dir, REFERENCES_DIR);
3155
+ const refsDir = join9(dir, REFERENCES_DIR);
2794
3156
  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());
3157
+ writeFileSync4(join9(refsDir, "import.md"), importReferenceDoc());
3158
+ writeFileSync4(join9(refsDir, "workspaces.md"), workspacesReferenceDoc());
3159
+ writeFileSync4(join9(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
2798
3160
  return { agent: agent2.id, path: dir, outcome };
2799
3161
  }
2800
3162
 
@@ -2803,9 +3165,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
2803
3165
  var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
2804
3166
  function systemCommandRunner() {
2805
3167
  return {
2806
- has: (command) => spawnSync3(command, ["--version"], { stdio: "ignore" }).status === 0,
3168
+ has: (command) => spawnSync4(command, ["--version"], { stdio: "ignore" }).status === 0,
2807
3169
  run: (command, args) => {
2808
- const result = spawnSync3(command, args, { encoding: "utf8" });
3170
+ const result = spawnSync4(command, args, { encoding: "utf8" });
2809
3171
  return { status: result.status, stderr: result.stderr ?? "" };
2810
3172
  }
2811
3173
  };
@@ -2845,10 +3207,10 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
2845
3207
  }
2846
3208
 
2847
3209
  // 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";
3210
+ import { isUtf8 as isUtf83 } from "buffer";
3211
+ import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
2850
3212
  import { homedir as homedir6 } from "os";
2851
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
3213
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
2852
3214
  import { parse as parseToml3 } from "smol-toml";
2853
3215
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
2854
3216
  var MAX_IMPORT_DEPTH = 5;
@@ -2862,7 +3224,7 @@ function sensitive(name) {
2862
3224
  return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
2863
3225
  }
2864
3226
  function redacted(data) {
2865
- if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
3227
+ if (data.includes(0) || !isUtf83(data)) return new Uint8Array(data);
2866
3228
  try {
2867
3229
  const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
2868
3230
  return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
@@ -2872,12 +3234,12 @@ function redacted(data) {
2872
3234
  }
2873
3235
  function skillFiles2(root, prefix) {
2874
3236
  const files2 = [];
2875
- for (const entry2 of readdirSync4(root, { withFileTypes: true })) {
2876
- const path = join9(root, entry2.name);
3237
+ for (const entry2 of readdirSync5(root, { withFileTypes: true })) {
3238
+ const path = join10(root, entry2.name);
2877
3239
  if (entry2.isDirectory()) {
2878
3240
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
2879
3241
  } else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
2880
- const bytes = redacted(readFileSync9(path));
3242
+ const bytes = redacted(readFileSync10(path));
2881
3243
  if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
2882
3244
  }
2883
3245
  }
@@ -2888,8 +3250,8 @@ function readLocalSkills(agents) {
2888
3250
  const ignored = [];
2889
3251
  for (const agent2 of agents) {
2890
3252
  if (!existsSync7(agent2.skillsDir)) continue;
2891
- for (const entry2 of readdirSync4(agent2.skillsDir, { withFileTypes: true })) {
2892
- const dir = join9(agent2.skillsDir, entry2.name);
3253
+ for (const entry2 of readdirSync5(agent2.skillsDir, { withFileTypes: true })) {
3254
+ const dir = join10(agent2.skillsDir, entry2.name);
2893
3255
  let isDir2 = entry2.isDirectory();
2894
3256
  if (entry2.isSymbolicLink()) {
2895
3257
  try {
@@ -2898,8 +3260,8 @@ function readLocalSkills(agents) {
2898
3260
  isDir2 = false;
2899
3261
  }
2900
3262
  }
2901
- if (!isDir2 || !existsSync7(join9(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
2902
- const name = frontmatterName(readFileSync9(join9(dir, "SKILL.md"), "utf8"));
3263
+ if (!isDir2 || !existsSync7(join10(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3264
+ const name = frontmatterName(readFileSync10(join10(dir, "SKILL.md"), "utf8"));
2903
3265
  if (!name) {
2904
3266
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
2905
3267
  continue;
@@ -2927,11 +3289,11 @@ function inlineImports(content, from, home, depth, seen) {
2927
3289
  }
2928
3290
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
2929
3291
  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);
3292
+ const path = target.startsWith("~/") ? join10(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
2931
3293
  if (seen.includes(path)) return "";
2932
3294
  let imported;
2933
3295
  try {
2934
- imported = readFileSync9(path, "utf8");
3296
+ imported = readFileSync10(path, "utf8");
2935
3297
  } catch {
2936
3298
  return line;
2937
3299
  }
@@ -2944,10 +3306,10 @@ function readGlobalInstructions(agents, home = homedir6()) {
2944
3306
  for (const agent2 of agents) {
2945
3307
  const file = INSTRUCTION_FILES2[agent2.id];
2946
3308
  if (!file) continue;
2947
- const path = join9(agent2.root, file);
3309
+ const path = join10(agent2.root, file);
2948
3310
  let raw;
2949
3311
  try {
2950
- raw = readFileSync9(path, "utf8");
3312
+ raw = readFileSync10(path, "utf8");
2951
3313
  } catch {
2952
3314
  continue;
2953
3315
  }
@@ -2966,7 +3328,7 @@ ${item.content.trim()}`).join("\n\n");
2966
3328
  }
2967
3329
  function readJson3(path) {
2968
3330
  try {
2969
- return JSON.parse(readFileSync9(path, "utf8"));
3331
+ return JSON.parse(readFileSync10(path, "utf8"));
2970
3332
  } catch {
2971
3333
  return void 0;
2972
3334
  }
@@ -2980,10 +3342,10 @@ function bearerOf(def) {
2980
3342
  return match?.[1] ?? null;
2981
3343
  }
2982
3344
  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 ?? {};
3345
+ if (agent2.id === "claude_code") return readJson3(join10(home, ".claude.json"))?.mcpServers ?? {};
3346
+ if (agent2.id === "cursor") return readJson3(join10(agent2.root, "mcp.json"))?.mcpServers ?? {};
2985
3347
  try {
2986
- return parseToml3(readFileSync9(join9(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3348
+ return parseToml3(readFileSync10(join10(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
2987
3349
  } catch {
2988
3350
  return {};
2989
3351
  }
@@ -3004,9 +3366,9 @@ function findLocalSecrets(agents, home = homedir6()) {
3004
3366
 
3005
3367
  // src/subscriptions.ts
3006
3368
  import { spawn as spawn3 } from "child_process";
3007
- import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
3369
+ import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
3008
3370
  import { homedir as homedir7 } from "os";
3009
- import { join as join10 } from "path";
3371
+ import { join as join11 } from "path";
3010
3372
  import { createInterface as createInterface2 } from "readline";
3011
3373
  var ALIASES = {
3012
3374
  claude: "anthropic",
@@ -3102,11 +3464,11 @@ async function connectClaude() {
3102
3464
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
3103
3465
  }
3104
3466
  function codexSignIn(env = process.env, home = homedir7()) {
3105
- const path = join10(env.CODEX_HOME || join10(home, ".codex"), "auth.json");
3467
+ const path = join11(env.CODEX_HOME || join11(home, ".codex"), "auth.json");
3106
3468
  if (!existsSync8(path)) {
3107
3469
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
3108
3470
  }
3109
- const raw = readFileSync10(path, "utf8");
3471
+ const raw = readFileSync11(path, "utf8");
3110
3472
  try {
3111
3473
  const parsed = JSON.parse(raw);
3112
3474
  return { path, raw, account: parsed.tokens?.account_id };
@@ -3115,11 +3477,11 @@ function codexSignIn(env = process.env, home = homedir7()) {
3115
3477
  }
3116
3478
  }
3117
3479
  function cursorSignIn(env = process.env, home = homedir7()) {
3118
- const path = join10(env.XDG_CONFIG_HOME || join10(home, ".config"), "cursor", "auth.json");
3480
+ const path = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "cursor", "auth.json");
3119
3481
  if (!existsSync8(path)) {
3120
3482
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
3121
3483
  }
3122
- const raw = readFileSync10(path, "utf8");
3484
+ const raw = readFileSync11(path, "utf8");
3123
3485
  try {
3124
3486
  const parsed = JSON.parse(raw);
3125
3487
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -3278,11 +3640,11 @@ function planProviders(names2) {
3278
3640
  }));
3279
3641
  }
3280
3642
  function reportPath() {
3281
- return join11(dirname6(configPath()), "setup.json");
3643
+ return join12(dirname6(configPath()), "setup.json");
3282
3644
  }
3283
3645
  function loadReport() {
3284
3646
  try {
3285
- const saved = JSON.parse(readFileSync11(reportPath(), "utf8"));
3647
+ const saved = JSON.parse(readFileSync12(reportPath(), "utf8"));
3286
3648
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
3287
3649
  } catch {
3288
3650
  return { agents: [], plans: [] };
@@ -3645,17 +4007,17 @@ function renderArtifact(artifact2) {
3645
4007
  }
3646
4008
 
3647
4009
  // 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";
4010
+ import { readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
4011
+ import { basename as basename7, join as join13, relative as relative4, resolve as resolve7 } from "path";
3650
4012
  function skillBundle(dir) {
3651
4013
  const root = resolve7(dir);
3652
4014
  const top = basename7(root);
3653
4015
  const files2 = [];
3654
4016
  const walk = (path) => {
3655
- for (const name of readdirSync5(path).sort()) {
3656
- const full = join12(path, name);
4017
+ for (const name of readdirSync6(path).sort()) {
4018
+ const full = join13(path, name);
3657
4019
  if (statSync8(full).isDirectory()) walk(full);
3658
- else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync12(full) });
4020
+ else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync13(full) });
3659
4021
  }
3660
4022
  };
3661
4023
  walk(root);
@@ -3908,11 +4270,11 @@ function confirmPrompt(question) {
3908
4270
  }
3909
4271
 
3910
4272
  // 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";
4273
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
4274
+ import { dirname as dirname7, join as join14, resolve as resolve8 } from "path";
3913
4275
  var HARNESS_FILE = "harness.json";
3914
4276
  function harnessFilePath(dir) {
3915
- return join13(resolve8(dir), HARNESS_FILE);
4277
+ return join14(resolve8(dir), HARNESS_FILE);
3916
4278
  }
3917
4279
  function readHarnessFile(dir) {
3918
4280
  const path = harnessFilePath(dir);
@@ -3920,7 +4282,7 @@ function readHarnessFile(dir) {
3920
4282
  throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
3921
4283
  }
3922
4284
  try {
3923
- return JSON.parse(readFileSync13(path, "utf8"));
4285
+ return JSON.parse(readFileSync14(path, "utf8"));
3924
4286
  } catch (err) {
3925
4287
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
3926
4288
  }
@@ -4621,22 +4983,33 @@ session.command("shared").description("sessions other people shared with you to
4621
4983
  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
4984
  })
4623
4985
  );
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(
4986
+ 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(
4987
+ run(async (ref, model, opts) => {
4988
+ const id = await resolveSessionRef(ref);
4989
+ const change = engineChange(model, opts.thinking);
4990
+ const { session: s } = change ? await api("PATCH", `/sessions/${encodeURIComponent(id)}`, change) : await api("GET", `/sessions/${encodeURIComponent(id)}`);
4991
+ console.log(`${id}: ${s.llm}${s.reasoningEffort ? ` \xB7 thinking ${s.reasoningEffort}` : ""}${change ? " (from the next turn)" : ""}`);
4992
+ })
4993
+ );
4994
+ 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
4995
  run(async (ref, targetAgent, opts) => {
4626
4996
  const id = await resolveSessionRef(ref);
4997
+ const change = engineChange(opts.model, opts.thinking);
4998
+ if (change && opts.wait === false) throw new ApiError(400, "invalid_request", "--model and --thinking need the handoff to finish; drop --no-wait");
4627
4999
  const done = await handoff(id, targetAgent, {
4628
5000
  fromMessage: opts.fromMessage,
4629
5001
  wait: opts.wait !== false,
4630
5002
  onStatus: (h) => console.error(`${h.id}: ${h.status}`)
4631
5003
  });
4632
5004
  if (done.status === "failed") throw new ApiError(422, done.error?.code ?? "handoff_failed", done.error?.message ?? "the handoff failed");
5005
+ if (done.target_session && change) await api("PATCH", `/sessions/${encodeURIComponent(done.target_session)}`, change);
4633
5006
  if (done.target_session) console.log(`${done.target_session} (${done.resolved_mode === "native_fork" ? "forked" : "from a written summary"})`);
4634
5007
  })
4635
5008
  );
4636
5009
  var sessionWorkspace = session.command("workspace").description("the session's workspace: list and read files, see changes, take a patch, copy a folder in");
4637
5010
  sessionWorkspace.command("ls <ref> [path]").description("list a directory (default: the workspace root)").option("--hidden", "include dotfiles").action(
4638
5011
  run(async (ref, path, opts) => {
4639
- const listing = await listFiles(await resolveSessionRef(ref), path, Boolean(opts.hidden));
5012
+ const listing = await listFiles2(await resolveSessionRef(ref), path, Boolean(opts.hidden));
4640
5013
  for (const e of listing.entries) console.log(entryLine(e));
4641
5014
  if (listing.truncated) console.error(`(only the first ${listing.entries.length} entries)`);
4642
5015
  })
@@ -4909,7 +5282,7 @@ function formatRelative(msAgo) {
4909
5282
  if (hours < 48) return `${hours}h ago`;
4910
5283
  return `${Math.round(hours / 24)}d ago`;
4911
5284
  }
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(
5285
+ 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
5286
  run(
4914
5287
  async (opts) => {
4915
5288
  if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
@@ -5017,6 +5390,7 @@ Importing ${label} \u2026
5017
5390
  importKey: opts.key,
5018
5391
  grantTraceId: opts.trace,
5019
5392
  files: opts.files,
5393
+ environment: opts.environment,
5020
5394
  onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
5021
5395
  `)
5022
5396
  });
@@ -5036,11 +5410,14 @@ Importing ${label} \u2026
5036
5410
  skills: result.setup.skills,
5037
5411
  missingSkills: result.setup.missingSkills,
5038
5412
  mcpServers: result.setup.mcpServers,
5413
+ environment: result.environment,
5039
5414
  continueUrl: result.url
5040
5415
  }));
5041
5416
  } else {
5042
5417
  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
5418
  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)` : ""}.`);
5419
+ 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` : ""}.`);
5420
+ else if (result.environment) console.log(` The working folder did not upload (${result.environment.error}); the conversation and the files it touched did.`);
5044
5421
  console.log(` \u2713 Imported. Continue here:
5045
5422
  ${result.url}
5046
5423
  `);
@@ -5106,7 +5483,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
5106
5483
  );
5107
5484
  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
5485
  run(async (taskId, path, local) => {
5109
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync14(local), mediaTypeFor(local));
5486
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync15(local), mediaTypeFor(local));
5110
5487
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
5111
5488
  })
5112
5489
  );
@@ -5155,6 +5532,28 @@ skills.command("list").description("the workspace's stored skills").action(
5155
5532
  for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
5156
5533
  })
5157
5534
  );
5535
+ async function decideReflection(id, verb) {
5536
+ const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
5537
+ console.log(`${p.id} ${p.status}`);
5538
+ }
5539
+ var reflections = program.command("reflections").description("skill and lesson changes proposed after finished sessions; an owner or admin approves each one");
5540
+ 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(
5541
+ run(async (opts) => {
5542
+ const { reflection_proposals: list } = await api(
5543
+ "GET",
5544
+ `/reflection_proposals?status=${encodeURIComponent(opts.status)}`
5545
+ );
5546
+ if (opts.json) return void console.log(JSON.stringify(list, null, 2));
5547
+ if (!list.length) return void console.log(`no ${opts.status} proposals`);
5548
+ for (const p of list) {
5549
+ const target = p.kind === "save_lesson" ? p.path : p.kind === "create_skill" ? `new skill ${p.payload.name}` : `skill ${p.target_skill_id}`;
5550
+ console.log(`${p.id} ${p.kind} ${target} from ${p.session_id}
5551
+ ${p.payload.why ?? ""}`);
5552
+ }
5553
+ })
5554
+ );
5555
+ reflections.command("approve <id>").description("write the proposal through the skill or memory store").action(run(async (id) => decideReflection(id, "approve")));
5556
+ reflections.command("reject <id>").description("decline the proposal; nothing is written").action(run(async (id) => decideReflection(id, "reject")));
5158
5557
  var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
5159
5558
  artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
5160
5559
  run(async (opts) => {
@@ -5769,7 +6168,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
5769
6168
  }));
5770
6169
  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
6170
  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");
6171
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-H3AP64UZ.js");
5773
6172
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
5774
6173
  const scopes = parseContentScopes(opts.capture);
5775
6174
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -5804,7 +6203,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
5804
6203
  }));
5805
6204
  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
6205
  try {
5807
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-IOARVWJ6.js");
6206
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-H3AP64UZ.js");
5808
6207
  const chunks = [];
5809
6208
  for await (const chunk of process.stdin) chunks.push(chunk);
5810
6209
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -5817,14 +6216,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
5817
6216
  }
5818
6217
  });
5819
6218
  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");
6219
+ const { readState, flush, grantSender } = await import("./trace-H3AP64UZ.js");
5821
6220
  const state = readState();
5822
6221
  if (!state) throw new Error("no trace is running here");
5823
6222
  const outcome = await flush(grantSender(state), { attempts: 4 });
5824
6223
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
5825
6224
  }));
5826
6225
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
5827
- const { readState, spoolStatus } = await import("./trace-IOARVWJ6.js");
6226
+ const { readState, spoolStatus } = await import("./trace-H3AP64UZ.js");
5828
6227
  const state = readState();
5829
6228
  if (!state) {
5830
6229
  console.log("No trace is running here.");
@@ -5834,7 +6233,7 @@ traceCmd.command("status").description("the running trace and its spool").action
5834
6233
  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
6234
  }));
5836
6235
  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");
6236
+ const { readState } = await import("./trace-H3AP64UZ.js");
5838
6237
  const ref = id ?? readState()?.trace_id;
5839
6238
  if (!ref) throw new Error("name a trace, or start one here");
5840
6239
  const bundle = await api("GET", `/traces/${ref}`);
@@ -5861,7 +6260,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
5861
6260
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
5862
6261
  }));
5863
6262
  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");
6263
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-H3AP64UZ.js");
5865
6264
  const state = readState();
5866
6265
  if (!state) throw new Error("no trace is running here");
5867
6266
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -5871,7 +6270,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
5871
6270
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
5872
6271
  }));
5873
6272
  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");
6273
+ const lib = await import("./trace-library-LGPKAQTY.js");
5875
6274
  if (opts.hook) {
5876
6275
  try {
5877
6276
  let raw = payload ?? "";
@@ -5919,10 +6318,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5919
6318
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
5920
6319
  }
5921
6320
  if (opts.installSchedule) {
5922
- const { join: join14 } = await import("path");
6321
+ const { join: join15 } = await import("path");
5923
6322
  const { homedir: homedir8 } = await import("os");
5924
6323
  const { execFileSync } = await import("child_process");
5925
- const log = join14(homedir8(), ".config", "sky", "trace-sync.log");
6324
+ const log = join15(homedir8(), ".config", "sky", "trace-sync.log");
5926
6325
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
5927
6326
  try {
5928
6327
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -5932,11 +6331,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5932
6331
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
5933
6332
  }
5934
6333
  if (opts.status) {
5935
- const { readFileSync: readFileSync15 } = await import("fs");
5936
- const { join: join14 } = await import("path");
6334
+ const { readFileSync: readFileSync16 } = await import("fs");
6335
+ const { join: join15 } = await import("path");
5937
6336
  let state = { sessions: {} };
5938
6337
  try {
5939
- state = JSON.parse(readFileSync15(join14(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
6338
+ state = JSON.parse(readFileSync16(join15(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
5940
6339
  } catch {
5941
6340
  }
5942
6341
  const counts = /* @__PURE__ */ new Map();
@@ -5969,7 +6368,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
5969
6368
  })();
5970
6369
  });
5971
6370
  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");
6371
+ const { libraryRequest } = await import("./trace-library-LGPKAQTY.js");
5973
6372
  const workspace2 = opts.workspace ?? currentUniverse();
5974
6373
  if (!workspace2) throw new Error("name a workspace with --workspace");
5975
6374
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -5986,7 +6385,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
5986
6385
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
5987
6386
  }));
5988
6387
  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");
6388
+ const { libraryRequest } = await import("./trace-library-LGPKAQTY.js");
5990
6389
  const workspace2 = opts.workspace ?? currentUniverse();
5991
6390
  if (!workspace2) throw new Error("name a workspace with --workspace");
5992
6391
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);