@agentsky/sky 2.20.2 → 2.22.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-WTFBVTMI.js → chunk-EBH3KXSU.js} +2 -2
- package/dist/{chunk-HB7SPKQ5.js → chunk-XT3W75V3.js} +1 -1
- package/dist/{chunk-GHTAIKQ6.js → chunk-YMYQT6KR.js} +70 -34
- package/dist/index.js +917 -717
- package/dist/{src-QBXMNV2V.js → src-TGRDQ5XR.js} +1 -1
- package/dist/{trace-HTWXINXI.js → trace-PDIOL2TC.js} +2 -2
- package/dist/{trace-library-QR54Q4Z3.js → trace-library-TA3QUBHJ.js} +97 -11
- package/package.json +6 -4
- package/vendor/trace-collect/SOURCE.json +6 -0
- package/vendor/trace-collect/collect.mjs +75 -0
- package/vendor/trace-collect/identity.d.ts +8 -0
- package/vendor/trace-collect/identity.mjs +23 -0
- package/vendor/trace-collect/muse.mjs +87 -0
- package/vendor/trace-collect/normalize.d.ts +6 -0
- package/vendor/trace-collect/normalize.mjs +312 -0
- package/vendor/trace-collect/scrub.d.ts +2 -0
- package/vendor/trace-collect/scrub.mjs +186 -0
- package/vendor/trace-collect/sinks/agentsky.mjs +156 -0
- package/vendor/trace-collect/sources/_cline-family.mjs +182 -0
- package/vendor/trace-collect/sources/_project-root.mjs +40 -0
- package/vendor/trace-collect/sources/amp.mjs +18 -0
- package/vendor/trace-collect/sources/chatgpt.mjs +203 -0
- package/vendor/trace-collect/sources/claude-ai.mjs +94 -0
- package/vendor/trace-collect/sources/claude-code.mjs +4 -0
- package/vendor/trace-collect/sources/claude-desktop.mjs +87 -0
- package/vendor/trace-collect/sources/cline.mjs +14 -0
- package/vendor/trace-collect/sources/codex.mjs +4 -0
- package/vendor/trace-collect/sources/continue.mjs +117 -0
- package/vendor/trace-collect/sources/copilot-chat.mjs +145 -0
- package/vendor/trace-collect/sources/cursor-chat.mjs +274 -0
- package/vendor/trace-collect/sources/cursor.mjs +4 -0
- package/vendor/trace-collect/sources/deepseek.mjs +172 -0
- package/vendor/trace-collect/sources/dsh.mjs +145 -0
- package/vendor/trace-collect/sources/gemini-cli.mjs +113 -0
- package/vendor/trace-collect/sources/gemini.mjs +150 -0
- package/vendor/trace-collect/sources/goose.mjs +93 -0
- package/vendor/trace-collect/sources/grok-cli.mjs +110 -0
- package/vendor/trace-collect/sources/grok.mjs +83 -0
- package/vendor/trace-collect/sources/hermes.mjs +4 -0
- package/vendor/trace-collect/sources/index.mjs +119 -0
- package/vendor/trace-collect/sources/jan.mjs +134 -0
- package/vendor/trace-collect/sources/kilo-code.mjs +14 -0
- package/vendor/trace-collect/sources/kiro.mjs +77 -0
- package/vendor/trace-collect/sources/lm-studio.mjs +84 -0
- package/vendor/trace-collect/sources/muse.mjs +11 -0
- package/vendor/trace-collect/sources/opencode.mjs +109 -0
- package/vendor/trace-collect/sources/qwen-code.mjs +95 -0
- package/vendor/trace-collect/sources/roo-code.mjs +14 -0
- package/vendor/trace-collect/sources/zed.mjs +70 -0
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-EBH3KXSU.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-XT3W75V3.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -42,12 +42,12 @@ import {
|
|
|
42
42
|
skillRefSchema,
|
|
43
43
|
splitMcpServers,
|
|
44
44
|
supersededBy
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-YMYQT6KR.js";
|
|
46
46
|
import "./chunk-MLKGABMK.js";
|
|
47
47
|
|
|
48
48
|
// src/index.ts
|
|
49
|
-
import { existsSync as
|
|
50
|
-
import { basename as
|
|
49
|
+
import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
50
|
+
import { basename as basename9, resolve as resolve9 } from "path";
|
|
51
51
|
import { createInterface as createInterface6 } from "readline";
|
|
52
52
|
import { Command } from "commander";
|
|
53
53
|
|
|
@@ -1697,7 +1697,7 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
|
1697
1697
|
const scratch = mkdtempSync(join6(tmpdir(), "sky-environment-"));
|
|
1698
1698
|
const path = join6(scratch, "environment.tar.gz");
|
|
1699
1699
|
const leftOut = [];
|
|
1700
|
-
const
|
|
1700
|
+
const written2 = /* @__PURE__ */ new Set();
|
|
1701
1701
|
const hash = createHash2("sha256");
|
|
1702
1702
|
let bytes = 0;
|
|
1703
1703
|
let redacted2 = 0;
|
|
@@ -1764,9 +1764,9 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
|
1764
1764
|
}
|
|
1765
1765
|
await write(tarPadding(stat.size));
|
|
1766
1766
|
}
|
|
1767
|
-
|
|
1767
|
+
written2.add(rel);
|
|
1768
1768
|
}
|
|
1769
|
-
const layout = importLayout(resources, cwd, platform, { files:
|
|
1769
|
+
const layout = importLayout(resources, cwd, platform, { files: written2.size, paths: written2 });
|
|
1770
1770
|
for (const [entry2, data] of layout?.entries ?? []) {
|
|
1771
1771
|
await write(tarHeader(entry2, data.length));
|
|
1772
1772
|
await write(data);
|
|
@@ -1781,7 +1781,7 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
|
1781
1781
|
folder: layout?.folder ?? "imported-session",
|
|
1782
1782
|
bytes,
|
|
1783
1783
|
sha256: hash.digest("hex"),
|
|
1784
|
-
files:
|
|
1784
|
+
files: written2.size + (layout?.entries.size ?? 0),
|
|
1785
1785
|
redacted: redacted2,
|
|
1786
1786
|
leftOut,
|
|
1787
1787
|
dispose: () => rmSync2(scratch, { recursive: true, force: true })
|
|
@@ -2294,507 +2294,92 @@ import { createHash as createHash4, randomBytes } from "crypto";
|
|
|
2294
2294
|
import { createServer } from "http";
|
|
2295
2295
|
import { hostname } from "os";
|
|
2296
2296
|
import { spawn as spawn2 } from "child_process";
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2297
|
+
|
|
2298
|
+
// src/mcp-register.ts
|
|
2299
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
2300
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2301
|
+
import { homedir as homedir6 } from "os";
|
|
2302
|
+
import { join as join10 } from "path";
|
|
2303
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
2304
|
+
import { isMap, parseDocument } from "yaml";
|
|
2305
|
+
|
|
2306
|
+
// src/local-agents.ts
|
|
2307
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2308
|
+
import { homedir as homedir5 } from "os";
|
|
2309
|
+
import { join as join8 } from "path";
|
|
2310
|
+
function localAgents(env = process.env, home = homedir5()) {
|
|
2311
|
+
const claude = env.CLAUDE_CONFIG_DIR || join8(home, ".claude");
|
|
2312
|
+
const codex = env.CODEX_HOME || join8(home, ".codex");
|
|
2313
|
+
const cursor = join8(home, ".cursor");
|
|
2314
|
+
return [
|
|
2315
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join8(claude, "skills") },
|
|
2316
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join8(codex, "skills") },
|
|
2317
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join8(cursor, "skills") }
|
|
2318
|
+
];
|
|
2306
2319
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
const challenge = b64url(createHash4("sha256").update(verifier).digest());
|
|
2310
|
-
const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
|
|
2311
|
-
saveConfig({
|
|
2312
|
-
...loadConfig(),
|
|
2313
|
-
apiBase: apiBase(),
|
|
2314
|
-
token: result.token,
|
|
2315
|
-
email: result.user.email ?? void 0,
|
|
2316
|
-
currentUniverse: void 0
|
|
2317
|
-
// personal by default
|
|
2318
|
-
});
|
|
2319
|
-
console.log(
|
|
2320
|
-
`Logged in as ${result.user.email ?? "you"}${result.universe ? ` (universe: ${result.universe.slug})` : ""}`
|
|
2321
|
-
);
|
|
2320
|
+
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
2321
|
+
return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
|
|
2322
2322
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
return;
|
|
2331
|
-
}
|
|
2332
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2333
|
-
res.end("<p>Logged in \u2014 you can close this tab and return to your terminal.</p>");
|
|
2334
|
-
const got = url.searchParams.get("code");
|
|
2335
|
-
if (url.searchParams.get("state") !== state || !got) {
|
|
2336
|
-
reject(new Error("login callback state mismatch"));
|
|
2337
|
-
} else {
|
|
2338
|
-
resolve10(got);
|
|
2339
|
-
}
|
|
2340
|
-
server.close();
|
|
2341
|
-
});
|
|
2342
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2343
|
-
const address = server.address();
|
|
2344
|
-
const port = typeof address === "object" && address ? address.port : 0;
|
|
2345
|
-
const authorizeUrl = `${consoleBase()}/cli/authorize?state=${state}&port=${port}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
2346
|
-
console.log("Opening your browser to authorize this machine\u2026");
|
|
2347
|
-
console.log(`If it doesn't open, visit:
|
|
2348
|
-
${authorizeUrl}`);
|
|
2349
|
-
openBrowser2(authorizeUrl);
|
|
2350
|
-
});
|
|
2351
|
-
setTimeout(() => {
|
|
2352
|
-
server.close();
|
|
2353
|
-
reject(new Error("login timed out after 10 minutes"));
|
|
2354
|
-
}, 10 * 60 * 1e3).unref();
|
|
2323
|
+
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
2324
|
+
if (!ids?.length) return detectLocalAgents(env, home);
|
|
2325
|
+
const known = localAgents(env, home);
|
|
2326
|
+
return ids.map((id) => {
|
|
2327
|
+
const agent2 = known.find((candidate) => candidate.id === id);
|
|
2328
|
+
if (!agent2) throw new Error(`unknown agent ${id} \u2014 expected ${known.map((candidate) => candidate.id).join(" | ")}`);
|
|
2329
|
+
return agent2;
|
|
2355
2330
|
});
|
|
2356
|
-
return api(
|
|
2357
|
-
"POST",
|
|
2358
|
-
"/cli/token-exchange",
|
|
2359
|
-
{ code, verifier, clientName: `cli ${hostname()}` },
|
|
2360
|
-
{ auth: false }
|
|
2361
|
-
);
|
|
2362
|
-
}
|
|
2363
|
-
async function headlessLogin(verifier, challenge) {
|
|
2364
|
-
const request = b64url(randomBytes(24));
|
|
2365
|
-
const authorizeUrl = `${consoleBase()}/cli/authorize?request=${request}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
2366
|
-
console.log(`Visit this URL on any device to authorize:
|
|
2367
|
-
${authorizeUrl}
|
|
2368
|
-
`);
|
|
2369
|
-
process.stdout.write("Waiting for approval\u2026");
|
|
2370
|
-
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
2371
|
-
while (Date.now() < deadline) {
|
|
2372
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
2373
|
-
const res = await fetch(
|
|
2374
|
-
`${apiBase()}/cli/poll?request=${request}&verifier=${encodeURIComponent(verifier)}&clientName=${encodeURIComponent(`cli ${hostname()}`)}`,
|
|
2375
|
-
{ headers: { "user-agent": USER_AGENT } }
|
|
2376
|
-
);
|
|
2377
|
-
if (!res.ok) {
|
|
2378
|
-
const body2 = await res.json().catch(() => null);
|
|
2379
|
-
throw new Error(body2?.error?.message ?? `poll failed (${res.status})`);
|
|
2380
|
-
}
|
|
2381
|
-
const body = await res.json();
|
|
2382
|
-
if (body.status === "approved") {
|
|
2383
|
-
console.log(" approved.");
|
|
2384
|
-
return body;
|
|
2385
|
-
}
|
|
2386
|
-
process.stdout.write(".");
|
|
2387
|
-
}
|
|
2388
|
-
throw new Error("login timed out after 10 minutes");
|
|
2389
2331
|
}
|
|
2390
2332
|
|
|
2391
|
-
// src/
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
const page = await api("GET", `/connectors/apps${qs ? `?${qs}` : ""}`);
|
|
2401
|
-
apps.push(...page.apps);
|
|
2402
|
-
cursor = page.next_cursor;
|
|
2403
|
-
} while (cursor && apps.length < limit);
|
|
2404
|
-
return apps.slice(0, limit);
|
|
2405
|
-
}
|
|
2406
|
-
function connectionLine2(c) {
|
|
2407
|
-
return `${c.id} ${c.app_name} (${c.app}) ${c.label} since ${c.created_at.slice(0, 10)}`;
|
|
2333
|
+
// src/skill.ts
|
|
2334
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2335
|
+
import { join as join9 } from "path";
|
|
2336
|
+
|
|
2337
|
+
// src/catalog-file.ts
|
|
2338
|
+
var CATALOG_FILE = "catalog.md";
|
|
2339
|
+
var CATEGORY_ORDER = ["websites", "content", "video", "code", "research", "general"];
|
|
2340
|
+
function categoryOf(agent2) {
|
|
2341
|
+
return CATEGORY_ORDER.find((category) => agent2.categories.includes(category)) ?? agent2.categories[0] ?? "general";
|
|
2408
2342
|
}
|
|
2409
|
-
function
|
|
2410
|
-
const
|
|
2411
|
-
return
|
|
2343
|
+
function describe(value) {
|
|
2344
|
+
const parts = Object.entries(value).filter(([, item]) => item !== null && item !== void 0 && item !== false && item !== "").map(([key, item]) => item === true ? key : `${key}: ${typeof item === "string" ? item : JSON.stringify(item)}`);
|
|
2345
|
+
return parts.length ? parts.join(", ") : null;
|
|
2412
2346
|
}
|
|
2413
|
-
function
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
if (ok) resolve10(url.searchParams.get("account_id") ?? void 0);
|
|
2426
|
-
else reject(new ApiError(400, "connect_failed", "the app was not connected"));
|
|
2427
|
-
});
|
|
2428
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2429
|
-
const address = server.address();
|
|
2430
|
-
const port = typeof address === "object" && address ? address.port : 0;
|
|
2431
|
-
const base = `http://127.0.0.1:${port}`;
|
|
2432
|
-
start(`${base}/done`, `${base}/failed`).then(
|
|
2433
|
-
(authorizeUrl) => {
|
|
2434
|
-
console.log(`Authorize access in your browser:
|
|
2435
|
-
${authorizeUrl}`);
|
|
2436
|
-
if (opts.open !== false) openBrowser2(authorizeUrl);
|
|
2437
|
-
},
|
|
2438
|
-
(err) => {
|
|
2439
|
-
server.close();
|
|
2440
|
-
reject(err);
|
|
2441
|
-
}
|
|
2442
|
-
);
|
|
2443
|
-
});
|
|
2444
|
-
setTimeout(() => {
|
|
2445
|
-
server.close();
|
|
2446
|
-
reject(new ApiError(408, "connect_timeout", "nobody finished the authorization within 10 minutes"));
|
|
2447
|
-
}, opts.timeoutMs ?? 10 * 6e4).unref();
|
|
2448
|
-
});
|
|
2347
|
+
function entry(agent2) {
|
|
2348
|
+
const engine = agent2.engine_type ? ` \xB7 engine ${agent2.engine_type}` : "";
|
|
2349
|
+
const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
|
|
2350
|
+
if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
|
|
2351
|
+
if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
|
|
2352
|
+
const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
|
|
2353
|
+
if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
|
|
2354
|
+
const details = describe(rest);
|
|
2355
|
+
if (details) lines.push(`Details: ${details}`);
|
|
2356
|
+
const pricing = describe(agent2.pricing);
|
|
2357
|
+
if (pricing) lines.push(`Pricing: ${pricing}`);
|
|
2358
|
+
return lines.join("\n");
|
|
2449
2359
|
}
|
|
2450
|
-
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2360
|
+
function renderCatalog(agents, generatedAt) {
|
|
2361
|
+
const header2 = [
|
|
2362
|
+
"# AgentSky agent catalog",
|
|
2363
|
+
"",
|
|
2364
|
+
`Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
|
|
2365
|
+
"Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
|
|
2366
|
+
];
|
|
2367
|
+
if (!agents.length) return [...header2, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
|
|
2368
|
+
const own = agents.filter((agent2) => agent2.visibility === "workspace");
|
|
2369
|
+
const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
|
|
2370
|
+
const sections = [];
|
|
2371
|
+
if (own.length) sections.push(["## Your workspace's agents", ...own.map(entry)].join("\n\n"));
|
|
2372
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2373
|
+
for (const agent2 of shared) groups.set(categoryOf(agent2), [...groups.get(categoryOf(agent2)) ?? [], agent2]);
|
|
2374
|
+
const ordered = [...groups.keys()].sort((a, b) => {
|
|
2375
|
+
const rank = (category) => CATEGORY_ORDER.includes(category) ? CATEGORY_ORDER.indexOf(category) : CATEGORY_ORDER.length;
|
|
2376
|
+
return rank(a) - rank(b) || a.localeCompare(b);
|
|
2459
2377
|
});
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
function inboundLine(u) {
|
|
2466
|
-
if (!u) return "no inbound URL \u2014 make one with --create";
|
|
2467
|
-
return u.url ?? `inbound URL ${u.prefix}\u2026 made ${u.created_at.slice(0, 10)}${u.last_used_at ? `, last used ${u.last_used_at.slice(0, 16).replace("T", " ")}` : ", never used"}`;
|
|
2468
|
-
}
|
|
2469
|
-
function precheckLine(p) {
|
|
2470
|
-
if (!p) return "no precheck: every scheduled fire runs";
|
|
2471
|
-
const last = p.last_result ? `last check ${p.last_result}${p.last_error ? ` (${p.last_error.type}: ${p.last_error.message})` : ""}` : "not checked yet";
|
|
2472
|
-
return `fires only when ${p.url} changes \xB7 ${last}`;
|
|
2473
|
-
}
|
|
2474
|
-
function triggerLine(t, available = true) {
|
|
2475
|
-
if (!t) return "no chat trigger";
|
|
2476
|
-
const where = t.thread_id ? `thread ${t.thread_id} on ${t.connection_id}` : `any conversation on ${t.connection_id}`;
|
|
2477
|
-
const what = t.text_prefix ? `messages starting "${t.text_prefix}"` : "every message";
|
|
2478
|
-
return `${what} in ${where} start a run${available ? "" : " (chat messages are not offered to routines in this deployment)"}`;
|
|
2479
|
-
}
|
|
2480
|
-
function deliverLine(d) {
|
|
2481
|
-
return d ? `answers post to thread ${d.thread_id} on ${d.connection_id}` : "answers are posted nowhere";
|
|
2482
|
-
}
|
|
2483
|
-
function triggerBody(opts) {
|
|
2484
|
-
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
2485
|
-
return { connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {}, ...opts.prefix ? { text_prefix: opts.prefix } : {} };
|
|
2486
|
-
}
|
|
2487
|
-
function deliverBody(opts) {
|
|
2488
|
-
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
2489
|
-
return { type: "channel_thread", connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {} };
|
|
2490
|
-
}
|
|
2491
|
-
async function triggerRequest(id, resource, change) {
|
|
2492
|
-
const path = triggerPath(id, resource);
|
|
2493
|
-
if (change.remove) return api("DELETE", path);
|
|
2494
|
-
if (change.create) return api("POST", path);
|
|
2495
|
-
if (change.body) return api("PUT", path, change.body);
|
|
2496
|
-
return api("GET", path);
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
// src/session-workspace.ts
|
|
2500
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
2501
|
-
import { createHash as createHash5, randomUUID } from "crypto";
|
|
2502
|
-
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
2503
|
-
import { basename as basename6, join as join8, relative as relative3, resolve as resolve5 } from "path";
|
|
2504
|
-
var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
|
|
2505
|
-
var PART_SIZE = 8 * 1024 * 1024;
|
|
2506
|
-
var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2507
|
-
var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
|
|
2508
|
-
async function listFiles2(id, path, hidden) {
|
|
2509
|
-
const query = new URLSearchParams();
|
|
2510
|
-
if (path) query.set("path", path);
|
|
2511
|
-
if (hidden) query.set("hidden", "true");
|
|
2512
|
-
const qs = query.toString();
|
|
2513
|
-
return api("GET", `${workspacePath(id)}/files${qs ? `?${qs}` : ""}`);
|
|
2514
|
-
}
|
|
2515
|
-
function entryLine(e) {
|
|
2516
|
-
const size = e.type === "directory" ? "-" : String(e.size ?? 0);
|
|
2517
|
-
return `${e.type === "directory" ? "d" : "-"} ${size.padStart(10)} ${(e.mtime ?? "").slice(0, 16).replace("T", " ")} ${e.name}${e.type === "directory" ? "/" : ""}`;
|
|
2518
|
-
}
|
|
2519
|
-
async function readFile(id, path) {
|
|
2520
|
-
return (await apiBytes("GET", `${workspacePath(id)}/files/content?path=${encodeURIComponent(path)}`)).bytes;
|
|
2521
|
-
}
|
|
2522
|
-
function renderChanges(changes) {
|
|
2523
|
-
if (!changes.repos.length) return [`no git repository under ${changes.root}`];
|
|
2524
|
-
return changes.repos.flatMap((r) => [
|
|
2525
|
-
`${r.path === "." ? changes.root : r.path}: ${r.status ? "changed" : "clean"}`,
|
|
2526
|
-
...(r.stat ?? r.status ?? "").split("\n").filter(Boolean).map((line) => ` ${line}`)
|
|
2527
|
-
]);
|
|
2528
|
-
}
|
|
2529
|
-
async function workspacePatch(id, repo) {
|
|
2530
|
-
const { patch, ...meta } = await api("POST", `${workspacePath(id)}/patch`, { repo });
|
|
2531
|
-
return { meta, patch: Buffer.from(patch, "base64") };
|
|
2532
|
-
}
|
|
2533
|
-
function folderFiles(dir) {
|
|
2534
|
-
const git2 = spawnSync3("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
|
|
2535
|
-
const top = spawnSync3("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
|
|
2536
|
-
if (git2.status === 0 && top.status === 0) {
|
|
2537
|
-
return git2.stdout.split("\0").filter((path) => path && statSync6(join8(dir, path), { throwIfNoEntry: false })?.isFile());
|
|
2538
|
-
}
|
|
2539
|
-
const files2 = [];
|
|
2540
|
-
const walk = (at) => {
|
|
2541
|
-
for (const entry2 of readdirSync5(at, { withFileTypes: true })) {
|
|
2542
|
-
if (SKIPPED2.has(entry2.name)) continue;
|
|
2543
|
-
const full = join8(at, entry2.name);
|
|
2544
|
-
if (entry2.isDirectory()) walk(full);
|
|
2545
|
-
else if (entry2.isFile()) files2.push(relative3(dir, full));
|
|
2546
|
-
}
|
|
2547
|
-
};
|
|
2548
|
-
walk(dir);
|
|
2549
|
-
return files2;
|
|
2550
|
-
}
|
|
2551
|
-
function folderArchive(dir, files2) {
|
|
2552
|
-
const tar = spawnSync3("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
|
|
2553
|
-
input: files2.join("\n"),
|
|
2554
|
-
env: { ...process.env, COPYFILE_DISABLE: "1" },
|
|
2555
|
-
maxBuffer: 2 * 1024 * 1024 * 1024
|
|
2556
|
-
});
|
|
2557
|
-
if (tar.status !== 0) throw new ApiError(400, "archive_failed", `tar failed: ${tar.stderr.toString().trim()}`);
|
|
2558
|
-
return tar.stdout;
|
|
2559
|
-
}
|
|
2560
|
-
async function importFolder(id, dir, opts = {}) {
|
|
2561
|
-
const root = resolve5(dir);
|
|
2562
|
-
const name = opts.name ?? basename6(root);
|
|
2563
|
-
const archive = opts.archive ?? folderArchive(root, folderFiles(root));
|
|
2564
|
-
if (archive.length <= SINGLE_IMPORT_MAX) {
|
|
2565
|
-
const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
|
|
2566
|
-
return res.json();
|
|
2567
|
-
}
|
|
2568
|
-
const sha256 = createHash5("sha256").update(archive).digest("hex");
|
|
2569
|
-
let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
|
|
2570
|
-
const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
|
|
2571
|
-
for (let part = 0; part < upload.parts; part += 1) {
|
|
2572
|
-
if (upload.received.includes(part)) continue;
|
|
2573
|
-
const bytes = archive.subarray(part * upload.part_size, (part + 1) * upload.part_size);
|
|
2574
|
-
await apiBytes("PUT", `${uploadPath}/parts/${part}`, { bytes, mediaType: "application/octet-stream" });
|
|
2575
|
-
}
|
|
2576
|
-
upload = await api("POST", `${uploadPath}/complete`);
|
|
2577
|
-
while (upload.state !== "imported" && upload.state !== "failed") {
|
|
2578
|
-
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
2579
|
-
upload = await api("GET", uploadPath);
|
|
2580
|
-
}
|
|
2581
|
-
if (upload.state === "failed") throw new ApiError(422, upload.error?.code ?? "import_failed", upload.error?.message ?? "the import failed");
|
|
2582
|
-
return { path: upload.path ?? name, files: upload.files ?? 0 };
|
|
2583
|
-
}
|
|
2584
|
-
function renderAccess(a) {
|
|
2585
|
-
const lines = [
|
|
2586
|
-
`${a.session_id}${a.title ? ` \u2014 ${a.title}` : ""}`,
|
|
2587
|
-
` ${a.visibility === "private" ? "private to its owner" : `visible to ${a.workspace.name} (${a.workspace.slug})`} \xB7 you are its ${a.access}`,
|
|
2588
|
-
` owner: ${a.owner.name ?? a.owner.email ?? "unknown"}`
|
|
2589
|
-
];
|
|
2590
|
-
if (a.viewers) lines.push(a.viewers.length ? ` viewers: ${a.viewers.map((v) => v.email).join(", ")}` : " viewers: none");
|
|
2591
|
-
return lines;
|
|
2592
|
-
}
|
|
2593
|
-
function accessPatch(opts) {
|
|
2594
|
-
if (opts.private && opts.members) throw new ApiError(400, "invalid_request", "pass --private or --members, not both");
|
|
2595
|
-
return {
|
|
2596
|
-
...opts.private ? { visibility: "private" } : opts.members ? { visibility: "workspace" } : {},
|
|
2597
|
-
...opts.addViewer?.length ? { add_viewers: opts.addViewer } : {},
|
|
2598
|
-
...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
|
|
2599
|
-
};
|
|
2600
|
-
}
|
|
2601
|
-
function engineChange(model, thinking) {
|
|
2602
|
-
if (!model && !thinking) return null;
|
|
2603
|
-
return {
|
|
2604
|
-
...model ? { llm: model } : {},
|
|
2605
|
-
...thinking ? { reasoningEffort: thinking === "default" ? null : thinking } : {}
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
2609
|
-
async function handoff(id, targetAgent, opts = {}) {
|
|
2610
|
-
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
|
|
2611
|
-
target_agent: targetAgent,
|
|
2612
|
-
...opts.fromMessage ? { last_message_id: opts.fromMessage } : {}
|
|
2613
|
-
}, { idempotencyKey: opts.idempotencyKey ?? randomUUID() });
|
|
2614
|
-
opts.onStatus?.(h);
|
|
2615
|
-
while (opts.wait && !TERMINAL.has(h.status)) {
|
|
2616
|
-
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
2617
|
-
const next = (await api("GET", `/session-handoffs/${encodeURIComponent(h.id)}`)).handoff;
|
|
2618
|
-
if (next.status !== h.status) opts.onStatus?.(next);
|
|
2619
|
-
h = next;
|
|
2620
|
-
}
|
|
2621
|
-
return h;
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
// src/local-agents.ts
|
|
2625
|
-
import { existsSync as existsSync6 } from "fs";
|
|
2626
|
-
import { homedir as homedir5 } from "os";
|
|
2627
|
-
import { join as join9 } from "path";
|
|
2628
|
-
function localAgents(env = process.env, home = homedir5()) {
|
|
2629
|
-
const claude = env.CLAUDE_CONFIG_DIR || join9(home, ".claude");
|
|
2630
|
-
const codex = env.CODEX_HOME || join9(home, ".codex");
|
|
2631
|
-
const cursor = join9(home, ".cursor");
|
|
2632
|
-
return [
|
|
2633
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join9(claude, "skills") },
|
|
2634
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir: join9(codex, "skills") },
|
|
2635
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join9(cursor, "skills") }
|
|
2636
|
-
];
|
|
2637
|
-
}
|
|
2638
|
-
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
2639
|
-
return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
|
|
2640
|
-
}
|
|
2641
|
-
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
2642
|
-
if (!ids?.length) return detectLocalAgents(env, home);
|
|
2643
|
-
const known = localAgents(env, home);
|
|
2644
|
-
return ids.map((id) => {
|
|
2645
|
-
const agent2 = known.find((candidate) => candidate.id === id);
|
|
2646
|
-
if (!agent2) throw new Error(`unknown agent ${id} \u2014 expected ${known.map((candidate) => candidate.id).join(" | ")}`);
|
|
2647
|
-
return agent2;
|
|
2648
|
-
});
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
// src/render.ts
|
|
2652
|
-
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2653
|
-
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
2654
|
-
function clip(value, max = 120) {
|
|
2655
|
-
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
2656
|
-
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
2657
|
-
}
|
|
2658
|
-
function renderEvent(event, data) {
|
|
2659
|
-
const part = data.part;
|
|
2660
|
-
switch (event) {
|
|
2661
|
-
case "agent.message":
|
|
2662
|
-
console.log(String(data.text ?? ""));
|
|
2663
|
-
break;
|
|
2664
|
-
case "agent.reasoning":
|
|
2665
|
-
console.log(dim(`\xB7 ${clip(part?.text ?? "")}`));
|
|
2666
|
-
break;
|
|
2667
|
-
case "agent.tool_use":
|
|
2668
|
-
console.log(dim(`\u2192 ${String(part?.tool_name ?? "tool")}(${clip(part?.args ?? {}, 80)})`));
|
|
2669
|
-
break;
|
|
2670
|
-
case "agent.tool_result":
|
|
2671
|
-
console.log(dim(`\u2190 ${String(part?.tool_name ?? "tool")} ${String(part?.status ?? "")}`));
|
|
2672
|
-
break;
|
|
2673
|
-
case "agent.status":
|
|
2674
|
-
console.log(dim(`\u2026 ${String(part?.level ?? "")} ${String(part?.text ?? "")}`.trim()));
|
|
2675
|
-
break;
|
|
2676
|
-
case "user.interrupt":
|
|
2677
|
-
console.log(dim("\u23F9 interrupting"));
|
|
2678
|
-
break;
|
|
2679
|
-
case "session.error": {
|
|
2680
|
-
const error = data.error;
|
|
2681
|
-
const message2 = String(error?.message ?? error?.type ?? "unknown");
|
|
2682
|
-
if (error?.retry_status === "retrying") console.error(dim(`retrying: ${message2}`));
|
|
2683
|
-
else console.error(red(`error: ${message2}`));
|
|
2684
|
-
break;
|
|
2685
|
-
}
|
|
2686
|
-
case "session.status_terminated":
|
|
2687
|
-
console.log(dim("session terminated"));
|
|
2688
|
-
break;
|
|
2689
|
-
default:
|
|
2690
|
-
break;
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
// src/session-read.ts
|
|
2695
|
-
var TURN_TYPES = ["user.message", "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
|
|
2696
|
-
function latestTurn(events) {
|
|
2697
|
-
let start = 0;
|
|
2698
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2699
|
-
if (events[index].type === "user.message") {
|
|
2700
|
-
start = index + 1;
|
|
2701
|
-
break;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
const reading = { finished: false, messages: [], errors: [] };
|
|
2705
|
-
for (const event of events.slice(start)) {
|
|
2706
|
-
if (event.type === "agent.message" && event.text?.trim()) reading.messages.push(event.text);
|
|
2707
|
-
else if (event.type === "session.error" && event.error?.retry_status !== "retrying") {
|
|
2708
|
-
reading.errors.push(event.error?.message ?? event.error?.type ?? "unknown error");
|
|
2709
|
-
} else if (event.type === "session.status_terminated") reading.finished = true;
|
|
2710
|
-
else if (event.type === "session.status_idle" && event.stop_reason?.type !== "requires_action") reading.finished = true;
|
|
2711
|
-
}
|
|
2712
|
-
return reading;
|
|
2713
|
-
}
|
|
2714
|
-
async function history2(sessionId) {
|
|
2715
|
-
const events = [];
|
|
2716
|
-
let cursor = null;
|
|
2717
|
-
for (; ; ) {
|
|
2718
|
-
const query = new URLSearchParams({ limit: "500" });
|
|
2719
|
-
for (const type of TURN_TYPES) query.append("types[]", type);
|
|
2720
|
-
if (cursor) query.set("cursor", cursor);
|
|
2721
|
-
const page = await api(
|
|
2722
|
-
"GET",
|
|
2723
|
-
`/sessions/${encodeURIComponent(sessionId)}/events?${query}`
|
|
2724
|
-
);
|
|
2725
|
-
events.push(...page.events);
|
|
2726
|
-
if (!page.hasMore || !page.cursor) return events;
|
|
2727
|
-
cursor = page.cursor;
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
async function readTurn(sessionId, opts = {}) {
|
|
2731
|
-
const deadline = Date.now() + (opts.timeoutSeconds ?? 600) * 1e3;
|
|
2732
|
-
for (; ; ) {
|
|
2733
|
-
const reading = latestTurn(await history2(sessionId));
|
|
2734
|
-
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
2735
|
-
await new Promise((resolve10) => setTimeout(resolve10, opts.intervalMs ?? 3e3));
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
// src/setup.ts
|
|
2740
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
2741
|
-
import { dirname as dirname6, join as join13 } from "path";
|
|
2742
|
-
import { createInterface as createInterface3 } from "readline";
|
|
2743
|
-
|
|
2744
|
-
// src/mcp-register.ts
|
|
2745
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
2746
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
2747
|
-
|
|
2748
|
-
// src/skill.ts
|
|
2749
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2750
|
-
import { join as join10 } from "path";
|
|
2751
|
-
|
|
2752
|
-
// src/catalog-file.ts
|
|
2753
|
-
var CATALOG_FILE = "catalog.md";
|
|
2754
|
-
var CATEGORY_ORDER = ["websites", "content", "video", "code", "research", "general"];
|
|
2755
|
-
function categoryOf(agent2) {
|
|
2756
|
-
return CATEGORY_ORDER.find((category) => agent2.categories.includes(category)) ?? agent2.categories[0] ?? "general";
|
|
2757
|
-
}
|
|
2758
|
-
function describe(value) {
|
|
2759
|
-
const parts = Object.entries(value).filter(([, item]) => item !== null && item !== void 0 && item !== false && item !== "").map(([key, item]) => item === true ? key : `${key}: ${typeof item === "string" ? item : JSON.stringify(item)}`);
|
|
2760
|
-
return parts.length ? parts.join(", ") : null;
|
|
2761
|
-
}
|
|
2762
|
-
function entry(agent2) {
|
|
2763
|
-
const engine = agent2.engine_type ? ` \xB7 engine ${agent2.engine_type}` : "";
|
|
2764
|
-
const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
|
|
2765
|
-
if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
|
|
2766
|
-
if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
|
|
2767
|
-
const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
|
|
2768
|
-
if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
|
|
2769
|
-
const details = describe(rest);
|
|
2770
|
-
if (details) lines.push(`Details: ${details}`);
|
|
2771
|
-
const pricing = describe(agent2.pricing);
|
|
2772
|
-
if (pricing) lines.push(`Pricing: ${pricing}`);
|
|
2773
|
-
return lines.join("\n");
|
|
2774
|
-
}
|
|
2775
|
-
function renderCatalog(agents, generatedAt) {
|
|
2776
|
-
const header2 = [
|
|
2777
|
-
"# AgentSky agent catalog",
|
|
2778
|
-
"",
|
|
2779
|
-
`Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
|
|
2780
|
-
"Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
|
|
2781
|
-
];
|
|
2782
|
-
if (!agents.length) return [...header2, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
|
|
2783
|
-
const own = agents.filter((agent2) => agent2.visibility === "workspace");
|
|
2784
|
-
const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
|
|
2785
|
-
const sections = [];
|
|
2786
|
-
if (own.length) sections.push(["## Your workspace's agents", ...own.map(entry)].join("\n\n"));
|
|
2787
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2788
|
-
for (const agent2 of shared) groups.set(categoryOf(agent2), [...groups.get(categoryOf(agent2)) ?? [], agent2]);
|
|
2789
|
-
const ordered = [...groups.keys()].sort((a, b) => {
|
|
2790
|
-
const rank = (category) => CATEGORY_ORDER.includes(category) ? CATEGORY_ORDER.indexOf(category) : CATEGORY_ORDER.length;
|
|
2791
|
-
return rank(a) - rank(b) || a.localeCompare(b);
|
|
2792
|
-
});
|
|
2793
|
-
for (const category of ordered) {
|
|
2794
|
-
const title = category.charAt(0).toUpperCase() + category.slice(1);
|
|
2795
|
-
sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
|
|
2796
|
-
}
|
|
2797
|
-
return [...header2, "", sections.join("\n\n"), ""].join("\n");
|
|
2378
|
+
for (const category of ordered) {
|
|
2379
|
+
const title = category.charAt(0).toUpperCase() + category.slice(1);
|
|
2380
|
+
sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
|
|
2381
|
+
}
|
|
2382
|
+
return [...header2, "", sections.join("\n\n"), ""].join("\n");
|
|
2798
2383
|
}
|
|
2799
2384
|
|
|
2800
2385
|
// src/skill.ts
|
|
@@ -3172,158 +2757,738 @@ Check one real session:
|
|
|
3172
2757
|
- A file id from another customer's workspace is refused at session create.
|
|
3173
2758
|
`;
|
|
3174
2759
|
}
|
|
3175
|
-
function workspacesReferenceDoc() {
|
|
3176
|
-
return `# Workspaces in AgentSky
|
|
3177
|
-
|
|
3178
|
-
A workspace (also called a universe) groups agents and sessions. Every API call
|
|
3179
|
-
and MCP tool accepts an optional workspace slug.
|
|
3180
|
-
|
|
3181
|
-
## How to choose
|
|
3182
|
-
|
|
3183
|
-
1. The person named one \u2192 use it.
|
|
3184
|
-
2. Check the git root for \`.agentsky.json\` with a \`"workspace"\` key \u2192 use that.
|
|
3185
|
-
3. Neither \u2192 use the account default (personal workspace). Never ask.
|
|
3186
|
-
|
|
3187
|
-
## Applying it
|
|
2760
|
+
function workspacesReferenceDoc() {
|
|
2761
|
+
return `# Workspaces in AgentSky
|
|
2762
|
+
|
|
2763
|
+
A workspace (also called a universe) groups agents and sessions. Every API call
|
|
2764
|
+
and MCP tool accepts an optional workspace slug.
|
|
2765
|
+
|
|
2766
|
+
## How to choose
|
|
2767
|
+
|
|
2768
|
+
1. The person named one \u2192 use it.
|
|
2769
|
+
2. Check the git root for \`.agentsky.json\` with a \`"workspace"\` key \u2192 use that.
|
|
2770
|
+
3. Neither \u2192 use the account default (personal workspace). Never ask.
|
|
2771
|
+
|
|
2772
|
+
## Applying it
|
|
2773
|
+
|
|
2774
|
+
- MCP tools: pass \`workspace: "<slug>"\` on tools that accept it.
|
|
2775
|
+
- sky CLI: \`--workspace <slug>\` on any command, or set \`SKY_WORKSPACE=<slug>\`.
|
|
2776
|
+
- Tools that take a \`task_id\` or \`session_id\` inherit that task's workspace
|
|
2777
|
+
automatically; one task never spans workspaces.
|
|
2778
|
+
|
|
2779
|
+
## Listing workspaces
|
|
2780
|
+
|
|
2781
|
+
\`sky workspace list\` (or \`sky universe list\`) prints your workspaces, with
|
|
2782
|
+
\`*\` marking the current default.
|
|
2783
|
+
|
|
2784
|
+
## .agentsky.json
|
|
2785
|
+
|
|
2786
|
+
To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
2787
|
+
|
|
2788
|
+
\`\`\`json
|
|
2789
|
+
{"workspace": "my-team-slug"}
|
|
2790
|
+
\`\`\`
|
|
2791
|
+
`;
|
|
2792
|
+
}
|
|
2793
|
+
function installedVersion(dir) {
|
|
2794
|
+
try {
|
|
2795
|
+
const marker = JSON.parse(readFileSync9(join9(dir, SKILL_MARKER), "utf8"));
|
|
2796
|
+
return typeof marker.version === "string" ? marker.version : null;
|
|
2797
|
+
} catch {
|
|
2798
|
+
return null;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
function isInstalledSkill(dir) {
|
|
2802
|
+
return installedVersion(dir) !== null;
|
|
2803
|
+
}
|
|
2804
|
+
function installSkill(agent2, opts = {}) {
|
|
2805
|
+
const dir = join9(agent2.skillsDir, SKILL_NAME);
|
|
2806
|
+
const previous = installedVersion(dir);
|
|
2807
|
+
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2808
|
+
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2809
|
+
if (outcome !== "current") {
|
|
2810
|
+
mkdirSync2(dir, { recursive: true });
|
|
2811
|
+
writeFileSync4(join9(dir, "SKILL.md"), skillDocument());
|
|
2812
|
+
writeFileSync4(join9(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2813
|
+
`);
|
|
2814
|
+
}
|
|
2815
|
+
if (opts.catalog) {
|
|
2816
|
+
mkdirSync2(dir, { recursive: true });
|
|
2817
|
+
writeFileSync4(join9(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2818
|
+
}
|
|
2819
|
+
const refsDir = join9(dir, REFERENCES_DIR);
|
|
2820
|
+
mkdirSync2(refsDir, { recursive: true });
|
|
2821
|
+
writeFileSync4(join9(refsDir, "import.md"), importReferenceDoc());
|
|
2822
|
+
writeFileSync4(join9(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2823
|
+
writeFileSync4(join9(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2824
|
+
return { agent: agent2.id, path: dir, outcome };
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// src/mcp-register.ts
|
|
2828
|
+
var MCP_SERVER_NAME = SKILL_NAME;
|
|
2829
|
+
function detectHermes(env = process.env, home = homedir6()) {
|
|
2830
|
+
const root = env.HERMES_HOME || join10(home, ".hermes");
|
|
2831
|
+
return existsSync7(root) ? { id: "hermes", name: "Hermes", root } : null;
|
|
2832
|
+
}
|
|
2833
|
+
function detectMcpClients(env = process.env, home = homedir6()) {
|
|
2834
|
+
const hermes = detectHermes(env, home);
|
|
2835
|
+
return [...detectLocalAgents(env, home), ...hermes ? [hermes] : []];
|
|
2836
|
+
}
|
|
2837
|
+
function systemCommandRunner() {
|
|
2838
|
+
return {
|
|
2839
|
+
has: (command) => spawnSync3(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2840
|
+
run: (command, args) => {
|
|
2841
|
+
const result = spawnSync3(command, args, { encoding: "utf8" });
|
|
2842
|
+
return { status: result.status, stderr: result.stderr ?? "" };
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function mcpUrlFor(apiBase2) {
|
|
2847
|
+
return `${apiBase2.replace(/\/$/, "")}/mcp`;
|
|
2848
|
+
}
|
|
2849
|
+
function mcpCommands(agent2, opts) {
|
|
2850
|
+
if (agent2 === "claude_code") {
|
|
2851
|
+
return {
|
|
2852
|
+
command: "claude",
|
|
2853
|
+
remove: ["mcp", "remove", "--scope", "user", MCP_SERVER_NAME],
|
|
2854
|
+
add: ["mcp", "add", "--scope", "user", "--transport", "http", MCP_SERVER_NAME, opts.mcpUrl, "--header", `Authorization: Bearer ${opts.token}`]
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
function redact(text, token) {
|
|
2860
|
+
return text.split(token).join(`${token.slice(0, 10)}\u2026`);
|
|
2861
|
+
}
|
|
2862
|
+
function registerMcpServer(client, opts, runner = systemCommandRunner()) {
|
|
2863
|
+
if (client.id === "codex") return registerCodex(client, opts);
|
|
2864
|
+
if (client.id === "cursor") return registerCursor(client, opts);
|
|
2865
|
+
if (client.id === "hermes") return registerHermes(client, opts);
|
|
2866
|
+
const commands = mcpCommands(client.id, opts);
|
|
2867
|
+
if (!commands) return { agent: client.id, outcome: "unsupported" };
|
|
2868
|
+
if (!runner.has(commands.command)) return { agent: client.id, outcome: "no_command", detail: `${commands.command} is not on PATH` };
|
|
2869
|
+
if (opts.dryRun) return { agent: client.id, outcome: "planned", detail: redact(`run ${[commands.command, ...commands.add].join(" ")}`, opts.token) };
|
|
2870
|
+
runner.run(commands.command, commands.remove);
|
|
2871
|
+
const added = runner.run(commands.command, commands.add);
|
|
2872
|
+
if (added.status !== 0) return { agent: client.id, outcome: "failed", detail: redact(added.stderr.trim() || `exit ${added.status}`, opts.token) };
|
|
2873
|
+
return { agent: client.id, outcome: "registered" };
|
|
2874
|
+
}
|
|
2875
|
+
function mcpConfigPath(client, env = process.env, home = homedir6()) {
|
|
2876
|
+
switch (client.id) {
|
|
2877
|
+
case "claude_code":
|
|
2878
|
+
return join10(env.CLAUDE_CONFIG_DIR || home, ".claude.json");
|
|
2879
|
+
case "codex":
|
|
2880
|
+
return join10(client.root, "config.toml");
|
|
2881
|
+
case "cursor":
|
|
2882
|
+
return join10(client.root, "mcp.json");
|
|
2883
|
+
case "hermes":
|
|
2884
|
+
return join10(client.root, "config.yaml");
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
function readText(path) {
|
|
2888
|
+
try {
|
|
2889
|
+
return readFileSync10(path, "utf8");
|
|
2890
|
+
} catch {
|
|
2891
|
+
return null;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function hasMcpServer(client, env = process.env, home = homedir6()) {
|
|
2895
|
+
const raw = readText(mcpConfigPath(client, env, home));
|
|
2896
|
+
if (raw === null) return false;
|
|
2897
|
+
try {
|
|
2898
|
+
if (client.id === "codex") return Boolean(parseToml3(raw).mcp_servers?.[MCP_SERVER_NAME]);
|
|
2899
|
+
if (client.id === "hermes") return parseDocument(raw).hasIn(["mcp_servers", MCP_SERVER_NAME]);
|
|
2900
|
+
return Boolean(JSON.parse(raw).mcpServers?.[MCP_SERVER_NAME]);
|
|
2901
|
+
} catch {
|
|
2902
|
+
return false;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
function planned(client, path, entry2, opts) {
|
|
2906
|
+
return { agent: client.id, outcome: "planned", detail: `write to ${path}: ${redact(entry2, opts.token)}` };
|
|
2907
|
+
}
|
|
2908
|
+
function written(client, path, text) {
|
|
2909
|
+
try {
|
|
2910
|
+
writeFileSync5(path, text, { encoding: "utf8", mode: 384 });
|
|
2911
|
+
} catch (err) {
|
|
2912
|
+
return { agent: client.id, outcome: "failed", detail: String(err) };
|
|
2913
|
+
}
|
|
2914
|
+
return { agent: client.id, outcome: "registered" };
|
|
2915
|
+
}
|
|
2916
|
+
function tomlTableKey(name) {
|
|
2917
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2918
|
+
return `(?:${escaped}|"${escaped}"|'${escaped}')`;
|
|
2919
|
+
}
|
|
2920
|
+
function withoutTomlServer(text, name) {
|
|
2921
|
+
const ours = new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*${tomlTableKey(name)}\\s*(?:\\.[^\\]]*)?\\]\\s*(?:#.*)?$`);
|
|
2922
|
+
let skipping = false;
|
|
2923
|
+
const kept = [];
|
|
2924
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2925
|
+
if (/^\s*\[/.test(line)) skipping = ours.test(line);
|
|
2926
|
+
if (!skipping) kept.push(line);
|
|
2927
|
+
}
|
|
2928
|
+
return kept.join("\n").replace(/\n+$/, "");
|
|
2929
|
+
}
|
|
2930
|
+
function registerCodex(client, opts) {
|
|
2931
|
+
const path = mcpConfigPath(client);
|
|
2932
|
+
const block = [
|
|
2933
|
+
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
2934
|
+
`url = ${JSON.stringify(opts.mcpUrl)}`,
|
|
2935
|
+
`http_headers = { Authorization = ${JSON.stringify(`Bearer ${opts.token}`)} }`
|
|
2936
|
+
].join("\n");
|
|
2937
|
+
if (opts.dryRun) return planned(client, path, block, opts);
|
|
2938
|
+
const raw = readText(path) ?? "";
|
|
2939
|
+
let before;
|
|
2940
|
+
try {
|
|
2941
|
+
before = parseToml3(raw);
|
|
2942
|
+
} catch (err) {
|
|
2943
|
+
return { agent: client.id, outcome: "failed", detail: `${path} is not valid TOML, left as it was (${String(err).split("\n")[0]})` };
|
|
2944
|
+
}
|
|
2945
|
+
const rest = withoutTomlServer(raw, MCP_SERVER_NAME);
|
|
2946
|
+
const text = `${rest ? `${rest}
|
|
2947
|
+
|
|
2948
|
+
` : ""}${block}
|
|
2949
|
+
`;
|
|
2950
|
+
try {
|
|
2951
|
+
const after = parseToml3(text);
|
|
2952
|
+
const others = Object.keys(before.mcp_servers ?? {}).filter((name) => name !== MCP_SERVER_NAME);
|
|
2953
|
+
if (after.mcp_servers?.[MCP_SERVER_NAME]?.url !== opts.mcpUrl || others.some((name) => !after.mcp_servers?.[name])) throw new Error("the edited file lost an entry");
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
return { agent: client.id, outcome: "failed", detail: `could not edit ${path} safely, left as it was (${String(err).split("\n")[0]})` };
|
|
2956
|
+
}
|
|
2957
|
+
return written(client, path, text);
|
|
2958
|
+
}
|
|
2959
|
+
function registerCursor(client, opts) {
|
|
2960
|
+
const path = mcpConfigPath(client);
|
|
2961
|
+
const entry2 = { url: opts.mcpUrl, headers: { Authorization: `Bearer ${opts.token}` } };
|
|
2962
|
+
if (opts.dryRun) return planned(client, path, JSON.stringify({ [MCP_SERVER_NAME]: entry2 }, null, 2), opts);
|
|
2963
|
+
let existing = {};
|
|
2964
|
+
try {
|
|
2965
|
+
const parsed = JSON.parse(readText(path) ?? "");
|
|
2966
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed;
|
|
2967
|
+
} catch {
|
|
2968
|
+
existing = {};
|
|
2969
|
+
}
|
|
2970
|
+
const updated = { ...existing, mcpServers: { ...existing.mcpServers ?? {}, [MCP_SERVER_NAME]: entry2 } };
|
|
2971
|
+
return written(client, path, `${JSON.stringify(updated, null, 2)}
|
|
2972
|
+
`);
|
|
2973
|
+
}
|
|
2974
|
+
function registerHermes(client, opts) {
|
|
2975
|
+
const path = mcpConfigPath(client);
|
|
2976
|
+
const entry2 = { url: opts.mcpUrl, headers: { Authorization: `Bearer ${opts.token}` } };
|
|
2977
|
+
if (opts.dryRun) return planned(client, path, `mcp_servers.${MCP_SERVER_NAME}: ${JSON.stringify(entry2)}`, opts);
|
|
2978
|
+
const doc = parseDocument(readText(path) ?? "");
|
|
2979
|
+
if (doc.errors.length) return { agent: client.id, outcome: "failed", detail: `${path} is not valid YAML, left as it was (${doc.errors[0].message.split("\n")[0]})` };
|
|
2980
|
+
if (doc.contents !== null && !isMap(doc.contents)) {
|
|
2981
|
+
return { agent: client.id, outcome: "failed", detail: `${path} is not a YAML mapping, left as it was` };
|
|
2982
|
+
}
|
|
2983
|
+
doc.setIn(["mcp_servers", MCP_SERVER_NAME], doc.createNode(entry2));
|
|
2984
|
+
return written(client, path, doc.toString());
|
|
2985
|
+
}
|
|
2986
|
+
function refreshMcpRegistrations(opts, clients = detectMcpClients(), runner = systemCommandRunner()) {
|
|
2987
|
+
return clients.filter((client) => hasMcpServer(client)).map((client) => registerMcpServer(client, opts, runner));
|
|
2988
|
+
}
|
|
3188
2989
|
|
|
3189
|
-
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
2990
|
+
// src/shell-env.ts
|
|
2991
|
+
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
2992
|
+
import { homedir as homedir7 } from "os";
|
|
2993
|
+
import { basename as basename6, dirname as dirname5, join as join11 } from "path";
|
|
2994
|
+
var TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
2995
|
+
var HOOK_MARK = "# AgentSky API token, kept current by sky";
|
|
2996
|
+
function shellEnvFiles() {
|
|
2997
|
+
const dir = dirname5(configPath());
|
|
2998
|
+
return { sh: join11(dir, "env.sh"), fish: join11(dir, "env.fish") };
|
|
2999
|
+
}
|
|
3000
|
+
function quoted(value) {
|
|
3001
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3002
|
+
}
|
|
3003
|
+
function shellStartupFiles(env = process.env, home = homedir7(), platform = process.platform) {
|
|
3004
|
+
const shell = basename6(env.SHELL ?? "");
|
|
3005
|
+
const files2 = [];
|
|
3006
|
+
const zshenv = join11(env.ZDOTDIR || home, ".zshenv");
|
|
3007
|
+
if (shell === "zsh" || platform === "darwin" || existsSync8(zshenv)) files2.push({ path: zshenv, kind: "sh" });
|
|
3008
|
+
const bashrc = join11(home, ".bashrc");
|
|
3009
|
+
if (shell === "bash" || existsSync8(bashrc)) files2.push({ path: bashrc, kind: "sh" });
|
|
3010
|
+
const bashProfile = join11(home, ".bash_profile");
|
|
3011
|
+
if (existsSync8(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
|
|
3012
|
+
const fish = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "fish");
|
|
3013
|
+
if (shell === "fish" || existsSync8(fish)) files2.push({ path: join11(fish, "conf.d", "agentsky.fish"), kind: "fish" });
|
|
3014
|
+
return files2;
|
|
3015
|
+
}
|
|
3016
|
+
function writeShellEnv(token, opts = {}) {
|
|
3017
|
+
const { sh, fish } = shellEnvFiles();
|
|
3018
|
+
const startup = shellStartupFiles(opts.env, opts.home, opts.platform);
|
|
3019
|
+
if (opts.dryRun) return { outcome: "planned", files: [sh, fish, ...startup.map((file) => file.path)] };
|
|
3020
|
+
const header2 = "# Written by sky. `sky auth login` and `sky auth logout` rewrite this file.\n";
|
|
3021
|
+
const hooked = [];
|
|
3022
|
+
try {
|
|
3023
|
+
mkdirSync3(dirname5(sh), { recursive: true });
|
|
3024
|
+
writeFileSync6(sh, `${header2}${token ? `export ${TOKEN_ENV}=${quoted(token)}` : `unset ${TOKEN_ENV}`}
|
|
3025
|
+
`, { mode: 384 });
|
|
3026
|
+
writeFileSync6(fish, `${header2}${token ? `set -gx ${TOKEN_ENV} ${quoted(token)}` : `set -e ${TOKEN_ENV}`}
|
|
3027
|
+
`, { mode: 384 });
|
|
3028
|
+
for (const file of startup) {
|
|
3029
|
+
const existing = existsSync8(file.path) ? readFileSync11(file.path, "utf8") : "";
|
|
3030
|
+
if (existing.includes(HOOK_MARK)) continue;
|
|
3031
|
+
const line = file.kind === "fish" ? `${HOOK_MARK}
|
|
3032
|
+
test -f ${quoted(fish)}; and source ${quoted(fish)}
|
|
3033
|
+
` : `${HOOK_MARK}
|
|
3034
|
+
[ -f ${quoted(sh)} ] && . ${quoted(sh)}
|
|
3035
|
+
`;
|
|
3036
|
+
mkdirSync3(dirname5(file.path), { recursive: true });
|
|
3037
|
+
appendFileSync(file.path, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line}`);
|
|
3038
|
+
hooked.push(file.path);
|
|
3039
|
+
}
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
return { outcome: "failed", files: [sh, fish, ...hooked], detail: String(err) };
|
|
3042
|
+
}
|
|
3043
|
+
return { outcome: token ? "written" : "cleared", files: [sh, fish, ...hooked] };
|
|
3044
|
+
}
|
|
3193
3045
|
|
|
3194
|
-
|
|
3046
|
+
// src/local-access.ts
|
|
3047
|
+
function syncLocalAccess(token, clients = detectMcpClients()) {
|
|
3048
|
+
const lines = [];
|
|
3049
|
+
const shell = writeShellEnv(token);
|
|
3050
|
+
if (shell.outcome === "failed") lines.push(` ${TOKEN_ENV} not updated for new shells: ${shell.detail}`);
|
|
3051
|
+
else lines.push(token ? ` new shells get ${TOKEN_ENV} for this login` : ` new shells no longer get ${TOKEN_ENV}`);
|
|
3052
|
+
if (token) {
|
|
3053
|
+
for (const registered of refreshMcpRegistrations({ token, mcpUrl: mcpUrlFor(apiBase()) }, clients)) {
|
|
3054
|
+
const name = clients.find((client) => client.id === registered.agent)?.name ?? registered.agent;
|
|
3055
|
+
lines.push(registered.outcome === "registered" ? ` agentsky MCP server for ${name} now uses this login` : ` agentsky MCP server for ${name} not updated: ${registered.detail ?? registered.outcome} \u2014 run \`sky setup skill\``);
|
|
3056
|
+
}
|
|
3057
|
+
return lines;
|
|
3058
|
+
}
|
|
3059
|
+
const holding = clients.filter((client) => hasMcpServer(client)).map((client) => client.name);
|
|
3060
|
+
if (holding.length) lines.push(` the agentsky MCP server in ${holding.join(", ")} still uses the old token: \`sky auth login\` replaces it, or revoke the key in Developer \u2192 API keys`);
|
|
3061
|
+
return lines;
|
|
3062
|
+
}
|
|
3195
3063
|
|
|
3196
|
-
|
|
3197
|
-
|
|
3064
|
+
// src/login.ts
|
|
3065
|
+
function b64url(buf) {
|
|
3066
|
+
return buf.toString("base64url");
|
|
3067
|
+
}
|
|
3068
|
+
function openBrowser2(url) {
|
|
3069
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3070
|
+
try {
|
|
3071
|
+
spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
3072
|
+
} catch {
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
async function login(opts) {
|
|
3076
|
+
const verifier = b64url(randomBytes(32));
|
|
3077
|
+
const challenge = b64url(createHash4("sha256").update(verifier).digest());
|
|
3078
|
+
const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
|
|
3079
|
+
saveConfig({
|
|
3080
|
+
...loadConfig(),
|
|
3081
|
+
apiBase: apiBase(),
|
|
3082
|
+
token: result.token,
|
|
3083
|
+
email: result.user.email ?? void 0,
|
|
3084
|
+
currentUniverse: void 0
|
|
3085
|
+
// personal by default
|
|
3086
|
+
});
|
|
3087
|
+
console.log(
|
|
3088
|
+
`Logged in as ${result.user.email ?? "you"}${result.universe ? ` (universe: ${result.universe.slug})` : ""}`
|
|
3089
|
+
);
|
|
3090
|
+
for (const line of syncLocalAccess(result.token)) console.log(line);
|
|
3091
|
+
}
|
|
3092
|
+
async function browserLogin(verifier, challenge) {
|
|
3093
|
+
const state = b64url(randomBytes(16));
|
|
3094
|
+
const code = await new Promise((resolve10, reject) => {
|
|
3095
|
+
const server = createServer((req, res) => {
|
|
3096
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3097
|
+
if (url.pathname !== "/callback") {
|
|
3098
|
+
res.writeHead(404).end();
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
3102
|
+
res.end("<p>Logged in \u2014 you can close this tab and return to your terminal.</p>");
|
|
3103
|
+
const got = url.searchParams.get("code");
|
|
3104
|
+
if (url.searchParams.get("state") !== state || !got) {
|
|
3105
|
+
reject(new Error("login callback state mismatch"));
|
|
3106
|
+
} else {
|
|
3107
|
+
resolve10(got);
|
|
3108
|
+
}
|
|
3109
|
+
server.close();
|
|
3110
|
+
});
|
|
3111
|
+
server.listen(0, "127.0.0.1", () => {
|
|
3112
|
+
const address = server.address();
|
|
3113
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
3114
|
+
const authorizeUrl = `${consoleBase()}/cli/authorize?state=${state}&port=${port}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
3115
|
+
console.log("Opening your browser to authorize this machine\u2026");
|
|
3116
|
+
console.log(`If it doesn't open, visit:
|
|
3117
|
+
${authorizeUrl}`);
|
|
3118
|
+
openBrowser2(authorizeUrl);
|
|
3119
|
+
});
|
|
3120
|
+
setTimeout(() => {
|
|
3121
|
+
server.close();
|
|
3122
|
+
reject(new Error("login timed out after 10 minutes"));
|
|
3123
|
+
}, 10 * 60 * 1e3).unref();
|
|
3124
|
+
});
|
|
3125
|
+
return api(
|
|
3126
|
+
"POST",
|
|
3127
|
+
"/cli/token-exchange",
|
|
3128
|
+
{ code, verifier, clientName: `cli ${hostname()}` },
|
|
3129
|
+
{ auth: false }
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
async function headlessLogin(verifier, challenge) {
|
|
3133
|
+
const request = b64url(randomBytes(24));
|
|
3134
|
+
const authorizeUrl = `${consoleBase()}/cli/authorize?request=${request}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
3135
|
+
console.log(`Visit this URL on any device to authorize:
|
|
3136
|
+
${authorizeUrl}
|
|
3137
|
+
`);
|
|
3138
|
+
process.stdout.write("Waiting for approval\u2026");
|
|
3139
|
+
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
3140
|
+
while (Date.now() < deadline) {
|
|
3141
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
3142
|
+
const res = await fetch(
|
|
3143
|
+
`${apiBase()}/cli/poll?request=${request}&verifier=${encodeURIComponent(verifier)}&clientName=${encodeURIComponent(`cli ${hostname()}`)}`,
|
|
3144
|
+
{ headers: { "user-agent": USER_AGENT } }
|
|
3145
|
+
);
|
|
3146
|
+
if (!res.ok) {
|
|
3147
|
+
const body2 = await res.json().catch(() => null);
|
|
3148
|
+
throw new Error(body2?.error?.message ?? `poll failed (${res.status})`);
|
|
3149
|
+
}
|
|
3150
|
+
const body = await res.json();
|
|
3151
|
+
if (body.status === "approved") {
|
|
3152
|
+
console.log(" approved.");
|
|
3153
|
+
return body;
|
|
3154
|
+
}
|
|
3155
|
+
process.stdout.write(".");
|
|
3156
|
+
}
|
|
3157
|
+
throw new Error("login timed out after 10 minutes");
|
|
3158
|
+
}
|
|
3198
3159
|
|
|
3199
|
-
|
|
3160
|
+
// src/connectors.ts
|
|
3161
|
+
async function listApps(query, limit = 50) {
|
|
3162
|
+
const apps = [];
|
|
3163
|
+
let cursor = null;
|
|
3164
|
+
do {
|
|
3165
|
+
const params = new URLSearchParams();
|
|
3166
|
+
if (query) params.set("q", query);
|
|
3167
|
+
if (cursor) params.set("cursor", cursor);
|
|
3168
|
+
const qs = params.toString();
|
|
3169
|
+
const page = await api("GET", `/connectors/apps${qs ? `?${qs}` : ""}`);
|
|
3170
|
+
apps.push(...page.apps);
|
|
3171
|
+
cursor = page.next_cursor;
|
|
3172
|
+
} while (cursor && apps.length < limit);
|
|
3173
|
+
return apps.slice(0, limit);
|
|
3174
|
+
}
|
|
3175
|
+
function connectionLine2(c) {
|
|
3176
|
+
return `${c.id} ${c.app_name} (${c.app}) ${c.label} since ${c.created_at.slice(0, 10)}`;
|
|
3177
|
+
}
|
|
3178
|
+
function workspaceConnectionLine(c) {
|
|
3179
|
+
const owner = c.workspace_owned ? "the workspace" : c.mine ? "you" : c.shared_by?.name ?? c.shared_by?.user_id ?? "someone";
|
|
3180
|
+
return `${c.connection_id} ${c.app_name} (${c.app}) ${c.label} shared by ${owner}`;
|
|
3181
|
+
}
|
|
3182
|
+
function browserReturn(start, opts = {}) {
|
|
3183
|
+
return new Promise((resolve10, reject) => {
|
|
3184
|
+
const server = createServer2((req, res) => {
|
|
3185
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3186
|
+
if (url.pathname !== "/done" && url.pathname !== "/failed") {
|
|
3187
|
+
res.writeHead(404).end();
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
const ok = url.pathname === "/done";
|
|
3191
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
3192
|
+
res.end(ok ? "<p>Connected \u2014 you can close this tab and return to your terminal.</p>" : "<p>The app was not connected. Return to your terminal.</p>");
|
|
3193
|
+
server.close();
|
|
3194
|
+
if (ok) resolve10(url.searchParams.get("account_id") ?? void 0);
|
|
3195
|
+
else reject(new ApiError(400, "connect_failed", "the app was not connected"));
|
|
3196
|
+
});
|
|
3197
|
+
server.listen(0, "127.0.0.1", () => {
|
|
3198
|
+
const address = server.address();
|
|
3199
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
3200
|
+
const base = `http://127.0.0.1:${port}`;
|
|
3201
|
+
start(`${base}/done`, `${base}/failed`).then(
|
|
3202
|
+
(authorizeUrl) => {
|
|
3203
|
+
console.log(`Authorize access in your browser:
|
|
3204
|
+
${authorizeUrl}`);
|
|
3205
|
+
if (opts.open !== false) openBrowser2(authorizeUrl);
|
|
3206
|
+
},
|
|
3207
|
+
(err) => {
|
|
3208
|
+
server.close();
|
|
3209
|
+
reject(err);
|
|
3210
|
+
}
|
|
3211
|
+
);
|
|
3212
|
+
});
|
|
3213
|
+
setTimeout(() => {
|
|
3214
|
+
server.close();
|
|
3215
|
+
reject(new ApiError(408, "connect_timeout", "nobody finished the authorization within 10 minutes"));
|
|
3216
|
+
}, opts.timeoutMs ?? 10 * 6e4).unref();
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
async function connectApp(app, opts = {}) {
|
|
3220
|
+
const base = opts.workspace ? `/universes/${encodeURIComponent(opts.workspace)}/connections` : "/connectors/connections";
|
|
3221
|
+
const accountId = await browserReturn(async (success_url, error_url) => {
|
|
3222
|
+
const { url } = await api("POST", base, { app, success_url, error_url });
|
|
3223
|
+
return url;
|
|
3224
|
+
}, { open: opts.open });
|
|
3225
|
+
const done = await api("POST", `${base}/complete`, {
|
|
3226
|
+
app,
|
|
3227
|
+
...accountId ? { account_id: accountId } : {}
|
|
3228
|
+
});
|
|
3229
|
+
return done.connection;
|
|
3230
|
+
}
|
|
3200
3231
|
|
|
3201
|
-
|
|
3232
|
+
// src/routine-triggers.ts
|
|
3233
|
+
var triggerPath = (id, resource) => `/routines/${encodeURIComponent(id)}/${resource}`;
|
|
3234
|
+
function inboundLine(u) {
|
|
3235
|
+
if (!u) return "no inbound URL \u2014 make one with --create";
|
|
3236
|
+
return u.url ?? `inbound URL ${u.prefix}\u2026 made ${u.created_at.slice(0, 10)}${u.last_used_at ? `, last used ${u.last_used_at.slice(0, 16).replace("T", " ")}` : ", never used"}`;
|
|
3237
|
+
}
|
|
3238
|
+
function precheckLine(p) {
|
|
3239
|
+
if (!p) return "no precheck: every scheduled fire runs";
|
|
3240
|
+
const last = p.last_result ? `last check ${p.last_result}${p.last_error ? ` (${p.last_error.type}: ${p.last_error.message})` : ""}` : "not checked yet";
|
|
3241
|
+
return `fires only when ${p.url} changes \xB7 ${last}`;
|
|
3242
|
+
}
|
|
3243
|
+
function triggerLine(t, available = true) {
|
|
3244
|
+
if (!t) return "no chat trigger";
|
|
3245
|
+
const where = t.thread_id ? `thread ${t.thread_id} on ${t.connection_id}` : `any conversation on ${t.connection_id}`;
|
|
3246
|
+
const what = t.text_prefix ? `messages starting "${t.text_prefix}"` : "every message";
|
|
3247
|
+
return `${what} in ${where} start a run${available ? "" : " (chat messages are not offered to routines in this deployment)"}`;
|
|
3248
|
+
}
|
|
3249
|
+
function deliverLine(d) {
|
|
3250
|
+
return d ? `answers post to thread ${d.thread_id} on ${d.connection_id}` : "answers are posted nowhere";
|
|
3251
|
+
}
|
|
3252
|
+
function triggerBody(opts) {
|
|
3253
|
+
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
3254
|
+
return { connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {}, ...opts.prefix ? { text_prefix: opts.prefix } : {} };
|
|
3255
|
+
}
|
|
3256
|
+
function deliverBody(opts) {
|
|
3257
|
+
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
3258
|
+
return { type: "channel_thread", connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {} };
|
|
3259
|
+
}
|
|
3260
|
+
async function triggerRequest(id, resource, change) {
|
|
3261
|
+
const path = triggerPath(id, resource);
|
|
3262
|
+
if (change.remove) return api("DELETE", path);
|
|
3263
|
+
if (change.create) return api("POST", path);
|
|
3264
|
+
if (change.body) return api("PUT", path, change.body);
|
|
3265
|
+
return api("GET", path);
|
|
3266
|
+
}
|
|
3202
3267
|
|
|
3203
|
-
|
|
3204
|
-
{
|
|
3205
|
-
|
|
3206
|
-
|
|
3268
|
+
// src/session-workspace.ts
|
|
3269
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
3270
|
+
import { createHash as createHash5, randomUUID } from "crypto";
|
|
3271
|
+
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
3272
|
+
import { basename as basename7, join as join12, relative as relative3, resolve as resolve5 } from "path";
|
|
3273
|
+
var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
|
|
3274
|
+
var PART_SIZE = 8 * 1024 * 1024;
|
|
3275
|
+
var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
3276
|
+
var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
|
|
3277
|
+
async function listFiles2(id, path, hidden) {
|
|
3278
|
+
const query = new URLSearchParams();
|
|
3279
|
+
if (path) query.set("path", path);
|
|
3280
|
+
if (hidden) query.set("hidden", "true");
|
|
3281
|
+
const qs = query.toString();
|
|
3282
|
+
return api("GET", `${workspacePath(id)}/files${qs ? `?${qs}` : ""}`);
|
|
3207
3283
|
}
|
|
3208
|
-
function
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3284
|
+
function entryLine(e) {
|
|
3285
|
+
const size = e.type === "directory" ? "-" : String(e.size ?? 0);
|
|
3286
|
+
return `${e.type === "directory" ? "d" : "-"} ${size.padStart(10)} ${(e.mtime ?? "").slice(0, 16).replace("T", " ")} ${e.name}${e.type === "directory" ? "/" : ""}`;
|
|
3287
|
+
}
|
|
3288
|
+
async function readFile(id, path) {
|
|
3289
|
+
return (await apiBytes("GET", `${workspacePath(id)}/files/content?path=${encodeURIComponent(path)}`)).bytes;
|
|
3290
|
+
}
|
|
3291
|
+
function renderChanges(changes) {
|
|
3292
|
+
if (!changes.repos.length) return [`no git repository under ${changes.root}`];
|
|
3293
|
+
return changes.repos.flatMap((r) => [
|
|
3294
|
+
`${r.path === "." ? changes.root : r.path}: ${r.status ? "changed" : "clean"}`,
|
|
3295
|
+
...(r.stat ?? r.status ?? "").split("\n").filter(Boolean).map((line) => ` ${line}`)
|
|
3296
|
+
]);
|
|
3297
|
+
}
|
|
3298
|
+
async function workspacePatch(id, repo) {
|
|
3299
|
+
const { patch, ...meta } = await api("POST", `${workspacePath(id)}/patch`, { repo });
|
|
3300
|
+
return { meta, patch: Buffer.from(patch, "base64") };
|
|
3301
|
+
}
|
|
3302
|
+
function folderFiles(dir) {
|
|
3303
|
+
const git2 = spawnSync4("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
|
|
3304
|
+
const top = spawnSync4("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
|
|
3305
|
+
if (git2.status === 0 && top.status === 0) {
|
|
3306
|
+
return git2.stdout.split("\0").filter((path) => path && statSync6(join12(dir, path), { throwIfNoEntry: false })?.isFile());
|
|
3214
3307
|
}
|
|
3308
|
+
const files2 = [];
|
|
3309
|
+
const walk = (at) => {
|
|
3310
|
+
for (const entry2 of readdirSync5(at, { withFileTypes: true })) {
|
|
3311
|
+
if (SKIPPED2.has(entry2.name)) continue;
|
|
3312
|
+
const full = join12(at, entry2.name);
|
|
3313
|
+
if (entry2.isDirectory()) walk(full);
|
|
3314
|
+
else if (entry2.isFile()) files2.push(relative3(dir, full));
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
walk(dir);
|
|
3318
|
+
return files2;
|
|
3215
3319
|
}
|
|
3216
|
-
function
|
|
3217
|
-
|
|
3320
|
+
function folderArchive(dir, files2) {
|
|
3321
|
+
const tar = spawnSync4("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
|
|
3322
|
+
input: files2.join("\n"),
|
|
3323
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" },
|
|
3324
|
+
maxBuffer: 2 * 1024 * 1024 * 1024
|
|
3325
|
+
});
|
|
3326
|
+
if (tar.status !== 0) throw new ApiError(400, "archive_failed", `tar failed: ${tar.stderr.toString().trim()}`);
|
|
3327
|
+
return tar.stdout;
|
|
3218
3328
|
}
|
|
3219
|
-
function
|
|
3220
|
-
const
|
|
3221
|
-
const
|
|
3222
|
-
const
|
|
3223
|
-
if (
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
writeFileSync4(join10(dir, "SKILL.md"), skillDocument());
|
|
3227
|
-
writeFileSync4(join10(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
3228
|
-
`);
|
|
3329
|
+
async function importFolder(id, dir, opts = {}) {
|
|
3330
|
+
const root = resolve5(dir);
|
|
3331
|
+
const name = opts.name ?? basename7(root);
|
|
3332
|
+
const archive = opts.archive ?? folderArchive(root, folderFiles(root));
|
|
3333
|
+
if (archive.length <= SINGLE_IMPORT_MAX) {
|
|
3334
|
+
const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
|
|
3335
|
+
return res.json();
|
|
3229
3336
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3337
|
+
const sha256 = createHash5("sha256").update(archive).digest("hex");
|
|
3338
|
+
let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
|
|
3339
|
+
const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
|
|
3340
|
+
for (let part = 0; part < upload.parts; part += 1) {
|
|
3341
|
+
if (upload.received.includes(part)) continue;
|
|
3342
|
+
const bytes = archive.subarray(part * upload.part_size, (part + 1) * upload.part_size);
|
|
3343
|
+
await apiBytes("PUT", `${uploadPath}/parts/${part}`, { bytes, mediaType: "application/octet-stream" });
|
|
3233
3344
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3345
|
+
upload = await api("POST", `${uploadPath}/complete`);
|
|
3346
|
+
while (upload.state !== "imported" && upload.state !== "failed") {
|
|
3347
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
3348
|
+
upload = await api("GET", uploadPath);
|
|
3349
|
+
}
|
|
3350
|
+
if (upload.state === "failed") throw new ApiError(422, upload.error?.code ?? "import_failed", upload.error?.message ?? "the import failed");
|
|
3351
|
+
return { path: upload.path ?? name, files: upload.files ?? 0 };
|
|
3240
3352
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3353
|
+
function renderAccess(a) {
|
|
3354
|
+
const lines = [
|
|
3355
|
+
`${a.session_id}${a.title ? ` \u2014 ${a.title}` : ""}`,
|
|
3356
|
+
` ${a.visibility === "private" ? "private to its owner" : `visible to ${a.workspace.name} (${a.workspace.slug})`} \xB7 you are its ${a.access}`,
|
|
3357
|
+
` owner: ${a.owner.name ?? a.owner.email ?? "unknown"}`
|
|
3358
|
+
];
|
|
3359
|
+
if (a.viewers) lines.push(a.viewers.length ? ` viewers: ${a.viewers.map((v) => v.email).join(", ")}` : " viewers: none");
|
|
3360
|
+
return lines;
|
|
3361
|
+
}
|
|
3362
|
+
function accessPatch(opts) {
|
|
3363
|
+
if (opts.private && opts.members) throw new ApiError(400, "invalid_request", "pass --private or --members, not both");
|
|
3246
3364
|
return {
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
return { status: result.status, stderr: result.stderr ?? "" };
|
|
3251
|
-
}
|
|
3365
|
+
...opts.private ? { visibility: "private" } : opts.members ? { visibility: "workspace" } : {},
|
|
3366
|
+
...opts.addViewer?.length ? { add_viewers: opts.addViewer } : {},
|
|
3367
|
+
...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
|
|
3252
3368
|
};
|
|
3253
3369
|
}
|
|
3254
|
-
function
|
|
3255
|
-
|
|
3370
|
+
function engineChange(model, thinking) {
|
|
3371
|
+
if (!model && !thinking) return null;
|
|
3372
|
+
return {
|
|
3373
|
+
...model ? { llm: model } : {},
|
|
3374
|
+
...thinking ? { reasoningEffort: thinking === "default" ? null : thinking } : {}
|
|
3375
|
+
};
|
|
3256
3376
|
}
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
add: ["mcp", "add", MCP_SERVER_NAME, "--url", opts.mcpUrl, "--bearer-token-env-var", CODEX_TOKEN_ENV]
|
|
3270
|
-
};
|
|
3377
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
3378
|
+
async function handoff(id, targetAgent, opts = {}) {
|
|
3379
|
+
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
|
|
3380
|
+
target_agent: targetAgent,
|
|
3381
|
+
...opts.fromMessage ? { last_message_id: opts.fromMessage } : {}
|
|
3382
|
+
}, { idempotencyKey: opts.idempotencyKey ?? randomUUID() });
|
|
3383
|
+
opts.onStatus?.(h);
|
|
3384
|
+
while (opts.wait && !TERMINAL.has(h.status)) {
|
|
3385
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
3386
|
+
const next = (await api("GET", `/session-handoffs/${encodeURIComponent(h.id)}`)).handoff;
|
|
3387
|
+
if (next.status !== h.status) opts.onStatus?.(next);
|
|
3388
|
+
h = next;
|
|
3271
3389
|
}
|
|
3272
|
-
return
|
|
3390
|
+
return h;
|
|
3273
3391
|
}
|
|
3274
|
-
|
|
3275
|
-
|
|
3392
|
+
|
|
3393
|
+
// src/render.ts
|
|
3394
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
3395
|
+
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
3396
|
+
function clip(value, max = 120) {
|
|
3397
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
3398
|
+
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
3276
3399
|
}
|
|
3277
|
-
function
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3400
|
+
function renderEvent(event, data) {
|
|
3401
|
+
const part = data.part;
|
|
3402
|
+
switch (event) {
|
|
3403
|
+
case "agent.message":
|
|
3404
|
+
console.log(String(data.text ?? ""));
|
|
3405
|
+
break;
|
|
3406
|
+
case "agent.reasoning":
|
|
3407
|
+
console.log(dim(`\xB7 ${clip(part?.text ?? "")}`));
|
|
3408
|
+
break;
|
|
3409
|
+
case "agent.tool_use":
|
|
3410
|
+
console.log(dim(`\u2192 ${String(part?.tool_name ?? "tool")}(${clip(part?.args ?? {}, 80)})`));
|
|
3411
|
+
break;
|
|
3412
|
+
case "agent.tool_result":
|
|
3413
|
+
console.log(dim(`\u2190 ${String(part?.tool_name ?? "tool")} ${String(part?.status ?? "")}`));
|
|
3414
|
+
break;
|
|
3415
|
+
case "agent.status":
|
|
3416
|
+
console.log(dim(`\u2026 ${String(part?.level ?? "")} ${String(part?.text ?? "")}`.trim()));
|
|
3417
|
+
break;
|
|
3418
|
+
case "user.interrupt":
|
|
3419
|
+
console.log(dim("\u23F9 interrupting"));
|
|
3420
|
+
break;
|
|
3421
|
+
case "session.error": {
|
|
3422
|
+
const error = data.error;
|
|
3423
|
+
const message2 = String(error?.message ?? error?.type ?? "unknown");
|
|
3424
|
+
if (error?.retry_status === "retrying") console.error(dim(`retrying: ${message2}`));
|
|
3425
|
+
else console.error(red(`error: ${message2}`));
|
|
3426
|
+
break;
|
|
3427
|
+
}
|
|
3428
|
+
case "session.status_terminated":
|
|
3429
|
+
console.log(dim("session terminated"));
|
|
3430
|
+
break;
|
|
3431
|
+
default:
|
|
3432
|
+
break;
|
|
3299
3433
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
// src/session-read.ts
|
|
3437
|
+
var TURN_TYPES = ["user.message", "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
|
|
3438
|
+
function latestTurn(events) {
|
|
3439
|
+
let start = 0;
|
|
3440
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
3441
|
+
if (events[index].type === "user.message") {
|
|
3442
|
+
start = index + 1;
|
|
3443
|
+
break;
|
|
3306
3444
|
}
|
|
3307
|
-
} catch {
|
|
3308
3445
|
}
|
|
3309
|
-
const
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3446
|
+
const reading = { finished: false, messages: [], errors: [] };
|
|
3447
|
+
for (const event of events.slice(start)) {
|
|
3448
|
+
if (event.type === "agent.message" && event.text?.trim()) reading.messages.push(event.text);
|
|
3449
|
+
else if (event.type === "session.error" && event.error?.retry_status !== "retrying") {
|
|
3450
|
+
reading.errors.push(event.error?.message ?? event.error?.type ?? "unknown error");
|
|
3451
|
+
} else if (event.type === "session.status_terminated") reading.finished = true;
|
|
3452
|
+
else if (event.type === "session.status_idle" && event.stop_reason?.type !== "requires_action") reading.finished = true;
|
|
3453
|
+
}
|
|
3454
|
+
return reading;
|
|
3455
|
+
}
|
|
3456
|
+
async function history2(sessionId) {
|
|
3457
|
+
const events = [];
|
|
3458
|
+
let cursor = null;
|
|
3459
|
+
for (; ; ) {
|
|
3460
|
+
const query = new URLSearchParams({ limit: "500" });
|
|
3461
|
+
for (const type of TURN_TYPES) query.append("types[]", type);
|
|
3462
|
+
if (cursor) query.set("cursor", cursor);
|
|
3463
|
+
const page = await api(
|
|
3464
|
+
"GET",
|
|
3465
|
+
`/sessions/${encodeURIComponent(sessionId)}/events?${query}`
|
|
3466
|
+
);
|
|
3467
|
+
events.push(...page.events);
|
|
3468
|
+
if (!page.hasMore || !page.cursor) return events;
|
|
3469
|
+
cursor = page.cursor;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
async function readTurn(sessionId, opts = {}) {
|
|
3473
|
+
const deadline = Date.now() + (opts.timeoutSeconds ?? 600) * 1e3;
|
|
3474
|
+
for (; ; ) {
|
|
3475
|
+
const reading = latestTurn(await history2(sessionId));
|
|
3476
|
+
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
3477
|
+
await new Promise((resolve10) => setTimeout(resolve10, opts.intervalMs ?? 3e3));
|
|
3317
3478
|
}
|
|
3318
|
-
return { agent: agent2.id, outcome: "registered" };
|
|
3319
3479
|
}
|
|
3320
3480
|
|
|
3481
|
+
// src/setup.ts
|
|
3482
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
3483
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
3484
|
+
import { createInterface as createInterface3 } from "readline";
|
|
3485
|
+
|
|
3321
3486
|
// src/setup-local.ts
|
|
3322
3487
|
import { isUtf8 as isUtf83 } from "buffer";
|
|
3323
|
-
import { existsSync as
|
|
3324
|
-
import { homedir as
|
|
3325
|
-
import { dirname as
|
|
3326
|
-
import { parse as
|
|
3488
|
+
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
3489
|
+
import { homedir as homedir8 } from "os";
|
|
3490
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, resolve as resolve6 } from "path";
|
|
3491
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
3327
3492
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
3328
3493
|
var MAX_IMPORT_DEPTH = 5;
|
|
3329
3494
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
@@ -3347,11 +3512,11 @@ function redacted(data) {
|
|
|
3347
3512
|
function skillFiles2(root, prefix) {
|
|
3348
3513
|
const files2 = [];
|
|
3349
3514
|
for (const entry2 of readdirSync6(root, { withFileTypes: true })) {
|
|
3350
|
-
const path =
|
|
3515
|
+
const path = join13(root, entry2.name);
|
|
3351
3516
|
if (entry2.isDirectory()) {
|
|
3352
3517
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
3353
3518
|
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
3354
|
-
const bytes = redacted(
|
|
3519
|
+
const bytes = redacted(readFileSync12(path));
|
|
3355
3520
|
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
3356
3521
|
}
|
|
3357
3522
|
}
|
|
@@ -3361,9 +3526,9 @@ function readLocalSkills(agents) {
|
|
|
3361
3526
|
const skills2 = [];
|
|
3362
3527
|
const ignored = [];
|
|
3363
3528
|
for (const agent2 of agents) {
|
|
3364
|
-
if (!
|
|
3529
|
+
if (!existsSync9(agent2.skillsDir)) continue;
|
|
3365
3530
|
for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
|
|
3366
|
-
const dir =
|
|
3531
|
+
const dir = join13(agent2.skillsDir, entry2.name);
|
|
3367
3532
|
let isDir2 = entry2.isDirectory();
|
|
3368
3533
|
if (entry2.isSymbolicLink()) {
|
|
3369
3534
|
try {
|
|
@@ -3372,8 +3537,8 @@ function readLocalSkills(agents) {
|
|
|
3372
3537
|
isDir2 = false;
|
|
3373
3538
|
}
|
|
3374
3539
|
}
|
|
3375
|
-
if (!isDir2 || !
|
|
3376
|
-
const name = frontmatterName(
|
|
3540
|
+
if (!isDir2 || !existsSync9(join13(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
3541
|
+
const name = frontmatterName(readFileSync12(join13(dir, "SKILL.md"), "utf8"));
|
|
3377
3542
|
if (!name) {
|
|
3378
3543
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
3379
3544
|
continue;
|
|
@@ -3401,11 +3566,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
3401
3566
|
}
|
|
3402
3567
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
3403
3568
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
3404
|
-
const path = target.startsWith("~/") ?
|
|
3569
|
+
const path = target.startsWith("~/") ? join13(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname6(from), target);
|
|
3405
3570
|
if (seen.includes(path)) return "";
|
|
3406
3571
|
let imported;
|
|
3407
3572
|
try {
|
|
3408
|
-
imported =
|
|
3573
|
+
imported = readFileSync12(path, "utf8");
|
|
3409
3574
|
} catch {
|
|
3410
3575
|
return line;
|
|
3411
3576
|
}
|
|
@@ -3413,15 +3578,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
3413
3578
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
3414
3579
|
}).join("\n");
|
|
3415
3580
|
}
|
|
3416
|
-
function readGlobalInstructions(agents, home =
|
|
3581
|
+
function readGlobalInstructions(agents, home = homedir8()) {
|
|
3417
3582
|
const found = [];
|
|
3418
3583
|
for (const agent2 of agents) {
|
|
3419
3584
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
3420
3585
|
if (!file) continue;
|
|
3421
|
-
const path =
|
|
3586
|
+
const path = join13(agent2.root, file);
|
|
3422
3587
|
let raw;
|
|
3423
3588
|
try {
|
|
3424
|
-
raw =
|
|
3589
|
+
raw = readFileSync12(path, "utf8");
|
|
3425
3590
|
} catch {
|
|
3426
3591
|
continue;
|
|
3427
3592
|
}
|
|
@@ -3440,7 +3605,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
3440
3605
|
}
|
|
3441
3606
|
function readJson3(path) {
|
|
3442
3607
|
try {
|
|
3443
|
-
return JSON.parse(
|
|
3608
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3444
3609
|
} catch {
|
|
3445
3610
|
return void 0;
|
|
3446
3611
|
}
|
|
@@ -3454,18 +3619,19 @@ function bearerOf(def) {
|
|
|
3454
3619
|
return match?.[1] ?? null;
|
|
3455
3620
|
}
|
|
3456
3621
|
function serversOf(agent2, home) {
|
|
3457
|
-
if (agent2.id === "claude_code") return readJson3(
|
|
3458
|
-
if (agent2.id === "cursor") return readJson3(
|
|
3622
|
+
if (agent2.id === "claude_code") return readJson3(join13(home, ".claude.json"))?.mcpServers ?? {};
|
|
3623
|
+
if (agent2.id === "cursor") return readJson3(join13(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
3459
3624
|
try {
|
|
3460
|
-
return
|
|
3625
|
+
return parseToml4(readFileSync12(join13(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
3461
3626
|
} catch {
|
|
3462
3627
|
return {};
|
|
3463
3628
|
}
|
|
3464
3629
|
}
|
|
3465
|
-
function findLocalSecrets(agents, home =
|
|
3630
|
+
function findLocalSecrets(agents, home = homedir8()) {
|
|
3466
3631
|
const secrets2 = [];
|
|
3467
3632
|
for (const agent2 of agents) {
|
|
3468
3633
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
3634
|
+
if (name === SKILL_NAME) continue;
|
|
3469
3635
|
if (!def || typeof def !== "object" || typeof def.url !== "string" || !/^https:\/\//i.test(def.url)) continue;
|
|
3470
3636
|
const token = bearerOf(def);
|
|
3471
3637
|
if (!token || /^\$\{?[A-Za-z_]/.test(token)) continue;
|
|
@@ -3478,9 +3644,9 @@ function findLocalSecrets(agents, home = homedir6()) {
|
|
|
3478
3644
|
|
|
3479
3645
|
// src/subscriptions.ts
|
|
3480
3646
|
import { spawn as spawn3 } from "child_process";
|
|
3481
|
-
import { existsSync as
|
|
3482
|
-
import { homedir as
|
|
3483
|
-
import { join as
|
|
3647
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
|
|
3648
|
+
import { homedir as homedir9 } from "os";
|
|
3649
|
+
import { join as join14 } from "path";
|
|
3484
3650
|
import { createInterface as createInterface2 } from "readline";
|
|
3485
3651
|
var ALIASES = {
|
|
3486
3652
|
claude: "anthropic",
|
|
@@ -3575,12 +3741,12 @@ async function connectClaude() {
|
|
|
3575
3741
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
3576
3742
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
3577
3743
|
}
|
|
3578
|
-
function codexSignIn(env = process.env, home =
|
|
3579
|
-
const path =
|
|
3580
|
-
if (!
|
|
3744
|
+
function codexSignIn(env = process.env, home = homedir9()) {
|
|
3745
|
+
const path = join14(env.CODEX_HOME || join14(home, ".codex"), "auth.json");
|
|
3746
|
+
if (!existsSync10(path)) {
|
|
3581
3747
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
3582
3748
|
}
|
|
3583
|
-
const raw =
|
|
3749
|
+
const raw = readFileSync13(path, "utf8");
|
|
3584
3750
|
try {
|
|
3585
3751
|
const parsed = JSON.parse(raw);
|
|
3586
3752
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -3588,12 +3754,12 @@ function codexSignIn(env = process.env, home = homedir7()) {
|
|
|
3588
3754
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
3589
3755
|
}
|
|
3590
3756
|
}
|
|
3591
|
-
function cursorSignIn(env = process.env, home =
|
|
3592
|
-
const path =
|
|
3593
|
-
if (!
|
|
3757
|
+
function cursorSignIn(env = process.env, home = homedir9()) {
|
|
3758
|
+
const path = join14(env.XDG_CONFIG_HOME || join14(home, ".config"), "cursor", "auth.json");
|
|
3759
|
+
if (!existsSync10(path)) {
|
|
3594
3760
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
3595
3761
|
}
|
|
3596
|
-
const raw =
|
|
3762
|
+
const raw = readFileSync13(path, "utf8");
|
|
3597
3763
|
try {
|
|
3598
3764
|
const parsed = JSON.parse(raw);
|
|
3599
3765
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -3752,11 +3918,11 @@ function planProviders(names2) {
|
|
|
3752
3918
|
}));
|
|
3753
3919
|
}
|
|
3754
3920
|
function reportPath() {
|
|
3755
|
-
return
|
|
3921
|
+
return join15(dirname7(configPath()), "setup.json");
|
|
3756
3922
|
}
|
|
3757
3923
|
function loadReport() {
|
|
3758
3924
|
try {
|
|
3759
|
-
const saved = JSON.parse(
|
|
3925
|
+
const saved = JSON.parse(readFileSync14(reportPath(), "utf8"));
|
|
3760
3926
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
3761
3927
|
} catch {
|
|
3762
3928
|
return { agents: [], plans: [] };
|
|
@@ -3769,8 +3935,8 @@ function mergeReport(base, next) {
|
|
|
3769
3935
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
3770
3936
|
}
|
|
3771
3937
|
function saveReport(report) {
|
|
3772
|
-
|
|
3773
|
-
|
|
3938
|
+
mkdirSync4(dirname7(reportPath()), { recursive: true });
|
|
3939
|
+
writeFileSync7(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
3774
3940
|
`, { mode: 384 });
|
|
3775
3941
|
}
|
|
3776
3942
|
function confirm3(question) {
|
|
@@ -3788,7 +3954,7 @@ function kb2(bytes) {
|
|
|
3788
3954
|
function describe2(event) {
|
|
3789
3955
|
switch (event.type) {
|
|
3790
3956
|
case "agents":
|
|
3791
|
-
return event.agents.length ? `Found on this computer: ${event.agents.map((agent2) => agent2.name).join(", ")}` : "No agent found on this computer (looked for Claude Code, Codex and
|
|
3957
|
+
return event.agents.length ? `Found on this computer: ${event.agents.map((agent2) => agent2.name).join(", ")}` : "No agent found on this computer (looked for Claude Code, Codex, Cursor and Hermes).";
|
|
3792
3958
|
case "catalog":
|
|
3793
3959
|
return event.outcome === "read" ? ` agent catalog: ${event.agents} agent${event.agents === 1 ? "" : "s"}` : ` agent catalog: not read${event.detail ? ` (${event.detail})` : ""}; the skill looks agents up with search_catalog`;
|
|
3794
3960
|
case "skill":
|
|
@@ -3798,12 +3964,23 @@ function describe2(event) {
|
|
|
3798
3964
|
case "registered":
|
|
3799
3965
|
return ` agentsky MCP server registered for ${event.agent}${event.detail ? ` (${event.detail})` : ""}`;
|
|
3800
3966
|
case "planned":
|
|
3801
|
-
return ` would
|
|
3967
|
+
return ` ${event.agent}: would ${event.detail}`;
|
|
3802
3968
|
case "unsupported":
|
|
3803
3969
|
return null;
|
|
3804
3970
|
default:
|
|
3805
3971
|
return ` agentsky MCP server not registered for ${event.agent}: ${event.detail ?? event.outcome}`;
|
|
3806
3972
|
}
|
|
3973
|
+
case "env":
|
|
3974
|
+
switch (event.outcome) {
|
|
3975
|
+
case "written":
|
|
3976
|
+
return ` new shells get ${event.variable} for this login (open a new terminal, or run: . ${event.files[0]})`;
|
|
3977
|
+
case "planned":
|
|
3978
|
+
return ` would write ${event.variable} to ${event.files.join(", ")}`;
|
|
3979
|
+
case "cleared":
|
|
3980
|
+
return null;
|
|
3981
|
+
default:
|
|
3982
|
+
return ` ${event.variable} not set for new shells: ${event.detail ?? event.outcome}`;
|
|
3983
|
+
}
|
|
3807
3984
|
case "plan":
|
|
3808
3985
|
return ` ${event.provider} plan: ${event.outcome === "already" ? "already connected" : event.outcome}${event.detail ? ` (${event.detail})` : ""}`;
|
|
3809
3986
|
case "found": {
|
|
@@ -3893,8 +4070,11 @@ async function runSetup(opts) {
|
|
|
3893
4070
|
};
|
|
3894
4071
|
const agreed = async (question) => !interactive || confirm3(question);
|
|
3895
4072
|
const has = (step) => steps.includes(step);
|
|
3896
|
-
const
|
|
3897
|
-
|
|
4073
|
+
const named = opts.agents?.filter((id) => id !== "hermes");
|
|
4074
|
+
const agents = opts.agents?.length && !named?.length ? [] : selectLocalAgents(named);
|
|
4075
|
+
const hermes = !opts.agents?.length || opts.agents.includes("hermes") ? detectHermes() : null;
|
|
4076
|
+
const mcpOnly = hermes ? [hermes] : [];
|
|
4077
|
+
emit({ type: "agents", agents: [...agents, ...mcpOnly].map((agent2) => ({ id: agent2.id, name: agent2.name })) });
|
|
3898
4078
|
const needsToken = !opts.dryRun && (has("skill") || has("logins") || has("secrets") || has("import") && !opts.key);
|
|
3899
4079
|
if (needsToken && !apiToken()) {
|
|
3900
4080
|
if (opts.json) throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
|
|
@@ -3916,10 +4096,14 @@ async function runSetup(opts) {
|
|
|
3916
4096
|
held = new Map((claimed.body.skills ?? []).map((skill) => [skill.name, skill.fingerprint]));
|
|
3917
4097
|
};
|
|
3918
4098
|
await tell();
|
|
3919
|
-
if (has("skill") && agents.length && await agreed("Add the AgentSky skill and MCP server to these agents, so they can hand work to others?")) {
|
|
4099
|
+
if (has("skill") && (agents.length || mcpOnly.length) && await agreed("Add the AgentSky skill and MCP server to these agents, so they can hand work to others?")) {
|
|
3920
4100
|
const { catalog: catalog2, detail } = await readCatalog();
|
|
3921
4101
|
emit(catalog2 ? { type: "catalog", outcome: "read", agents: catalog2.length } : { type: "catalog", outcome: "unavailable", ...detail ? { detail } : {} });
|
|
3922
4102
|
const token = apiToken();
|
|
4103
|
+
const registerMcp = (client) => {
|
|
4104
|
+
const registered = token ? registerMcpServer(client, { token, mcpUrl: mcpUrlFor(apiBase()), dryRun: opts.dryRun }) : { agent: client.id, outcome: "skipped", detail: "not logged in" };
|
|
4105
|
+
emit({ type: "mcp", agent: client.id, outcome: registered.outcome, ...registered.detail ? { detail: registered.detail } : {} });
|
|
4106
|
+
};
|
|
3923
4107
|
for (const agent2 of agents) {
|
|
3924
4108
|
try {
|
|
3925
4109
|
const installed = installSkill(agent2, { dryRun: opts.dryRun, catalog: catalog2 });
|
|
@@ -3931,8 +4115,12 @@ async function runSetup(opts) {
|
|
|
3931
4115
|
run2.agents.push({ id: agent2.id, skill: "failed" });
|
|
3932
4116
|
continue;
|
|
3933
4117
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
4118
|
+
registerMcp(agent2);
|
|
4119
|
+
}
|
|
4120
|
+
for (const client of mcpOnly) registerMcp(client);
|
|
4121
|
+
if (token) {
|
|
4122
|
+
const shell = writeShellEnv(token, { dryRun: opts.dryRun });
|
|
4123
|
+
emit({ type: "env", variable: TOKEN_ENV, outcome: shell.outcome, files: shell.files, ...shell.detail ? { detail: shell.detail } : {} });
|
|
3936
4124
|
}
|
|
3937
4125
|
await tell();
|
|
3938
4126
|
}
|
|
@@ -4119,17 +4307,17 @@ function renderArtifact(artifact2) {
|
|
|
4119
4307
|
}
|
|
4120
4308
|
|
|
4121
4309
|
// src/skills-store.ts
|
|
4122
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
4123
|
-
import { basename as
|
|
4310
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync8 } from "fs";
|
|
4311
|
+
import { basename as basename8, join as join16, relative as relative4, resolve as resolve7 } from "path";
|
|
4124
4312
|
function skillBundle(dir) {
|
|
4125
4313
|
const root = resolve7(dir);
|
|
4126
|
-
const top =
|
|
4314
|
+
const top = basename8(root);
|
|
4127
4315
|
const files2 = [];
|
|
4128
4316
|
const walk = (path) => {
|
|
4129
4317
|
for (const name of readdirSync7(path).sort()) {
|
|
4130
|
-
const full =
|
|
4318
|
+
const full = join16(path, name);
|
|
4131
4319
|
if (statSync8(full).isDirectory()) walk(full);
|
|
4132
|
-
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes:
|
|
4320
|
+
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync15(full) });
|
|
4133
4321
|
}
|
|
4134
4322
|
};
|
|
4135
4323
|
walk(root);
|
|
@@ -4139,7 +4327,7 @@ function skillBundle(dir) {
|
|
|
4139
4327
|
async function uploadSkill(dir, displayName) {
|
|
4140
4328
|
const form = new FormData();
|
|
4141
4329
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
4142
|
-
form.append("displayName", displayName ??
|
|
4330
|
+
form.append("displayName", displayName ?? basename8(resolve7(dir)));
|
|
4143
4331
|
return apiForm("POST", "/skills", form);
|
|
4144
4332
|
}
|
|
4145
4333
|
async function listSkills() {
|
|
@@ -4168,8 +4356,8 @@ async function getSkillUsage(since) {
|
|
|
4168
4356
|
|
|
4169
4357
|
// src/task-export.ts
|
|
4170
4358
|
import { createHash as createHash6 } from "crypto";
|
|
4171
|
-
import { mkdirSync as
|
|
4172
|
-
import { dirname as
|
|
4359
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
4360
|
+
import { dirname as dirname8, join as join17 } from "path";
|
|
4173
4361
|
import { stringify as stringifyToml2 } from "smol-toml";
|
|
4174
4362
|
var TEXT_MAX = 2e4;
|
|
4175
4363
|
function scrub(text, names2) {
|
|
@@ -4457,14 +4645,14 @@ async function exportTaskHarbor(taskRef, outDir, opts) {
|
|
|
4457
4645
|
fileBytes.set(file.path, bytes);
|
|
4458
4646
|
}
|
|
4459
4647
|
const files2 = buildHarborTask(record, { ...opts, fileBytes });
|
|
4460
|
-
const
|
|
4648
|
+
const written2 = [];
|
|
4461
4649
|
for (const [path, content] of files2) {
|
|
4462
|
-
const target =
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4650
|
+
const target = join17(outDir, path);
|
|
4651
|
+
mkdirSync5(dirname8(target), { recursive: true });
|
|
4652
|
+
writeFileSync8(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
|
|
4653
|
+
written2.push(path);
|
|
4466
4654
|
}
|
|
4467
|
-
return { written, record };
|
|
4655
|
+
return { written: written2, record };
|
|
4468
4656
|
}
|
|
4469
4657
|
function parseRedactNames(values) {
|
|
4470
4658
|
return (values ?? []).map((value) => {
|
|
@@ -4806,19 +4994,19 @@ function renderThreadSummary(t) {
|
|
|
4806
4994
|
}
|
|
4807
4995
|
|
|
4808
4996
|
// src/harness.ts
|
|
4809
|
-
import { existsSync as
|
|
4810
|
-
import { dirname as
|
|
4997
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
4998
|
+
import { dirname as dirname9, join as join18, resolve as resolve8 } from "path";
|
|
4811
4999
|
var HARNESS_FILE = "harness.json";
|
|
4812
5000
|
function harnessFilePath(dir) {
|
|
4813
|
-
return
|
|
5001
|
+
return join18(resolve8(dir), HARNESS_FILE);
|
|
4814
5002
|
}
|
|
4815
5003
|
function readHarnessFile(dir) {
|
|
4816
5004
|
const path = harnessFilePath(dir);
|
|
4817
|
-
if (!
|
|
5005
|
+
if (!existsSync11(path)) {
|
|
4818
5006
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
4819
5007
|
}
|
|
4820
5008
|
try {
|
|
4821
|
-
return JSON.parse(
|
|
5009
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
4822
5010
|
} catch (err) {
|
|
4823
5011
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
4824
5012
|
}
|
|
@@ -4845,11 +5033,11 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
4845
5033
|
`;
|
|
4846
5034
|
function harnessInit(dir) {
|
|
4847
5035
|
const path = harnessFilePath(dir);
|
|
4848
|
-
if (
|
|
5036
|
+
if (existsSync11(path)) {
|
|
4849
5037
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
4850
5038
|
}
|
|
4851
|
-
|
|
4852
|
-
|
|
5039
|
+
mkdirSync6(dirname9(path), { recursive: true });
|
|
5040
|
+
writeFileSync9(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
4853
5041
|
`);
|
|
4854
5042
|
console.log(`Created ${path}`);
|
|
4855
5043
|
console.log(INIT_COMMENT);
|
|
@@ -5010,6 +5198,7 @@ auth.command("logout").description("forget the stored token (revoke it in Develo
|
|
|
5010
5198
|
run(async () => {
|
|
5011
5199
|
saveConfig({ ...loadConfig(), token: void 0, email: void 0 });
|
|
5012
5200
|
console.log(`Logged out. (Token revocation: Developer \u2192 API keys.)`);
|
|
5201
|
+
for (const line of syncLocalAccess(void 0)) console.log(line);
|
|
5013
5202
|
})
|
|
5014
5203
|
);
|
|
5015
5204
|
auth.command("whoami").description("who the current token acts as").action(
|
|
@@ -5558,7 +5747,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
|
|
|
5558
5747
|
run(async (ref, path, opts) => {
|
|
5559
5748
|
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
5560
5749
|
if (opts.out) {
|
|
5561
|
-
|
|
5750
|
+
writeFileSync10(opts.out, bytes);
|
|
5562
5751
|
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
5563
5752
|
} else process.stdout.write(bytes);
|
|
5564
5753
|
})
|
|
@@ -5575,7 +5764,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
|
|
|
5575
5764
|
console.error(`${meta.repo}: ${meta.commits.length} commit${meta.commits.length === 1 ? "" : "s"} since ${meta.base_branch || "the imported copy"} (${meta.base_commit.slice(0, 12)})`);
|
|
5576
5765
|
if (!patch.length) console.error("nothing changed");
|
|
5577
5766
|
else if (opts.out) {
|
|
5578
|
-
|
|
5767
|
+
writeFileSync10(opts.out, patch);
|
|
5579
5768
|
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
5580
5769
|
} else process.stdout.write(patch);
|
|
5581
5770
|
})
|
|
@@ -5720,7 +5909,7 @@ catalog.command("search [words...]").description("find an agent for a piece of w
|
|
|
5720
5909
|
Start one: sky session create @<handle> --message "<the job>"`);
|
|
5721
5910
|
})
|
|
5722
5911
|
);
|
|
5723
|
-
program.command("skill").description("the AgentSky skill for the agents on this machine").command("install").description("add the AgentSky skill to each installed agent (Claude Code, Codex, Cursor)").option("--agent <id...>", "only these agents: claude_code | codex | cursor").option("--dry-run", "say what would be written; write nothing").option("--json", "print the outcome per agent as JSON").action(
|
|
5912
|
+
program.command("skill").description("the AgentSky skill for the agents on this machine").command("install").description("add the AgentSky skill to each installed agent (Claude Code, Codex, Cursor)").option("--agent <id...>", "only these agents: claude_code | codex | cursor | hermes").option("--dry-run", "say what would be written; write nothing").option("--json", "print the outcome per agent as JSON").action(
|
|
5724
5913
|
run(async (opts) => {
|
|
5725
5914
|
const installs = selectLocalAgents(opts.agent).map((a) => installSkill(a, { dryRun: opts.dryRun }));
|
|
5726
5915
|
if (opts.json) {
|
|
@@ -5736,7 +5925,7 @@ program.command("skill").description("the AgentSky skill for the agents on this
|
|
|
5736
5925
|
})
|
|
5737
5926
|
);
|
|
5738
5927
|
program.command("setup [steps...]").description("bring this machine's agents to AgentSky: the skill and its MCP server, your plans, your skills and instructions, your tool keys").addHelpText("after", `
|
|
5739
|
-
Steps (all when none is named): ${SETUP_STEPS.join(" | ")}`).option("--key <key>", "a setup key from agentsky.dev/setup").option("--agent <id...>", "only these agents: claude_code | codex | cursor").option("--secret <name...>", "save these tool keys to your vault (asked one by one when omitted)").option("--no-claude", "leave the Claude plan alone").option("--plan <name...>", "connect only these plans: claude | chatgpt | cursor").option("--dry-run", "say what would be done; write and send nothing").option("-y, --yes", "do not ask before each step").option("--json", "print progress as one JSON object per line; never asks").action(
|
|
5928
|
+
Steps (all when none is named): ${SETUP_STEPS.join(" | ")}`).option("--key <key>", "a setup key from agentsky.dev/setup").option("--agent <id...>", "only these agents: claude_code | codex | cursor | hermes").option("--secret <name...>", "save these tool keys to your vault (asked one by one when omitted)").option("--no-claude", "leave the Claude plan alone").option("--plan <name...>", "connect only these plans: claude | chatgpt | cursor").option("--dry-run", "say what would be done; write and send nothing").option("-y, --yes", "do not ask before each step").option("--json", "print progress as one JSON object per line; never asks").action(
|
|
5740
5929
|
run(async (steps, opts) => {
|
|
5741
5930
|
const unknown = steps.find((step) => !SETUP_STEPS.includes(step));
|
|
5742
5931
|
if (unknown) throw new Error(`unknown step ${unknown} \u2014 expected ${SETUP_STEPS.join(" | ")}`);
|
|
@@ -5784,8 +5973,8 @@ secrets.command("unset <id> <key>").action(
|
|
|
5784
5973
|
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").action(
|
|
5785
5974
|
run(async (opts) => {
|
|
5786
5975
|
const path = resolve9(opts.file);
|
|
5787
|
-
if (!
|
|
5788
|
-
const name =
|
|
5976
|
+
if (!existsSync12(path)) {
|
|
5977
|
+
const name = basename9(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
5789
5978
|
scaffoldAgentFile(path, name);
|
|
5790
5979
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
5791
5980
|
}
|
|
@@ -6067,8 +6256,8 @@ files.command("rm <id>").description("delete a file (a session that already rece
|
|
|
6067
6256
|
);
|
|
6068
6257
|
files.command("download <id>").description("download a session file's bytes").requiredOption("-o, --out <path>", "where to write the bytes").action(
|
|
6069
6258
|
run(async (id, opts) => {
|
|
6070
|
-
const
|
|
6071
|
-
console.log(`Wrote ${
|
|
6259
|
+
const written2 = await downloadFileTo(id, opts.out);
|
|
6260
|
+
console.log(`Wrote ${written2} bytes to ${opts.out}`);
|
|
6072
6261
|
})
|
|
6073
6262
|
);
|
|
6074
6263
|
var task = program.command("task").description("a task's shared files and open requests (the task is its first session's id)");
|
|
@@ -6083,14 +6272,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
6083
6272
|
run(async (taskId, path, opts) => {
|
|
6084
6273
|
const bytes = await readTaskFile(taskId, path);
|
|
6085
6274
|
if (opts.out) {
|
|
6086
|
-
|
|
6275
|
+
writeFileSync10(opts.out, bytes);
|
|
6087
6276
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6088
6277
|
} else process.stdout.write(bytes);
|
|
6089
6278
|
})
|
|
6090
6279
|
);
|
|
6091
6280
|
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(
|
|
6092
6281
|
run(async (taskId, path, local) => {
|
|
6093
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
6282
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync17(local), mediaTypeFor(local));
|
|
6094
6283
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
6095
6284
|
})
|
|
6096
6285
|
);
|
|
@@ -6104,9 +6293,9 @@ task.command("export <task>").description("write the task's session record as a
|
|
|
6104
6293
|
run(async (taskId, opts) => {
|
|
6105
6294
|
const tier = opts.tier === "gold" || opts.tier === "exploratory" ? opts.tier : "silver";
|
|
6106
6295
|
const outDir = opts.out ?? `./harbor-${taskId.replace(/[^A-Za-z0-9_-]/g, "_")}`;
|
|
6107
|
-
const { written, record } = await exportTaskHarbor(taskId, outDir, { tier, redactNames: parseRedactNames(opts.redact) });
|
|
6108
|
-
console.log(`Wrote ${
|
|
6109
|
-
if (!
|
|
6296
|
+
const { written: written2, record } = await exportTaskHarbor(taskId, outDir, { tier, redactNames: parseRedactNames(opts.redact) });
|
|
6297
|
+
console.log(`Wrote ${written2.length} files to ${outDir} (${record.threads.length} thread${record.threads.length === 1 ? "" : "s"}, ${record.files.length} task file${record.files.length === 1 ? "" : "s"}${record.goal ? ", goal rubric" : ", no goal: derived rubric"})`);
|
|
6298
|
+
if (!written2.includes("environment/Dockerfile")) console.log("environment/: not resettable from the record \u2014 see environment/README.md before running with Harbor");
|
|
6110
6299
|
})
|
|
6111
6300
|
);
|
|
6112
6301
|
task.command("show <task>").description("the task's sessions and the requests waiting on you (questions, approvals, apps to connect)").option("--json", "print the task as JSON").action(
|
|
@@ -6244,7 +6433,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
6244
6433
|
run(async (id, path, opts) => {
|
|
6245
6434
|
const bytes = await readArtifactFile(id, path);
|
|
6246
6435
|
if (opts.out) {
|
|
6247
|
-
|
|
6436
|
+
writeFileSync10(opts.out, bytes);
|
|
6248
6437
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6249
6438
|
} else process.stdout.write(bytes);
|
|
6250
6439
|
})
|
|
@@ -6542,7 +6731,7 @@ usageOptions(
|
|
|
6542
6731
|
run(async (opts) => {
|
|
6543
6732
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
6544
6733
|
if (opts.out) {
|
|
6545
|
-
|
|
6734
|
+
writeFileSync10(opts.out, bytes);
|
|
6546
6735
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6547
6736
|
} else {
|
|
6548
6737
|
process.stdout.write(bytes);
|
|
@@ -7093,7 +7282,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
7093
7282
|
}));
|
|
7094
7283
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
7095
7284
|
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) => {
|
|
7096
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
7285
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-PDIOL2TC.js");
|
|
7097
7286
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
7098
7287
|
const scopes = parseContentScopes(opts.capture);
|
|
7099
7288
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -7128,7 +7317,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
7128
7317
|
}));
|
|
7129
7318
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
7130
7319
|
try {
|
|
7131
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
7320
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-PDIOL2TC.js");
|
|
7132
7321
|
const chunks = [];
|
|
7133
7322
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
7134
7323
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -7141,14 +7330,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
7141
7330
|
}
|
|
7142
7331
|
});
|
|
7143
7332
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
7144
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
7333
|
+
const { readState, flush, grantSender } = await import("./trace-PDIOL2TC.js");
|
|
7145
7334
|
const state = readState();
|
|
7146
7335
|
if (!state) throw new Error("no trace is running here");
|
|
7147
7336
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
7148
7337
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
7149
7338
|
}));
|
|
7150
7339
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
7151
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
7340
|
+
const { readState, spoolStatus } = await import("./trace-PDIOL2TC.js");
|
|
7152
7341
|
const state = readState();
|
|
7153
7342
|
if (!state) {
|
|
7154
7343
|
console.log("No trace is running here.");
|
|
@@ -7158,7 +7347,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
7158
7347
|
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`);
|
|
7159
7348
|
}));
|
|
7160
7349
|
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) => {
|
|
7161
|
-
const { readState } = await import("./trace-
|
|
7350
|
+
const { readState } = await import("./trace-PDIOL2TC.js");
|
|
7162
7351
|
const ref = id ?? readState()?.trace_id;
|
|
7163
7352
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
7164
7353
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -7185,7 +7374,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
7185
7374
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
7186
7375
|
}));
|
|
7187
7376
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
7188
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
7377
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-PDIOL2TC.js");
|
|
7189
7378
|
const state = readState();
|
|
7190
7379
|
if (!state) throw new Error("no trace is running here");
|
|
7191
7380
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -7194,8 +7383,8 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
7194
7383
|
clearState();
|
|
7195
7384
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
7196
7385
|
}));
|
|
7197
|
-
traceCmd.command("sync [payload]").description("upload this machine's Claude Code and
|
|
7198
|
-
const lib = await import("./trace-library-
|
|
7386
|
+
traceCmd.command("sync [payload]").description("upload this machine's agent sessions (Claude Code, Codex, Cursor, Gemini CLI and every other agent `--agents` lists) 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("--import <export>", "add a downloaded cloud export (claude.ai, ChatGPT, Gemini, Grok, DeepSeek \u2026: its .zip, folder or file) so sync uploads it (repeatable)", collect).option("--agents", "list the agents sync reads and which are on this machine").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) => {
|
|
7387
|
+
const lib = await import("./trace-library-TA3QUBHJ.js");
|
|
7199
7388
|
if (opts.hook) {
|
|
7200
7389
|
try {
|
|
7201
7390
|
let raw = payload ?? "";
|
|
@@ -7236,17 +7425,28 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
7236
7425
|
};
|
|
7237
7426
|
lib.saveSyncConfig(next);
|
|
7238
7427
|
console.log(`Trace sync is ${next.enabled ? "on" : "off"} on this machine. It reads only:`);
|
|
7239
|
-
for (const folder of lib.sourceFolders()) console.log(` ${folder}`);
|
|
7428
|
+
for (const folder of await lib.sourceFolders()) console.log(` ${folder}`);
|
|
7240
7429
|
console.log("and sends each session, redacted here first, to:");
|
|
7241
7430
|
for (const rule of next.rules) console.log(` folder path contains "${rule.match}" \u2192 ${rule.workspace}`);
|
|
7242
7431
|
console.log(` anything else \u2192 ${next.fallback ?? "stays on this machine"}`);
|
|
7243
7432
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
7244
7433
|
}
|
|
7434
|
+
if (opts.agents) {
|
|
7435
|
+
for (const agent2 of await lib.supportedAgents()) {
|
|
7436
|
+
const how = agent2.kind === "export" ? "cloud export: sky trace sync --import <file>" : agent2.found ? "found on this machine" : "not on this machine";
|
|
7437
|
+
console.log(` ${agent2.name.padEnd(46)} ${how}`);
|
|
7438
|
+
}
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7441
|
+
for (const input of opts.import ?? []) {
|
|
7442
|
+
const imported = await lib.importExport(input);
|
|
7443
|
+
console.log(`Added ${imported.sessions} ${imported.agent} session${imported.sessions === 1 ? "" : "s"} from ${input}.`);
|
|
7444
|
+
}
|
|
7245
7445
|
if (opts.installSchedule) {
|
|
7246
|
-
const { join:
|
|
7247
|
-
const { homedir:
|
|
7446
|
+
const { join: join19 } = await import("path");
|
|
7447
|
+
const { homedir: homedir10 } = await import("os");
|
|
7248
7448
|
const { execFileSync } = await import("child_process");
|
|
7249
|
-
const log =
|
|
7449
|
+
const log = join19(homedir10(), ".config", "sky", "trace-sync.log");
|
|
7250
7450
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
7251
7451
|
try {
|
|
7252
7452
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -7256,11 +7456,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
7256
7456
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
7257
7457
|
}
|
|
7258
7458
|
if (opts.status) {
|
|
7259
|
-
const { readFileSync:
|
|
7260
|
-
const { join:
|
|
7459
|
+
const { readFileSync: readFileSync18 } = await import("fs");
|
|
7460
|
+
const { join: join19 } = await import("path");
|
|
7261
7461
|
let state = { sessions: {} };
|
|
7262
7462
|
try {
|
|
7263
|
-
state = JSON.parse(
|
|
7463
|
+
state = JSON.parse(readFileSync18(join19(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
7264
7464
|
} catch {
|
|
7265
7465
|
}
|
|
7266
7466
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -7293,7 +7493,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
7293
7493
|
})();
|
|
7294
7494
|
});
|
|
7295
7495
|
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) => {
|
|
7296
|
-
const { libraryRequest } = await import("./trace-library-
|
|
7496
|
+
const { libraryRequest } = await import("./trace-library-TA3QUBHJ.js");
|
|
7297
7497
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7298
7498
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7299
7499
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -7310,7 +7510,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
7310
7510
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
7311
7511
|
}));
|
|
7312
7512
|
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) => {
|
|
7313
|
-
const { libraryRequest } = await import("./trace-library-
|
|
7513
|
+
const { libraryRequest } = await import("./trace-library-TA3QUBHJ.js");
|
|
7314
7514
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7315
7515
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7316
7516
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|