@agentsky/sky 2.7.3 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,10 +16,10 @@ import {
16
16
  skillRefSchema,
17
17
  splitMcpServers,
18
18
  supersededBy
19
- } from "./chunk-IC4N2ZPL.js";
19
+ } from "./chunk-NRTKH367.js";
20
20
 
21
21
  // src/index.ts
22
- import { existsSync as existsSync7, writeFileSync as writeFileSync6 } from "fs";
22
+ import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
23
23
  import { basename as basename3, resolve as resolve4 } from "path";
24
24
  import { createInterface as createInterface4 } from "readline";
25
25
  import { Command } from "commander";
@@ -64,7 +64,7 @@ function currentUniverse() {
64
64
  }
65
65
 
66
66
  // src/version.ts
67
- var CLI_VERSION = "2.7.3";
67
+ var CLI_VERSION = "2.9.0";
68
68
  var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
69
69
 
70
70
  // src/api.ts
@@ -148,6 +148,24 @@ async function downloadFile(fileId) {
148
148
  if (!res.ok) await parseError(res);
149
149
  return { bytes: new Uint8Array(await res.arrayBuffer()), mediaType: res.headers.get("content-type") ?? "application/octet-stream" };
150
150
  }
151
+ async function apiBytes(method, path, body) {
152
+ const res = await fetch(`${apiBase()}${path}`, {
153
+ method,
154
+ headers: {
155
+ "user-agent": USER_AGENT,
156
+ ...authHeaders(),
157
+ ...body ? { "content-type": body.mediaType } : {}
158
+ },
159
+ body: body ? new Uint8Array(body.bytes) : void 0
160
+ });
161
+ if (!res.ok) await parseError(res);
162
+ const bytes = new Uint8Array(await res.arrayBuffer());
163
+ return {
164
+ bytes,
165
+ mediaType: res.headers.get("content-type") ?? "application/octet-stream",
166
+ json: () => JSON.parse(new TextDecoder().decode(bytes))
167
+ };
168
+ }
151
169
  async function resolveSessionRef(ref) {
152
170
  if (ref.startsWith("sess-")) return ref;
153
171
  const { agent: agent2 } = await api(
@@ -167,7 +185,7 @@ async function resolveSessionRef(ref) {
167
185
  return agent2.defaultSessionId;
168
186
  }
169
187
  async function* streamEvents(sessionId, signal) {
170
- const { parseSseStream } = await import("./src-S76Y7GHZ.js");
188
+ const { parseSseStream } = await import("./src-OEZ7BT2L.js");
171
189
  const res = await fetch(
172
190
  `${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
173
191
  {
@@ -390,7 +408,7 @@ function scaffoldAgentFile(path, name) {
390
408
  path,
391
409
  `# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
392
410
  name = "${name}"
393
- # type = "hermes" # hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
411
+ # type = "pi" # pi (default) | hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
394
412
  # llm = "deepseek-flash" # must be compatible with type
395
413
  # reasoning_effort = "high" # per harness \u2014 ${reasoningEffortDocLine()}
396
414
  # capabilities = []
@@ -608,7 +626,7 @@ async function createClone(local, opts) {
608
626
  if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
609
627
  if (local.droppedServers.length) {
610
628
  console.log(
611
- `Dropped (need credentials \u2014 not supported yet): ${local.droppedServers.map((n) => `${n} MCP`).join(", ")}`
629
+ `Left out (need a key): ${local.droppedServers.map((n) => `${n} MCP`).join(", ")}. Run \`sky setup secrets\` to choose which keys to save to your vault.`
612
630
  );
613
631
  }
614
632
  if (opts.dryRun) {
@@ -722,7 +740,7 @@ async function syncClone(local, link, opts) {
722
740
  }
723
741
  if (local.droppedServers.length) {
724
742
  console.log(
725
- ` dropped (need credentials \u2014 not supported yet): ${local.droppedServers.map((n) => `${n} MCP`).join(", ")}`
743
+ ` left out (need a key): ${local.droppedServers.map((n) => `${n} MCP`).join(", ")}. Run \`sky setup secrets\` to choose which keys to save to your vault.`
726
744
  );
727
745
  }
728
746
  writeLink(local.configRoot, { ...link, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
@@ -827,10 +845,10 @@ function listClaudeCodeSessions(limit) {
827
845
  continue;
828
846
  }
829
847
  const workspace = decodeWorkspace(encoded);
830
- for (const entry of entries) {
831
- if (!entry.endsWith(".jsonl")) continue;
832
- const sessionId = entry.slice(0, -6);
833
- const mainFile = join3(projectDir, entry);
848
+ for (const entry2 of entries) {
849
+ if (!entry2.endsWith(".jsonl")) continue;
850
+ const sessionId = entry2.slice(0, -6);
851
+ const mainFile = join3(projectDir, entry2);
834
852
  let stat;
835
853
  try {
836
854
  stat = statSync(mainFile);
@@ -876,13 +894,13 @@ function listCodexSessions(limit) {
876
894
  } catch {
877
895
  return;
878
896
  }
879
- for (const entry of entries) {
880
- const full = join3(dir, entry.name);
881
- if (entry.isDirectory()) {
897
+ for (const entry2 of entries) {
898
+ const full = join3(dir, entry2.name);
899
+ if (entry2.isDirectory()) {
882
900
  walk(full);
883
901
  continue;
884
902
  }
885
- if (!entry.isFile() || !entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl")) continue;
903
+ if (!entry2.isFile() || !entry2.name.startsWith("rollout-") || !entry2.name.endsWith(".jsonl")) continue;
886
904
  try {
887
905
  const first = readPrefix(full).split("\n", 1)[0];
888
906
  const meta = JSON.parse(first);
@@ -1251,92 +1269,179 @@ import { mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync a
1251
1269
  import { dirname as dirname4, join as join8 } from "path";
1252
1270
  import { createInterface as createInterface3 } from "readline";
1253
1271
 
1254
- // src/setup-local.ts
1255
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
1256
- import { homedir as homedir5 } from "os";
1257
- import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
1258
- import { parse as parseToml2 } from "smol-toml";
1272
+ // src/mcp-register.ts
1273
+ import { spawnSync } from "child_process";
1259
1274
 
1260
1275
  // src/skill.ts
1261
1276
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1262
1277
  import { join as join5 } from "path";
1278
+
1279
+ // src/catalog-file.ts
1280
+ var CATALOG_FILE = "catalog.md";
1281
+ var CATEGORY_ORDER = ["websites", "content", "video", "code", "research", "general"];
1282
+ function categoryOf(agent2) {
1283
+ return CATEGORY_ORDER.find((category) => agent2.categories.includes(category)) ?? agent2.categories[0] ?? "general";
1284
+ }
1285
+ function describe(value) {
1286
+ 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)}`);
1287
+ return parts.length ? parts.join(", ") : null;
1288
+ }
1289
+ function entry(agent2) {
1290
+ const engine = agent2.engine_type ? ` \xB7 engine ${agent2.engine_type}` : "";
1291
+ const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
1292
+ if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
1293
+ if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
1294
+ const { mcp_servers: mcpServers, ...rest } = agent2.requirements;
1295
+ if (typeof mcpServers === "boolean") lines.push(mcpServers ? "Can use MCP servers." : "Cannot use MCP servers.");
1296
+ const details = describe(rest);
1297
+ if (details) lines.push(`Details: ${details}`);
1298
+ const pricing = describe(agent2.pricing);
1299
+ if (pricing) lines.push(`Pricing: ${pricing}`);
1300
+ return lines.join("\n");
1301
+ }
1302
+ function renderCatalog(agents, generatedAt) {
1303
+ const header = [
1304
+ "# AgentSky agent catalog",
1305
+ "",
1306
+ `Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
1307
+ "Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
1308
+ ];
1309
+ if (!agents.length) return [...header, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
1310
+ const own = agents.filter((agent2) => agent2.visibility === "workspace");
1311
+ const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
1312
+ const sections = [];
1313
+ if (own.length) sections.push(["## Your workspace's agents", ...own.map(entry)].join("\n\n"));
1314
+ const groups = /* @__PURE__ */ new Map();
1315
+ for (const agent2 of shared) groups.set(categoryOf(agent2), [...groups.get(categoryOf(agent2)) ?? [], agent2]);
1316
+ const ordered = [...groups.keys()].sort((a, b) => {
1317
+ const rank = (category) => CATEGORY_ORDER.includes(category) ? CATEGORY_ORDER.indexOf(category) : CATEGORY_ORDER.length;
1318
+ return rank(a) - rank(b) || a.localeCompare(b);
1319
+ });
1320
+ for (const category of ordered) {
1321
+ const title = category.charAt(0).toUpperCase() + category.slice(1);
1322
+ sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
1323
+ }
1324
+ return [...header, "", sections.join("\n\n"), ""].join("\n");
1325
+ }
1326
+
1327
+ // src/skill.ts
1263
1328
  var SKILL_NAME = "agentsky";
1264
1329
  var SKILL_MARKER = ".agentsky.json";
1265
1330
  function skillDocument() {
1266
1331
  return `---
1267
1332
  name: ${SKILL_NAME}
1268
- description: Hand part of a job to another agent running in the cloud through AgentSky, and bring its result back. Use when the person asks for another agent or a named engine (Claude Code, Codex, Hermes, OpenClaw and others) to do, test or review something, or when independent work could run in parallel.
1333
+ description: "Hire cloud agents from the AgentSky catalog (included as ${CATALOG_FILE}) and coordinate them on one task. Use when the person names an agent by @handle, asks to have another agent (or another engine such as Codex, Claude Code, Gemini or Hermes) do, test or review part of the work, wants work to keep running in the cloud, or the work splits into independent parts that can run at the same time. Works through the agentsky MCP server, or the sky command where those tools are missing."
1269
1334
  ---
1270
1335
 
1271
1336
  # Working with other agents through AgentSky
1272
1337
 
1273
- AgentSky runs long-lived agents in the cloud. With the \`sky\` command you can
1274
- find one, give it a job, and read what it produced. You stay responsible for
1275
- the result.
1276
-
1277
- If the \`agentsky\` MCP tools are available in this session (\`search_catalog\`,
1278
- \`start_session\`, \`read_session\`, \`send_message\`), use them instead of the
1279
- commands below; they do the same things.
1280
-
1281
- ## Before you start
1338
+ AgentSky runs long-lived agents in the cloud. You can hire them from its
1339
+ catalog, give each a part of the work, and collect what they produce. Agents you
1340
+ hire for the same piece of work share one task: a shared \`task/\` folder they all
1341
+ read and write, listed as one task in the person's AgentSky account. You stay
1342
+ responsible for the result.
1282
1343
 
1283
- Run \`sky auth whoami\`. If it says you are not logged in, ask the person to run
1284
- \`sky auth login\` and stop there. Never ask them for a token.
1344
+ Everything below is a tool of the \`agentsky\` MCP server. If those tools are
1345
+ missing in this session, use the \`sky\` commands under "Without the MCP tools"
1346
+ at the end; they do the same things, one agent at a time.
1285
1347
 
1286
- ## When to hand work over
1348
+ ## When to hire
1287
1349
 
1288
- - When the person asks another agent to do, test or review something, do that.
1289
- Honor an agent or engine they name.
1290
- - Otherwise, hand over work another agent is better suited to, or independent
1291
- parts that can run at the same time as your own.
1292
- - Do the work yourself when it is small, when it depends on what you already
1293
- have in front of you, or when explaining it would take as long as doing it.
1350
+ - When the person names an agent (\`@handle\`) or an engine, or asks another agent
1351
+ to test, review or do part of the work, do that. Do not substitute another one
1352
+ silently.
1353
+ - Otherwise hire for work another agent is better suited to than you are, for
1354
+ independent parts that can run at the same time, or for work that should keep
1355
+ going after this conversation ends.
1356
+ - Do the work yourself when it is small, when it depends on what you already have
1357
+ in front of you, or when explaining it would take as long as doing it.
1358
+ - Every hire spends the person's AgentSky balance. Say what you are about to hire
1359
+ and roughly what it may cost before the first one, unless they already asked
1360
+ for it.
1294
1361
 
1295
1362
  ## How to pick
1296
1363
 
1297
- \`sky catalog search <words for the work> --json\`
1364
+ Read \`${CATALOG_FILE}\` in this skill's folder. It lists every agent you can hire,
1365
+ your own workspace's first, then by category: its \`@handle\`, its engine, what it
1366
+ does and does not do, whether it can use MCP servers, and how it is paid. Pick by
1367
+ the work, not by the name, and respect what an agent says it does not do.
1298
1368
 
1299
- Each result has a \`handle\`, what the agent does, its \`engine_type\`, its
1300
- categories, how it is paid, and what it needs. If the person names an engine,
1301
- match its \`engine_type\` and do not substitute another one silently. Run the
1302
- command with no words to see every agent. If nothing fits, do the work
1303
- yourself and say so.
1369
+ Call \`search_catalog\` (with words for the work, or none to list everything)
1370
+ when \`${CATALOG_FILE}\` is missing, when nothing in it fits, or when the person names
1371
+ a handle it does not list: the catalog file is a snapshot from when the skill
1372
+ was installed. If nothing fits, do the work yourself and say so.
1304
1373
 
1305
1374
  ## How to brief
1306
1375
 
1307
- \`sky session create @<handle> --title "<short name>" --message "<brief>" --json\`
1376
+ Start the first agent with \`start_session\`:
1308
1377
 
1309
- It prints the new session's \`id\`. The agent starts working at once, on its own
1310
- computer in the cloud. It cannot see this conversation or this machine, so a
1311
- brief is one self-contained message that states:
1378
+ - \`agent_id\`: the handle, with its \`@\`.
1379
+ - \`message\`: the brief.
1380
+ - \`title\`: a short name for its part.
1381
+ - \`budget_cents\`: the most it may spend, in US cents. Always set one. When it is
1382
+ reached the agent pauses instead of spending more.
1383
+ - \`idempotency_key\`: a new unique value for this hire. Reuse it only to retry
1384
+ the exact same call after a timeout.
1312
1385
 
1313
- 1. what to produce,
1314
- 2. what "done" means,
1315
- 3. anything it must not do.
1386
+ The agent cannot see this conversation or your files. A brief is one
1387
+ self-contained paragraph that says:
1316
1388
 
1317
- To give it a file, add \`--file <path>\`. Never put a password, key or token in a
1318
- brief.
1389
+ 1. what to produce;
1390
+ 2. where to write it: a path under \`task/<its part>/\`;
1391
+ 3. what "done" means;
1392
+ 4. anything it must not do.
1319
1393
 
1320
- ## How to collect
1394
+ Never put a password, key or token in a brief.
1321
1395
 
1322
- \`sky session read <session id> --wait --json\`
1396
+ Its task id is the first session's id. Start every other agent for the same work
1397
+ with that \`task_id\`, so it sees the same \`task/\` folder and the person sees one
1398
+ task. To hand an agent a file, write it with \`write_task_file\` before you brief
1399
+ it, and name its path in the brief.
1323
1400
 
1324
- It waits until the agent finishes its turn and prints its messages, with
1325
- \`finished\` true or false. If \`finished\` is false the wait timed out: run it
1326
- again. A first turn can take a minute to begin while the agent's computer
1327
- starts.
1401
+ ## How to supervise
1328
1402
 
1329
- To correct it, answer it or ask for more:
1403
+ - Call \`read_session\` with the \`session_id\`, \`cursor\` and \`message_id\` that
1404
+ \`start_session\` or \`send_message\` returned and \`wait_seconds: 50\`. Call again
1405
+ with the returned cursor until \`turn_finished\` is true, and at once whenever
1406
+ \`has_more\` is true. The first turn also boots the agent's sandbox, which can
1407
+ take a minute.
1408
+ - Run independent agents at the same time: start them all, then read each.
1409
+ - To steer or answer an agent, \`send_message\` once its turn is finished.
1410
+ - If \`get_task\` shows a request waiting on the person (a question, an approval,
1411
+ or an app to connect with its link), show it to them and pass their answer with
1412
+ \`answer_request\`. Never approve spending or access on their behalf.
1413
+ - An agent that finishes its first turn with no reply did not start properly:
1414
+ \`stop_session\` it and hire again, or do that part yourself.
1330
1415
 
1331
- \`sky session message <session id> "<text>" --detach\`
1416
+ ## How to finish
1332
1417
 
1333
- then read again. An agent saying it is done is not evidence: check what it
1334
- produced before you rely on it.
1418
+ 1. \`collect_result\` for each agent: its last reply and the files it wrote.
1419
+ 2. Read the files with \`read_task_file\` and check each one exists and does what
1420
+ was asked. An agent saying "done" is not evidence.
1421
+ 3. Put the pieces together.
1422
+ 4. If the person should be able to open or share the result, \`publish_artifact\`
1423
+ the files; \`share_artifact\` only when they asked for a link.
1424
+ 5. \`stop_session\` every agent you no longer need.
1425
+ 6. Tell the person what was done, which agent did which part, and what it cost
1426
+ (\`get_task\` reports the task's cost).
1335
1427
 
1336
- ## How to finish
1428
+ ## Without the MCP tools
1429
+
1430
+ Run \`sky auth whoami\`. If it says you are not logged in, ask the person to run
1431
+ \`sky auth login\` and stop there. Never ask them for a token.
1337
1432
 
1338
- Tell the person what was done and which agent did which part. Every agent you
1339
- start is paid for by the person, so start only what the job needs.
1433
+ - Pick: \`sky catalog search <words for the work> --json\`. Each result has a
1434
+ \`handle\`, what the agent does, its \`engine_type\`, its categories, how it is
1435
+ paid, and what it needs. Run it with no words to see every agent.
1436
+ - Brief: \`sky session create @<handle> --title "<short name>" --message "<brief>" --json\`
1437
+ prints the new session's \`id\`. To give it a file, add \`--file <path>:/<name>\`:
1438
+ the file is in the agent's working directory under that name, read-only, so
1439
+ call it by that name in the brief.
1440
+ - Collect: \`sky session read <session id> --wait --json\` waits until the agent
1441
+ finishes its turn and prints its messages, with \`finished\` true or false. If
1442
+ \`finished\` is false the wait timed out: run it again. To correct it, answer it
1443
+ or ask for more, \`sky session message <session id> "<text>" --detach\`, then
1444
+ read again.
1340
1445
  `;
1341
1446
  }
1342
1447
  function installedVersion(dir) {
@@ -1354,16 +1459,71 @@ function installSkill(agent2, opts = {}) {
1354
1459
  const dir = join5(agent2.skillsDir, SKILL_NAME);
1355
1460
  const previous = installedVersion(dir);
1356
1461
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
1357
- if (outcome !== "current" && !opts.dryRun) {
1462
+ if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
1463
+ if (outcome !== "current") {
1358
1464
  mkdirSync2(dir, { recursive: true });
1359
1465
  writeFileSync4(join5(dir, "SKILL.md"), skillDocument());
1360
1466
  writeFileSync4(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
1361
1467
  `);
1362
1468
  }
1469
+ if (opts.catalog) {
1470
+ mkdirSync2(dir, { recursive: true });
1471
+ writeFileSync4(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
1472
+ }
1363
1473
  return { agent: agent2.id, path: dir, outcome };
1364
1474
  }
1365
1475
 
1476
+ // src/mcp-register.ts
1477
+ var MCP_SERVER_NAME = SKILL_NAME;
1478
+ var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
1479
+ function systemCommandRunner() {
1480
+ return {
1481
+ has: (command) => spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0,
1482
+ run: (command, args) => {
1483
+ const result = spawnSync(command, args, { encoding: "utf8" });
1484
+ return { status: result.status, stderr: result.stderr ?? "" };
1485
+ }
1486
+ };
1487
+ }
1488
+ function mcpUrlFor(apiBase2) {
1489
+ return `${apiBase2.replace(/\/$/, "")}/mcp`;
1490
+ }
1491
+ function mcpCommands(agent2, opts) {
1492
+ if (agent2 === "claude_code") {
1493
+ return {
1494
+ command: "claude",
1495
+ remove: ["mcp", "remove", "--scope", "user", MCP_SERVER_NAME],
1496
+ add: ["mcp", "add", "--scope", "user", "--transport", "http", MCP_SERVER_NAME, opts.mcpUrl, "--header", `Authorization: Bearer ${opts.token}`]
1497
+ };
1498
+ }
1499
+ if (agent2 === "codex") {
1500
+ return {
1501
+ command: "codex",
1502
+ remove: ["mcp", "remove", MCP_SERVER_NAME],
1503
+ add: ["mcp", "add", MCP_SERVER_NAME, "--url", opts.mcpUrl, "--bearer-token-env-var", CODEX_TOKEN_ENV]
1504
+ };
1505
+ }
1506
+ return null;
1507
+ }
1508
+ function redact(text, token) {
1509
+ return text.split(token).join(`${token.slice(0, 10)}\u2026`);
1510
+ }
1511
+ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
1512
+ const commands = mcpCommands(agent2.id, opts);
1513
+ if (!commands) return { agent: agent2.id, outcome: "unsupported" };
1514
+ if (!runner.has(commands.command)) return { agent: agent2.id, outcome: "no_command", detail: `${commands.command} is not on PATH` };
1515
+ if (opts.dryRun) return { agent: agent2.id, outcome: "planned", detail: redact([commands.command, ...commands.add].join(" "), opts.token) };
1516
+ runner.run(commands.command, commands.remove);
1517
+ const added = runner.run(commands.command, commands.add);
1518
+ if (added.status !== 0) return { agent: agent2.id, outcome: "failed", detail: redact(added.stderr.trim() || `exit ${added.status}`, opts.token) };
1519
+ return agent2.id === "codex" ? { agent: agent2.id, outcome: "registered", detail: `set ${CODEX_TOKEN_ENV} to an AgentSky API token in the shell that runs codex` } : { agent: agent2.id, outcome: "registered" };
1520
+ }
1521
+
1366
1522
  // src/setup-local.ts
1523
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
1524
+ import { homedir as homedir5 } from "os";
1525
+ import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
1526
+ import { parse as parseToml2 } from "smol-toml";
1367
1527
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
1368
1528
  var MAX_IMPORT_DEPTH = 5;
1369
1529
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -1377,12 +1537,12 @@ function sensitive(name) {
1377
1537
  }
1378
1538
  function skillFiles(root, prefix) {
1379
1539
  const files2 = [];
1380
- for (const entry of readdirSync2(root, { withFileTypes: true })) {
1381
- const path = join6(root, entry.name);
1382
- if (entry.isDirectory()) {
1383
- if (!SKIPPED_DIRS.has(entry.name)) files2.push(...skillFiles(path, `${prefix}/${entry.name}`));
1384
- } else if (entry.isFile() && !sensitive(entry.name) && statSync2(path).size <= MAX_SKILL_FILE_BYTES) {
1385
- files2.push({ path: `${prefix}/${entry.name}`, bytes: new Uint8Array(readFileSync7(path)) });
1540
+ for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
1541
+ const path = join6(root, entry2.name);
1542
+ if (entry2.isDirectory()) {
1543
+ if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
1544
+ } else if (entry2.isFile() && !sensitive(entry2.name) && statSync2(path).size <= MAX_SKILL_FILE_BYTES) {
1545
+ files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(readFileSync7(path)) });
1386
1546
  }
1387
1547
  }
1388
1548
  return files2;
@@ -1392,10 +1552,10 @@ function readLocalSkills(agents) {
1392
1552
  const ignored = [];
1393
1553
  for (const agent2 of agents) {
1394
1554
  if (!existsSync5(agent2.skillsDir)) continue;
1395
- for (const entry of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
1396
- const dir = join6(agent2.skillsDir, entry.name);
1397
- let isDir = entry.isDirectory();
1398
- if (entry.isSymbolicLink()) {
1555
+ for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
1556
+ const dir = join6(agent2.skillsDir, entry2.name);
1557
+ let isDir = entry2.isDirectory();
1558
+ if (entry2.isSymbolicLink()) {
1399
1559
  try {
1400
1560
  isDir = statSync2(dir).isDirectory();
1401
1561
  } catch {
@@ -1413,7 +1573,7 @@ function readLocalSkills(agents) {
1413
1573
  ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
1414
1574
  continue;
1415
1575
  }
1416
- const files2 = skillFiles(dir, entry.name);
1576
+ const files2 = skillFiles(dir, entry2.name);
1417
1577
  skills.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
1418
1578
  }
1419
1579
  }
@@ -1479,8 +1639,8 @@ function bearerOf(def) {
1479
1639
  if (typeof def.bearer_token === "string" && def.bearer_token.trim()) return def.bearer_token.trim();
1480
1640
  const headers = def.headers ?? def.http_headers;
1481
1641
  if (!headers || typeof headers !== "object") return null;
1482
- const entry = Object.entries(headers).find(([name]) => name.toLowerCase() === "authorization");
1483
- const match = typeof entry?.[1] === "string" ? /^Bearer\s+(\S+)$/i.exec(entry[1].trim()) : null;
1642
+ const entry2 = Object.entries(headers).find(([name]) => name.toLowerCase() === "authorization");
1643
+ const match = typeof entry2?.[1] === "string" ? /^Bearer\s+(\S+)$/i.exec(entry2[1].trim()) : null;
1484
1644
  return match?.[1] ?? null;
1485
1645
  }
1486
1646
  function serversOf(agent2, home) {
@@ -1725,12 +1885,25 @@ function confirm3(question) {
1725
1885
  function kb2(bytes) {
1726
1886
  return `${Math.max(1, Math.round(bytes / 1024))} KB`;
1727
1887
  }
1728
- function describe(event) {
1888
+ function describe2(event) {
1729
1889
  switch (event.type) {
1730
1890
  case "agents":
1731
1891
  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 Cursor).";
1892
+ case "catalog":
1893
+ 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`;
1732
1894
  case "skill":
1733
1895
  return ` AgentSky skill ${event.outcome === "current" ? "already up to date" : event.outcome} for ${event.agent}`;
1896
+ case "mcp":
1897
+ switch (event.outcome) {
1898
+ case "registered":
1899
+ return ` agentsky MCP server registered for ${event.agent}${event.detail ? ` (${event.detail})` : ""}`;
1900
+ case "planned":
1901
+ return ` would run: ${event.detail}`;
1902
+ case "unsupported":
1903
+ return null;
1904
+ default:
1905
+ return ` agentsky MCP server not registered for ${event.agent}: ${event.detail ?? event.outcome}`;
1906
+ }
1734
1907
  case "plan":
1735
1908
  return ` ${event.provider} plan: ${event.outcome === "already" ? "already connected" : event.outcome}${event.detail ? ` (${event.detail})` : ""}`;
1736
1909
  case "found": {
@@ -1781,22 +1954,40 @@ async function connectedProviders() {
1781
1954
  const { modelSubscriptions } = await api("GET", "/model-subscriptions");
1782
1955
  return new Set(modelSubscriptions.map((subscription) => subscription.provider));
1783
1956
  }
1784
- async function defaultVaultId() {
1957
+ var WORKSPACE_VAULT_NAME = "default";
1958
+ async function findWorkspaceVault() {
1785
1959
  const { vaults } = await api("GET", "/vaults");
1786
- const existing = vaults.find((vault) => vault.display_name === "default");
1787
- if (existing) return existing.id;
1788
- return (await api("POST", "/vaults", { display_name: "default" })).vault.id;
1960
+ return vaults.find((vault) => vault.display_name === WORKSPACE_VAULT_NAME)?.id ?? null;
1961
+ }
1962
+ async function workspaceVaultId() {
1963
+ const existing = await findWorkspaceVault();
1964
+ if (existing) return existing;
1965
+ try {
1966
+ return (await api("POST", "/vaults", { display_name: WORKSPACE_VAULT_NAME })).vault.id;
1967
+ } catch (error) {
1968
+ const raced = error instanceof ApiError && error.status === 409 ? await findWorkspaceVault() : null;
1969
+ if (!raced) throw error;
1970
+ return raced;
1971
+ }
1789
1972
  }
1790
1973
  function message(error) {
1791
1974
  return error instanceof Error ? error.message : String(error);
1792
1975
  }
1976
+ async function readCatalog() {
1977
+ if (!apiToken()) return { catalog: null, detail: "not logged in" };
1978
+ try {
1979
+ return { catalog: (await api("GET", "/catalog/agents")).agents };
1980
+ } catch (error) {
1981
+ return { catalog: null, detail: message(error) };
1982
+ }
1983
+ }
1793
1984
  async function runSetup(opts) {
1794
1985
  const steps = opts.steps?.length ? opts.steps : [...SETUP_STEPS];
1795
1986
  const interactive = !opts.json && !opts.yes && !opts.dryRun;
1796
1987
  const emit = (event) => {
1797
1988
  if (opts.json) console.log(JSON.stringify(event));
1798
1989
  else {
1799
- const line = describe(event);
1990
+ const line = describe2(event);
1800
1991
  if (line) console.log(line);
1801
1992
  }
1802
1993
  };
@@ -1804,7 +1995,7 @@ async function runSetup(opts) {
1804
1995
  const has = (step) => steps.includes(step);
1805
1996
  const agents = selectLocalAgents(opts.agents);
1806
1997
  emit({ type: "agents", agents: agents.map((agent2) => ({ id: agent2.id, name: agent2.name })) });
1807
- const needsToken = !opts.dryRun && (has("logins") || has("secrets") || has("import") && !opts.key);
1998
+ const needsToken = !opts.dryRun && (has("skill") || has("logins") || has("secrets") || has("import") && !opts.key);
1808
1999
  if (needsToken && !apiToken()) {
1809
2000
  if (opts.json) throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
1810
2001
  console.log("First, sign this computer in to AgentSky.");
@@ -1825,17 +2016,23 @@ async function runSetup(opts) {
1825
2016
  held = new Map((claimed.body.skills ?? []).map((skill) => [skill.name, skill.fingerprint]));
1826
2017
  };
1827
2018
  await tell();
1828
- if (has("skill") && agents.length && await agreed("Add the AgentSky skill to these agents, so they can hand work to others?")) {
2019
+ if (has("skill") && agents.length && await agreed("Add the AgentSky skill and MCP server to these agents, so they can hand work to others?")) {
2020
+ const { catalog: catalog2, detail } = await readCatalog();
2021
+ emit(catalog2 ? { type: "catalog", outcome: "read", agents: catalog2.length } : { type: "catalog", outcome: "unavailable", ...detail ? { detail } : {} });
2022
+ const token = apiToken();
1829
2023
  for (const agent2 of agents) {
1830
2024
  try {
1831
- const installed = installSkill(agent2, { dryRun: opts.dryRun });
2025
+ const installed = installSkill(agent2, { dryRun: opts.dryRun, catalog: catalog2 });
1832
2026
  emit({ type: "skill", agent: agent2.id, outcome: installed.outcome, path: installed.path });
1833
2027
  if (!opts.dryRun) run2.agents.push({ id: agent2.id, skill: installed.outcome });
1834
2028
  } catch (error) {
1835
2029
  emit({ type: "skill", agent: agent2.id, outcome: "failed", path: agent2.skillsDir });
1836
2030
  emit({ type: "error", code: "skill_install_failed", message: message(error) });
1837
2031
  run2.agents.push({ id: agent2.id, skill: "failed" });
2032
+ continue;
1838
2033
  }
2034
+ const registered = token ? registerMcpServer(agent2, { token, mcpUrl: mcpUrlFor(apiBase()), dryRun: opts.dryRun }) : { agent: agent2.id, outcome: "skipped", detail: "not logged in" };
2035
+ emit({ type: "mcp", agent: agent2.id, outcome: registered.outcome, ...registered.detail ? { detail: registered.detail } : {} });
1839
2036
  }
1840
2037
  await tell();
1841
2038
  }
@@ -1929,7 +2126,7 @@ async function saveSecrets(found, opts, interactive, emit) {
1929
2126
  continue;
1930
2127
  }
1931
2128
  try {
1932
- vaultId ??= await defaultVaultId();
2129
+ vaultId ??= await workspaceVaultId();
1933
2130
  await api("POST", `/vaults/${encodeURIComponent(vaultId)}/secrets`, {
1934
2131
  display_name: secret.name,
1935
2132
  auth: { type: "static_bearer", mcp_server_url: secret.url, token: secret.token }
@@ -1977,6 +2174,144 @@ async function importToWorkspace(key, held, skills, instructions, emit) {
1977
2174
  if (!finished.ok) emit({ type: "error", code: finished.code, message: finished.message });
1978
2175
  }
1979
2176
 
2177
+ // src/task-files.ts
2178
+ function encodePath(path) {
2179
+ return path.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
2180
+ }
2181
+ var filesPath = (task2) => `/tasks/${encodeURIComponent(task2)}/files`;
2182
+ async function listTaskFiles(task2) {
2183
+ return (await api("GET", filesPath(task2))).files;
2184
+ }
2185
+ async function readTaskFile(task2, path) {
2186
+ return (await apiBytes("GET", `${filesPath(task2)}/${encodePath(path)}`)).bytes;
2187
+ }
2188
+ async function writeTaskFile(task2, path, bytes, mediaType) {
2189
+ return (await apiBytes("PUT", `${filesPath(task2)}/${encodePath(path)}`, { bytes, mediaType })).json();
2190
+ }
2191
+ async function deleteTaskFile(task2, path) {
2192
+ await api("DELETE", `${filesPath(task2)}/${encodePath(path)}`);
2193
+ }
2194
+ async function listArtifacts(task2) {
2195
+ const query = task2 ? `?task_id=${encodeURIComponent(task2)}` : "";
2196
+ return (await api("GET", `/artifacts${query}`)).artifacts;
2197
+ }
2198
+ function publishBody(task2, options) {
2199
+ return { task_id: task2, name: options.name, paths: options.path, ...options.entry ? { entry: options.entry } : {} };
2200
+ }
2201
+ async function publishArtifact(task2, options) {
2202
+ return (await api("POST", "/artifacts", publishBody(task2, options))).artifact;
2203
+ }
2204
+ async function readArtifactFile(id, path) {
2205
+ return (await apiBytes("GET", `/artifacts/${encodeURIComponent(id)}/files/${encodePath(path)}`)).bytes;
2206
+ }
2207
+ async function shareArtifact(id, enabled) {
2208
+ return (await api("POST", `/artifacts/${encodeURIComponent(id)}/share`, { enabled })).share_url;
2209
+ }
2210
+ async function deleteArtifact(id) {
2211
+ await api("DELETE", `/artifacts/${encodeURIComponent(id)}`);
2212
+ }
2213
+ function renderTaskFile(file) {
2214
+ return `${file.path} ${file.size_bytes} bytes ${file.updated_at}${file.updated_by_session ? ` by ${file.updated_by_session}` : ""}`;
2215
+ }
2216
+ function renderArtifact(artifact2) {
2217
+ const files2 = `${artifact2.files.length} ${artifact2.files.length === 1 ? "file" : "files"}`;
2218
+ return `${artifact2.id} ${artifact2.name} v${artifact2.version} ${files2} entry ${artifact2.entry}${artifact2.share_url ? ` shared ${artifact2.share_url}` : ""}`;
2219
+ }
2220
+
2221
+ // src/workspace.ts
2222
+ var KINDS = ["TOOL_CALL", "SPEND_CAP", "CALL_COUNT", "CAPABILITY"];
2223
+ async function workspaceSlug(explicit) {
2224
+ if (explicit) return explicit;
2225
+ const current = currentUniverse();
2226
+ if (current) return current;
2227
+ return (await api("GET", "/whoami")).universe.slug;
2228
+ }
2229
+ var workspacePath = (slug) => `/universes/${encodeURIComponent(slug)}`;
2230
+ async function listMembers(slug) {
2231
+ return api("GET", `${workspacePath(slug)}/members`);
2232
+ }
2233
+ function findMember(members, ref) {
2234
+ const needle = ref.toLowerCase();
2235
+ const found = members.find((m) => m.user_id === ref || m.email?.toLowerCase() === needle);
2236
+ if (!found) throw new ApiError(404, "not_found", `no member ${ref} in this workspace`);
2237
+ return found;
2238
+ }
2239
+ function inviteBody(emails, options) {
2240
+ if (options.role && options.role !== "admin" && options.role !== "member") {
2241
+ throw new ApiError(400, "invalid_request", "--role is admin or member");
2242
+ }
2243
+ return {
2244
+ emails,
2245
+ ...options.role ? { role: options.role } : {},
2246
+ ...options.sendEmail ? { send_email: true } : {}
2247
+ };
2248
+ }
2249
+ async function invite(slug, emails, options) {
2250
+ return api("POST", `${workspacePath(slug)}/invites`, inviteBody(emails, options));
2251
+ }
2252
+ async function withdrawInvite(slug, inviteId) {
2253
+ await api("DELETE", `${workspacePath(slug)}/invites/${encodeURIComponent(inviteId)}`);
2254
+ }
2255
+ async function setRole(slug, memberRef, role) {
2256
+ if (role !== "admin" && role !== "member") throw new ApiError(400, "invalid_request", "a role is admin or member");
2257
+ const member = findMember((await listMembers(slug)).members, memberRef);
2258
+ await api("PATCH", `${workspacePath(slug)}/members/${encodeURIComponent(member.user_id)}`, { role });
2259
+ return member;
2260
+ }
2261
+ async function removeMember(slug, memberRef) {
2262
+ const member = findMember((await listMembers(slug)).members, memberRef);
2263
+ await api("DELETE", `${workspacePath(slug)}/members/${encodeURIComponent(member.user_id)}`);
2264
+ return member;
2265
+ }
2266
+ async function joinWorkspace(token) {
2267
+ return (await api("POST", `/invites/${encodeURIComponent(token)}`)).workspace;
2268
+ }
2269
+ function renderMembers(page) {
2270
+ const lines = page.members.map((m) => `${m.role.padEnd(6)} ${m.email ?? m.user_id}${m.name ? ` (${m.name})` : ""} ${m.user_id}`);
2271
+ for (const i of page.invites) lines.push(`invited ${i.email} as ${i.role} by ${i.invited_by} until ${i.expires_at.slice(0, 10)} ${i.id}`);
2272
+ return lines;
2273
+ }
2274
+ function policyRuleBody(options) {
2275
+ const kind = options.kind.toUpperCase().replace(/-/g, "_");
2276
+ if (!KINDS.includes(kind)) throw new ApiError(400, "invalid_request", `--kind is one of ${KINDS.join(", ")}`);
2277
+ let config;
2278
+ try {
2279
+ config = JSON.parse(options.config);
2280
+ } catch {
2281
+ throw new ApiError(400, "invalid_request", "--config must be a JSON object");
2282
+ }
2283
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
2284
+ throw new ApiError(400, "invalid_request", "--config must be a JSON object");
2285
+ }
2286
+ const position = options.position === void 0 ? void 0 : Number(options.position);
2287
+ if (position !== void 0 && !Number.isInteger(position)) throw new ApiError(400, "invalid_request", "--position is a whole number");
2288
+ return {
2289
+ name: options.name,
2290
+ kind,
2291
+ config,
2292
+ ...position !== void 0 ? { position } : {},
2293
+ ...options.disabled ? { enabled: false } : {}
2294
+ };
2295
+ }
2296
+ var policiesPath = (scope) => "session" in scope ? `/sessions/${encodeURIComponent(scope.session)}/policies` : `${workspacePath(scope.slug)}/policies`;
2297
+ async function listPolicies(scope, effective = false) {
2298
+ if (effective && "session" in scope) return api("GET", `/sessions/${encodeURIComponent(scope.session)}/effective-policy`);
2299
+ return api("GET", policiesPath(scope));
2300
+ }
2301
+ async function addPolicy(scope, options) {
2302
+ return (await api("POST", policiesPath(scope), policyRuleBody(options))).rule;
2303
+ }
2304
+ async function setPolicyEnabled(scope, ruleId, enabled) {
2305
+ return (await api("PATCH", `${policiesPath(scope)}/${encodeURIComponent(ruleId)}`, { enabled })).rule;
2306
+ }
2307
+ async function removePolicy(scope, ruleId) {
2308
+ await api("DELETE", `${policiesPath(scope)}/${encodeURIComponent(ruleId)}`);
2309
+ }
2310
+ function renderPolicy(rule) {
2311
+ const state = rule.enabled ? "on " : "off";
2312
+ return `${state} ${rule.layer.padEnd(9)} ${rule.kind.padEnd(10)} ${rule.name} ${JSON.stringify(rule.config)} ${rule.id}`;
2313
+ }
2314
+
1980
2315
  // src/index.ts
1981
2316
  function printModelBilling(session2) {
1982
2317
  if (session2.modelBilling !== "account") return;
@@ -2055,6 +2390,84 @@ universe.command("show").action(
2055
2390
  }
2056
2391
  })
2057
2392
  );
2393
+ universe.command("members [slug]").description("who is in the workspace, and who is invited (owners and admins)").action(
2394
+ run(async (slug) => {
2395
+ for (const line of renderMembers(await listMembers(await workspaceSlug(slug)))) console.log(line);
2396
+ })
2397
+ );
2398
+ universe.command("invite <emails...>").description("invite people by email; prints each person's join link token").option("--role <role>", "admin | member (default member; admin needs the owner)").option("--send-email", "also email each invitation").option("--universe <slug>", "the workspace (default: the current one)").action(
2399
+ run(async (emails, opts) => {
2400
+ const result = await invite(await workspaceSlug(opts.universe), emails, opts);
2401
+ for (const i of result.invites) console.log(`invited ${i.email} as ${i.role} ${i.id}`);
2402
+ for (const link of result.links) console.log(` ${link.email} joins with: sky universe join ${link.token}`);
2403
+ if (!result.invites.length) console.log("nobody new to invite \u2014 everyone listed is already a member");
2404
+ })
2405
+ );
2406
+ universe.command("uninvite <inviteId>").description("withdraw an invitation that has not been taken up").option("--universe <slug>", "the workspace (default: the current one)").action(
2407
+ run(async (inviteId, opts) => {
2408
+ await withdrawInvite(await workspaceSlug(opts.universe), inviteId);
2409
+ console.log(`Withdrew ${inviteId}`);
2410
+ })
2411
+ );
2412
+ universe.command("role <member> <role>").description("make a member (user id or email) an admin or a member (owner only)").option("--universe <slug>", "the workspace (default: the current one)").action(
2413
+ run(async (memberRef, role, opts) => {
2414
+ const member = await setRole(await workspaceSlug(opts.universe), memberRef, role);
2415
+ console.log(`${member.email ?? member.user_id} is now ${role}`);
2416
+ })
2417
+ );
2418
+ universe.command("remove <member>").description("remove a member (user id or email) from the workspace (owner only)").option("--universe <slug>", "the workspace (default: the current one)").action(
2419
+ run(async (memberRef, opts) => {
2420
+ const member = await removeMember(await workspaceSlug(opts.universe), memberRef);
2421
+ console.log(`Removed ${member.email ?? member.user_id}`);
2422
+ })
2423
+ );
2424
+ universe.command("join <token>").description("accept an invitation sent to your verified email").action(
2425
+ run(async (token) => {
2426
+ const workspace = await joinWorkspace(token);
2427
+ console.log(`Joined ${workspace.slug} (${workspace.name}) \u2014 sky universe switch ${workspace.slug}`);
2428
+ })
2429
+ );
2430
+ var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
2431
+ async function policyScope(opts) {
2432
+ return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
2433
+ }
2434
+ var scopeOptions = (command) => command.option("--session <ref>", "one session's rules instead of the workspace's").option("--universe <slug>", "the workspace (default: the current one)");
2435
+ scopeOptions(
2436
+ policy.command("list").description("the workspace's rules, or one session's; --effective merges every layer").option("--effective", "with --session: every rule the session runs under")
2437
+ ).action(
2438
+ run(async (opts) => {
2439
+ const { rules, note } = await listPolicies(await policyScope(opts), opts.effective);
2440
+ if (!rules.length) console.log("no policy rules");
2441
+ for (const rule of rules) console.log(renderPolicy(rule));
2442
+ if (note) console.log(note);
2443
+ })
2444
+ );
2445
+ scopeOptions(
2446
+ policy.command("add").description("add a rule (workspace owner, or the session's owner with --session)").requiredOption("--name <name>", "what the rule is for").requiredOption("--kind <kind>", "TOOL_CALL | SPEND_CAP | CALL_COUNT | CAPABILITY").requiredOption(
2447
+ "--config <json>",
2448
+ `e.g. '{"action":"deny","matcher":"Bash","commandPatterns":["rm -rf *"]}', '{"hardCapUsdMicros":5000000}', '{"limitPerSession":200}', '{"platform.orchestration":"deny"}'`
2449
+ ).option("--position <n>", "evaluation order, lowest first (default 0)").option("--disabled", "create it switched off")
2450
+ ).action(
2451
+ run(async (opts) => {
2452
+ console.log(renderPolicy(await addPolicy(await policyScope(opts), opts)));
2453
+ })
2454
+ );
2455
+ for (const [verb, enabled, description] of [
2456
+ ["enable", true, "switch a rule on"],
2457
+ ["disable", false, "switch a rule off (kept, so it can be switched on again)"]
2458
+ ]) {
2459
+ scopeOptions(policy.command(`${verb} <ruleId>`).description(description)).action(
2460
+ run(async (ruleId, opts) => {
2461
+ console.log(renderPolicy(await setPolicyEnabled(await policyScope(opts), ruleId, enabled)));
2462
+ })
2463
+ );
2464
+ }
2465
+ scopeOptions(policy.command("rm <ruleId>").description("delete a rule")).action(
2466
+ run(async (ruleId, opts) => {
2467
+ await removePolicy(await policyScope(opts), ruleId);
2468
+ console.log(`Deleted ${ruleId}`);
2469
+ })
2470
+ );
2058
2471
  var agent = program.command("agent").description("agent specs: create, inspect, talk (via the default session)");
2059
2472
  agent.command("create").description("create an agent spec \u2014 no runtime; `sky session create` starts one").option("--type <type>", "hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "model id (defaults per type)").option(
2060
2473
  "--reasoning-effort <level>",
@@ -2449,7 +2862,7 @@ program.command("skill").description("the AgentSky skill for the agents on this
2449
2862
  for (const i of installs) console.log(`${i.agent.padEnd(12)} ${verb[i.outcome].padEnd(14)} ${i.path}`);
2450
2863
  })
2451
2864
  );
2452
- program.command("setup [steps...]").description("bring this machine's agents to AgentSky: the skill, your plans, your skills and instructions, your tool keys").addHelpText("after", `
2865
+ 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", `
2453
2866
  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(
2454
2867
  run(async (steps, opts) => {
2455
2868
  const unknown = steps.find((step) => !SETUP_STEPS.includes(step));
@@ -2700,6 +3113,69 @@ files.command("download <id>").description("download a session file's bytes").re
2700
3113
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
2701
3114
  })
2702
3115
  );
3116
+ var task = program.command("task").description("a task's shared files (the task is its first session's id)");
3117
+ task.command("files <task>").description("list the task's current files").action(
3118
+ run(async (taskId) => {
3119
+ const list = await listTaskFiles(taskId);
3120
+ if (!list.length) console.log("no files");
3121
+ for (const file of list) console.log(renderTaskFile(file));
3122
+ })
3123
+ );
3124
+ task.command("get <task> <path>").description("read one task file; prints it, or writes it with -o").option("-o, --out <path>", "where to write the bytes").action(
3125
+ run(async (taskId, path, opts) => {
3126
+ const bytes = await readTaskFile(taskId, path);
3127
+ if (opts.out) {
3128
+ writeFileSync6(opts.out, bytes);
3129
+ console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
3130
+ } else process.stdout.write(bytes);
3131
+ })
3132
+ );
3133
+ 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(
3134
+ run(async (taskId, path, local) => {
3135
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync10(local), mediaTypeFor(local));
3136
+ console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
3137
+ })
3138
+ );
3139
+ task.command("rm <task> <path>").description("remove a path from the task's files (its history is kept)").action(
3140
+ run(async (taskId, path) => {
3141
+ await deleteTaskFile(taskId, path);
3142
+ console.log(`Removed ${path}`);
3143
+ })
3144
+ );
3145
+ var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
3146
+ artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
3147
+ run(async (opts) => {
3148
+ const list = await listArtifacts(opts.task);
3149
+ if (!list.length) console.log("no artifacts");
3150
+ for (const a of list) console.log(renderArtifact(a));
3151
+ })
3152
+ );
3153
+ artifact.command("publish <task>").description("publish paths from the task's files as a new version of the named artifact").requiredOption("--name <name>", "the artifact's name; publishing the same name again adds a version").requiredOption("--path <path...>", "task file paths to include").option("--entry <path>", "the file a viewer opens first (default: the first path)").action(
3154
+ run(async (taskId, opts) => {
3155
+ console.log(renderArtifact(await publishArtifact(taskId, opts)));
3156
+ })
3157
+ );
3158
+ artifact.command("get <id> <path>").description("read one file of an artifact; prints it, or writes it with -o").option("-o, --out <path>", "where to write the bytes").action(
3159
+ run(async (id, path, opts) => {
3160
+ const bytes = await readArtifactFile(id, path);
3161
+ if (opts.out) {
3162
+ writeFileSync6(opts.out, bytes);
3163
+ console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
3164
+ } else process.stdout.write(bytes);
3165
+ })
3166
+ );
3167
+ artifact.command("share <id>").description("turn on a public link anyone can open; --off turns it off (the old link stops working)").option("--off", "stop sharing").action(
3168
+ run(async (id, opts) => {
3169
+ const url = await shareArtifact(id, !opts.off);
3170
+ console.log(url ?? `Stopped sharing ${id}`);
3171
+ })
3172
+ );
3173
+ artifact.command("rm <id>").description("delete an artifact").action(
3174
+ run(async (id) => {
3175
+ await deleteArtifact(id);
3176
+ console.log(`Deleted ${id}`);
3177
+ })
3178
+ );
2703
3179
  function routineLine(r) {
2704
3180
  const target = r.target.type === "session" ? `\u2192 ${r.target.session_id}` : `+ ${r.target.agent}`;
2705
3181
  const cron = r.schedule ? `${r.schedule.expression} ${r.schedule.timezone}` : "manual-only";