@pome-sh/cli 0.37.0 → 0.38.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "pome-sh",
3
- "version": "0.37.0",
4
- "git_sha": "cc0696be326889bdad4e58559dff013f090c1310",
5
- "build_time": "2026-08-30T04:33:14.394Z"
3
+ "version": "0.38.0",
4
+ "git_sha": "cf682a4d73f8a19d38a0d6a934fdef5eae82bb50",
5
+ "build_time": "2026-08-30T04:48:25.959Z"
6
6
  }
@@ -12,7 +12,7 @@ import '../../chunk-5NPGY73F.js';
12
12
  import { TWIN_NAME_LIST, isTwinName, createGitHubSmokeApp } from '../../chunk-RPPF2KDQ.js';
13
13
  import { readManifest, writeManifest, MANIFEST_JSON, readRequiredManifest, normalizeManifestTwins } from '../../chunk-DFOQGAKS.js';
14
14
  import '../../chunk-XDU6TD4O.js';
15
- import { MOUNTED_TWINS, deriveAgentSlug, exitCodeFor, HostedUsageError, HostedOrchError, HostedAuthError, HostedQuotaError } from '../../chunk-NNXVR46L.js';
15
+ import { MOUNTED_TWINS, deriveAgentSlug, exitCodeFor, HostedUsageError, HostedOrchError } from '../../chunk-NNXVR46L.js';
16
16
  import '../../chunk-CBFKZZBR.js';
17
17
  import { TAPE_ASSERTABLE_TOOLS } from '../../chunk-C4BVTUA3.js';
18
18
  import { seedSchema } from '../../chunk-NBOQN5VX.js';
@@ -727,99 +727,6 @@ async function compileSeed(prose, opts = {}) {
727
727
  durationMs
728
728
  };
729
729
  }
730
- var DEFAULT_TIMEOUT_MS = 6e4;
731
- var responseSchema = z.object({
732
- seed: z.unknown(),
733
- source_hash: z.string(),
734
- model: z.string(),
735
- compiled_at: z.string(),
736
- cached: z.boolean().optional(),
737
- // Server can return usage metadata for transparency. Optional — older
738
- // server versions or cache-hit responses may omit it.
739
- input_tokens: z.number().int().nonnegative().optional(),
740
- output_tokens: z.number().int().nonnegative().optional(),
741
- cost_cents: z.number().nonnegative().optional()
742
- });
743
- var errorBodySchema = z.object({
744
- error: z.object({
745
- type: z.string().optional(),
746
- message: z.string().optional(),
747
- request_id: z.string().optional()
748
- }).optional()
749
- });
750
- async function compileSeedHosted(prose, opts) {
751
- const creds = await resolveCredentials({ apiBaseUrl: opts.apiBaseUrl });
752
- const t0 = Date.now();
753
- const controller = new AbortController();
754
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
755
- const timer = setTimeout(() => controller.abort(), timeoutMs);
756
- let res;
757
- try {
758
- res = await fetch(`${creds.apiBaseUrl}/v1/scenarios/compile-seed`, {
759
- method: "POST",
760
- headers: {
761
- "content-type": "application/json",
762
- "x-api-key": creds.apiKey,
763
- "user-agent": "pome-cli"
764
- },
765
- body: JSON.stringify({
766
- prose,
767
- twin: opts.twin ?? "github",
768
- scenario_path: opts.taskPath
769
- }),
770
- signal: controller.signal
771
- });
772
- } catch (err) {
773
- throw new HostedOrchError(
774
- `Could not reach the Pome control plane at ${creds.apiBaseUrl}: ${err.message}`
775
- );
776
- } finally {
777
- clearTimeout(timer);
778
- }
779
- const durationMs = Date.now() - t0;
780
- if (!res.ok) {
781
- await throwForStatus(res);
782
- }
783
- let parsed;
784
- try {
785
- const body = await res.json();
786
- parsed = responseSchema.parse(body);
787
- } catch (err) {
788
- throw new HostedOrchError(
789
- `Control plane returned a response we could not parse: ${err.message}`
790
- );
791
- }
792
- const seed = parseGitHubSeedState(parsed.seed);
793
- return {
794
- seed,
795
- inputTokens: parsed.input_tokens ?? 0,
796
- outputTokens: parsed.output_tokens ?? 0,
797
- model: parsed.model,
798
- durationMs
799
- };
800
- }
801
- async function throwForStatus(res) {
802
- let body = {};
803
- try {
804
- body = errorBodySchema.parse(await res.json());
805
- } catch {
806
- }
807
- const detail = body.error?.message ?? `HTTP ${res.status}`;
808
- const requestId = body.error?.request_id ?? res.headers.get("x-request-id") ?? void 0;
809
- if (res.status === 401 || res.status === 403) {
810
- throw new HostedAuthError(`Authentication failed: ${detail}`, requestId);
811
- }
812
- if (res.status === 402 || res.status === 429) {
813
- throw new HostedQuotaError(`Quota or rate limit hit: ${detail}`, requestId);
814
- }
815
- if (res.status === 502) {
816
- throw new HostedOrchError(
817
- `Pome's hosted seed compiler hit a temporary capacity limit. Drop \`--hosted\` (or set ANTHROPIC_API_KEY then re-run \`pome compile-seeds --force\`) to compile locally via BYOK, or retry in a minute.`,
818
- requestId
819
- );
820
- }
821
- throw new HostedOrchError(`Compile-seed failed (HTTP ${res.status}): ${detail}`, requestId);
822
- }
823
730
 
824
731
  // src/task/seed-verifier.ts
825
732
  async function verifySeedWithTwin(seed) {
@@ -853,14 +760,15 @@ async function runCompileSeeds(target, opts) {
853
760
  const stamp = statusStamp(r.status);
854
761
  const tail = r.message ? ` \u2014 ${r.message}` : "";
855
762
  const cost = r.inputTokens !== void 0 ? ` (${r.inputTokens} in / ${r.outputTokens} out, ${r.durationMs}ms)` : "";
856
- console.error(`${stamp} ${r.path}${tail}${cost}`);
763
+ const line = `${stamp} ${r.path}${tail}${cost}`;
764
+ if (r.status === "error") console.error(line);
765
+ else console.log(line);
857
766
  }
858
767
  const errors = results.filter((r) => r.status === "error");
859
768
  if (errors.length > 0) {
860
769
  console.error(`
861
770
  ${errors.length} error(s).`);
862
- const worstHosted = errors.reduce((max, r) => Math.max(max, r.exitCode ?? 0), 0);
863
- return worstHosted > 0 ? worstHosted : 1;
771
+ return 1;
864
772
  }
865
773
  return 0;
866
774
  }
@@ -900,7 +808,7 @@ async function compileOne(taskPath, opts) {
900
808
  message: `hand-authored seed left untouched (${sidecarPath}) \u2014 delete it or drop its _meta to recompile`
901
809
  };
902
810
  }
903
- if (!opts.force && !opts.hosted && existsSync(sidecarPath)) {
811
+ if (!opts.force && existsSync(sidecarPath)) {
904
812
  const cached = await readSidecarMeta(sidecarPath);
905
813
  if (cached && cached.source_hash === proseHash && cached.model === COMPILER_MODEL) {
906
814
  return { path: taskPath, status: "skipped-cached", message: `up-to-date (${sidecarPath})` };
@@ -908,13 +816,12 @@ async function compileOne(taskPath, opts) {
908
816
  }
909
817
  let result;
910
818
  try {
911
- result = opts.hosted ? await compileSeedHosted(seedText, { apiBaseUrl: opts.apiBaseUrl, taskPath }) : await compileSeed(seedText);
819
+ result = await compileSeed(seedText);
912
820
  } catch (err) {
913
821
  return {
914
822
  path: taskPath,
915
823
  status: "error",
916
- message: `compile failed: ${err.message}`,
917
- exitCode: opts.hosted ? exitCodeFor(err) : void 0
824
+ message: `compile failed: ${err.message}`
918
825
  };
919
826
  }
920
827
  try {
@@ -1222,8 +1129,13 @@ function bold2(s) {
1222
1129
  return useColor2() ? `\x1B[1m${s}\x1B[0m` : s;
1223
1130
  }
1224
1131
  async function runTasksCommand(twinArg, opts) {
1132
+ if (!opts.copy && (opts.force || opts.dest)) {
1133
+ console.error("`--force` and `--dest` only do anything with `--copy`.");
1134
+ process.exitCode = 2;
1135
+ return;
1136
+ }
1225
1137
  if (!twinArg) {
1226
- if (opts.copy || opts.force || opts.dest) {
1138
+ if (opts.copy) {
1227
1139
  console.error("Specify a twin to copy from, e.g. `pome tasks github --copy`.");
1228
1140
  process.exitCode = 2;
1229
1141
  return;
@@ -1278,11 +1190,9 @@ function printTwinTasks(twin) {
1278
1190
  `Copy locally: \`pome tasks ${twin.id} --copy\` (or \`--copy --dest <dir>\`).`
1279
1191
  )
1280
1192
  );
1281
- console.log(
1282
- dim2(
1283
- `Run one: \`pome run tasks/${runnable[0]?.filename ?? "01-bug-happy-path.md"}\`.`
1284
- )
1285
- );
1193
+ if (runnable[0]) {
1194
+ console.log(dim2(`Run one: \`pome run tasks/${runnable[0].filename}\`.`));
1195
+ }
1286
1196
  }
1287
1197
  async function copyTwinTasks(twin, opts) {
1288
1198
  const root = resolvePackageRoot(import.meta.url);
@@ -1302,14 +1212,18 @@ async function copyTwinTasks(twin, opts) {
1302
1212
  destDir,
1303
1213
  force: opts.force
1304
1214
  });
1215
+ const written = outcome.copied.length + outcome.overwritten.length;
1305
1216
  console.log(
1306
1217
  bold2(
1307
- `Copied ${outcome.copied.length} ${twin.label} task${outcome.copied.length === 1 ? "" : "s"} into ${opts.destDir}/.`
1218
+ `Copied ${written} ${twin.label} task${written === 1 ? "" : "s"} into ${opts.destDir}/.`
1308
1219
  )
1309
1220
  );
1310
1221
  for (const file of outcome.copied) {
1311
1222
  console.log(` ${dim2("+")} ${file}`);
1312
1223
  }
1224
+ for (const file of outcome.overwritten) {
1225
+ console.log(` ${dim2("~")} ${file} ${dim2("(overwritten)")}`);
1226
+ }
1313
1227
  for (const file of outcome.skipped) {
1314
1228
  console.log(
1315
1229
  ` ${dim2("-")} ${file} ${dim2("(exists \u2014 pass --force to overwrite)")}`
@@ -1325,7 +1239,7 @@ async function copyTwinTasks(twin, opts) {
1325
1239
  return;
1326
1240
  }
1327
1241
  console.log("");
1328
- const first = outcome.copied[0] ?? outcome.skipped[0];
1242
+ const first = runnableTasks(twin)[0]?.filename;
1329
1243
  if (first) {
1330
1244
  console.log(
1331
1245
  dim2(`Next: \`pome run ${opts.destDir}/${first}\`.`)
@@ -1333,7 +1247,7 @@ async function copyTwinTasks(twin, opts) {
1333
1247
  }
1334
1248
  }
1335
1249
  async function copyTaskFiles(input) {
1336
- const outcome = { copied: [], skipped: [], missingSources: [] };
1250
+ const outcome = { copied: [], overwritten: [], skipped: [], missingSources: [] };
1337
1251
  for (const task of input.tasks) {
1338
1252
  const src = join(input.sourceDir, task.filename);
1339
1253
  const dest = join(input.destDir, task.filename);
@@ -1341,22 +1255,24 @@ async function copyTaskFiles(input) {
1341
1255
  outcome.missingSources.push(task.filename);
1342
1256
  continue;
1343
1257
  }
1344
- if (existsSync(dest) && !input.force) {
1258
+ const destExists = existsSync(dest);
1259
+ if (destExists && !input.force) {
1345
1260
  outcome.skipped.push(task.filename);
1346
1261
  } else {
1347
1262
  await copyFile(src, dest);
1348
- outcome.copied.push(task.filename);
1263
+ (destExists ? outcome.overwritten : outcome.copied).push(task.filename);
1349
1264
  }
1350
1265
  const sidecar = task.filename.replace(/\.md$/i, ".seed.json");
1351
1266
  const sidecarSrc = join(input.sourceDir, sidecar);
1352
1267
  const sidecarDest = join(input.destDir, sidecar);
1353
1268
  if (!existsSync(sidecarSrc)) continue;
1354
- if (existsSync(sidecarDest) && !input.force) {
1269
+ const sidecarExists = existsSync(sidecarDest);
1270
+ if (sidecarExists && !input.force) {
1355
1271
  outcome.skipped.push(sidecar);
1356
1272
  continue;
1357
1273
  }
1358
1274
  await copyFile(sidecarSrc, sidecarDest);
1359
- outcome.copied.push(sidecar);
1275
+ (sidecarExists ? outcome.overwritten : outcome.copied).push(sidecar);
1360
1276
  }
1361
1277
  return outcome;
1362
1278
  }
@@ -3391,11 +3307,19 @@ function formatSkewRefusal(twin, findings) {
3391
3307
 
3392
3308
  // src/cli/checks-add.ts
3393
3309
  function parseArgFlags(pairs) {
3394
- const args = {};
3310
+ const args = /* @__PURE__ */ Object.create(null);
3395
3311
  for (const pair2 of pairs) {
3396
3312
  const eq = pair2.indexOf("=");
3397
- if (eq <= 0) return { error: `--arg must be key=value, got ${JSON.stringify(pair2)}` };
3398
- args[pair2.slice(0, eq)] = pair2.slice(eq + 1);
3313
+ if (eq <= 0) {
3314
+ console.error(`--arg must be key=value, got ${JSON.stringify(pair2)}`);
3315
+ return null;
3316
+ }
3317
+ const key = pair2.slice(0, eq);
3318
+ if (Object.hasOwn(args, key)) {
3319
+ console.error(`--arg ${key} given twice. Pass each parameter once.`);
3320
+ return null;
3321
+ }
3322
+ args[key] = pair2.slice(eq + 1);
3399
3323
  }
3400
3324
  return args;
3401
3325
  }
@@ -3454,9 +3378,10 @@ async function pickInteractively(source, seams) {
3454
3378
  }
3455
3379
  return picked;
3456
3380
  }
3457
- async function promptArgs(def, seams) {
3458
- const args = {};
3381
+ async function promptArgs(def, seams, known) {
3382
+ const args = { ...known };
3459
3383
  for (const name of templateSlots(def.template).params) {
3384
+ if (args[name] !== void 0) continue;
3460
3385
  const type = def.params[name];
3461
3386
  const valid = new RegExp(`^${type.pattern}$`);
3462
3387
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -3468,7 +3393,10 @@ async function promptArgs(def, seams) {
3468
3393
  }
3469
3394
  console.error(` not a valid ${name} \u2014 it should look like ${type.example}`);
3470
3395
  }
3471
- if (args[name] === void 0) return { error: `Gave up on \`${name}\` after three tries.` };
3396
+ if (args[name] === void 0) {
3397
+ console.error(`Gave up on \`${name}\` after three tries.`);
3398
+ return null;
3399
+ }
3472
3400
  }
3473
3401
  return args;
3474
3402
  }
@@ -3486,8 +3414,10 @@ async function runChecksAddCommand(file, opts) {
3486
3414
  if (opts.check) {
3487
3415
  picked = findCheck(opts.check);
3488
3416
  if (!picked) {
3417
+ const twins = readConfigTwins(source);
3418
+ const declared = twins.flatMap((twin2) => [...checksFor(twin2)]).map((c) => c.id);
3489
3419
  console.error(
3490
- `Unknown check "${opts.check}". Run \`pome checks <twin>\` to see the declared set; github declares: ${checksFor("github").map((c) => c.id).join(", ")}.`
3420
+ `Unknown check "${opts.check}". Run \`pome checks <twin>\` to see the declared set; this task's twins (${twins.join(", ")}) declare: ${declared.join(", ") || "none"}.`
3491
3421
  );
3492
3422
  process.exitCode = 2;
3493
3423
  return;
@@ -3496,14 +3426,25 @@ async function runChecksAddCommand(file, opts) {
3496
3426
  picked = await pickInteractively(source, seams);
3497
3427
  if (!picked) return;
3498
3428
  }
3499
- const parsed = opts.check ? parseArgFlags(opts.arg) : await promptArgs(picked, seams);
3500
- if ("error" in parsed) {
3501
- console.error(parsed.error);
3429
+ const fromFlags = parseArgFlags(opts.arg);
3430
+ if (!fromFlags) {
3431
+ process.exitCode = 2;
3432
+ return;
3433
+ }
3434
+ const args = opts.check ? fromFlags : await promptArgs(picked, seams, fromFlags);
3435
+ if (!args) {
3502
3436
  process.exitCode = 2;
3503
3437
  return;
3504
3438
  }
3505
- const args = parsed;
3506
3439
  const slots = templateSlots(picked.template).params;
3440
+ const extra = Object.keys(args).filter((key) => !slots.includes(key));
3441
+ if (extra.length > 0) {
3442
+ console.error(
3443
+ `${picked.id} does not declare: ${extra.map((k) => JSON.stringify(k)).join(", ")}. It takes: ${slots.join(", ")}.`
3444
+ );
3445
+ process.exitCode = 2;
3446
+ return;
3447
+ }
3507
3448
  for (const name of slots) {
3508
3449
  const type = picked.params[name];
3509
3450
  const value = args[name];
@@ -3520,14 +3461,6 @@ async function runChecksAddCommand(file, opts) {
3520
3461
  return;
3521
3462
  }
3522
3463
  }
3523
- const extra = Object.keys(args).filter((key) => !slots.includes(key));
3524
- if (extra.length > 0) {
3525
- console.error(
3526
- `${picked.id} does not declare: ${extra.join(", ")}. It takes: ${slots.join(", ")}.`
3527
- );
3528
- process.exitCode = 2;
3529
- return;
3530
- }
3531
3464
  const twin = twinOf(picked.id);
3532
3465
  const fetchRemote = opts.fetchRemote ?? (async (t) => liveFetch(
3533
3466
  await resolveCredentials({
@@ -4642,7 +4575,7 @@ function firstSentence(description) {
4642
4575
  function resolveExampleRef(env = process.env) {
4643
4576
  const override = env.POME_EXAMPLE_REF?.trim();
4644
4577
  if (override) return override;
4645
- const baked = "cc0696be326889bdad4e58559dff013f090c1310".trim() ;
4578
+ const baked = "cf682a4d73f8a19d38a0d6a934fdef5eae82bb50".trim() ;
4646
4579
  return FULL_SHA.test(baked) ? baked : "main";
4647
4580
  }
4648
4581
  function rawUrlFor(example, file, ref) {
@@ -4996,7 +4929,7 @@ var DEFAULT_AGENT_COMMAND = `node ${DEFAULT_AGENT_FILE}`;
4996
4929
  var MANIFEST_SCHEMA_URL = "https://pome.sh/schemas/v1/pome.json";
4997
4930
  var MAX_UNREADABLE_PATHS_SHOWN = 5;
4998
4931
  function readPackageVersion() {
4999
- if ("0.37.0".length > 0) return "0.37.0";
4932
+ if ("0.38.0".length > 0) return "0.38.0";
5000
4933
  try {
5001
4934
  const here = dirname(fileURLToPath(import.meta.url));
5002
4935
  const candidates = [
@@ -5213,22 +5146,10 @@ function createProgram() {
5213
5146
  checks.command("lint").argument("<file...>", "Task markdown file(s) \u2014 shell globs work: tasks/*.md").description("Report [code] criteria that bind no declared check, so are never graded").action(async (files) => {
5214
5147
  await runChecksLintCommand(files);
5215
5148
  });
5216
- program.command("compile-seeds").summary("Compile a task's seed state to JSON").argument("[target]", "Task .md file or directory (defaults to ./tasks)").option("--force", "Recompile even if the sidecar's source hash matches", false).option(
5217
- "--hosted",
5218
- "Compile via the Pome control plane instead of calling Anthropic directly (uses your Pome API key; no ANTHROPIC_API_KEY needed)",
5219
- false
5220
- ).option(
5221
- "--api-url <url>",
5222
- "Control-plane base URL (only relevant with --hosted).",
5223
- process.env.POME_API_URL ?? DEFAULT_CONTROL_PLANE_URL
5224
- ).description(
5225
- "Compile prose `## Seed State` sections into sidecar .seed.json files (local: ANTHROPIC_API_KEY; --hosted: routes through Pome cloud)"
5149
+ program.command("compile-seeds").summary("Compile prose seed state to JSON via Claude").argument("[target]", "Task .md file or directory (defaults to ./tasks)").option("--force", "Recompile even if the sidecar's source hash matches", false).description(
5150
+ "Compile prose `## Seed State` sections into sidecar .seed.json files \u2014 one Claude call per file, billed to your ANTHROPIC_API_KEY"
5226
5151
  ).action(async (target, opts) => {
5227
- const code = await runCompileSeeds(target, {
5228
- force: opts.force,
5229
- hosted: opts.hosted,
5230
- apiBaseUrl: opts.apiUrl
5231
- });
5152
+ const code = await runCompileSeeds(target, { force: opts.force });
5232
5153
  if (code !== 0) process.exitCode = code;
5233
5154
  });
5234
5155
  const register = program.command("register").summary("Register an agent with Pome").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pome-sh/cli",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Test AI agents against digital twins of real SaaS APIs. Records tool-call traces and scores them on pome.sh.",
5
5
  "keywords": [
6
6
  "ai",