@pome-sh/cli 0.43.1 → 0.45.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/README.md +1 -1
- package/dist/build-info.json +3 -3
- package/dist/{checks-V442KL5O.js → checks-RDU56OGI.js} +4 -3
- package/dist/chunk-26UAPLHK.js +191 -0
- package/dist/chunk-4LQ4IJOC.js +1 -0
- package/dist/{chunk-5KFDRR53.js → chunk-7VZBAHQ2.js} +2 -2
- package/dist/chunk-AGZOWSLG.js +142 -0
- package/dist/{chunk-4DXTT3RV.js → chunk-BXYPNEJY.js} +7 -7
- package/dist/{chunk-GQSNU3YP.js → chunk-DX2KCFJM.js} +1 -1
- package/dist/{chunk-64AWQJ7R.js → chunk-EZYVICDB.js} +1 -1
- package/dist/{chunk-A6AM6KS4.js → chunk-HEEFOMSZ.js} +11 -70
- package/dist/{chunk-CS7O2ZXB.js → chunk-M7ATJ423.js} +5 -190
- package/dist/{runTrialGroup-HYGGBLMM.js → runTrialGroup-MMTTTDWY.js} +7 -5
- package/dist/{server-2WHSHTUD.js → server-GECKG2H6.js} +1 -1
- package/dist/src/cli/main.js +246 -174
- package/dist/{src-G6CMFUR6.js → src-46YGR2WY.js} +1 -7
- package/dist/{src-DBOHIQNB.js → src-CV7QDV5E.js} +1 -1
- package/dist/{src-FRPUTB2H.js → src-D3PKLT54.js} +1 -1
- package/dist/{src-SJDGRXC5.js → src-LPTWNBO5.js} +1 -1
- package/dist/{src-VBXONWR3.js → src-STAXYPD3.js} +18 -2
- package/dist/twinHarness-I2JGKQYD.js +6 -0
- package/dist/{twinSeed-J7UAY3GM.js → twinSeed-ZFZO7BL7.js} +1 -1
- package/dist/twinStart-XN3BYOUF.js +422 -0
- package/dist/twinTape-5P3HQ27N.js +418 -0
- package/examples/agents/mcp-loop-agent.ts +0 -6
- package/package.json +30 -24
- package/dist/twinHarness-33MAVAXV.js +0 -6
- package/dist/twinStart-3Q74IURZ.js +0 -187
|
@@ -0,0 +1,418 @@
|
|
|
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';
|
|
7
|
+
import { recorderEventSchema } from './chunk-7VZBAHQ2.js';
|
|
8
|
+
import './chunk-SG6ZTIMT.js';
|
|
9
|
+
import './chunk-2K6BJ3PI.js';
|
|
10
|
+
import './chunk-FBSA5L36.js';
|
|
11
|
+
|
|
12
|
+
// src/twin/stateDiff.ts
|
|
13
|
+
var IDENTITY_KEYS = ["full_name", "number", "name", "login", "email", "path", "ts", "key", "id"];
|
|
14
|
+
function isRow(value) {
|
|
15
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function isPrimitive(value) {
|
|
18
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
19
|
+
}
|
|
20
|
+
function isCollection(value) {
|
|
21
|
+
return Array.isArray(value) && value.length > 0 && value.every(isRow);
|
|
22
|
+
}
|
|
23
|
+
function isMembership(value) {
|
|
24
|
+
return Array.isArray(value) && value.length > 0 && value.every(isPrimitive);
|
|
25
|
+
}
|
|
26
|
+
function identityKeyOf(...sides) {
|
|
27
|
+
const rows = sides.flat();
|
|
28
|
+
if (rows.length === 0) return void 0;
|
|
29
|
+
for (const key of IDENTITY_KEYS) {
|
|
30
|
+
if (!rows.every((row) => row[key] !== void 0 && row[key] !== null)) continue;
|
|
31
|
+
const unique = sides.every((side) => {
|
|
32
|
+
const values = side.map((row) => String(row[key]));
|
|
33
|
+
return new Set(values).size === values.length;
|
|
34
|
+
});
|
|
35
|
+
if (unique) return key;
|
|
36
|
+
}
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
function label(row, key, _index) {
|
|
40
|
+
const value = String(row[key]);
|
|
41
|
+
if (key === "number") return `#${value}`;
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function scalarsOf(row) {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const [key, value] of Object.entries(row)) {
|
|
47
|
+
if (Array.isArray(value) && (value.length === 0 || isCollection(value) || isMembership(value))) continue;
|
|
48
|
+
out[key] = value;
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function contentLabel(row) {
|
|
53
|
+
const parts = Object.entries(scalarsOf(row)).slice(0, 3).map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
54
|
+
const text = `{${parts.join(", ")}}`;
|
|
55
|
+
return text.length > 60 ? `${text.slice(0, 59)}\u2026` : text;
|
|
56
|
+
}
|
|
57
|
+
function stableStringify(value) {
|
|
58
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
59
|
+
if (isRow(value)) {
|
|
60
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
61
|
+
}
|
|
62
|
+
return JSON.stringify(value) ?? "undefined";
|
|
63
|
+
}
|
|
64
|
+
function sameScalars(a, b) {
|
|
65
|
+
return stableStringify(scalarsOf(a)) === stableStringify(scalarsOf(b));
|
|
66
|
+
}
|
|
67
|
+
function diffState(before, after) {
|
|
68
|
+
const out = [];
|
|
69
|
+
walk(before, after, "", out);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function walk(before, after, path, out) {
|
|
73
|
+
if (Array.isArray(before) || Array.isArray(after)) {
|
|
74
|
+
const b = Array.isArray(before) ? before : [];
|
|
75
|
+
const a = Array.isArray(after) ? after : [];
|
|
76
|
+
if (isMembership(b) || isMembership(a)) {
|
|
77
|
+
if ((b.length === 0 || isMembership(b)) && (a.length === 0 || isMembership(a))) {
|
|
78
|
+
diffMembership(b, a, path, out);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if ((b.length === 0 || isCollection(b)) && (a.length === 0 || isCollection(a))) {
|
|
83
|
+
diffCollection(b, a, path, out);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (isRow(before) && isRow(after)) {
|
|
88
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
89
|
+
for (const key of [...keys].sort()) {
|
|
90
|
+
walk(before[key], after[key], path === "" ? key : `${path}.${key}`, out);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function diffMembership(before, after, path, out) {
|
|
95
|
+
const count = (values) => {
|
|
96
|
+
const map = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const value of values) map.set(String(value), (map.get(String(value)) ?? 0) + 1);
|
|
98
|
+
return map;
|
|
99
|
+
};
|
|
100
|
+
const b = count(before);
|
|
101
|
+
const a = count(after);
|
|
102
|
+
const entry = { path: path || "(root)", added: [], changed: [], removed: [] };
|
|
103
|
+
for (const [value, n] of a) for (let i = b.get(value) ?? 0; i < n; i += 1) entry.added.push(value);
|
|
104
|
+
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);
|
|
106
|
+
}
|
|
107
|
+
function diffCollection(before, after, path, out) {
|
|
108
|
+
const key = identityKeyOf(before, after);
|
|
109
|
+
const entry = { path: path || "(root)", added: [], changed: [], removed: [] };
|
|
110
|
+
const byId = (rows) => {
|
|
111
|
+
const seen = /* @__PURE__ */ new Map();
|
|
112
|
+
return new Map(
|
|
113
|
+
rows.map((row, index) => {
|
|
114
|
+
let id = key === void 0 ? stableStringify(scalarsOf(row)) : String(row[key]);
|
|
115
|
+
if (key === void 0) {
|
|
116
|
+
const n = seen.get(id) ?? 0;
|
|
117
|
+
seen.set(id, n + 1);
|
|
118
|
+
if (n > 0) id = `${id}#${n}`;
|
|
119
|
+
}
|
|
120
|
+
return [id, { row, index }];
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
const name = (row, index) => key === void 0 ? contentLabel(row) : label(row, key);
|
|
125
|
+
const beforeById = byId(before);
|
|
126
|
+
const afterById = byId(after);
|
|
127
|
+
const nested = [];
|
|
128
|
+
for (const [id, { row, index }] of afterById) {
|
|
129
|
+
const prior = beforeById.get(id);
|
|
130
|
+
if (prior === void 0) {
|
|
131
|
+
entry.added.push(name(row));
|
|
132
|
+
} else {
|
|
133
|
+
const rowName = name(row);
|
|
134
|
+
if (!sameScalars(prior.row, row)) entry.changed.push(rowName);
|
|
135
|
+
nested.push({ before: prior.row, after: row, label: rowName });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const [id, { row, index }] of beforeById) {
|
|
139
|
+
if (!afterById.has(id)) entry.removed.push(name(row));
|
|
140
|
+
}
|
|
141
|
+
if (entry.added.length || entry.changed.length || entry.removed.length) out.push(entry);
|
|
142
|
+
for (const pair of nested) {
|
|
143
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(pair.before), ...Object.keys(pair.after)]);
|
|
144
|
+
for (const field of [...keys].sort()) {
|
|
145
|
+
const b = pair.before[field];
|
|
146
|
+
const a = pair.after[field];
|
|
147
|
+
if (Array.isArray(b) || Array.isArray(a) || isRow(b) && isRow(a)) {
|
|
148
|
+
walk(b, a, `${path}[${pair.label}].${field}`, out);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function renderStateDiff(entries) {
|
|
154
|
+
if (entries.length === 0) return ["State: unchanged since boot."];
|
|
155
|
+
const lines = ["State diff since boot (seed \u2192 now):"];
|
|
156
|
+
const width = Math.max(...entries.map((entry) => entry.path.length));
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const parts = [];
|
|
159
|
+
if (entry.added.length) parts.push(`+${entry.added.length} added: ${entry.added.join(", ")}`);
|
|
160
|
+
if (entry.changed.length) parts.push(`~${entry.changed.length} changed: ${entry.changed.join(", ")}`);
|
|
161
|
+
if (entry.removed.length) parts.push(`-${entry.removed.length} removed: ${entry.removed.join(", ")}`);
|
|
162
|
+
lines.push(` ${entry.path.padEnd(width)} ${parts.join("; ")}`);
|
|
163
|
+
}
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/twin/twinTape.ts
|
|
168
|
+
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
169
|
+
var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
170
|
+
"create",
|
|
171
|
+
"update",
|
|
172
|
+
"delete",
|
|
173
|
+
"remove",
|
|
174
|
+
"add",
|
|
175
|
+
"set",
|
|
176
|
+
"post",
|
|
177
|
+
"send",
|
|
178
|
+
"merge",
|
|
179
|
+
"close",
|
|
180
|
+
"reopen",
|
|
181
|
+
"assign",
|
|
182
|
+
"unassign",
|
|
183
|
+
"apply",
|
|
184
|
+
"archive",
|
|
185
|
+
"unarchive",
|
|
186
|
+
"cancel",
|
|
187
|
+
"push",
|
|
188
|
+
"fork",
|
|
189
|
+
"star",
|
|
190
|
+
"unstar",
|
|
191
|
+
"label",
|
|
192
|
+
"unlabel",
|
|
193
|
+
"comment",
|
|
194
|
+
"reply",
|
|
195
|
+
"edit",
|
|
196
|
+
"move",
|
|
197
|
+
"rename",
|
|
198
|
+
"invite",
|
|
199
|
+
"kick",
|
|
200
|
+
"join",
|
|
201
|
+
"leave",
|
|
202
|
+
"pin",
|
|
203
|
+
"unpin",
|
|
204
|
+
"react",
|
|
205
|
+
"upload",
|
|
206
|
+
"trash",
|
|
207
|
+
"untrash",
|
|
208
|
+
"modify",
|
|
209
|
+
"insert",
|
|
210
|
+
"import",
|
|
211
|
+
"batch",
|
|
212
|
+
"submit",
|
|
213
|
+
"request",
|
|
214
|
+
"dismiss",
|
|
215
|
+
"approve",
|
|
216
|
+
"reject",
|
|
217
|
+
"resolve",
|
|
218
|
+
"transfer",
|
|
219
|
+
"attach",
|
|
220
|
+
"detach",
|
|
221
|
+
"refund",
|
|
222
|
+
"capture",
|
|
223
|
+
"confirm",
|
|
224
|
+
"void",
|
|
225
|
+
"finalize",
|
|
226
|
+
"pay"
|
|
227
|
+
]);
|
|
228
|
+
function isMcpTransport(path) {
|
|
229
|
+
return path === "/mcp" || path.startsWith("/mcp/");
|
|
230
|
+
}
|
|
231
|
+
function requestKind(method, path, tool) {
|
|
232
|
+
if (tool && isMcpTransport(path)) {
|
|
233
|
+
return tool.toLowerCase().split(/[_\-.]/).some((part) => WRITE_VERBS.has(part)) ? "write" : "read";
|
|
234
|
+
}
|
|
235
|
+
return READ_METHODS.has(method) ? "read" : "write";
|
|
236
|
+
}
|
|
237
|
+
function tapeRows(events, sessionPath) {
|
|
238
|
+
if (!Array.isArray(events)) {
|
|
239
|
+
throw new Error("pome twin tape: the twin answered /_pome/events with something other than a list.");
|
|
240
|
+
}
|
|
241
|
+
return events.map((raw, index) => {
|
|
242
|
+
const parsed = recorderEventSchema.safeParse(raw);
|
|
243
|
+
if (!parsed.success) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`pome twin tape: event ${index + 1} on the tape is not a recorded event \u2014 ${parsed.error.issues[0]?.message ?? "unrecognized shape"}.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const event = parsed.data;
|
|
249
|
+
const method = event.method.toUpperCase();
|
|
250
|
+
const path = event.path.startsWith(sessionPath) ? event.path.slice(sessionPath.length) || "/" : event.path;
|
|
251
|
+
const kind = requestKind(method, path, event.tool ?? null);
|
|
252
|
+
let note = null;
|
|
253
|
+
if (event.fidelity === "unsupported" || event.status === 501) {
|
|
254
|
+
note = "not modelled by this twin";
|
|
255
|
+
} else if (event.idempotency_dedupe) {
|
|
256
|
+
note = "replayed from the idempotency cache";
|
|
257
|
+
} else if (kind === "write" && !event.state_mutation) {
|
|
258
|
+
note = event.status >= 400 ? `write did not land (${event.status})` : "write landed nothing";
|
|
259
|
+
}
|
|
260
|
+
if (event.error) note = note ? `${note}; error: ${event.error}` : `error: ${event.error}`;
|
|
261
|
+
return {
|
|
262
|
+
ts: event.ts,
|
|
263
|
+
method,
|
|
264
|
+
path,
|
|
265
|
+
tool: event.tool ?? null,
|
|
266
|
+
status: event.status,
|
|
267
|
+
fidelity: event.fidelity,
|
|
268
|
+
state_mutation: event.state_mutation,
|
|
269
|
+
error: event.error,
|
|
270
|
+
kind,
|
|
271
|
+
note
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function tapeSummary(rows) {
|
|
276
|
+
return {
|
|
277
|
+
requests: rows.length,
|
|
278
|
+
changed_state: rows.filter((row) => row.state_mutation).length,
|
|
279
|
+
writes_not_landed: rows.filter((row) => row.note?.startsWith("write ")).length,
|
|
280
|
+
unsupported: rows.filter((row) => row.note?.startsWith("not modelled")).length,
|
|
281
|
+
reads: rows.filter((row) => row.kind === "read" && !row.note).length
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function requestLabel(row) {
|
|
285
|
+
if (row.tool && isMcpTransport(row.path)) return row.tool;
|
|
286
|
+
if (row.tool) return `${row.method} ${row.path} (${row.tool})`;
|
|
287
|
+
return `${row.method} ${row.path}`;
|
|
288
|
+
}
|
|
289
|
+
function stateLabel(row) {
|
|
290
|
+
if (row.state_mutation) return "changed";
|
|
291
|
+
return row.kind === "read" ? "read" : "no change";
|
|
292
|
+
}
|
|
293
|
+
function renderTape(rows, where) {
|
|
294
|
+
const lines = [`${where.twin} twin at ${where.url} \u2014 ${rows.length} request${rows.length === 1 ? "" : "s"}`];
|
|
295
|
+
if (rows.length === 0) {
|
|
296
|
+
lines.push("(nothing recorded yet \u2014 connect an agent and ask it for something)");
|
|
297
|
+
return lines;
|
|
298
|
+
}
|
|
299
|
+
const cells = rows.map((row) => ({
|
|
300
|
+
time: row.ts.length >= 23 ? row.ts.slice(11, 23) : row.ts,
|
|
301
|
+
request: requestLabel(row),
|
|
302
|
+
status: String(row.status),
|
|
303
|
+
fidelity: row.fidelity,
|
|
304
|
+
state: stateLabel(row),
|
|
305
|
+
note: row.note
|
|
306
|
+
}));
|
|
307
|
+
const width = (pick, min) => Math.max(min, ...cells.map((cell) => pick(cell).length));
|
|
308
|
+
const w = {
|
|
309
|
+
time: width((c) => c.time, 4),
|
|
310
|
+
request: width((c) => c.request, 7),
|
|
311
|
+
status: width((c) => c.status, 6),
|
|
312
|
+
fidelity: width((c) => c.fidelity, 8),
|
|
313
|
+
state: width((c) => c.state, 5)
|
|
314
|
+
};
|
|
315
|
+
lines.push("");
|
|
316
|
+
lines.push(
|
|
317
|
+
[
|
|
318
|
+
"TIME".padEnd(w.time),
|
|
319
|
+
"REQUEST".padEnd(w.request),
|
|
320
|
+
"STATUS".padEnd(w.status),
|
|
321
|
+
"FIDELITY".padEnd(w.fidelity),
|
|
322
|
+
"STATE".padEnd(w.state)
|
|
323
|
+
].join(" ").trimEnd()
|
|
324
|
+
);
|
|
325
|
+
for (const cell of cells) {
|
|
326
|
+
const line = [
|
|
327
|
+
cell.time.padEnd(w.time),
|
|
328
|
+
cell.request.padEnd(w.request),
|
|
329
|
+
cell.status.padEnd(w.status),
|
|
330
|
+
cell.fidelity.padEnd(w.fidelity),
|
|
331
|
+
cell.state.padEnd(w.state)
|
|
332
|
+
].join(" ");
|
|
333
|
+
lines.push(cell.note ? `${line} \u2190 ${cell.note}` : line.trimEnd());
|
|
334
|
+
}
|
|
335
|
+
const summary = tapeSummary(rows);
|
|
336
|
+
const parts = [`${summary.changed_state} changed state`];
|
|
337
|
+
if (summary.writes_not_landed) {
|
|
338
|
+
parts.push(`${summary.writes_not_landed} write${summary.writes_not_landed === 1 ? "" : "s"} did not land`);
|
|
339
|
+
}
|
|
340
|
+
if (summary.unsupported) parts.push(`${summary.unsupported} unsupported`);
|
|
341
|
+
parts.push(`${summary.reads} read${summary.reads === 1 ? "" : "s"}`);
|
|
342
|
+
lines.push("");
|
|
343
|
+
lines.push(`${summary.requests} request${summary.requests === 1 ? "" : "s"}: ${parts.join(" \xB7 ")}`);
|
|
344
|
+
return lines;
|
|
345
|
+
}
|
|
346
|
+
function pickRecordedTwin(entries, name) {
|
|
347
|
+
if (entries.length === 0) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`No standalone twin status found. Start one with \`pome twin start <${TWIN_NAMES.join("|")}>\`.`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (name === void 0) {
|
|
353
|
+
if (entries.length === 1) return entries[0];
|
|
354
|
+
throw new Error(
|
|
355
|
+
`pome twin tape: ${entries.length} twins are recorded in ${STANDALONE_STATUS_PATH} (${entries.map((entry) => entry.name).join(", ")}) \u2014 name one: pome twin tape ${entries[0].name}`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const found = entries.find((entry) => entry.name === name);
|
|
359
|
+
if (found) return found;
|
|
360
|
+
throw new Error(
|
|
361
|
+
`pome twin tape: no ${name} twin is recorded in ${STANDALONE_STATUS_PATH} (recorded: ${entries.map((entry) => entry.name).join(", ")}). Start it with \`pome twin start ${name}\`.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
async function fetchTwin(entry, leaf) {
|
|
365
|
+
const url = `${entry.rest_url}/_pome/${leaf}`;
|
|
366
|
+
let res;
|
|
367
|
+
try {
|
|
368
|
+
res = await fetch(url, {
|
|
369
|
+
headers: { Authorization: `Bearer ${entry.auth_token}` },
|
|
370
|
+
signal: AbortSignal.timeout(5e3)
|
|
371
|
+
});
|
|
372
|
+
} catch {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`pome twin tape: the ${entry.name} twin is not answering at ${entry.rest_url} \u2014 start it with \`pome twin start ${entry.name}\`.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (res.status === 401) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`pome twin tape: the ${entry.name} twin refused the token in ${STANDALONE_STATUS_PATH} (it may be from an earlier start) \u2014 restart it with \`pome twin start ${entry.name}\`.`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (!res.ok) {
|
|
383
|
+
throw new Error(`pome twin tape: ${url} answered ${res.status}.`);
|
|
384
|
+
}
|
|
385
|
+
return await res.json();
|
|
386
|
+
}
|
|
387
|
+
async function runTwinTapeCommand(nameArg, options) {
|
|
388
|
+
const entry = pickRecordedTwin(standaloneStatusEntries(await readStandaloneStatusFile()), nameArg);
|
|
389
|
+
const rows = tapeRows(await fetchTwin(entry, "events"), new URL(entry.rest_url).pathname);
|
|
390
|
+
let diff;
|
|
391
|
+
if (options.diff) {
|
|
392
|
+
const initial = await readStandaloneInitialState(entry.name);
|
|
393
|
+
if (initial === void 0) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`pome twin tape --diff: no boot snapshot at ${standaloneInitialStatePath(entry.name)} \u2014 the ${entry.name} twin was started by an older pome. Restart it with \`pome twin start ${entry.name}\`.`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
diff = diffState(initial, await fetchTwin(entry, "state"));
|
|
399
|
+
}
|
|
400
|
+
if (options.json) {
|
|
401
|
+
const envelope = {
|
|
402
|
+
twin: entry.name,
|
|
403
|
+
url: entry.rest_url,
|
|
404
|
+
requests: rows,
|
|
405
|
+
summary: tapeSummary(rows),
|
|
406
|
+
...diff !== void 0 ? { diff } : {}
|
|
407
|
+
};
|
|
408
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const line of renderTape(rows, { twin: entry.name, url: entry.rest_url })) console.log(line);
|
|
412
|
+
if (diff !== void 0) {
|
|
413
|
+
console.log("");
|
|
414
|
+
for (const line of renderStateDiff(diff)) console.log(line);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export { pickRecordedTwin, renderTape, requestKind, requestLabel, runTwinTapeCommand, tapeRows, tapeSummary };
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
preflightModel,
|
|
26
26
|
preflightModelMessage,
|
|
27
|
-
resolveLlmHost,
|
|
28
27
|
resolveModel,
|
|
29
28
|
} from "../../src/scaffolds/mcp-loop/providers.js";
|
|
30
29
|
|
|
@@ -56,11 +55,6 @@ async function main() {
|
|
|
56
55
|
mcp,
|
|
57
56
|
task: contract.task,
|
|
58
57
|
system,
|
|
59
|
-
signalsPath: contract.signalsPath,
|
|
60
|
-
// The verbatim slug is the LlmCallEvent.model label + the Tier-2 pricing
|
|
61
|
-
// key; host is the synthetic source for the emitted LLM-usage rows.
|
|
62
|
-
modelId: contract.model,
|
|
63
|
-
host: resolveLlmHost(contract.model, process.env),
|
|
64
58
|
});
|
|
65
59
|
|
|
66
60
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pome-sh/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Test
|
|
3
|
+
"version": "0.45.0",
|
|
4
|
+
"description": "Test mode for your integrations, built for the way agents build. Local, stateful twins of GitHub, Slack, Stripe, Gmail and Linear, over REST and MCP.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
6
|
+
"mcp",
|
|
7
|
+
"mcp-server",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"codex",
|
|
11
|
+
"testing",
|
|
12
|
+
"test-mode",
|
|
13
|
+
"sandbox",
|
|
14
|
+
"api-mocking",
|
|
9
15
|
"digital-twin",
|
|
10
|
-
"
|
|
11
|
-
"evals",
|
|
12
|
-
"claude",
|
|
13
|
-
"anthropic",
|
|
16
|
+
"ai-agents",
|
|
14
17
|
"github",
|
|
18
|
+
"slack",
|
|
15
19
|
"stripe",
|
|
20
|
+
"gmail",
|
|
21
|
+
"linear",
|
|
16
22
|
"cli"
|
|
17
23
|
],
|
|
18
24
|
"homepage": "https://pome.sh",
|
|
@@ -54,19 +60,19 @@
|
|
|
54
60
|
"check:manifest-schema": "node scripts/emit-manifest-schema.mjs --check"
|
|
55
61
|
},
|
|
56
62
|
"dependencies": {
|
|
57
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
58
|
-
"@ai-sdk/gateway": "^4.0.
|
|
59
|
-
"@ai-sdk/google": "^4.0.
|
|
60
|
-
"@ai-sdk/openai": "^4.0.
|
|
61
|
-
"@anthropic-ai/sdk": "^0.
|
|
62
|
-
"@hono/node-server": "^2.1.
|
|
63
|
+
"@ai-sdk/anthropic": "^4.0.54",
|
|
64
|
+
"@ai-sdk/gateway": "^4.0.82",
|
|
65
|
+
"@ai-sdk/google": "^4.0.72",
|
|
66
|
+
"@ai-sdk/openai": "^4.0.67",
|
|
67
|
+
"@anthropic-ai/sdk": "^0.126.0",
|
|
68
|
+
"@hono/node-server": "^2.1.1",
|
|
63
69
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
64
|
-
"ai": "^7.0.
|
|
70
|
+
"ai": "^7.0.102",
|
|
65
71
|
"commander": "^15.0.0",
|
|
66
|
-
"graphql": "^16.
|
|
67
|
-
"hono": "^4.13.
|
|
68
|
-
"yaml": "^2.9.
|
|
69
|
-
"zod": "^4.
|
|
72
|
+
"graphql": "^16.14.2",
|
|
73
|
+
"hono": "^4.13.8",
|
|
74
|
+
"yaml": "^2.9.1",
|
|
75
|
+
"zod": "^4.6.5"
|
|
70
76
|
},
|
|
71
77
|
"devDependencies": {
|
|
72
78
|
"@pome-sh/sdk": "*",
|
|
@@ -76,11 +82,11 @@
|
|
|
76
82
|
"@pome-sh/twin-linear": "*",
|
|
77
83
|
"@pome-sh/twin-slack": "*",
|
|
78
84
|
"@pome-sh/twin-stripe": "*",
|
|
79
|
-
"@types/node": "^26.1
|
|
80
|
-
"tsx": "^4.23.
|
|
85
|
+
"@types/node": "^26.6.1",
|
|
86
|
+
"tsx": "^4.23.13",
|
|
81
87
|
"typescript": "^5.9.3",
|
|
82
|
-
"vitest": "^
|
|
83
|
-
"tsup": "^8.5.
|
|
88
|
+
"vitest": "^5.0.1",
|
|
89
|
+
"tsup": "^8.5.1"
|
|
84
90
|
},
|
|
85
91
|
"engines": {
|
|
86
92
|
"node": ">=24"
|