@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.
- package/LICENSE +11 -0
- package/README.md +167 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2330 -0
- package/dist/postinstall/index.d.ts +1 -0
- package/dist/postinstall/index.js +30 -0
- package/dist/skills-template-CBLbA5-E.js +716 -0
- package/package.json +60 -0
|
@@ -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 };
|