@rubicon-caliga/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/circle.d.ts +48 -0
- package/dist/circle.d.ts.map +1 -0
- package/dist/circle.js +198 -0
- package/dist/circle.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +9 -1
- package/dist/errors.js.map +1 -1
- package/dist/format.d.ts +5 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +81 -6
- package/dist/format.js.map +1 -1
- package/dist/index.js +236 -39
- package/dist/index.js.map +1 -1
- package/dist/payments.d.ts +1 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +3 -1
- package/dist/payments.js.map +1 -1
- package/dist/quickstart.d.ts +30 -0
- package/dist/quickstart.d.ts.map +1 -0
- package/dist/quickstart.js +270 -0
- package/dist/quickstart.js.map +1 -0
- package/dist/quickstart.test.d.ts +2 -0
- package/dist/quickstart.test.d.ts.map +1 -0
- package/dist/quickstart.test.js +229 -0
- package/dist/quickstart.test.js.map +1 -0
- package/package.json +12 -10
- package/src/circle.ts +232 -0
- package/src/errors.ts +16 -1
- package/src/format.ts +87 -6
- package/src/index.ts +248 -37
- package/src/payments.ts +4 -1
- package/src/quickstart.test.ts +264 -0
- package/src/quickstart.ts +330 -0
- package/LICENSE +0 -21
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { RubiconClient } from "@rubicon-caliga/agent-sdk";
|
|
5
|
-
import { parseUsdcToAtomic, type ArticleSummary } from "@rubicon-caliga/core";
|
|
5
|
+
import { parseUsdcToAtomic, settlementNetworkInfo, type ArticleSectionSummary, type ArticleSummary, type StreamMode } from "@rubicon-caliga/core";
|
|
6
6
|
import { parseArgs, booleanFlag, stringFlag, type ParsedArgs } from "./args.js";
|
|
7
7
|
import { configPath, HOSTED_GATEWAY_URL, readConfig, writeConfig, type RubiconCliConfig } from "./config.js";
|
|
8
8
|
import { CliError, toCliError } from "./errors.js";
|
|
@@ -12,11 +12,17 @@ import {
|
|
|
12
12
|
humanArticle,
|
|
13
13
|
humanNavigation,
|
|
14
14
|
humanReceipt,
|
|
15
|
+
humanReceiptSummary,
|
|
15
16
|
printJson,
|
|
16
17
|
printJsonEvent,
|
|
18
|
+
readReceiptSummaryJson,
|
|
19
|
+
recommendedReadCommandFor,
|
|
20
|
+
receiptSummaryJson,
|
|
17
21
|
} from "./format.js";
|
|
18
22
|
import { selectPaymentEngine, type PaymentMode } from "./payments.js";
|
|
23
|
+
import { runDoctor, runQuickstartRead } from "./quickstart.js";
|
|
19
24
|
import { listReceipts, loadReceipt, saveReceipt } from "./receipts.js";
|
|
25
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
20
26
|
|
|
21
27
|
interface Runtime {
|
|
22
28
|
parsed: ParsedArgs;
|
|
@@ -25,6 +31,7 @@ interface Runtime {
|
|
|
25
31
|
gatewayUrl: string;
|
|
26
32
|
apiKey?: string;
|
|
27
33
|
paymentMode: PaymentMode;
|
|
34
|
+
circleChain?: string;
|
|
28
35
|
client: RubiconClient;
|
|
29
36
|
}
|
|
30
37
|
|
|
@@ -50,7 +57,7 @@ async function main(): Promise<void> {
|
|
|
50
57
|
authorization: apiKey ? `Bearer ${apiKey}` : undefined,
|
|
51
58
|
paymentEngine: payment.engine,
|
|
52
59
|
});
|
|
53
|
-
await dispatch({ parsed, json, config, gatewayUrl, apiKey, paymentMode: payment.mode, client });
|
|
60
|
+
await dispatch({ parsed, json, config, gatewayUrl, apiKey, paymentMode: payment.mode, circleChain: payment.circleChain, client });
|
|
54
61
|
} catch (error) {
|
|
55
62
|
const cliError = toCliError(error);
|
|
56
63
|
if (json) {
|
|
@@ -65,11 +72,24 @@ async function main(): Promise<void> {
|
|
|
65
72
|
async function dispatch(runtime: Runtime): Promise<void> {
|
|
66
73
|
const [command, subcommand, ...rest] = runtime.parsed.positionals;
|
|
67
74
|
|
|
75
|
+
if (booleanFlag(runtime.parsed.flags, "version") || command === "version") {
|
|
76
|
+
process.stdout.write(`${packageJson.version}\n`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
if (!command || command === "help" || booleanFlag(runtime.parsed.flags, "help")) {
|
|
69
81
|
showHelp(runtime.json);
|
|
70
82
|
return;
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
if (command === "doctor") {
|
|
86
|
+
printJson(await runDoctor(runtime, { cliVersion: packageJson.version }));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (command === "quickstart-read") {
|
|
90
|
+
printJson(await runQuickstartRead(runtime));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
73
93
|
if (command === "repository") {
|
|
74
94
|
await repository(runtime);
|
|
75
95
|
return;
|
|
@@ -156,8 +176,12 @@ async function articleNavigation(runtime: Runtime, articleId: string | undefined
|
|
|
156
176
|
const goal = stringFlag(runtime.parsed.flags, "goal");
|
|
157
177
|
if (!goal) throw new CliError("MISSING_GOAL", "rubicon article navigation requires --goal.");
|
|
158
178
|
const response = await runtime.client.getNavigation(articleId, goal);
|
|
179
|
+
const recommendedReadCommand = recommendedReadCommandFor(
|
|
180
|
+
response.article.articleId,
|
|
181
|
+
response.navigation.sellerAgent.recommendedSectionId,
|
|
182
|
+
);
|
|
159
183
|
if (runtime.json) {
|
|
160
|
-
printJson({ success: true, article: articleJson(response.article), navigation: response.navigation });
|
|
184
|
+
printJson({ success: true, article: articleJson(response.article), navigation: response.navigation, recommendedReadCommand });
|
|
161
185
|
return;
|
|
162
186
|
}
|
|
163
187
|
process.stdout.write(`${humanArticle(response.article)}\n\n${humanNavigation(response.navigation)}\n`);
|
|
@@ -167,60 +191,129 @@ async function readArticle(runtime: Runtime, articleId: string | undefined): Pro
|
|
|
167
191
|
if (!articleId) throw new CliError("MISSING_ARTICLE_ID", "rubicon read requires an article id.");
|
|
168
192
|
const maxSpendAtomic = parseBudget(runtime.parsed);
|
|
169
193
|
const goal = stringFlag(runtime.parsed.flags, "goal");
|
|
194
|
+
const sectionId = sectionFlag(runtime.parsed);
|
|
195
|
+
const stopAfterSection = booleanFlag(runtime.parsed.flags, "stop-after-section");
|
|
196
|
+
const summary = booleanFlag(runtime.parsed.flags, "summary") || booleanFlag(runtime.parsed.flags, "receipt-summary");
|
|
197
|
+
const chunkWords = chunkWordsFlag(runtime.parsed);
|
|
198
|
+
const streamMode = streamModeFlag(runtime.parsed);
|
|
170
199
|
const maxWordsFlag = stringFlag(runtime.parsed.flags, "max-words");
|
|
171
200
|
const maxWords = maxWordsFlag === undefined ? undefined : Number(maxWordsFlag);
|
|
172
201
|
if (maxWords !== undefined && (!Number.isInteger(maxWords) || maxWords < 1)) {
|
|
173
202
|
throw new CliError("INVALID_MAX_WORDS", "--max-words must be a positive integer.");
|
|
174
203
|
}
|
|
204
|
+
if (stopAfterSection && !sectionId && !goal) {
|
|
205
|
+
throw new CliError("MISSING_SECTION", "--stop-after-section requires --section/--section-id or --goal.");
|
|
206
|
+
}
|
|
207
|
+
if (sectionId) {
|
|
208
|
+
await validateSection(runtime, articleId, sectionId);
|
|
209
|
+
}
|
|
175
210
|
|
|
176
211
|
if (booleanFlag(runtime.parsed.flags, "dry-run")) {
|
|
177
|
-
await dryRun(runtime, articleId, maxSpendAtomic, goal, maxWords);
|
|
212
|
+
await dryRun(runtime, articleId, maxSpendAtomic, goal, maxWords, sectionId, stopAfterSection, chunkWords, streamMode);
|
|
178
213
|
return;
|
|
179
214
|
}
|
|
180
215
|
|
|
181
216
|
let finalReceipt = undefined;
|
|
217
|
+
let storedReceipt = undefined;
|
|
218
|
+
let currentSessionId: string | undefined;
|
|
219
|
+
let cancelled = false;
|
|
220
|
+
const abortOnSigint = (): void => {
|
|
221
|
+
cancelled = true;
|
|
222
|
+
process.exitCode = 130;
|
|
223
|
+
if (!runtime.json) {
|
|
224
|
+
process.stderr.write("\nCancelling read and aborting the active session...\n");
|
|
225
|
+
}
|
|
226
|
+
if (currentSessionId) {
|
|
227
|
+
void runtime.client.abort(currentSessionId, "agent_cancelled").catch(() => {});
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
process.once("SIGINT", abortOnSigint);
|
|
231
|
+
|
|
182
232
|
const stream = runtime.client.read({
|
|
183
233
|
articleId,
|
|
184
234
|
goal,
|
|
235
|
+
sectionId,
|
|
185
236
|
maxSpendAtomic,
|
|
186
237
|
maxWords,
|
|
238
|
+
chunkWords,
|
|
239
|
+
streamMode,
|
|
240
|
+
metadata: stopAfterSection ? { stopAfterSection: true } : undefined,
|
|
187
241
|
});
|
|
188
242
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
243
|
+
try {
|
|
244
|
+
for await (const event of stream) {
|
|
245
|
+
if (event.type === "session.started") {
|
|
246
|
+
currentSessionId = event.session.sessionId;
|
|
247
|
+
}
|
|
248
|
+
if (runtime.json && !summary) {
|
|
249
|
+
printJsonEvent("event", { event });
|
|
250
|
+
if (event.type === "article.completed") {
|
|
251
|
+
finalReceipt = event.receipt;
|
|
252
|
+
}
|
|
253
|
+
continue;
|
|
194
254
|
}
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
255
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
256
|
+
if (runtime.json && summary) {
|
|
257
|
+
if (event.type === "article.completed") {
|
|
258
|
+
finalReceipt = event.receipt;
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
switch (event.type) {
|
|
264
|
+
case "seller.message":
|
|
265
|
+
if (!summary) process.stdout.write(`Seller: ${event.content}\n\n`);
|
|
266
|
+
break;
|
|
267
|
+
case "session.started":
|
|
268
|
+
if (!summary) process.stdout.write(`Session: ${event.session.sessionId}\n\n`);
|
|
269
|
+
break;
|
|
270
|
+
case "article.word":
|
|
271
|
+
if (!summary) process.stdout.write(`${event.word} `);
|
|
272
|
+
break;
|
|
273
|
+
case "article.bundle":
|
|
274
|
+
case "article.chunk":
|
|
275
|
+
if (!summary) process.stdout.write(`${event.words.map((entry) => entry.word).join(" ")} `);
|
|
276
|
+
break;
|
|
277
|
+
case "article.error":
|
|
278
|
+
process.stderr.write(`\nError: ${event.message}\n`);
|
|
279
|
+
break;
|
|
280
|
+
case "article.completed":
|
|
281
|
+
finalReceipt = event.receipt;
|
|
282
|
+
if (!summary) {
|
|
283
|
+
process.stdout.write(`\n\n${humanReceipt(event.receipt)}\n`);
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
case "article.usage":
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (cancelled) {
|
|
216
291
|
break;
|
|
292
|
+
}
|
|
217
293
|
}
|
|
294
|
+
} finally {
|
|
295
|
+
process.removeListener("SIGINT", abortOnSigint);
|
|
218
296
|
}
|
|
219
297
|
|
|
220
298
|
if (finalReceipt) {
|
|
221
|
-
|
|
299
|
+
storedReceipt = await saveReceipt(finalReceipt);
|
|
300
|
+
if (runtime.json && !summary) {
|
|
301
|
+
printJson({ type: "receipt.saved", success: true, receiptId: storedReceipt.receiptId, savedAt: storedReceipt.savedAt, receipt: storedReceipt.receipt });
|
|
302
|
+
} else if (!runtime.json && !summary) {
|
|
303
|
+
process.stdout.write(`Receipt ID: ${storedReceipt.receiptId}\n`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (summary && finalReceipt) {
|
|
222
308
|
if (runtime.json) {
|
|
223
|
-
printJson({
|
|
309
|
+
printJson({
|
|
310
|
+
success: true,
|
|
311
|
+
receiptId: storedReceipt?.receiptId,
|
|
312
|
+
savedAt: storedReceipt?.savedAt,
|
|
313
|
+
receipt: readReceiptSummaryJson(finalReceipt),
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
process.stdout.write(`${humanReceiptSummary(finalReceipt, storedReceipt?.receiptId)}\n`);
|
|
224
317
|
}
|
|
225
318
|
}
|
|
226
319
|
}
|
|
@@ -231,20 +324,54 @@ async function dryRun(
|
|
|
231
324
|
maxSpendAtomic: `${bigint}`,
|
|
232
325
|
goal: string | undefined,
|
|
233
326
|
maxWords: number | undefined,
|
|
327
|
+
sectionId: string | undefined,
|
|
328
|
+
stopAfterSection: boolean,
|
|
329
|
+
chunkWords: number | undefined,
|
|
330
|
+
streamMode: StreamMode,
|
|
234
331
|
): Promise<void> {
|
|
235
332
|
const article = await findArticle(runtime, articleId);
|
|
333
|
+
const navigation = goal && !sectionId ? await runtime.client.getNavigation(articleId, goal).catch(() => undefined) : undefined;
|
|
334
|
+
const effectiveSectionId = sectionId ?? navigation?.navigation.sellerAgent.recommendedSectionId;
|
|
335
|
+
const effectiveSection = effectiveSectionId ? findSection(article, effectiveSectionId) : undefined;
|
|
336
|
+
const estimatedWords = Math.min(maxWords ?? Number.MAX_SAFE_INTEGER, effectiveSection?.wordCount ?? article.totalWords);
|
|
337
|
+
const estimatedMaxCostAtomic = BigInt(article.pricePerWordAtomic) * BigInt(estimatedWords);
|
|
338
|
+
const budgetAtomic = BigInt(maxSpendAtomic);
|
|
339
|
+
const networkInfo = settlementNetworkInfo(article.paymentTerms?.network);
|
|
340
|
+
const fundingMethod = article.paymentTerms?.fundingMethod ?? networkInfo.fundingMethod;
|
|
341
|
+
const balanceCheck = {
|
|
342
|
+
checked: false,
|
|
343
|
+
sufficient: undefined,
|
|
344
|
+
reason: "Live Circle Gateway balance is not checked during dry-run.",
|
|
345
|
+
};
|
|
346
|
+
const budgetSufficiency = {
|
|
347
|
+
sufficientForEstimatedMax: budgetAtomic >= estimatedMaxCostAtomic,
|
|
348
|
+
estimatedMaxCostAtomic: `${estimatedMaxCostAtomic}`,
|
|
349
|
+
estimatedMaxCostUsdc: formatAtomic(`${estimatedMaxCostAtomic}`),
|
|
350
|
+
estimatedWords,
|
|
351
|
+
};
|
|
236
352
|
if (runtime.json) {
|
|
237
353
|
printJson({
|
|
238
354
|
success: true,
|
|
239
355
|
dryRun: true,
|
|
240
356
|
gatewayUrl: runtime.gatewayUrl,
|
|
241
357
|
paymentMode: runtime.paymentMode,
|
|
358
|
+
circleChain: article.paymentTerms?.circleChain ?? networkInfo.circleChain ?? runtime.circleChain,
|
|
242
359
|
budget: {
|
|
243
360
|
maxSpendAtomic,
|
|
244
361
|
maxSpendUsdc: formatAtomic(maxSpendAtomic),
|
|
245
362
|
maxWords,
|
|
246
363
|
},
|
|
247
364
|
goal,
|
|
365
|
+
sectionId,
|
|
366
|
+
recommendedSectionId: navigation?.navigation.sellerAgent.recommendedSectionId,
|
|
367
|
+
effectiveSectionId,
|
|
368
|
+
readStartsAt: effectiveSectionId ? `section:${effectiveSectionId}` : "full-article",
|
|
369
|
+
stopAfterSection,
|
|
370
|
+
chunkWords,
|
|
371
|
+
streamMode,
|
|
372
|
+
fundingMethod,
|
|
373
|
+
estimatedMax: budgetSufficiency,
|
|
374
|
+
walletBalance: balanceCheck,
|
|
248
375
|
article: articleJson(article),
|
|
249
376
|
});
|
|
250
377
|
return;
|
|
@@ -255,9 +382,22 @@ async function dryRun(
|
|
|
255
382
|
"Dry run: no paid read started.",
|
|
256
383
|
`Gateway: ${runtime.gatewayUrl}`,
|
|
257
384
|
`Payment mode: ${runtime.paymentMode}`,
|
|
385
|
+
article.paymentTerms?.circleChain ?? networkInfo.circleChain ?? runtime.circleChain
|
|
386
|
+
? `Circle chain: ${article.paymentTerms?.circleChain ?? networkInfo.circleChain ?? runtime.circleChain}`
|
|
387
|
+
: undefined,
|
|
258
388
|
`Budget: ${formatAtomic(maxSpendAtomic)} USDC (${maxSpendAtomic} atomic)`,
|
|
389
|
+
`Estimated max for ${effectiveSectionId ? effectiveSectionId : "full article"}: ${formatAtomic(`${estimatedMaxCostAtomic}`)} USDC (${estimatedWords.toLocaleString("en-US")} words)`,
|
|
390
|
+
`Budget covers estimate: ${budgetSufficiency.sufficientForEstimatedMax ? "yes" : "no"}`,
|
|
391
|
+
`Wallet balance check: not checked`,
|
|
392
|
+
fundingMethod ? `Funding: ${fundingMethod}` : undefined,
|
|
259
393
|
maxWords ? `Max words: ${maxWords}` : undefined,
|
|
260
394
|
goal ? `Goal: ${goal}` : undefined,
|
|
395
|
+
navigation?.navigation.sellerAgent.recommendedSectionId ? `Recommended section: ${navigation.navigation.sellerAgent.recommendedSectionId}` : undefined,
|
|
396
|
+
`Read starts at: ${effectiveSectionId ? `section ${effectiveSectionId}` : "full article"}`,
|
|
397
|
+
sectionId ? `Section: ${sectionId}` : undefined,
|
|
398
|
+
stopAfterSection ? "Stop after section: yes" : undefined,
|
|
399
|
+
`Stream mode: ${streamMode}`,
|
|
400
|
+
streamMode === "bundled" ? `Bundle words: ${chunkWords ?? 32}` : undefined,
|
|
261
401
|
"",
|
|
262
402
|
humanArticle(article),
|
|
263
403
|
"",
|
|
@@ -268,9 +408,11 @@ async function dryRun(
|
|
|
268
408
|
}
|
|
269
409
|
|
|
270
410
|
async function receiptsList(runtime: Runtime): Promise<void> {
|
|
271
|
-
const
|
|
411
|
+
const limit = limitFlag(runtime.parsed);
|
|
412
|
+
const receipts = (await listReceipts()).slice(0, limit);
|
|
413
|
+
const summary = booleanFlag(runtime.parsed.flags, "summary") || booleanFlag(runtime.parsed.flags, "receipt-summary");
|
|
272
414
|
if (runtime.json) {
|
|
273
|
-
printJson({ success: true, receipts });
|
|
415
|
+
printJson({ success: true, receipts: summary ? receipts.map(receiptSummaryJson) : receipts });
|
|
274
416
|
return;
|
|
275
417
|
}
|
|
276
418
|
if (receipts.length === 0) {
|
|
@@ -290,8 +432,13 @@ async function receiptsList(runtime: Runtime): Promise<void> {
|
|
|
290
432
|
async function receiptsShow(runtime: Runtime, receiptId: string | undefined): Promise<void> {
|
|
291
433
|
if (!receiptId) throw new CliError("MISSING_RECEIPT_ID", "rubicon receipts show requires a receipt id.");
|
|
292
434
|
const stored = await loadReceipt(receiptId);
|
|
435
|
+
const summary = booleanFlag(runtime.parsed.flags, "summary") || booleanFlag(runtime.parsed.flags, "receipt-summary");
|
|
293
436
|
if (runtime.json) {
|
|
294
|
-
printJson({ success: true, ...stored });
|
|
437
|
+
printJson({ success: true, ...(summary ? receiptSummaryJson(stored) : stored) });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (summary) {
|
|
441
|
+
process.stdout.write(`${humanReceiptSummary(stored.receipt, stored.receiptId)}\n`);
|
|
295
442
|
return;
|
|
296
443
|
}
|
|
297
444
|
process.stdout.write(`Receipt ID: ${stored.receiptId}\nSaved: ${stored.savedAt}\n${humanReceipt(stored.receipt)}\n`);
|
|
@@ -309,6 +456,7 @@ async function configShow(runtime: Runtime): Promise<void> {
|
|
|
309
456
|
gatewayUrl: runtime.gatewayUrl,
|
|
310
457
|
apiKey: runtime.apiKey ? "set" : undefined,
|
|
311
458
|
paymentMode: runtime.paymentMode,
|
|
459
|
+
circleChain: runtime.circleChain,
|
|
312
460
|
},
|
|
313
461
|
};
|
|
314
462
|
if (runtime.json) {
|
|
@@ -381,6 +529,67 @@ function parseBudget(parsed: ParsedArgs): `${bigint}` {
|
|
|
381
529
|
}
|
|
382
530
|
}
|
|
383
531
|
|
|
532
|
+
function sectionFlag(parsed: ParsedArgs): string | undefined {
|
|
533
|
+
const section = stringFlag(parsed.flags, "section");
|
|
534
|
+
const sectionId = stringFlag(parsed.flags, "section-id");
|
|
535
|
+
if (section && sectionId && section !== sectionId) {
|
|
536
|
+
throw new CliError("MULTIPLE_SECTIONS", "Use either --section or --section-id, not both.");
|
|
537
|
+
}
|
|
538
|
+
return section ?? sectionId;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function limitFlag(parsed: ParsedArgs): number | undefined {
|
|
542
|
+
const rawLimit = stringFlag(parsed.flags, "limit");
|
|
543
|
+
if (rawLimit === undefined) return undefined;
|
|
544
|
+
const limit = Number(rawLimit);
|
|
545
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
546
|
+
throw new CliError("INVALID_LIMIT", "--limit must be a positive integer.");
|
|
547
|
+
}
|
|
548
|
+
return limit;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function chunkWordsFlag(parsed: ParsedArgs): number | undefined {
|
|
552
|
+
const fast = booleanFlag(parsed.flags, "fast");
|
|
553
|
+
const mode = stringFlag(parsed.flags, "mode");
|
|
554
|
+
if (mode !== undefined && mode !== "batch" && mode !== "word") {
|
|
555
|
+
throw new CliError("INVALID_READ_MODE", "--mode must be batch or word.");
|
|
556
|
+
}
|
|
557
|
+
const rawChunkWords = stringFlag(parsed.flags, "chunk-words");
|
|
558
|
+
if (rawChunkWords === undefined) return fast || mode === "batch" ? 32 : undefined;
|
|
559
|
+
const chunkWords = Number(rawChunkWords);
|
|
560
|
+
if (!Number.isInteger(chunkWords) || chunkWords < 1) {
|
|
561
|
+
throw new CliError("INVALID_CHUNK_WORDS", "--chunk-words must be a positive integer.");
|
|
562
|
+
}
|
|
563
|
+
return Math.min(chunkWords, 256);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function streamModeFlag(parsed: ParsedArgs): StreamMode {
|
|
567
|
+
const streamMode = stringFlag(parsed.flags, "stream-mode");
|
|
568
|
+
const legacyMode = stringFlag(parsed.flags, "mode");
|
|
569
|
+
const perWord = booleanFlag(parsed.flags, "per-word");
|
|
570
|
+
if (streamMode !== undefined && streamMode !== "bundled" && streamMode !== "word") {
|
|
571
|
+
throw new CliError("INVALID_STREAM_MODE", "--stream-mode must be bundled or word.");
|
|
572
|
+
}
|
|
573
|
+
if (perWord && streamMode === "bundled") {
|
|
574
|
+
throw new CliError("INVALID_STREAM_MODE", "--per-word cannot be combined with --stream-mode bundled.");
|
|
575
|
+
}
|
|
576
|
+
if ((perWord || legacyMode === "word" || streamMode === "word") && stringFlag(parsed.flags, "chunk-words") !== undefined) {
|
|
577
|
+
throw new CliError("INVALID_STREAM_MODE", "Per-word mode cannot be combined with --chunk-words.");
|
|
578
|
+
}
|
|
579
|
+
return perWord || legacyMode === "word" ? "word" : streamMode ?? "bundled";
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function findSection(article: ArticleSummary, sectionId: string): ArticleSectionSummary | undefined {
|
|
583
|
+
return article.sections.find((section) => section.sectionId === sectionId);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function validateSection(runtime: Runtime, articleId: string, sectionId: string): Promise<void> {
|
|
587
|
+
const article = await findArticle(runtime, articleId);
|
|
588
|
+
if (!article.sections.some((section) => section.sectionId === sectionId)) {
|
|
589
|
+
throw new CliError("SECTION_NOT_FOUND", `Section not found for ${articleId}: ${sectionId}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
384
593
|
function matchesQuery(article: ArticleSummary, query: string): boolean {
|
|
385
594
|
const haystack = [
|
|
386
595
|
article.articleId,
|
|
@@ -402,12 +611,14 @@ function matchesQuery(article: ArticleSummary, query: string): boolean {
|
|
|
402
611
|
function showHelp(json: boolean): void {
|
|
403
612
|
const usage = [
|
|
404
613
|
"rubicon repository",
|
|
614
|
+
"rubicon doctor --json",
|
|
615
|
+
"rubicon quickstart-read --first --goal \"<goal>\" --max-usdc 0.10 --json",
|
|
405
616
|
"rubicon search \"<query>\"",
|
|
406
617
|
"rubicon article show <article-id>",
|
|
407
618
|
"rubicon article navigation <article-id> --goal \"<goal>\"",
|
|
408
|
-
"rubicon read <article-id> --max-usdc 0.10 [--goal \"...\"] [--max-words 50] [--dry-run]",
|
|
409
|
-
"rubicon receipts list",
|
|
410
|
-
"rubicon receipts show <receipt-id>",
|
|
619
|
+
"rubicon read <article-id> --max-usdc 0.10 [--goal \"...\"] [--section <section-id>] [--stop-after-section] [--stream-mode bundled|word] [--chunk-words 32] [--per-word] [--max-words 50] [--summary] [--dry-run]",
|
|
620
|
+
"rubicon receipts list [--limit 10] [--summary]",
|
|
621
|
+
"rubicon receipts show <receipt-id> [--summary]",
|
|
411
622
|
"rubicon config show",
|
|
412
623
|
"rubicon config set gateway-url <url>",
|
|
413
624
|
"rubicon config set api-key <key>",
|
package/src/payments.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type PaymentMode = "static" | "circle-cli";
|
|
|
11
11
|
export interface PaymentSelection {
|
|
12
12
|
mode: PaymentMode;
|
|
13
13
|
engine: AgentPaymentEngine;
|
|
14
|
+
circleChain?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function selectPaymentEngine(input: {
|
|
@@ -25,12 +26,14 @@ export function selectPaymentEngine(input: {
|
|
|
25
26
|
return { mode: selectedMode, engine: new StaticPaymentEngine() };
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
const circleChain = process.env.CIRCLE_CLI_CHAIN ?? input.config.circleChain ?? "ARC-TESTNET";
|
|
28
30
|
return {
|
|
29
31
|
mode: selectedMode,
|
|
30
32
|
engine: new CircleCliGatewayPaymentEngine({
|
|
31
33
|
agentWalletAddress: envAddress("CIRCLE_AGENT_WALLET_ADDRESS") ?? input.config.agentWalletAddress,
|
|
32
|
-
chain:
|
|
34
|
+
chain: circleChain,
|
|
33
35
|
}),
|
|
36
|
+
circleChain,
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
|