@agentsky/sky 2.20.0 → 2.20.2

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
@@ -9,7 +9,7 @@ import {
9
9
  resolveSessionRef,
10
10
  streamEvents,
11
11
  uploadFile
12
- } from "./chunk-ICSGMEG5.js";
12
+ } from "./chunk-WTFBVTMI.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-BHW2NAXP.js";
23
+ } from "./chunk-HB7SPKQ5.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -42,11 +42,11 @@ import {
42
42
  skillRefSchema,
43
43
  splitMcpServers,
44
44
  supersededBy
45
- } from "./chunk-DC3R6BV2.js";
45
+ } from "./chunk-GHTAIKQ6.js";
46
46
  import "./chunk-MLKGABMK.js";
47
47
 
48
48
  // src/index.ts
49
- import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
49
+ import { existsSync as existsSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
50
50
  import { basename as basename8, resolve as resolve9 } from "path";
51
51
  import { createInterface as createInterface6 } from "readline";
52
52
  import { Command } from "commander";
@@ -2279,6 +2279,12 @@ async function bind(connectionId, opts) {
2279
2279
  async function send(threadId, text) {
2280
2280
  await api("POST", `/channels/threads/${encodeURIComponent(threadId)}/messages`, { parts: [{ type: "markdown", text }] });
2281
2281
  }
2282
+ async function history(threadId, opts) {
2283
+ const query = new URLSearchParams();
2284
+ if (opts.limit) query.set("limit", opts.limit);
2285
+ if (opts.cursor) query.set("cursor", opts.cursor);
2286
+ return api("GET", `/channels/threads/${encodeURIComponent(threadId)}/messages?${query}`);
2287
+ }
2282
2288
 
2283
2289
  // src/connectors.ts
2284
2290
  import { createServer as createServer2 } from "http";
@@ -2705,7 +2711,7 @@ function latestTurn(events) {
2705
2711
  }
2706
2712
  return reading;
2707
2713
  }
2708
- async function history(sessionId) {
2714
+ async function history2(sessionId) {
2709
2715
  const events = [];
2710
2716
  let cursor = null;
2711
2717
  for (; ; ) {
@@ -2724,18 +2730,19 @@ async function history(sessionId) {
2724
2730
  async function readTurn(sessionId, opts = {}) {
2725
2731
  const deadline = Date.now() + (opts.timeoutSeconds ?? 600) * 1e3;
2726
2732
  for (; ; ) {
2727
- const reading = latestTurn(await history(sessionId));
2733
+ const reading = latestTurn(await history2(sessionId));
2728
2734
  if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
2729
2735
  await new Promise((resolve10) => setTimeout(resolve10, opts.intervalMs ?? 3e3));
2730
2736
  }
2731
2737
  }
2732
2738
 
2733
2739
  // src/setup.ts
2734
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2740
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
2735
2741
  import { dirname as dirname6, join as join13 } from "path";
2736
2742
  import { createInterface as createInterface3 } from "readline";
2737
2743
 
2738
2744
  // src/mcp-register.ts
2745
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
2739
2746
  import { spawnSync as spawnSync4 } from "child_process";
2740
2747
 
2741
2748
  // src/skill.ts
@@ -2831,6 +2838,25 @@ Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
2831
2838
  - Do the work yourself when it is small, when it depends on what you already have
2832
2839
  in front of you, or when explaining it would take as long as doing it.
2833
2840
 
2841
+ ## Local first, cloud when it is not here, never a stand-in
2842
+
2843
+ - When the person names an engine that is installed and signed in on this
2844
+ computer, run it here: it costs them nothing on AgentSky.
2845
+ - When it is not installed here, start that same engine in the cloud: its handle
2846
+ (\`@codex\`, \`@claude_code\`, \u2026) with \`start_session\` and a \`budget_cents\`, and say so
2847
+ in one line ("Codex is not installed on this Mac, so it ran in the cloud").
2848
+ Do not pick another engine instead.
2849
+ - Every refused tool call carries a stable error \`code\` (in the error text and in
2850
+ \`structuredContent.error\`) and, when there is one, a \`next_step\`. Read the code:
2851
+ \`insufficient_credits\`, \`workspace_wallet_empty\` and \`payment_setup_required\` mean
2852
+ the account cannot pay; \`budget_reached\` means that session's cap is spent.
2853
+ Stop that piece of work and tell the person what was refused and what unblocks
2854
+ it (credit or a card under Settings \u203A Billing, a larger \`budget_cents\`), then
2855
+ offer the engines that are installed here as the free alternative. Never hire a
2856
+ cheaper or different agent to get around a refusal.
2857
+ - \`get_usage\` shows the balance; check it before starting several cloud sessions
2858
+ at once so the person is told once, up front, instead of after the first fails.
2859
+
2834
2860
  ## How to pick
2835
2861
 
2836
2862
  Read \`${CATALOG_FILE}\` in this skill's folder. It lists every agent you can hire,
@@ -3249,6 +3275,9 @@ function redact(text, token) {
3249
3275
  return text.split(token).join(`${token.slice(0, 10)}\u2026`);
3250
3276
  }
3251
3277
  function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
3278
+ if (agent2.id === "cursor") {
3279
+ return registerCursorMcpServer(agent2, opts);
3280
+ }
3252
3281
  const commands = mcpCommands(agent2.id, opts);
3253
3282
  if (!commands) return { agent: agent2.id, outcome: "unsupported" };
3254
3283
  if (!runner.has(commands.command)) return { agent: agent2.id, outcome: "no_command", detail: `${commands.command} is not on PATH` };
@@ -3258,10 +3287,40 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
3258
3287
  if (added.status !== 0) return { agent: agent2.id, outcome: "failed", detail: redact(added.stderr.trim() || `exit ${added.status}`, opts.token) };
3259
3288
  return agent2.id === "codex" ? { agent: agent2.id, outcome: "registered", detail: `set ${CODEX_TOKEN_ENV} to an AgentSky API token in the shell that runs codex` } : { agent: agent2.id, outcome: "registered" };
3260
3289
  }
3290
+ function registerCursorMcpServer(agent2, opts) {
3291
+ const configPath2 = `${agent2.root}/mcp.json`;
3292
+ const entry2 = {
3293
+ url: opts.mcpUrl,
3294
+ headers: { Authorization: `Bearer ${opts.token}` }
3295
+ };
3296
+ if (opts.dryRun) {
3297
+ const preview = JSON.stringify({ agentsky: { url: opts.mcpUrl, headers: { Authorization: `Bearer ${opts.token.slice(0, 10)}\u2026` } } }, null, 2);
3298
+ return { agent: agent2.id, outcome: "planned", detail: `would write to ${configPath2}: ${preview}` };
3299
+ }
3300
+ let existing = { mcpServers: {} };
3301
+ try {
3302
+ const raw = readFileSync10(configPath2, "utf8");
3303
+ const parsed = JSON.parse(raw);
3304
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3305
+ existing = parsed;
3306
+ }
3307
+ } catch {
3308
+ }
3309
+ const updated = {
3310
+ ...existing,
3311
+ mcpServers: { ...existing.mcpServers ?? {}, [MCP_SERVER_NAME]: entry2 }
3312
+ };
3313
+ try {
3314
+ writeFileSync5(configPath2, JSON.stringify(updated, null, 2) + "\n", "utf8");
3315
+ } catch (err) {
3316
+ return { agent: agent2.id, outcome: "failed", detail: String(err) };
3317
+ }
3318
+ return { agent: agent2.id, outcome: "registered" };
3319
+ }
3261
3320
 
3262
3321
  // src/setup-local.ts
3263
3322
  import { isUtf8 as isUtf83 } from "buffer";
3264
- import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
3323
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
3265
3324
  import { homedir as homedir6 } from "os";
3266
3325
  import { dirname as dirname5, isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
3267
3326
  import { parse as parseToml3 } from "smol-toml";
@@ -3292,7 +3351,7 @@ function skillFiles2(root, prefix) {
3292
3351
  if (entry2.isDirectory()) {
3293
3352
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
3294
3353
  } else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
3295
- const bytes = redacted(readFileSync10(path));
3354
+ const bytes = redacted(readFileSync11(path));
3296
3355
  if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
3297
3356
  }
3298
3357
  }
@@ -3314,7 +3373,7 @@ function readLocalSkills(agents) {
3314
3373
  }
3315
3374
  }
3316
3375
  if (!isDir2 || !existsSync7(join11(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3317
- const name = frontmatterName(readFileSync10(join11(dir, "SKILL.md"), "utf8"));
3376
+ const name = frontmatterName(readFileSync11(join11(dir, "SKILL.md"), "utf8"));
3318
3377
  if (!name) {
3319
3378
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
3320
3379
  continue;
@@ -3346,7 +3405,7 @@ function inlineImports(content, from, home, depth, seen) {
3346
3405
  if (seen.includes(path)) return "";
3347
3406
  let imported;
3348
3407
  try {
3349
- imported = readFileSync10(path, "utf8");
3408
+ imported = readFileSync11(path, "utf8");
3350
3409
  } catch {
3351
3410
  return line;
3352
3411
  }
@@ -3362,7 +3421,7 @@ function readGlobalInstructions(agents, home = homedir6()) {
3362
3421
  const path = join11(agent2.root, file);
3363
3422
  let raw;
3364
3423
  try {
3365
- raw = readFileSync10(path, "utf8");
3424
+ raw = readFileSync11(path, "utf8");
3366
3425
  } catch {
3367
3426
  continue;
3368
3427
  }
@@ -3381,7 +3440,7 @@ ${item.content.trim()}`).join("\n\n");
3381
3440
  }
3382
3441
  function readJson3(path) {
3383
3442
  try {
3384
- return JSON.parse(readFileSync10(path, "utf8"));
3443
+ return JSON.parse(readFileSync11(path, "utf8"));
3385
3444
  } catch {
3386
3445
  return void 0;
3387
3446
  }
@@ -3398,7 +3457,7 @@ function serversOf(agent2, home) {
3398
3457
  if (agent2.id === "claude_code") return readJson3(join11(home, ".claude.json"))?.mcpServers ?? {};
3399
3458
  if (agent2.id === "cursor") return readJson3(join11(agent2.root, "mcp.json"))?.mcpServers ?? {};
3400
3459
  try {
3401
- return parseToml3(readFileSync10(join11(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3460
+ return parseToml3(readFileSync11(join11(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3402
3461
  } catch {
3403
3462
  return {};
3404
3463
  }
@@ -3419,7 +3478,7 @@ function findLocalSecrets(agents, home = homedir6()) {
3419
3478
 
3420
3479
  // src/subscriptions.ts
3421
3480
  import { spawn as spawn3 } from "child_process";
3422
- import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
3481
+ import { existsSync as existsSync8, readFileSync as readFileSync12 } from "fs";
3423
3482
  import { homedir as homedir7 } from "os";
3424
3483
  import { join as join12 } from "path";
3425
3484
  import { createInterface as createInterface2 } from "readline";
@@ -3521,7 +3580,7 @@ function codexSignIn(env = process.env, home = homedir7()) {
3521
3580
  if (!existsSync8(path)) {
3522
3581
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
3523
3582
  }
3524
- const raw = readFileSync11(path, "utf8");
3583
+ const raw = readFileSync12(path, "utf8");
3525
3584
  try {
3526
3585
  const parsed = JSON.parse(raw);
3527
3586
  return { path, raw, account: parsed.tokens?.account_id };
@@ -3534,7 +3593,7 @@ function cursorSignIn(env = process.env, home = homedir7()) {
3534
3593
  if (!existsSync8(path)) {
3535
3594
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
3536
3595
  }
3537
- const raw = readFileSync11(path, "utf8");
3596
+ const raw = readFileSync12(path, "utf8");
3538
3597
  try {
3539
3598
  const parsed = JSON.parse(raw);
3540
3599
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
@@ -3697,7 +3756,7 @@ function reportPath() {
3697
3756
  }
3698
3757
  function loadReport() {
3699
3758
  try {
3700
- const saved = JSON.parse(readFileSync12(reportPath(), "utf8"));
3759
+ const saved = JSON.parse(readFileSync13(reportPath(), "utf8"));
3701
3760
  return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
3702
3761
  } catch {
3703
3762
  return { agents: [], plans: [] };
@@ -3711,7 +3770,7 @@ function mergeReport(base, next) {
3711
3770
  }
3712
3771
  function saveReport(report) {
3713
3772
  mkdirSync3(dirname6(reportPath()), { recursive: true });
3714
- writeFileSync5(reportPath(), `${JSON.stringify(report, null, 2)}
3773
+ writeFileSync6(reportPath(), `${JSON.stringify(report, null, 2)}
3715
3774
  `, { mode: 384 });
3716
3775
  }
3717
3776
  function confirm3(question) {
@@ -4060,7 +4119,7 @@ function renderArtifact(artifact2) {
4060
4119
  }
4061
4120
 
4062
4121
  // src/skills-store.ts
4063
- import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
4122
+ import { readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync8 } from "fs";
4064
4123
  import { basename as basename7, join as join14, relative as relative4, resolve as resolve7 } from "path";
4065
4124
  function skillBundle(dir) {
4066
4125
  const root = resolve7(dir);
@@ -4070,7 +4129,7 @@ function skillBundle(dir) {
4070
4129
  for (const name of readdirSync7(path).sort()) {
4071
4130
  const full = join14(path, name);
4072
4131
  if (statSync8(full).isDirectory()) walk(full);
4073
- else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync13(full) });
4132
+ else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync14(full) });
4074
4133
  }
4075
4134
  };
4076
4135
  walk(root);
@@ -4109,7 +4168,7 @@ async function getSkillUsage(since) {
4109
4168
 
4110
4169
  // src/task-export.ts
4111
4170
  import { createHash as createHash6 } from "crypto";
4112
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
4171
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4113
4172
  import { dirname as dirname7, join as join15 } from "path";
4114
4173
  import { stringify as stringifyToml2 } from "smol-toml";
4115
4174
  var TEXT_MAX = 2e4;
@@ -4402,7 +4461,7 @@ async function exportTaskHarbor(taskRef, outDir, opts) {
4402
4461
  for (const [path, content] of files2) {
4403
4462
  const target = join15(outDir, path);
4404
4463
  mkdirSync4(dirname7(target), { recursive: true });
4405
- writeFileSync6(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
4464
+ writeFileSync7(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
4406
4465
  written.push(path);
4407
4466
  }
4408
4467
  return { written, record };
@@ -4677,7 +4736,9 @@ async function listBrowserProfiles() {
4677
4736
  return data;
4678
4737
  }
4679
4738
  async function getBrowserProfile(scope) {
4680
- const { profile } = await api("GET", `/browser_profiles/${encodeURIComponent(scope)}`);
4739
+ const profiles = await listBrowserProfiles();
4740
+ const profile = profiles.find((entry2) => entry2.scope === scope);
4741
+ if (!profile) throw new ApiError(404, "not_found", "browser profile not found");
4681
4742
  return profile;
4682
4743
  }
4683
4744
  async function importBrowserProfileState(scope, state) {
@@ -4687,16 +4748,24 @@ async function listBrowserProfileSites(scope) {
4687
4748
  const profile = await getBrowserProfile(scope);
4688
4749
  return profile.sites;
4689
4750
  }
4751
+ async function browserSiteReference(scope, site) {
4752
+ const sites = await listBrowserProfileSites(scope);
4753
+ const row = sites.find((entry2) => entry2.site === site) ?? sites.find((entry2) => entry2.site_id === site);
4754
+ if (!row) throw new ApiError(404, "not_found", "site not found in this browser profile");
4755
+ return row.site_id ? `${encodeURIComponent(row.site_id)}?by=id` : encodeURIComponent(row.site);
4756
+ }
4690
4757
  async function removeBrowserProfileSite(scope, site) {
4691
- await api("DELETE", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}`);
4758
+ await api("DELETE", `/browser_profiles/${encodeURIComponent(scope)}/sites/${await browserSiteReference(scope, site)}`);
4692
4759
  }
4693
4760
  async function moveBrowserProfileSite(scope, site, to) {
4694
- return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}/move`, { to });
4761
+ const reference = await browserSiteReference(scope, site);
4762
+ const [identifier, query] = reference.split("?");
4763
+ return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/sites/${identifier}/move${query ? `?${query}` : ""}`, { to });
4695
4764
  }
4696
4765
  function renderBrowserProfileSite(site) {
4697
4766
  const who = site.signed_in_by?.email ?? site.signed_in_by?.name ?? "unknown";
4698
4767
  const used = site.last_used_at ? `used ${site.last_used_at.slice(0, 10)}` : "never used";
4699
- return `${site.site.padEnd(40)} ${site.status.padEnd(14)} ${who} ${used}`;
4768
+ return `${site.site.padEnd(40)} ${site.status.padEnd(14)} ${who} ${used}${site.status === "removing" && site.site_id ? ` retry with site ID ${site.site_id}` : ""}`;
4700
4769
  }
4701
4770
  function renderBrowserProfile(profile) {
4702
4771
  const lines = [`scope: ${profile.scope} revision: ${profile.revision} sites: ${profile.sites.length}`];
@@ -4737,7 +4806,7 @@ function renderThreadSummary(t) {
4737
4806
  }
4738
4807
 
4739
4808
  // src/harness.ts
4740
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
4809
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
4741
4810
  import { dirname as dirname8, join as join16, resolve as resolve8 } from "path";
4742
4811
  var HARNESS_FILE = "harness.json";
4743
4812
  function harnessFilePath(dir) {
@@ -4749,7 +4818,7 @@ function readHarnessFile(dir) {
4749
4818
  throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
4750
4819
  }
4751
4820
  try {
4752
- return JSON.parse(readFileSync14(path, "utf8"));
4821
+ return JSON.parse(readFileSync15(path, "utf8"));
4753
4822
  } catch (err) {
4754
4823
  throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
4755
4824
  }
@@ -4780,7 +4849,7 @@ function harnessInit(dir) {
4780
4849
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
4781
4850
  }
4782
4851
  mkdirSync5(dirname8(path), { recursive: true });
4783
- writeFileSync7(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4852
+ writeFileSync8(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4784
4853
  `);
4785
4854
  console.log(`Created ${path}`);
4786
4855
  console.log(INIT_COMMENT);
@@ -5239,12 +5308,15 @@ session.command("create <agent>").description("create a session (starts provisio
5239
5308
  }
5240
5309
  )
5241
5310
  );
5242
- session.command("list [agent]").action(
5243
- run(async (agentRef) => {
5244
- const qs = agentRef ? `?agent=${encodeURIComponent(agentRef)}` : "";
5245
- const { sessions } = await api("GET", `/sessions${qs}`);
5311
+ session.command("list [agent]").option("--limit <n>", "how many of the newest sessions to list (1-500)", "100").option("--cursor <cursor>", "continue from the cursor a previous page printed").action(
5312
+ run(async (agentRef, opts = {}) => {
5313
+ const params = new URLSearchParams();
5314
+ if (agentRef) params.set("agent", agentRef);
5315
+ if (opts.limit) params.set("limit", opts.limit);
5316
+ if (opts.cursor) params.set("cursor", opts.cursor);
5317
+ const { sessions, next_cursor } = await api("GET", `/sessions?${params}`);
5246
5318
  if (!sessions.length) {
5247
- console.log("No sessions. `sky session create <agent>` to start one.");
5319
+ console.log(opts.cursor ? "No more sessions." : "No sessions. `sky session create <agent>` to start one.");
5248
5320
  return;
5249
5321
  }
5250
5322
  for (const s of sessions) {
@@ -5252,6 +5324,7 @@ session.command("list [agent]").action(
5252
5324
  `${s.id.padEnd(24)} ${(s.agentId ?? "-").padEnd(20)} ${(s.title ?? "").padEnd(16)} ${s.status}${s.default ? " *" : ""}`
5253
5325
  );
5254
5326
  }
5327
+ if (next_cursor) console.log(` more: sky session list${agentRef ? ` ${agentRef}` : ""} --cursor ${next_cursor}`);
5255
5328
  })
5256
5329
  );
5257
5330
  session.command("show <id>").action(
@@ -5485,7 +5558,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
5485
5558
  run(async (ref, path, opts) => {
5486
5559
  const bytes = await readFile(await resolveSessionRef(ref), path);
5487
5560
  if (opts.out) {
5488
- writeFileSync8(opts.out, bytes);
5561
+ writeFileSync9(opts.out, bytes);
5489
5562
  console.error(`saved ${bytes.length} bytes to ${opts.out}`);
5490
5563
  } else process.stdout.write(bytes);
5491
5564
  })
@@ -5502,7 +5575,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
5502
5575
  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)})`);
5503
5576
  if (!patch.length) console.error("nothing changed");
5504
5577
  else if (opts.out) {
5505
- writeFileSync8(opts.out, patch);
5578
+ writeFileSync9(opts.out, patch);
5506
5579
  console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
5507
5580
  } else process.stdout.write(patch);
5508
5581
  })
@@ -6010,14 +6083,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
6010
6083
  run(async (taskId, path, opts) => {
6011
6084
  const bytes = await readTaskFile(taskId, path);
6012
6085
  if (opts.out) {
6013
- writeFileSync8(opts.out, bytes);
6086
+ writeFileSync9(opts.out, bytes);
6014
6087
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
6015
6088
  } else process.stdout.write(bytes);
6016
6089
  })
6017
6090
  );
6018
6091
  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(
6019
6092
  run(async (taskId, path, local) => {
6020
- const { file, conflict } = await writeTaskFile(taskId, path, readFileSync15(local), mediaTypeFor(local));
6093
+ const { file, conflict } = await writeTaskFile(taskId, path, readFileSync16(local), mediaTypeFor(local));
6021
6094
  console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
6022
6095
  })
6023
6096
  );
@@ -6171,7 +6244,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
6171
6244
  run(async (id, path, opts) => {
6172
6245
  const bytes = await readArtifactFile(id, path);
6173
6246
  if (opts.out) {
6174
- writeFileSync8(opts.out, bytes);
6247
+ writeFileSync9(opts.out, bytes);
6175
6248
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
6176
6249
  } else process.stdout.write(bytes);
6177
6250
  })
@@ -6469,7 +6542,7 @@ usageOptions(
6469
6542
  run(async (opts) => {
6470
6543
  const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
6471
6544
  if (opts.out) {
6472
- writeFileSync8(opts.out, bytes);
6545
+ writeFileSync9(opts.out, bytes);
6473
6546
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
6474
6547
  } else {
6475
6548
  process.stdout.write(bytes);
@@ -6543,7 +6616,7 @@ webhook.command("list").action(
6543
6616
  for (const w of webhooks) console.log(webhookLine(w));
6544
6617
  })
6545
6618
  );
6546
- webhook.command("create <url>").description("register an endpoint; its signing secret is printed once").requiredOption("--event <type...>", "event types to deliver, e.g. goal.completed routine_run.failed").action(
6619
+ webhook.command("create <url>").description("register an endpoint; its signing secret is printed once").requiredOption("--event <type...>", "event types to deliver, e.g. goal.completed routine_run.failed connector.connected connector.granted").action(
6547
6620
  run(async (url, opts) => {
6548
6621
  const { webhook: created } = await api("POST", "/webhooks", { url, events: opts.event });
6549
6622
  console.error(webhookLine(created));
@@ -6587,6 +6660,9 @@ channel.command("bind <connectionId>").description("route a connection to a sess
6587
6660
  console.log(`${connectionId} \u2192 ${opts.session ?? opts.webhook}`);
6588
6661
  })
6589
6662
  );
6663
+ channel.command("history <threadId>").description("read one authorized history page; provider history or retained SDK cache, never an agent turn").option("--limit <count>", "messages per page (1..50)", "20").option("--cursor <cursor>", "opaque next_cursor returned for this thread and page size").action(run(async (threadId, opts) => {
6664
+ console.log(JSON.stringify(await history(threadId, opts), null, 2));
6665
+ }));
6590
6666
  channel.command("send <threadId> <text...>").description("post a markdown message into a connected thread").action(
6591
6667
  run(async (threadId, text) => {
6592
6668
  await send(threadId, text.join(" "));
@@ -6672,6 +6748,35 @@ connector.command("unshare <connectionId>").description("stop sharing a connecti
6672
6748
  console.log(`Stopped sharing ${connectionId} with ${slug}`);
6673
6749
  })
6674
6750
  );
6751
+ var grantLine = (g) => `${g.id} \u2192 ${g.workspace.slug} ${g.key ? `key ${g.key.name} (${g.key.id})` : "every key"} since ${g.created_at.slice(0, 10)} ${g.use_count} call${g.use_count === 1 ? "" : "s"}`;
6752
+ connector.command("grant <connectionId>").description("let a workspace's API keys call through one of your connections (POST /connectors/proxy)").option("--key <id>", "only this key of the workspace, instead of every key").option("--universe <slug>", "the workspace (default: the current one)").action(
6753
+ run(async (connectionId, opts) => {
6754
+ const slug = await workspaceSlug(opts.universe);
6755
+ const { grant } = await api("POST", `/connectors/connections/${encodeURIComponent(connectionId)}/grants`, opts.key ? { key_id: opts.key } : {}, { universe: slug });
6756
+ console.log(`Granted ${connectionId} to ${grant.key ? `key ${grant.key.name}` : "every key"} of ${slug}`);
6757
+ })
6758
+ );
6759
+ connector.command("revoke <connectionId>").description("take a grant back; the service loses the connection at once").option("--key <id>", "the grant for this one key, instead of the workspace-wide one").option("--universe <slug>", "the workspace (default: the current one)").action(
6760
+ run(async (connectionId, opts) => {
6761
+ const slug = await workspaceSlug(opts.universe);
6762
+ await api("DELETE", `/connectors/connections/${encodeURIComponent(connectionId)}/grants`, opts.key ? { key_id: opts.key } : {}, { universe: slug });
6763
+ console.log(`Revoked ${connectionId} from ${opts.key ? `key ${opts.key}` : "every key"} of ${slug}`);
6764
+ })
6765
+ );
6766
+ connector.command("grants <connectionId>").description("who may call through one of your connections with a key, and how often they did").action(
6767
+ run(async (connectionId) => {
6768
+ const { grants } = await api("GET", `/connectors/connections/${encodeURIComponent(connectionId)}/grants`);
6769
+ if (!grants.length) console.log("no grants \u2014 `sky connector grant <connectionId> --universe <slug>`");
6770
+ for (const g of grants) console.log(grantLine(g));
6771
+ })
6772
+ );
6773
+ connector.command("granted").description("connections other people let this key call through").action(
6774
+ run(async () => {
6775
+ const { connections } = await api("GET", "/connectors/connections?granted=true");
6776
+ if (!connections.length) console.log("nothing is granted to this key");
6777
+ for (const c of connections) console.log(`${connectionLine2(c)} owner ${c.owner_user_id}${c.grant.key_id ? "" : " (workspace-wide)"}`);
6778
+ })
6779
+ );
6675
6780
  connector.command("shared").description("everything you share with workspaces, and the calls made through each").action(
6676
6781
  run(async () => {
6677
6782
  const { shared } = await api("GET", "/shared-connections");
@@ -6988,7 +7093,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
6988
7093
  }));
6989
7094
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
6990
7095
  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) => {
6991
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DI2SQI7M.js");
7096
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-HTWXINXI.js");
6992
7097
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
6993
7098
  const scopes = parseContentScopes(opts.capture);
6994
7099
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -7023,7 +7128,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
7023
7128
  }));
7024
7129
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
7025
7130
  try {
7026
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DI2SQI7M.js");
7131
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-HTWXINXI.js");
7027
7132
  const chunks = [];
7028
7133
  for await (const chunk of process.stdin) chunks.push(chunk);
7029
7134
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -7036,14 +7141,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
7036
7141
  }
7037
7142
  });
7038
7143
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
7039
- const { readState, flush, grantSender } = await import("./trace-DI2SQI7M.js");
7144
+ const { readState, flush, grantSender } = await import("./trace-HTWXINXI.js");
7040
7145
  const state = readState();
7041
7146
  if (!state) throw new Error("no trace is running here");
7042
7147
  const outcome = await flush(grantSender(state), { attempts: 4 });
7043
7148
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
7044
7149
  }));
7045
7150
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
7046
- const { readState, spoolStatus } = await import("./trace-DI2SQI7M.js");
7151
+ const { readState, spoolStatus } = await import("./trace-HTWXINXI.js");
7047
7152
  const state = readState();
7048
7153
  if (!state) {
7049
7154
  console.log("No trace is running here.");
@@ -7053,7 +7158,7 @@ traceCmd.command("status").description("the running trace and its spool").action
7053
7158
  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`);
7054
7159
  }));
7055
7160
  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) => {
7056
- const { readState } = await import("./trace-DI2SQI7M.js");
7161
+ const { readState } = await import("./trace-HTWXINXI.js");
7057
7162
  const ref = id ?? readState()?.trace_id;
7058
7163
  if (!ref) throw new Error("name a trace, or start one here");
7059
7164
  const bundle = await api("GET", `/traces/${ref}`);
@@ -7080,7 +7185,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
7080
7185
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
7081
7186
  }));
7082
7187
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
7083
- const { readState, record, flush, grantSender, clearState } = await import("./trace-DI2SQI7M.js");
7188
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-HTWXINXI.js");
7084
7189
  const state = readState();
7085
7190
  if (!state) throw new Error("no trace is running here");
7086
7191
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -7090,7 +7195,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
7090
7195
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
7091
7196
  }));
7092
7197
  traceCmd.command("sync [payload]").description("upload this machine's Claude Code and Codex sessions to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
7093
- const lib = await import("./trace-library-4WWKCSVU.js");
7198
+ const lib = await import("./trace-library-QR54Q4Z3.js");
7094
7199
  if (opts.hook) {
7095
7200
  try {
7096
7201
  let raw = payload ?? "";
@@ -7151,11 +7256,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
7151
7256
  console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
7152
7257
  }
7153
7258
  if (opts.status) {
7154
- const { readFileSync: readFileSync16 } = await import("fs");
7259
+ const { readFileSync: readFileSync17 } = await import("fs");
7155
7260
  const { join: join17 } = await import("path");
7156
7261
  let state = { sessions: {} };
7157
7262
  try {
7158
- state = JSON.parse(readFileSync16(join17(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
7263
+ state = JSON.parse(readFileSync17(join17(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
7159
7264
  } catch {
7160
7265
  }
7161
7266
  const counts = /* @__PURE__ */ new Map();
@@ -7188,7 +7293,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
7188
7293
  })();
7189
7294
  });
7190
7295
  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) => {
7191
- const { libraryRequest } = await import("./trace-library-4WWKCSVU.js");
7296
+ const { libraryRequest } = await import("./trace-library-QR54Q4Z3.js");
7192
7297
  const workspace2 = opts.workspace ?? currentUniverse();
7193
7298
  if (!workspace2) throw new Error("name a workspace with --workspace");
7194
7299
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -7205,7 +7310,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
7205
7310
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
7206
7311
  }));
7207
7312
  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) => {
7208
- const { libraryRequest } = await import("./trace-library-4WWKCSVU.js");
7313
+ const { libraryRequest } = await import("./trace-library-QR54Q4Z3.js");
7209
7314
  const workspace2 = opts.workspace ?? currentUniverse();
7210
7315
  if (!workspace2) throw new Error("name a workspace with --workspace");
7211
7316
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);