@agentsky/sky 2.22.0 → 2.23.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.
@@ -18978,7 +18978,7 @@ var CATALOG_MODELS = [
18978
18978
  "maxPromptTokens": 1048576,
18979
18979
  "input": 0.09284,
18980
18980
  "output": 0.58025,
18981
- "cachedInput": 0.09284
18981
+ "cachedInput": 9284e-6
18982
18982
  }
18983
18983
  ],
18984
18984
  "maxPrice": {
@@ -22372,7 +22372,7 @@ var ACP_AGENTS = {
22372
22372
  "registryId": "grok-build",
22373
22373
  "label": "Grok Build",
22374
22374
  "description": "xAI's coding agent and CLI",
22375
- "version": "1.0.67",
22375
+ "version": "1.0.70",
22376
22376
  "website": "https://x.ai/cli"
22377
22377
  },
22378
22378
  "harn": {
@@ -22563,7 +22563,7 @@ var MINIMAX = ["minimax-m3", "minimax-m3.1-flash"];
22563
22563
  var GEMINI = ["gemini-4-argon", "gemini-3.8-flash", "gemini-3.5-flash-lite"];
22564
22564
  var CLAUDE_5_5 = ["claude-sonnet-5-5", "claude-haiku-5-5"];
22565
22565
  var HARNESS_MODEL_ADDITIONS = {
22566
- claude_code: [{ models: CLAUDE_5_5, releases: ["2.1.295", "2.1.296", "2.1.303"] }, {
22566
+ claude_code: [{ models: CLAUDE_5_5, releases: ["2.1.295", "2.1.296", "2.1.303", "2.1.310"] }, {
22567
22567
  models: [
22568
22568
  "gpt-6.1-sol",
22569
22569
  "gpt-6-sol",
@@ -22585,7 +22585,7 @@ var HARNESS_MODEL_ADDITIONS = {
22585
22585
  ...MINIMAX,
22586
22586
  ...CATALOG_IDS
22587
22587
  ],
22588
- releases: ["2.1.303"]
22588
+ releases: ["2.1.303", "2.1.310"]
22589
22589
  }],
22590
22590
  codex: [{ models: [...CLAUDE_5_5, ...GEMINI, "grok-4.7", ...MUSE, ...MINIMAX, ...CATALOG_IDS], releases: ["0.163.0"] }],
22591
22591
  hermes: [
@@ -22606,7 +22606,7 @@ var HARNESS_MODEL_ADDITIONS = {
22606
22606
  kilo: [{ models: ["grok-4.7"], releases: ["7.8.8", "7.8.12", "7.8.15", "7.8.17"] }, { models: ["claude-sonnet-5-5", "claude-haiku-5-5"], releases: ["7.8.8", "7.8.12", "7.8.15", "7.8.17"] }],
22607
22607
  docker_cagent: [{ models: ["grok-4.7"], releases: ["1.150.0", "1.151.0", "1.152.0"] }, { models: ["claude-sonnet-5-5", "claude-haiku-5-5"], releases: ["1.150.0", "1.151.0", "1.152.0"] }],
22608
22608
  cline: [{ models: ["grok-4.7"], releases: ["3.0.70", "3.0.73"] }],
22609
- grok_build: [{ models: ["grok-4.7"], releases: ["1.0.50", "1.0.62", "1.0.67"] }],
22609
+ grok_build: [{ models: ["grok-4.7"], releases: ["1.0.50", "1.0.62", "1.0.67", "1.0.70"] }],
22610
22610
  mistral_vibe: [{ models: ["grok-4.7"], releases: ["2.26.1"] }],
22611
22611
  sigit: [{ models: ["grok-4.7"], releases: ["1.6.5", "1.7.2", "1.7.3", "1.7.4"] }],
22612
22612
  cursor: [{ models: ["kimi-k2.7-code"], releases: ["2026.10.01"] }],
@@ -3,7 +3,7 @@ import {
3
3
  apiBase,
4
4
  apiToken,
5
5
  currentUniverse
6
- } from "./chunk-XT3W75V3.js";
6
+ } from "./chunk-V5XLV37X.js";
7
7
 
8
8
  // src/api.ts
9
9
  import { createWriteStream, writeFileSync } from "fs";
@@ -145,7 +145,7 @@ async function resolveSessionRef(ref) {
145
145
  return agent.defaultSessionId;
146
146
  }
147
147
  async function* streamEvents(sessionId, signal) {
148
- const { parseSseStream } = await import("./src-TGRDQ5XR.js");
148
+ const { parseSseStream } = await import("./src-UMT6FHLG.js");
149
149
  const res = await fetch(
150
150
  `${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
151
151
  {
@@ -38,7 +38,7 @@ function currentUniverse() {
38
38
  }
39
39
 
40
40
  // src/version.ts
41
- var CLI_VERSION = "2.22.0";
41
+ var CLI_VERSION = "2.23.0";
42
42
  var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
43
43
 
44
44
  export {
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSessionRef,
10
10
  streamEvents,
11
11
  uploadFile
12
- } from "./chunk-EBH3KXSU.js";
12
+ } from "./chunk-HQPOCQRQ.js";
13
13
  import {
14
14
  CLI_VERSION,
15
15
  USER_AGENT,
@@ -20,7 +20,7 @@ import {
20
20
  currentUniverse,
21
21
  loadConfig,
22
22
  saveConfig
23
- } from "./chunk-XT3W75V3.js";
23
+ } from "./chunk-V5XLV37X.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -42,7 +42,7 @@ import {
42
42
  skillRefSchema,
43
43
  splitMcpServers,
44
44
  supersededBy
45
- } from "./chunk-YMYQT6KR.js";
45
+ } from "./chunk-HDPDY77W.js";
46
46
  import "./chunk-MLKGABMK.js";
47
47
 
48
48
  // src/index.ts
@@ -410,6 +410,52 @@ function renderApiKey(key) {
410
410
  return `${key.id} ${key.token_prefix}\u2026 ${key.status.padEnd(7)} ${key.scopes.join(",").padEnd(16)} ${where} ${used} ${key.name}${key.current ? " (this key)" : ""}`;
411
411
  }
412
412
 
413
+ // src/account-access.ts
414
+ function linkToken(ref) {
415
+ const fromUrl = /\/s\/([A-Za-z0-9_-]+)\/?(?:[?#].*)?$/.exec(ref.trim());
416
+ const token = fromUrl ? fromUrl[1] : ref.trim();
417
+ if (!/^[A-Za-z0-9_-]{16,64}$/.test(token)) throw new ApiError(400, "invalid_request", "a share link is a /s/<token> URL or its token");
418
+ return token;
419
+ }
420
+ async function sendViaLink(ref, text, idempotencyKey) {
421
+ await api("POST", `/links/${encodeURIComponent(linkToken(ref))}/messages`, { text }, { idempotencyKey });
422
+ }
423
+ async function stopViaLink(ref) {
424
+ await api("POST", `/links/${encodeURIComponent(linkToken(ref))}/interrupt`);
425
+ }
426
+ function inviteCode(ref) {
427
+ const fromUrl = /\/i\/([^/?#]+)\/?(?:[?#].*)?$/.exec(ref.trim());
428
+ const code = fromUrl ? decodeURIComponent(fromUrl[1]) : ref.trim();
429
+ if (!code) throw new ApiError(400, "invalid_request", "a credit invite is an /i/<code> URL or its code");
430
+ return code;
431
+ }
432
+ async function claimCreditInvite(ref) {
433
+ return api("POST", "/billing/credit-invites/claim", { code: inviteCode(ref) });
434
+ }
435
+ function renderCreditInviteClaim(claim) {
436
+ const amount = `$${claim.amount_usd.toFixed(2)}`;
437
+ return claim.status === "granted" ? `Added ${amount} of credit to your balance` : `Already claimed: this link added ${amount} to your balance`;
438
+ }
439
+ async function listAppGrants() {
440
+ return (await api("GET", "/oauth_grants")).oauth_grants;
441
+ }
442
+ async function revokeAppGrant(clientId) {
443
+ await api("DELETE", `/oauth_grants/${encodeURIComponent(clientId)}`);
444
+ }
445
+ function renderAppGrant(grant) {
446
+ const since = grant.allowed_at ? ` since ${grant.allowed_at.slice(0, 10)}` : "";
447
+ const review = grant.review_status === "approved" ? "" : " (unreviewed)";
448
+ return `${grant.client_id} ${grant.name || "(unnamed)"} ${grant.scopes.join(" ")}${since}${review}`;
449
+ }
450
+ async function unsubscribeMarketing() {
451
+ const { notification_preferences } = await api(
452
+ "PATCH",
453
+ "/notification-preferences",
454
+ { email: { marketing: false } }
455
+ );
456
+ return { marketing: notification_preferences.email.marketing };
457
+ }
458
+
413
459
  // src/context.ts
414
460
  function memoryBody(operation, version) {
415
461
  return { ...operation, expectedVersion: version };
@@ -682,9 +728,9 @@ function readClaudeCode() {
682
728
  sources.push("global CLAUDE.md");
683
729
  }
684
730
  for (const name of ["CLAUDE.md", "AGENTS.md"]) {
685
- const project = readIfExists(join3(process.cwd(), name));
686
- if (project?.trim()) {
687
- parts.push({ label: `Project ${name}`, content: project });
731
+ const project2 = readIfExists(join3(process.cwd(), name));
732
+ if (project2?.trim()) {
733
+ parts.push({ label: `Project ${name}`, content: project2 });
688
734
  sources.push(`project ${name}`);
689
735
  break;
690
736
  }
@@ -739,11 +785,11 @@ function linkPath(configRoot) {
739
785
  return join3(configRoot, "agentsky-link.json");
740
786
  }
741
787
  function readLink(configRoot) {
742
- const link = readJson(linkPath(configRoot));
743
- return link?.agentId ? link : void 0;
788
+ const link2 = readJson(linkPath(configRoot));
789
+ return link2?.agentId ? link2 : void 0;
744
790
  }
745
- function writeLink(configRoot, link) {
746
- writeFileSync3(linkPath(configRoot), `${JSON.stringify(link, null, 2)}
791
+ function writeLink(configRoot, link2) {
792
+ writeFileSync3(linkPath(configRoot), `${JSON.stringify(link2, null, 2)}
747
793
  `);
748
794
  }
749
795
  var DEFAULT_VAULT_NAME = "default";
@@ -909,14 +955,14 @@ async function createClone(local, opts) {
909
955
  await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
910
956
  if (opts.open) openBrowser(url);
911
957
  }
912
- async function syncClone(local, link, opts) {
913
- const since = link.lastSyncedAt ?? link.clonedAt;
958
+ async function syncClone(local, link2, opts) {
959
+ const since = link2.lastSyncedAt ?? link2.clonedAt;
914
960
  console.log(
915
- `Syncing ${link.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
961
+ `Syncing ${link2.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
916
962
  );
917
963
  if (opts.dryRun) {
918
964
  console.log(
919
- ` would push prompt (${kb(local.prompt)}) to ${link.agentId}. Dry run \u2014 nothing sent.`
965
+ ` would push prompt (${kb(local.prompt)}) to ${link2.agentId}. Dry run \u2014 nothing sent.`
920
966
  );
921
967
  return;
922
968
  }
@@ -924,24 +970,24 @@ async function syncClone(local, link, opts) {
924
970
  try {
925
971
  ({ agent: agent2 } = await api(
926
972
  "GET",
927
- `/agents/${encodeURIComponent(link.agentId)}`
973
+ `/agents/${encodeURIComponent(link2.agentId)}`
928
974
  ));
929
975
  } catch (error) {
930
976
  if (error instanceof ApiError && error.status === 404) {
931
977
  throw new Error(
932
- `linked agent ${link.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
978
+ `linked agent ${link2.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
933
979
  );
934
980
  }
935
981
  throw error;
936
982
  }
937
983
  if (agent2.agentType !== local.harness) {
938
984
  throw new Error(
939
- `linked agent ${link.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
985
+ `linked agent ${link2.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
940
986
  );
941
987
  }
942
988
  const result = await api(
943
989
  "PUT",
944
- `/agents/${encodeURIComponent(link.agentId)}/prompt`,
990
+ `/agents/${encodeURIComponent(link2.agentId)}/prompt`,
945
991
  { prompt: local.prompt }
946
992
  );
947
993
  console.log(
@@ -949,11 +995,11 @@ async function syncClone(local, link, opts) {
949
995
  );
950
996
  const drift = [];
951
997
  const localModel = local.model ?? null;
952
- const linkedModel = link.llm ?? agent2.llm;
998
+ const linkedModel = link2.llm ?? agent2.llm;
953
999
  if (localModel && linkedModel && localModel !== linkedModel) {
954
1000
  drift.push(`model changed ${linkedModel} \u2192 ${localModel}`);
955
1001
  }
956
- const before = new Set(link.mcpServers ?? []);
1002
+ const before = new Set(link2.mcpServers ?? []);
957
1003
  const nowKept = Object.keys(local.keptServers);
958
1004
  const newServers = nowKept.filter((n) => !before.has(n));
959
1005
  const goneServers = [...before].filter((n) => !nowKept.includes(n));
@@ -968,16 +1014,16 @@ async function syncClone(local, link, opts) {
968
1014
  ` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
969
1015
  );
970
1016
  }
971
- writeLink(local.configRoot, { ...link, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
972
- console.log(`Synced ${link.agentId}.`);
1017
+ writeLink(local.configRoot, { ...link2, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
1018
+ console.log(`Synced ${link2.agentId}.`);
973
1019
  await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
974
- if (opts.open) openBrowser(`${apiBase()}/agents/${link.agentId}`);
1020
+ if (opts.open) openBrowser(`${apiBase()}/agents/${link2.agentId}`);
975
1021
  }
976
1022
  async function clone(opts) {
977
1023
  const harness2 = detectHarness(opts.harness);
978
1024
  const local = readLocal(harness2);
979
- const link = opts.new ? void 0 : readLink(local.configRoot);
980
- if (link) await syncClone(local, link, opts);
1025
+ const link2 = opts.new ? void 0 : readLink(local.configRoot);
1026
+ if (link2) await syncClone(local, link2, opts);
981
1027
  else await createClone(local, opts);
982
1028
  }
983
1029
 
@@ -1000,7 +1046,7 @@ import { homedir as homedir2 } from "os";
1000
1046
  import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
1001
1047
  import { parse as parseToml2 } from "smol-toml";
1002
1048
  var MAX_TREE = 5e3;
1003
- var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
1049
+ var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
1004
1050
  function git(cwd, ...args) {
1005
1051
  const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
1006
1052
  return run2.status === 0 ? run2.stdout : null;
@@ -1046,8 +1092,7 @@ function skillRoots(cwd, home) {
1046
1092
  ];
1047
1093
  }
1048
1094
  function pluginSkill(plugin, skill, home) {
1049
- const base = join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins");
1050
- const walk = (dir, depth) => {
1095
+ const walk = (dir, depth, underPlugin) => {
1051
1096
  if (depth > 7) return null;
1052
1097
  let entries;
1053
1098
  try {
@@ -1058,13 +1103,13 @@ function pluginSkill(plugin, skill, home) {
1058
1103
  for (const entry2 of entries) {
1059
1104
  if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
1060
1105
  const path = join4(dir, entry2.name);
1061
- if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join4(path, "SKILL.md"))) return path;
1062
- const found = walk(path, depth + 1);
1106
+ if (entry2.name === skill && basename4(dir) === "skills" && (underPlugin || path.includes(`${sep}${plugin}${sep}`)) && isFile(join4(path, "SKILL.md"))) return realpathSync(path);
1107
+ const found = walk(path, depth + 1, underPlugin);
1063
1108
  if (found) return found;
1064
1109
  }
1065
1110
  return null;
1066
1111
  };
1067
- return walk(base, 0);
1112
+ return walk(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins"), 0, false) ?? walk(join4(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions", "skills-plugin"), 0, true);
1068
1113
  }
1069
1114
  function findSkill(name, cwd, home) {
1070
1115
  const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
@@ -1084,23 +1129,24 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
1084
1129
  if (![...dirs.values()].includes(real)) dirs.set(name, real);
1085
1130
  return true;
1086
1131
  };
1132
+ const called = [];
1087
1133
  for (const record of records) {
1088
1134
  const texts = [];
1089
1135
  strings(record, texts);
1090
- for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].replace(/[.,;:)]+$/, ""));
1136
+ for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].trim().replace(/[.,;:)]+$/, ""));
1091
1137
  const content = record?.message?.content;
1092
1138
  if (!Array.isArray(content)) continue;
1093
1139
  for (const block of content) {
1094
1140
  const b = block;
1095
- if (b?.type !== "tool_use" || b.name !== "Skill" || typeof b.input?.skill !== "string") continue;
1096
- const name = b.input.skill.replace(/^\//, "");
1097
- if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
1098
- const dir = findSkill(name, cwd, home);
1099
- if (!dir || !add(dir, name)) missing.add(name);
1141
+ if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
1100
1142
  }
1101
1143
  }
1102
1144
  for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname3(path));
1103
- for (const name of dirs.keys()) missing.delete(name);
1145
+ for (const name of called) {
1146
+ if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
1147
+ const dir = findSkill(name, cwd, home);
1148
+ if (!dir || !add(dir, name)) missing.add(name);
1149
+ }
1104
1150
  return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
1105
1151
  }
1106
1152
  function readJson2(path) {
@@ -4961,6 +5007,44 @@ function renderBrowserProfile(profile) {
4961
5007
  return lines.join("\n");
4962
5008
  }
4963
5009
 
5010
+ // src/mac.ts
5011
+ var REACH_KINDS = ["folder", "browser", "screen", "host"];
5012
+ var OPEN = /* @__PURE__ */ new Set(["pending", "asking"]);
5013
+ var MAX_WAIT_SECONDS = 25;
5014
+ async function listMacs() {
5015
+ return (await api("GET", "/desktop/machines")).machines;
5016
+ }
5017
+ async function askMac(machine, kind, reason) {
5018
+ if (!REACH_KINDS.includes(kind)) throw new Error(`kind must be one of ${REACH_KINDS.join(", ")}`);
5019
+ return api("POST", `/desktop/machines/${encodeURIComponent(machine)}/reach-requests`, { kind, reason });
5020
+ }
5021
+ async function readReachRequest(id, waitSeconds = 0) {
5022
+ const wait = Math.min(Math.max(Math.floor(waitSeconds), 0), MAX_WAIT_SECONDS);
5023
+ return (await api("GET", `/desktop/reach-requests/${encodeURIComponent(id)}${wait ? `?wait=${wait}` : ""}`)).reach_request;
5024
+ }
5025
+ async function waitForAnswer(id, budgetSeconds) {
5026
+ const until = Date.now() + budgetSeconds * 1e3;
5027
+ for (; ; ) {
5028
+ const left = Math.ceil((until - Date.now()) / 1e3);
5029
+ const request = await readReachRequest(id, Math.max(left, 0));
5030
+ if (!OPEN.has(request.status) || left <= 0) return request;
5031
+ }
5032
+ }
5033
+ async function takeBack(mount) {
5034
+ await api("DELETE", `/desktop/mounts/${encodeURIComponent(mount)}`);
5035
+ }
5036
+ function renderMac(mac2) {
5037
+ const lends = mac2.reach ? mac2.reach.kinds.join(", ") || "nothing" : "unknown (update the Mac app)";
5038
+ const screen = mac2.reach && !mac2.reach.kinds.includes("screen") ? ` screen: ${mac2.reach.screen}` : "";
5039
+ const lines = [`${mac2.id} ${mac2.name} ${mac2.online ? "online" : "offline"} can lend: ${lends}${screen}`];
5040
+ for (const mount of mac2.mounts ?? []) lines.push(` ${mount.id} ${mount.kind.padEnd(7)} ${mount.root ?? mount.label}`);
5041
+ return lines.join("\n");
5042
+ }
5043
+ function renderReachRequest(request) {
5044
+ if (request.status === "granted" && request.mount) return `${request.id} granted ${request.mount.id} (${request.mount.kind}${request.mount.root ? ` ${request.mount.root}` : ""})`;
5045
+ return `${request.id} ${request.status}${request.error ? ` ${request.error.code}: ${request.error.message}` : ""}`;
5046
+ }
5047
+
4964
5048
  // src/compare.ts
4965
5049
  async function listComparisons(opts = {}) {
4966
5050
  const qs = opts.archived ? "?archived=true" : "";
@@ -4981,6 +5065,53 @@ async function listThreads2(opts = {}) {
4981
5065
  const { threads } = await api("GET", `/threads${qs}`);
4982
5066
  return threads;
4983
5067
  }
5068
+ async function startThread(body) {
5069
+ return api("POST", "/threads", body);
5070
+ }
5071
+ var threadPath = (id) => `/threads/${encodeURIComponent(id)}`;
5072
+ async function getThread(id) {
5073
+ return api("GET", threadPath(id));
5074
+ }
5075
+ async function organizeThread(id, change) {
5076
+ return api("PATCH", threadPath(id), change);
5077
+ }
5078
+ async function deleteThread(id) {
5079
+ return api("DELETE", threadPath(id));
5080
+ }
5081
+ async function sendThreadMessage(id, body) {
5082
+ return api("POST", `${threadPath(id)}/messages`, body);
5083
+ }
5084
+ async function addThreadAgent(id, body) {
5085
+ return api("POST", `${threadPath(id)}/agents`, body);
5086
+ }
5087
+ async function removeThreadAgent(id, columnId) {
5088
+ return api("DELETE", `${threadPath(id)}/agents/${encodeURIComponent(columnId)}`);
5089
+ }
5090
+ async function interruptThread(id, columnId) {
5091
+ return api("POST", `${threadPath(id)}/interrupt`, columnId ? { columnId } : {});
5092
+ }
5093
+ async function forkThread(id, body) {
5094
+ return api("POST", `${threadPath(id)}/fork`, body);
5095
+ }
5096
+ async function changeThreadModel(id, change, columnId) {
5097
+ return api("PATCH", `${threadPath(id)}/engine${columnId ? `?column=${encodeURIComponent(columnId)}` : ""}`, change);
5098
+ }
5099
+ async function searchThreads(q, opts = {}) {
5100
+ const params = new URLSearchParams({ q, ...opts.threads ? { threads: opts.threads } : {}, ...opts.cursor ? { cursor: opts.cursor } : {} });
5101
+ return api("GET", `/threads/search?${params}`);
5102
+ }
5103
+ async function threadOptions() {
5104
+ return api("GET", "/threads/options");
5105
+ }
5106
+ async function joinThread(sessionId) {
5107
+ return api("POST", "/threads/joinable", { sessionId });
5108
+ }
5109
+ async function listProjects() {
5110
+ return api("GET", "/projects");
5111
+ }
5112
+ async function rememberProject(project2) {
5113
+ return api("PUT", "/projects", { project: project2 });
5114
+ }
4984
5115
  function renderComparisonSummary(c) {
4985
5116
  const agents = c.agents.map((a) => `${a.agentType}/${a.llm}`).join(" vs ");
4986
5117
  const status = c.statusLine ?? c.attention ?? (c.archived ? "archived" : "active");
@@ -4993,6 +5124,115 @@ function renderThreadSummary(t) {
4993
5124
  return `${t.id.slice(0, 12)} ${flag}${t.title.slice(0, 40).padEnd(44)} [${agents}] ${status}`;
4994
5125
  }
4995
5126
 
5127
+ // src/library.ts
5128
+ var artifactPath = (id) => `/library/artifacts/${encodeURIComponent(id)}`;
5129
+ async function listLibrary(query = {}) {
5130
+ const params = new URLSearchParams(Object.entries(query).filter((entry2) => Boolean(entry2[1])));
5131
+ const qs = params.size ? `?${params}` : "";
5132
+ return api("GET", `/library/artifacts${qs}`);
5133
+ }
5134
+ async function uploadToLibrary(file, requestId, workspace2) {
5135
+ const form = new FormData();
5136
+ form.append("file", new Blob([new Uint8Array(file.bytes)], { type: file.mediaType }), file.name);
5137
+ form.append("requestId", requestId);
5138
+ if (workspace2) form.append("workspace", workspace2);
5139
+ return apiForm("POST", "/library/artifacts", form);
5140
+ }
5141
+ async function getLibraryArtifact(id) {
5142
+ return api("GET", artifactPath(id));
5143
+ }
5144
+ async function deleteLibraryArtifact(id, revision) {
5145
+ return api("DELETE", artifactPath(id), { revision });
5146
+ }
5147
+ async function shareLibraryArtifact(id, enabled) {
5148
+ const { currentVersion } = await getLibraryArtifact(id);
5149
+ return api("PATCH", `${artifactPath(id)}/share`, { enabled, revision: currentVersion });
5150
+ }
5151
+ async function placeLibraryArtifact(id, workspace2) {
5152
+ return api("PATCH", `${artifactPath(id)}/workspace`, { workspace: workspace2 });
5153
+ }
5154
+ async function downloadLibraryArtifact(id, version) {
5155
+ return apiBytes("GET", `${artifactPath(id)}/download${version ? `?version=${version}` : ""}`);
5156
+ }
5157
+ function renderLibraryArtifact(artifact2) {
5158
+ const version = artifact2.currentVersion ? `v${artifact2.currentVersion}` : "";
5159
+ return `${artifact2.id} ${artifact2.name} ${version}${artifact2.shared ? " shared" : ""}`;
5160
+ }
5161
+
5162
+ // src/billing-plans.ts
5163
+ var readCodingPlan = () => api("GET", "/billing/coding-plan");
5164
+ function joinCodingPlan(dollars2, requestId) {
5165
+ const amount = Number(dollars2.trim().replace(/^\$/, ""));
5166
+ if (!Number.isInteger(amount) || amount <= 0) throw new Error(`<dollars> must be whole dollars, got ${JSON.stringify(dollars2)}`);
5167
+ return api("POST", "/billing/coding-plan", { amount_usd: amount, request_id: requestId });
5168
+ }
5169
+ var cancelCodingPlan = () => api("DELETE", "/billing/coding-plan");
5170
+ function renderCodingPlan(plan) {
5171
+ if (!plan.active) {
5172
+ const lines2 = [`coding plan: not a member (enrollment ${plan.enrollment}; monthly amounts $${plan.amounts_usd.join(", $")})`];
5173
+ if (plan.charge_failure) lines2.push(` last charge failed: ${plan.charge_failure}`);
5174
+ return lines2.join("\n");
5175
+ }
5176
+ const until = plan.current_period_end?.slice(0, 10) ?? "?";
5177
+ const lines = [`coding plan: $${plan.monthly_credit_usd}/month, paid until ${until}, ${plan.renews ? "renews" : "does not renew"}`];
5178
+ if (plan.charge_failure) lines.push(` last charge failed: ${plan.charge_failure}`);
5179
+ return lines.join("\n");
5180
+ }
5181
+ function readRequestUsage(opts) {
5182
+ const query = usageQuery(opts);
5183
+ const cursor = opts.cursor ? `${query ? "&" : "?"}cursor=${encodeURIComponent(opts.cursor)}` : "";
5184
+ return api("GET", `/billing/usage/requests${query}${cursor}`);
5185
+ }
5186
+ function renderRequestUsage(page) {
5187
+ const lines = page.rows.map((row) => `${row.at.slice(0, 19).replace("T", " ")} ${(row.apiKeyName ?? "").padEnd(16).slice(0, 16)} ${(row.model ?? "-").padEnd(28).slice(0, 28)} ${String(row.httpStatus ?? "-").padStart(3)} ${String(row.inputTokens ?? 0).padStart(9)} in ${String(row.outputTokens ?? 0).padStart(8)} out ${`$${(row.billedCostUsd ?? 0).toFixed(6)}`.padStart(11)}`);
5188
+ if (!lines.length) lines.push("no API requests in this range");
5189
+ if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
5190
+ return lines.join("\n");
5191
+ }
5192
+ var readReferralLinks = () => api("GET", "/billing/referral-links");
5193
+ function referralLinkBody(opts) {
5194
+ if (opts.price === void 0 === !opts.sku?.length) throw new Error("pass --price for every model, or --sku id=multiplier for each SKU, not both");
5195
+ const body = {};
5196
+ if (opts.price !== void 0) body.customer_multiplier = multiplier("--price", opts.price);
5197
+ if (opts.sku?.length) {
5198
+ body.sku_prices = opts.sku.map((pair) => {
5199
+ const at = pair.lastIndexOf("=");
5200
+ if (at <= 0) throw new Error(`--sku must be id=multiplier, got ${JSON.stringify(pair)}`);
5201
+ return { sku_id: pair.slice(0, at), multiplier: multiplier("--sku", pair.slice(at + 1)) };
5202
+ });
5203
+ }
5204
+ if (opts.email) body.audience_email = opts.email;
5205
+ if (opts.uses) body.max_redemptions = wholeNumber2("--uses", opts.uses);
5206
+ if (opts.days) body.expires_in_days = wholeNumber2("--days", opts.days);
5207
+ return body;
5208
+ }
5209
+ var createReferralLink = (opts) => api("POST", "/billing/referral-links", referralLinkBody(opts));
5210
+ var disableReferralLink = (id) => api("POST", `/billing/referral-links/${encodeURIComponent(id)}/disable`);
5211
+ function renderReferralLinks(dashboard) {
5212
+ const lines = [`priced from ${dashboard.source_plan.name}: customers pay at least ${Math.round(dashboard.source_plan.multiplier * 100)}% of public price`];
5213
+ for (const link2 of dashboard.links) {
5214
+ const state = link2.disabled_at ? "disabled" : `expires ${link2.expires_at.slice(0, 10)}`;
5215
+ const audience = link2.audience_email ? ` for ${link2.audience_email} (${link2.email_status})` : "";
5216
+ lines.push(` ${link2.id} ${Math.round(link2.customer_multiplier * 100)}% ${link2.redeemed_count}/${link2.max_redemptions} used ${state}${audience}`);
5217
+ lines.push(` ${link2.url}`);
5218
+ }
5219
+ if (!dashboard.links.length) lines.push(" no links yet");
5220
+ const earned = dashboard.earnings.reduce((sum, earning) => sum + earning.amount_usd, 0);
5221
+ lines.push(`earned $${earned.toFixed(2)} from ${dashboard.earnings.length} customer${dashboard.earnings.length === 1 ? "" : "s"}`);
5222
+ return lines.join("\n");
5223
+ }
5224
+ function multiplier(flag, value) {
5225
+ const trimmed = value.trim();
5226
+ const percent = trimmed.endsWith("%");
5227
+ const number = Number(percent ? trimmed.slice(0, -1) : trimmed);
5228
+ if (!Number.isFinite(number) || number <= 0) throw new Error(`${flag} must be a share of public price like 0.8 or 80%, got ${JSON.stringify(value)}`);
5229
+ return percent ? number / 100 : number;
5230
+ }
5231
+ function wholeNumber2(flag, value) {
5232
+ if (!/^\d+$/.test(value.trim())) throw new Error(`${flag} must be a whole number, got ${JSON.stringify(value)}`);
5233
+ return Number(value.trim());
5234
+ }
5235
+
4996
5236
  // src/harness.ts
4997
5237
  import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
4998
5238
  import { dirname as dirname9, join as join18, resolve as resolve8 } from "path";
@@ -5231,6 +5471,19 @@ auth.command("usage <provider>").description("plan allowance left on a connected
5231
5471
  auth.command("switch-to-credits <provider>").description("move the agents on a model subscription onto platform credits (do this before disconnecting)").option("--account <id>", "an additional account (default: the provider's first account)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => switchToCredits(name, opts)));
5232
5472
  auth.command("accounts").description("list connected model subscriptions and the agents they power").action(run(async () => listSubscriptions()));
5233
5473
  auth.command("disconnect <provider>").description("disconnect a model subscription (agents on it fail until switched)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => disconnect(name, opts)));
5474
+ auth.command("apps").description("the registered apps you let into your account, with the scopes you allowed").action(
5475
+ run(async () => {
5476
+ const grants = await listAppGrants();
5477
+ if (!grants.length) console.log("No apps have access to your account.");
5478
+ for (const grant of grants) console.log(renderAppGrant(grant));
5479
+ })
5480
+ );
5481
+ auth.command("revoke-app <clientId>").description("remove an app's access to your account; its tokens stop working at once (admin scope)").action(
5482
+ run(async (clientId) => {
5483
+ await revokeAppGrant(clientId);
5484
+ console.log(`Removed ${clientId}'s access to your account`);
5485
+ })
5486
+ );
5234
5487
  var universe = program.command("universe").description("grouping boundary for agents");
5235
5488
  universe.command("list").action(
5236
5489
  run(async () => {
@@ -5277,7 +5530,7 @@ universe.command("invite <emails...>").description("invite people by email; prin
5277
5530
  run(async (emails, opts) => {
5278
5531
  const result = await invite(await workspaceSlug(opts.universe), emails, opts);
5279
5532
  for (const i of result.invites) console.log(`invited ${i.email} as ${i.role} ${i.id}`);
5280
- for (const link of result.links) console.log(` ${link.email} joins with: sky universe join ${link.token}`);
5533
+ for (const link2 of result.links) console.log(` ${link2.email} joins with: sky universe join ${link2.token}`);
5281
5534
  if (!result.invites.length) console.log("nobody new to invite \u2014 everyone listed is already a member");
5282
5535
  })
5283
5536
  );
@@ -5594,12 +5847,12 @@ session.command("share <ref>").description("print the session's share link, turn
5594
5847
  console.log(`Sharing is off for ${id}`);
5595
5848
  return;
5596
5849
  }
5597
- const link = await api("POST", path, {
5850
+ const link2 = await api("POST", path, {
5598
5851
  access: opts.view ? "view" : "prompt",
5599
5852
  ...opts.rotate ? { rotate: true } : {}
5600
5853
  });
5601
- if (link.shareUrl) console.log(link.shareUrl);
5602
- else console.log(`view-only token: ${link.token} \u2014 read with GET /links/${link.token}/events`);
5854
+ if (link2.shareUrl) console.log(link2.shareUrl);
5855
+ else console.log(`view-only token: ${link2.token} \u2014 read with GET /links/${link2.token}/events`);
5603
5856
  })
5604
5857
  );
5605
5858
  session.command("usage <ref>").description("what the session has cost so far, and its newest billed entries").option("--limit <n>", "entries to list (default 20)").option("--json", "print the API's answer as JSON").action(
@@ -6416,6 +6669,56 @@ reflections.command("list").description("the workspace's proposals, newest first
6416
6669
  );
6417
6670
  reflections.command("approve <id>").description("write the proposal through the skill or memory store").action(run(async (id) => decideReflection(id, "approve")));
6418
6671
  reflections.command("reject <id>").description("decline the proposal; nothing is written").action(run(async (id) => decideReflection(id, "reject")));
6672
+ var library = program.command("library").description("your artifact Library on agentsky.dev: files and sites your agents saved or you uploaded");
6673
+ library.command("list").description("your Library, newest first").option("--search <text>", "only names that match").option("--cursor <cursor>", "the next page").option("--json", "print as JSON").action(
6674
+ run(async (opts) => {
6675
+ const page = await listLibrary({ q: opts.search, cursor: opts.cursor });
6676
+ if (opts.json) {
6677
+ console.log(JSON.stringify(page, null, 2));
6678
+ return;
6679
+ }
6680
+ if (!page.artifacts.length) console.log("no artifacts");
6681
+ for (const a of page.artifacts) console.log(renderLibraryArtifact(a));
6682
+ if (page.nextCursor) console.log(`more: --cursor ${page.nextCursor}`);
6683
+ })
6684
+ );
6685
+ library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
6686
+ run(async (path, opts) => {
6687
+ const bytes = readFileSync17(path);
6688
+ const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
6689
+ console.log(renderLibraryArtifact(artifact2));
6690
+ })
6691
+ );
6692
+ library.command("show <id>").description("one artifact and its saved versions").action(run(async (id) => {
6693
+ console.log(JSON.stringify(await getLibraryArtifact(id), null, 2));
6694
+ }));
6695
+ library.command("delete <id>").description("delete your artifact with all its versions").action(
6696
+ run(async (id) => {
6697
+ const { currentVersion } = await getLibraryArtifact(id);
6698
+ await deleteLibraryArtifact(id, currentVersion ?? 0);
6699
+ console.log(`deleted ${id}`);
6700
+ })
6701
+ );
6702
+ for (const [verb, enabled, description] of [
6703
+ ["share", true, "turn on the artifact's public link"],
6704
+ ["unshare", false, "turn off the artifact's public link"]
6705
+ ]) {
6706
+ library.command(`${verb} <id>`).description(description).action(run(async (id) => {
6707
+ const state = await shareLibraryArtifact(id, enabled);
6708
+ console.log(state.url ?? `${verb}: ${id}`);
6709
+ }));
6710
+ }
6711
+ library.command("place <id> <workspace>").description('place your artifact in a workspace, or "me" to take it back to you alone').action(run(async (id, workspace2) => {
6712
+ await placeLibraryArtifact(id, workspace2 === "me" ? null : workspace2);
6713
+ console.log(`placed ${id}`);
6714
+ }));
6715
+ 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
+ run(async (id, out, opts) => {
6717
+ const { bytes } = await downloadLibraryArtifact(id, opts.version ? Number(opts.version) : void 0);
6718
+ writeFileSync10(out, bytes);
6719
+ console.log(`wrote ${bytes.length} bytes to ${out}`);
6720
+ })
6721
+ );
6419
6722
  var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
6420
6723
  artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
6421
6724
  run(async (opts) => {
@@ -6683,6 +6986,19 @@ program.command("config").description("show the CLI config location and current
6683
6986
  console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
6684
6987
  console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
6685
6988
  });
6989
+ var link = program.command("link").description("a session someone shared with you by a guest link: send to it and stop it");
6990
+ link.command("send <link> <text...>").description("send a message through a guest link (/s/<token> URL or token); it runs and bills as the session's owner").option("--idempotency-key <key>", "repeat a send without sending twice (default: a new key)").action(
6991
+ run(async (ref, words, opts) => {
6992
+ await sendViaLink(ref, words.join(" "), opts.idempotencyKey ?? `sky-${crypto.randomUUID()}`);
6993
+ console.log("Sent. Read the reply with GET /links/<token>/events.");
6994
+ })
6995
+ );
6996
+ link.command("stop <link>").description("stop the turn running on the session a guest link opens").action(
6997
+ run(async (ref) => {
6998
+ await stopViaLink(ref);
6999
+ console.log("Stopped.");
7000
+ })
7001
+ );
6686
7002
  var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
6687
7003
  billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
6688
7004
  run(async () => {
@@ -6709,6 +7025,11 @@ billing.command("topup <dollars>").description("buy credit with your saved card:
6709
7025
  console.log(renderTopup(result));
6710
7026
  })
6711
7027
  );
7028
+ billing.command("claim <link>").description("claim a credit invite link (agentsky.dev/i/<code>, or just the code) for your own balance").action(
7029
+ run(async (link2) => {
7030
+ console.log(renderCreditInviteClaim(await claimCreditInvite(link2)));
7031
+ })
7032
+ );
6712
7033
  billing.command("auto-topup").description("show auto top-up, or change it: --on/--off, --threshold <5|10|20>, --amount <10|20|50|100>").option("--on", "charge the saved card when the balance falls under the threshold").option("--off", "stop charging automatically").option("--threshold <dollars>", "the balance that triggers a charge").option("--amount <dollars>", "the credit each charge adds").action(
6713
7034
  run(async (opts) => {
6714
7035
  const body = autoTopupBody(opts);
@@ -6738,6 +7059,35 @@ usageOptions(
6738
7059
  }
6739
7060
  })
6740
7061
  );
7062
+ billing.command("requests").description("one row per API request your developer API keys made, newest first, a page at a time").option("--from <day>", "first UTC day, YYYY-MM-DD (default: 29 days ago)").option("--to <day>", "first UTC day not included (default: tomorrow)").option("--key <id>", "only this API key").option("--cursor <cursor>", "the next page, as printed after the previous one").option("--json", "print the API's answer as JSON").action(
7063
+ run(async (opts) => {
7064
+ const page = await readRequestUsage(opts);
7065
+ console.log(opts.json ? JSON.stringify(page, null, 2) : renderRequestUsage(page));
7066
+ })
7067
+ );
7068
+ var codingPlan = billing.command("coding-plan").description("the coding plan: monthly credit on your saved card, API gateway calls billed at cost");
7069
+ codingPlan.command("show").description("whether you are a member, the monthly amount, and whether it renews").action(run(async () => console.log(renderCodingPlan(await readCodingPlan()))));
7070
+ codingPlan.command("join <dollars>").description("join with your saved card (charged now), or change your monthly amount from the next renewal").option("--request-id <id>", "repeat a request without charging twice (default: a new id)").action(
7071
+ run(async (dollars2, opts) => {
7072
+ console.log(renderCodingPlan(await joinCodingPlan(dollars2, opts.requestId ?? `sky-${crypto.randomUUID()}`)));
7073
+ })
7074
+ );
7075
+ codingPlan.command("cancel").description("stop the plan renewing; the paid month stays").action(run(async () => console.log(renderCodingPlan(await cancelCodingPlan()))));
7076
+ var referralLinks = billing.command("referral-links").description("customer discount links for the API gateway (accounts on a discount plan)");
7077
+ referralLinks.command("list").description("your links, their use, and what referred customers earned you").action(run(async () => console.log(renderReferralLinks(await readReferralLinks()))));
7078
+ referralLinks.command("create").description("create a link priced at no less than your own price; --email sends it to one customer").option("--price <share>", "what the customer pays for every model, as a share of public price: 0.8 or 80%").option("--sku <id=share>", "a price for one SKU; repeat for several", collect).option("--email <address>", "email the link to this customer; it then redeems once").option("--uses <n>", "how many customers can redeem it (default 100)").option("--days <n>", "days until it expires (default 30)").action(
7079
+ run(async (opts) => {
7080
+ const { url, email_status: emailStatus } = await createReferralLink(opts);
7081
+ console.log(emailStatus ? `${url}
7082
+ email: ${emailStatus}` : url);
7083
+ })
7084
+ );
7085
+ referralLinks.command("disable <id>").description("stop a link being redeemed; customers already enrolled keep their price").action(
7086
+ run(async (id) => {
7087
+ await disableReferralLink(id);
7088
+ console.log(`Disabled ${id}`);
7089
+ })
7090
+ );
6741
7091
  var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
6742
7092
  keys.command("list").action(
6743
7093
  run(async () => {
@@ -7058,6 +7408,48 @@ vendor.command("usage <id>").description("the agents and tasks that used this ve
7058
7408
  for (const t of tasks) console.log(` task ${t.session_id} ${t.title ?? "(untitled)"} ${t.last_used_at.slice(0, 10)}`);
7059
7409
  })
7060
7410
  );
7411
+ var mac = program.command("mac").description("your Macs: what each lends to agents now, and asking one for a folder, a browser window, its screen or its apps and files");
7412
+ mac.command("list").description("your Macs: online or not, what each can lend, and the folders, browser windows and screens lent now").option("--json", "print as JSON").action(
7413
+ run(async (opts) => {
7414
+ const macs = await listMacs();
7415
+ if (opts.json) {
7416
+ console.log(JSON.stringify(macs, null, 2));
7417
+ return;
7418
+ }
7419
+ if (!macs.length) console.log("no Macs connected; sign in to the AgentSky Mac app");
7420
+ for (const entry2 of macs) console.log(renderMac(entry2));
7421
+ })
7422
+ );
7423
+ mac.command("ask <machine> <kind>").description("ask a Mac to lend a folder, a browser window, its screen or its apps and files (host: local chats, call notes, uploads, saves, local git); the person answers on the Mac. Prints the MCP URL a session attaches once it is granted").requiredOption("--reason <text>", "why, in one sentence the person reads on their Mac").option("--no-wait", "return as soon as the Mac has the ask").option("--json", "print as JSON").action(
7424
+ run(async (machine, kind, opts) => {
7425
+ const asked = await askMac(machine, kind, opts.reason);
7426
+ const answer = opts.wait ? await waitForAnswer(asked.reach_request.id, 240) : asked.reach_request;
7427
+ if (opts.json) {
7428
+ console.log(JSON.stringify({ reach_request: answer, mcp_url: asked.mcp_url }, null, 2));
7429
+ return;
7430
+ }
7431
+ console.log(renderReachRequest(answer));
7432
+ console.log(`mcp_url: ${asked.mcp_url}`);
7433
+ if (answer.status === "granted") console.log("attach it as an MCP server: mcp_servers on POST /v1/sessions (MCP start_session: sources), or PATCH /v1/sessions/{id}");
7434
+ else if (!["pending", "asking"].includes(answer.status)) process.exitCode = 1;
7435
+ })
7436
+ );
7437
+ mac.command("ask-status <id>").description("where an ask stands: pending, asking, granted, denied, unavailable or expired").option("--wait <seconds>", "wait up to this long (at most 25) for the person to answer", "0").option("--json", "print as JSON").action(
7438
+ run(async (id, opts) => {
7439
+ const answer = await readReachRequest(id, Number(opts.wait) || 0);
7440
+ if (opts.json) {
7441
+ console.log(JSON.stringify(answer, null, 2));
7442
+ return;
7443
+ }
7444
+ console.log(renderReachRequest(answer));
7445
+ })
7446
+ );
7447
+ mac.command("take-back <mount>").description("end a folder, browser window, screen or apps and files lent from a Mac; every later call to it fails").action(
7448
+ run(async (mount) => {
7449
+ await takeBack(mount);
7450
+ console.log(`took back ${mount}`);
7451
+ })
7452
+ );
7061
7453
  var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
7062
7454
  browserProfile.command("list").description("your workspace's browser profiles: the sites your agents stay signed in to").option("--json", "print as JSON").action(
7063
7455
  run(async (opts) => {
@@ -7121,10 +7513,16 @@ notifications.command("get").description("show the current notification preferen
7121
7513
  console.log(JSON.stringify(prefs, null, 2));
7122
7514
  return;
7123
7515
  }
7124
- console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
7516
+ console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done} marketing=${prefs.email.marketing}`);
7125
7517
  console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
7126
7518
  })
7127
7519
  );
7520
+ notifications.command("unsubscribe-marketing").description("stop AgentSky marketing email to your address; login and account email continue").action(
7521
+ run(async () => {
7522
+ const { marketing } = await unsubscribeMarketing();
7523
+ console.log(marketing ? "Marketing email is still on." : "Unsubscribed from AgentSky marketing email.");
7524
+ })
7525
+ );
7128
7526
  notifications.command("set").description("update notification preferences (pass at least one flag)").option("--email-needs-you <bool>", "email when the task needs your input: true or false").option("--email-done <bool>", "email when the task finishes: true or false").option("--push-needs-you <bool>", "push notification when the task needs your input: true or false").option("--push-done <bool>", "push notification when the task finishes: true or false").action(
7129
7527
  run(async (opts) => {
7130
7528
  const parseBool = (v) => v === void 0 ? void 0 : v === "true" ? true : v === "false" ? false : (() => {
@@ -7282,7 +7680,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
7282
7680
  }));
7283
7681
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
7284
7682
  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) => {
7285
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-PDIOL2TC.js");
7683
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-ZRU624EY.js");
7286
7684
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
7287
7685
  const scopes = parseContentScopes(opts.capture);
7288
7686
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -7317,7 +7715,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
7317
7715
  }));
7318
7716
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
7319
7717
  try {
7320
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-PDIOL2TC.js");
7718
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-ZRU624EY.js");
7321
7719
  const chunks = [];
7322
7720
  for await (const chunk of process.stdin) chunks.push(chunk);
7323
7721
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -7330,14 +7728,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
7330
7728
  }
7331
7729
  });
7332
7730
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
7333
- const { readState, flush, grantSender } = await import("./trace-PDIOL2TC.js");
7731
+ const { readState, flush, grantSender } = await import("./trace-ZRU624EY.js");
7334
7732
  const state = readState();
7335
7733
  if (!state) throw new Error("no trace is running here");
7336
7734
  const outcome = await flush(grantSender(state), { attempts: 4 });
7337
7735
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
7338
7736
  }));
7339
7737
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
7340
- const { readState, spoolStatus } = await import("./trace-PDIOL2TC.js");
7738
+ const { readState, spoolStatus } = await import("./trace-ZRU624EY.js");
7341
7739
  const state = readState();
7342
7740
  if (!state) {
7343
7741
  console.log("No trace is running here.");
@@ -7347,7 +7745,7 @@ traceCmd.command("status").description("the running trace and its spool").action
7347
7745
  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`);
7348
7746
  }));
7349
7747
  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) => {
7350
- const { readState } = await import("./trace-PDIOL2TC.js");
7748
+ const { readState } = await import("./trace-ZRU624EY.js");
7351
7749
  const ref = id ?? readState()?.trace_id;
7352
7750
  if (!ref) throw new Error("name a trace, or start one here");
7353
7751
  const bundle = await api("GET", `/traces/${ref}`);
@@ -7374,7 +7772,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
7374
7772
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
7375
7773
  }));
7376
7774
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
7377
- const { readState, record, flush, grantSender, clearState } = await import("./trace-PDIOL2TC.js");
7775
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-ZRU624EY.js");
7378
7776
  const state = readState();
7379
7777
  if (!state) throw new Error("no trace is running here");
7380
7778
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -7384,7 +7782,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
7384
7782
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
7385
7783
  }));
7386
7784
  traceCmd.command("sync [payload]").description("upload this machine's agent sessions (Claude Code, Codex, Cursor, Gemini CLI and every other agent `--agents` lists) to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--import <export>", "add a downloaded cloud export (claude.ai, ChatGPT, Gemini, Grok, DeepSeek \u2026: its .zip, folder or file) so sync uploads it (repeatable)", collect).option("--agents", "list the agents sync reads and which are on this machine").option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
7387
- const lib = await import("./trace-library-TA3QUBHJ.js");
7785
+ const lib = await import("./trace-library-QOQUAL5H.js");
7388
7786
  if (opts.hook) {
7389
7787
  try {
7390
7788
  let raw = payload ?? "";
@@ -7493,7 +7891,7 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
7493
7891
  })();
7494
7892
  });
7495
7893
  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) => {
7496
- const { libraryRequest } = await import("./trace-library-TA3QUBHJ.js");
7894
+ const { libraryRequest } = await import("./trace-library-QOQUAL5H.js");
7497
7895
  const workspace2 = opts.workspace ?? currentUniverse();
7498
7896
  if (!workspace2) throw new Error("name a workspace with --workspace");
7499
7897
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -7510,7 +7908,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
7510
7908
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
7511
7909
  }));
7512
7910
  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) => {
7513
- const { libraryRequest } = await import("./trace-library-TA3QUBHJ.js");
7911
+ const { libraryRequest } = await import("./trace-library-QOQUAL5H.js");
7514
7912
  const workspace2 = opts.workspace ?? currentUniverse();
7515
7913
  if (!workspace2) throw new Error("name a workspace with --workspace");
7516
7914
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
@@ -7626,6 +8024,140 @@ thread.command("list").description("your workspace threads, most recently update
7626
8024
  for (const t of threads) console.log(renderThreadSummary(t));
7627
8025
  })
7628
8026
  );
8027
+ thread.command("start <text>").description("start a thread and send it its first message, as the web composer does").requiredOption("--agent <type>", "the agent's harness type (e.g. claude_code, codex)").option("--llm <model>", "the model (the harness default when omitted)").option("--effort <level>", "thinking level").option("--vs <type:model...>", "more agents that get the same message, side by side", (val, acc) => [...acc, val], []).option("--saved-agent <id>", "run on one of your saved agents").option("--workspace <slug>", "start it in this team workspace").option("--plan", "send the first message in Plan mode").option("--json", "print the new thread as JSON").action(
8028
+ run(async (text, opts) => {
8029
+ const compared = (opts.vs ?? []).map((v) => {
8030
+ const [agentType, llm] = v.split(":");
8031
+ if (!agentType) throw new ApiError(400, "invalid_request", `--vs must be type[:model], got "${v}"`);
8032
+ return { agentType, ...llm ? { llm } : {} };
8033
+ });
8034
+ const result = await startThread({
8035
+ requestId: `sky-thread-${crypto.randomUUID()}`,
8036
+ agentType: opts.agent,
8037
+ text,
8038
+ ...opts.llm ? { llm: opts.llm } : {},
8039
+ ...opts.effort ? { reasoningEffort: opts.effort } : {},
8040
+ ...compared.length ? { compare: compared } : {},
8041
+ ...opts.savedAgent ? { agentId: opts.savedAgent } : {},
8042
+ ...opts.workspace ? { workspace: opts.workspace } : {},
8043
+ ...opts.plan ? { interactionMode: "plan" } : {}
8044
+ });
8045
+ if (opts.json) {
8046
+ console.log(JSON.stringify(result, null, 2));
8047
+ return;
8048
+ }
8049
+ console.log(`started ${result.playgroundId}`);
8050
+ for (const col of result.columns) console.log(` agent ${col.columnId} session ${col.sessionId}`);
8051
+ })
8052
+ );
8053
+ thread.command("show <id>").description("one thread with every agent and the session each one runs in").option("--json", "print as JSON").action(
8054
+ run(async (id, opts) => {
8055
+ const detail = await getThread(id);
8056
+ if (opts.json) {
8057
+ console.log(JSON.stringify(detail, null, 2));
8058
+ return;
8059
+ }
8060
+ console.log(`thread ${detail.id} ${detail.title}`);
8061
+ for (const col of detail.columns) console.log(` agent ${col.id} session ${col.sessionId} ${col.blind ? "(hidden until the vote)" : `${col.agentType}/${col.llm}`} ${col.status ?? col.podStatus ?? ""}`);
8062
+ })
8063
+ );
8064
+ thread.command("rename <id> <title>").description("rename a thread").action(run(async (id, title) => {
8065
+ await organizeThread(id, { title });
8066
+ console.log(`renamed ${id}`);
8067
+ }));
8068
+ for (const [verb, change, description] of [
8069
+ ["pin", { pinned: true }, "pin a thread to the top of the rail"],
8070
+ ["unpin", { pinned: false }, "unpin a thread"],
8071
+ ["archive", { archived: true }, "put a thread away"],
8072
+ ["unarchive", { archived: false }, "bring an archived thread back"],
8073
+ ["mark-read", { seen: true }, "mark a thread read"],
8074
+ ["mark-unread", { seen: false }, "mark a thread unread"]
8075
+ ]) {
8076
+ thread.command(`${verb} <id>`).description(description).action(run(async (id) => {
8077
+ await organizeThread(id, change);
8078
+ console.log(`${verb}: ${id}`);
8079
+ }));
8080
+ }
8081
+ thread.command("delete <id>").description("delete a thread with its sessions").action(run(async (id) => {
8082
+ await deleteThread(id);
8083
+ console.log(`deleted ${id}`);
8084
+ }));
8085
+ thread.command("send <id> <text>").description("send one message to every agent of the thread, or to the ones --agent names").option("--agent <columnId...>", "only these agents (column ids from `sky thread show`)").option("--json", "print each agent's outcome as JSON").action(
8086
+ run(async (id, text, opts) => {
8087
+ const { results } = await sendThreadMessage(id, { text, deliveryId: crypto.randomUUID(), ...opts.agent?.length ? { columnIds: opts.agent } : {} });
8088
+ if (opts.json) {
8089
+ console.log(JSON.stringify(results, null, 2));
8090
+ return;
8091
+ }
8092
+ for (const [column, outcome] of Object.entries(results)) console.log(` ${column} ${outcome.ok ? "sent" : `not sent: ${outcome.error ?? "refused"}`}`);
8093
+ })
8094
+ );
8095
+ thread.command("add-agent <id>").description("add an agent to the thread, in a session of its own").requiredOption("--agent <type>", "the agent's harness type").option("--llm <model>", "the model").option("--effort <level>", "thinking level").action(
8096
+ run(async (id, opts) => {
8097
+ const { columns } = await addThreadAgent(id, { requestId: `sky-agent-${crypto.randomUUID()}`, agentType: opts.agent, ...opts.llm ? { llm: opts.llm } : {}, ...opts.effort ? { reasoningEffort: opts.effort } : {} });
8098
+ for (const col of columns) console.log(`added agent ${col.columnId} session ${col.sessionId}`);
8099
+ })
8100
+ );
8101
+ thread.command("remove-agent <id> <columnId>").description("take one agent out of the thread; its session is left as it is").action(run(async (id, columnId) => {
8102
+ await removeThreadAgent(id, columnId);
8103
+ console.log(`removed ${columnId}`);
8104
+ }));
8105
+ thread.command("stop <id>").description("stop what the thread's agents are doing").option("--agent <columnId>", "stop only this agent").action(run(async (id, opts) => {
8106
+ await interruptThread(id, opts.agent);
8107
+ console.log(`stopped ${id}`);
8108
+ }));
8109
+ thread.command("fork <id>").description("continue one agent's conversation in a thread of its own").option("--agent <columnId>", "the agent to fork (the first when omitted)").option("--at <messageId>", "carry the conversation only up to this message").option("--to <type:model>", "continue on another agent or model").action(
8110
+ run(async (id, opts) => {
8111
+ const [agentType, llm] = (opts.to ?? "").split(":");
8112
+ const { playgroundId } = await forkThread(id, {
8113
+ requestId: `sky-fork-${crypto.randomUUID()}`,
8114
+ ...opts.agent ? { columnId: opts.agent } : {},
8115
+ ...opts.at ? { messageId: opts.at } : {},
8116
+ ...agentType ? { target: { agentType, ...llm ? { llm } : {} } } : {}
8117
+ });
8118
+ console.log(`forked into ${playgroundId}`);
8119
+ })
8120
+ );
8121
+ thread.command("model <id>").description("change the model or thinking level one agent of the thread runs on, from its next turn").option("--agent <columnId>", "the agent (the first when omitted)").option("--llm <model>", "the new model").option("--effort <level>", 'the new thinking level, or "default"').action(
8122
+ run(async (id, opts) => {
8123
+ if (!opts.llm && !opts.effort) throw new ApiError(400, "invalid_request", "Provide --llm or --effort.");
8124
+ const result = await changeThreadModel(id, { ...opts.llm ? { llm: opts.llm } : {}, ...opts.effort ? { reasoningEffort: opts.effort === "default" ? null : opts.effort } : {} }, opts.agent);
8125
+ console.log(`now ${result.llm}${result.reasoningEffort ? ` (${result.reasoningEffort})` : ""}`);
8126
+ })
8127
+ );
8128
+ thread.command("search <query>").description("search the words of your conversations").option("--archived", "search archived threads only").option("--all", "search active and archived threads").option("--cursor <cursor>", "the next page").action(
8129
+ run(async (query, opts) => {
8130
+ const threads = opts.all ? "all" : opts.archived ? "archived" : void 0;
8131
+ console.log(JSON.stringify(await searchThreads(query, { ...threads ? { threads } : {}, ...opts.cursor ? { cursor: opts.cursor } : {} }), null, 2));
8132
+ })
8133
+ );
8134
+ thread.command("options").description("what a new thread can start on: agents, models, thinking levels and grantable tools").action(run(async () => {
8135
+ console.log(JSON.stringify(await threadOptions(), null, 2));
8136
+ }));
8137
+ thread.command("join <sessionId>").description("open a teammate's thread, or one shared with you, as the session link does").action(
8138
+ run(async (sessionId) => {
8139
+ const opened = await joinThread(sessionId);
8140
+ console.log(opened.readOnly ? `read-only: session ${opened.sessionId}` : `opened ${opened.playgroundId}`);
8141
+ })
8142
+ );
8143
+ var project = program.command("project").description("the GitHub repositories a new thread can start on");
8144
+ project.command("list").description("repositories a new thread can start on, and the one you chose last").option("--json", "print as JSON").action(
8145
+ run(async (opts) => {
8146
+ const listed = await listProjects();
8147
+ if (opts.json) {
8148
+ console.log(JSON.stringify(listed, null, 2));
8149
+ return;
8150
+ }
8151
+ if (listed.unavailable) console.log(listed.unavailable.message);
8152
+ for (const repo of listed.repos) console.log(`${repo.fullName === listed.last?.repoFullName ? "* " : " "}${repo.fullName}`);
8153
+ })
8154
+ );
8155
+ project.command("use [repo]").description("remember a repository (owner/name) for your next new thread, or none when omitted").option("--branch <branch>", "the branch", "main").action(
8156
+ run(async (repo, opts) => {
8157
+ const { project: remembered } = await rememberProject(repo ? { repoFullName: repo, branch: opts.branch } : null);
8158
+ console.log(remembered ? `next thread starts on ${remembered.repoFullName}@${remembered.branch}` : "next thread starts on no repository");
8159
+ })
8160
+ );
7629
8161
  session.command("feedback <id>").description("read the thumbs-up/down feedback on this session's messages").option("--json", "print as JSON").action(
7630
8162
  run(async (id, opts) => {
7631
8163
  const result = await api(
@@ -2,7 +2,7 @@ import {
2
2
  admittedInputScopeSchema,
3
3
  external_exports,
4
4
  messagePartSchema
5
- } from "./chunk-YMYQT6KR.js";
5
+ } from "./chunk-HDPDY77W.js";
6
6
  import "./chunk-MLKGABMK.js";
7
7
 
8
8
  // ../../packages/chat/src/stream.ts
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  USER_AGENT,
3
3
  configPath
4
- } from "./chunk-XT3W75V3.js";
4
+ } from "./chunk-V5XLV37X.js";
5
5
  import {
6
6
  TRACE_CONTENT_SCOPES,
7
7
  redactTraceText
8
- } from "./chunk-YMYQT6KR.js";
8
+ } from "./chunk-HDPDY77W.js";
9
9
  import "./chunk-MLKGABMK.js";
10
10
 
11
11
  // src/trace.ts
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  ApiError
3
- } from "./chunk-EBH3KXSU.js";
3
+ } from "./chunk-HQPOCQRQ.js";
4
4
  import {
5
5
  USER_AGENT,
6
6
  apiBase,
7
7
  apiToken
8
- } from "./chunk-XT3W75V3.js";
8
+ } from "./chunk-V5XLV37X.js";
9
9
  import {
10
10
  SessionSourceCollector,
11
11
  publicRemote,
12
12
  redactTraceText
13
- } from "./chunk-YMYQT6KR.js";
13
+ } from "./chunk-HDPDY77W.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
 
16
16
  // src/trace-library.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsky/sky",
3
- "version": "2.22.0",
3
+ "version": "2.23.0",
4
4
  "description": "sky \u2014 the AgentSky developer CLI: launch and talk to long-lived agents from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",