@agentsky/sky 2.17.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSessionRef,
10
10
  streamEvents,
11
11
  uploadFile
12
- } from "./chunk-FU43NICZ.js";
12
+ } from "./chunk-4MVWXHZY.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-H7SWXCH7.js";
23
+ } from "./chunk-MOYCAI4S.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -37,22 +37,23 @@ import {
37
37
  modelSubscriptionProviderFor,
38
38
  reasoningEffortDocLine,
39
39
  redactLine,
40
+ redactPlainText,
40
41
  redactSecretValues,
41
42
  skillRefSchema,
42
43
  splitMcpServers,
43
44
  supersededBy
44
- } from "./chunk-ZJVFWFOY.js";
45
+ } from "./chunk-UJ3D24IG.js";
45
46
  import "./chunk-MLKGABMK.js";
46
47
 
47
48
  // src/index.ts
48
- import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
49
+ import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
49
50
  import { basename as basename8, resolve as resolve9 } from "path";
50
51
  import { createInterface as createInterface6 } from "readline";
51
52
  import { Command } from "commander";
52
53
 
53
54
  // src/files.ts
54
- import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
55
- import { basename as basename2, extname } from "path";
55
+ import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
56
+ import { basename as basename2, extname, join as join2 } from "path";
56
57
 
57
58
  // src/resumable-upload.ts
58
59
  import { createHash } from "crypto";
@@ -184,6 +185,58 @@ async function uploadLocalFile(path, expiresInSeconds, onProgress = () => {
184
185
  const bytes = readFileSync2(path);
185
186
  return uploadFile(new Uint8Array(bytes), basename2(path), mediaTypeFor(path), expiresInSeconds);
186
187
  }
188
+ var IGNORED_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
189
+ function filesUnder(paths) {
190
+ const found = [];
191
+ const walk = (path) => {
192
+ const stat = statSync2(path);
193
+ if (stat.isFile()) {
194
+ if (!IGNORED_NAMES.has(basename2(path))) found.push(path);
195
+ return;
196
+ }
197
+ if (!stat.isDirectory()) return;
198
+ for (const name of readdirSync(path).sort()) walk(join2(path, name));
199
+ };
200
+ for (const path of paths) walk(path);
201
+ return found;
202
+ }
203
+ function transient(error) {
204
+ return !(error instanceof ApiError) || error.status === 429 || error.status >= 500;
205
+ }
206
+ async function uploadLocalFiles(paths, {
207
+ concurrency = 4,
208
+ attempts = 4,
209
+ expiresInSeconds,
210
+ onUploaded,
211
+ onFailed,
212
+ wait = (ms) => new Promise((done) => setTimeout(done, ms))
213
+ }) {
214
+ let next = 0;
215
+ let uploaded = 0;
216
+ let failed = 0;
217
+ const worker = async () => {
218
+ while (next < paths.length) {
219
+ const path = paths[next++];
220
+ for (let attempt = 1; ; attempt++) {
221
+ try {
222
+ onUploaded(path, await uploadLocalFile(path, expiresInSeconds));
223
+ uploaded += 1;
224
+ break;
225
+ } catch (error) {
226
+ if (attempt < attempts && transient(error)) {
227
+ await wait(1e3 * 2 ** (attempt - 1));
228
+ continue;
229
+ }
230
+ onFailed(path, error);
231
+ failed += 1;
232
+ break;
233
+ }
234
+ }
235
+ }
236
+ };
237
+ await Promise.all(Array.from({ length: Math.min(concurrency, paths.length) }, worker));
238
+ return { uploaded, failed };
239
+ }
187
240
  function fileBlock(file) {
188
241
  const source = { type: "file", file_id: file.id };
189
242
  return file.mime_type.startsWith("image/") ? { type: "image", source } : { type: "document", source, title: file.filename };
@@ -565,12 +618,12 @@ function renderCatalogAgent(agent2) {
565
618
  import { spawn } from "child_process";
566
619
  import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
567
620
  import { homedir } from "os";
568
- import { basename as basename3, join as join2 } from "path";
621
+ import { basename as basename3, join as join3 } from "path";
569
622
  import { createInterface } from "readline";
570
623
  var PROMPT_CAP = 1e5;
571
624
  var HARNESSES = ["claude_code", "codex", "openclaw"];
572
625
  function claudeConfigRoot() {
573
- return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
626
+ return process.env.CLAUDE_CONFIG_DIR ?? join3(homedir(), ".claude");
574
627
  }
575
628
  function readIfExists(path) {
576
629
  try {
@@ -590,9 +643,9 @@ function readJson(path) {
590
643
  }
591
644
  function claudeModel(configRoot) {
592
645
  const candidates = [
593
- join2(process.cwd(), ".claude", "settings.local.json"),
594
- join2(process.cwd(), ".claude", "settings.json"),
595
- join2(configRoot, "settings.json")
646
+ join3(process.cwd(), ".claude", "settings.local.json"),
647
+ join3(process.cwd(), ".claude", "settings.json"),
648
+ join3(configRoot, "settings.json")
596
649
  ];
597
650
  for (const path of candidates) {
598
651
  const model = readJson(path)?.model;
@@ -602,11 +655,11 @@ function claudeModel(configRoot) {
602
655
  }
603
656
  function claudeMcpServers() {
604
657
  const merged = {};
605
- const userConfig = readJson(join2(homedir(), ".claude.json"));
658
+ const userConfig = readJson(join3(homedir(), ".claude.json"));
606
659
  Object.assign(merged, userConfig?.mcpServers ?? {});
607
660
  Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
608
661
  const projectConfig = readJson(
609
- join2(process.cwd(), ".mcp.json")
662
+ join3(process.cwd(), ".mcp.json")
610
663
  );
611
664
  Object.assign(merged, projectConfig?.mcpServers ?? {});
612
665
  return merged;
@@ -623,13 +676,13 @@ function readClaudeCode() {
623
676
  const configRoot = claudeConfigRoot();
624
677
  const parts = [];
625
678
  const sources = [];
626
- const global = readIfExists(join2(configRoot, "CLAUDE.md"));
679
+ const global = readIfExists(join3(configRoot, "CLAUDE.md"));
627
680
  if (global?.trim()) {
628
681
  parts.push({ label: "Global CLAUDE.md", content: global });
629
682
  sources.push("global CLAUDE.md");
630
683
  }
631
684
  for (const name of ["CLAUDE.md", "AGENTS.md"]) {
632
- const project = readIfExists(join2(process.cwd(), name));
685
+ const project = readIfExists(join3(process.cwd(), name));
633
686
  if (project?.trim()) {
634
687
  parts.push({ label: `Project ${name}`, content: project });
635
688
  sources.push(`project ${name}`);
@@ -670,8 +723,8 @@ function detectHarness(explicit) {
670
723
  return explicit;
671
724
  }
672
725
  if (existsSync2(claudeConfigRoot())) return "claude_code";
673
- if (existsSync2(join2(homedir(), ".codex"))) return "codex";
674
- if (existsSync2(join2(homedir(), ".openclaw"))) return "openclaw";
726
+ if (existsSync2(join3(homedir(), ".codex"))) return "codex";
727
+ if (existsSync2(join3(homedir(), ".openclaw"))) return "openclaw";
675
728
  throw new Error(
676
729
  "no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
677
730
  );
@@ -683,7 +736,7 @@ function readLocal(harness2) {
683
736
  );
684
737
  }
685
738
  function linkPath(configRoot) {
686
- return join2(configRoot, "agentsky-link.json");
739
+ return join3(configRoot, "agentsky-link.json");
687
740
  }
688
741
  function readLink(configRoot) {
689
742
  const link = readJson(linkPath(configRoot));
@@ -929,22 +982,22 @@ async function clone(opts) {
929
982
  }
930
983
 
931
984
  // src/import-local.ts
932
- import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
985
+ import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
933
986
  import { homedir as homedir4 } from "os";
934
- import { join as join6 } from "path";
987
+ import { join as join7 } from "path";
935
988
  import { createHash as createHash3 } from "crypto";
936
989
 
937
990
  // src/import-files.ts
938
991
  import { isUtf8 } from "buffer";
939
992
  import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
940
993
  import { homedir as homedir3 } from "os";
941
- import { dirname as dirname4, extname as extname2, isAbsolute, join as join4, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
994
+ import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
942
995
 
943
996
  // src/import-workspace.ts
944
997
  import { spawnSync } from "child_process";
945
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
998
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
946
999
  import { homedir as homedir2 } from "os";
947
- import { basename as basename4, dirname as dirname3, join as join3, relative, sep } from "path";
1000
+ import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
948
1001
  import { parse as parseToml2 } from "smol-toml";
949
1002
  var MAX_TREE = 5e3;
950
1003
  var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
@@ -967,13 +1020,13 @@ var isDir = (path) => {
967
1020
  }
968
1021
  };
969
1022
  function sharedInstructionPaths(home = homedir2()) {
970
- const rules = join3(home, ".agents", "rules");
1023
+ const rules = join4(home, ".agents", "rules");
971
1024
  let ruleFiles = [];
972
1025
  try {
973
- ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join3(rules, name));
1026
+ ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
974
1027
  } catch {
975
1028
  }
976
- return [join3(home, "AGENTS.md"), join3(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
1029
+ return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
977
1030
  }
978
1031
  function strings(value, out, depth = 0) {
979
1032
  if (depth > 12) return;
@@ -982,30 +1035,30 @@ function strings(value, out, depth = 0) {
982
1035
  else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
983
1036
  }
984
1037
  function skillRoots(cwd, home) {
985
- const claude = process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude");
986
- const codex = process.env.CODEX_HOME ?? join3(home, ".codex");
1038
+ const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
1039
+ const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
987
1040
  return [
988
- ...cwd ? [join3(cwd, ".claude", "skills"), join3(cwd, ".agents", "skills")] : [],
989
- join3(home, ".agents", "skills"),
990
- join3(claude, "skills"),
991
- join3(codex, "skills"),
992
- join3(home, ".hermes", "skills")
1041
+ ...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
1042
+ join4(home, ".agents", "skills"),
1043
+ join4(claude, "skills"),
1044
+ join4(codex, "skills"),
1045
+ join4(home, ".hermes", "skills")
993
1046
  ];
994
1047
  }
995
1048
  function pluginSkill(plugin, skill, home) {
996
- const base = join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "plugins");
1049
+ const base = join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins");
997
1050
  const walk = (dir, depth) => {
998
1051
  if (depth > 7) return null;
999
1052
  let entries;
1000
1053
  try {
1001
- entries = readdirSync(dir, { withFileTypes: true });
1054
+ entries = readdirSync2(dir, { withFileTypes: true });
1002
1055
  } catch {
1003
1056
  return null;
1004
1057
  }
1005
1058
  for (const entry2 of entries) {
1006
1059
  if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
1007
- const path = join3(dir, entry2.name);
1008
- if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join3(path, "SKILL.md"))) return path;
1060
+ 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;
1009
1062
  const found = walk(path, depth + 1);
1010
1063
  if (found) return found;
1011
1064
  }
@@ -1017,8 +1070,8 @@ function findSkill(name, cwd, home) {
1017
1070
  const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
1018
1071
  if (scope && skill) return pluginSkill(scope, skill, home);
1019
1072
  for (const root of skillRoots(cwd, home)) {
1020
- const direct = join3(root, name);
1021
- if (isFile(join3(direct, "SKILL.md"))) return realpathSync(direct);
1073
+ const direct = join4(root, name);
1074
+ if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
1022
1075
  }
1023
1076
  return null;
1024
1077
  }
@@ -1026,7 +1079,7 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
1026
1079
  const dirs = /* @__PURE__ */ new Map();
1027
1080
  const missing = /* @__PURE__ */ new Set();
1028
1081
  const add = (dir, name = basename4(dir)) => {
1029
- if (!isFile(join3(dir, "SKILL.md"))) return false;
1082
+ if (!isFile(join4(dir, "SKILL.md"))) return false;
1030
1083
  const real = realpathSync(dir);
1031
1084
  if (![...dirs.values()].includes(real)) dirs.set(name, real);
1032
1085
  return true;
@@ -1097,16 +1150,16 @@ function mcpServers(harness2, cwd, home = homedir2()) {
1097
1150
  }
1098
1151
  };
1099
1152
  if (harness2 === "claude_code") {
1100
- const config = readJson2(join3(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
1153
+ const config = readJson2(join4(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
1101
1154
  const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
1102
1155
  const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
1103
1156
  if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
1104
- if (root) push(readJson2(join3(root, ".mcp.json"))?.mcpServers, "project");
1157
+ if (root) push(readJson2(join4(root, ".mcp.json"))?.mcpServers, "project");
1105
1158
  push(config?.mcpServers, "user");
1106
1159
  }
1107
1160
  if (harness2 === "codex") {
1108
1161
  try {
1109
- const config = parseToml2(readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "config.toml"), "utf8"));
1162
+ const config = parseToml2(readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
1110
1163
  push(config.mcp_servers, "user");
1111
1164
  } catch {
1112
1165
  }
@@ -1160,13 +1213,13 @@ function skillFiles(skill) {
1160
1213
  if (depth > 8 || out.length > 400) return;
1161
1214
  let entries;
1162
1215
  try {
1163
- entries = readdirSync(dir, { withFileTypes: true });
1216
+ entries = readdirSync2(dir, { withFileTypes: true });
1164
1217
  } catch {
1165
1218
  return;
1166
1219
  }
1167
1220
  for (const entry2 of entries) {
1168
1221
  if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
1169
- const path = join3(dir, entry2.name);
1222
+ const path = join4(dir, entry2.name);
1170
1223
  if (entry2.isDirectory()) walk(path, depth + 1);
1171
1224
  else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
1172
1225
  }
@@ -1177,7 +1230,7 @@ function skillFiles(skill) {
1177
1230
  function codexThreadName(sessionId, home = homedir2()) {
1178
1231
  let name = null;
1179
1232
  try {
1180
- for (const line of readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
1233
+ for (const line of readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
1181
1234
  if (!line.includes(sessionId)) continue;
1182
1235
  try {
1183
1236
  const entry2 = JSON.parse(line);
@@ -1297,7 +1350,7 @@ function touchedPaths(records, cwd, home = homedir3()) {
1297
1350
  }
1298
1351
  function gitRoot(dir) {
1299
1352
  for (let d = dir; ; d = dirname4(d)) {
1300
- if (existsSync4(join4(d, ".git"))) return d;
1353
+ if (existsSync4(join5(d, ".git"))) return d;
1301
1354
  if (d === dirname4(d)) return null;
1302
1355
  }
1303
1356
  }
@@ -1306,12 +1359,12 @@ function instructionPaths(cwd, harness2, home = homedir3()) {
1306
1359
  if (cwd && existsSync4(cwd)) {
1307
1360
  const root = gitRoot(cwd) ?? cwd;
1308
1361
  for (let d = resolve4(cwd); ; d = dirname4(d)) {
1309
- for (const name of INSTRUCTION_FILES) out.push(join4(d, name));
1362
+ for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
1310
1363
  if (d === root || d === dirname4(d)) break;
1311
1364
  }
1312
1365
  }
1313
- if (harness2 === "claude_code") out.push(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "CLAUDE.md"));
1314
- if (harness2 === "codex") out.push(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "AGENTS.md"));
1366
+ if (harness2 === "claude_code") out.push(join5(process.env.CLAUDE_CONFIG_DIR ?? join5(home, ".claude"), "CLAUDE.md"));
1367
+ if (harness2 === "codex") out.push(join5(process.env.CODEX_HOME ?? join5(home, ".codex"), "AGENTS.md"));
1315
1368
  out.push(...sharedInstructionPaths(home));
1316
1369
  return out.filter((path) => {
1317
1370
  try {
@@ -1436,9 +1489,9 @@ function collectSessionFiles(records, opts) {
1436
1489
  // src/import-environment.ts
1437
1490
  import { spawnSync as spawnSync2 } from "child_process";
1438
1491
  import { createHash as createHash2 } from "crypto";
1439
- import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync7, readSync as readSync2, rmSync as rmSync2 } from "fs";
1492
+ import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync as readSync2, rmSync as rmSync2 } from "fs";
1440
1493
  import { tmpdir } from "os";
1441
- import { join as join5 } from "path";
1494
+ import { join as join6 } from "path";
1442
1495
  import { isUtf8 as isUtf82 } from "buffer";
1443
1496
  import { createGzip } from "zlib";
1444
1497
 
@@ -1617,14 +1670,14 @@ function listFiles(cwd) {
1617
1670
  const walk = (dir, prefix) => {
1618
1671
  let entries;
1619
1672
  try {
1620
- entries = readdirSync2(dir, { withFileTypes: true });
1673
+ entries = readdirSync3(dir, { withFileTypes: true });
1621
1674
  } catch {
1622
1675
  return;
1623
1676
  }
1624
1677
  for (const entry2 of entries) {
1625
1678
  if (SKIPPED.has(entry2.name)) continue;
1626
1679
  const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
1627
- if (entry2.isDirectory()) walk(join5(dir, entry2.name), rel);
1680
+ if (entry2.isDirectory()) walk(join6(dir, entry2.name), rel);
1628
1681
  else out.push(rel);
1629
1682
  if (out.length > MAX_ENVIRONMENT_FILES) return;
1630
1683
  }
@@ -1641,8 +1694,8 @@ function textOf(path, size) {
1641
1694
  async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
1642
1695
  const listed = listFiles(cwd);
1643
1696
  if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
1644
- const scratch = mkdtempSync(join5(tmpdir(), "sky-environment-"));
1645
- const path = join5(scratch, "environment.tar.gz");
1697
+ const scratch = mkdtempSync(join6(tmpdir(), "sky-environment-"));
1698
+ const path = join6(scratch, "environment.tar.gz");
1646
1699
  const leftOut = [];
1647
1700
  const written = /* @__PURE__ */ new Set();
1648
1701
  const hash = createHash2("sha256");
@@ -1670,7 +1723,7 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
1670
1723
  });
1671
1724
  try {
1672
1725
  for (const rel of listed) {
1673
- const full = join5(cwd, ...rel.split("/"));
1726
+ const full = join6(cwd, ...rel.split("/"));
1674
1727
  if (SECRET_FILE2.test(rel)) {
1675
1728
  leftOut.push({ path: rel, reason: "credential file" });
1676
1729
  continue;
@@ -1824,16 +1877,16 @@ function sniffTitle(filePath) {
1824
1877
  }
1825
1878
  }
1826
1879
  function listClaudeCodeSessions(limit) {
1827
- const root = process.env.CLAUDE_CONFIG_DIR ?? join6(homedir4(), ".claude");
1828
- const projectsDir = join6(root, "projects");
1880
+ const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
1881
+ const projectsDir = join7(root, "projects");
1829
1882
  if (!existsSync5(projectsDir)) return [];
1830
1883
  const sessions = [];
1831
1884
  try {
1832
- for (const encoded of readdirSync3(projectsDir)) {
1833
- const projectDir = join6(projectsDir, encoded);
1885
+ for (const encoded of readdirSync4(projectsDir)) {
1886
+ const projectDir = join7(projectsDir, encoded);
1834
1887
  let entries;
1835
1888
  try {
1836
- entries = readdirSync3(projectDir);
1889
+ entries = readdirSync4(projectDir);
1837
1890
  } catch {
1838
1891
  continue;
1839
1892
  }
@@ -1841,7 +1894,7 @@ function listClaudeCodeSessions(limit) {
1841
1894
  for (const entry2 of entries) {
1842
1895
  if (!entry2.endsWith(".jsonl")) continue;
1843
1896
  const sessionId = entry2.slice(0, -6);
1844
- const mainFile = join6(projectDir, entry2);
1897
+ const mainFile = join7(projectDir, entry2);
1845
1898
  let stat;
1846
1899
  try {
1847
1900
  stat = statSync5(mainFile);
@@ -1851,12 +1904,12 @@ function listClaudeCodeSessions(limit) {
1851
1904
  if (!stat.isFile()) continue;
1852
1905
  let discoveryError;
1853
1906
  const subagentFiles = [];
1854
- const subagentDir = join6(projectDir, sessionId, "subagents");
1907
+ const subagentDir = join7(projectDir, sessionId, "subagents");
1855
1908
  if (existsSync5(subagentDir)) {
1856
1909
  try {
1857
- const subFiles = readdirSync3(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1910
+ const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
1858
1911
  for (const f of subFiles) {
1859
- subagentFiles.push(join6(subagentDir, f));
1912
+ subagentFiles.push(join7(subagentDir, f));
1860
1913
  }
1861
1914
  } catch {
1862
1915
  discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
@@ -1878,17 +1931,17 @@ function listClaudeCodeSessions(limit) {
1878
1931
  return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
1879
1932
  }
1880
1933
  function listCodexSessions(limit) {
1881
- const sessionsDir = join6(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join6(homedir4(), ".codex"), "sessions");
1934
+ const sessionsDir = join7(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join7(homedir4(), ".codex"), "sessions");
1882
1935
  const records = [];
1883
1936
  function walk(dir) {
1884
1937
  let entries;
1885
1938
  try {
1886
- entries = readdirSync3(dir, { withFileTypes: true });
1939
+ entries = readdirSync4(dir, { withFileTypes: true });
1887
1940
  } catch {
1888
1941
  return;
1889
1942
  }
1890
1943
  for (const entry2 of entries) {
1891
- const full = join6(dir, entry2.name);
1944
+ const full = join7(dir, entry2.name);
1892
1945
  if (entry2.isDirectory()) {
1893
1946
  walk(full);
1894
1947
  continue;
@@ -2440,8 +2493,8 @@ async function triggerRequest(id, resource, change) {
2440
2493
  // src/session-workspace.ts
2441
2494
  import { spawnSync as spawnSync3 } from "child_process";
2442
2495
  import { createHash as createHash5, randomUUID } from "crypto";
2443
- import { readdirSync as readdirSync4, statSync as statSync6 } from "fs";
2444
- import { basename as basename6, join as join7, relative as relative3, resolve as resolve5 } from "path";
2496
+ import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
2497
+ import { basename as basename6, join as join8, relative as relative3, resolve as resolve5 } from "path";
2445
2498
  var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
2446
2499
  var PART_SIZE = 8 * 1024 * 1024;
2447
2500
  var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -2475,13 +2528,13 @@ function folderFiles(dir) {
2475
2528
  const git2 = spawnSync3("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
2476
2529
  const top = spawnSync3("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
2477
2530
  if (git2.status === 0 && top.status === 0) {
2478
- return git2.stdout.split("\0").filter((path) => path && statSync6(join7(dir, path), { throwIfNoEntry: false })?.isFile());
2531
+ return git2.stdout.split("\0").filter((path) => path && statSync6(join8(dir, path), { throwIfNoEntry: false })?.isFile());
2479
2532
  }
2480
2533
  const files2 = [];
2481
2534
  const walk = (at) => {
2482
- for (const entry2 of readdirSync4(at, { withFileTypes: true })) {
2535
+ for (const entry2 of readdirSync5(at, { withFileTypes: true })) {
2483
2536
  if (SKIPPED2.has(entry2.name)) continue;
2484
- const full = join7(at, entry2.name);
2537
+ const full = join8(at, entry2.name);
2485
2538
  if (entry2.isDirectory()) walk(full);
2486
2539
  else if (entry2.isFile()) files2.push(relative3(dir, full));
2487
2540
  }
@@ -2565,15 +2618,15 @@ async function handoff(id, targetAgent, opts = {}) {
2565
2618
  // src/local-agents.ts
2566
2619
  import { existsSync as existsSync6 } from "fs";
2567
2620
  import { homedir as homedir5 } from "os";
2568
- import { join as join8 } from "path";
2621
+ import { join as join9 } from "path";
2569
2622
  function localAgents(env = process.env, home = homedir5()) {
2570
- const claude = env.CLAUDE_CONFIG_DIR || join8(home, ".claude");
2571
- const codex = env.CODEX_HOME || join8(home, ".codex");
2572
- const cursor = join8(home, ".cursor");
2623
+ const claude = env.CLAUDE_CONFIG_DIR || join9(home, ".claude");
2624
+ const codex = env.CODEX_HOME || join9(home, ".codex");
2625
+ const cursor = join9(home, ".cursor");
2573
2626
  return [
2574
- { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join8(claude, "skills") },
2575
- { id: "codex", name: "Codex", root: codex, skillsDir: join8(codex, "skills") },
2576
- { id: "cursor", name: "Cursor", root: cursor, skillsDir: join8(cursor, "skills") }
2627
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join9(claude, "skills") },
2628
+ { id: "codex", name: "Codex", root: codex, skillsDir: join9(codex, "skills") },
2629
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join9(cursor, "skills") }
2577
2630
  ];
2578
2631
  }
2579
2632
  function detectLocalAgents(env = process.env, home = homedir5()) {
@@ -2679,7 +2732,7 @@ async function readTurn(sessionId, opts = {}) {
2679
2732
 
2680
2733
  // src/setup.ts
2681
2734
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2682
- import { dirname as dirname6, join as join12 } from "path";
2735
+ import { dirname as dirname6, join as join13 } from "path";
2683
2736
  import { createInterface as createInterface3 } from "readline";
2684
2737
 
2685
2738
  // src/mcp-register.ts
@@ -2687,7 +2740,7 @@ import { spawnSync as spawnSync4 } from "child_process";
2687
2740
 
2688
2741
  // src/skill.ts
2689
2742
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2690
- import { join as join9 } from "path";
2743
+ import { join as join10 } from "path";
2691
2744
 
2692
2745
  // src/catalog-file.ts
2693
2746
  var CATALOG_FILE = "catalog.md";
@@ -3128,7 +3181,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
3128
3181
  }
3129
3182
  function installedVersion(dir) {
3130
3183
  try {
3131
- const marker = JSON.parse(readFileSync9(join9(dir, SKILL_MARKER), "utf8"));
3184
+ const marker = JSON.parse(readFileSync9(join10(dir, SKILL_MARKER), "utf8"));
3132
3185
  return typeof marker.version === "string" ? marker.version : null;
3133
3186
  } catch {
3134
3187
  return null;
@@ -3138,25 +3191,25 @@ function isInstalledSkill(dir) {
3138
3191
  return installedVersion(dir) !== null;
3139
3192
  }
3140
3193
  function installSkill(agent2, opts = {}) {
3141
- const dir = join9(agent2.skillsDir, SKILL_NAME);
3194
+ const dir = join10(agent2.skillsDir, SKILL_NAME);
3142
3195
  const previous = installedVersion(dir);
3143
3196
  const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
3144
3197
  if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
3145
3198
  if (outcome !== "current") {
3146
3199
  mkdirSync2(dir, { recursive: true });
3147
- writeFileSync4(join9(dir, "SKILL.md"), skillDocument());
3148
- writeFileSync4(join9(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
3200
+ writeFileSync4(join10(dir, "SKILL.md"), skillDocument());
3201
+ writeFileSync4(join10(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
3149
3202
  `);
3150
3203
  }
3151
3204
  if (opts.catalog) {
3152
3205
  mkdirSync2(dir, { recursive: true });
3153
- writeFileSync4(join9(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
3206
+ writeFileSync4(join10(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
3154
3207
  }
3155
- const refsDir = join9(dir, REFERENCES_DIR);
3208
+ const refsDir = join10(dir, REFERENCES_DIR);
3156
3209
  mkdirSync2(refsDir, { recursive: true });
3157
- writeFileSync4(join9(refsDir, "import.md"), importReferenceDoc());
3158
- writeFileSync4(join9(refsDir, "workspaces.md"), workspacesReferenceDoc());
3159
- writeFileSync4(join9(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
3210
+ writeFileSync4(join10(refsDir, "import.md"), importReferenceDoc());
3211
+ writeFileSync4(join10(refsDir, "workspaces.md"), workspacesReferenceDoc());
3212
+ writeFileSync4(join10(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
3160
3213
  return { agent: agent2.id, path: dir, outcome };
3161
3214
  }
3162
3215
 
@@ -3208,9 +3261,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
3208
3261
 
3209
3262
  // src/setup-local.ts
3210
3263
  import { isUtf8 as isUtf83 } from "buffer";
3211
- import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
3264
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
3212
3265
  import { homedir as homedir6 } from "os";
3213
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join10, resolve as resolve6 } from "path";
3266
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
3214
3267
  import { parse as parseToml3 } from "smol-toml";
3215
3268
  var MAX_SKILL_FILE_BYTES = 1024 * 1024;
3216
3269
  var MAX_IMPORT_DEPTH = 5;
@@ -3234,8 +3287,8 @@ function redacted(data) {
3234
3287
  }
3235
3288
  function skillFiles2(root, prefix) {
3236
3289
  const files2 = [];
3237
- for (const entry2 of readdirSync5(root, { withFileTypes: true })) {
3238
- const path = join10(root, entry2.name);
3290
+ for (const entry2 of readdirSync6(root, { withFileTypes: true })) {
3291
+ const path = join11(root, entry2.name);
3239
3292
  if (entry2.isDirectory()) {
3240
3293
  if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
3241
3294
  } else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
@@ -3250,8 +3303,8 @@ function readLocalSkills(agents) {
3250
3303
  const ignored = [];
3251
3304
  for (const agent2 of agents) {
3252
3305
  if (!existsSync7(agent2.skillsDir)) continue;
3253
- for (const entry2 of readdirSync5(agent2.skillsDir, { withFileTypes: true })) {
3254
- const dir = join10(agent2.skillsDir, entry2.name);
3306
+ for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
3307
+ const dir = join11(agent2.skillsDir, entry2.name);
3255
3308
  let isDir2 = entry2.isDirectory();
3256
3309
  if (entry2.isSymbolicLink()) {
3257
3310
  try {
@@ -3260,8 +3313,8 @@ function readLocalSkills(agents) {
3260
3313
  isDir2 = false;
3261
3314
  }
3262
3315
  }
3263
- if (!isDir2 || !existsSync7(join10(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3264
- const name = frontmatterName(readFileSync10(join10(dir, "SKILL.md"), "utf8"));
3316
+ if (!isDir2 || !existsSync7(join11(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
3317
+ const name = frontmatterName(readFileSync10(join11(dir, "SKILL.md"), "utf8"));
3265
3318
  if (!name) {
3266
3319
  ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
3267
3320
  continue;
@@ -3289,7 +3342,7 @@ function inlineImports(content, from, home, depth, seen) {
3289
3342
  }
3290
3343
  const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
3291
3344
  if (!target || depth >= MAX_IMPORT_DEPTH) return line;
3292
- const path = target.startsWith("~/") ? join10(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
3345
+ const path = target.startsWith("~/") ? join11(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
3293
3346
  if (seen.includes(path)) return "";
3294
3347
  let imported;
3295
3348
  try {
@@ -3306,7 +3359,7 @@ function readGlobalInstructions(agents, home = homedir6()) {
3306
3359
  for (const agent2 of agents) {
3307
3360
  const file = INSTRUCTION_FILES2[agent2.id];
3308
3361
  if (!file) continue;
3309
- const path = join10(agent2.root, file);
3362
+ const path = join11(agent2.root, file);
3310
3363
  let raw;
3311
3364
  try {
3312
3365
  raw = readFileSync10(path, "utf8");
@@ -3342,10 +3395,10 @@ function bearerOf(def) {
3342
3395
  return match?.[1] ?? null;
3343
3396
  }
3344
3397
  function serversOf(agent2, home) {
3345
- if (agent2.id === "claude_code") return readJson3(join10(home, ".claude.json"))?.mcpServers ?? {};
3346
- if (agent2.id === "cursor") return readJson3(join10(agent2.root, "mcp.json"))?.mcpServers ?? {};
3398
+ if (agent2.id === "claude_code") return readJson3(join11(home, ".claude.json"))?.mcpServers ?? {};
3399
+ if (agent2.id === "cursor") return readJson3(join11(agent2.root, "mcp.json"))?.mcpServers ?? {};
3347
3400
  try {
3348
- return parseToml3(readFileSync10(join10(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3401
+ return parseToml3(readFileSync10(join11(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
3349
3402
  } catch {
3350
3403
  return {};
3351
3404
  }
@@ -3368,7 +3421,7 @@ function findLocalSecrets(agents, home = homedir6()) {
3368
3421
  import { spawn as spawn3 } from "child_process";
3369
3422
  import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
3370
3423
  import { homedir as homedir7 } from "os";
3371
- import { join as join11 } from "path";
3424
+ import { join as join12 } from "path";
3372
3425
  import { createInterface as createInterface2 } from "readline";
3373
3426
  var ALIASES = {
3374
3427
  claude: "anthropic",
@@ -3464,7 +3517,7 @@ async function connectClaude() {
3464
3517
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
3465
3518
  }
3466
3519
  function codexSignIn(env = process.env, home = homedir7()) {
3467
- const path = join11(env.CODEX_HOME || join11(home, ".codex"), "auth.json");
3520
+ const path = join12(env.CODEX_HOME || join12(home, ".codex"), "auth.json");
3468
3521
  if (!existsSync8(path)) {
3469
3522
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
3470
3523
  }
@@ -3477,7 +3530,7 @@ function codexSignIn(env = process.env, home = homedir7()) {
3477
3530
  }
3478
3531
  }
3479
3532
  function cursorSignIn(env = process.env, home = homedir7()) {
3480
- const path = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "cursor", "auth.json");
3533
+ const path = join12(env.XDG_CONFIG_HOME || join12(home, ".config"), "cursor", "auth.json");
3481
3534
  if (!existsSync8(path)) {
3482
3535
  throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
3483
3536
  }
@@ -3640,7 +3693,7 @@ function planProviders(names2) {
3640
3693
  }));
3641
3694
  }
3642
3695
  function reportPath() {
3643
- return join12(dirname6(configPath()), "setup.json");
3696
+ return join13(dirname6(configPath()), "setup.json");
3644
3697
  }
3645
3698
  function loadReport() {
3646
3699
  try {
@@ -4007,15 +4060,15 @@ function renderArtifact(artifact2) {
4007
4060
  }
4008
4061
 
4009
4062
  // src/skills-store.ts
4010
- import { readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
4011
- import { basename as basename7, join as join13, relative as relative4, resolve as resolve7 } from "path";
4063
+ import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
4064
+ import { basename as basename7, join as join14, relative as relative4, resolve as resolve7 } from "path";
4012
4065
  function skillBundle(dir) {
4013
4066
  const root = resolve7(dir);
4014
4067
  const top = basename7(root);
4015
4068
  const files2 = [];
4016
4069
  const walk = (path) => {
4017
- for (const name of readdirSync6(path).sort()) {
4018
- const full = join13(path, name);
4070
+ for (const name of readdirSync7(path).sort()) {
4071
+ const full = join14(path, name);
4019
4072
  if (statSync8(full).isDirectory()) walk(full);
4020
4073
  else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync13(full) });
4021
4074
  }
@@ -4049,6 +4102,355 @@ function renderRequest(request) {
4049
4102
  return `${request.id} ${request.kind} from ${request.session_id}
4050
4103
  ${request.question}${options}`;
4051
4104
  }
4105
+ async function getSkillUsage(since) {
4106
+ const qs = since ? `?since=${encodeURIComponent(since)}` : "";
4107
+ return api("GET", `/skills/usage${qs}`);
4108
+ }
4109
+
4110
+ // src/task-export.ts
4111
+ import { createHash as createHash6 } from "crypto";
4112
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
4113
+ import { dirname as dirname7, join as join15 } from "path";
4114
+ import { stringify as stringifyToml2 } from "smol-toml";
4115
+ var TEXT_MAX = 2e4;
4116
+ function scrub(text, names2) {
4117
+ let out = redactPlainText(text).text;
4118
+ for (const { name, alias } of names2) {
4119
+ if (!name) continue;
4120
+ out = out.split(name).join(alias);
4121
+ }
4122
+ return out.length > TEXT_MAX ? `${out.slice(0, TEXT_MAX)}
4123
+ \u2026[cut]` : out;
4124
+ }
4125
+ function textOf2(event) {
4126
+ if (typeof event.text === "string" && event.text.trim()) return event.text;
4127
+ if (Array.isArray(event.content)) {
4128
+ return event.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").filter(Boolean).join("\n");
4129
+ }
4130
+ return "";
4131
+ }
4132
+ function firstAsk(record) {
4133
+ const root = record.threads.find((thread) => thread.relation === "primary") ?? record.threads[0];
4134
+ if (!root) return null;
4135
+ for (const event of record.events[root.id] ?? []) {
4136
+ if (event.type === "user.message") {
4137
+ const text = textOf2(event).trim();
4138
+ if (text) return text;
4139
+ }
4140
+ }
4141
+ return null;
4142
+ }
4143
+ function decisionsOf(record) {
4144
+ const out = [];
4145
+ for (const thread of record.threads) {
4146
+ for (const event of record.events[thread.id] ?? []) {
4147
+ if (event.type === "human.decision" || event.type === "outcome.observed") out.push({ ...event, thread_id: thread.id });
4148
+ }
4149
+ }
4150
+ return out.sort((a, b) => (a.at ?? "").localeCompare(b.at ?? ""));
4151
+ }
4152
+ function adoptedSession(record) {
4153
+ const decisions = decisionsOf(record).filter((event) => event.type === "human.decision");
4154
+ for (const event of [...decisions].reverse()) {
4155
+ if (event.kind === "adopt") return event.target_session_id ?? event.thread_id;
4156
+ if (event.kind === "vote" && event.target_session_id) return event.target_session_id;
4157
+ }
4158
+ return null;
4159
+ }
4160
+ function stepsOf(record, names2) {
4161
+ const root = record.threads.find((thread) => thread.relation === "primary") ?? record.threads[0];
4162
+ if (!root) return [];
4163
+ const events = record.events[root.id] ?? [];
4164
+ const steps = [];
4165
+ events.forEach((event, index) => {
4166
+ if (event.type !== "human.decision" || event.kind !== "revise" && event.kind !== "rerun") return;
4167
+ const next = events.slice(index + 1).find((later) => later.type === "user.message" && textOf2(later).trim());
4168
+ const words = next ? textOf2(next) : event.reason ?? "";
4169
+ if (!words.trim()) return;
4170
+ steps.push({ at: event.at ?? "", instruction: scrub(words, names2) });
4171
+ });
4172
+ return steps;
4173
+ }
4174
+ function rubricOf(record, instruction, names2) {
4175
+ if (record.goal && record.goal.criteria.length) {
4176
+ const verdicts = new Map((record.goal.last_evaluation?.criteria ?? []).map((row) => [row.id, row.verdict]));
4177
+ return {
4178
+ version: 1,
4179
+ source: "goal",
4180
+ criteria: record.goal.criteria.map((criterion) => ({
4181
+ id: criterion.id,
4182
+ text: scrub(criterion.text, names2),
4183
+ must: true,
4184
+ source: `goal criterion ${criterion.id}`,
4185
+ ...criterion.command ? { command: criterion.command } : {},
4186
+ ...verdicts.has(criterion.id) ? { last_verdict: verdicts.get(criterion.id) } : {}
4187
+ }))
4188
+ };
4189
+ }
4190
+ return {
4191
+ version: 1,
4192
+ source: "instruction",
4193
+ criteria: [{ id: "c1", text: `The deliverable does what the instruction asks: ${instruction.split("\n")[0]?.slice(0, 300) ?? ""}`, must: true, source: "derived from the instruction; no goal was set" }]
4194
+ };
4195
+ }
4196
+ var JUDGE_PY = String.raw`#!/usr/bin/env python3
4197
+ """Harbor verifier: grade the agent's output against tests/rubric.json with an LLM judge.
4198
+
4199
+ Reads every file under /workspace/outputs (falling back to /workspace) and asks the judge
4200
+ model, criterion by criterion, for a yes/no with quoted evidence. Writes
4201
+ /logs/verifier/reward.json ({"reward": 0|1, "criteria": [...]}) and reward.txt.
4202
+ Model and key come from the environment (JUDGE_MODEL, JUDGE_API_KEY, JUDGE_BASE_URL);
4203
+ nothing secret is in this file. Reward is 1 only when every MUST criterion passes.
4204
+ """
4205
+ import json, os, pathlib, sys, urllib.request
4206
+
4207
+ HERE = pathlib.Path(__file__).resolve().parent
4208
+ RUBRIC = json.loads((HERE / "rubric.json").read_text())
4209
+ OUT_DIR = pathlib.Path(os.environ.get("HARBOR_OUTPUTS", "/workspace/outputs"))
4210
+ if not OUT_DIR.exists():
4211
+ OUT_DIR = pathlib.Path("/workspace")
4212
+ LOGS = pathlib.Path(os.environ.get("HARBOR_VERIFIER_LOGS", "/logs/verifier"))
4213
+ LOGS.mkdir(parents=True, exist_ok=True)
4214
+
4215
+ def collect(limit=200_000):
4216
+ parts, size = [], 0
4217
+ for path in sorted(p for p in OUT_DIR.rglob("*") if p.is_file()):
4218
+ if any(s in path.parts for s in (".git", "node_modules", "__pycache__")):
4219
+ continue
4220
+ try:
4221
+ text = path.read_text(errors="replace")
4222
+ except OSError:
4223
+ continue
4224
+ chunk = f"\n===== {path.relative_to(OUT_DIR)} =====\n{text}"
4225
+ if size + len(chunk) > limit:
4226
+ break
4227
+ parts.append(chunk); size += len(chunk)
4228
+ return "".join(parts)
4229
+
4230
+ def ask(prompt):
4231
+ base = os.environ.get("JUDGE_BASE_URL", "https://api.openai.com/v1").rstrip("/")
4232
+ body = json.dumps({"model": os.environ.get("JUDGE_MODEL", "gpt-5.5"), "temperature": 0,
4233
+ "messages": [{"role": "user", "content": prompt}]}).encode()
4234
+ req = urllib.request.Request(f"{base}/chat/completions", data=body, headers={
4235
+ "content-type": "application/json", "authorization": f"Bearer {os.environ['JUDGE_API_KEY']}"})
4236
+ with urllib.request.urlopen(req, timeout=120) as res:
4237
+ return json.load(res)["choices"][0]["message"]["content"]
4238
+
4239
+ def grade(criterion, evidence):
4240
+ prompt = (
4241
+ "You grade one requirement of a task against the agent's output.\n"
4242
+ f"Requirement: {criterion['text']}\n"
4243
+ "Answer as JSON only: {\"pass\": true|false, \"evidence\": \"<quote from the output, or why it is missing>\"}.\n"
4244
+ "Pass only when the output itself shows the requirement met.\n\nOUTPUT:\n" + evidence)
4245
+ raw = ask(prompt).strip()
4246
+ start, end = raw.find("{"), raw.rfind("}")
4247
+ verdict = json.loads(raw[start:end + 1]) if start >= 0 else {"pass": False, "evidence": raw[:300]}
4248
+ return {"id": criterion["id"], "must": criterion.get("must", True), "pass": bool(verdict.get("pass")), "evidence": str(verdict.get("evidence", ""))[:1000]}
4249
+
4250
+ def main():
4251
+ evidence = collect()
4252
+ results = [grade(c, evidence) for c in RUBRIC["criteria"]]
4253
+ reward = 1 if results and all(r["pass"] for r in results if r["must"]) else 0
4254
+ (LOGS / "reward.json").write_text(json.dumps({"reward": reward, "criteria": results}, indent=2))
4255
+ (LOGS / "reward.txt").write_text(f"{reward}\n")
4256
+ print(json.dumps({"reward": reward, "passed": sum(r["pass"] for r in results), "total": len(results)}))
4257
+ return 0
4258
+
4259
+ if __name__ == "__main__":
4260
+ sys.exit(main())
4261
+ `;
4262
+ var TEST_SH = `#!/usr/bin/env bash
4263
+ # Harbor verifier entry point: the judge reads tests/rubric.json and writes /logs/verifier/reward.json.
4264
+ set -euo pipefail
4265
+ cd "$(dirname "$0")"
4266
+ exec python3 judge.py
4267
+ `;
4268
+ var SOLVE_SH = `#!/usr/bin/env bash
4269
+ # Reference solution: the files the task's sessions produced, as the person left them.
4270
+ set -euo pipefail
4271
+ cd "$(dirname "$0")"
4272
+ mkdir -p /workspace/outputs
4273
+ if [ -d files ]; then cp -R files/. /workspace/outputs/; fi
4274
+ `;
4275
+ function buildHarborTask(record, opts) {
4276
+ const names2 = opts.redactNames;
4277
+ const out = /* @__PURE__ */ new Map();
4278
+ const ask2 = record.goal?.objective ?? firstAsk(record) ?? record.title ?? "Complete the task described by the workspace.";
4279
+ const instruction = scrub(ask2, names2);
4280
+ const root = record.threads.find((thread) => thread.relation === "primary") ?? record.threads[0];
4281
+ const harnesses = [...new Set(record.threads.map((thread) => thread.agent.harness))];
4282
+ const decisions = decisionsOf(record);
4283
+ const adopted = adoptedSession(record);
4284
+ const steps = stepsOf(record, names2);
4285
+ const idHash = createHash6("sha256").update(record.task_id).digest("hex").slice(0, 16);
4286
+ out.set("instruction.md", `${instruction.trim()}
4287
+
4288
+ Write your deliverables under /workspace/outputs.
4289
+ `);
4290
+ out.set(
4291
+ "task.toml",
4292
+ stringifyToml2({
4293
+ metadata: {
4294
+ name: `agentsky-${idHash}`,
4295
+ tier: opts.tier,
4296
+ source: "agentsky-session-record",
4297
+ source_task: idHash,
4298
+ exported_at: record.exported_at,
4299
+ harnesses,
4300
+ has_goal: Boolean(record.goal),
4301
+ decisions: decisions.length,
4302
+ steps: steps.length,
4303
+ adopted_session: adopted ?? "",
4304
+ did_the_job: record.completion_answer?.did_the_job ?? "unknown"
4305
+ },
4306
+ verifier: { kind: "rubric_judge", rubric: "tests/rubric.json", reward: "/logs/verifier/reward.json" }
4307
+ })
4308
+ );
4309
+ const env = record.environment ?? root?.environment ?? null;
4310
+ if (env && typeof env.repo === "string" && typeof env.commit === "string") {
4311
+ out.set(
4312
+ "environment/Dockerfile",
4313
+ `FROM node:22-bookworm
4314
+ RUN apt-get update && apt-get install -y --no-install-recommends git python3 && rm -rf /var/lib/apt/lists/*
4315
+ WORKDIR /workspace
4316
+ RUN git clone ${env.repo} repo && cd repo && git checkout ${env.commit}
4317
+ `
4318
+ );
4319
+ } else {
4320
+ out.set(
4321
+ "environment/README.md",
4322
+ "This task is a real execution, not a resettable environment: the record carries no repository and commit to rebuild from.\nAuthor environment/Dockerfile by hand from solution/files (the task's own files at export) before running it with Harbor, or keep it as trajectory data.\n"
4323
+ );
4324
+ }
4325
+ const rubric = rubricOf(record, instruction, names2);
4326
+ out.set("tests/rubric.json", `${JSON.stringify(rubric, null, 2)}
4327
+ `);
4328
+ out.set("tests/judge.py", JUDGE_PY);
4329
+ out.set("tests/test.sh", TEST_SH);
4330
+ out.set("solution/solve.sh", SOLVE_SH);
4331
+ out.set(
4332
+ "solution/deliverables.json",
4333
+ `${JSON.stringify({ files: record.files.map((file) => ({ path: file.path, bytes: file.size_bytes ?? null })), adopted_session: adopted }, null, 2)}
4334
+ `
4335
+ );
4336
+ for (const [path, bytes] of opts.fileBytes ?? []) out.set(`solution/files/${path}`, bytes);
4337
+ const scrubbedEvents = {};
4338
+ for (const [threadId, events] of Object.entries(record.events)) {
4339
+ scrubbedEvents[threadId] = events.map((event) => ({
4340
+ ...event,
4341
+ ...typeof event.text === "string" ? { text: scrub(event.text, names2) } : {},
4342
+ ...Array.isArray(event.content) ? { content: event.content.map((block) => typeof block.text === "string" ? { ...block, text: scrub(block.text, names2) } : block) } : {},
4343
+ ...typeof event.reason === "string" ? { reason: scrub(event.reason, names2) } : {},
4344
+ ..."parts" in event ? { parts: void 0 } : {}
4345
+ }));
4346
+ }
4347
+ out.set("rollout/record.json", `${JSON.stringify({ ...record, title: record.title ? scrub(record.title, names2) : null, events: scrubbedEvents, goal: record.goal ? { ...record.goal, objective: scrub(record.goal.objective, names2) } : null }, null, 2)}
4348
+ `);
4349
+ out.set("rollout/decisions.json", `${JSON.stringify(decisions.map((event) => ({ ...event, reason: typeof event.reason === "string" ? scrub(event.reason, names2) : event.reason })), null, 2)}
4350
+ `);
4351
+ steps.forEach((step, index) => {
4352
+ out.set(`steps/step-${index + 1}/instruction.md`, `${step.instruction.trim()}
4353
+ `);
4354
+ });
4355
+ return out;
4356
+ }
4357
+ async function pagedEvents(sessionId) {
4358
+ const events = [];
4359
+ let cursor = null;
4360
+ for (; ; ) {
4361
+ const query = new URLSearchParams({ limit: "500" });
4362
+ if (cursor) query.set("cursor", cursor);
4363
+ const page = await api("GET", `/sessions/${encodeURIComponent(sessionId)}/events?${query}`);
4364
+ events.push(...page.events);
4365
+ if (!page.hasMore || !page.cursor) return events;
4366
+ cursor = page.cursor;
4367
+ }
4368
+ }
4369
+ async function fetchTaskRecord(taskRef) {
4370
+ const { task: task2 } = await api("GET", `/tasks/${encodeURIComponent(taskRef)}`);
4371
+ const { threads } = await api("GET", `/sessions/${encodeURIComponent(task2.id)}/threads`);
4372
+ const events = {};
4373
+ for (const thread of threads) events[thread.id] = await pagedEvents(thread.id);
4374
+ let goal2 = null;
4375
+ try {
4376
+ goal2 = (await api("GET", `/sessions/${encodeURIComponent(task2.id)}/goal`)).goal;
4377
+ } catch (error) {
4378
+ if (!(error instanceof ApiError && error.status === 404)) throw error;
4379
+ }
4380
+ let files2 = [];
4381
+ try {
4382
+ files2 = await listTaskFiles(task2.id);
4383
+ } catch (error) {
4384
+ if (!(error instanceof ApiError && (error.status === 404 || error.status === 503))) throw error;
4385
+ }
4386
+ const root = threads.find((thread) => thread.relation === "primary") ?? threads[0];
4387
+ return { task_id: task2.id, exported_at: (/* @__PURE__ */ new Date()).toISOString(), title: task2.title ?? null, threads, events, goal: goal2, files: files2, completion_answer: task2.completion_answer ?? null, environment: root?.environment ?? null };
4388
+ }
4389
+ var FILE_BYTES_MAX = 50 * 1024 * 1024;
4390
+ async function exportTaskHarbor(taskRef, outDir, opts) {
4391
+ const record = await fetchTaskRecord(taskRef);
4392
+ const fileBytes = /* @__PURE__ */ new Map();
4393
+ let total = 0;
4394
+ for (const file of record.files) {
4395
+ if (total + (file.size_bytes ?? 0) > FILE_BYTES_MAX) break;
4396
+ const bytes = await readTaskFile(record.task_id, file.path);
4397
+ total += bytes.byteLength;
4398
+ fileBytes.set(file.path, bytes);
4399
+ }
4400
+ const files2 = buildHarborTask(record, { ...opts, fileBytes });
4401
+ const written = [];
4402
+ for (const [path, content] of files2) {
4403
+ const target = join15(outDir, path);
4404
+ mkdirSync4(dirname7(target), { recursive: true });
4405
+ writeFileSync6(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
4406
+ written.push(path);
4407
+ }
4408
+ return { written, record };
4409
+ }
4410
+ function parseRedactNames(values) {
4411
+ return (values ?? []).map((value) => {
4412
+ const index = value.indexOf("=");
4413
+ if (index < 0) return { name: value.trim(), alias: "[redacted]" };
4414
+ return { name: value.slice(0, index).trim(), alias: value.slice(index + 1).trim() || "[redacted]" };
4415
+ }).filter((pair) => pair.name);
4416
+ }
4417
+
4418
+ // src/task-record.ts
4419
+ import { randomUUID as randomUUID2 } from "crypto";
4420
+ var DECISION_KINDS = ["adopt", "rerun", "revise", "discard", "vote", "did_the_job", "approve"];
4421
+ async function listThreads(taskRef) {
4422
+ return (await api("GET", `/sessions/${encodeURIComponent(taskRef)}/threads`)).threads;
4423
+ }
4424
+ function renderThread(thread) {
4425
+ const spend = (Number(thread.usage.spent_usd_micros) / 1e6).toFixed(4);
4426
+ const env = thread.environment?.repo ? ` ${thread.environment.repo}${thread.environment.commit ? `@${thread.environment.commit.slice(0, 7)}` : ""}` : "";
4427
+ return `${thread.id} ${thread.relation.padEnd(7)} ${thread.source.padEnd(12)} ${thread.agent.harness}/${thread.agent.model} ${thread.status} $${spend}${env}`;
4428
+ }
4429
+ async function recordDecision(sessionRef, kind, opts = {}) {
4430
+ if (!DECISION_KINDS.includes(kind)) throw new Error(`kind must be one of ${DECISION_KINDS.join(", ")}`);
4431
+ if ((kind === "did_the_job" || kind === "approve") && typeof opts.value !== "boolean") throw new Error(`${kind} needs --yes or --no`);
4432
+ const key = opts.idempotencyKey ?? randomUUID2();
4433
+ const res = await fetchRecord(sessionRef, {
4434
+ type: "human.decision",
4435
+ payload: {
4436
+ kind,
4437
+ ...opts.target ? { target_session_id: opts.target } : {},
4438
+ ...typeof opts.value === "boolean" ? { value: opts.value } : {},
4439
+ ...opts.reason ? { reason: opts.reason } : {},
4440
+ surface: opts.surface ?? "cli"
4441
+ }
4442
+ }, key);
4443
+ return res;
4444
+ }
4445
+ var OUTCOME_KINDS = ["pr_merged", "deployed", "reopened", "later_failure", "shipped"];
4446
+ async function recordOutcome(sessionRef, kind, opts = {}) {
4447
+ if (!OUTCOME_KINDS.includes(kind)) throw new Error(`kind must be one of ${OUTCOME_KINDS.join(", ")}`);
4448
+ return fetchRecord(sessionRef, { type: "outcome.observed", payload: { kind, ...opts.ref ? { ref: opts.ref } : {}, observer: opts.observer ?? "cli" } }, opts.idempotencyKey ?? randomUUID2());
4449
+ }
4450
+ async function fetchRecord(sessionRef, body, idempotencyKey) {
4451
+ const result = await api("POST", `/sessions/${encodeURIComponent(sessionRef)}/record`, body, { idempotencyKey });
4452
+ return { event: result.event, created: true };
4453
+ }
4052
4454
 
4053
4455
  // src/workspace.ts
4054
4456
  import { createInterface as createInterface4 } from "readline";
@@ -4269,12 +4671,45 @@ function confirmPrompt(question) {
4269
4671
  });
4270
4672
  }
4271
4673
 
4674
+ // src/browser-profiles.ts
4675
+ async function listBrowserProfiles() {
4676
+ const { data } = await api("GET", "/browser_profiles");
4677
+ return data;
4678
+ }
4679
+ async function getBrowserProfile(scope) {
4680
+ const { profile } = await api("GET", `/browser_profiles/${encodeURIComponent(scope)}`);
4681
+ return profile;
4682
+ }
4683
+ async function importBrowserProfileState(scope, state) {
4684
+ return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/imports`, state);
4685
+ }
4686
+ async function listBrowserProfileSites(scope) {
4687
+ const profile = await getBrowserProfile(scope);
4688
+ return profile.sites;
4689
+ }
4690
+ async function removeBrowserProfileSite(scope, site) {
4691
+ await api("DELETE", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}`);
4692
+ }
4693
+ async function moveBrowserProfileSite(scope, site, to) {
4694
+ return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}/move`, { to });
4695
+ }
4696
+ function renderBrowserProfileSite(site) {
4697
+ const who = site.signed_in_by?.email ?? site.signed_in_by?.name ?? "unknown";
4698
+ 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}`;
4700
+ }
4701
+ function renderBrowserProfile(profile) {
4702
+ const lines = [`scope: ${profile.scope} revision: ${profile.revision} sites: ${profile.sites.length}`];
4703
+ for (const site of profile.sites) lines.push(` ${renderBrowserProfileSite(site)}`);
4704
+ return lines.join("\n");
4705
+ }
4706
+
4272
4707
  // src/harness.ts
4273
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
4274
- import { dirname as dirname7, join as join14, resolve as resolve8 } from "path";
4708
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
4709
+ import { dirname as dirname8, join as join16, resolve as resolve8 } from "path";
4275
4710
  var HARNESS_FILE = "harness.json";
4276
4711
  function harnessFilePath(dir) {
4277
- return join14(resolve8(dir), HARNESS_FILE);
4712
+ return join16(resolve8(dir), HARNESS_FILE);
4278
4713
  }
4279
4714
  function readHarnessFile(dir) {
4280
4715
  const path = harnessFilePath(dir);
@@ -4312,8 +4747,8 @@ function harnessInit(dir) {
4312
4747
  if (existsSync9(path)) {
4313
4748
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
4314
4749
  }
4315
- mkdirSync4(dirname7(path), { recursive: true });
4316
- writeFileSync6(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4750
+ mkdirSync5(dirname8(path), { recursive: true });
4751
+ writeFileSync7(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4317
4752
  `);
4318
4753
  console.log(`Created ${path}`);
4319
4754
  console.log(INIT_COMMENT);
@@ -5018,7 +5453,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
5018
5453
  run(async (ref, path, opts) => {
5019
5454
  const bytes = await readFile(await resolveSessionRef(ref), path);
5020
5455
  if (opts.out) {
5021
- writeFileSync7(opts.out, bytes);
5456
+ writeFileSync8(opts.out, bytes);
5022
5457
  console.error(`saved ${bytes.length} bytes to ${opts.out}`);
5023
5458
  } else process.stdout.write(bytes);
5024
5459
  })
@@ -5035,7 +5470,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
5035
5470
  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)})`);
5036
5471
  if (!patch.length) console.error("nothing changed");
5037
5472
  else if (opts.out) {
5038
- writeFileSync7(opts.out, patch);
5473
+ writeFileSync8(opts.out, patch);
5039
5474
  console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
5040
5475
  } else process.stdout.write(patch);
5041
5476
  })
@@ -5046,6 +5481,43 @@ sessionWorkspace.command("import <ref> <dir>").description("copy a local folder
5046
5481
  console.log(`${imported.path} (${imported.files} files)`);
5047
5482
  })
5048
5483
  );
5484
+ var sessionBrowser = session.command("browser").description("the sandbox browser for a session (start, show status, stop)");
5485
+ sessionBrowser.command("show <ref>").description("show the sandbox browser status for a session").option("--json", "print as JSON").action(
5486
+ run(async (ref, opts) => {
5487
+ const id = await resolveSessionRef(ref);
5488
+ const { browser } = await api("GET", `/sessions/${encodeURIComponent(id)}/browser`);
5489
+ if (opts.json) {
5490
+ console.log(JSON.stringify(browser, null, 2));
5491
+ return;
5492
+ }
5493
+ if (!browser) {
5494
+ console.log("no browser");
5495
+ return;
5496
+ }
5497
+ const b = browser;
5498
+ console.log(`${b.status ?? "unknown"}${b.url ? ` ${b.url}` : ""}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
5499
+ })
5500
+ );
5501
+ sessionBrowser.command("start <ref>").description("start a browser for a session (provisions it if not running)").option("--json", "print as JSON").action(
5502
+ run(async (ref, opts) => {
5503
+ const id = await resolveSessionRef(ref);
5504
+ const { browser } = await api("POST", `/sessions/${encodeURIComponent(id)}/browser`, {});
5505
+ if (opts.json) {
5506
+ console.log(JSON.stringify(browser, null, 2));
5507
+ return;
5508
+ }
5509
+ const b = browser;
5510
+ console.log(`${b.status ?? "starting"}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
5511
+ })
5512
+ );
5513
+ sessionBrowser.command("stop <ref>").description("stop the browser for a session").action(
5514
+ run(async (ref) => {
5515
+ const id = await resolveSessionRef(ref);
5516
+ const { browser } = await api("DELETE", `/sessions/${encodeURIComponent(id)}/browser`);
5517
+ const b = browser;
5518
+ console.log(b ? b.status ?? "stopped" : "stopped");
5519
+ })
5520
+ );
5049
5521
  function interactionMode(opts) {
5050
5522
  if (opts.plan && opts.build) throw new ApiError(400, "invalid_request", "pass --plan or --build, not both");
5051
5523
  return opts.plan ? "plan" : opts.build ? "build" : void 0;
@@ -5427,10 +5899,40 @@ Importing ${label} \u2026
5427
5899
  )
5428
5900
  );
5429
5901
  var files = program.command("files").description("files: upload once, reference by id from any message");
5430
- files.command("upload <path>").description("upload a local file; prints the file id to reference from a message").option("--expires <seconds>", "expire the upload after this many seconds (3600\u20137776000)").action(
5431
- run(async (path, opts) => {
5432
- const file = await uploadLocalFile(path, opts.expires ? Number(opts.expires) : void 0, uploadProgress);
5433
- console.log(`${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`);
5902
+ files.command("upload <paths...>").description("upload local files and folders, up to 50 GiB each; prints each file id to reference from a message").option("--expires <seconds>", "expire the uploads after this many seconds (3600\u20137776000)").action(
5903
+ run(async (paths, opts) => {
5904
+ const expires = opts.expires ? Number(opts.expires) : void 0;
5905
+ const chosen = filesUnder(paths);
5906
+ if (chosen.length === 0) throw new Error("no files to upload");
5907
+ const line = (file) => `${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`;
5908
+ if (chosen.length === 1) {
5909
+ console.log(line(await uploadLocalFile(chosen[0], expires, uploadProgress)));
5910
+ return;
5911
+ }
5912
+ let done = 0;
5913
+ const tally = () => process.stderr.write(`${process.stderr.isTTY ? "\r" : ""}uploaded ${++done} / ${chosen.length}${process.stderr.isTTY && done < chosen.length ? "" : "\n"}`);
5914
+ const result = await uploadLocalFiles(chosen, {
5915
+ expiresInSeconds: expires,
5916
+ onUploaded: (_path, file) => {
5917
+ console.log(line(file));
5918
+ tally();
5919
+ },
5920
+ onFailed: (path, error) => {
5921
+ console.error(`sky: ${path}: ${error instanceof Error ? error.message : String(error)}`);
5922
+ tally();
5923
+ }
5924
+ });
5925
+ if (result.failed) {
5926
+ console.error(`sky: ${result.failed} of ${chosen.length} files failed; run the same command for those paths to retry`);
5927
+ process.exitCode = 1;
5928
+ }
5929
+ })
5930
+ );
5931
+ files.command("usage").description("how many files your workspace holds and the space they take (there is no storage limit)").action(
5932
+ run(async () => {
5933
+ const usage = await api("GET", "/files/usage");
5934
+ const gib = (bytes) => `${(bytes / 1024 ** 3).toFixed(2)} GiB`;
5935
+ console.log(`${usage.file_count} files ${gib(usage.size_bytes)} used ${usage.storage_limit_bytes === null ? "no storage limit" : `${gib(usage.storage_limit_bytes)} limit`} up to ${gib(usage.max_file_bytes)} per file`);
5434
5936
  })
5435
5937
  );
5436
5938
  files.command("list").description("list your uploads, or one session's files with --session").option("--session <ref>", "list the files of this session (its message copies and outputs)").option("--limit <n>", "page size (default 20)").option("--page <cursor>", "continue from a previous page").action(
@@ -5476,7 +5978,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
5476
5978
  run(async (taskId, path, opts) => {
5477
5979
  const bytes = await readTaskFile(taskId, path);
5478
5980
  if (opts.out) {
5479
- writeFileSync7(opts.out, bytes);
5981
+ writeFileSync8(opts.out, bytes);
5480
5982
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5481
5983
  } else process.stdout.write(bytes);
5482
5984
  })
@@ -5493,6 +5995,15 @@ task.command("rm <task> <path>").description("remove a path from the task's file
5493
5995
  console.log(`Removed ${path}`);
5494
5996
  })
5495
5997
  );
5998
+ task.command("export <task>").description("write the task's session record as a Harbor task directory: instruction, rubric + judge, files, rollout, and one step per revision").option("-o, --out <dir>", "where to write the directory (default ./harbor-<task>)").option("--tier <tier>", "gold | silver | exploratory (recorded in task.toml)", "silver").option("--redact <name=alias...>", "replace a name everywhere in the export (repeatable)").action(
5999
+ run(async (taskId, opts) => {
6000
+ const tier = opts.tier === "gold" || opts.tier === "exploratory" ? opts.tier : "silver";
6001
+ const outDir = opts.out ?? `./harbor-${taskId.replace(/[^A-Za-z0-9_-]/g, "_")}`;
6002
+ const { written, record } = await exportTaskHarbor(taskId, outDir, { tier, redactNames: parseRedactNames(opts.redact) });
6003
+ console.log(`Wrote ${written.length} files to ${outDir} (${record.threads.length} thread${record.threads.length === 1 ? "" : "s"}, ${record.files.length} task file${record.files.length === 1 ? "" : "s"}${record.goal ? ", goal rubric" : ", no goal: derived rubric"})`);
6004
+ if (!written.includes("environment/Dockerfile")) console.log("environment/: not resettable from the record \u2014 see environment/README.md before running with Harbor");
6005
+ })
6006
+ );
5496
6007
  task.command("show <task>").description("the task's sessions and the requests waiting on you (questions, approvals, apps to connect)").option("--json", "print the task as JSON").action(
5497
6008
  run(async (taskId, opts) => {
5498
6009
  const found = await getTask(taskId);
@@ -5511,6 +6022,42 @@ task.command("answer <task> <request>").description("answer an open request; the
5511
6022
  console.log(`Answered ${requestId}`);
5512
6023
  })
5513
6024
  );
6025
+ task.command("threads <task>").description("the task's threads from the session record: root first, then every helper, handoff and child with its source, agent, status and spend").option("--json", "print the threads as JSON").action(
6026
+ run(async (taskId, opts) => {
6027
+ const threads = await listThreads(taskId);
6028
+ if (opts.json) {
6029
+ console.log(JSON.stringify({ threads }));
6030
+ return;
6031
+ }
6032
+ for (const thread of threads) console.log(renderThread(thread));
6033
+ })
6034
+ );
6035
+ task.command("decide <session> <kind>").description("record what you did with a session's result: adopt | rerun | revise | discard | vote | did_the_job | approve").option("--target <session>", "the session the decision names (a vote's winner, the option you adopted)").option("--yes", "the value for did_the_job and approve").option("--no", "the value for did_the_job and approve").option("--reason <text>", "your own words, kept with the decision").option("--key <key>", "an idempotency key, so a repeat records nothing new").action(
6036
+ run(async (sessionId, kind, opts) => {
6037
+ const value = opts.yes ? true : opts.no ? false : void 0;
6038
+ const { event } = await recordDecision(sessionId, kind, { target: opts.target, value, reason: opts.reason, idempotencyKey: opts.key });
6039
+ console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
6040
+ })
6041
+ );
6042
+ task.command("outcome <session> <kind>").description("record what became of a session's result: pr_merged | deployed | reopened | later_failure | shipped").option("--ref <ref>", "the PR, deployment or task this refers to").option("--key <key>", "an idempotency key, so a repeat records nothing new").action(
6043
+ run(async (sessionId, kind, opts) => {
6044
+ const { event } = await recordOutcome(sessionId, kind, { ref: opts.ref, idempotencyKey: opts.key });
6045
+ console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
6046
+ })
6047
+ );
6048
+ task.command("dispute <task>").description("dispute an agent fee: tell the platform the task was not completed (allowed within 72 hours of the result)").requiredOption("--reason <text>", "why the task was not completed (up to 1000 characters)").option("--session <id>", "the specific session whose fee you dispute (required when the task has more than one)").action(
6049
+ run(async (taskId, opts) => {
6050
+ const { task: updated } = await api("POST", `/tasks/${encodeURIComponent(taskId)}/agent-fee/dispute`, { reason: opts.reason, ...opts.session ? { session_id: opts.session } : {} });
6051
+ console.log(`Disputed fee on task ${updated.id}`);
6052
+ })
6053
+ );
6054
+ task.command("verdict <task> <did-the-job>").description("record whether the task did the job: `yes` or `no` (replaces an earlier answer)").action(
6055
+ run(async (taskId, value) => {
6056
+ if (value !== "yes" && value !== "no") throw new Error("value must be yes or no");
6057
+ const { task: updated } = await api("PUT", `/tasks/${encodeURIComponent(taskId)}/completion_answer`, { did_the_job: value === "yes" });
6058
+ console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
6059
+ })
6060
+ );
5514
6061
  var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent by id");
5515
6062
  skills.command("upload <dir>").description("upload a skill directory (SKILL.md at its root) as a new skill").option("--name <name>", "display name (default: the directory's name)").option("--json", "print the stored skill as JSON").action(
5516
6063
  run(async (dir, opts) => {
@@ -5532,6 +6079,27 @@ skills.command("list").description("the workspace's stored skills").action(
5532
6079
  for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
5533
6080
  })
5534
6081
  );
6082
+ skills.command("usage").description("view and miss counts per skill name in this workspace").option("--since <iso>", `start date (ISO 8601); default is the last 30 days`).option("--json", "print JSON").action(
6083
+ run(async (opts) => {
6084
+ const summary = await getSkillUsage(opts.since);
6085
+ if (opts.json) {
6086
+ console.log(JSON.stringify(summary, null, 2));
6087
+ return;
6088
+ }
6089
+ if (!summary.skills.length) {
6090
+ console.log("No skill usage recorded in this period.");
6091
+ } else {
6092
+ console.log(`${"NAME".padEnd(32)} ${"VIEWS".padStart(6)} ${"MISSES".padStart(7)} LAST SEEN`);
6093
+ for (const s of summary.skills) {
6094
+ console.log(`${s.name.padEnd(32)} ${String(s.views).padStart(6)} ${String(s.misses).padStart(7)} ${s.lastSeen.slice(0, 10)}`);
6095
+ }
6096
+ }
6097
+ if (summary.topMissed.length) {
6098
+ console.log("\nTop missed:");
6099
+ for (const m of summary.topMissed) console.log(` ${m.name} (${m.misses})`);
6100
+ }
6101
+ })
6102
+ );
5535
6103
  async function decideReflection(id, verb) {
5536
6104
  const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
5537
6105
  console.log(`${p.id} ${p.status}`);
@@ -5571,7 +6139,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
5571
6139
  run(async (id, path, opts) => {
5572
6140
  const bytes = await readArtifactFile(id, path);
5573
6141
  if (opts.out) {
5574
- writeFileSync7(opts.out, bytes);
6142
+ writeFileSync8(opts.out, bytes);
5575
6143
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5576
6144
  } else process.stdout.write(bytes);
5577
6145
  })
@@ -5869,7 +6437,7 @@ usageOptions(
5869
6437
  run(async (opts) => {
5870
6438
  const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
5871
6439
  if (opts.out) {
5872
- writeFileSync7(opts.out, bytes);
6440
+ writeFileSync8(opts.out, bytes);
5873
6441
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5874
6442
  } else {
5875
6443
  process.stdout.write(bytes);
@@ -6079,6 +6647,226 @@ connector.command("shared").description("everything you share with workspaces, a
6079
6647
  for (const s of shared) console.log(`${s.connection_id} ${s.app_name} (${s.label}) \u2192 ${s.workspace.slug} ${s.use_count} call${s.use_count === 1 ? "" : "s"}`);
6080
6648
  })
6081
6649
  );
6650
+ var hookRule = program.command("hook-rule").description("workspace hook rules: run a webhook or block at platform-controlled points");
6651
+ hookRule.command("list").description("the workspace's hook rules in evaluation order, or a specific workspace's with --workspace").option("--workspace <slug>", "list the workspace-level hooks for this workspace (PUT/GET /universes/{slug}/hooks)").option("--json", "print as JSON").action(
6652
+ run(async (opts) => {
6653
+ const slug = opts.workspace ?? await workspaceSlug();
6654
+ let data;
6655
+ if (opts.workspace) {
6656
+ data = await api("GET", `/universes/${encodeURIComponent(slug)}/hooks`);
6657
+ } else {
6658
+ data = await api("GET", "/hook_rules");
6659
+ }
6660
+ console.log(opts.json ? JSON.stringify(data, null, 2) : JSON.stringify(data, null, 2));
6661
+ })
6662
+ );
6663
+ hookRule.command("set").description("replace the entire ordered list of workspace-level hook rules (owner only); pass JSON rules on stdin or --rules").option("--workspace <slug>", "act on this workspace's hooks instead of the top-level hook_rules").option("--rules <json>", "JSON array of rule objects (overrides stdin)").action(
6664
+ run(async (opts) => {
6665
+ const raw = opts.rules ?? await new Promise((resolve10) => {
6666
+ const chunks = [];
6667
+ process.stdin.on("data", (d) => chunks.push(d));
6668
+ process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
6669
+ });
6670
+ const rules = JSON.parse(raw);
6671
+ const slug = opts.workspace ?? await workspaceSlug();
6672
+ let result;
6673
+ if (opts.workspace) {
6674
+ result = await api("PUT", `/universes/${encodeURIComponent(slug)}/hooks`, { hooks: rules });
6675
+ } else {
6676
+ result = await api("PUT", "/hook_rules", { rules });
6677
+ }
6678
+ console.log(JSON.stringify(result, null, 2));
6679
+ })
6680
+ );
6681
+ var vendorLine = (a) => `${a.id} ${a.vendor_name.padEnd(18)} ${a.status.padEnd(14)} ${a.label}${a.plan ? ` (${a.plan})` : ""} used ${a.last_used_at?.slice(0, 10) ?? "never"}`;
6682
+ var vendor = program.command("vendor").description("vendor accounts: third-party API keys and agent logins your agents use");
6683
+ vendor.command("list").description("your workspace's vendor accounts").option("--json", "print as JSON").action(
6684
+ run(async (opts) => {
6685
+ const { external_accounts: list } = await api("GET", "/external_accounts");
6686
+ if (opts.json) {
6687
+ console.log(JSON.stringify(list, null, 2));
6688
+ return;
6689
+ }
6690
+ if (!list.length) console.log("no vendor accounts");
6691
+ for (const a of list) console.log(vendorLine(a));
6692
+ })
6693
+ );
6694
+ vendor.command("add <vendor>").description("connect a vendor account by pasting its key or settings JSON").option("--label <label>", "a name for this account").option("--credential <value>", "the key or JSON settings (prompts if omitted)").action(
6695
+ run(async (vendorId, opts) => {
6696
+ let credential = opts.credential ?? "";
6697
+ if (!credential) {
6698
+ credential = await new Promise((resolve10) => {
6699
+ const rl = createInterface6({ input: process.stdin, output: process.stderr });
6700
+ rl.question(`${vendorId} credential: `, (a) => {
6701
+ rl.close();
6702
+ resolve10(a.trim());
6703
+ });
6704
+ });
6705
+ }
6706
+ let parsed = credential;
6707
+ try {
6708
+ parsed = JSON.parse(credential);
6709
+ } catch {
6710
+ }
6711
+ const { external_account: created } = await api("POST", "/external_accounts", { vendor: vendorId, label: opts.label, credential: parsed });
6712
+ console.log(vendorLine(created));
6713
+ })
6714
+ );
6715
+ vendor.command("rm <id>").description("disconnect a vendor account").action(
6716
+ run(async (id) => {
6717
+ await api("DELETE", `/external_accounts/${encodeURIComponent(id)}`);
6718
+ console.log(`Removed ${id}`);
6719
+ })
6720
+ );
6721
+ vendor.command("usage <id>").description("the agents and tasks that used this vendor account").option("--json", "print as JSON").action(
6722
+ run(async (id, opts) => {
6723
+ const { usage } = await api("GET", `/external_accounts/${encodeURIComponent(id)}/usage`);
6724
+ if (opts.json) {
6725
+ console.log(JSON.stringify(usage, null, 2));
6726
+ return;
6727
+ }
6728
+ console.log(`${usage.agents.length} agent${usage.agents.length === 1 ? "" : "s"}, ${usage.tasks.length} task${usage.tasks.length === 1 ? "" : "s"}`);
6729
+ const agents = usage.agents;
6730
+ for (const a of agents) console.log(` agent ${a.id} ${a.name}`);
6731
+ const tasks = usage.tasks;
6732
+ for (const t of tasks) console.log(` task ${t.session_id} ${t.title ?? "(untitled)"} ${t.last_used_at.slice(0, 10)}`);
6733
+ })
6734
+ );
6735
+ var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
6736
+ browserProfile.command("list").description("your workspace's browser profiles (requires BROWSER_PROFILES to be enabled)").option("--json", "print as JSON").action(
6737
+ run(async (opts) => {
6738
+ const profiles = await listBrowserProfiles();
6739
+ if (opts.json) {
6740
+ console.log(JSON.stringify(profiles, null, 2));
6741
+ return;
6742
+ }
6743
+ if (!profiles.length) console.log("no browser profiles");
6744
+ for (const p of profiles) console.log(renderBrowserProfile(p));
6745
+ })
6746
+ );
6747
+ browserProfile.command("sites <scope>").description("list the sites in a profile: workspace or private").option("--json", "print as JSON").action(
6748
+ run(async (scope, opts) => {
6749
+ const sites = await listBrowserProfileSites(scope);
6750
+ if (opts.json) {
6751
+ console.log(JSON.stringify(sites, null, 2));
6752
+ return;
6753
+ }
6754
+ if (!sites.length) console.log("no sites");
6755
+ console.log(`${"SITE".padEnd(40)} ${"STATUS".padEnd(14)} SIGNED IN BY LAST USED`);
6756
+ for (const s of sites) console.log(renderBrowserProfileSite(s));
6757
+ })
6758
+ );
6759
+ browserProfile.command("import <scope>").description("import saved browser state into a profile (workspace or private); reads Playwright storage state JSON from stdin or --file").option("--file <path>", "path to a Playwright storage state JSON file (reads stdin if omitted)").action(
6760
+ run(async (scope, opts) => {
6761
+ let raw;
6762
+ if (opts.file) {
6763
+ const { readFileSync: rf } = await import("fs");
6764
+ raw = rf(opts.file, "utf8");
6765
+ } else {
6766
+ raw = await new Promise((resolve10) => {
6767
+ const chunks = [];
6768
+ process.stdin.on("data", (d) => chunks.push(d));
6769
+ process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
6770
+ });
6771
+ }
6772
+ const state = JSON.parse(raw);
6773
+ const result = await importBrowserProfileState(scope, { state });
6774
+ console.log(`Imported ${result.sites.length} site${result.sites.length === 1 ? "" : "s"}: ${result.sites.join(", ")}`);
6775
+ })
6776
+ );
6777
+ browserProfile.command("remove-site <scope> <site>").description("remove a site from a profile (the agent will need to sign in again)").action(
6778
+ run(async (scope, site) => {
6779
+ await removeBrowserProfileSite(scope, site);
6780
+ console.log(`Removed ${site} from ${scope}`);
6781
+ })
6782
+ );
6783
+ browserProfile.command("move-site <scope> <site> <to>").description("move a site between profiles: <scope> is the current one, <to> is workspace or private").action(
6784
+ run(async (scope, site, to) => {
6785
+ if (to !== "workspace" && to !== "private") throw new ApiError(400, "invalid_request", "to must be workspace or private");
6786
+ const result = await moveBrowserProfileSite(scope, site, to);
6787
+ console.log(`Moved ${site} from ${scope} to ${result.to}`);
6788
+ })
6789
+ );
6790
+ var notifications = program.command("notifications").description("email and browser push notification preferences for tasks");
6791
+ notifications.command("get").description("show the current notification preferences").option("--json", "print as JSON").action(
6792
+ run(async (opts) => {
6793
+ const { notification_preferences: prefs } = await api("GET", "/notification-preferences");
6794
+ if (opts.json) {
6795
+ console.log(JSON.stringify(prefs, null, 2));
6796
+ return;
6797
+ }
6798
+ console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
6799
+ console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
6800
+ })
6801
+ );
6802
+ 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(
6803
+ run(async (opts) => {
6804
+ const parseBool = (v) => v === void 0 ? void 0 : v === "true" ? true : v === "false" ? false : (() => {
6805
+ throw new ApiError(400, "invalid_request", "value must be true or false");
6806
+ })();
6807
+ const email = {};
6808
+ const push = {};
6809
+ const ny_e = parseBool(opts.emailNeedsYou);
6810
+ if (ny_e !== void 0) email.needs_you = ny_e;
6811
+ const d_e = parseBool(opts.emailDone);
6812
+ if (d_e !== void 0) email.done = d_e;
6813
+ const ny_p = parseBool(opts.pushNeedsYou);
6814
+ if (ny_p !== void 0) push.needs_you = ny_p;
6815
+ const d_p = parseBool(opts.pushDone);
6816
+ if (d_p !== void 0) push.done = d_p;
6817
+ if (!Object.keys(email).length && !Object.keys(push).length) throw new ApiError(400, "invalid_request", "pass at least one --email-* or --push-* flag");
6818
+ const { notification_preferences: prefs } = await api("PATCH", "/notification-preferences", {
6819
+ ...Object.keys(email).length ? { email } : {},
6820
+ ...Object.keys(push).length ? { push } : {}
6821
+ });
6822
+ console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
6823
+ console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
6824
+ })
6825
+ );
6826
+ var helpBase = () => agentUseBase().replace(/\/$/, "");
6827
+ async function helpFetch(path) {
6828
+ const res = await fetch(`${helpBase()}${path}`, { headers: { "user-agent": USER_AGENT } });
6829
+ if (!res.ok) {
6830
+ let msg = `${res.status} ${res.statusText}`;
6831
+ try {
6832
+ const b = await res.json();
6833
+ if (b.error) msg = b.error;
6834
+ } catch {
6835
+ }
6836
+ throw new ApiError(res.status, "http_error", msg);
6837
+ }
6838
+ return await res.json();
6839
+ }
6840
+ var help = program.command("help").description("search the AgentSky help center");
6841
+ help.command("search <query...>").description("search help articles by keyword").option("--top <n>", "how many results (1\u201320, default 5)", "5").option("--json", "print as JSON").action(
6842
+ run(async (words, opts) => {
6843
+ const q = words.join(" ");
6844
+ const topK = Math.min(Math.max(parseInt(opts.top, 10) || 5, 1), 20);
6845
+ const { results } = await helpFetch(`/api/help/search?q=${encodeURIComponent(q)}&top_k=${topK}`);
6846
+ if (opts.json) {
6847
+ console.log(JSON.stringify(results, null, 2));
6848
+ return;
6849
+ }
6850
+ if (!results.length) {
6851
+ console.log("no results");
6852
+ return;
6853
+ }
6854
+ for (const r of results) console.log(`${r.slug.padEnd(40)} [${r.category}] ${r.title}
6855
+ ${r.snippet.slice(0, 100)}`);
6856
+ })
6857
+ );
6858
+ help.command("get <slug>").description("read a help article by its slug").option("--json", "print as JSON").action(
6859
+ run(async (slug, opts) => {
6860
+ const article = await helpFetch(`/api/help/${encodeURIComponent(slug)}`);
6861
+ if (opts.json) {
6862
+ console.log(JSON.stringify(article, null, 2));
6863
+ return;
6864
+ }
6865
+ console.log(`# ${article.title}
6866
+ `);
6867
+ console.log(article.body);
6868
+ })
6869
+ );
6082
6870
  var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
6083
6871
  vault.command("list").description("the workspace's vaults").action(
6084
6872
  run(async () => {
@@ -6168,7 +6956,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
6168
6956
  }));
6169
6957
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
6170
6958
  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) => {
6171
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-H3AP64UZ.js");
6959
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-VZX6UIOF.js");
6172
6960
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
6173
6961
  const scopes = parseContentScopes(opts.capture);
6174
6962
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -6203,7 +6991,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
6203
6991
  }));
6204
6992
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
6205
6993
  try {
6206
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-H3AP64UZ.js");
6994
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-VZX6UIOF.js");
6207
6995
  const chunks = [];
6208
6996
  for await (const chunk of process.stdin) chunks.push(chunk);
6209
6997
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -6216,14 +7004,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
6216
7004
  }
6217
7005
  });
6218
7006
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
6219
- const { readState, flush, grantSender } = await import("./trace-H3AP64UZ.js");
7007
+ const { readState, flush, grantSender } = await import("./trace-VZX6UIOF.js");
6220
7008
  const state = readState();
6221
7009
  if (!state) throw new Error("no trace is running here");
6222
7010
  const outcome = await flush(grantSender(state), { attempts: 4 });
6223
7011
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
6224
7012
  }));
6225
7013
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
6226
- const { readState, spoolStatus } = await import("./trace-H3AP64UZ.js");
7014
+ const { readState, spoolStatus } = await import("./trace-VZX6UIOF.js");
6227
7015
  const state = readState();
6228
7016
  if (!state) {
6229
7017
  console.log("No trace is running here.");
@@ -6233,7 +7021,7 @@ traceCmd.command("status").description("the running trace and its spool").action
6233
7021
  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`);
6234
7022
  }));
6235
7023
  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) => {
6236
- const { readState } = await import("./trace-H3AP64UZ.js");
7024
+ const { readState } = await import("./trace-VZX6UIOF.js");
6237
7025
  const ref = id ?? readState()?.trace_id;
6238
7026
  if (!ref) throw new Error("name a trace, or start one here");
6239
7027
  const bundle = await api("GET", `/traces/${ref}`);
@@ -6260,7 +7048,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
6260
7048
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
6261
7049
  }));
6262
7050
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
6263
- const { readState, record, flush, grantSender, clearState } = await import("./trace-H3AP64UZ.js");
7051
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-VZX6UIOF.js");
6264
7052
  const state = readState();
6265
7053
  if (!state) throw new Error("no trace is running here");
6266
7054
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -6270,7 +7058,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
6270
7058
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
6271
7059
  }));
6272
7060
  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) => {
6273
- const lib = await import("./trace-library-LGPKAQTY.js");
7061
+ const lib = await import("./trace-library-EBH2GYDA.js");
6274
7062
  if (opts.hook) {
6275
7063
  try {
6276
7064
  let raw = payload ?? "";
@@ -6318,10 +7106,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6318
7106
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
6319
7107
  }
6320
7108
  if (opts.installSchedule) {
6321
- const { join: join15 } = await import("path");
7109
+ const { join: join17 } = await import("path");
6322
7110
  const { homedir: homedir8 } = await import("os");
6323
7111
  const { execFileSync } = await import("child_process");
6324
- const log = join15(homedir8(), ".config", "sky", "trace-sync.log");
7112
+ const log = join17(homedir8(), ".config", "sky", "trace-sync.log");
6325
7113
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
6326
7114
  try {
6327
7115
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -6332,10 +7120,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6332
7120
  }
6333
7121
  if (opts.status) {
6334
7122
  const { readFileSync: readFileSync16 } = await import("fs");
6335
- const { join: join15 } = await import("path");
7123
+ const { join: join17 } = await import("path");
6336
7124
  let state = { sessions: {} };
6337
7125
  try {
6338
- state = JSON.parse(readFileSync16(join15(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
7126
+ state = JSON.parse(readFileSync16(join17(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
6339
7127
  } catch {
6340
7128
  }
6341
7129
  const counts = /* @__PURE__ */ new Map();
@@ -6368,7 +7156,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6368
7156
  })();
6369
7157
  });
6370
7158
  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) => {
6371
- const { libraryRequest } = await import("./trace-library-LGPKAQTY.js");
7159
+ const { libraryRequest } = await import("./trace-library-EBH2GYDA.js");
6372
7160
  const workspace2 = opts.workspace ?? currentUniverse();
6373
7161
  if (!workspace2) throw new Error("name a workspace with --workspace");
6374
7162
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -6385,7 +7173,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
6385
7173
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
6386
7174
  }));
6387
7175
  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) => {
6388
- const { libraryRequest } = await import("./trace-library-LGPKAQTY.js");
7176
+ const { libraryRequest } = await import("./trace-library-EBH2GYDA.js");
6389
7177
  const workspace2 = opts.workspace ?? currentUniverse();
6390
7178
  if (!workspace2) throw new Error("name a workspace with --workspace");
6391
7179
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);