@aeon-ai-pay/aigateway 0.1.5 → 0.2.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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Minimal JSON Schema validator for tools-catalog inputs schemas.
3
+ *
4
+ * Supports the subset of JSON Schema actually used in tools-catalog.json:
5
+ * - type: object | string | number | integer | boolean | array
6
+ * - required: string[]
7
+ * - properties: { fieldName: <fieldSchema> }
8
+ * - enum: any[]
9
+ * - minimum / maximum (number, integer)
10
+ * - items (for arrays)
11
+ * - oneOf: [schema, ...] (treated as "any-of" — passes if any branch matches)
12
+ *
13
+ * We intentionally avoid pulling in ajv to keep the CLI dep tree small.
14
+ *
15
+ * @returns {{ ok: boolean, errors: Array<{ field: string, message: string, kind: "missing"|"type"|"enum"|"range"|"other" }> }}
16
+ */
17
+ export function validateInputs(inputs, schema) {
18
+ const errors = [];
19
+ if (!schema) return { ok: true, errors };
20
+
21
+ // root must be an object
22
+ if (schema.type === "object" || schema.properties || schema.required) {
23
+ if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
24
+ errors.push({ field: "(root)", kind: "type", message: "inputs must be a JSON object" });
25
+ return { ok: false, errors };
26
+ }
27
+ }
28
+
29
+ // Required fields
30
+ if (Array.isArray(schema.required)) {
31
+ for (const field of schema.required) {
32
+ const v = inputs[field];
33
+ if (v === undefined || v === null || (typeof v === "string" && v.trim() === "")) {
34
+ errors.push({ field, kind: "missing", message: `required field "${field}" is missing` });
35
+ }
36
+ }
37
+ }
38
+
39
+ // Per-field validation
40
+ if (schema.properties) {
41
+ for (const [field, fieldSchema] of Object.entries(schema.properties)) {
42
+ if (inputs[field] === undefined || inputs[field] === null) continue;
43
+ errors.push(...validateField(field, inputs[field], fieldSchema));
44
+ }
45
+ }
46
+
47
+ return { ok: errors.length === 0, errors };
48
+ }
49
+
50
+ function validateField(field, value, schema) {
51
+ const errors = [];
52
+
53
+ // oneOf: pass if any branch accepts (no errors).
54
+ if (Array.isArray(schema.oneOf)) {
55
+ const branchErrors = schema.oneOf.map((sub) => validateField(field, value, sub));
56
+ const anyPass = branchErrors.some((errs) => errs.length === 0);
57
+ if (!anyPass) {
58
+ errors.push({
59
+ field,
60
+ kind: "type",
61
+ message: `value does not match any of oneOf schemas (tried ${schema.oneOf.length} branches)`,
62
+ });
63
+ }
64
+ return errors;
65
+ }
66
+
67
+ // Type check
68
+ if (schema.type) {
69
+ if (!checkType(value, schema.type)) {
70
+ errors.push({
71
+ field,
72
+ kind: "type",
73
+ message: `expected type "${schema.type}", got "${actualType(value)}"`,
74
+ });
75
+ return errors; // bail out on type mismatch
76
+ }
77
+ }
78
+
79
+ // Enum
80
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
81
+ errors.push({
82
+ field,
83
+ kind: "enum",
84
+ message: `value ${JSON.stringify(value)} must be one of [${schema.enum.map((v) => JSON.stringify(v)).join(", ")}]`,
85
+ });
86
+ }
87
+
88
+ // Number range
89
+ if ((schema.type === "number" || schema.type === "integer") && typeof value === "number") {
90
+ if (schema.minimum != null && value < schema.minimum) {
91
+ errors.push({ field, kind: "range", message: `value ${value} is below minimum ${schema.minimum}` });
92
+ }
93
+ if (schema.maximum != null && value > schema.maximum) {
94
+ errors.push({ field, kind: "range", message: `value ${value} is above maximum ${schema.maximum}` });
95
+ }
96
+ }
97
+
98
+ // Array items
99
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
100
+ value.forEach((item, i) => {
101
+ errors.push(...validateField(`${field}[${i}]`, item, schema.items));
102
+ });
103
+ }
104
+
105
+ return errors;
106
+ }
107
+
108
+ function checkType(value, type) {
109
+ switch (type) {
110
+ case "object": return typeof value === "object" && value !== null && !Array.isArray(value);
111
+ case "array": return Array.isArray(value);
112
+ case "string": return typeof value === "string";
113
+ case "number": return typeof value === "number" && !Number.isNaN(value);
114
+ case "integer": return typeof value === "number" && Number.isInteger(value);
115
+ case "boolean": return typeof value === "boolean";
116
+ case "null": return value === null;
117
+ default: return true;
118
+ }
119
+ }
120
+
121
+ function actualType(value) {
122
+ if (value === null) return "null";
123
+ if (Array.isArray(value)) return "array";
124
+ return typeof value;
125
+ }
package/src/output.mjs CHANGED
@@ -33,7 +33,7 @@ export function isVerboseMode() { return VERBOSE_MODE; }
33
33
  /**
34
34
  * Emit a success result. Callers should let the function return naturally
35
35
  * (do not call process.exit afterwards).
36
- * @param {string} command - command name, e.g. "create-card" / "wallet-init"
36
+ * @param {string} command - command name, e.g. "sb-invoke" / "wallet-init"
37
37
  * @param {object} data - placed under envelope.data
38
38
  * @param {object} [legacyShape] - the legacy-mode payload; if omitted, `data` is used
39
39
  */
@@ -0,0 +1,264 @@
1
+ /**
2
+ * tools-download: shared binary download + metadata helpers used by
3
+ * sb-invoke (any AI tool returning image / video / audio URLs).
4
+ *
5
+ * Public API:
6
+ * - extractOutputs(rawResponseData) → { kind, items }
7
+ * Walks the upstream tool response and returns a normalized list of
8
+ * downloadable URLs along with the inferred media kind ("image" | "video"
9
+ * | "audio" | null).
10
+ * - resolveOutputDir(opts.output, kind) → absolute path
11
+ * Per-kind default: ~/aigateway-{kind}s/, override with `--output`.
12
+ * - downloadOutputs(items, dir) → DownloadedItem[]
13
+ * Downloads every item to `dir`, parses metadata for known image formats.
14
+ * - DEFAULT_DIRS, downloadFile, readImageMeta, humanSize
15
+ * Exposed for advanced reuse / tests.
16
+ */
17
+ import { mkdirSync, createWriteStream, existsSync, unlinkSync, openSync, readSync, closeSync, statSync } from "node:fs";
18
+ import { join, basename, extname } from "node:path";
19
+ import { homedir } from "node:os";
20
+ import { URL } from "node:url";
21
+ import { get as httpsGet } from "node:https";
22
+ import { get as httpGet } from "node:http";
23
+
24
+ export const DEFAULT_DIRS = {
25
+ image: join(homedir(), "aigateway-images"),
26
+ video: join(homedir(), "aigateway-videos"),
27
+ audio: join(homedir(), "aigateway-audio"),
28
+ unknown: join(homedir(), "aigateway-downloads"),
29
+ };
30
+
31
+ const IMAGE_EXT = new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp", "svg"]);
32
+ const VIDEO_EXT = new Set(["mp4", "webm", "mov", "mkv", "avi", "m4v"]);
33
+ const AUDIO_EXT = new Set(["mp3", "wav", "ogg", "flac", "m4a", "aac", "opus"]);
34
+
35
+ function extFromUrl(url) {
36
+ try {
37
+ const p = new URL(url).pathname;
38
+ const e = extname(p).replace(/^\./, "").toLowerCase();
39
+ return e || null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function kindFromExt(ext) {
46
+ if (!ext) return null;
47
+ if (IMAGE_EXT.has(ext)) return "image";
48
+ if (VIDEO_EXT.has(ext)) return "video";
49
+ if (AUDIO_EXT.has(ext)) return "audio";
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Normalize an upstream tool response into a list of downloadable items.
55
+ * Tries the common locations: data.images[], data.video.url, data.audio.url,
56
+ * data.url, data.output_url, plus raw top-level fallbacks.
57
+ */
58
+ export function extractOutputs(responseData) {
59
+ const items = [];
60
+ let kind = null;
61
+
62
+ const inner = responseData?.data ?? responseData;
63
+
64
+ if (Array.isArray(inner?.images)) {
65
+ for (const img of inner.images) {
66
+ if (img?.url) items.push({ url: img.url });
67
+ }
68
+ if (items.length) kind = "image";
69
+ }
70
+
71
+ const pushSingle = (url, inferred) => {
72
+ if (!url || typeof url !== "string") return;
73
+ items.push({ url });
74
+ if (!kind && inferred) kind = inferred;
75
+ };
76
+
77
+ if (!items.length) {
78
+ pushSingle(inner?.video?.url, "video");
79
+ pushSingle(inner?.video_url, "video");
80
+ }
81
+ if (!items.length) {
82
+ pushSingle(inner?.audio?.url, "audio");
83
+ pushSingle(inner?.audio_url, "audio");
84
+ }
85
+ if (!items.length) {
86
+ const generic = inner?.url || inner?.output_url || inner?.file_url || inner?.image_url;
87
+ if (typeof generic === "string") {
88
+ const k = kindFromExt(extFromUrl(generic));
89
+ pushSingle(generic, k);
90
+ }
91
+ }
92
+ if (!items.length && Array.isArray(inner?.outputs)) {
93
+ for (const o of inner.outputs) {
94
+ const u = typeof o === "string" ? o : o?.url;
95
+ if (typeof u === "string") {
96
+ items.push({ url: u });
97
+ const k = kindFromExt(extFromUrl(u));
98
+ if (!kind && k) kind = k;
99
+ }
100
+ }
101
+ }
102
+
103
+ if (!kind && items.length) {
104
+ const first = items[0]?.url;
105
+ kind = kindFromExt(extFromUrl(first)) ?? null;
106
+ }
107
+
108
+ return { kind, items };
109
+ }
110
+
111
+ export function resolveOutputDir(userOverride, kind) {
112
+ if (userOverride) return userOverride;
113
+ return DEFAULT_DIRS[kind] ?? DEFAULT_DIRS.unknown;
114
+ }
115
+
116
+ /**
117
+ * Download every item to `dir`, parse image metadata where possible.
118
+ * Returns an array of { url, localPath, format?, width?, height?, sizeBytes, sizeHuman }
119
+ * with `error` populated on per-item failures (the loop never throws).
120
+ */
121
+ export async function downloadOutputs(items, dir) {
122
+ if (!items.length) return [];
123
+ mkdirSync(dir, { recursive: true });
124
+ const out = [];
125
+ for (const item of items) {
126
+ if (!item?.url) continue;
127
+ try {
128
+ const localPath = await downloadFile(item.url, dir);
129
+ const meta = readImageMeta(localPath);
130
+ out.push({
131
+ url: item.url,
132
+ localPath,
133
+ format: meta.format,
134
+ width: meta.width,
135
+ height: meta.height,
136
+ sizeBytes: meta.sizeBytes,
137
+ sizeHuman: meta.sizeHuman,
138
+ });
139
+ } catch (e) {
140
+ out.push({ url: item.url, error: e.message });
141
+ }
142
+ }
143
+ return out;
144
+ }
145
+
146
+ export function readImageMeta(filePath) {
147
+ const sizeBytes = statSync(filePath).size;
148
+ const sizeHuman = humanSize(sizeBytes);
149
+ let format = null, width = null, height = null;
150
+ const fd = openSync(filePath, "r");
151
+ try {
152
+ const buf = Buffer.alloc(64 * 1024);
153
+ const len = readSync(fd, buf, 0, buf.length, 0);
154
+ if (len >= 24 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) {
155
+ format = "png";
156
+ width = buf.readUInt32BE(16);
157
+ height = buf.readUInt32BE(20);
158
+ } else if (len >= 4 && buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) {
159
+ format = "jpeg";
160
+ let i = 2;
161
+ while (i + 9 < len) {
162
+ if (buf[i] !== 0xFF) { i++; continue; }
163
+ while (i < len && buf[i] === 0xFF) i++;
164
+ const marker = buf[i];
165
+ i++;
166
+ if (marker === 0xD8 || marker === 0xD9) continue;
167
+ const segLen = buf.readUInt16BE(i);
168
+ if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) {
169
+ height = buf.readUInt16BE(i + 3);
170
+ width = buf.readUInt16BE(i + 5);
171
+ break;
172
+ }
173
+ i += segLen;
174
+ }
175
+ } else if (len >= 30 && buf.slice(0, 4).toString("ascii") === "RIFF" && buf.slice(8, 12).toString("ascii") === "WEBP") {
176
+ format = "webp";
177
+ const fourCC = buf.slice(12, 16).toString("ascii");
178
+ if (fourCC === "VP8 ") {
179
+ width = buf.readUInt16LE(26) & 0x3FFF;
180
+ height = buf.readUInt16LE(28) & 0x3FFF;
181
+ } else if (fourCC === "VP8L") {
182
+ const b0 = buf[21], b1 = buf[22], b2 = buf[23], b3 = buf[24];
183
+ width = ((b1 & 0x3F) << 8 | b0) + 1;
184
+ height = ((b3 & 0x0F) << 10 | b2 << 2 | (b1 & 0xC0) >> 6) + 1;
185
+ } else if (fourCC === "VP8X") {
186
+ width = (buf[24] | (buf[25] << 8) | (buf[26] << 16)) + 1;
187
+ height = (buf[27] | (buf[28] << 8) | (buf[29] << 16)) + 1;
188
+ }
189
+ }
190
+ } catch {
191
+ // leave null on parse failure
192
+ } finally {
193
+ closeSync(fd);
194
+ }
195
+ return { format, width, height, sizeBytes, sizeHuman };
196
+ }
197
+
198
+ export function humanSize(bytes) {
199
+ if (!Number.isFinite(bytes)) return "?";
200
+ if (bytes < 1024) return `${bytes} B`;
201
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
202
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
203
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
204
+ }
205
+
206
+ export function downloadFile(fileUrl, outputDir, { maxRedirects = 5, timeoutMs = 60_000 } = {}) {
207
+ let filename;
208
+ try {
209
+ filename = basename(new URL(fileUrl).pathname) || `download-${Date.now()}`;
210
+ } catch {
211
+ filename = `download-${Date.now()}`;
212
+ }
213
+ if (!extname(filename)) filename += ".bin";
214
+
215
+ let target = join(outputDir, filename);
216
+ if (existsSync(target)) {
217
+ const ext = extname(filename);
218
+ const stem = filename.slice(0, filename.length - ext.length);
219
+ let i = 1;
220
+ while (existsSync(join(outputDir, `${stem}-${i}${ext}`))) i++;
221
+ target = join(outputDir, `${stem}-${i}${ext}`);
222
+ }
223
+
224
+ return new Promise((resolve, reject) => {
225
+ const fetchOnce = (currentUrl, redirectsLeft) => {
226
+ let parsed;
227
+ try {
228
+ parsed = new URL(currentUrl);
229
+ } catch (e) {
230
+ return reject(new Error(`Invalid URL: ${currentUrl}`));
231
+ }
232
+ const httpModule = parsed.protocol === "http:" ? httpGet : httpsGet;
233
+ const req = httpModule(currentUrl, { timeout: timeoutMs }, (res) => {
234
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
235
+ res.resume();
236
+ if (redirectsLeft <= 0) return reject(new Error("Too many redirects"));
237
+ const nextUrl = new URL(res.headers.location, currentUrl).toString();
238
+ return fetchOnce(nextUrl, redirectsLeft - 1);
239
+ }
240
+ if (res.statusCode !== 200) {
241
+ res.resume();
242
+ return reject(new Error(`HTTP ${res.statusCode} from ${currentUrl}`));
243
+ }
244
+ const file = createWriteStream(target);
245
+ res.pipe(file);
246
+ file.on("finish", () => file.close(() => resolve(target)));
247
+ file.on("error", (err) => {
248
+ try { unlinkSync(target); } catch {}
249
+ reject(err);
250
+ });
251
+ res.on("error", (err) => {
252
+ file.destroy();
253
+ try { unlinkSync(target); } catch {}
254
+ reject(err);
255
+ });
256
+ });
257
+ req.on("error", reject);
258
+ req.on("timeout", () => {
259
+ req.destroy(new Error(`Download timed out after ${timeoutMs}ms`));
260
+ });
261
+ };
262
+ fetchOnce(fileUrl, maxRedirects);
263
+ });
264
+ }
@@ -1,23 +1,26 @@
1
1
  /**
2
- * Auto version check + silent background upgrade.
2
+ * Synchronous version check + foreground upgrade.
3
3
  *
4
- * Strategy:
5
- * 1. Synchronously poll the npm registry (`npm view`) — print a notice when a newer version is found.
6
- * 2. Spawn a detached background process to run `npm install -g`.
7
- * 3. After install, run postinstall.mjs (which re-installs the skill into every detected tool via the skills CLI).
8
- * Does not block the main process.
4
+ * Why foreground (not background-detached): a detached `npm install -g` mid-command
5
+ * can leave the globally installed package in a half-replaced state `bin/cli.mjs`
6
+ * may already be the new version while `src/commands/*` is still the old one (or
7
+ * vice versa), causing `ERR_MODULE_NOT_FOUND` on the very next invocation.
8
+ *
9
+ * Synchronous upgrade keeps the package consistent:
10
+ * - upgrade succeeds → exit with UPDATE_APPLIED so the caller (or agent) reruns
11
+ * the previous command on the new version
12
+ * - upgrade fails → log the failure and continue on the current version
13
+ *
14
+ * Output on upgrade: an envelope-shaped error so agents can detect / handle it
15
+ * uniformly via `envelope.error.code === "UPDATE_APPLIED"`.
9
16
  */
10
-
11
- import { execFileSync, spawn } from "node:child_process";
17
+ import { execFileSync } from "node:child_process";
18
+ import { join } from "node:path";
19
+ import { emitErr } from "./output.mjs";
12
20
 
13
21
  const PKG_NAME = "@aeon-ai-pay/aigateway";
14
22
 
15
- /**
16
- * Called at CLI startup: synchronous version probe + detached upgrade.
17
- * @param {string} currentVersion
18
- */
19
23
  export function checkForUpdates(currentVersion) {
20
- // Synchronous fast probe (short timeout so it does not block too long)
21
24
  let latest;
22
25
  try {
23
26
  latest = execFileSync("npm", ["view", PKG_NAME, "version"], {
@@ -25,45 +28,45 @@ export function checkForUpdates(currentVersion) {
25
28
  stdio: ["ignore", "pipe", "ignore"],
26
29
  }).toString().trim();
27
30
  } catch {
28
- return; // network unavailable — silently skip
31
+ return; // no network / npm unavailable — silently keep going
29
32
  }
30
33
 
31
34
  if (!latest || latest === currentVersion) return;
32
35
 
33
- // A newer version exists print a notice
34
- console.error(`[update] ${PKG_NAME} ${currentVersion} → ${latest}, upgrading in background...`);
36
+ console.error(`[update] ${PKG_NAME} ${currentVersion} ${latest}, upgrading (foreground)...`);
37
+
38
+ try {
39
+ execFileSync("npm", ["install", "-g", `${PKG_NAME}@${latest}`], {
40
+ timeout: 120000,
41
+ stdio: ["ignore", "inherit", "inherit"],
42
+ });
43
+ } catch (e) {
44
+ console.error(`[update] Upgrade failed: ${(e && e.message) || e}. Continuing on ${currentVersion}.`);
45
+ return;
46
+ }
47
+
48
+ // Re-run the new version's postinstall so the SKILL.md copies in
49
+ // ~/.claude/skills/, .cursor/rules/, etc. get refreshed too.
50
+ try {
51
+ const root = execFileSync("npm", ["root", "-g"], {
52
+ timeout: 10000,
53
+ stdio: ["ignore", "pipe", "ignore"],
54
+ }).toString().trim();
55
+ const postinstall = join(root, PKG_NAME, "scripts", "postinstall.mjs");
56
+ execFileSync("node", [postinstall], {
57
+ timeout: 30000,
58
+ stdio: ["ignore", "inherit", "inherit"],
59
+ });
60
+ } catch (e) {
61
+ console.error(`[update] postinstall failed: ${(e && e.message) || e}`);
62
+ }
35
63
 
36
- // Run the upgrade in a detached child, writing the result to a log file
37
- const script = `
38
- const { execFileSync } = require("child_process");
39
- const { join } = require("path");
40
- const { appendFileSync, mkdirSync } = require("fs");
41
- const { homedir } = require("os");
42
- const pkg = ${JSON.stringify(PKG_NAME)};
43
- const ver = ${JSON.stringify(latest)};
44
- const logDir = join(homedir(), ".aigateway");
45
- const logFile = join(logDir, "update.log");
46
- function log(msg) {
47
- try {
48
- mkdirSync(logDir, { recursive: true });
49
- appendFileSync(logFile, new Date().toISOString() + " " + msg + "\\n");
50
- } catch {}
51
- }
52
- try {
53
- log("Upgrading " + pkg + " to " + ver + "...");
54
- execFileSync("npm", ["install", "-g", pkg + "@" + ver], { timeout: 120000 });
55
- const root = execFileSync("npm", ["root", "-g"], { timeout: 10000 }).toString().trim();
56
- const postinstall = join(root, pkg, "scripts", "postinstall.mjs");
57
- execFileSync("node", [postinstall], { timeout: 30000 });
58
- log("Upgrade to " + ver + " succeeded.");
59
- } catch (e) {
60
- log("Upgrade to " + ver + " failed: " + (e.message || e));
61
- }
62
- `;
64
+ console.error(`[update] Upgraded to ${latest}. Please rerun the previous command on the new version.`);
63
65
 
64
- const child = spawn("node", ["-e", script], {
65
- stdio: "ignore",
66
- detached: true,
66
+ // Emit the envelope so agents can detect it programmatically, then exit.
67
+ emitErr("update-check", "UPDATE_APPLIED", {
68
+ message: `Upgraded ${PKG_NAME} ${currentVersion} → ${latest}. Rerun the previous command.`,
69
+ from: currentVersion,
70
+ to: latest,
67
71
  });
68
- child.unref();
69
72
  }
package/src/x402.mjs CHANGED
@@ -70,7 +70,7 @@ export function createX402Api(privateKey) {
70
70
  * sign manually later.
71
71
  * Field names follow the x402 v2 PaymentRequirements standard: asset, payTo, amount.
72
72
  *
73
- * Supports both GET (card path) and POST (image / Skill Boss path).
73
+ * Supports both GET and POST entry points.
74
74
  *
75
75
  * @param {string} url
76
76
  * @param {{ method?: "GET"|"POST", data?: any, headers?: object }} [options]
@@ -1,68 +0,0 @@
1
- # Check Card Status
2
-
3
- ## Command
4
-
5
- ```bash
6
- # Single query
7
- npx @aeon-ai-pay/aigateway create-card-status --order-no <orderNo>
8
-
9
- # Poll until terminal status (SUCCESS or FAIL)
10
- npx @aeon-ai-pay/aigateway create-card-status --order-no <orderNo> --poll
11
- ```
12
-
13
- ## Response Format
14
-
15
- ```json
16
- {
17
- "code": "0",
18
- "msg": "success",
19
- "model": {
20
- "orderNo": "300217748668047431791",
21
- "orderStatus": "SUCCESS",
22
- "channelStatus": "COMPLETED",
23
- "orderAmount": 0.6,
24
- "txHash": "0xabc...def",
25
- "cardLastFour": "4321",
26
- "cardBin": "485932",
27
- "cardScheme": "VISA",
28
- "cardBalance": 0.6,
29
- "cardStatus": "ACTIVE"
30
- }
31
- }
32
- ```
33
-
34
- ## Status Values
35
-
36
- ### orderStatus (Order Status)
37
-
38
- | Status | Meaning | Action |
39
- |------|------|------|
40
- | `INIT` | Order created, not yet paid | Wait |
41
- | `PENDING` | Payment submitted, awaiting on-chain confirmation | Continue polling |
42
- | `SUCCESS` | Card created successfully | Show card details |
43
- | `FAIL` | Failed | Show error, suggest retry |
44
-
45
- ### channelStatus (Channel Status)
46
-
47
- | Status | Meaning |
48
- |------|------|
49
- | `INIT` | Not yet sent to card provider |
50
- | `PROCESSING` | Provider is creating the card |
51
- | `COMPLETED` | Card is ready |
52
- | `FAILED` | Card creation failed |
53
-
54
- ### cardStatus (Card Status)
55
-
56
- | Status | Meaning |
57
- |------|------|
58
- | `PENDING` | Being provisioned |
59
- | `ACTIVE` | Ready to use |
60
- | `FROZEN` | Frozen |
61
- | `CANCELLED` | Cancelled |
62
-
63
- ## Polling Behavior
64
-
65
- With `--poll`:
66
- - Up to **42 attempts** (first 5 at **2-second** intervals, then every **5 seconds**)
67
- - Stops on `SUCCESS`, `FAIL`, or `cardStatus=ACTIVE`
68
- - If timed out, notify user and provide manual query command