@apiosk/mcp 1.7.0 → 2.0.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.
Files changed (100) hide show
  1. package/README.md +83 -612
  2. package/assets/brand/apiosk-a-20260921.png +0 -0
  3. package/assets/brand/apple-touch-icon.png +0 -0
  4. package/assets/brand/favicon-dark.ico +0 -0
  5. package/assets/brand/favicon-light.ico +0 -0
  6. package/assets/brand/favicon.ico +0 -0
  7. package/assets/brand/icon-192.png +0 -0
  8. package/assets/brand/icon-512.png +0 -0
  9. package/assets/brand/icon-maskable-512.png +0 -0
  10. package/assets/brand/inter-OFL.txt +93 -0
  11. package/assets/brand/inter-latin-400-normal.woff2 +0 -0
  12. package/assets/brand/inter-latin-500-normal.woff2 +0 -0
  13. package/assets/brand/inter-latin-600-normal.woff2 +0 -0
  14. package/assets/brand/mark-20260905-transparent.svg +1 -0
  15. package/assets/brand/mark-20260918.svg +1 -0
  16. package/assets/brand/mark-black-20260918.svg +1 -0
  17. package/assets/brand/mark-dark-20260905-transparent.png +0 -0
  18. package/assets/brand/mark-dark-20260918.png +0 -0
  19. package/assets/brand/mark-dark-96.png +0 -0
  20. package/assets/brand/mark-light-20260905-transparent.png +0 -0
  21. package/assets/brand/mark-light-20260918.png +0 -0
  22. package/assets/brand/mark-light-96.png +0 -0
  23. package/assets/brand/wordmark-black-320.png +0 -0
  24. package/assets/brand/wordmark-white-320.png +0 -0
  25. package/docs/branding.md +11 -0
  26. package/docs/marketplace-submission-2026-04-08.md +4 -0
  27. package/docs/marketplace-submission-2026-08-20.md +157 -0
  28. package/docs/openai-plugin-submission-2026-09-05.md +143 -0
  29. package/dxt.json +26 -22
  30. package/index.mjs +3 -1
  31. package/logo-optimized-light.png +0 -0
  32. package/package.json +14 -14
  33. package/plugin/apiosk/.codex-plugin/plugin.json +45 -0
  34. package/plugin/apiosk/.mcp.json +8 -0
  35. package/plugin/apiosk/assets/icon-dark.png +0 -0
  36. package/plugin/apiosk/assets/icon.png +0 -0
  37. package/plugin/apiosk/assets/icon.svg +1 -0
  38. package/plugin/apiosk/assets/logo.png +0 -0
  39. package/plugin/apiosk/skills/apiosk/SKILL.md +33 -0
  40. package/plugin/apiosk/skills/apiosk/agents/openai.yaml +12 -0
  41. package/server.json +58 -24
  42. package/server.mjs +136 -151
  43. package/src/approval-feedback.mjs +21 -0
  44. package/src/brand-routes.mjs +28 -0
  45. package/src/create-server.mjs +160 -46
  46. package/src/display-money.mjs +20 -0
  47. package/src/display-text.mjs +67 -0
  48. package/src/gateway-client.mjs +42 -0
  49. package/src/gateway-v2-ask.mjs +50 -0
  50. package/src/gateway-v2-card-account.mjs +23 -0
  51. package/src/gateway-v2-card-actions.mjs +67 -0
  52. package/src/gateway-v2-card-answer-text.mjs +130 -0
  53. package/src/gateway-v2-card-answer.mjs +68 -0
  54. package/src/gateway-v2-card-blocks.mjs +166 -0
  55. package/src/gateway-v2-card-body.mjs +259 -0
  56. package/src/gateway-v2-card-budget.mjs +21 -0
  57. package/src/gateway-v2-card-cbs.mjs +56 -0
  58. package/src/gateway-v2-card-choices.mjs +4 -0
  59. package/src/gateway-v2-card-clarification.mjs +25 -0
  60. package/src/gateway-v2-card-compact.mjs +34 -0
  61. package/src/gateway-v2-card-events.mjs +41 -0
  62. package/src/gateway-v2-card-presentation.mjs +125 -0
  63. package/src/gateway-v2-card-research.mjs +63 -0
  64. package/src/gateway-v2-card-result.mjs +32 -0
  65. package/src/gateway-v2-card-search.mjs +39 -0
  66. package/src/gateway-v2-card-sources.mjs +32 -0
  67. package/src/gateway-v2-card-style.mjs +76 -0
  68. package/src/gateway-v2-card-verdict.mjs +66 -0
  69. package/src/gateway-v2-card.mjs +91 -0
  70. package/src/gateway-v2-contracts.json +66 -0
  71. package/src/gateway-v2-instructions.md +113 -0
  72. package/src/gateway-v2-recovery.mjs +18 -0
  73. package/src/gateway-v2-report-links.mjs +14 -0
  74. package/src/gateway-v2-workflows.mjs +17 -0
  75. package/src/gateway-v2.mjs +179 -0
  76. package/src/oauth.mjs +402 -1949
  77. package/src/observability.mjs +19 -3
  78. package/src/result-presentation.mjs +9 -0
  79. package/src/runtime.mjs +24 -3670
  80. package/src/source-groups.mjs +91 -0
  81. package/src/source-value-format.mjs +25 -0
  82. package/src/tool-result.mjs +20 -0
  83. package/src/ui-bridge.mjs +185 -0
  84. package/src/well-known-routes.mjs +132 -0
  85. package/src/assets/wallet-accounts.mjs +0 -20513
  86. package/src/assets/walletconnect-provider.mjs +0 -6319
  87. package/src/discovery.mjs +0 -929
  88. package/src/external-fetch.mjs +0 -203
  89. package/src/funding-options.mjs +0 -255
  90. package/src/gateway-management.mjs +0 -107
  91. package/src/hosted-payment.mjs +0 -552
  92. package/src/hosted-wallets.mjs +0 -530
  93. package/src/listing-metadata.mjs +0 -287
  94. package/src/local-config.mjs +0 -256
  95. package/src/payment-guidance.mjs +0 -298
  96. package/src/publisher.mjs +0 -1288
  97. package/src/result-canvas.mjs +0 -16
  98. package/src/source-registry.mjs +0 -215
  99. package/src/wallet-store.mjs +0 -476
  100. package/src/x402-inspect.mjs +0 -361
@@ -1,287 +0,0 @@
1
- function isObject(value) {
2
- return value !== null && typeof value === "object" && !Array.isArray(value);
3
- }
4
-
5
- function cloneJson(value, fallback) {
6
- if (value === undefined) return fallback;
7
- return JSON.parse(JSON.stringify(value));
8
- }
9
-
10
- function normalizePath(pathValue) {
11
- const value = String(pathValue || "/").trim();
12
- if (!value || value === "/") return "/";
13
- return value.startsWith("/") ? value : `/${value}`;
14
- }
15
-
16
- function normalizeTags(tags = []) {
17
- return Array.from(
18
- new Set(
19
- tags
20
- .map((tag) => String(tag || "").trim().toLowerCase())
21
- .filter(Boolean)
22
- )
23
- );
24
- }
25
-
26
- function defaultExecuteInputSchema() {
27
- return {
28
- type: "object",
29
- additionalProperties: false,
30
- properties: {
31
- operation: {
32
- type: "string",
33
- description: "Optional operation selector. Accepts an operation id, a path, or a METHOD /path key.",
34
- },
35
- input: {
36
- description: "JSON payload forwarded to the selected operation.",
37
- },
38
- query: {
39
- type: "object",
40
- additionalProperties: true,
41
- description: "Optional query parameter override for GET-style operations.",
42
- },
43
- path_params: {
44
- type: "object",
45
- additionalProperties: true,
46
- description: "Optional path parameter override for routes that include placeholders.",
47
- },
48
- },
49
- };
50
- }
51
-
52
- function defaultExecuteOutputSchema() {
53
- return {
54
- type: "object",
55
- required: ["status", "result", "cost", "latency", "operation", "api", "upstream_status"],
56
- properties: {
57
- status: { type: "string", enum: ["success", "error"] },
58
- result: {},
59
- cost: { type: "number" },
60
- latency: { type: "integer" },
61
- operation: { type: "string" },
62
- api: { type: "string" },
63
- upstream_status: { type: "integer" },
64
- },
65
- };
66
- }
67
-
68
- function defaultOperations(priceUsd) {
69
- return [
70
- {
71
- id: "/",
72
- key: "GET /",
73
- method: "GET",
74
- path: "/",
75
- price_usd: priceUsd,
76
- payment_required: true,
77
- description: "Forward a GET request to the API root.",
78
- },
79
- {
80
- id: "/",
81
- key: "POST /",
82
- method: "POST",
83
- path: "/",
84
- price_usd: priceUsd,
85
- payment_required: true,
86
- description: "Forward a POST request to the API root.",
87
- },
88
- ];
89
- }
90
-
91
- function normalizeOperation(operation, priceUsd) {
92
- const method = String(operation?.method || "POST").trim().toUpperCase();
93
- const path = normalizePath(operation?.path || operation?.id || "/");
94
- return {
95
- ...cloneJson(operation, {}),
96
- id: String(operation?.id || path),
97
- key: String(operation?.key || `${method} ${path}`),
98
- method,
99
- path,
100
- price_usd:
101
- typeof operation?.price_usd === "number" && Number.isFinite(operation.price_usd)
102
- ? operation.price_usd
103
- : priceUsd,
104
- payment_required:
105
- typeof operation?.payment_required === "boolean" ? operation.payment_required : true,
106
- };
107
- }
108
-
109
- function isDiagnosticOperationPath(path = "/") {
110
- const normalized = normalizePath(path);
111
- if (normalized === "/") return true;
112
- return normalized.split("/").filter(Boolean).some((segment) =>
113
- /^(health|status|metadata|docs?|openapi|swagger|ping|readyz?|livez?)$/i.test(segment)
114
- );
115
- }
116
-
117
- function defaultOperationRank(operation = {}) {
118
- const path = normalizePath(operation.path || operation.id || "/");
119
- const trimmed = path.replace(/^\/+|\/+$/g, "");
120
- const segments = trimmed ? trimmed.split("/").filter(Boolean).length : 0;
121
- const method = String(operation.method || "POST").trim().toUpperCase();
122
- const methodRank =
123
- method === "POST"
124
- ? 0
125
- : method === "PUT"
126
- ? 1
127
- : method === "PATCH"
128
- ? 2
129
- : method === "DELETE"
130
- ? 3
131
- : method === "GET"
132
- ? 4
133
- : method === "HEAD"
134
- ? 5
135
- : 6;
136
- return [
137
- isDiagnosticOperationPath(path) ? 1 : 0,
138
- /batch/i.test(path) ? 1 : 0,
139
- segments,
140
- path.length,
141
- methodRank,
142
- operation.payment_required === false ? 1 : 0,
143
- `${method} ${path}`,
144
- ];
145
- }
146
-
147
- function inferDefaultOperation(operations = []) {
148
- const ranked = [...operations].sort((left, right) => {
149
- const leftRank = defaultOperationRank(left);
150
- const rightRank = defaultOperationRank(right);
151
- for (let index = 0; index < leftRank.length; index += 1) {
152
- if (leftRank[index] < rightRank[index]) return -1;
153
- if (leftRank[index] > rightRank[index]) return 1;
154
- }
155
- return 0;
156
- });
157
- return ranked[0]?.id || operations[0]?.id || "/";
158
- }
159
-
160
- function inferReadOnlyHint(operations = [], slug = "", name = "", description = "") {
161
- const allReadOnly =
162
- operations.length > 0 &&
163
- operations.every((operation) => operation.method === "GET" || operation.method === "HEAD");
164
-
165
- if (allReadOnly) return true;
166
-
167
- return /extract|lookup|watch|map|research|digest|diff|monitor|verify|metadata|status/i.test(
168
- `${slug} ${name} ${description}`
169
- );
170
- }
171
-
172
- function inferIdempotentHint(operations = []) {
173
- return (
174
- operations.length > 0 &&
175
- operations.every((operation) => operation.method === "GET" || operation.method === "HEAD")
176
- );
177
- }
178
-
179
- function normalizeListingGroup(listingGroup) {
180
- const value = String(listingGroup || "").trim().toLowerCase();
181
- return value || null;
182
- }
183
-
184
- export function resolveCategory(category, listingGroup) {
185
- const explicit = String(category || "").trim().toLowerCase();
186
- if (explicit) return explicit;
187
-
188
- const normalizedGroup = normalizeListingGroup(listingGroup);
189
- if (normalizedGroup === "datasets") return "dataset";
190
- if (normalizedGroup === "compute") return "compute";
191
- return "data";
192
- }
193
-
194
- function resolveProviderListingType(category, listingGroup, baseProvider = null) {
195
- const explicit = String(baseProvider?.listing_type || "").trim().toLowerCase();
196
- if (explicit) return explicit;
197
-
198
- const normalizedGroup = normalizeListingGroup(listingGroup);
199
- if (normalizedGroup === "datasets" || category === "dataset") return "dataset";
200
- if (["service", "connector", "skill", "product"].includes(normalizedGroup || "")) {
201
- return normalizedGroup;
202
- }
203
-
204
- return "api";
205
- }
206
-
207
- export function buildListingMetadata({
208
- name,
209
- slug,
210
- description,
211
- endpoint_url,
212
- price_usd,
213
- category,
214
- listing_group,
215
- listing_metadata = {},
216
- } = {}) {
217
- const safeSlug = String(slug || "").trim();
218
- const safeName = String(name || safeSlug || "Apiosk API").trim();
219
- const safeDescription = String(description || "").trim();
220
- const safeEndpointUrl = String(endpoint_url || "").trim();
221
- const safePrice = Number(price_usd);
222
- const normalizedCategory = resolveCategory(category, listing_group);
223
- const base = isObject(listing_metadata) ? cloneJson(listing_metadata, {}) : {};
224
- const operationsInput = Array.isArray(base.operations) ? base.operations : defaultOperations(safePrice);
225
- const operations = operationsInput.map((operation) => normalizeOperation(operation, safePrice));
226
- const inputSchema = cloneJson(
227
- base.input_schema || base.mcp_tool?.inputSchema,
228
- defaultExecuteInputSchema()
229
- );
230
- const outputSchema = cloneJson(
231
- base.output_schema || base.mcp_tool?.outputSchema,
232
- defaultExecuteOutputSchema()
233
- );
234
- const provider = {
235
- type: "http_api",
236
- origin_url: safeEndpointUrl,
237
- ...cloneJson(base.provider, {}),
238
- origin_url: safeEndpointUrl,
239
- listing_type: resolveProviderListingType(normalizedCategory, listing_group, base.provider),
240
- };
241
- const mergedTags = normalizeTags([
242
- ...(Array.isArray(base.tags) ? base.tags : []),
243
- normalizedCategory,
244
- "api",
245
- "agent-ready",
246
- "mcp-native",
247
- ]);
248
- const defaultOperation = String(base.default_operation || inferDefaultOperation(operations));
249
- const readOnlyHint =
250
- typeof base.mcp_tool?.annotations?.readOnlyHint === "boolean"
251
- ? base.mcp_tool.annotations.readOnlyHint
252
- : inferReadOnlyHint(operations, safeSlug, safeName, safeDescription);
253
- const idempotentHint =
254
- typeof base.mcp_tool?.annotations?.idempotentHint === "boolean"
255
- ? base.mcp_tool.annotations.idempotentHint
256
- : inferIdempotentHint(operations);
257
-
258
- return {
259
- ...base,
260
- agent_native: true,
261
- mcp_native: true,
262
- metadata_path: String(base.metadata_path || "/metadata"),
263
- execute_path: String(base.execute_path || "/execute"),
264
- default_operation: defaultOperation,
265
- cost_per_call: safePrice,
266
- input_schema: inputSchema,
267
- output_schema: outputSchema,
268
- operations,
269
- provider,
270
- tags: mergedTags,
271
- mcp_tool: {
272
- ...cloneJson(base.mcp_tool, {}),
273
- name: String(base.mcp_tool?.name || safeSlug),
274
- title: String(base.mcp_tool?.title || safeName),
275
- description: String(base.mcp_tool?.description || safeDescription || safeName),
276
- inputSchema: inputSchema,
277
- outputSchema: outputSchema,
278
- annotations: {
279
- readOnlyHint,
280
- idempotentHint,
281
- destructiveHint: false,
282
- openWorldHint: false,
283
- ...cloneJson(base.mcp_tool?.annotations, {}),
284
- },
285
- },
286
- };
287
- }
@@ -1,256 +0,0 @@
1
- import os from "node:os";
2
- import path from "node:path";
3
- import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
-
5
- const DEFAULT_CONNECT_HEADER_NAME = "X-Apiosk-Connect-Token";
6
- const DEFAULT_GATEWAY_URL = "https://gateway.apiosk.com";
7
- const DEFAULT_DASHBOARD_URL = "https://apiosk.com";
8
- const DEFAULT_CONTROL_PLANE_URL = "https://mcp.apiosk.com";
9
- const DEFAULT_CHAIN_ID = 8453;
10
- const DEFAULT_RPC_URL = "https://mainnet.base.org";
11
- const DEFAULT_USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
12
-
13
- function trimString(value) {
14
- return String(value || "").trim();
15
- }
16
-
17
- function defaultApioskDir(env = process.env) {
18
- const configured = trimString(env.APIOSK_HOME);
19
- if (configured) return configured;
20
-
21
- const home = trimString(env.HOME) || os.homedir();
22
- return path.join(home, ".apiosk");
23
- }
24
-
25
- function normalizeWalletAddress(value, { optional = false } = {}) {
26
- const normalized = trimString(value).toLowerCase();
27
- if (!normalized && optional) return "";
28
- if (!/^0x[a-f0-9]{40}$/.test(normalized)) {
29
- throw new Error("Wallet address must use a 0x-prefixed 40-byte hex address.");
30
- }
31
- return normalized;
32
- }
33
-
34
- function normalizeConnectToken(value, { optional = false } = {}) {
35
- const normalized = trimString(value);
36
- if (!normalized && optional) return "";
37
- if (!/^aw_[A-Za-z0-9_:-]+$/.test(normalized)) {
38
- throw new Error("Connect token must match the Apiosk managed-token format.");
39
- }
40
- return normalized;
41
- }
42
-
43
- async function safeChmod(filePath, mode) {
44
- try {
45
- await chmod(filePath, mode);
46
- } catch {
47
- // Best effort only; not all environments support chmod.
48
- }
49
- }
50
-
51
- async function writeAtomic(filePath, value) {
52
- const dir = path.dirname(filePath);
53
- const tempFile = `${filePath}.${process.pid}.${Date.now()}.tmp`;
54
-
55
- await mkdir(dir, { recursive: true });
56
- await writeFile(tempFile, value, "utf8");
57
- await safeChmod(tempFile, 0o600);
58
- await rename(tempFile, filePath);
59
- await safeChmod(filePath, 0o600);
60
- }
61
-
62
- export function createApioskLocalConfigPaths(env = process.env) {
63
- const apioskDir = defaultApioskDir(env);
64
- return {
65
- apioskDir,
66
- configFile: path.join(apioskDir, "config.json"),
67
- connectEnvFile: path.join(apioskDir, "connect.env"),
68
- };
69
- }
70
-
71
- export async function readLocalApioskConfig(env = process.env) {
72
- const { configFile } = createApioskLocalConfigPaths(env);
73
-
74
- try {
75
- const raw = await readFile(configFile, "utf8");
76
- const parsed = JSON.parse(raw);
77
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
78
- return null;
79
- }
80
- return parsed;
81
- } catch (error) {
82
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
83
- return null;
84
- }
85
- throw new Error(
86
- `Could not read the local Apiosk config: ${
87
- error instanceof Error ? error.message : String(error)
88
- }`
89
- );
90
- }
91
- }
92
-
93
- export async function saveLocalApioskConfig(input = {}, env = process.env) {
94
- const paths = createApioskLocalConfigPaths(env);
95
- const existing = (await readLocalApioskConfig(env)) || {};
96
- const gatewayUrl = trimString(input.gateway_url || DEFAULT_GATEWAY_URL).replace(/\/+$/, "");
97
- const chainId = Number.parseInt(String(input.chain_id || DEFAULT_CHAIN_ID), 10) || DEFAULT_CHAIN_ID;
98
- const walletAddress = normalizeWalletAddress(input.agent_wallet_address || input.wallet_address, {
99
- optional: true,
100
- });
101
- const connectToken = normalizeConnectToken(input.connect_token, { optional: true });
102
- const connectAuthorization = trimString(
103
- input.connect_authorization || (connectToken ? `Bearer ${connectToken}` : "")
104
- );
105
- const connectHeaderName = trimString(input.connect_header_name || DEFAULT_CONNECT_HEADER_NAME);
106
- const dailyLimitUsdc = Number.isFinite(Number(input.daily_limit_usdc))
107
- ? Number(input.daily_limit_usdc)
108
- : 100;
109
- const perRequestLimitUsdc = Number.isFinite(Number(input.per_request_limit_usdc))
110
- ? Number(input.per_request_limit_usdc)
111
- : 1;
112
-
113
- if (!connectToken && !connectAuthorization) {
114
- throw new Error("A connect token or connect authorization value is required.");
115
- }
116
-
117
- const config = {
118
- rpc_url: DEFAULT_RPC_URL,
119
- chain_id: chainId,
120
- usdc_contract: DEFAULT_USDC_CONTRACT,
121
- gateway_url: gatewayUrl,
122
- agent_wallet_address: walletAddress || null,
123
- daily_limit_usdc: dailyLimitUsdc,
124
- per_request_limit_usdc: perRequestLimitUsdc,
125
- connect_token: connectToken || null,
126
- connect_authorization: connectAuthorization || null,
127
- connect_header_name: connectHeaderName || DEFAULT_CONNECT_HEADER_NAME,
128
- control_plane_url:
129
- trimString(existing.control_plane_url || env.APIOSK_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL).replace(
130
- /\/+$/,
131
- ""
132
- ) || null,
133
- dashboard_url:
134
- trimString(existing.dashboard_url || env.APIOSK_DASHBOARD_URL || DEFAULT_DASHBOARD_URL).replace(/\/+$/, "") ||
135
- null,
136
- dashboard_session_token: trimString(existing.dashboard_session_token) || null,
137
- dashboard_session_email: trimString(existing.dashboard_session_email) || null,
138
- dashboard_session_expires_at: Number.isFinite(Number(existing.dashboard_session_expires_at))
139
- ? Number(existing.dashboard_session_expires_at)
140
- : null,
141
- };
142
-
143
- const connectEnv = [
144
- `APIO_GATEWAY_URL=${gatewayUrl}`,
145
- `APIO_CHAIN_ID=${chainId}`,
146
- `APIO_AGENT_WALLET_ADDRESS=${walletAddress}`,
147
- `APIO_CONNECT_TOKEN=${connectToken}`,
148
- `APIO_CONNECT_AUTHORIZATION=${connectAuthorization}`,
149
- `APIO_CONNECT_HEADER_NAME=${connectHeaderName || DEFAULT_CONNECT_HEADER_NAME}`,
150
- `APIO_WALLET_DAILY_LIMIT_USDC=${dailyLimitUsdc}`,
151
- `APIO_WALLET_PER_TX_LIMIT_USDC=${perRequestLimitUsdc}`,
152
- ].join("\n");
153
-
154
- await writeAtomic(paths.configFile, `${JSON.stringify(config, null, 2)}\n`);
155
- await writeAtomic(paths.connectEnvFile, `${connectEnv}\n`);
156
-
157
- return {
158
- paths,
159
- config,
160
- };
161
- }
162
-
163
- export async function saveLocalApioskDashboardSession(input = {}, env = process.env) {
164
- const paths = createApioskLocalConfigPaths(env);
165
- const existing = (await readLocalApioskConfig(env)) || {};
166
- const sessionToken = trimString(input.dashboard_session_token || input.session_token);
167
-
168
- if (!sessionToken) {
169
- throw new Error("A dashboard session token is required.");
170
- }
171
-
172
- const expiresAtRaw = input.dashboard_session_expires_at ?? input.expires_at;
173
- const expiresAt = Number(expiresAtRaw);
174
- const config = {
175
- ...existing,
176
- control_plane_url:
177
- trimString(
178
- input.control_plane_url || existing.control_plane_url || env.APIOSK_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL
179
- ).replace(/\/+$/, "") || null,
180
- dashboard_url:
181
- trimString(input.dashboard_url || existing.dashboard_url || env.APIOSK_DASHBOARD_URL || DEFAULT_DASHBOARD_URL).replace(/\/+$/, "") ||
182
- null,
183
- dashboard_session_token: sessionToken,
184
- dashboard_session_email:
185
- trimString(input.dashboard_session_email || input.email || existing.dashboard_session_email) || null,
186
- dashboard_session_expires_at: Number.isFinite(expiresAt)
187
- ? expiresAt
188
- : Number.isFinite(Number(existing.dashboard_session_expires_at))
189
- ? Number(existing.dashboard_session_expires_at)
190
- : null,
191
- };
192
-
193
- await writeAtomic(paths.configFile, `${JSON.stringify(config, null, 2)}\n`);
194
-
195
- return {
196
- paths,
197
- config,
198
- };
199
- }
200
-
201
- export function parseConnectString(raw) {
202
- const lines = String(raw || "").split(/\r?\n/);
203
- const parsed = {
204
- gateway_url: "",
205
- chain_id: DEFAULT_CHAIN_ID,
206
- wallet_address: "",
207
- connect_token: "",
208
- connect_authorization: "",
209
- connect_header_name: DEFAULT_CONNECT_HEADER_NAME,
210
- daily_limit_usdc: 100,
211
- per_request_limit_usdc: 1,
212
- };
213
-
214
- for (const line of lines) {
215
- const trimmed = line.trim();
216
- if (!trimmed || trimmed.startsWith("#")) continue;
217
-
218
- const normalizedLine = trimmed.startsWith("export ") ? trimmed.slice(7) : trimmed;
219
- const separatorIndex = normalizedLine.indexOf("=");
220
- if (separatorIndex === -1) continue;
221
-
222
- const key = normalizedLine.slice(0, separatorIndex).trim();
223
- let value = normalizedLine.slice(separatorIndex + 1).trim();
224
- if (
225
- (value.startsWith('"') && value.endsWith('"')) ||
226
- (value.startsWith("'") && value.endsWith("'"))
227
- ) {
228
- value = value.slice(1, -1);
229
- }
230
-
231
- if (key === "APIO_GATEWAY_URL") parsed.gateway_url = value;
232
- if (key === "APIO_CHAIN_ID") parsed.chain_id = Number.parseInt(value, 10) || DEFAULT_CHAIN_ID;
233
- if (key === "APIO_AGENT_WALLET_ADDRESS") parsed.wallet_address = value;
234
- if (key === "APIO_CONNECT_TOKEN") parsed.connect_token = value;
235
- if (key === "APIO_CONNECT_AUTHORIZATION") parsed.connect_authorization = value;
236
- if (key === "APIO_CONNECT_HEADER_NAME") parsed.connect_header_name = value || DEFAULT_CONNECT_HEADER_NAME;
237
- if (key === "APIO_WALLET_DAILY_LIMIT_USDC") parsed.daily_limit_usdc = Number(value) || 100;
238
- if (key === "APIO_WALLET_PER_TX_LIMIT_USDC") parsed.per_request_limit_usdc = Number(value) || 1;
239
- }
240
-
241
- if (!parsed.gateway_url || !parsed.wallet_address || !parsed.connect_token) {
242
- throw new Error("Connect string is missing one of: APIO_GATEWAY_URL, APIO_AGENT_WALLET_ADDRESS, APIO_CONNECT_TOKEN.");
243
- }
244
-
245
- return {
246
- gateway_url: trimString(parsed.gateway_url).replace(/\/+$/, ""),
247
- chain_id: parsed.chain_id || DEFAULT_CHAIN_ID,
248
- wallet_address: normalizeWalletAddress(parsed.wallet_address),
249
- connect_token: normalizeConnectToken(parsed.connect_token),
250
- connect_authorization:
251
- trimString(parsed.connect_authorization) || `Bearer ${normalizeConnectToken(parsed.connect_token)}`,
252
- connect_header_name: trimString(parsed.connect_header_name) || DEFAULT_CONNECT_HEADER_NAME,
253
- daily_limit_usdc: parsed.daily_limit_usdc,
254
- per_request_limit_usdc: parsed.per_request_limit_usdc,
255
- };
256
- }