@agentsky/sky 2.23.0 → 2.24.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
@@ -4,12 +4,13 @@ import {
4
4
  api,
5
5
  apiBytes,
6
6
  apiForm,
7
+ apiRaw,
7
8
  downloadFileTo,
8
9
  postEvents,
9
10
  resolveSessionRef,
10
11
  streamEvents,
11
12
  uploadFile
12
- } from "./chunk-HQPOCQRQ.js";
13
+ } from "./chunk-UHMKEYJN.js";
13
14
  import {
14
15
  CLI_VERSION,
15
16
  USER_AGENT,
@@ -20,7 +21,7 @@ import {
20
21
  currentUniverse,
21
22
  loadConfig,
22
23
  saveConfig
23
- } from "./chunk-V5XLV37X.js";
24
+ } from "./chunk-RAFHOB5S.js";
24
25
  import {
25
26
  AGENT_TYPES,
26
27
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -42,17 +43,17 @@ import {
42
43
  skillRefSchema,
43
44
  splitMcpServers,
44
45
  supersededBy
45
- } from "./chunk-HDPDY77W.js";
46
+ } from "./chunk-277AZSSG.js";
46
47
  import "./chunk-MLKGABMK.js";
47
48
 
48
49
  // src/index.ts
49
- import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
50
+ import { existsSync as existsSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
50
51
  import { basename as basename9, resolve as resolve9 } from "path";
51
52
  import { createInterface as createInterface6 } from "readline";
52
53
  import { Command } from "commander";
53
54
 
54
55
  // src/files.ts
55
- import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
56
+ import { existsSync, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
56
57
  import { basename as basename2, extname, join as join2 } from "path";
57
58
 
58
59
  // src/resumable-upload.ts
@@ -250,6 +251,16 @@ function eventsFor(text, files2, system, interactionMode2) {
250
251
  if (system?.trim()) events.push({ type: "system.message", content: [{ type: "text", text: system }] });
251
252
  return events;
252
253
  }
254
+ function isFileId(ref) {
255
+ return /^file_[A-Za-z0-9]+$/.test(ref);
256
+ }
257
+ async function attachment(ref, opts = {}) {
258
+ if (opts.byId || isFileId(ref) && !existsSync(ref)) {
259
+ if (!isFileId(ref)) throw new Error(`--file-id takes a stored file's id (file_\u2026), got ${JSON.stringify(ref)}`);
260
+ return { file: await api("GET", `/files/${encodeURIComponent(ref)}`), uploaded: false };
261
+ }
262
+ return { file: await uploadLocalFile(ref), uploaded: true };
263
+ }
253
264
 
254
265
  // src/goal.ts
255
266
  var CHECK_SEPARATOR = " :: ";
@@ -494,9 +505,9 @@ function renderContextPage(page) {
494
505
  }
495
506
 
496
507
  // src/agent-file.ts
497
- import { existsSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
508
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
498
509
  import { dirname as dirname2, resolve as resolve2 } from "path";
499
- import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
510
+ import { parse as parseToml } from "smol-toml";
500
511
  var fileInstructionSchema = external_exports.object({
501
512
  name: external_exports.string(),
502
513
  content: external_exports.string().optional(),
@@ -512,7 +523,8 @@ var agentFileSchema = external_exports.object({
512
523
  type: external_exports.enum(AGENT_TYPES).optional(),
513
524
  llm: external_exports.string().optional(),
514
525
  reasoning_effort: external_exports.enum(REASONING_EFFORT_VALUES).optional(),
515
- capabilities: capabilitiesSchema.optional(),
526
+ capabilities: capabilitiesSchema.removeDefault().optional(),
527
+ // omitted = leave the agent's as they are
516
528
  prompt: external_exports.string().optional(),
517
529
  prompt_file: external_exports.string().optional(),
518
530
  instructions: external_exports.array(fileInstructionSchema).optional(),
@@ -569,13 +581,40 @@ function readAgentFile(path) {
569
581
  return { file, spec };
570
582
  }
571
583
  function stampId(path, id) {
572
- const doc = parseToml(readFileSync3(path, "utf8"));
573
- doc.id = id;
574
- delete doc.slug;
575
- writeFileSync2(path, stringifyToml(doc));
584
+ writeFileSync2(path, stampIdText(readFileSync3(path, "utf8"), id));
585
+ }
586
+ function stampIdText(text, id) {
587
+ const out = [];
588
+ let topLevel = true;
589
+ let openString = null;
590
+ let stamped = false;
591
+ let nameAt = -1;
592
+ for (const line of text.split("\n")) {
593
+ if (openString) {
594
+ if (line.includes(openString)) openString = null;
595
+ out.push(line);
596
+ continue;
597
+ }
598
+ if (/^\s*\[/.test(line)) topLevel = false;
599
+ if (topLevel && /^\s*(id|slug)\s*=/.test(line)) {
600
+ if (/^\s*id\s*=/.test(line) && !stamped) {
601
+ out.push(`id = ${JSON.stringify(id)}`);
602
+ stamped = true;
603
+ }
604
+ continue;
605
+ }
606
+ if (topLevel && nameAt < 0 && /^\s*name\s*=/.test(line)) nameAt = out.length;
607
+ const opener = /=\s*("""|''')/.exec(line);
608
+ if (opener && !line.slice(opener.index + opener[0].length).includes(opener[1])) openString = opener[1];
609
+ out.push(line);
610
+ }
611
+ if (!stamped) out.splice(nameAt + 1, 0, `id = ${JSON.stringify(id)}`);
612
+ const next = out.join("\n");
613
+ if (parseToml(next).id !== id) throw new Error("could not stamp the agent id into the file");
614
+ return next;
576
615
  }
577
616
  function scaffoldAgentFile(path, name) {
578
- if (existsSync(path)) throw new Error(`${path} already exists`);
617
+ if (existsSync2(path)) throw new Error(`${path} already exists`);
579
618
  writeFileSync2(
580
619
  path,
581
620
  `# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
@@ -596,6 +635,38 @@ You are ${name}, a helpful long-lived agent.
596
635
  `
597
636
  );
598
637
  }
638
+ function deployPatch(spec) {
639
+ return {
640
+ displayName: spec.displayName,
641
+ ...spec.agentType ? { agentType: spec.agentType } : {},
642
+ ...spec.llm ? { llm: spec.llm } : {},
643
+ ...spec.capabilities ? { capabilities: spec.capabilities } : {},
644
+ ...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
645
+ ...spec.skills ? { skills: spec.skills } : {},
646
+ ...spec.hiringPolicy ? { hiringPolicy: spec.hiringPolicy } : {},
647
+ ...spec.environmentId ? { environmentId: spec.environmentId } : {}
648
+ };
649
+ }
650
+ function deployReport(id, spec, before, after, promptChanged) {
651
+ if (spec.agentType && after.agentType !== spec.agentType) {
652
+ throw new Error(`${id} is still ${after.agentType}: the API did not apply type = "${spec.agentType}"`);
653
+ }
654
+ if (spec.llm && after.llm !== spec.llm) {
655
+ throw new Error(`${id} still runs ${after.llm}: the API did not apply llm = "${spec.llm}"`);
656
+ }
657
+ const changed = [];
658
+ if (promptChanged) changed.push("prompt");
659
+ if (after.version !== before.version) changed.push("settings");
660
+ if (!changed.length) return [`${id} is up to date \u2014 nothing to deploy (version ${after.version})`];
661
+ const lines = [`Deployed ${id}: ${changed.join(" and ")} updated (version ${before.version} \u2192 ${after.version})`];
662
+ if (after.agentType !== before.agentType || after.llm !== before.llm) {
663
+ lines.push(` now ${after.agentType} \xB7 ${after.llm} for new sessions (was ${before.agentType} \xB7 ${before.llm})`);
664
+ }
665
+ if (spec.skills && after.version !== before.version) {
666
+ lines.push(` Skills apply to new sessions; running sessions keep the skills they started with \u2014 start one with \`sky session create ${id}\`.`);
667
+ }
668
+ return lines;
669
+ }
599
670
 
600
671
  // src/catalog.ts
601
672
  var bare = (handle) => handle.replace(/^@/, "");
@@ -662,7 +733,7 @@ function renderCatalogAgent(agent2) {
662
733
 
663
734
  // src/clone.ts
664
735
  import { spawn } from "child_process";
665
- import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
736
+ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
666
737
  import { homedir } from "os";
667
738
  import { basename as basename3, join as join3 } from "path";
668
739
  import { createInterface } from "readline";
@@ -768,9 +839,9 @@ function detectHarness(explicit) {
768
839
  }
769
840
  return explicit;
770
841
  }
771
- if (existsSync2(claudeConfigRoot())) return "claude_code";
772
- if (existsSync2(join3(homedir(), ".codex"))) return "codex";
773
- if (existsSync2(join3(homedir(), ".openclaw"))) return "openclaw";
842
+ if (existsSync3(claudeConfigRoot())) return "claude_code";
843
+ if (existsSync3(join3(homedir(), ".codex"))) return "codex";
844
+ if (existsSync3(join3(homedir(), ".openclaw"))) return "openclaw";
774
845
  throw new Error(
775
846
  "no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
776
847
  );
@@ -1028,20 +1099,20 @@ async function clone(opts) {
1028
1099
  }
1029
1100
 
1030
1101
  // src/import-local.ts
1031
- import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
1102
+ import { closeSync as closeSync3, existsSync as existsSync6, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
1032
1103
  import { homedir as homedir4 } from "os";
1033
1104
  import { join as join7 } from "path";
1034
1105
  import { createHash as createHash3 } from "crypto";
1035
1106
 
1036
1107
  // src/import-files.ts
1037
1108
  import { isUtf8 } from "buffer";
1038
- import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1109
+ import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1039
1110
  import { homedir as homedir3 } from "os";
1040
1111
  import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
1041
1112
 
1042
1113
  // src/import-workspace.ts
1043
1114
  import { spawnSync } from "child_process";
1044
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
1115
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
1045
1116
  import { homedir as homedir2 } from "os";
1046
1117
  import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
1047
1118
  import { parse as parseToml2 } from "smol-toml";
@@ -1270,7 +1341,7 @@ function skillFiles(skill) {
1270
1341
  else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
1271
1342
  }
1272
1343
  };
1273
- if (existsSync3(skill.dir)) walk(skill.dir, 0);
1344
+ if (existsSync4(skill.dir)) walk(skill.dir, 0);
1274
1345
  return out;
1275
1346
  }
1276
1347
  function codexThreadName(sessionId, home = homedir2()) {
@@ -1396,13 +1467,13 @@ function touchedPaths(records, cwd, home = homedir3()) {
1396
1467
  }
1397
1468
  function gitRoot(dir) {
1398
1469
  for (let d = dir; ; d = dirname4(d)) {
1399
- if (existsSync4(join5(d, ".git"))) return d;
1470
+ if (existsSync5(join5(d, ".git"))) return d;
1400
1471
  if (d === dirname4(d)) return null;
1401
1472
  }
1402
1473
  }
1403
1474
  function instructionPaths(cwd, harness2, home = homedir3()) {
1404
1475
  const out = [];
1405
- if (cwd && existsSync4(cwd)) {
1476
+ if (cwd && existsSync5(cwd)) {
1406
1477
  const root = gitRoot(cwd) ?? cwd;
1407
1478
  for (let d = resolve4(cwd); ; d = dirname4(d)) {
1408
1479
  for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
@@ -1925,7 +1996,7 @@ function sniffTitle(filePath) {
1925
1996
  function listClaudeCodeSessions(limit) {
1926
1997
  const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
1927
1998
  const projectsDir = join7(root, "projects");
1928
- if (!existsSync5(projectsDir)) return [];
1999
+ if (!existsSync6(projectsDir)) return [];
1929
2000
  const sessions = [];
1930
2001
  try {
1931
2002
  for (const encoded of readdirSync4(projectsDir)) {
@@ -1951,7 +2022,7 @@ function listClaudeCodeSessions(limit) {
1951
2022
  let discoveryError;
1952
2023
  const subagentFiles = [];
1953
2024
  const subagentDir = join7(projectDir, sessionId, "subagents");
1954
- if (existsSync5(subagentDir)) {
2025
+ if (existsSync6(subagentDir)) {
1955
2026
  try {
1956
2027
  const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1957
2028
  for (const f of subFiles) {
@@ -2229,7 +2300,7 @@ async function importSession(opts) {
2229
2300
  }
2230
2301
  const result = await response.json();
2231
2302
  const finalTraceId = result.traceId ?? traceId;
2232
- const environment = opts.files === false || opts.environment === false || !cwd || !existsSync5(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
2303
+ const environment = opts.files === false || opts.environment === false || !cwd || !existsSync6(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
2233
2304
  return {
2234
2305
  traceId: finalTraceId,
2235
2306
  url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
@@ -2342,7 +2413,7 @@ import { hostname } from "os";
2342
2413
  import { spawn as spawn2 } from "child_process";
2343
2414
 
2344
2415
  // src/mcp-register.ts
2345
- import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
2416
+ import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
2346
2417
  import { spawnSync as spawnSync3 } from "child_process";
2347
2418
  import { homedir as homedir6 } from "os";
2348
2419
  import { join as join10 } from "path";
@@ -2350,7 +2421,7 @@ import { parse as parseToml3 } from "smol-toml";
2350
2421
  import { isMap, parseDocument } from "yaml";
2351
2422
 
2352
2423
  // src/local-agents.ts
2353
- import { existsSync as existsSync6 } from "fs";
2424
+ import { existsSync as existsSync7 } from "fs";
2354
2425
  import { homedir as homedir5 } from "os";
2355
2426
  import { join as join8 } from "path";
2356
2427
  function localAgents(env = process.env, home = homedir5()) {
@@ -2364,7 +2435,7 @@ function localAgents(env = process.env, home = homedir5()) {
2364
2435
  ];
2365
2436
  }
2366
2437
  function detectLocalAgents(env = process.env, home = homedir5()) {
2367
- return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
2438
+ return localAgents(env, home).filter((agent2) => existsSync7(agent2.root));
2368
2439
  }
2369
2440
  function selectLocalAgents(ids, env = process.env, home = homedir5()) {
2370
2441
  if (!ids?.length) return detectLocalAgents(env, home);
@@ -2487,6 +2558,12 @@ Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
2487
2558
  cheaper or different agent to get around a refusal.
2488
2559
  - \`get_usage\` shows the balance; check it before starting several cloud sessions
2489
2560
  at once so the person is told once, up front, instead of after the first fails.
2561
+ - Anything else the person can do on agentsky.dev or in the Mac app (rename or
2562
+ archive a thread, edit a Library file or skill, change an agent's model, invite
2563
+ a member, \u2026) is an API operation: find it with \`search_api\`, read its
2564
+ parameters with \`describe_api\`, and run it with \`call_api\`. It acts as the
2565
+ person with their own key, so make only the change they asked for. Where MCP
2566
+ is missing, \`sky api search <words>\` and \`sky api <METHOD> <path>\` do the same.
2490
2567
 
2491
2568
  ## How to pick
2492
2569
 
@@ -2874,7 +2951,7 @@ function installSkill(agent2, opts = {}) {
2874
2951
  var MCP_SERVER_NAME = SKILL_NAME;
2875
2952
  function detectHermes(env = process.env, home = homedir6()) {
2876
2953
  const root = env.HERMES_HOME || join10(home, ".hermes");
2877
- return existsSync7(root) ? { id: "hermes", name: "Hermes", root } : null;
2954
+ return existsSync8(root) ? { id: "hermes", name: "Hermes", root } : null;
2878
2955
  }
2879
2956
  function detectMcpClients(env = process.env, home = homedir6()) {
2880
2957
  const hermes = detectHermes(env, home);
@@ -3034,7 +3111,7 @@ function refreshMcpRegistrations(opts, clients = detectMcpClients(), runner = sy
3034
3111
  }
3035
3112
 
3036
3113
  // src/shell-env.ts
3037
- import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3114
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3038
3115
  import { homedir as homedir7 } from "os";
3039
3116
  import { basename as basename6, dirname as dirname5, join as join11 } from "path";
3040
3117
  var TOKEN_ENV = "AGENTSKY_API_KEY";
@@ -3050,13 +3127,13 @@ function shellStartupFiles(env = process.env, home = homedir7(), platform = proc
3050
3127
  const shell = basename6(env.SHELL ?? "");
3051
3128
  const files2 = [];
3052
3129
  const zshenv = join11(env.ZDOTDIR || home, ".zshenv");
3053
- if (shell === "zsh" || platform === "darwin" || existsSync8(zshenv)) files2.push({ path: zshenv, kind: "sh" });
3130
+ if (shell === "zsh" || platform === "darwin" || existsSync9(zshenv)) files2.push({ path: zshenv, kind: "sh" });
3054
3131
  const bashrc = join11(home, ".bashrc");
3055
- if (shell === "bash" || existsSync8(bashrc)) files2.push({ path: bashrc, kind: "sh" });
3132
+ if (shell === "bash" || existsSync9(bashrc)) files2.push({ path: bashrc, kind: "sh" });
3056
3133
  const bashProfile = join11(home, ".bash_profile");
3057
- if (existsSync8(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
3134
+ if (existsSync9(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
3058
3135
  const fish = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "fish");
3059
- if (shell === "fish" || existsSync8(fish)) files2.push({ path: join11(fish, "conf.d", "agentsky.fish"), kind: "fish" });
3136
+ if (shell === "fish" || existsSync9(fish)) files2.push({ path: join11(fish, "conf.d", "agentsky.fish"), kind: "fish" });
3060
3137
  return files2;
3061
3138
  }
3062
3139
  function writeShellEnv(token, opts = {}) {
@@ -3072,7 +3149,7 @@ function writeShellEnv(token, opts = {}) {
3072
3149
  writeFileSync6(fish, `${header2}${token ? `set -gx ${TOKEN_ENV} ${quoted(token)}` : `set -e ${TOKEN_ENV}`}
3073
3150
  `, { mode: 384 });
3074
3151
  for (const file of startup) {
3075
- const existing = existsSync8(file.path) ? readFileSync11(file.path, "utf8") : "";
3152
+ const existing = existsSync9(file.path) ? readFileSync11(file.path, "utf8") : "";
3076
3153
  if (existing.includes(HOOK_MARK)) continue;
3077
3154
  const line = file.kind === "fish" ? `${HOOK_MARK}
3078
3155
  test -f ${quoted(fish)}; and source ${quoted(fish)}
@@ -3443,12 +3520,42 @@ function clip(value, max = 120) {
3443
3520
  const text = typeof value === "string" ? value : JSON.stringify(value);
3444
3521
  return text.length > max ? `${text.slice(0, max)}\u2026` : text;
3445
3522
  }
3446
- function renderEvent(event, data) {
3523
+ function createEventRenderer(out = process.stdout) {
3524
+ const seen = /* @__PURE__ */ new Set();
3525
+ let openStream = null;
3526
+ const closeLine = () => {
3527
+ if (openStream === null) return;
3528
+ out.write("\n");
3529
+ openStream = null;
3530
+ };
3531
+ return (event, data) => {
3532
+ const id = typeof data.id === "string" ? data.id : void 0;
3533
+ if (id) {
3534
+ if (seen.has(id)) return;
3535
+ seen.add(id);
3536
+ }
3537
+ if (event !== "agent.message") {
3538
+ if (event && event !== "keepalive") closeLine();
3539
+ renderOther(event, data);
3540
+ return;
3541
+ }
3542
+ const parts = Array.isArray(data.parts) ? data.parts : [];
3543
+ const streamId = typeof parts[0]?.stream_id === "string" ? parts[0].stream_id : null;
3544
+ const text = String(data.text ?? "");
3545
+ if (streamId === null) {
3546
+ closeLine();
3547
+ out.write(`${text}
3548
+ `);
3549
+ } else {
3550
+ if (openStream !== streamId) closeLine();
3551
+ out.write(text);
3552
+ openStream = streamId;
3553
+ }
3554
+ };
3555
+ }
3556
+ function renderOther(event, data) {
3447
3557
  const part = data.part;
3448
3558
  switch (event) {
3449
- case "agent.message":
3450
- console.log(String(data.text ?? ""));
3451
- break;
3452
3559
  case "agent.reasoning":
3453
3560
  console.log(dim(`\xB7 ${clip(part?.text ?? "")}`));
3454
3561
  break;
@@ -3480,11 +3587,12 @@ function renderEvent(event, data) {
3480
3587
  }
3481
3588
 
3482
3589
  // src/session-read.ts
3483
- var TURN_TYPES = ["user.message", "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
3590
+ var TURN_STARTS = /* @__PURE__ */ new Set(["user.message", "goal.defined", "goal.iteration"]);
3591
+ var TURN_TYPES = [...TURN_STARTS, "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
3484
3592
  function latestTurn(events) {
3485
3593
  let start = 0;
3486
3594
  for (let index = events.length - 1; index >= 0; index -= 1) {
3487
- if (events[index].type === "user.message") {
3595
+ if (TURN_STARTS.has(events[index].type)) {
3488
3596
  start = index + 1;
3489
3597
  break;
3490
3598
  }
@@ -3531,7 +3639,7 @@ import { createInterface as createInterface3 } from "readline";
3531
3639
 
3532
3640
  // src/setup-local.ts
3533
3641
  import { isUtf8 as isUtf83 } from "buffer";
3534
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
3642
+ import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
3535
3643
  import { homedir as homedir8 } from "os";
3536
3644
  import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, resolve as resolve6 } from "path";
3537
3645
  import { parse as parseToml4 } from "smol-toml";
@@ -3572,7 +3680,7 @@ function readLocalSkills(agents) {
3572
3680
  const skills2 = [];
3573
3681
  const ignored = [];
3574
3682
  for (const agent2 of agents) {
3575
- if (!existsSync9(agent2.skillsDir)) continue;
3683
+ if (!existsSync10(agent2.skillsDir)) continue;
3576
3684
  for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
3577
3685
  const dir = join13(agent2.skillsDir, entry2.name);
3578
3686
  let isDir2 = entry2.isDirectory();
@@ -3583,7 +3691,7 @@ function readLocalSkills(agents) {
3583
3691
  isDir2 = false;
3584
3692
  }
3585
3693
  }
3586
- if (!isDir2 || !existsSync9(join13(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3694
+ if (!isDir2 || !existsSync10(join13(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3587
3695
  const name = frontmatterName(readFileSync12(join13(dir, "SKILL.md"), "utf8"));
3588
3696
  if (!name) {
3589
3697
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
@@ -3690,7 +3798,7 @@ function findLocalSecrets(agents, home = homedir8()) {
3690
3798
 
3691
3799
  // src/subscriptions.ts
3692
3800
  import { spawn as spawn3 } from "child_process";
3693
- import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
3801
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
3694
3802
  import { homedir as homedir9 } from "os";
3695
3803
  import { join as join14 } from "path";
3696
3804
  import { createInterface as createInterface2 } from "readline";
@@ -3789,7 +3897,7 @@ async function connectClaude() {
3789
3897
  }
3790
3898
  function codexSignIn(env = process.env, home = homedir9()) {
3791
3899
  const path = join14(env.CODEX_HOME || join14(home, ".codex"), "auth.json");
3792
- if (!existsSync10(path)) {
3900
+ if (!existsSync11(path)) {
3793
3901
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
3794
3902
  }
3795
3903
  const raw = readFileSync13(path, "utf8");
@@ -3802,7 +3910,7 @@ function codexSignIn(env = process.env, home = homedir9()) {
3802
3910
  }
3803
3911
  function cursorSignIn(env = process.env, home = homedir9()) {
3804
3912
  const path = join14(env.XDG_CONFIG_HOME || join14(home, ".config"), "cursor", "auth.json");
3805
- if (!existsSync10(path)) {
3913
+ if (!existsSync11(path)) {
3806
3914
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
3807
3915
  }
3808
3916
  const raw = readFileSync13(path, "utf8");
@@ -3924,7 +4032,9 @@ async function subscriptionUsage(name, account) {
3924
4032
  return usage;
3925
4033
  }
3926
4034
  function renderSubscriptionUsage(name, usage) {
3927
- const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[managedProviderFor(name)];
4035
+ const provider = managedProviderFor(name);
4036
+ const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[provider];
4037
+ if (usage.status === "unavailable" && (usage.reason === "unsupported" || provider === "anthropic" && usage.reason === "permission")) return [`${label}: usage isn't available for this account type`];
3928
4038
  if (usage.status === "unavailable") return [`${label}: usage unavailable (${usage.reason ?? "unknown"})`];
3929
4039
  const head = [`${label}${usage.plan ? ` ${usage.plan}` : ""}`, usage.allowed === false ? "limit reached" : null].filter(Boolean).join(" \xB7 ");
3930
4040
  const rows = usage.windows.map(
@@ -4399,12 +4509,25 @@ async function getSkillUsage(since) {
4399
4509
  const qs = since ? `?since=${encodeURIComponent(since)}` : "";
4400
4510
  return api("GET", `/skills/usage${qs}`);
4401
4511
  }
4512
+ function nextSkills(current, skillId, verb) {
4513
+ const has = current.some((ref) => ref.type === "skill" && ref.skillId === skillId);
4514
+ if (verb === "attach") return has ? null : [...current, { type: "skill", skillId, version: "latest" }];
4515
+ return has ? current.filter((ref) => !(ref.type === "skill" && ref.skillId === skillId)) : null;
4516
+ }
4517
+ async function setAgentSkill(agentId, skillId, verb) {
4518
+ const path = `/agents/${encodeURIComponent(agentId)}`;
4519
+ const { agent: agent2 } = await api("GET", path);
4520
+ const skills2 = nextSkills(agent2.skills ?? [], skillId, verb);
4521
+ if (!skills2) return "unchanged";
4522
+ await api("PATCH", path, { skills: skills2, expectedVersion: agent2.version });
4523
+ return "changed";
4524
+ }
4402
4525
 
4403
4526
  // src/task-export.ts
4404
4527
  import { createHash as createHash6 } from "crypto";
4405
4528
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
4406
4529
  import { dirname as dirname8, join as join17 } from "path";
4407
- import { stringify as stringifyToml2 } from "smol-toml";
4530
+ import { stringify as stringifyToml } from "smol-toml";
4408
4531
  var TEXT_MAX = 2e4;
4409
4532
  function scrub(text, names2) {
4410
4533
  let out = redactPlainText(text).text;
@@ -4582,7 +4705,7 @@ Write your deliverables under /workspace/outputs.
4582
4705
  `);
4583
4706
  out.set(
4584
4707
  "task.toml",
4585
- stringifyToml2({
4708
+ stringifyToml({
4586
4709
  metadata: {
4587
4710
  name: `agentsky-${idHash}`,
4588
4711
  tier: opts.tier,
@@ -5159,6 +5282,80 @@ function renderLibraryArtifact(artifact2) {
5159
5282
  return `${artifact2.id} ${artifact2.name} ${version}${artifact2.shared ? " shared" : ""}`;
5160
5283
  }
5161
5284
 
5285
+ // src/api-command.ts
5286
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
5287
+ var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
5288
+ function readData(data, stdin = () => readFileSync16(0, "utf8")) {
5289
+ if (data === void 0) return void 0;
5290
+ const raw = data === "-" ? stdin() : data.startsWith("@") ? readFileSync16(data.slice(1), "utf8") : data;
5291
+ try {
5292
+ JSON.parse(raw);
5293
+ } catch {
5294
+ throw new Error("--data must be JSON (inline, @file or - for stdin)");
5295
+ }
5296
+ return raw;
5297
+ }
5298
+ function requestPath(path, query = []) {
5299
+ if (!path.startsWith("/") || path.startsWith("//")) throw new Error("path must start with /, e.g. /threads");
5300
+ const clean = path.replace(/^\/v1(?=\/)/, "");
5301
+ const params = new URLSearchParams();
5302
+ for (const pair of query) {
5303
+ const at = pair.indexOf("=");
5304
+ if (at <= 0) throw new Error(`--query needs key=value, got "${pair}"`);
5305
+ params.append(pair.slice(0, at), pair.slice(at + 1));
5306
+ }
5307
+ const sep3 = clean.includes("?") ? "&" : "?";
5308
+ const encoded = params.toString();
5309
+ return encoded ? `${clean}${sep3}${encoded}` : clean;
5310
+ }
5311
+ function matchOperations(doc, words) {
5312
+ const needles = words.map((w) => w.toLowerCase());
5313
+ const out = [];
5314
+ for (const [path, item] of Object.entries(doc.paths ?? {})) {
5315
+ for (const [method, op] of Object.entries(item)) {
5316
+ if (!API_METHODS.includes(method.toUpperCase())) continue;
5317
+ const hay = `${method} ${path} ${op.summary ?? ""} ${op.operationId ?? ""}`.toLowerCase();
5318
+ if (needles.every((n) => hay.includes(n))) out.push({ method: method.toUpperCase(), path, summary: op.summary, operationId: op.operationId });
5319
+ }
5320
+ }
5321
+ return out.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
5322
+ }
5323
+ async function searchApi(words) {
5324
+ const res = await apiRaw("GET", "/openapi.json", { auth: false });
5325
+ if (!res.ok) throw new Error(`could not read the API reference (${res.status})`);
5326
+ const ops = matchOperations(await res.json(), words);
5327
+ if (!ops.length) {
5328
+ console.log("no operations match");
5329
+ return;
5330
+ }
5331
+ for (const op of ops) console.log(`${op.method.padEnd(6)} ${op.path}${op.summary ? ` \u2014 ${op.summary}` : ""}`);
5332
+ }
5333
+ async function callApi(method, path, opts) {
5334
+ const verb = method.toUpperCase();
5335
+ if (!API_METHODS.includes(verb)) throw new Error(`method must be one of ${API_METHODS.join(", ")} or "search"`);
5336
+ const body = readData(opts.data);
5337
+ if (body !== void 0 && verb === "GET") throw new Error("a GET takes no --data; use --query");
5338
+ const res = await apiRaw(verb, requestPath(path, opts.query), { body, idempotencyKey: opts.idempotencyKey, universe: opts.workspace });
5339
+ const type = res.headers.get("content-type") ?? "";
5340
+ if (opts.output) {
5341
+ writeFileSync9(opts.output, new Uint8Array(await res.arrayBuffer()));
5342
+ console.error(`${res.status} \u2192 ${opts.output}`);
5343
+ } else if (/json/.test(type)) {
5344
+ const text = await res.text();
5345
+ let printed = text;
5346
+ try {
5347
+ printed = JSON.stringify(JSON.parse(text), null, 2);
5348
+ } catch {
5349
+ }
5350
+ (res.ok ? console.log : console.error)(printed);
5351
+ } else if (/^text\/|xml|csv|markdown/.test(type) || !type) {
5352
+ (res.ok ? console.log : console.error)(await res.text());
5353
+ } else {
5354
+ console.error(`${res.status}: ${type} body (${res.headers.get("content-length") ?? "?"} bytes); pass --output <file> to save it`);
5355
+ }
5356
+ if (!res.ok && res.status !== 304) process.exitCode = 1;
5357
+ }
5358
+
5162
5359
  // src/billing-plans.ts
5163
5360
  var readCodingPlan = () => api("GET", "/billing/coding-plan");
5164
5361
  function joinCodingPlan(dollars2, requestId) {
@@ -5233,8 +5430,19 @@ function wholeNumber2(flag, value) {
5233
5430
  return Number(value.trim());
5234
5431
  }
5235
5432
 
5433
+ // src/workspace-flag.ts
5434
+ function workspaceFlagIsCommands(argv, commandPath) {
5435
+ let at = 0;
5436
+ let matched = 0;
5437
+ for (; at < argv.length && matched < commandPath.length; at += 1) {
5438
+ if (argv[at] === commandPath[matched]) matched += 1;
5439
+ }
5440
+ if (matched < commandPath.length) return false;
5441
+ return argv.slice(at).some((arg) => arg === "--workspace" || arg.startsWith("--workspace="));
5442
+ }
5443
+
5236
5444
  // src/harness.ts
5237
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
5445
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
5238
5446
  import { dirname as dirname9, join as join18, resolve as resolve8 } from "path";
5239
5447
  var HARNESS_FILE = "harness.json";
5240
5448
  function harnessFilePath(dir) {
@@ -5242,11 +5450,11 @@ function harnessFilePath(dir) {
5242
5450
  }
5243
5451
  function readHarnessFile(dir) {
5244
5452
  const path = harnessFilePath(dir);
5245
- if (!existsSync11(path)) {
5453
+ if (!existsSync12(path)) {
5246
5454
  throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
5247
5455
  }
5248
5456
  try {
5249
- return JSON.parse(readFileSync16(path, "utf8"));
5457
+ return JSON.parse(readFileSync17(path, "utf8"));
5250
5458
  } catch (err) {
5251
5459
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
5252
5460
  }
@@ -5273,11 +5481,11 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
5273
5481
  `;
5274
5482
  function harnessInit(dir) {
5275
5483
  const path = harnessFilePath(dir);
5276
- if (existsSync11(path)) {
5484
+ if (existsSync12(path)) {
5277
5485
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
5278
5486
  }
5279
5487
  mkdirSync6(dirname9(path), { recursive: true });
5280
- writeFileSync9(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
5488
+ writeFileSync10(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
5281
5489
  `);
5282
5490
  console.log(`Created ${path}`);
5283
5491
  console.log(INIT_COMMENT);
@@ -5410,9 +5618,16 @@ function printModelBilling(session2) {
5410
5618
  }
5411
5619
  var program = new Command();
5412
5620
  program.name("sky").description("Launch and talk to long-lived agents \u2014 agentsky.dev").version(CLI_VERSION).option("--workspace <slug>", "workspace to act in (overrides sky universe switch and SKY_UNIVERSE; alias of --universe on sub-commands)");
5413
- program.hook("preAction", () => {
5621
+ program.hook("preAction", (_root, action) => {
5414
5622
  const workspace2 = program.opts().workspace;
5415
- if (workspace2) process.env.SKY_WORKSPACE = workspace2;
5623
+ if (!workspace2) return;
5624
+ const path = [];
5625
+ for (let command = action; command && command !== program; command = command.parent) path.unshift(command.name());
5626
+ if (action.options.some((option) => option.long === "--workspace") && workspaceFlagIsCommands(process.argv.slice(2), path)) {
5627
+ action.setOptionValue("workspace", workspace2);
5628
+ return;
5629
+ }
5630
+ process.env.SKY_WORKSPACE = workspace2;
5416
5631
  });
5417
5632
  var lastProgressDecile = -1;
5418
5633
  function uploadProgress(sent, total) {
@@ -5447,6 +5662,9 @@ auth.command("whoami").description("who the current token acts as").action(
5447
5662
  console.log(
5448
5663
  `${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (default)" : ""} \xB7 scopes ${who.scopes.join(",")}`
5449
5664
  );
5665
+ if (who.credential_universe) {
5666
+ console.log(` this key is fixed to workspace ${who.credential_universe}: it cannot act in other workspaces or create new ones (make an account-wide key with \`sky keys create\`, or \`sky auth login\`)`);
5667
+ }
5450
5668
  })
5451
5669
  );
5452
5670
  auth.command("connect <provider>").description("connect a model subscription: claude (setup-token) or chatgpt (Codex sign-in); --browser signs any plan in on its own page").option("-y, --yes", "skip the consent prompt").option("--browser", "sign in on the provider's page instead of using this machine's sign-in (claude, chatgpt, cursor, grok, muse)").option("--account <id>", "with --browser: re-sign an additional account, or `new` to add one").option("--label <label>", "with --browser: name the account").option("--no-open", "with --browser: print the sign-in link without opening it").action(
@@ -5583,6 +5801,12 @@ workspace.command("list").action(
5583
5801
  })
5584
5802
  );
5585
5803
  workspace.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
5804
+ async function renameWorkspaceCommand(slug, name) {
5805
+ const { universe: renamed } = await api("PATCH", `/universes/${encodeURIComponent(slug)}`, { name: name.join(" ") });
5806
+ console.log(`${renamed.slug} is now "${renamed.name}"`);
5807
+ }
5808
+ universe.command("rename <slug> <name...>").description("rename a workspace you own (its slug stays)").action(run(renameWorkspaceCommand));
5809
+ workspace.command("rename <slug> <name...>").description("rename a workspace you own (alias of universe rename)").action(run(renameWorkspaceCommand));
5586
5810
  var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
5587
5811
  async function policyScope(opts) {
5588
5812
  return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
@@ -5693,7 +5917,7 @@ agent.command("destroy <id>").description("delete the spec; requires all session
5693
5917
  console.log(`Destroyed ${id}`);
5694
5918
  })
5695
5919
  );
5696
- agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
5920
+ agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks); a file_\u2026 id attaches that stored file").option("--file-id <id...>", "attach files already uploaded (`sky files upload`), by id, without uploading again").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
5697
5921
  run(
5698
5922
  async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
5699
5923
  )
@@ -5807,7 +6031,7 @@ session.command("delete <id>").description("permanently delete the session, its
5807
6031
  console.log(`Deleted ${id}`);
5808
6032
  })
5809
6033
  );
5810
- session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
6034
+ session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks); a file_\u2026 id attaches that stored file").option("--file-id <id...>", "attach files already uploaded (`sky files upload`), by id, without uploading again").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
5811
6035
  run(
5812
6036
  async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text ?? "", opts)
5813
6037
  )
@@ -6000,7 +6224,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
6000
6224
  run(async (ref, path, opts) => {
6001
6225
  const bytes = await readFile(await resolveSessionRef(ref), path);
6002
6226
  if (opts.out) {
6003
- writeFileSync10(opts.out, bytes);
6227
+ writeFileSync11(opts.out, bytes);
6004
6228
  console.error(`saved ${bytes.length} bytes to ${opts.out}`);
6005
6229
  } else process.stdout.write(bytes);
6006
6230
  })
@@ -6017,7 +6241,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
6017
6241
  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)})`);
6018
6242
  if (!patch.length) console.error("nothing changed");
6019
6243
  else if (opts.out) {
6020
- writeFileSync10(opts.out, patch);
6244
+ writeFileSync11(opts.out, patch);
6021
6245
  console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
6022
6246
  } else process.stdout.write(patch);
6023
6247
  })
@@ -6071,9 +6295,10 @@ function interactionMode(opts) {
6071
6295
  }
6072
6296
  async function uploadAttachments(opts) {
6073
6297
  const files2 = [];
6074
- for (const path of [...opts.image ?? [], ...opts.file ?? []]) {
6075
- const file = await uploadLocalFile(path);
6076
- console.error(`uploaded ${file.filename} \u2192 ${file.id}`);
6298
+ const refs = [...opts.image ?? [], ...opts.file ?? []].map((ref) => ({ ref, byId: false }));
6299
+ for (const { ref, byId } of [...refs, ...(opts.fileId ?? []).map((ref2) => ({ ref: ref2, byId: true }))]) {
6300
+ const { file, uploaded } = await attachment(ref, { byId });
6301
+ console.error(uploaded ? `uploaded ${file.filename} \u2192 ${file.id}` : `attaching ${file.id} (${file.filename})`);
6077
6302
  files2.push(file);
6078
6303
  }
6079
6304
  return files2;
@@ -6090,9 +6315,10 @@ async function sendAndFollow(sessionId, text, opts) {
6090
6315
  const controller = new AbortController();
6091
6316
  let opened = null;
6092
6317
  if (!opts.detach) {
6318
+ const render = createEventRenderer();
6093
6319
  opened = (async () => {
6094
6320
  for await (const frame of streamEvents(sessionId, controller.signal)) {
6095
- renderEvent(frame.event, frame.data);
6321
+ render(frame.event, frame.data);
6096
6322
  if (turnOver(frame)) {
6097
6323
  controller.abort();
6098
6324
  break;
@@ -6113,10 +6339,11 @@ async function chatLoop(sessionId, opts = {}) {
6113
6339
  const controller = new AbortController();
6114
6340
  const rl = createInterface6({ input: process.stdin, output: process.stdout, prompt: "> " });
6115
6341
  let system = opts.system;
6342
+ const render = createEventRenderer();
6116
6343
  void (async () => {
6117
6344
  try {
6118
6345
  for await (const frame of streamEvents(sessionId, controller.signal)) {
6119
- renderEvent(frame.event, frame.data);
6346
+ render(frame.event, frame.data);
6120
6347
  if (frame.event === "session.status_terminated") rl.close();
6121
6348
  else if (frame.event === "session.status_idle") rl.prompt();
6122
6349
  }
@@ -6223,10 +6450,32 @@ secrets.command("unset <id> <key>").action(
6223
6450
  console.log(`unset ${key}`);
6224
6451
  })
6225
6452
  );
6226
- 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(
6453
+ async function launchFromFile(path, spec, opts) {
6454
+ const { vcpus, memoryMb, ...specBody } = spec;
6455
+ const body = {
6456
+ ...specBody,
6457
+ ...opts.type ? { agentType: opts.type } : {},
6458
+ ...opts.llm ? { llm: opts.llm } : {}
6459
+ };
6460
+ const { agent: created } = await api("POST", "/agents", body);
6461
+ const { session: session2 } = await api("POST", "/sessions", {
6462
+ agent: created.id,
6463
+ ...opts.start ? {} : { provision: "deferred" },
6464
+ ...vcpus !== void 0 ? { vcpus } : {},
6465
+ ...memoryMb !== void 0 ? { memoryMb } : {}
6466
+ });
6467
+ stampId(path, created.id);
6468
+ console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
6469
+ printModelBilling(session2);
6470
+ console.log(opts.start ? " its first session is starting now" : " its first session starts on the first message");
6471
+ if (!spec.llm && !opts.llm) console.log(` model: ${created.llm}, the ${created.agentType} default \u2014 set llm = "\u2026" in ${basename9(path)} and run \`sky deploy\` to change it`);
6472
+ console.log(` sky agent message ${created.id} "hello"`);
6473
+ console.log(` sky agent chat ${created.id}`);
6474
+ }
6475
+ program.command("launch").description("scaffold ./agent.toml if absent, create the agent and its first session (idle until the first message), 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)").option("--start", "boot the first session now instead of on the first message").action(
6227
6476
  run(async (opts) => {
6228
6477
  const path = resolve9(opts.file);
6229
- if (!existsSync12(path)) {
6478
+ if (!existsSync13(path)) {
6230
6479
  const name = basename9(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
6231
6480
  scaffoldAgentFile(path, name);
6232
6481
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
@@ -6236,55 +6485,27 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
6236
6485
  console.log(`${file.id} already launched \u2014 use \`sky deploy\` to re-apply.`);
6237
6486
  return;
6238
6487
  }
6239
- const { vcpus, memoryMb, ...specBody } = spec;
6240
- const body = {
6241
- ...specBody,
6242
- ...opts.type ? { agentType: opts.type } : {},
6243
- ...opts.llm ? { llm: opts.llm } : {}
6244
- };
6245
- const { agent: created } = await api("POST", "/agents", body);
6246
- console.log("Starting its runtime\u2026");
6247
- const { session: session2 } = await api("POST", "/sessions", {
6248
- agent: created.id,
6249
- ...vcpus !== void 0 ? { vcpus } : {},
6250
- ...memoryMb !== void 0 ? { memoryMb } : {}
6251
- });
6252
- stampId(path, created.id);
6253
- console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
6254
- printModelBilling(session2);
6255
- console.log(` sky agent message ${created.id} "hello"`);
6256
- console.log(` sky agent chat ${created.id}`);
6488
+ await launchFromFile(path, spec, opts);
6257
6489
  })
6258
6490
  );
6259
- program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
6491
+ program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent: an unchanged file changes nothing)").option("-f, --file <path>", "agent file", "agent.toml").action(
6260
6492
  run(async (opts) => {
6261
6493
  const path = resolve9(opts.file);
6262
6494
  const { file, spec } = readAgentFile(path);
6263
6495
  if (!file.id) {
6264
6496
  console.log("No agent id in the file yet \u2014 running launch instead.");
6265
- const { vcpus, memoryMb, ...specBody } = spec;
6266
- const { agent: created } = await api("POST", "/agents", specBody);
6267
- await api("POST", "/sessions", {
6268
- agent: created.id,
6269
- ...vcpus !== void 0 ? { vcpus } : {},
6270
- ...memoryMb !== void 0 ? { memoryMb } : {}
6271
- });
6272
- stampId(path, created.id);
6273
- console.log(`Launched ${created.id}`);
6497
+ await launchFromFile(path, spec, {});
6274
6498
  return;
6275
6499
  }
6500
+ const ref = encodeURIComponent(file.id);
6501
+ const { agent: before } = await api("GET", `/agents/${ref}`);
6502
+ let promptChanged = false;
6276
6503
  if (spec.prompt !== void 0) {
6277
- await api("PUT", `/agents/${encodeURIComponent(file.id)}/prompt`, {
6278
- prompt: spec.prompt
6279
- });
6504
+ const saved = await api("PUT", `/agents/${ref}/prompt`, { prompt: spec.prompt });
6505
+ promptChanged = saved.unchanged === false;
6280
6506
  }
6281
- await api("PATCH", `/agents/${encodeURIComponent(file.id)}`, {
6282
- displayName: spec.displayName,
6283
- ...spec.capabilities ? { capabilities: spec.capabilities } : {},
6284
- ...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
6285
- ...spec.skills ? { skills: spec.skills } : {}
6286
- });
6287
- console.log(`Deployed ${file.id} (prompt + settings re-applied)`);
6507
+ const { agent: after } = await api("PATCH", `/agents/${ref}`, deployPatch(spec));
6508
+ for (const line of deployReport(file.id, spec, before, after, promptChanged)) console.log(line);
6288
6509
  })
6289
6510
  );
6290
6511
  program.command("clone").description("clone your local agent (Claude Code, \u2026) into the cloud; re-run to sync its prompt").option("--harness <type>", "claude_code | codex | openclaw (default: detected)").option("--new", "always launch a fresh agent, even if this machine is linked").option("--dry-run", "print the plan and send nothing").option("-y, --yes", "skip the confirmation").option("--open", "open the agent page when done").action(
@@ -6525,14 +6746,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
6525
6746
  run(async (taskId, path, opts) => {
6526
6747
  const bytes = await readTaskFile(taskId, path);
6527
6748
  if (opts.out) {
6528
- writeFileSync10(opts.out, bytes);
6749
+ writeFileSync11(opts.out, bytes);
6529
6750
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
6530
6751
  } else process.stdout.write(bytes);
6531
6752
  })
6532
6753
  );
6533
6754
  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(
6534
6755
  run(async (taskId, path, local) => {
6535
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync17(local), mediaTypeFor(local));
6756
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync18(local), mediaTypeFor(local));
6536
6757
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
6537
6758
  })
6538
6759
  );
@@ -6605,7 +6826,7 @@ task.command("verdict <task> <did-the-job>").description("record whether the tas
6605
6826
  console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
6606
6827
  })
6607
6828
  );
6608
- var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent by id");
6829
+ var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent (sky skills attach, or [[skills]] in agent.toml)");
6609
6830
  skills.command("upload <dir>").description("upload a skill directory (SKILL.md at its root) as a new skill").option("--name <name>", "display name (default: the directory's name)").option("--json", "print the stored skill as JSON").action(
6610
6831
  run(async (dir, opts) => {
6611
6832
  const { skill } = await uploadSkill(dir, opts.name);
@@ -6613,7 +6834,7 @@ skills.command("upload <dir>").description("upload a skill directory (SKILL.md a
6613
6834
  console.log(JSON.stringify(skill));
6614
6835
  return;
6615
6836
  }
6616
- console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it in agent.toml:`);
6837
+ console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it: sky skills attach ${skill.id} --agent <id>, or in agent.toml:`);
6617
6838
  console.log(` [[skills]]
6618
6839
  type = "skill"
6619
6840
  skillId = "${skill.id}"`);
@@ -6626,6 +6847,18 @@ skills.command("list").description("the workspace's stored skills").action(
6626
6847
  for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
6627
6848
  })
6628
6849
  );
6850
+ for (const [verb, blurb] of [
6851
+ ["attach", "add a stored skill to an agent; its new sessions load it"],
6852
+ ["detach", "remove a skill from an agent; its new sessions no longer load it"]
6853
+ ]) {
6854
+ skills.command(`${verb} <skill>`).description(blurb).requiredOption("--agent <id>", "the agent").action(
6855
+ run(async (skillId, opts) => {
6856
+ const outcome = await setAgentSkill(opts.agent, skillId, verb);
6857
+ if (outcome === "unchanged") console.log(`${skillId} is ${verb === "attach" ? "already" : "not"} attached to ${opts.agent}`);
6858
+ else console.log(`${verb === "attach" ? "Attached" : "Detached"} ${skillId} ${verb === "attach" ? "to" : "from"} ${opts.agent} \u2014 new sessions ${verb === "attach" ? "load it" : "no longer load it"}; running sessions keep the skills they started with`);
6859
+ })
6860
+ );
6861
+ }
6629
6862
  skills.command("usage").description("view and miss counts per skill name in this workspace").option("--since <iso>", `start date (ISO 8601); default is the last 30 days`).option("--json", "print JSON").action(
6630
6863
  run(async (opts) => {
6631
6864
  const summary = await getSkillUsage(opts.since);
@@ -6684,7 +6917,7 @@ library.command("list").description("your Library, newest first").option("--sear
6684
6917
  );
6685
6918
  library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
6686
6919
  run(async (path, opts) => {
6687
- const bytes = readFileSync17(path);
6920
+ const bytes = readFileSync18(path);
6688
6921
  const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
6689
6922
  console.log(renderLibraryArtifact(artifact2));
6690
6923
  })
@@ -6715,7 +6948,7 @@ library.command("place <id> <workspace>").description('place your artifact in a
6715
6948
  library.command("download <id> <out>").description("save one version's bytes to a file").option("--version <n>", "a saved version (the current one when omitted)").action(
6716
6949
  run(async (id, out, opts) => {
6717
6950
  const { bytes } = await downloadLibraryArtifact(id, opts.version ? Number(opts.version) : void 0);
6718
- writeFileSync10(out, bytes);
6951
+ writeFileSync11(out, bytes);
6719
6952
  console.log(`wrote ${bytes.length} bytes to ${out}`);
6720
6953
  })
6721
6954
  );
@@ -6736,7 +6969,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
6736
6969
  run(async (id, path, opts) => {
6737
6970
  const bytes = await readArtifactFile(id, path);
6738
6971
  if (opts.out) {
6739
- writeFileSync10(opts.out, bytes);
6972
+ writeFileSync11(opts.out, bytes);
6740
6973
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
6741
6974
  } else process.stdout.write(bytes);
6742
6975
  })
@@ -6835,6 +7068,12 @@ for (const [verb, blurb] of [
6835
7068
  })
6836
7069
  );
6837
7070
  }
7071
+ routine.command("rm <id>").description("delete a routine: it never runs again; the sessions it started are kept").action(
7072
+ run(async (id) => {
7073
+ await api("DELETE", `/routines/${encodeURIComponent(id)}`);
7074
+ console.log(`Deleted routine ${id}. It will not run again; sessions it started are kept.`);
7075
+ })
7076
+ );
6838
7077
  routine.command("runs [id]").description("every fire attempt, newest first \u2014 of one routine, or all of them").option("--failed", "only runs that failed to start").option("--limit <n>", "how many (default 20)").action(
6839
7078
  run(async (id, opts) => {
6840
7079
  const params = new URLSearchParams({ limit: opts.limit ?? "20" });
@@ -7052,7 +7291,7 @@ usageOptions(
7052
7291
  run(async (opts) => {
7053
7292
  const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
7054
7293
  if (opts.out) {
7055
- writeFileSync10(opts.out, bytes);
7294
+ writeFileSync11(opts.out, bytes);
7056
7295
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
7057
7296
  } else {
7058
7297
  process.stdout.write(bytes);
@@ -7591,6 +7830,85 @@ help.command("get <slug>").description("read a help article by its slug").option
7591
7830
  console.log(article.body);
7592
7831
  })
7593
7832
  );
7833
+ program.command("api <method> [path...]").description("call any AgentSky API operation (GET|POST|PUT|PATCH|DELETE <path>), or `sky api search <words\u2026>` to find one").option("-d, --data <json>", "JSON body: inline, @file, or - for stdin").option("-q, --query <key=value>", "query parameter (repeatable)", (v, acc) => [...acc, v], []).option("--workspace <slug>", "send the request in this workspace (X-Universe)").option("--idempotency-key <key>", "retry-safe key for a POST").option("-o, --output <file>", "save the body to a file (for downloads)").action(
7834
+ run(async (method, rest, opts) => {
7835
+ if (method.toLowerCase() === "search") return searchApi(rest);
7836
+ if (rest.length !== 1) throw new Error("usage: sky api <METHOD> <path> (one path), or sky api search <words\u2026>");
7837
+ return callApi(method, rest[0], opts);
7838
+ })
7839
+ );
7840
+ agent.command("edit <id>").description("change an agent's name, engine, model or thinking level (as its Library folder does)").option("--name <name>", "new name").option("--engine <agentType>", "engine, e.g. CLAUDE_CODE, CODEX, HERMES").option("--model <llm>", "model id, e.g. anthropic/claude-sonnet-4-5").option("--thinking <effort>", 'reasoning effort, or "default" to clear the pin').action(
7841
+ run(async (id, opts) => {
7842
+ const body = {
7843
+ ...opts.name ? { displayName: opts.name } : {},
7844
+ ...opts.engine ? { agentType: opts.engine } : {},
7845
+ ...opts.model ? { llm: opts.model } : {},
7846
+ ...opts.thinking ? { reasoningEffort: opts.thinking === "default" ? null : opts.thinking } : {}
7847
+ };
7848
+ if (!Object.keys(body).length) throw new Error("pass at least one of --name, --engine, --model, --thinking");
7849
+ const { agent: a } = await api("PATCH", `/agents/${encodeURIComponent(id)}`, body);
7850
+ console.log(`${a.id} \u2014 ${a.displayName} \xB7 ${a.agentType} \xB7 ${a.llm}${a.reasoningEffort ? ` \xB7 ${a.reasoningEffort}` : ""}`);
7851
+ })
7852
+ );
7853
+ skills.command("revisions <skillId>").description("a skill's saved versions, newest first").option("--json", "print as JSON").action(
7854
+ run(async (skillId, opts) => {
7855
+ const page = await api("GET", `/skills/${encodeURIComponent(skillId)}/revisions`);
7856
+ const rows = page.revisions ?? [];
7857
+ if (opts.json) return void console.log(JSON.stringify(rows, null, 2));
7858
+ if (!rows.length) return void console.log("no versions");
7859
+ for (const r of rows) console.log(`${r.id} ${r.createdAt ?? r.created_at ?? ""}`);
7860
+ })
7861
+ );
7862
+ skills.command("cat <skillId> <revisionId> <path>").description("print one file (e.g. SKILL.md) of a skill version").action(
7863
+ run(async (skillId, revisionId, path) => {
7864
+ const res = await apiRaw("GET", `/skills/${encodeURIComponent(skillId)}/revisions/${encodeURIComponent(revisionId)}/content?path=${encodeURIComponent(path)}`);
7865
+ if (!res.ok) throw new ApiError(res.status, "http_error", await res.text() || `${res.status}`);
7866
+ process.stdout.write(await res.text());
7867
+ })
7868
+ );
7869
+ skills.command("rm <skillId>").description("delete a skill from the workspace").action(
7870
+ run(async (skillId) => {
7871
+ await api("DELETE", `/skills/${encodeURIComponent(skillId)}`);
7872
+ console.log(`Deleted ${skillId}`);
7873
+ })
7874
+ );
7875
+ var notes = program.command("notes").description("memory notes in the Library's memory stores: list, read, write (every save keeps a version)");
7876
+ notes.command("stores").description("the workspace's memory stores").action(
7877
+ run(async () => {
7878
+ const { memory_stores: stores } = await api("GET", "/memory_stores?limit=100");
7879
+ if (!stores.length) return void console.log("no memory stores");
7880
+ for (const st of stores) console.log(`${st.id} ${st.name}`);
7881
+ })
7882
+ );
7883
+ notes.command("list <storeId> [folder]").description("the notes in a store, optionally under a folder ending in /").action(
7884
+ run(async (storeId, folder) => {
7885
+ const q = folder ? `&path_prefix=${encodeURIComponent(folder)}` : "";
7886
+ const { memories } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories?limit=100${q}`);
7887
+ if (!memories.length) return void console.log("no notes");
7888
+ for (const n of memories) console.log(`${n.id} ${n.path}`);
7889
+ })
7890
+ );
7891
+ notes.command("read <storeId> <noteId>").description("print one note").action(
7892
+ run(async (storeId, noteId) => {
7893
+ const { memory: memory2 } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories/${encodeURIComponent(noteId)}`);
7894
+ process.stdout.write(memory2.content.endsWith("\n") ? memory2.content : `${memory2.content}
7895
+ `);
7896
+ })
7897
+ );
7898
+ notes.command("save <storeId> <file>").description("write a note from a local file: --path creates one, --note saves a new version of one").option("--path <path>", "path of a new note, e.g. /projects/launch.md").option("--note <noteId>", "the note to update").action(
7899
+ run(async (storeId, file, opts) => {
7900
+ if (!opts.path === !opts.note) throw new Error("pass exactly one of --path (new note) or --note (update)");
7901
+ const content = readFileSync18(file, "utf8");
7902
+ const target = `/memory_stores/${encodeURIComponent(storeId)}/memories${opts.note ? `/${encodeURIComponent(opts.note)}` : ""}`;
7903
+ const { memory: memory2 } = await api("POST", target, { content, ...opts.path ? { path: opts.path } : {} });
7904
+ console.log(`Saved ${memory2.path} (${memory2.id})`);
7905
+ })
7906
+ );
7907
+ workspace.command("context").description("what every session in the workspace loads at start: AGENTS.md, MEMORY.md, indexes, your member page").action(
7908
+ run(async () => {
7909
+ console.log(JSON.stringify(await api("GET", "/workspace-context"), null, 2));
7910
+ })
7911
+ );
7594
7912
  var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
7595
7913
  vault.command("list").description("the workspace's vaults").action(
7596
7914
  run(async () => {
@@ -7680,7 +7998,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
7680
7998
  }));
7681
7999
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
7682
8000
  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) => {
7683
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-ZRU624EY.js");
8001
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-CFHCOGKB.js");
7684
8002
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
7685
8003
  const scopes = parseContentScopes(opts.capture);
7686
8004
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -7715,7 +8033,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
7715
8033
  }));
7716
8034
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
7717
8035
  try {
7718
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-ZRU624EY.js");
8036
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-CFHCOGKB.js");
7719
8037
  const chunks = [];
7720
8038
  for await (const chunk of process.stdin) chunks.push(chunk);
7721
8039
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -7728,14 +8046,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
7728
8046
  }
7729
8047
  });
7730
8048
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
7731
- const { readState, flush, grantSender } = await import("./trace-ZRU624EY.js");
8049
+ const { readState, flush, grantSender } = await import("./trace-CFHCOGKB.js");
7732
8050
  const state = readState();
7733
8051
  if (!state) throw new Error("no trace is running here");
7734
8052
  const outcome = await flush(grantSender(state), { attempts: 4 });
7735
8053
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
7736
8054
  }));
7737
8055
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
7738
- const { readState, spoolStatus } = await import("./trace-ZRU624EY.js");
8056
+ const { readState, spoolStatus } = await import("./trace-CFHCOGKB.js");
7739
8057
  const state = readState();
7740
8058
  if (!state) {
7741
8059
  console.log("No trace is running here.");
@@ -7745,7 +8063,7 @@ traceCmd.command("status").description("the running trace and its spool").action
7745
8063
  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`);
7746
8064
  }));
7747
8065
  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) => {
7748
- const { readState } = await import("./trace-ZRU624EY.js");
8066
+ const { readState } = await import("./trace-CFHCOGKB.js");
7749
8067
  const ref = id ?? readState()?.trace_id;
7750
8068
  if (!ref) throw new Error("name a trace, or start one here");
7751
8069
  const bundle = await api("GET", `/traces/${ref}`);
@@ -7772,7 +8090,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
7772
8090
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
7773
8091
  }));
7774
8092
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
7775
- const { readState, record, flush, grantSender, clearState } = await import("./trace-ZRU624EY.js");
8093
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-CFHCOGKB.js");
7776
8094
  const state = readState();
7777
8095
  if (!state) throw new Error("no trace is running here");
7778
8096
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -7782,7 +8100,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
7782
8100
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
7783
8101
  }));
7784
8102
  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) => {
7785
- const lib = await import("./trace-library-QOQUAL5H.js");
8103
+ const lib = await import("./trace-library-2VHTJCOY.js");
7786
8104
  if (opts.hook) {
7787
8105
  try {
7788
8106
  let raw = payload ?? "";
@@ -7854,11 +8172,11 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
7854
8172
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
7855
8173
  }
7856
8174
  if (opts.status) {
7857
- const { readFileSync: readFileSync18 } = await import("fs");
8175
+ const { readFileSync: readFileSync19 } = await import("fs");
7858
8176
  const { join: join19 } = await import("path");
7859
8177
  let state = { sessions: {} };
7860
8178
  try {
7861
- state = JSON.parse(readFileSync18(join19(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
8179
+ state = JSON.parse(readFileSync19(join19(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
7862
8180
  } catch {
7863
8181
  }
7864
8182
  const counts = /* @__PURE__ */ new Map();
@@ -7891,7 +8209,7 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
7891
8209
  })();
7892
8210
  });
7893
8211
  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) => {
7894
- const { libraryRequest } = await import("./trace-library-QOQUAL5H.js");
8212
+ const { libraryRequest } = await import("./trace-library-2VHTJCOY.js");
7895
8213
  const workspace2 = opts.workspace ?? currentUniverse();
7896
8214
  if (!workspace2) throw new Error("name a workspace with --workspace");
7897
8215
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -7908,7 +8226,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
7908
8226
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
7909
8227
  }));
7910
8228
  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) => {
7911
- const { libraryRequest } = await import("./trace-library-QOQUAL5H.js");
8229
+ const { libraryRequest } = await import("./trace-library-2VHTJCOY.js");
7912
8230
  const workspace2 = opts.workspace ?? currentUniverse();
7913
8231
  if (!workspace2) throw new Error("name a workspace with --workspace");
7914
8232
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
@@ -8214,7 +8532,7 @@ session.command("code-review <id>").description("run the code-review assistant o
8214
8532
  }
8215
8533
  })
8216
8534
  );
8217
- session.command("snapshot <id>").description("read or revoke the public share link for a session").requiredOption("--thread <threadId>", "the thread/comparison that owns this session").option("--revoke", "revoke the snapshot link instead of reading it").option("--json", "print as JSON").action(
8535
+ session.command("snapshot <id>").usage("<id> --thread <threadId> [--revoke] [--json]").description("read or revoke the public share link for a session; --thread is required").requiredOption("--thread <threadId>", "(required) the thread/comparison that owns this session").option("--revoke", "revoke the snapshot link instead of reading it").option("--json", "print as JSON").action(
8218
8536
  run(async (id, opts) => {
8219
8537
  const path = `/sessions/${encodeURIComponent(id)}/snapshot?thread=${encodeURIComponent(opts.thread)}`;
8220
8538
  const result = await api(opts.revoke ? "DELETE" : "GET", path);