@dvmkit/dvmctl 0.2.1-rc.1 → 0.2.1-rc.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/README.md CHANGED
@@ -6,7 +6,7 @@ Install this exact release:
6
6
 
7
7
  <!-- x-release-please-start-version -->
8
8
  ```bash
9
- npm install -g @dvmkit/dvmctl@0.2.1-rc.1
9
+ npm install -g @dvmkit/dvmctl@0.2.1-rc.2
10
10
  ```
11
11
  <!-- x-release-please-end -->
12
12
 
@@ -14,7 +14,7 @@ Install the complete bundled skill directory so its references remain available:
14
14
 
15
15
  <!-- x-release-please-start-version -->
16
16
  ```bash
17
- npm install --ignore-scripts --prefix "$HOME/.dvmkit" @dvmkit/dvmctl@0.2.1-rc.1
17
+ npm install --ignore-scripts --prefix "$HOME/.dvmkit" @dvmkit/dvmctl@0.2.1-rc.2
18
18
  skill_dir="$(dirname "$(NODE_PATH="$HOME/.dvmkit/node_modules" node -p "require.resolve('@dvmkit/dvmctl/skill')")")"
19
19
  ```
20
20
  <!-- x-release-please-end -->
package/dist/dvmctl.js CHANGED
@@ -2136,7 +2136,7 @@ Opening browser to authenticate...`);console.log(`
2136
2136
  ${authUrl}
2137
2137
  `);console.log(`Session code: ${fingerprint}\u2026 (must match the code shown in your browser)
2138
2138
  `);try{const{exec}=await import("child_process");const cmd=process.platform==="darwin"?`open "${authUrl}"`:process.platform==="win32"?`start "${authUrl}"`:`xdg-open "${authUrl}"`;exec(cmd)}catch{console.log("Could not open browser automatically. Please open the URL above.")}console.log("Waiting for authentication...");const pollUrl=`${PLATFORM_URL}/api/v1/cli-auth/${sessionId}`;for(let i=0;i<CLI_AUTH_MAX_POLL_ATTEMPTS;i++){const jitter=Math.floor(Math.random()*CLI_AUTH_POLL_JITTER_MS);await sleep(CLI_AUTH_POLL_INTERVAL_MS+jitter);let res;try{res=await fetch(pollUrl,{headers:{Authorization:`Bearer ${verifier}`}})}catch{continue}if(res.status===404)continue;if(!res.ok)continue;const data=await res.json().catch(()=>null);if(!data)continue;if(data.cancelled){if(human){console.log("\nSign-in cancelled.")}else{console.log(JSON.stringify({authenticated:false,cancelled:true}))}process.exit(1)}if(data.token&&data.account){saveAuthToken({token:data.token,account:data.account});if(human){console.log(`
2139
- Authenticated as ${data.account}`)}else{console.log(JSON.stringify({authenticated:true,account:data.account}))}return}}console.error("\nAuthentication timed out. Try again or use DVMKIT_API_KEY env var.");process.exit(1)}function sleep(ms){return new Promise(resolve8=>setTimeout(resolve8,ms))}init_output();import{DvmError as DvmError10}from"@dvmkit/sdk/internal";import{DvmError as DvmError9,providerProseSources as providerProseSources3}from"@dvmkit/sdk/internal";import{ADMIN_STATE_UNAVAILABLE,buildAdminAuthHeader,DvmError as DvmError3}from"@dvmkit/sdk/internal";var AdminCashuClient=class{endpoint;dvmId;privkeyHex;fetchImpl;now;constructor(opts){this.endpoint=opts.endpoint.replace(/\/+$/,"");this.dvmId=opts.dvmId;this.privkeyHex=opts.privkeyHex;this.fetchImpl=opts.fetchImpl??globalThis.fetch.bind(globalThis);this.now=opts.now??Date.now}async getPendingBatches(){return this.requestJson("GET","/admin/cashu/pending-batches",void 0)}async markMelted(body){return this.requestJson("POST","/admin/cashu/mark-melted",body)}async rotateLockPubkey(body){return this.requestJson("POST","/admin/cashu/rotate-lock-pubkey",body)}async getPendingDrains(){return this.requestJson("GET","/admin/credit/pending-drains",void 0)}async getBitcoinLiability(){return this.requestJson("GET","/admin/credit/bitcoin-liability",void 0)}async parkDrain(body){return this.requestJson("POST","/admin/credit/park-drain",body)}async markDrainSent(body){return this.requestJson("POST","/admin/credit/mark-drain-sent",body)}async getBlockedInvoices(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/blocked-invoices",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileInvoice(body){return this.requestJson("POST","/admin/credit/reconcile-invoice",body)}async writeOffInvoice(body){return this.requestJson("POST","/admin/credit/write-off-invoice",body)}async getX402Settlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/x402-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402Settlement(body){return this.requestJson("POST","/admin/credit/reconcile-x402-settlement",body)}async writeOffX402Settlement(body){return this.requestJson("POST","/admin/credit/write-off-x402-settlement",body)}async getX402ExactSettlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/x402-exact-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402ExactSettlement(settlementIntentId){return this.requestJson("POST","/admin/credit/reconcile-x402-exact-settlement",{settlement_intent_id:settlementIntentId})}async getTempoDrains(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/tempo-drains",void 0,params.size===0?void 0:`?${params.toString()}`)}async getTempoCreditLosses(opts){const query=opts?.limit===void 0?void 0:`?limit=${String(opts.limit)}`;return this.requestJson("GET","/admin/credit/tempo-losses",void 0,query)}async reconcileTempoDrain(body){return this.requestJson("POST","/admin/credit/reconcile-tempo-drain",body)}async writeOffTempoDrain(body){return this.requestJson("POST","/admin/credit/write-off-tempo-drain",body)}async requestJson(method,path,body,query){const{header}=buildAdminAuthHeader({method,path,dvmId:this.dvmId,privkeyHex:this.privkeyHex,now:this.now});const init2={method,headers:{Authorization:header,...body===void 0?{}:{"Content-Type":"application/json"}},...body===void 0?{}:{body:JSON.stringify(body)}};let res;try{res=await this.fetchImpl(`${this.endpoint}${path}${query??""}`,init2)}catch(err){throw new DvmError3("admin_unreachable",`Failed to reach DVM admin endpoint ${this.endpoint}${path}: ${err instanceof Error?err.message:String(err)}`,"The DVM may be down or the endpoint URL is wrong. Verify with `dvmctl info <handle>`.")}const text=await res.text();let parsed;try{parsed=text.length===0?{}:JSON.parse(text)}catch{throw new DvmError3("admin_invalid_json",`DVM admin endpoint ${path} returned non-JSON (HTTP ${res.status}): ${text.slice(0,200)}`,"The DVM may not be running the tracked change SDK. Redeploy with the latest SDK.",void 0,void 0,{message:"provider"})}if(!res.ok){const body2=parsed;const apiError=new AdminApiError(res.status,{error:typeof body2.error==="string"?body2.error:"admin_error",reason:typeof body2.reason==="string"?body2.reason:void 0,message:typeof body2.message==="string"?body2.message:void 0,hint:typeof body2.hint==="string"?body2.hint:void 0,display:typeof body2.display==="string"?body2.display:void 0,raw:parsed});if(res.status===503&&apiError.body.error===ADMIN_STATE_UNAVAILABLE){throw new DvmError3(ADMIN_STATE_UNAVAILABLE,"DVM admin authentication state is temporarily unavailable; the requested DVM operation did not run.","Retry the same command. If this keeps happening, inspect the DVM's database health and restart the DVM. Do not rotate or replace the lock key.")}throw apiError}return parsed}};var AdminApiError=class extends Error{constructor(status2,body){super(`Admin endpoint returned HTTP ${status2}: ${body.error}${body.reason?` (${body.reason})`:""}`);this.status=status2;this.body=body;this.name="AdminApiError"}status;body;get isBadSignature(){return this.status===401&&this.body.reason==="bad_signature"}};function rethrowIfBadSignature(err){if(!(err instanceof AdminApiError)||!err.isBadSignature)return;throw new DvmError3("admin_bad_signature","DVM admin endpoint rejected our signed challenge (bad_signature).","The DVM's DVMKIT_CASHU_LOCK_PUBKEY does not match the privkey derived from this mnemonic. Re-deploy with the matching pubkey or switch mnemonics.")}function rethrowIfAdminStateUnavailable(err){if(err instanceof DvmError3&&err.code===ADMIN_STATE_UNAVAILABLE)throw err}init_builder_lock_identity();import{DvmError as DvmError8}from"@dvmkit/sdk/internal";async function resolveDvm(slug,opts={}){const{token,platformUrl}=requireAuth();const{dvms,org}=await listDvms(platformUrl,token,{org:resolveOrgScope(opts)});const dvm=dvms.find(candidate=>candidate.slug===slug);if(!dvm){throw new DvmError8("not_found",`DVM "${slug}" not found in ${scopeLabel(org)}.`,"Run `dvmctl list` to see the DVMs available in this context.")}return{dvmId:dvm.id,dvmkitUrl:dvm.dvmkitUrl,platformUrl,token}}async function connectAdmin(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);return{client:new AdminCashuClient({endpoint:await resolveAdminEndpoint(handle2,opts.endpoint,selected),dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey}),dvmId:canonicalDvmId}}async function adminCall(fn){try{return await fn()}catch(err){rethrowIfBadSignature(err);if(err instanceof AdminApiError){throw new DvmError9(err.body.error,err.body.message??err.message,err.body.hint,{http_status:err.status,...err.body.reason?{reason:err.body.reason}:{}},err.body.display,providerProseSources3(err.body,{message:err.message}))}throw err}}async function resolveAdminEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError9("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}var MAX_LIMIT=200;async function blockedInvoices(handle2,opts){const limit=resolveLimit(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getBlockedInvoices({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{}}));const unresolved=res.invoices.filter(i=>i.status==="blocked");if(opts.human){if(res.invoices.length===0){info("No blocked Lightning funding invoices. Nothing to reconcile.");return}for(const invoice of res.invoices){raw(`${invoice.payment_hash} ${formatAmount(invoice)} ${invoice.status} ${invoice.blocked_reason??"-"} credit=${invoice.credit_id} fund=${invoice.fund_id}`)}info(`${unresolved.length} awaiting a decision. Credit one with \`dvmctl credit reconcile ${handle2} <payment-hash> --credit-id <id>\`.`);if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_blocked",handle:handle2,dvmId,slug:handle2,invoices:res.invoices,blocked_count:unresolved.length,...res.next_after?{next_after:res.next_after}:{},display:unresolved.length===0?"No Lightning payments are waiting on an operator decision.":`${unresolved.length} Lightning payment(s) landed in this DVM's wallet with no caller balance to put them on.`,hint:"Credit one onto a credit the caller holds with `dvmctl credit reconcile <handle> <payment-hash> --credit-id <id>`, or close it with `dvmctl credit write-off`."})}async function reconcileInvoice(handle2,paymentHash,opts){const creditId=opts.creditId;if(!creditId){throw new DvmError10("missing_credit_id","No target credit named.","Pass `--credit-id <id>`. Which credit this payment should land on is not derivable from the failed row \u2014 `dvmctl credit blocked` shows the one it was minted for, and `dvm credit list` (as the caller) shows what they hold.")}const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileInvoice({payment_hash:paymentHash,credit_id:creditId}));if(opts.human){info(res.replayed?`Already credited to ${res.credit_id} \u2014 nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}.`);return}json({op:"credit_reconcile",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,credit_id:res.credit_id,fund_id:res.fund_id,amount_micro:res.amount_micro,currency:res.currency,replayed:res.replayed,...res.credit?{credit:res.credit}:{},display:res.replayed?`This payment was already credited to ${res.credit_id}. Nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}. The caller can spend it now.`,hint:"The deposit is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function writeOffInvoice(handle2,paymentHash,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffInvoice({payment_hash:paymentHash,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (${res.payment_hash})${res.note?` \u2014 note on record: "${res.note}"`:""}. Nothing changed.`:`Wrote off ${formatMicro(res.amount_micro,res.currency)} (${res.payment_hash}). No balance was created.`);return}json({op:"credit_write_off",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,amount_micro:res.amount_micro,currency:res.currency,note:res.note,replayed:res.replayed,display:res.replayed?`This payment was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:`Closed this payment without crediting it. The ${formatMicro(res.amount_micro,res.currency)} stays in your wallet and no caller balance was created for it.`,hint:"Reversible: `dvmctl credit reconcile` still accepts a written-off invoice if you change your mind."})}function resolveLimit(limit){if(limit===void 0)return 50;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT){throw new DvmError10("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT} (got ${limit}).`,"Leave it unset for the default page of 50, then follow `next_after` for the rest.")}return limit}function formatAmount(invoice){return`${formatMicro(invoice.amount_micro,invoice.currency)} (${invoice.amount_msats} msats)`}function formatMicro(amountMicro,currency){return`${(amountMicro/1e6).toFixed(6)} ${currency.toUpperCase()}`}import{lstatSync,mkdirSync as mkdirSync4,writeFileSync as writeFileSync4}from"fs";import{basename,join as join5,resolve as resolve2}from"path";import{createInterface}from"readline";import{DvmError as DvmError11}from"@dvmkit/sdk/internal";import{existsSync as existsSync4,readFileSync as readFileSync4}from"fs";import{dirname,join as join4}from"path";import{fileURLToPath}from"url";var CLI_VERSION=readVersion();function readVersion(){let dir=dirname(fileURLToPath(import.meta.url));for(;;){const candidate=join4(dir,"package.json");if(existsSync4(candidate)){const{version:version4}=JSON.parse(readFileSync4(candidate,"utf8"));return version4}const parent=dirname(dir);if(parent===dir){throw new Error(`tracked change: no package.json found walking up from ${import.meta.url}`)}dir=parent}}function packageJsonTemplate(opts){const pkg={name:opts.dirName,version:"0.1.0",private:true,type:"module",engines:{node:"^22.12.0 || ^24.0.0 || >=26.0.0"},scripts:{test:"vitest run",typecheck:"tsc --noEmit",dev:"dvmctl dev handler.ts",deploy:"dvmctl deploy"},dependencies:{"@dvmkit/sdk":"0.1.5-rc.8",pg:"^8"},devDependencies:{"@types/node":"^22",vitest:"5.0.1",typescript:"^5"}};return JSON.stringify(pkg,null,2)+"\n"}function tsconfigTemplate(){const config2={compilerOptions:{target:"ES2024",module:"ES2022",moduleResolution:"bundler",isolatedModules:true,strict:true,skipLibCheck:true,outDir:"dist"},include:["*.ts"],exclude:["*.test.ts"]};return JSON.stringify(config2,null,2)+"\n"}function envExampleTemplate(){return`# Local settings for \`dvmctl dev\`. Copy this file to \`.env\`.
2139
+ Authenticated as ${data.account}`)}else{console.log(JSON.stringify({authenticated:true,account:data.account}))}return}}console.error("\nAuthentication timed out. Try again or use DVMKIT_API_KEY env var.");process.exit(1)}function sleep(ms){return new Promise(resolve8=>setTimeout(resolve8,ms))}init_output();import{DvmError as DvmError10}from"@dvmkit/sdk/internal";import{DvmError as DvmError9,providerProseSources as providerProseSources3}from"@dvmkit/sdk/internal";import{ADMIN_STATE_UNAVAILABLE,buildAdminAuthHeader,DvmError as DvmError3}from"@dvmkit/sdk/internal";var AdminCashuClient=class{endpoint;dvmId;privkeyHex;fetchImpl;now;constructor(opts){this.endpoint=opts.endpoint.replace(/\/+$/,"");this.dvmId=opts.dvmId;this.privkeyHex=opts.privkeyHex;this.fetchImpl=opts.fetchImpl??globalThis.fetch.bind(globalThis);this.now=opts.now??Date.now}async getPendingBatches(){return this.requestJson("GET","/admin/cashu/pending-batches",void 0)}async markMelted(body){return this.requestJson("POST","/admin/cashu/mark-melted",body)}async rotateLockPubkey(body){return this.requestJson("POST","/admin/cashu/rotate-lock-pubkey",body)}async getPendingDrains(){return this.requestJson("GET","/admin/credit/pending-drains",void 0)}async getBitcoinLiability(){return this.requestJson("GET","/admin/credit/bitcoin-liability",void 0)}async parkDrain(body){return this.requestJson("POST","/admin/credit/park-drain",body)}async markDrainSent(body){return this.requestJson("POST","/admin/credit/mark-drain-sent",body)}async getBlockedInvoices(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/blocked-invoices",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileInvoice(body){return this.requestJson("POST","/admin/credit/reconcile-invoice",body)}async writeOffInvoice(body){return this.requestJson("POST","/admin/credit/write-off-invoice",body)}async getX402Settlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/x402-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402Settlement(body){return this.requestJson("POST","/admin/credit/reconcile-x402-settlement",body)}async writeOffX402Settlement(body){return this.requestJson("POST","/admin/credit/write-off-x402-settlement",body)}async getX402ExactSettlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/x402-exact-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402ExactSettlement(settlementIntentId){return this.requestJson("POST","/admin/credit/reconcile-x402-exact-settlement",{settlement_intent_id:settlementIntentId})}async getTempoDrains(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/tempo-drains",void 0,params.size===0?void 0:`?${params.toString()}`)}async getTempoCreditLosses(opts){const query=opts?.limit===void 0?void 0:`?limit=${String(opts.limit)}`;return this.requestJson("GET","/admin/credit/tempo-losses",void 0,query)}async reconcileTempoDrain(body){return this.requestJson("POST","/admin/credit/reconcile-tempo-drain",body)}async writeOffTempoDrain(body){return this.requestJson("POST","/admin/credit/write-off-tempo-drain",body)}async requestJson(method,path,body,query){const{header}=buildAdminAuthHeader({method,path,dvmId:this.dvmId,privkeyHex:this.privkeyHex,now:this.now});const init2={method,headers:{Authorization:header,...body===void 0?{}:{"Content-Type":"application/json"}},...body===void 0?{}:{body:JSON.stringify(body)}};let res;try{res=await this.fetchImpl(`${this.endpoint}${path}${query??""}`,init2)}catch(err){throw new DvmError3("admin_unreachable",`Failed to reach DVM admin endpoint ${this.endpoint}${path}: ${err instanceof Error?err.message:String(err)}`,"The DVM may be down or the endpoint URL is wrong. Verify with `dvmctl info <handle>`.")}const text=await res.text();let parsed;try{parsed=text.length===0?{}:JSON.parse(text)}catch{throw new DvmError3("admin_invalid_json",`DVM admin endpoint ${path} returned non-JSON (HTTP ${res.status}): ${text.slice(0,200)}`,"The DVM may not be running the tracked change SDK. Redeploy with the latest SDK.",void 0,void 0,{message:"provider"})}if(!res.ok){const body2=parsed;const apiError=new AdminApiError(res.status,{error:typeof body2.error==="string"?body2.error:"admin_error",reason:typeof body2.reason==="string"?body2.reason:void 0,message:typeof body2.message==="string"?body2.message:void 0,hint:typeof body2.hint==="string"?body2.hint:void 0,display:typeof body2.display==="string"?body2.display:void 0,raw:parsed});if(res.status===503&&apiError.body.error===ADMIN_STATE_UNAVAILABLE){throw new DvmError3(ADMIN_STATE_UNAVAILABLE,"DVM admin authentication state is temporarily unavailable; the requested DVM operation did not run.","Retry the same command. If this keeps happening, inspect the DVM's database health and restart the DVM. Do not rotate or replace the lock key.")}throw apiError}return parsed}};var AdminApiError=class extends Error{constructor(status2,body){super(`Admin endpoint returned HTTP ${status2}: ${body.error}${body.reason?` (${body.reason})`:""}`);this.status=status2;this.body=body;this.name="AdminApiError"}status;body;get isBadSignature(){return this.status===401&&this.body.reason==="bad_signature"}};function rethrowIfBadSignature(err){if(!(err instanceof AdminApiError)||!err.isBadSignature)return;throw new DvmError3("admin_bad_signature","DVM admin endpoint rejected our signed challenge (bad_signature).","The DVM's DVMKIT_CASHU_LOCK_PUBKEY does not match the privkey derived from this mnemonic. Re-deploy with the matching pubkey or switch mnemonics.")}function rethrowIfAdminStateUnavailable(err){if(err instanceof DvmError3&&err.code===ADMIN_STATE_UNAVAILABLE)throw err}init_builder_lock_identity();import{DvmError as DvmError8}from"@dvmkit/sdk/internal";async function resolveDvm(slug,opts={}){const{token,platformUrl}=requireAuth();const{dvms,org}=await listDvms(platformUrl,token,{org:resolveOrgScope(opts)});const dvm=dvms.find(candidate=>candidate.slug===slug);if(!dvm){throw new DvmError8("not_found",`DVM "${slug}" not found in ${scopeLabel(org)}.`,"Run `dvmctl list` to see the DVMs available in this context.")}return{dvmId:dvm.id,dvmkitUrl:dvm.dvmkitUrl,platformUrl,token}}async function connectAdmin(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);return{client:new AdminCashuClient({endpoint:await resolveAdminEndpoint(handle2,opts.endpoint,selected),dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey}),dvmId:canonicalDvmId}}async function adminCall(fn){try{return await fn()}catch(err){rethrowIfBadSignature(err);if(err instanceof AdminApiError){throw new DvmError9(err.body.error,err.body.message??err.message,err.body.hint,{http_status:err.status,...err.body.reason?{reason:err.body.reason}:{}},err.body.display,providerProseSources3(err.body,{message:err.message}))}throw err}}async function resolveAdminEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError9("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}var MAX_LIMIT=200;async function blockedInvoices(handle2,opts){const limit=resolveLimit(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getBlockedInvoices({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{}}));const unresolved=res.invoices.filter(i=>i.status==="blocked");if(opts.human){if(res.invoices.length===0){info("No blocked Lightning funding invoices. Nothing to reconcile.");return}for(const invoice of res.invoices){raw(`${invoice.payment_hash} ${formatAmount(invoice)} ${invoice.status} ${invoice.blocked_reason??"-"} credit=${invoice.credit_id} fund=${invoice.fund_id}`)}info(`${unresolved.length} awaiting a decision. Credit one with \`dvmctl credit reconcile ${handle2} <payment-hash> --credit-id <id>\`.`);if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_blocked",handle:handle2,dvmId,slug:handle2,invoices:res.invoices,blocked_count:unresolved.length,...res.next_after?{next_after:res.next_after}:{},display:unresolved.length===0?"No Lightning payments are waiting on an operator decision.":`${unresolved.length} Lightning payment(s) landed in this DVM's wallet with no caller balance to put them on.`,hint:"Credit one onto a credit the caller holds with `dvmctl credit reconcile <handle> <payment-hash> --credit-id <id>`, or close it with `dvmctl credit write-off`."})}async function reconcileInvoice(handle2,paymentHash,opts){const creditId=opts.creditId;if(!creditId){throw new DvmError10("missing_credit_id","No target credit named.","Pass `--credit-id <id>`. Which credit this payment should land on is not derivable from the failed row \u2014 `dvmctl credit blocked` shows the one it was minted for, and `dvm credit list` (as the caller) shows what they hold.")}const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileInvoice({payment_hash:paymentHash,credit_id:creditId}));if(opts.human){info(res.replayed?`Already credited to ${res.credit_id} \u2014 nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}.`);return}json({op:"credit_reconcile",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,credit_id:res.credit_id,fund_id:res.fund_id,amount_micro:res.amount_micro,currency:res.currency,replayed:res.replayed,...res.credit?{credit:res.credit}:{},display:res.replayed?`This payment was already credited to ${res.credit_id}. Nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}. The caller can spend it now.`,hint:"The deposit is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function writeOffInvoice(handle2,paymentHash,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffInvoice({payment_hash:paymentHash,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (${res.payment_hash})${res.note?` \u2014 note on record: "${res.note}"`:""}. Nothing changed.`:`Wrote off ${formatMicro(res.amount_micro,res.currency)} (${res.payment_hash}). No balance was created.`);return}json({op:"credit_write_off",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,amount_micro:res.amount_micro,currency:res.currency,note:res.note,replayed:res.replayed,display:res.replayed?`This payment was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:`Closed this payment without crediting it. The ${formatMicro(res.amount_micro,res.currency)} stays in your wallet and no caller balance was created for it.`,hint:"Reversible: `dvmctl credit reconcile` still accepts a written-off invoice if you change your mind."})}function resolveLimit(limit){if(limit===void 0)return 50;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT){throw new DvmError10("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT} (got ${limit}).`,"Leave it unset for the default page of 50, then follow `next_after` for the rest.")}return limit}function formatAmount(invoice){return`${formatMicro(invoice.amount_micro,invoice.currency)} (${invoice.amount_msats} msats)`}function formatMicro(amountMicro,currency){return`${(amountMicro/1e6).toFixed(6)} ${currency.toUpperCase()}`}import{lstatSync,mkdirSync as mkdirSync4,writeFileSync as writeFileSync4}from"fs";import{basename,join as join5,resolve as resolve2}from"path";import{createInterface}from"readline";import{DvmError as DvmError11}from"@dvmkit/sdk/internal";import{existsSync as existsSync4,readFileSync as readFileSync4}from"fs";import{dirname,join as join4}from"path";import{fileURLToPath}from"url";var CLI_VERSION=readVersion();function readVersion(){let dir=dirname(fileURLToPath(import.meta.url));for(;;){const candidate=join4(dir,"package.json");if(existsSync4(candidate)){const{version:version4}=JSON.parse(readFileSync4(candidate,"utf8"));return version4}const parent=dirname(dir);if(parent===dir){throw new Error(`tracked change: no package.json found walking up from ${import.meta.url}`)}dir=parent}}function packageJsonTemplate(opts){const pkg={name:opts.dirName,version:"0.1.0",private:true,type:"module",engines:{node:"^22.12.0 || ^24.0.0 || >=26.0.0"},scripts:{test:"vitest run",typecheck:"tsc --noEmit",dev:"dvmctl dev handler.ts",deploy:"dvmctl deploy"},dependencies:{"@dvmkit/sdk":"0.1.5-rc.9",pg:"^8"},devDependencies:{"@types/node":"^22",vitest:"5.0.1",typescript:"^5"}};return JSON.stringify(pkg,null,2)+"\n"}function tsconfigTemplate(){const config2={compilerOptions:{target:"ES2024",module:"ES2022",moduleResolution:"bundler",isolatedModules:true,strict:true,skipLibCheck:true,outDir:"dist"},include:["*.ts"],exclude:["*.test.ts"]};return JSON.stringify(config2,null,2)+"\n"}function envExampleTemplate(){return`# Local settings for \`dvmctl dev\`. Copy this file to \`.env\`.
2140
2140
  # On deploy, set production secrets on the platform \u2014 they are not read from this file.
2141
2141
  # OPENAI_API_KEY=sk-...
2142
2142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvmkit/dvmctl",
3
- "version": "0.2.1-rc.1",
3
+ "version": "0.2.1-rc.2",
4
4
  "description": "Builder CLI for creating, testing, and deploying Digital Vending Machines",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "verify": "npm run build && npm run lint && npm run typecheck && npm test"
37
37
  },
38
38
  "dependencies": {
39
- "@dvmkit/sdk": "0.1.5-rc.8"
39
+ "@dvmkit/sdk": "0.1.5-rc.9"
40
40
  },
41
41
  "optionalDependencies": {
42
42
  "pg": "8.20.0",