@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/{chunk-MQECNHD2.js → chunk-FU43NICZ.js} +2 -2
- package/dist/{chunk-5JHQH7WH.js → chunk-H7SWXCH7.js} +1 -1
- package/dist/{chunk-RHRREQMF.js → chunk-ZJVFWFOY.js} +13 -0
- package/dist/index.js +523 -124
- package/dist/products.js +1 -1
- package/dist/{src-KLP774OA.js → src-TJJKOFXZ.js} +1 -1
- package/dist/{trace-IOARVWJ6.js → trace-H3AP64UZ.js} +2 -2
- package/dist/{trace-library-K3NE26GH.js → trace-library-LGPKAQTY.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveSessionRef,
|
|
10
10
|
streamEvents,
|
|
11
11
|
uploadFile
|
|
12
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
934
|
-
import { createHash as
|
|
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 =
|
|
1767
|
+
const fd = openSync3(filePath, "r");
|
|
1448
1768
|
try {
|
|
1449
1769
|
const bytes = Buffer.alloc(256 * 1024);
|
|
1450
|
-
return bytes.subarray(0,
|
|
1770
|
+
return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
1451
1771
|
} finally {
|
|
1452
|
-
|
|
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 ??
|
|
1508
|
-
const projectsDir =
|
|
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
|
|
1513
|
-
const projectDir =
|
|
1832
|
+
for (const encoded of readdirSync3(projectsDir)) {
|
|
1833
|
+
const projectDir = join6(projectsDir, encoded);
|
|
1514
1834
|
let entries;
|
|
1515
1835
|
try {
|
|
1516
|
-
entries =
|
|
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 =
|
|
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 =
|
|
1854
|
+
const subagentDir = join6(projectDir, sessionId, "subagents");
|
|
1535
1855
|
if (existsSync5(subagentDir)) {
|
|
1536
1856
|
try {
|
|
1537
|
-
const subFiles =
|
|
1857
|
+
const subFiles = readdirSync3(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1538
1858
|
for (const f of subFiles) {
|
|
1539
|
-
subagentFiles.push(
|
|
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 =
|
|
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 =
|
|
1886
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1567
1887
|
} catch {
|
|
1568
1888
|
return;
|
|
1569
1889
|
}
|
|
1570
1890
|
for (const entry2 of entries) {
|
|
1571
|
-
const full =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
2087
|
-
import { createHash as
|
|
2088
|
-
import { readdirSync as
|
|
2089
|
-
import { basename as basename6, join as
|
|
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
|
|
2447
|
+
var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2093
2448
|
var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
|
|
2094
|
-
async function
|
|
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 =
|
|
2121
|
-
const top =
|
|
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(
|
|
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
|
|
2128
|
-
if (
|
|
2129
|
-
const full =
|
|
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 =
|
|
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 =
|
|
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
|
|
2568
|
+
import { join as join8 } from "path";
|
|
2207
2569
|
function localAgents(env = process.env, home = homedir5()) {
|
|
2208
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
2209
|
-
const codex = env.CODEX_HOME ||
|
|
2210
|
-
const 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:
|
|
2213
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
2214
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
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
|
|
2320
|
-
import { dirname as dirname6, join as
|
|
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
|
|
2686
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2325
2687
|
|
|
2326
2688
|
// src/skill.ts
|
|
2327
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
2328
|
-
import { join as
|
|
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
|
|
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 [...
|
|
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 [...
|
|
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(
|
|
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 =
|
|
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(
|
|
2786
|
-
writeFileSync4(
|
|
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(
|
|
3153
|
+
writeFileSync4(join9(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2792
3154
|
}
|
|
2793
|
-
const refsDir =
|
|
3155
|
+
const refsDir = join9(dir, REFERENCES_DIR);
|
|
2794
3156
|
mkdirSync2(refsDir, { recursive: true });
|
|
2795
|
-
writeFileSync4(
|
|
2796
|
-
writeFileSync4(
|
|
2797
|
-
writeFileSync4(
|
|
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) =>
|
|
3168
|
+
has: (command) => spawnSync4(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2807
3169
|
run: (command, args) => {
|
|
2808
|
-
const result =
|
|
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
|
|
2849
|
-
import { existsSync as existsSync7, readdirSync as
|
|
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
|
|
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) || !
|
|
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
|
|
2876
|
-
const path =
|
|
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(
|
|
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
|
|
2892
|
-
const dir =
|
|
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(
|
|
2902
|
-
const name = frontmatterName(
|
|
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("~/") ?
|
|
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 =
|
|
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 =
|
|
3309
|
+
const path = join10(agent2.root, file);
|
|
2948
3310
|
let raw;
|
|
2949
3311
|
try {
|
|
2950
|
-
raw =
|
|
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(
|
|
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(
|
|
2984
|
-
if (agent2.id === "cursor") return readJson3(
|
|
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(
|
|
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
|
|
3369
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
3008
3370
|
import { homedir as homedir7 } from "os";
|
|
3009
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3643
|
+
return join12(dirname6(configPath()), "setup.json");
|
|
3282
3644
|
}
|
|
3283
3645
|
function loadReport() {
|
|
3284
3646
|
try {
|
|
3285
|
-
const saved = JSON.parse(
|
|
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
|
|
3649
|
-
import { basename as basename7, join as
|
|
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
|
|
3656
|
-
const full =
|
|
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:
|
|
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
|
|
3912
|
-
import { dirname as dirname7, join as
|
|
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
|
|
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(
|
|
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("
|
|
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
|
|
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,
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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:
|
|
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 =
|
|
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:
|
|
5936
|
-
const { join:
|
|
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(
|
|
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-
|
|
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-
|
|
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);
|