@mainlayer/cli 0.1.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,716 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ //#region src/postinstall/platforms.ts
5
+ const MCP_URL = "https://api.mainlayer.io/mcp";
6
+ function hasExistingEntry(servers, targetUrl) {
7
+ const urlKeys = [
8
+ "url",
9
+ "serverUrl",
10
+ "httpUrl"
11
+ ];
12
+ return Object.values(servers).some((entry) => {
13
+ const e = entry;
14
+ return urlKeys.some((k) => e[k] === targetUrl);
15
+ });
16
+ }
17
+ const PLATFORMS = [
18
+ {
19
+ name: "Claude Code",
20
+ configPath: (home) => join(home, ".claude.json"),
21
+ format: "json",
22
+ topLevelKey: "mcpServers",
23
+ buildEntry: () => ({
24
+ type: "http",
25
+ url: MCP_URL,
26
+ headers: { Authorization: "Bearer ${MAINLAYER_API_KEY}" }
27
+ }),
28
+ idempotencyCheck: (home) => {
29
+ const p = join(home, ".claude.json");
30
+ if (!existsSync(p)) return false;
31
+ try {
32
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["mcpServers"] ?? {}, MCP_URL);
33
+ } catch {
34
+ return false;
35
+ }
36
+ },
37
+ skillsDir: (home) => join(home, ".claude")
38
+ },
39
+ {
40
+ name: "Claude Desktop (macOS)",
41
+ configPath: (home) => join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
42
+ format: "json",
43
+ topLevelKey: "mcpServers",
44
+ buildEntry: () => ({}),
45
+ idempotencyCheck: () => false,
46
+ skillsDir: (home) => join(home, "Library", "Application Support", "Claude"),
47
+ skipReason: "Remote MCP requires manual setup via Settings > Connectors"
48
+ },
49
+ {
50
+ name: "Claude Desktop (Windows)",
51
+ configPath: () => {
52
+ return join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json");
53
+ },
54
+ format: "json",
55
+ topLevelKey: "mcpServers",
56
+ buildEntry: () => ({}),
57
+ idempotencyCheck: () => false,
58
+ skillsDir: () => {
59
+ return join(process.env["APPDATA"] ?? "", "Claude");
60
+ },
61
+ skipReason: "Remote MCP requires manual setup via Settings > Connectors"
62
+ },
63
+ {
64
+ name: "Cursor",
65
+ configPath: (home) => join(home, ".cursor", "mcp.json"),
66
+ format: "json",
67
+ topLevelKey: "mcpServers",
68
+ buildEntry: () => ({
69
+ url: MCP_URL,
70
+ headers: { Authorization: "Bearer ${env:MAINLAYER_API_KEY}" }
71
+ }),
72
+ idempotencyCheck: (home) => {
73
+ const p = join(home, ".cursor", "mcp.json");
74
+ if (!existsSync(p)) return false;
75
+ try {
76
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["mcpServers"] ?? {}, MCP_URL);
77
+ } catch {
78
+ return false;
79
+ }
80
+ },
81
+ skillsDir: (home) => join(home, ".cursor")
82
+ },
83
+ {
84
+ name: "Windsurf",
85
+ configPath: (home) => join(home, ".codeium", "windsurf", "mcp_config.json"),
86
+ format: "json",
87
+ topLevelKey: "mcpServers",
88
+ buildEntry: () => ({
89
+ serverUrl: MCP_URL,
90
+ headers: { Authorization: "Bearer ${env:MAINLAYER_API_KEY}" }
91
+ }),
92
+ idempotencyCheck: (home) => {
93
+ const p = join(home, ".codeium", "windsurf", "mcp_config.json");
94
+ if (!existsSync(p)) return false;
95
+ try {
96
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["mcpServers"] ?? {}, MCP_URL);
97
+ } catch {
98
+ return false;
99
+ }
100
+ },
101
+ skillsDir: (home) => join(home, ".codeium", "windsurf")
102
+ },
103
+ {
104
+ name: "Gemini CLI",
105
+ configPath: (home) => join(home, ".gemini", "settings.json"),
106
+ format: "json",
107
+ topLevelKey: "mcpServers",
108
+ buildEntry: () => ({ httpUrl: MCP_URL }),
109
+ idempotencyCheck: (home) => {
110
+ const p = join(home, ".gemini", "settings.json");
111
+ if (!existsSync(p)) return false;
112
+ try {
113
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["mcpServers"] ?? {}, MCP_URL);
114
+ } catch {
115
+ return false;
116
+ }
117
+ },
118
+ skillsDir: (home) => join(home, ".gemini")
119
+ },
120
+ {
121
+ name: "VS Code",
122
+ configPath: (home) => join(home, "Library", "Application Support", "Code", "User", "mcp.json"),
123
+ format: "json",
124
+ topLevelKey: "servers",
125
+ buildEntry: () => ({
126
+ type: "http",
127
+ url: MCP_URL
128
+ }),
129
+ idempotencyCheck: (home) => {
130
+ const p = join(home, "Library", "Application Support", "Code", "User", "mcp.json");
131
+ if (!existsSync(p)) return false;
132
+ try {
133
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["servers"] ?? {}, MCP_URL);
134
+ } catch {
135
+ return false;
136
+ }
137
+ },
138
+ skillsDir: (home) => join(home, "Library", "Application Support", "Code", "User")
139
+ },
140
+ {
141
+ name: "Zed",
142
+ configPath: (home) => join(home, ".zed", "settings.json"),
143
+ altConfigPath: (home) => join(home, ".config", "zed", "settings.json"),
144
+ format: "json",
145
+ topLevelKey: "context_servers",
146
+ buildEntry: () => ({ url: MCP_URL }),
147
+ idempotencyCheck: (home) => {
148
+ const primary = join(home, ".zed", "settings.json");
149
+ const alt = join(home, ".config", "zed", "settings.json");
150
+ const p = existsSync(primary) ? primary : alt;
151
+ if (!existsSync(p)) return false;
152
+ try {
153
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["context_servers"] ?? {}, MCP_URL);
154
+ } catch {
155
+ return false;
156
+ }
157
+ },
158
+ skillsDir: (home) => {
159
+ const primary = join(home, ".zed");
160
+ return existsSync(primary) ? primary : join(home, ".config", "zed");
161
+ }
162
+ },
163
+ {
164
+ name: "Cline",
165
+ configPath: (home) => join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
166
+ format: "json",
167
+ topLevelKey: "mcpServers",
168
+ buildEntry: () => ({
169
+ url: MCP_URL,
170
+ disabled: false
171
+ }),
172
+ idempotencyCheck: (home) => {
173
+ const p = join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
174
+ if (!existsSync(p)) return false;
175
+ try {
176
+ return hasExistingEntry(JSON.parse(readFileSync(p, "utf8"))["mcpServers"] ?? {}, MCP_URL);
177
+ } catch {
178
+ return false;
179
+ }
180
+ },
181
+ skillsDir: (home) => join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings")
182
+ },
183
+ {
184
+ name: "Continue",
185
+ configPath: (home) => join(home, ".continue", "mcpServers", "mainlayer.yaml"),
186
+ format: "yaml-file",
187
+ topLevelKey: "",
188
+ buildEntry: () => ({}),
189
+ idempotencyCheck: (home) => {
190
+ return existsSync(join(home, ".continue", "mcpServers", "mainlayer.yaml"));
191
+ },
192
+ skillsDir: (home) => join(home, ".continue")
193
+ }
194
+ ];
195
+ function writeJsonPlatform(desc, home, force) {
196
+ let configPath = desc.configPath(home);
197
+ if (!existsSync(configPath) && desc.altConfigPath) configPath = desc.altConfigPath(home);
198
+ if (!existsSync(configPath)) return {
199
+ configured: false,
200
+ detected: false
201
+ };
202
+ let config = {};
203
+ try {
204
+ config = JSON.parse(readFileSync(configPath, "utf8"));
205
+ } catch {
206
+ config = {};
207
+ }
208
+ const servers = config[desc.topLevelKey] ?? {};
209
+ if (!force && hasExistingEntry(servers, "https://api.mainlayer.io/mcp")) return {
210
+ configured: false,
211
+ detected: true
212
+ };
213
+ servers["mainlayer"] = desc.buildEntry();
214
+ config[desc.topLevelKey] = servers;
215
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
216
+ return {
217
+ configured: true,
218
+ detected: true
219
+ };
220
+ }
221
+ function writeContinuePlatform(home, force) {
222
+ const continueDir = join(home, ".continue");
223
+ if (!existsSync(continueDir)) return {
224
+ configured: false,
225
+ detected: false
226
+ };
227
+ const mcpServersDir = join(continueDir, "mcpServers");
228
+ const targetFile = join(mcpServersDir, "mainlayer.yaml");
229
+ if (!force && existsSync(targetFile)) return {
230
+ configured: false,
231
+ detected: true
232
+ };
233
+ mkdirSync(mcpServersDir, { recursive: true });
234
+ writeFileSync(targetFile, `name: mainlayer
235
+ version: 0.0.1
236
+ schema: v1
237
+ mcpServers:
238
+ - name: mainlayer
239
+ type: streamable-http
240
+ url: ${MCP_URL}
241
+ `, "utf8");
242
+ return {
243
+ configured: true,
244
+ detected: true
245
+ };
246
+ }
247
+ async function configurePlatforms(options) {
248
+ const force = options.force ?? false;
249
+ const home = homedir();
250
+ const results = [];
251
+ for (const desc of PLATFORMS) {
252
+ if (desc.skipReason) {
253
+ results.push({
254
+ name: desc.name,
255
+ configured: false,
256
+ detected: false,
257
+ skillsDropped: false,
258
+ skipped: true,
259
+ error: desc.skipReason
260
+ });
261
+ continue;
262
+ }
263
+ try {
264
+ let configured = false;
265
+ let detected = false;
266
+ if (desc.format === "yaml-file") ({configured, detected} = writeContinuePlatform(home, force));
267
+ else ({configured, detected} = writeJsonPlatform(desc, home, force));
268
+ results.push({
269
+ name: desc.name,
270
+ configured,
271
+ detected,
272
+ skillsDropped: false,
273
+ skipped: false
274
+ });
275
+ } catch (err) {
276
+ results.push({
277
+ name: desc.name,
278
+ configured: false,
279
+ detected: false,
280
+ skillsDropped: false,
281
+ skipped: false,
282
+ error: err instanceof Error ? err.message : String(err)
283
+ });
284
+ }
285
+ }
286
+ return results;
287
+ }
288
+ //#endregion
289
+ //#region src/postinstall/skills-template.ts
290
+ /**
291
+ * skills-template.ts
292
+ *
293
+ * Pure string generator for the Mainlayer agent skills guide.
294
+ * No imports from src/cli/, src/services/, or src/utils/.
295
+ * Zero external dependencies.
296
+ */
297
+ const SKILLS_FILENAME = "mainlayer-skills.md";
298
+ function generateSkillsMd() {
299
+ return `# Mainlayer CLI — Agent Skills Guide
300
+
301
+ > Generated by \`@mainlayer/cli\` postinstall. All commands support \`--json\` for machine-readable output.
302
+
303
+ ---
304
+
305
+ ## Setup
306
+
307
+ ### Required Environment Variables
308
+
309
+ | Variable | Purpose |
310
+ |----------|---------|
311
+ | \`MAINLAYER_API_KEY\` | Authenticates API and MCP requests. Get from \`mainlayer auth api-key create --label "my-agent" --json\` |
312
+ | \`MAINLAYER_WALLET_PASSPHRASE\` | Unlocks the encrypted wallet for signing without an interactive prompt (headless/agent mode) |
313
+
314
+ ### Optional Environment Variables
315
+
316
+ | Variable | Default | Purpose |
317
+ |----------|---------|---------|
318
+ | \`MAINLAYER_API_URL\` | \`https://api.mainlayer.io\` | Override the API base URL (staging, local dev) |
319
+ | \`MAINLAYER_SOLANA_NETWORK\` | \`solana:mainnet\` | Override Solana network (e.g. \`solana:devnet\`) |
320
+
321
+ ### Flags Available on All Commands
322
+
323
+ - \`--json\` — Output structured JSON to stdout. Also auto-enabled when stdout is not a TTY.
324
+ - \`--api-key <key>\` — Per-command API key override (overrides \`MAINLAYER_API_KEY\` env var).
325
+
326
+ ### Exit Codes
327
+
328
+ | Code | Meaning |
329
+ |------|---------|
330
+ | \`0\` | Success |
331
+ | \`1\` | General error |
332
+ | \`2\` | Authentication error (re-authenticate with \`mainlayer auth login\`) |
333
+ | \`3\` | Not found (resource, subscription, invoice, etc. does not exist) |
334
+ | \`4\` | Validation error (invalid input, missing required field) |
335
+ | \`5\` | Already exists (duplicate resource slug, coupon code, etc.) |
336
+
337
+ ---
338
+
339
+ ## Vendor Quick-Start
340
+
341
+ Step-by-step for an AI agent acting as a **vendor** (selling digital resources):
342
+
343
+ \`\`\`bash
344
+ # 1. Register an account
345
+ mainlayer auth register --email vendor@example.com --password YourP@ssw0rd --json
346
+
347
+ # 2. Create an API key and save it to MAINLAYER_API_KEY
348
+ mainlayer auth api-key create --label "my-agent" --json
349
+ # → { "key": "sk_live_...", "label": "my-agent", "id": "..." }
350
+ export MAINLAYER_API_KEY=sk_live_...
351
+
352
+ # 3. Create an encrypted wallet (set MAINLAYER_WALLET_PASSPHRASE for headless use)
353
+ export MAINLAYER_WALLET_PASSPHRASE=my-secure-passphrase
354
+ mainlayer wallet create --json
355
+ # → { "address": "SolanaPublicKey...", "created": true }
356
+
357
+ # 4. Create a resource
358
+ mainlayer resource create \\
359
+ --slug my-api \\
360
+ --description "My AI API endpoint" \\
361
+ --price 0.01 \\
362
+ --fee-model per_call \\
363
+ --type api \\
364
+ --json
365
+ # → { "id": "res_...", "slug": "my-api", "status": "active" }
366
+
367
+ # 5. Add a subscription pricing plan
368
+ mainlayer resource plan create \\
369
+ --resource-id res_... \\
370
+ --name "Basic" \\
371
+ --price 9.99 \\
372
+ --interval monthly \\
373
+ --json
374
+ # → { "id": "plan_...", "name": "Basic", "price": 9.99 }
375
+
376
+ # 6. Configure your webhook to receive payment events
377
+ mainlayer webhook update --url https://example.com/webhooks --json
378
+ # → { "url": "https://example.com/webhooks", "configured": true }
379
+ \`\`\`
380
+
381
+ ---
382
+
383
+ ## Buyer Quick-Start
384
+
385
+ Step-by-step for an AI agent acting as a **buyer** (purchasing digital resources):
386
+
387
+ \`\`\`bash
388
+ # 1. Register an account
389
+ mainlayer auth register --email buyer@example.com --password YourP@ssw0rd --json
390
+
391
+ # 2. Create and fund a wallet (fund with SOL + USDC before buying)
392
+ export MAINLAYER_WALLET_PASSPHRASE=my-secure-passphrase
393
+ mainlayer wallet create --json
394
+ # → { "address": "SolanaPublicKey...", "created": true }
395
+
396
+ # 3. Discover available resources
397
+ mainlayer discover --json
398
+ # → [{ "id": "res_...", "slug": "my-api", "price": 0.01, "fee_model": "per_call" }]
399
+
400
+ # 4. Buy a resource (X402 on-chain payment)
401
+ mainlayer buy res_... --json
402
+ # → { "payment_id": "pay_...", "status": "confirmed", "entitlement_id": "ent_..." }
403
+
404
+ # 5. Verify access
405
+ mainlayer entitlements --json
406
+ # → [{ "id": "ent_...", "resource_id": "res_...", "status": "active" }]
407
+ \`\`\`
408
+
409
+ ---
410
+
411
+ ## Command Reference
412
+
413
+ ### \`mainlayer auth\` — Authentication & API Keys
414
+
415
+ **Subcommands:**
416
+
417
+ | Subcommand | Description | Key Flags |
418
+ |------------|-------------|-----------|
419
+ | \`register\` | Create a new account | \`--email\`, \`--password\` |
420
+ | \`login\` | Log in and receive a JWT | \`--email\`, \`--password\` |
421
+ | \`logout\` | Clear stored JWT | — |
422
+ | \`status\` | Show current auth state | — |
423
+ | \`api-key create\` | Create an API key | \`--label\` |
424
+ | \`api-key list\` | List all API keys | — |
425
+ | \`api-key revoke\` | Revoke an API key by ID | \`<key-id>\` |
426
+
427
+ **JSON output shape:**
428
+ \`\`\`json
429
+ // auth register / login
430
+ { "token": "jwt...", "email": "...", "user_id": "..." }
431
+
432
+ // api-key create
433
+ { "id": "...", "key": "sk_live_...", "label": "..." }
434
+ \`\`\`
435
+
436
+ ---
437
+
438
+ ### \`mainlayer wallet\` — Encrypted Local Wallet
439
+
440
+ **Subcommands:**
441
+
442
+ | Subcommand | Description | Key Flags |
443
+ |------------|-------------|-----------|
444
+ | \`create\` | Generate new Solana keypair, encrypt at rest | — |
445
+ | \`import\` | Import existing keypair (base58 or mnemonic) | \`--key\`, \`--mnemonic\` |
446
+ | \`address\` | Print the wallet's public key | — |
447
+ | \`balance\` | Show SOL + USDC balances | — |
448
+ | \`export\` | Export private key (requires passphrase confirmation) | — |
449
+
450
+ **JSON output shape:**
451
+ \`\`\`json
452
+ // wallet create / import
453
+ { "address": "SolPublicKey...", "created": true }
454
+
455
+ // wallet balance
456
+ { "sol": 1.5, "usdc": 100.00, "address": "SolPublicKey..." }
457
+ \`\`\`
458
+
459
+ ---
460
+
461
+ ### \`mainlayer config\` — CLI Configuration
462
+
463
+ **Subcommands:**
464
+
465
+ | Subcommand | Description | Key Flags |
466
+ |------------|-------------|-----------|
467
+ | \`get\` | Get a config value | \`<key>\` |
468
+ | \`set\` | Set a config value | \`<key> <value>\` |
469
+
470
+ ---
471
+
472
+ ### \`mainlayer resource\` — Resource Management (Vendor)
473
+
474
+ **Subcommands:**
475
+
476
+ | Subcommand | Description | Key Flags |
477
+ |------------|-------------|-----------|
478
+ | \`create\` | Register a new resource | \`--slug\`, \`--description\`, \`--price\`, \`--fee-model\`, \`--type\` |
479
+ | \`list\` | List all your resources | \`--page\`, \`--limit\` |
480
+ | \`get\` | Get a specific resource | \`<resource-id>\` |
481
+ | \`update\` | Update a resource | \`<resource-id>\`, \`--description\`, \`--price\` |
482
+ | \`delete\` | Delete a resource | \`<resource-id>\`, \`--force\` |
483
+ | \`plan create\` | Add a subscription plan | \`--resource-id\`, \`--name\`, \`--price\`, \`--interval\` |
484
+ | \`plan list\` | List plans for a resource | \`--resource-id\` |
485
+ | \`plan update\` | Update a plan | \`--plan-id\`, \`--price\` |
486
+ | \`plan delete\` | Delete a plan | \`--plan-id\` |
487
+ | \`quota set\` | Set per-wallet rate limit | \`--resource-id\`, \`--wallet\`, \`--calls\`, \`--period\` |
488
+ | \`quota get\` | Get current quota | \`--resource-id\`, \`--wallet\` |
489
+ | \`quota delete\` | Remove quota limit | \`--resource-id\`, \`--wallet\` |
490
+
491
+ **JSON output shape:**
492
+ \`\`\`json
493
+ // resource create
494
+ { "id": "res_...", "slug": "my-api", "status": "active", "price": 0.01, "fee_model": "per_call" }
495
+ \`\`\`
496
+
497
+ ---
498
+
499
+ ### \`mainlayer coupon\` — Discount Codes (Vendor)
500
+
501
+ **Subcommands:**
502
+
503
+ | Subcommand | Description | Key Flags |
504
+ |------------|-------------|-----------|
505
+ | \`create\` | Create a discount coupon | \`--code\`, \`--discount\`, \`--resource-id\`, \`--expires\` |
506
+ | \`list\` | List your coupons | \`--resource-id\` |
507
+ | \`delete\` | Delete a coupon | \`<coupon-id>\` |
508
+
509
+ **Note:** Coupon codes are automatically uppercased before API calls.
510
+
511
+ ---
512
+
513
+ ### \`mainlayer webhook\` — Webhook Management (Vendor)
514
+
515
+ **Subcommands:**
516
+
517
+ | Subcommand | Description | Key Flags |
518
+ |------------|-------------|-----------|
519
+ | \`update\` | Set or update webhook URL | \`--url\` |
520
+ | \`logs\` | View recent webhook delivery logs | \`--resource-id\` |
521
+ | \`retry\` | Retry a failed delivery | \`<log-id>\` |
522
+ | \`rotate-secret\` | Rotate webhook signing secret | \`--force\` |
523
+
524
+ ---
525
+
526
+ ### \`mainlayer earnings\` — Revenue Summary (Vendor)
527
+
528
+ Shows revenue summary with period expansion and daily totals.
529
+
530
+ **Key flags:** \`--resource-id\`, \`--period\` (default: \`30d\`), \`--from\`, \`--to\`
531
+
532
+ **JSON output shape:**
533
+ \`\`\`json
534
+ { "total_usdc": 123.45, "period": "30d", "breakdown": [{ "date": "...", "amount": 4.5 }] }
535
+ \`\`\`
536
+
537
+ ---
538
+
539
+ ### \`mainlayer metrics\` — Usage Analytics (Vendor)
540
+
541
+ Shows per-resource aggregate analytics.
542
+
543
+ **Key flags:** \`--resource-id\`
544
+
545
+ **JSON output shape:**
546
+ \`\`\`json
547
+ { "calls": 1024, "unique_buyers": 12, "errors": 3, "resource_id": "res_..." }
548
+ \`\`\`
549
+
550
+ ---
551
+
552
+ ### \`mainlayer discover\` — Browse Resources (Buyer, no auth required)
553
+
554
+ **Key flags:** \`--query\`, \`--type\`, \`--fee-model\`, \`--max-price\`, \`--page\`, \`--limit\`
555
+
556
+ **JSON output shape:**
557
+ \`\`\`json
558
+ [{ "id": "res_...", "slug": "...", "description": "...", "price": 0.01, "fee_model": "per_call" }]
559
+ \`\`\`
560
+
561
+ ---
562
+
563
+ ### \`mainlayer buy\` — Purchase a Resource (Buyer)
564
+
565
+ X402 on-chain payment flow: prepare → sign → submit.
566
+
567
+ **Usage:** \`mainlayer buy <resource-id> [--json]\`
568
+
569
+ **JSON output shape:**
570
+ \`\`\`json
571
+ { "payment_id": "pay_...", "status": "confirmed", "entitlement_id": "ent_...", "tx_signature": "..." }
572
+ \`\`\`
573
+
574
+ **Error hints:**
575
+ - \`plan_required\` — resource requires a subscription plan; use \`mainlayer subscribe approve\`
576
+ - \`tx_expired\` — transaction TTL expired; retry the buy command
577
+
578
+ ---
579
+
580
+ ### \`mainlayer entitlements\` — View Access Grants (Buyer)
581
+
582
+ **Key flags:** \`--resource-id\`
583
+
584
+ **JSON output shape:**
585
+ \`\`\`json
586
+ [{ "id": "ent_...", "resource_id": "res_...", "status": "active", "expires_at": "..." }]
587
+ \`\`\`
588
+
589
+ ---
590
+
591
+ ### \`mainlayer subscribe\` — Subscription Management
592
+
593
+ **Subcommands:**
594
+
595
+ | Subcommand | Description | Key Flags |
596
+ |------------|-------------|-----------|
597
+ | \`approve\` | Approve a subscription (signs USDC delegate on-chain) | \`--resource-id\`, \`--plan-id\` |
598
+ | \`pause\` | Pause an active subscription | \`<subscription-id>\` |
599
+ | \`resume\` | Resume a paused subscription | \`<subscription-id>\` |
600
+ | \`cancel\` | Cancel a subscription | \`<subscription-id>\`, \`--force\` |
601
+ | \`list\` | List your subscriptions | \`--resource-id\` |
602
+ | \`get\` | Get a specific subscription | \`<subscription-id>\` |
603
+
604
+ **JSON output shape:**
605
+ \`\`\`json
606
+ { "id": "sub_...", "resource_id": "res_...", "plan_id": "plan_...", "status": "active" }
607
+ \`\`\`
608
+
609
+ ---
610
+
611
+ ### \`mainlayer invoices\` — Invoice History
612
+
613
+ **Key flags:** \`--resource-id\`
614
+
615
+ **JSON output shape:**
616
+ \`\`\`json
617
+ [{ "id": "inv_...", "amount": 9.99, "status": "paid", "created_at": "...", "resource_id": "res_..." }]
618
+ \`\`\`
619
+
620
+ ---
621
+
622
+ ### \`mainlayer refund\` — Request a Refund
623
+
624
+ **Usage:** \`mainlayer refund request <payment-id> [--json]\`
625
+
626
+ **JSON output shape:**
627
+ \`\`\`json
628
+ { "id": "ref_...", "payment_id": "pay_...", "status": "pending", "amount": 0.01 }
629
+ \`\`\`
630
+
631
+ ---
632
+
633
+ ### \`mainlayer dispute\` — Dispute Management
634
+
635
+ **Subcommands:**
636
+
637
+ | Subcommand | Description | Key Flags |
638
+ |------------|-------------|-----------|
639
+ | \`create\` | Open a dispute on a payment | \`--payment-id\`, \`--reason\` |
640
+ | \`list\` | List disputes (vendor-scoped) | \`--resource-id\` |
641
+
642
+ **JSON output shape:**
643
+ \`\`\`json
644
+ // dispute create
645
+ { "id": "dis_...", "payment_id": "pay_...", "status": "open", "reason": "..." }
646
+ \`\`\`
647
+
648
+ ---
649
+
650
+ ### \`mainlayer setup\` — MCP Auto-Configuration
651
+
652
+ Re-runs the platform detection and MCP registration that occurs on \`npm install\`.
653
+
654
+ **Key flags:** \`--force\` (overwrite existing entries), \`--json\`
655
+
656
+ **JSON output shape:**
657
+ \`\`\`json
658
+ {
659
+ "platforms": [
660
+ { "name": "Claude Code", "configured": true, "skills_dropped": true, "error": null },
661
+ { "name": "Cursor", "configured": true, "skills_dropped": true, "error": null }
662
+ ]
663
+ }
664
+ \`\`\`
665
+
666
+ ---
667
+
668
+ ## Agent Notes
669
+
670
+ ### Headless / Non-Interactive Mode
671
+
672
+ Set \`MAINLAYER_WALLET_PASSPHRASE\` as an environment variable to avoid the interactive passphrase prompt when any command needs to sign a transaction:
673
+
674
+ \`\`\`bash
675
+ export MAINLAYER_WALLET_PASSPHRASE=your-passphrase
676
+ mainlayer buy res_... --json
677
+ \`\`\`
678
+
679
+ ### CI / Automated Environments
680
+
681
+ When \`CI=true\` is set, the CLI skips interactive output formatting (spinners, color, prompts). All commands still function normally.
682
+
683
+ ### JSON Output
684
+
685
+ All commands return valid JSON when:
686
+ - \`--json\` flag is set, OR
687
+ - stdout is not a TTY (e.g., piped output, CI environment)
688
+
689
+ ### Programmatic Error Handling
690
+
691
+ Use exit codes to determine success or failure without parsing stderr:
692
+
693
+ \`\`\`bash
694
+ mainlayer buy res_... --json
695
+ if [ $? -eq 0 ]; then
696
+ echo "Payment confirmed"
697
+ elif [ $? -eq 2 ]; then
698
+ echo "Auth error — check MAINLAYER_API_KEY"
699
+ fi
700
+ \`\`\`
701
+
702
+ ### MCP Server Authentication
703
+
704
+ When using the Mainlayer MCP server via Claude Code, Cursor, or other AI platforms, set \`MAINLAYER_API_KEY\` in your environment. The MCP config written by postinstall uses \`"Authorization": "Bearer \${MAINLAYER_API_KEY}"\` with env var interpolation where supported.
705
+
706
+ ### Claude Desktop Users
707
+
708
+ Claude Desktop does **not** support remote HTTP MCP servers via \`claude_desktop_config.json\`. To connect Claude Desktop to the Mainlayer MCP server, use **Settings > Connectors** in the Claude Desktop UI and add: \`https://api.mainlayer.io/mcp\`.
709
+
710
+ ### OpenClaw Users
711
+
712
+ Add the Mainlayer MCP server manually to \`~/.openclaw/openclaw.json\` using the standard MCP server entry for your OpenClaw version.
713
+ `;
714
+ }
715
+ //#endregion
716
+ export { configurePlatforms as i, generateSkillsMd as n, PLATFORMS as r, SKILLS_FILENAME as t };