@dvmkit/dvmctl 0.3.2-rc.1 → 0.3.4-rc.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvmkit/dvmctl",
3
- "version": "0.3.2-rc.1",
3
+ "version": "0.3.4-rc.1",
4
4
  "description": "Builder CLI for creating, testing, and deploying Digital Vending Machines",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -29,14 +29,16 @@
29
29
  "node": ">=22"
30
30
  },
31
31
  "scripts": {
32
- "build": "tsup --metafile && node scripts/check-bundled-notices.mjs",
32
+ "build": "node scripts/check-source-boundary.mjs && node scripts/check-bundled-skills.mjs && tsup --metafile && node scripts/check-bundled-notices.mjs",
33
33
  "lint": "eslint . --max-warnings 0",
34
34
  "typecheck": "tsc --noEmit",
35
- "test": "node --test test/*.test.mjs",
36
- "verify": "npm run build && npm run lint && npm run typecheck && npm test"
35
+ "test": "npm run test:source && npm run test:package",
36
+ "verify": "npm run build && npm run lint && npm run typecheck && npm test",
37
+ "test:source": "vitest run",
38
+ "test:package": "node --test test/*.test.mjs"
37
39
  },
38
40
  "dependencies": {
39
- "@dvmkit/sdk": "0.1.5-rc.9"
41
+ "@dvmkit/sdk": "0.2.1-rc.9"
40
42
  },
41
43
  "optionalDependencies": {
42
44
  "pg": "8.20.0",
@@ -61,6 +63,7 @@
61
63
  "@eslint/js": "^10.0.1",
62
64
  "eslint": "^10.0.2",
63
65
  "globals": "^17.7.0",
64
- "typescript-eslint": "^8.56.1"
66
+ "typescript-eslint": "^8.56.1",
67
+ "vitest": "5.0.2"
65
68
  }
66
69
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: build-dvm
3
3
  description: Build a first DVM with dvmkit. Use when asked to turn an idea into a paid HTTPS capability, scaffold or hand-write a DVM, implement a `configureDVM` handler, run it locally, verify Cashu, x402, or Tempo test payments, or prepare it for deployment. Do not use for changing the dvmkit platform, SDK internals, or a caller wallet unrelated to testing a DVM.
4
- compatibility: Node.js 22+, npm, the invited `@dvmkit/dvmctl` release, `@dvmkit/sdk@0.1.5-rc.9`, and `@dvmkit/dvm-cli@0.3.0`. Docker is needed only when this skill starts its local Cashu FakeWallet mint or performs a container build.
4
+ compatibility: Node.js 22+, npm, the invited `@dvmkit/dvmctl` release, `@dvmkit/sdk@0.2.1-rc.9`, and `@dvmkit/dvm-cli@0.3.0`. Docker is needed only when this skill starts its local Cashu FakeWallet mint or performs a container build.
5
5
  allowed-tools: Bash Read Write Edit Glob Grep
6
6
  ---
7
7
 
@@ -64,7 +64,7 @@ Scaffold when it helps; a hand-written Node 22 project with `@dvmkit/sdk` is equ
64
64
  For a hand-written project, pin the admitted SDK exactly:
65
65
 
66
66
  ```bash
67
- npm install --save-exact @dvmkit/sdk@0.1.5-rc.9
67
+ npm install --save-exact @dvmkit/sdk@0.2.1-rc.9
68
68
  ```
69
69
 
70
70
  ```bash
@@ -84,18 +84,45 @@ dvm request --endpoint "$dvm_endpoint" --data "$DVM_INPUT" --rail cashu --mint "
84
84
  --max-increment "$DVM_PAYMENT_CAP" --max-payments 1 </dev/null >"$test_root/paid.json"
85
85
  dvm wallet show >"$test_root/after.json"
86
86
  job_id="$(node -p 'require(process.argv[1]).jobId' "$test_root/paid.json")"
87
- dvm receipts verify "$job_id" >"$test_root/verified.json"
88
- node - "$test_root/unpaid.json" "$test_root/before.json" "$test_root/after.json" "$test_root/paid.json" "$test_root/verified.json" <<'NODE'
89
- const [challengePath, beforePath, afterPath, paidPath, verifiedPath] = process.argv.slice(2);
90
- const challenge=require(challengePath), before=require(beforePath), after=require(afterPath), paid=require(paidPath), verified=require(verifiedPath);
87
+ result_dir="$PWD/.dvmkit-test-results/$job_id"
88
+ mkdir -p -m 700 "$result_dir"
89
+ cp "$test_root/unpaid.json" "$test_root/before.json" "$test_root/after.json" "$test_root/paid.json" "$result_dir/"
90
+ dvm messages "$job_id" --no-stream >/dev/null
91
+ TMPDIR="$result_dir" dvm messages "$job_id" --no-stream --human | tee "$result_dir/artifacts.txt"
92
+ retained_config="$result_dir/caller"
93
+ node - "$DVM_CONFIG_DIR" "$retained_config" "$result_dir/messages.json" "$job_id" <<'NODE'
94
+ const { mkdirSync, readFileSync, writeFileSync } = require("node:fs");
95
+ const { join } = require("node:path");
96
+ const [source, destination, resultPath, jobId] = process.argv.slice(2);
97
+ const job = JSON.parse(readFileSync(join(source, "jobs.json"), "utf8")).find(item => item.id === jobId);
98
+ if (!job) throw Error(`caller state did not retain ${jobId}`);
99
+ const receipts = readFileSync(join(source, "receipts.jsonl"), "utf8").trim().split("\n").filter(Boolean).filter(line => JSON.parse(line).receipt?.job_id === jobId);
100
+ if (receipts.length !== 1) throw Error(`expected one receipt for ${jobId}, found ${receipts.length}`);
101
+ const messages = readFileSync(join(source, "messages", `${jobId}.jsonl`), "utf8").trim().split("\n").filter(Boolean).map(line => JSON.parse(line));
102
+ const retainedMessages = messages.filter(message => ["text", "artifact", "complete", "cancel"].includes(message.type));
103
+ if (!retainedMessages.some(message => message.type === "complete" || message.type === "cancel")) throw Error("caller state did not retain a terminal message");
104
+ const retainedJob = { id: job.id, endpoint: job.endpoint, status: job.status, submittedAt: job.submittedAt, completedAt: job.completedAt, completion: job.completion, paymentsHonoured: job.paymentsHonoured, policy: job.policy };
105
+ mkdirSync(join(destination, "messages"), { recursive: true, mode: 0o700 });
106
+ writeFileSync(join(destination, "jobs.json"), `${JSON.stringify([retainedJob])}\n`, { mode: 0o600 });
107
+ writeFileSync(join(destination, "receipts.jsonl"), `${receipts[0]}\n`, { mode: 0o600 });
108
+ writeFileSync(join(destination, "messages", `${jobId}.jsonl`), `${retainedMessages.map(message => JSON.stringify(message)).join("\n")}\n`, { mode: 0o600 });
109
+ writeFileSync(resultPath, `${JSON.stringify({ jobId, messages: retainedMessages })}\n`, { mode: 0o600 });
110
+ NODE
111
+ DVM_CONFIG_DIR="$retained_config" dvm receipts verify "$job_id" >"$result_dir/verified.json"
112
+ node - "$result_dir/unpaid.json" "$result_dir/before.json" "$result_dir/after.json" "$result_dir/paid.json" "$result_dir/messages.json" "$result_dir/verified.json" <<'NODE'
113
+ const [challengePath, beforePath, afterPath, paidPath, messagesPath, verifiedPath] = process.argv.slice(2);
114
+ const challenge=require(challengePath), before=require(beforePath), after=require(afterPath), paid=require(paidPath), messages=require(messagesPath), verified=require(verifiedPath);
91
115
  const validReceiptStatuses = new Set(["verified", "verified_unattested"]);
92
116
  const expectedSats=challenge.required_msats/1000;
93
117
  if (before.total_sats-after.total_sats !== expectedSats) throw Error(`Cashu debit was ${before.total_sats-after.total_sats}, quote was ${expectedSats} sats`);
94
118
  if (!paid.jobId || paid.next_action !== null || !validReceiptStatuses.has(paid.receipt_verified)) throw Error("paid result is not terminal with a valid receipt signature");
95
119
  if (paid.receipt?.outcome !== "completed" || paid.receipt?.paid?.rail !== "cashu" || paid.receipt.paid.msats !== challenge.required_msats) throw Error("receipt is not bound to the 402 quote");
96
- if (verified.job_id !== paid.jobId || !validReceiptStatuses.has(verified.receipt_verified)) throw Error("stored receipt signature did not verify");
120
+ if (!messages.messages?.some(message => message.type === "complete")) throw Error("paid messages did not complete");
121
+ if ((paid.has_artifacts || paid.artifact_count > 0) && !messages.messages?.some(message => message.type === "artifact")) throw Error("paid result advertised an artifact that was not retrieved");
122
+ if (verified.job_id !== paid.jobId || !validReceiptStatuses.has(verified.receipt_verified) || verified.receipt_checks?.result_hash !== "ok") throw Error("stored receipt and paid result hash did not verify");
97
123
  NODE
98
- cat "$test_root/paid.json"
124
+ cat "$result_dir/paid.json"
125
+ printf '\nPaid result and artifact evidence retained in %s\n' "$result_dir"
99
126
  ```
100
127
 
101
128
  The observed hosted-mint acceptance for a `$0.01` call quoted `12000` msats and debited `12` test sats. That is evidence from that rate snapshot, not a constant for future runs.
@@ -56,17 +56,34 @@ dvm request --endpoint "$dvm_endpoint" --data "$DVM_INPUT" --rail tempo \
56
56
  --budget "$DVM_PAYMENT_CAP" --auto-pay-below "$DVM_PAYMENT_CAP" \
57
57
  --max-increment "$DVM_PAYMENT_CAP" --max-payments 1 </dev/null >"$test_root/paid.json"
58
58
  job_id="$(node -p 'require(process.argv[1]).jobId' "$test_root/paid.json")"
59
- dvm receipts verify "$job_id" >"$test_root/verified.json"
60
- node - "$test_root/unpaid.json" "$test_root/paid.json" "$test_root/verified.json" <<'NODE'
61
- const [challengePath,paidPath,verifiedPath]=process.argv.slice(2);const challenge=require(challengePath),paid=require(paidPath),verified=require(verifiedPath);
59
+ result_dir="$PWD/.dvmkit-test-results/$job_id"
60
+ mkdir -p -m 700 "$result_dir"
61
+ cp "$test_root/unpaid.json" "$test_root/paid.json" "$result_dir/"
62
+ dvm messages "$job_id" --no-stream >/dev/null
63
+ TMPDIR="$result_dir" dvm messages "$job_id" --no-stream --human | tee "$result_dir/artifacts.txt"
64
+ retained_config="$result_dir/caller"
65
+ node - "$DVM_CONFIG_DIR" "$retained_config" "$result_dir/messages.json" "$job_id" <<'NODE'
66
+ const { mkdirSync, readFileSync, writeFileSync }=require("node:fs");const { join }=require("node:path");const [source,destination,resultPath,jobId]=process.argv.slice(2);
67
+ const job=JSON.parse(readFileSync(join(source,"jobs.json"),"utf8")).find(item=>item.id===jobId);if(!job)throw Error(`caller state did not retain ${jobId}`);
68
+ const receipts=readFileSync(join(source,"receipts.jsonl"),"utf8").trim().split("\n").filter(Boolean).filter(line=>JSON.parse(line).receipt?.job_id===jobId);if(receipts.length!==1)throw Error(`expected one receipt for ${jobId}, found ${receipts.length}`);
69
+ const messages=readFileSync(join(source,"messages",`${jobId}.jsonl`),"utf8").trim().split("\n").filter(Boolean).map(line=>JSON.parse(line));const retainedMessages=messages.filter(message=>["text","artifact","complete","cancel"].includes(message.type));if(!retainedMessages.some(message=>message.type==="complete"||message.type==="cancel"))throw Error("caller state did not retain a terminal message");
70
+ const retainedJob={id:job.id,endpoint:job.endpoint,status:job.status,submittedAt:job.submittedAt,completedAt:job.completedAt,completion:job.completion,paymentsHonoured:job.paymentsHonoured,policy:job.policy};mkdirSync(join(destination,"messages"),{recursive:true,mode:0o700});
71
+ writeFileSync(join(destination,"jobs.json"),`${JSON.stringify([retainedJob])}\n`,{mode:0o600});writeFileSync(join(destination,"receipts.jsonl"),`${receipts[0]}\n`,{mode:0o600});writeFileSync(join(destination,"messages",`${jobId}.jsonl`),`${retainedMessages.map(message=>JSON.stringify(message)).join("\n")}\n`,{mode:0o600});writeFileSync(resultPath,`${JSON.stringify({jobId,messages:retainedMessages})}\n`,{mode:0o600});
72
+ NODE
73
+ DVM_CONFIG_DIR="$retained_config" dvm receipts verify "$job_id" >"$result_dir/verified.json"
74
+ node - "$result_dir/unpaid.json" "$result_dir/paid.json" "$result_dir/messages.json" "$result_dir/verified.json" <<'NODE'
75
+ const [challengePath,paidPath,messagesPath,verifiedPath]=process.argv.slice(2);const challenge=require(challengePath),paid=require(paidPath),messages=require(messagesPath),verified=require(verifiedPath);
62
76
  const validReceiptStatuses=new Set(["verified","verified_unattested"]);
63
77
  if(!paid.jobId || paid.next_action!==null || !validReceiptStatuses.has(paid.receipt_verified)) throw Error("paid result is not terminal with a valid receipt signature");
64
78
  const receipt=paid.receipt;
65
79
  if(receipt?.outcome!=="completed" || receipt?.paid?.rail!=="tempo" || receipt.paid.msats!==challenge.required_msats) throw Error("receipt is not quote-bound Tempo");
66
80
  if(receipt.paid.native_amount!==10000 || receipt.paid.native_asset!=="usdc") throw Error("$0.01 must settle as exactly 10000 pathUSD micro-units");
67
- if(verified.job_id!==paid.jobId || !validReceiptStatuses.has(verified.receipt_verified)) throw Error("stored receipt signature did not verify");
81
+ if(!messages.messages?.some(message=>message.type==="complete")) throw Error("paid messages did not complete");
82
+ if((paid.has_artifacts || paid.artifact_count>0) && !messages.messages?.some(message=>message.type==="artifact")) throw Error("paid result advertised an artifact that was not retrieved");
83
+ if(verified.job_id!==paid.jobId || !validReceiptStatuses.has(verified.receipt_verified) || verified.receipt_checks?.result_hash!=="ok") throw Error("stored receipt and paid result hash did not verify");
68
84
  NODE
69
- cat "$test_root/paid.json"
85
+ cat "$result_dir/paid.json"
86
+ printf '\nPaid result and artifact evidence retained in %s\n' "$result_dir"
70
87
  ```
71
88
 
72
89
  Stop if the Moderato chain check, faucet, balance, or cap fails. Do not switch to mainnet or fund a production wallet as a workaround.
@@ -58,17 +58,34 @@ dvm request --endpoint "$dvm_endpoint" --data "$DVM_INPUT" --rail x402 \
58
58
  --budget "$DVM_PAYMENT_CAP" --auto-pay-below "$DVM_PAYMENT_CAP" \
59
59
  --max-increment "$DVM_PAYMENT_CAP" --max-payments 1 </dev/null >"$test_root/paid.json"
60
60
  job_id="$(node -p 'require(process.argv[1]).jobId' "$test_root/paid.json")"
61
- dvm receipts verify "$job_id" >"$test_root/verified.json"
62
- node - "$test_root/unpaid.json" "$test_root/paid.json" "$test_root/verified.json" <<'NODE'
63
- const [challengePath,paidPath,verifiedPath]=process.argv.slice(2);const challenge=require(challengePath),paid=require(paidPath),verified=require(verifiedPath);
61
+ result_dir="$PWD/.dvmkit-test-results/$job_id"
62
+ mkdir -p -m 700 "$result_dir"
63
+ cp "$test_root/unpaid.json" "$test_root/paid.json" "$result_dir/"
64
+ dvm messages "$job_id" --no-stream >/dev/null
65
+ TMPDIR="$result_dir" dvm messages "$job_id" --no-stream --human | tee "$result_dir/artifacts.txt"
66
+ retained_config="$result_dir/caller"
67
+ node - "$DVM_CONFIG_DIR" "$retained_config" "$result_dir/messages.json" "$job_id" <<'NODE'
68
+ const { mkdirSync, readFileSync, writeFileSync }=require("node:fs");const { join }=require("node:path");const [source,destination,resultPath,jobId]=process.argv.slice(2);
69
+ const job=JSON.parse(readFileSync(join(source,"jobs.json"),"utf8")).find(item=>item.id===jobId);if(!job)throw Error(`caller state did not retain ${jobId}`);
70
+ const receipts=readFileSync(join(source,"receipts.jsonl"),"utf8").trim().split("\n").filter(Boolean).filter(line=>JSON.parse(line).receipt?.job_id===jobId);if(receipts.length!==1)throw Error(`expected one receipt for ${jobId}, found ${receipts.length}`);
71
+ const messages=readFileSync(join(source,"messages",`${jobId}.jsonl`),"utf8").trim().split("\n").filter(Boolean).map(line=>JSON.parse(line));const retainedMessages=messages.filter(message=>["text","artifact","complete","cancel"].includes(message.type));if(!retainedMessages.some(message=>message.type==="complete"||message.type==="cancel"))throw Error("caller state did not retain a terminal message");
72
+ const retainedJob={id:job.id,endpoint:job.endpoint,status:job.status,submittedAt:job.submittedAt,completedAt:job.completedAt,completion:job.completion,paymentsHonoured:job.paymentsHonoured,policy:job.policy};mkdirSync(join(destination,"messages"),{recursive:true,mode:0o700});
73
+ writeFileSync(join(destination,"jobs.json"),`${JSON.stringify([retainedJob])}\n`,{mode:0o600});writeFileSync(join(destination,"receipts.jsonl"),`${receipts[0]}\n`,{mode:0o600});writeFileSync(join(destination,"messages",`${jobId}.jsonl`),`${retainedMessages.map(message=>JSON.stringify(message)).join("\n")}\n`,{mode:0o600});writeFileSync(resultPath,`${JSON.stringify({jobId,messages:retainedMessages})}\n`,{mode:0o600});
74
+ NODE
75
+ DVM_CONFIG_DIR="$retained_config" dvm receipts verify "$job_id" >"$result_dir/verified.json"
76
+ node - "$result_dir/unpaid.json" "$result_dir/paid.json" "$result_dir/messages.json" "$result_dir/verified.json" <<'NODE'
77
+ const [challengePath,paidPath,messagesPath,verifiedPath]=process.argv.slice(2);const challenge=require(challengePath),paid=require(paidPath),messages=require(messagesPath),verified=require(verifiedPath);
64
78
  const validReceiptStatuses=new Set(["verified","verified_unattested"]);
65
79
  if(!paid.jobId || paid.next_action!==null || !validReceiptStatuses.has(paid.receipt_verified)) throw Error("paid result is not terminal with a valid receipt signature");
66
80
  const receipt=paid.receipt;
67
81
  if(receipt?.outcome!=="completed" || receipt?.paid?.rail!=="x402" || receipt.paid.msats!==challenge.required_msats) throw Error("receipt is not quote-bound x402");
68
82
  if(receipt.paid.native_amount!==10000 || receipt.paid.native_asset!=="usdc") throw Error("$0.01 must settle as exactly 10000 micro-USDC");
69
- if(verified.job_id!==paid.jobId || !validReceiptStatuses.has(verified.receipt_verified)) throw Error("stored receipt signature did not verify");
83
+ if(!messages.messages?.some(message=>message.type==="complete")) throw Error("paid messages did not complete");
84
+ if((paid.has_artifacts || paid.artifact_count>0) && !messages.messages?.some(message=>message.type==="artifact")) throw Error("paid result advertised an artifact that was not retrieved");
85
+ if(verified.job_id!==paid.jobId || !validReceiptStatuses.has(verified.receipt_verified) || verified.receipt_checks?.result_hash!=="ok") throw Error("stored receipt and paid result hash did not verify");
70
86
  NODE
71
- cat "$test_root/paid.json"
87
+ cat "$result_dir/paid.json"
88
+ printf '\nPaid result and artifact evidence retained in %s\n' "$result_dir"
72
89
  ```
73
90
 
74
91
  Stop on a missing balance, RPC/facilitator failure, or cap refusal. Do not replace the network, key, or cap without new approval.
@@ -150,6 +150,8 @@ stage. VM resources go in `package.json`'s `dvmkit` field (`{ "dvmkit": { "memor
150
150
 
151
151
  ### Environment variables
152
152
 
153
+ For cloud compute deployment automation, `dvmctl deploy --target cloud --env-stdin` accepts a piped JSON object of string environment values. They override the project `.env` for this deployment without rewriting it. Input must finish within 5 seconds and fit in 64 KiB. Send secret values through stdin; local, self-host, connect-tier, and dry-run deployments reject this flag.
154
+
153
155
  The SDK reads only `DVMKIT_*`-prefixed variables. Old unprefixed names (`MINTS`,
154
156
  `MPP_TEMPO_RECIPIENT`, `X402_PAY_TO`, etc.) and the legacy `DVMKIT_FIRST_PARTY` /
155
157
  `DVMKIT_ACCESS_TOKENS` / `DVMKIT_SKIP_PAYMENT` are gone — don't use them.