warpmetal 0.3.0 → 0.4.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 +56 -7
- package/package.json +2 -2
- package/skills/warpmetal/SKILL.md +44 -22
- package/skills/warpmetal/agents/openai.yaml +2 -2
- package/skills/warpmetal/references/cli-reference.md +28 -4
- package/skills/warpmetal/references/payments.md +110 -0
- package/skills/warpmetal/references/safety.md +20 -7
- package/src/cli.js +134 -24
- package/src/payment.js +769 -0
- package/src/state.js +55 -1
package/src/cli.js
CHANGED
|
@@ -14,6 +14,13 @@ import { connectionProfile, writeConnectionProfile } from "./connection.js";
|
|
|
14
14
|
import { CliError, toErrorMessage } from "./errors.js";
|
|
15
15
|
import { installSkill } from "./install-skill.js";
|
|
16
16
|
import { installRuntime } from "./installer.js";
|
|
17
|
+
import {
|
|
18
|
+
createPaymentRequestEnvelope,
|
|
19
|
+
defaultPaymentPaths,
|
|
20
|
+
paymentWorkflow,
|
|
21
|
+
readPaymentArtifact,
|
|
22
|
+
writePaymentRequestEnvelope,
|
|
23
|
+
} from "./payment.js";
|
|
17
24
|
import {
|
|
18
25
|
readSandboxFile,
|
|
19
26
|
requireTemporaryConfirmation,
|
|
@@ -66,8 +73,9 @@ Usage:
|
|
|
66
73
|
--ssh-public-key-file <path> [--runtime-file <path>] [--confirm TEMPORARY]
|
|
67
74
|
[--email <address>] [--idempotency-key <key>]
|
|
68
75
|
warpmetal order status --task <taskId> [--wait] [--timeout-seconds <n>]
|
|
69
|
-
warpmetal checkout challenge --task <taskId>
|
|
70
|
-
warpmetal checkout submit --task <taskId>
|
|
76
|
+
warpmetal checkout challenge --task <taskId> [--request-envelope-out <path>]
|
|
77
|
+
warpmetal checkout submit --task <taskId>
|
|
78
|
+
(--payment-artifact <path> | --payment-signature-file <path>)
|
|
71
79
|
[--wait] [--timeout-seconds <n>]
|
|
72
80
|
warpmetal server login --server <serverId> --identity <private-key-path>
|
|
73
81
|
warpmetal server get --server <serverId>
|
|
@@ -112,6 +120,16 @@ function writeLine(stream, value = "") {
|
|
|
112
120
|
stream.write(`${value}\n`);
|
|
113
121
|
}
|
|
114
122
|
|
|
123
|
+
function shellCommand(argv) {
|
|
124
|
+
return argv
|
|
125
|
+
.map((value) =>
|
|
126
|
+
/^[A-Za-z0-9_./:=+@-]+$/.test(value)
|
|
127
|
+
? value
|
|
128
|
+
: `'${value.replaceAll("'", `'\\''`)}'`,
|
|
129
|
+
)
|
|
130
|
+
.join(" ");
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
function emit(stream, value, json, human) {
|
|
116
134
|
if (json) writeLine(stream, JSON.stringify(value, null, 2));
|
|
117
135
|
else writeLine(stream, human);
|
|
@@ -344,6 +362,46 @@ function challengeResult(taskId, checkoutBody, response) {
|
|
|
344
362
|
};
|
|
345
363
|
}
|
|
346
364
|
|
|
365
|
+
async function attachPaymentWorkflow(
|
|
366
|
+
client,
|
|
367
|
+
store,
|
|
368
|
+
taskId,
|
|
369
|
+
order,
|
|
370
|
+
safe,
|
|
371
|
+
requestedEnvelopePath,
|
|
372
|
+
) {
|
|
373
|
+
if (!safe.paymentRequired) return safe;
|
|
374
|
+
const request = createPaymentRequestEnvelope({
|
|
375
|
+
baseUrl: client.baseUrl,
|
|
376
|
+
checkoutPath: order.checkoutPath,
|
|
377
|
+
checkoutBody: order.checkoutBody,
|
|
378
|
+
paymentRequired: safe.paymentRequired,
|
|
379
|
+
merchantReference: taskId,
|
|
380
|
+
});
|
|
381
|
+
const defaults = defaultPaymentPaths(
|
|
382
|
+
store.directory,
|
|
383
|
+
taskId,
|
|
384
|
+
request.challengeDigest,
|
|
385
|
+
);
|
|
386
|
+
const requestEnvelopePath = await writePaymentRequestEnvelope(
|
|
387
|
+
requestedEnvelopePath || defaults.requestEnvelopePath,
|
|
388
|
+
request.envelope,
|
|
389
|
+
);
|
|
390
|
+
const workflow = paymentWorkflow({
|
|
391
|
+
taskId,
|
|
392
|
+
requestEnvelopePath,
|
|
393
|
+
paymentArtifactPath: defaults.paymentArtifactPath,
|
|
394
|
+
});
|
|
395
|
+
Object.assign(safe, {
|
|
396
|
+
paymentTerms: request.terms,
|
|
397
|
+
paymentRequestDigest: request.requestDigest,
|
|
398
|
+
paymentChallengeDigest: request.challengeDigest,
|
|
399
|
+
paymentWorkflow: workflow,
|
|
400
|
+
});
|
|
401
|
+
await store.savePaymentChallenge(taskId, safe);
|
|
402
|
+
return safe;
|
|
403
|
+
}
|
|
404
|
+
|
|
347
405
|
async function handleHealth(client, { json, stdout }) {
|
|
348
406
|
const result = await client.health();
|
|
349
407
|
emit(
|
|
@@ -470,16 +528,23 @@ async function handleCheckoutChallenge(client, store, options, context) {
|
|
|
470
528
|
bodyText: order.checkoutBody,
|
|
471
529
|
token,
|
|
472
530
|
});
|
|
473
|
-
const safe =
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
531
|
+
const safe = await attachPaymentWorkflow(
|
|
532
|
+
client,
|
|
533
|
+
store,
|
|
534
|
+
taskId,
|
|
535
|
+
order,
|
|
536
|
+
challengeResult(taskId, order.checkoutBody, response),
|
|
537
|
+
stringOption(options, "request-envelope-out"),
|
|
538
|
+
);
|
|
539
|
+
const paymentInstructions = safe.paymentWorkflow
|
|
540
|
+
? `\nWallet package: ${safe.paymentWorkflow.signerPackage.spec} (Node ${safe.paymentWorkflow.signerNodeRequirement})\nInstall: ${shellCommand(safe.paymentWorkflow.signerPackage.install.argv)}\nVerify: ${shellCommand(safe.paymentWorkflow.signerContract.probe.argv)}\nRequest envelope: ${safe.paymentWorkflow.requestEnvelopePath}\nAuthorize: ${shellCommand(safe.paymentWorkflow.authorize.argv)}\nSubmit with WarpMetal: ${shellCommand(safe.paymentWorkflow.submit.argv)}`
|
|
541
|
+
: "";
|
|
477
542
|
emit(
|
|
478
543
|
context.stdout,
|
|
479
544
|
safe,
|
|
480
545
|
context.json,
|
|
481
546
|
response.status === 402
|
|
482
|
-
? `Payment authorization required for ${taskId}
|
|
547
|
+
? `Payment authorization required for ${taskId}.${paymentInstructions}`
|
|
483
548
|
: `Checkout status for ${taskId}: ${safe.status}`,
|
|
484
549
|
);
|
|
485
550
|
return response.status === 409 ? 6 : response.status === 402 ? 7 : 0;
|
|
@@ -487,19 +552,45 @@ async function handleCheckoutChallenge(client, store, options, context) {
|
|
|
487
552
|
|
|
488
553
|
async function handleCheckoutSubmit(client, store, options, context) {
|
|
489
554
|
const taskId = stringOption(options, "task", { required: true });
|
|
490
|
-
const signatureFile = stringOption(options, "payment-signature-file"
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
555
|
+
const signatureFile = stringOption(options, "payment-signature-file");
|
|
556
|
+
const artifactFile = stringOption(options, "payment-artifact");
|
|
557
|
+
if (Boolean(signatureFile) === Boolean(artifactFile)) {
|
|
558
|
+
throw new CliError(
|
|
559
|
+
"Use exactly one of --payment-artifact or --payment-signature-file.",
|
|
560
|
+
{ exitCode: 2 },
|
|
561
|
+
);
|
|
562
|
+
}
|
|
497
563
|
const order = await store.order(taskId);
|
|
498
564
|
if (!order?.checkoutPath || !order?.checkoutBody) {
|
|
499
565
|
throw new CliError(`No exact checkout state exists for ${taskId}.`, {
|
|
500
566
|
exitCode: 2,
|
|
501
567
|
});
|
|
502
568
|
}
|
|
569
|
+
let walletPayment;
|
|
570
|
+
let paymentSignature;
|
|
571
|
+
if (artifactFile) {
|
|
572
|
+
if (!order.paymentRequired) {
|
|
573
|
+
throw new CliError(
|
|
574
|
+
`No saved PAYMENT-REQUIRED challenge exists for ${taskId}. Run warpmetal checkout challenge first.`,
|
|
575
|
+
{ exitCode: 2 },
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
const expected = createPaymentRequestEnvelope({
|
|
579
|
+
baseUrl: client.baseUrl,
|
|
580
|
+
checkoutPath: order.checkoutPath,
|
|
581
|
+
checkoutBody: order.checkoutBody,
|
|
582
|
+
paymentRequired: order.paymentRequired,
|
|
583
|
+
merchantReference: taskId,
|
|
584
|
+
});
|
|
585
|
+
walletPayment = await readPaymentArtifact(artifactFile, expected);
|
|
586
|
+
paymentSignature = walletPayment.paymentSignature;
|
|
587
|
+
await store.saveWalletPaymentAttempt(taskId, walletPayment);
|
|
588
|
+
} else {
|
|
589
|
+
paymentSignature = await readHeaderValueFile(
|
|
590
|
+
signatureFile,
|
|
591
|
+
"The payment signature file",
|
|
592
|
+
);
|
|
593
|
+
}
|
|
503
594
|
const token = await requireTaskToken(store, taskId, options, context.env);
|
|
504
595
|
const wait = booleanOption(options, "wait");
|
|
505
596
|
const deadline = timeoutDeadline(
|
|
@@ -512,8 +603,13 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
512
603
|
token,
|
|
513
604
|
paymentSignature,
|
|
514
605
|
});
|
|
515
|
-
const safe =
|
|
516
|
-
|
|
606
|
+
const safe = await attachPaymentWorkflow(
|
|
607
|
+
client,
|
|
608
|
+
store,
|
|
609
|
+
taskId,
|
|
610
|
+
order,
|
|
611
|
+
challengeResult(taskId, order.checkoutBody, response),
|
|
612
|
+
);
|
|
517
613
|
const retryable =
|
|
518
614
|
response.status === 202 &&
|
|
519
615
|
["payment_pending", "payment_finalizing"].includes(response.data?.status);
|
|
@@ -527,6 +623,19 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
527
623
|
...safe,
|
|
528
624
|
task: response.data?.task,
|
|
529
625
|
message: response.data?.message,
|
|
626
|
+
...(walletPayment
|
|
627
|
+
? {
|
|
628
|
+
walletPayment: {
|
|
629
|
+
attemptId: walletPayment.attemptId,
|
|
630
|
+
requestDigest: walletPayment.requestDigest,
|
|
631
|
+
buyerPaymentIdentifier: walletPayment.buyerPaymentIdentifier,
|
|
632
|
+
wallet: walletPayment.wallet,
|
|
633
|
+
payerAddress: walletPayment.payerAddress,
|
|
634
|
+
artifactPath: walletPayment.path,
|
|
635
|
+
expiresAt: walletPayment.expiresAt,
|
|
636
|
+
},
|
|
637
|
+
}
|
|
638
|
+
: {}),
|
|
530
639
|
};
|
|
531
640
|
emit(
|
|
532
641
|
context.stdout,
|
|
@@ -662,12 +771,7 @@ async function handleServerReload(client, store, options, context) {
|
|
|
662
771
|
options,
|
|
663
772
|
"acknowledge-agent-runtime-reset",
|
|
664
773
|
);
|
|
665
|
-
const token = await requireReloadToken(
|
|
666
|
-
store,
|
|
667
|
-
serverId,
|
|
668
|
-
options,
|
|
669
|
-
context.env,
|
|
670
|
-
);
|
|
774
|
+
const token = await requireReloadToken(store, serverId, options, context.env);
|
|
671
775
|
const server = (await client.getServer(serverId, token)).data?.task;
|
|
672
776
|
if (server?.agentRuntime && !acknowledgeRuntimeReset) {
|
|
673
777
|
throw new CliError(
|
|
@@ -717,7 +821,7 @@ async function handleServerReload(client, store, options, context) {
|
|
|
717
821
|
? " Verify and refresh the owner SSH host key, reinstall Agent Runtime, then refresh every sandbox connection profile."
|
|
718
822
|
: operation?.state === "succeeded"
|
|
719
823
|
? " Verify and refresh the owner SSH host key before reconnecting."
|
|
720
|
-
|
|
824
|
+
: ""),
|
|
721
825
|
);
|
|
722
826
|
if (operation?.state === "manual_review") return 6;
|
|
723
827
|
if (operation?.state === "failed") return 5;
|
|
@@ -1399,13 +1503,19 @@ async function dispatch(positionals, options, passthrough, context) {
|
|
|
1399
1503
|
]);
|
|
1400
1504
|
return handleTaskStatus(client, store, options, context);
|
|
1401
1505
|
case "checkout challenge":
|
|
1402
|
-
rejectUnknownOptions(options, [
|
|
1506
|
+
rejectUnknownOptions(options, [
|
|
1507
|
+
...COMMON_OPTIONS,
|
|
1508
|
+
"task",
|
|
1509
|
+
"token-file",
|
|
1510
|
+
"request-envelope-out",
|
|
1511
|
+
]);
|
|
1403
1512
|
return handleCheckoutChallenge(client, store, options, context);
|
|
1404
1513
|
case "checkout submit":
|
|
1405
1514
|
rejectUnknownOptions(options, [
|
|
1406
1515
|
...COMMON_OPTIONS,
|
|
1407
1516
|
"task",
|
|
1408
1517
|
"token-file",
|
|
1518
|
+
"payment-artifact",
|
|
1409
1519
|
"payment-signature-file",
|
|
1410
1520
|
"wait",
|
|
1411
1521
|
"timeout-seconds",
|