@pome-sh/cli 0.45.1 → 0.46.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.
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <!-- No favicon request, no font fetch, no analytics: the page makes no
7
+ network call other than its own `/api/snapshot`. D7 · 11. -->
8
+ <title>Pome — local twin dashboard</title>
9
+ <script type="module" crossorigin src="/app.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/app.css">
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "pome-sh",
3
- "version": "0.45.1",
4
- "git_sha": "f9ed48177ccfffac7020d1987e6ec9b810d7a4de",
5
- "build_time": "2026-09-16T19:49:02.473Z"
3
+ "version": "0.46.0",
4
+ "git_sha": "c830c89c6055e3371f4693f49bffe96cf230e060",
5
+ "build_time": "2026-09-19T16:59:46.785Z"
6
6
  }
@@ -1,7 +1,7 @@
1
1
  import { getAvailablePort } from './chunk-XDU6TD4O.js';
2
2
  import { findManifestPath, readManifest } from './chunk-26UAPLHK.js';
3
- import './chunk-M7ATJ423.js';
4
3
  import { buildEgressAllowlist } from './chunk-CBFKZZBR.js';
4
+ import './chunk-M7ATJ423.js';
5
5
  import './chunk-NBOQN5VX.js';
6
6
  import './chunk-YBWG5JK2.js';
7
7
  import './chunk-7VZBAHQ2.js';
@@ -183,7 +183,7 @@ async function checkTwinReachable(_configDir) {
183
183
  });
184
184
  let harness;
185
185
  try {
186
- const { bootTwin } = await import('./twinHarness-I2JGKQYD.js');
186
+ const { bootTwin } = await import('./twinHarness-VEIXQJ4Y.js');
187
187
  harness = await bootTwin({
188
188
  twin: "github",
189
189
  seedState: void 0,
@@ -1,13 +1,7 @@
1
- import { STANDALONE_STATUS_PATH, standaloneStatusEntries, readStandaloneStatusFile, readStandaloneInitialState, standaloneInitialStatePath } from './chunk-AGZOWSLG.js';
2
- import './chunk-4LQ4IJOC.js';
3
- import './chunk-M7ATJ423.js';
4
- import { TWIN_NAMES } from './chunk-BXYPNEJY.js';
5
- import './chunk-NBOQN5VX.js';
6
- import './chunk-YBWG5JK2.js';
1
+ import { TWIN_NAMES } from './chunk-LDDR6XRN.js';
7
2
  import { recorderEventSchema } from './chunk-7VZBAHQ2.js';
8
- import './chunk-SG6ZTIMT.js';
9
- import './chunk-2K6BJ3PI.js';
10
- import './chunk-FBSA5L36.js';
3
+ import { mkdir, writeFile, rename, chmod, readFile, unlink, open, stat } from 'node:fs/promises';
4
+ import { dirname, basename, join } from 'node:path';
11
5
 
12
6
  // src/twin/stateDiff.ts
13
7
  var IDENTITY_KEYS = ["full_name", "number", "name", "login", "email", "path", "ts", "key", "id"];
@@ -65,33 +59,45 @@ function sameScalars(a, b) {
65
59
  return stableStringify(scalarsOf(a)) === stableStringify(scalarsOf(b));
66
60
  }
67
61
  function diffState(before, after) {
68
- const out = [];
69
- walk(before, after, "", out);
70
- return out;
62
+ return censusState(before, after, { changedOnly: true }).map(({ path, added, changed, removed }) => ({
63
+ path,
64
+ added,
65
+ changed,
66
+ removed
67
+ }));
68
+ }
69
+ function censusState(before, after, options = {}) {
70
+ const sink = { out: [], changedOnly: options.changedOnly === true };
71
+ walk(before, after, "", sink);
72
+ return sink.out;
73
+ }
74
+ function emit(sink, entry) {
75
+ const moved = entry.added.length > 0 || entry.changed.length > 0 || entry.removed.length > 0;
76
+ if (moved || !sink.changedOnly) sink.out.push(entry);
71
77
  }
72
- function walk(before, after, path, out) {
78
+ function walk(before, after, path, sink) {
73
79
  if (Array.isArray(before) || Array.isArray(after)) {
74
80
  const b = Array.isArray(before) ? before : [];
75
81
  const a = Array.isArray(after) ? after : [];
76
82
  if (isMembership(b) || isMembership(a)) {
77
83
  if ((b.length === 0 || isMembership(b)) && (a.length === 0 || isMembership(a))) {
78
- diffMembership(b, a, path, out);
84
+ diffMembership(b, a, path, sink);
79
85
  }
80
86
  return;
81
87
  }
82
88
  if ((b.length === 0 || isCollection(b)) && (a.length === 0 || isCollection(a))) {
83
- diffCollection(b, a, path, out);
89
+ diffCollection(b, a, path, sink);
84
90
  }
85
91
  return;
86
92
  }
87
93
  if (isRow(before) && isRow(after)) {
88
94
  const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
89
95
  for (const key of [...keys].sort()) {
90
- walk(before[key], after[key], path === "" ? key : `${path}.${key}`, out);
96
+ walk(before[key], after[key], path === "" ? key : `${path}.${key}`, sink);
91
97
  }
92
98
  }
93
99
  }
94
- function diffMembership(before, after, path, out) {
100
+ function diffMembership(before, after, path, sink) {
95
101
  const count = (values) => {
96
102
  const map = /* @__PURE__ */ new Map();
97
103
  for (const value of values) map.set(String(value), (map.get(String(value)) ?? 0) + 1);
@@ -99,14 +105,30 @@ function diffMembership(before, after, path, out) {
99
105
  };
100
106
  const b = count(before);
101
107
  const a = count(after);
102
- const entry = { path: path || "(root)", added: [], changed: [], removed: [] };
108
+ const entry = {
109
+ path: path || "(root)",
110
+ boot: before.length,
111
+ now: after.length,
112
+ kind: "membership",
113
+ added: [],
114
+ changed: [],
115
+ removed: []
116
+ };
103
117
  for (const [value, n] of a) for (let i = b.get(value) ?? 0; i < n; i += 1) entry.added.push(value);
104
118
  for (const [value, n] of b) for (let i = a.get(value) ?? 0; i < n; i += 1) entry.removed.push(value);
105
- if (entry.added.length || entry.removed.length) out.push(entry);
119
+ emit(sink, entry);
106
120
  }
107
- function diffCollection(before, after, path, out) {
121
+ function diffCollection(before, after, path, sink) {
108
122
  const key = identityKeyOf(before, after);
109
- const entry = { path: path || "(root)", added: [], changed: [], removed: [] };
123
+ const entry = {
124
+ path: path || "(root)",
125
+ boot: before.length,
126
+ now: after.length,
127
+ kind: "collection",
128
+ added: [],
129
+ changed: [],
130
+ removed: []
131
+ };
110
132
  const byId = (rows) => {
111
133
  const seen = /* @__PURE__ */ new Map();
112
134
  return new Map(
@@ -138,14 +160,14 @@ function diffCollection(before, after, path, out) {
138
160
  for (const [id, { row, index }] of beforeById) {
139
161
  if (!afterById.has(id)) entry.removed.push(name(row));
140
162
  }
141
- if (entry.added.length || entry.changed.length || entry.removed.length) out.push(entry);
163
+ emit(sink, entry);
142
164
  for (const pair of nested) {
143
165
  const keys = /* @__PURE__ */ new Set([...Object.keys(pair.before), ...Object.keys(pair.after)]);
144
166
  for (const field of [...keys].sort()) {
145
167
  const b = pair.before[field];
146
168
  const a = pair.after[field];
147
169
  if (Array.isArray(b) || Array.isArray(a) || isRow(b) && isRow(a)) {
148
- walk(b, a, `${path}[${pair.label}].${field}`, out);
170
+ walk(b, a, `${path}[${pair.label}].${field}`, sink);
149
171
  }
150
172
  }
151
173
  }
@@ -163,6 +185,142 @@ function renderStateDiff(entries) {
163
185
  }
164
186
  return lines;
165
187
  }
188
+ var STANDALONE_STATUS_PATH = ".pome/twin-status.json";
189
+ async function writeStandaloneStatusFile(status, path = STANDALONE_STATUS_PATH) {
190
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
191
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
192
+ await writeFile(tmp, JSON.stringify(status, null, 2), { mode: 384 });
193
+ await rename(tmp, path);
194
+ await chmod(path, 384);
195
+ }
196
+ async function ensureSelfIgnoring(dir) {
197
+ if (basename(dir) !== ".pome") return;
198
+ const path = join(dir, ".gitignore");
199
+ try {
200
+ await writeFile(path, "*\n", { flag: "wx", mode: 384 });
201
+ } catch (err) {
202
+ if (err.code !== "EEXIST") throw err;
203
+ }
204
+ }
205
+ var STALE_LOCK_MS = 1e4;
206
+ async function withStatusLock(path, fn) {
207
+ const lockPath = `${path}.lock`;
208
+ const deadline = Date.now() + 5e3;
209
+ for (; ; ) {
210
+ try {
211
+ const handle = await open(lockPath, "wx", 384);
212
+ try {
213
+ await handle.writeFile(String(process.pid));
214
+ } finally {
215
+ await handle.close();
216
+ }
217
+ break;
218
+ } catch (err) {
219
+ if (err.code !== "EEXIST") throw err;
220
+ const age = await stat(lockPath).then((s) => Date.now() - s.mtimeMs, () => 0);
221
+ if (age > STALE_LOCK_MS) {
222
+ await unlink(lockPath).catch(() => void 0);
223
+ continue;
224
+ }
225
+ if (Date.now() > deadline) {
226
+ throw new Error(
227
+ `pome twin start: ${lockPath} is held by another pome process \u2014 if none is running, delete it.`
228
+ );
229
+ }
230
+ await new Promise((resolve) => setTimeout(resolve, 20));
231
+ }
232
+ }
233
+ try {
234
+ return await fn();
235
+ } finally {
236
+ await unlink(lockPath).catch(() => void 0);
237
+ }
238
+ }
239
+ async function updateStandaloneStatusFile(entries, path = STANDALONE_STATUS_PATH) {
240
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
241
+ await ensureSelfIgnoring(dirname(path));
242
+ return await withStatusLock(path, async () => {
243
+ const merged = mergeStandaloneStatus(await readStandaloneStatusFile(path), entries);
244
+ await writeStandaloneStatusFile(merged, path);
245
+ return merged;
246
+ });
247
+ }
248
+ function isStatusEntry(value) {
249
+ if (value === null || typeof value !== "object") return false;
250
+ const entry = value;
251
+ return typeof entry.name === "string" && typeof entry.url === "string" && typeof entry.rest_url === "string" && typeof entry.mcp_url === "string" && typeof entry.auth_token === "string";
252
+ }
253
+ function standaloneStatusEntries(contents) {
254
+ if (contents === null || typeof contents !== "object") return [];
255
+ const file = contents;
256
+ if (file.twins !== null && typeof file.twins === "object") {
257
+ return Object.values(file.twins).filter(isStatusEntry);
258
+ }
259
+ return isStatusEntry(file) ? [pick(file)] : [];
260
+ }
261
+ function pick(entry) {
262
+ return {
263
+ name: entry.name,
264
+ url: entry.url,
265
+ rest_url: entry.rest_url,
266
+ mcp_url: entry.mcp_url,
267
+ auth_token: entry.auth_token
268
+ };
269
+ }
270
+ function mergeStandaloneStatus(existing, entries) {
271
+ const twins = {};
272
+ for (const entry of standaloneStatusEntries(existing)) twins[entry.name] = pick(entry);
273
+ for (const entry of entries) twins[entry.name] = pick(entry);
274
+ const first = entries[0];
275
+ if (first === void 0) throw new Error("mergeStandaloneStatus: no entries to write");
276
+ return { ...pick(first), twins };
277
+ }
278
+ async function readStandaloneStatusFile(path = STANDALONE_STATUS_PATH) {
279
+ try {
280
+ return JSON.parse(await readFile(path, "utf8"));
281
+ } catch {
282
+ return void 0;
283
+ }
284
+ }
285
+ var STANDALONE_STATE_DIR = ".pome/twin-state";
286
+ function standaloneInitialStatePath(name, dir = STANDALONE_STATE_DIR) {
287
+ return join(dir, `${name}.initial.json`);
288
+ }
289
+ async function writeStandaloneInitialState(name, state, dir = STANDALONE_STATE_DIR) {
290
+ const path = standaloneInitialStatePath(name, dir);
291
+ await mkdir(dir, { recursive: true, mode: 448 });
292
+ const tmp = `${path}.${process.pid}.tmp`;
293
+ await writeFile(tmp, JSON.stringify(state), { mode: 384 });
294
+ await rename(tmp, path);
295
+ await chmod(path, 384);
296
+ }
297
+ async function readStandaloneInitialState(name, dir = STANDALONE_STATE_DIR) {
298
+ const path = standaloneInitialStatePath(name, dir);
299
+ let raw;
300
+ try {
301
+ raw = await readFile(path, "utf8");
302
+ } catch (err) {
303
+ if (err.code === "ENOENT") return void 0;
304
+ throw err;
305
+ }
306
+ try {
307
+ return JSON.parse(raw);
308
+ } catch {
309
+ throw new Error(`pome twin tape: ${path} is not valid JSON \u2014 restart the twin to rewrite it.`);
310
+ }
311
+ }
312
+ async function snapshotStandaloneInitialState(name, exportState, dir = STANDALONE_STATE_DIR) {
313
+ await unlink(standaloneInitialStatePath(name, dir)).catch((err) => {
314
+ if (err.code !== "ENOENT") throw err;
315
+ });
316
+ try {
317
+ await writeStandaloneInitialState(name, await exportState(), dir);
318
+ } catch (err) {
319
+ console.error(
320
+ `pome twin start: could not snapshot the ${name} twin's state for \`pome twin tape --diff\`: ${err.message}`
321
+ );
322
+ }
323
+ }
166
324
 
167
325
  // src/twin/twinTape.ts
168
326
  var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
@@ -223,15 +381,83 @@ var WRITE_VERBS = /* @__PURE__ */ new Set([
223
381
  "confirm",
224
382
  "void",
225
383
  "finalize",
226
- "pay"
384
+ "pay",
385
+ // Slack's `conversations.open` (opens a DM) and `chat.scheduleMessage`. Checked
386
+ // against all 115 MCP tool names across the five twins: neither word turns a
387
+ // read into a write, and `schedule` fixes `slack_schedule_message`, a write
388
+ // this list used to read as a read.
389
+ "open",
390
+ "schedule",
391
+ // GitHub's consolidated `issue_write` and Linear's `save_issue`,
392
+ // `save_project`, `save_document` — found by a real Claude Code session, whose
393
+ // failed `issue_write` would otherwise never have been marked. Every tool that
394
+ // declares `readOnlyHint` is now checked against this list
395
+ // (`requestKind.test.ts`).
396
+ "write",
397
+ "save"
227
398
  ]);
399
+ var COMPOUND_NOUNS = [[/pull_request/gi, "pullrequest"]];
400
+ function nameWords(name) {
401
+ let joined = name;
402
+ for (const [compound, noun] of COMPOUND_NOUNS) joined = joined.replace(compound, noun);
403
+ return joined.split(/[_\-.]|(?=[A-Z])/).map((word) => word.toLowerCase()).filter((word) => word.length > 0);
404
+ }
405
+ function namesAWrite(name) {
406
+ return nameWords(name).some((word) => WRITE_VERBS.has(word));
407
+ }
228
408
  function isMcpTransport(path) {
229
409
  return path === "/mcp" || path.startsWith("/mcp/");
230
410
  }
231
- function requestKind(method, path, tool) {
232
- if (tool && isMcpTransport(path)) {
233
- return tool.toLowerCase().split(/[_\-.]/).some((part) => WRITE_VERBS.has(part)) ? "write" : "read";
411
+ function isRpcMethod(path) {
412
+ return /^\/[a-z][a-zA-Z]*(\.[a-zA-Z]+)+$/.test(path);
413
+ }
414
+ function graphqlOperationKind(body) {
415
+ if (Array.isArray(body)) {
416
+ const kinds = body.map(graphqlOperationKind);
417
+ if (kinds.includes("write")) return "write";
418
+ return kinds.some((kind) => kind === "read") ? "read" : void 0;
419
+ }
420
+ if (body === null || typeof body !== "object") return void 0;
421
+ const { query, operationName } = body;
422
+ if (typeof query !== "string") return void 0;
423
+ const source = query.replace(/"""[\s\S]*?"""/g, '""').replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/#[^\n]*/g, "");
424
+ const operations = [];
425
+ let depth = 0;
426
+ let parens = 0;
427
+ let pending = null;
428
+ const tokens = /[{}()]|\b(query|mutation|subscription|fragment)\b\s*([A-Za-z_]\w*)?/g;
429
+ for (const [token, keyword, name] of source.matchAll(tokens)) {
430
+ if (token === "(") parens += 1;
431
+ else if (token === ")") parens = Math.max(0, parens - 1);
432
+ else if (parens > 0) continue;
433
+ else if (token === "{") {
434
+ if (depth === 0) {
435
+ if (pending === null) operations.push({ type: "query" });
436
+ pending = null;
437
+ }
438
+ depth += 1;
439
+ } else if (token === "}") {
440
+ depth = Math.max(0, depth - 1);
441
+ } else if (depth === 0 && keyword !== void 0) {
442
+ if (keyword === "fragment") {
443
+ pending = "fragment";
444
+ } else {
445
+ operations.push({ type: keyword, ...name ? { name } : {} });
446
+ pending = "operation";
447
+ }
448
+ }
449
+ }
450
+ const chosen = typeof operationName === "string" ? operations.find((operation) => operation.name === operationName) : operations[0];
451
+ if (chosen === void 0) return void 0;
452
+ return chosen.type === "mutation" ? "write" : "read";
453
+ }
454
+ function requestKind(method, path, tool, body) {
455
+ if (tool && isMcpTransport(path)) return namesAWrite(tool) ? "write" : "read";
456
+ if (path === "/graphql" || path.endsWith("/graphql")) {
457
+ if (READ_METHODS.has(method)) return "read";
458
+ return graphqlOperationKind(body) ?? "read";
234
459
  }
460
+ if (isRpcMethod(path)) return namesAWrite(path.slice(1)) ? "write" : "read";
235
461
  return READ_METHODS.has(method) ? "read" : "write";
236
462
  }
237
463
  function tapeRows(events, sessionPath) {
@@ -248,7 +474,7 @@ function tapeRows(events, sessionPath) {
248
474
  const event = parsed.data;
249
475
  const method = event.method.toUpperCase();
250
476
  const path = event.path.startsWith(sessionPath) ? event.path.slice(sessionPath.length) || "/" : event.path;
251
- const kind = requestKind(method, path, event.tool ?? null);
477
+ const kind = requestKind(method, path, event.tool ?? null, event.request_body);
252
478
  let note = null;
253
479
  if (event.fidelity === "unsupported" || event.status === 501) {
254
480
  note = "not modelled by this twin";
@@ -278,7 +504,11 @@ function tapeSummary(rows) {
278
504
  changed_state: rows.filter((row) => row.state_mutation).length,
279
505
  writes_not_landed: rows.filter((row) => row.note?.startsWith("write ")).length,
280
506
  unsupported: rows.filter((row) => row.note?.startsWith("not modelled")).length,
281
- reads: rows.filter((row) => row.kind === "read" && !row.note).length
507
+ // A row that changed state is counted there and nowhere else, whatever its
508
+ // name suggests: before, a `changed` row whose tool the verb list misread
509
+ // was counted twice, and "4 requests: 2 changed · 1 did not land · 2 reads"
510
+ // summed to five.
511
+ reads: rows.filter((row) => row.kind === "read" && !row.note && !row.state_mutation).length
282
512
  };
283
513
  }
284
514
  function requestLabel(row) {
@@ -304,7 +534,7 @@ function renderTape(rows, where) {
304
534
  state: stateLabel(row),
305
535
  note: row.note
306
536
  }));
307
- const width = (pick, min) => Math.max(min, ...cells.map((cell) => pick(cell).length));
537
+ const width = (pick2, min) => Math.max(min, ...cells.map((cell) => pick2(cell).length));
308
538
  const w = {
309
539
  time: width((c) => c.time, 4),
310
540
  request: width((c) => c.request, 7),
@@ -415,4 +645,4 @@ async function runTwinTapeCommand(nameArg, options) {
415
645
  }
416
646
  }
417
647
 
418
- export { pickRecordedTwin, renderTape, requestKind, requestLabel, runTwinTapeCommand, tapeRows, tapeSummary };
648
+ export { STANDALONE_STATUS_PATH, censusState, graphqlOperationKind, mergeStandaloneStatus, pickRecordedTwin, readStandaloneStatusFile, renderTape, requestKind, requestLabel, runTwinTapeCommand, snapshotStandaloneInitialState, standaloneStatusEntries, tapeRows, tapeSummary, updateStandaloneStatusFile, writeStandaloneStatusFile };
@@ -335,21 +335,45 @@ function expandBrackets(form) {
335
335
  if (segments.some((segment) => typeof segment === "string" && POLLUTION_KEYS.has(segment))) {
336
336
  continue;
337
337
  }
338
- let cursor = out;
339
- for (let index = 0; index < segments.length; index += 1) {
340
- const key = segments[index];
341
- if (index === segments.length - 1) {
342
- cursor[key] = Array.isArray(value) && value.length === 1 ? value[0] : value;
343
- break;
338
+ const appended = segments.length > 1 && segments.at(-1) === "";
339
+ if (appended)
340
+ segments.pop();
341
+ const decoded = appended ? Array.isArray(value) ? value : [value] : Array.isArray(value) && value.length === 1 ? value[0] : value;
342
+ const walks = segments.indexOf("") >= 0 && !appended && Array.isArray(decoded);
343
+ if (walks) {
344
+ for (const [element, one] of decoded.entries()) {
345
+ assignAt(out, elementPath(segments, element), one);
344
346
  }
345
- const nextKey = segments[index + 1];
346
- if (cursor[key] === void 0)
347
- cursor[key] = typeof nextKey === "number" ? [] : {};
348
- cursor = cursor[key];
347
+ continue;
349
348
  }
349
+ assignAt(out, elementPath(segments, 0), decoded);
350
350
  }
351
351
  return out;
352
352
  }
353
+ function elementPath(segments, element) {
354
+ let first = true;
355
+ return segments.map((segment) => {
356
+ if (segment !== "")
357
+ return segment;
358
+ const index = first ? element : 0;
359
+ first = false;
360
+ return index;
361
+ });
362
+ }
363
+ function assignAt(out, path, value) {
364
+ let cursor = out;
365
+ for (let index = 0; index < path.length; index += 1) {
366
+ const key = path[index];
367
+ if (index === path.length - 1) {
368
+ cursor[key] = value;
369
+ return;
370
+ }
371
+ const nextKey = path[index + 1];
372
+ if (cursor[key] === void 0)
373
+ cursor[key] = typeof nextKey === "number" ? [] : {};
374
+ cursor = cursor[key];
375
+ }
376
+ }
353
377
  function isPlainObject(value) {
354
378
  return typeof value === "object" && value !== null && !Array.isArray(value);
355
379
  }
@@ -1,4 +1,4 @@
1
- import { isTwinName, TWIN_REGISTRY, TWIN_NAMES } from './chunk-BXYPNEJY.js';
1
+ import { isTwinName, TWIN_REGISTRY, TWIN_NAMES } from './chunk-LDDR6XRN.js';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { parse } from 'yaml';
4
4
 
@@ -1,9 +1,9 @@
1
1
  import { HostedDiscardRefusedError, HostedOrchError, HostedAuthError, readManifest, normalizeManifestTwins, HostedQuotaError, HostedTrialError } from './chunk-26UAPLHK.js';
2
- import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, MOUNTED_TWINS, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, agentResponseSchema, isMultiTwinSeedEnvelope, sessionPublicSchema } from './chunk-M7ATJ423.js';
3
2
  import { gmailSeedSchema, defaultSeedState } from './chunk-PASFBRK4.js';
4
3
  import { linearSeedSchema, defaultSeedState as defaultSeedState$1 } from './chunk-3FZY376K.js';
5
- import { readSeedFileText, parseSeedFileText, soleTwinOf, twinsNamedBy, seedsForTwins } from './chunk-DX2KCFJM.js';
6
- import { isTwinName, TWIN_REGISTRY } from './chunk-BXYPNEJY.js';
4
+ import { readSeedFileText, parseSeedFileText, soleTwinOf, twinsNamedBy, seedsForTwins } from './chunk-GTLGXFLQ.js';
5
+ import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, MOUNTED_TWINS, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, agentResponseSchema, isMultiTwinSeedEnvelope, sessionPublicSchema } from './chunk-M7ATJ423.js';
6
+ import { isTwinName, TWIN_REGISTRY } from './chunk-LDDR6XRN.js';
7
7
  import { seedSchema as seedSchema$1, parseSeed, defaultSeedState as defaultSeedState$2 } from './chunk-NBOQN5VX.js';
8
8
  import { seedSchema } from './chunk-YBWG5JK2.js';
9
9
  import { toTwinHttpEventRow } from './chunk-TWURH7YM.js';
@@ -1510,7 +1510,9 @@ async function resolveCredentials(input) {
1510
1510
  throw new Error(`${path} is not valid JSON.`);
1511
1511
  }
1512
1512
  if (typeof parsed.api_key !== "string" || parsed.api_key.trim().length === 0) {
1513
- throw new Error(`${path} is missing "api_key".`);
1513
+ throw new HostedAuthError(
1514
+ `${path} is missing "api_key". Run \`pome login\` or set POME_API_KEY.`
1515
+ );
1514
1516
  }
1515
1517
  return {
1516
1518
  apiKey: parsed.api_key.trim(),
@@ -1768,6 +1770,7 @@ async function resolveRunAgentIdentity(input) {
1768
1770
  }
1769
1771
  return { ...base, agentId: resolved.id };
1770
1772
  } catch (err) {
1773
+ if (err instanceof HostedAuthError) throw err;
1771
1774
  console.error(
1772
1775
  `pome: could not resolve agent identity (${err instanceof Error ? err.message : String(err)}); running unattributed.`
1773
1776
  );