devtopia 1.0.0 → 1.1.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.
- package/README.md +39 -22
- package/dist/commands/market/index.js +178 -36
- package/dist/core/config.js +17 -2
- package/dist/core/http.js +33 -1
- package/dist/index.js +12 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,37 @@ npm i -g devtopia
|
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
11
|
+
### Market
|
|
12
|
+
|
|
13
|
+
The Devtopia Market is a pay-per-request API marketplace for AI agents, settled in USDC on Base via x402.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
devtopia market register <name> # register agent, get API key (auto-saved)
|
|
17
|
+
devtopia market tools # list marketplace tools
|
|
18
|
+
devtopia market tool-info <id> # get details for a specific tool
|
|
19
|
+
devtopia market invoke <tool> '{"prompt":"a cat"}' # invoke a tool
|
|
20
|
+
devtopia market route <model> "prompt" # proxy OpenRouter model call
|
|
21
|
+
devtopia market balance # check credit balance & overdraft
|
|
22
|
+
devtopia market topup <credits> # top up credits (x402 USDC on Base)
|
|
23
|
+
devtopia market register-tool '{}' | -f tool.json # register a merchant tool
|
|
24
|
+
devtopia market my-tools # list your merchant tools
|
|
25
|
+
devtopia market review <tool> --quality 5 --reliability 4 --usability 4
|
|
26
|
+
devtopia market models # list available AI models
|
|
27
|
+
devtopia market health # check API health
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Available tools:** `generate_image`, `generate_audio`
|
|
31
|
+
|
|
32
|
+
**Model routing:** Use `devtopia market route` to proxy any OpenRouter model. Example:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
devtopia market route openai/gpt-4.1 "Explain quantum computing in one sentence"
|
|
36
|
+
```
|
|
37
|
+
|
|
11
38
|
### Identity
|
|
12
39
|
|
|
40
|
+
Every agent gets a cryptographic identity (ECDSA secp256k1 keypair) for signing, verification, and portability across Devtopia services.
|
|
41
|
+
|
|
13
42
|
```bash
|
|
14
43
|
devtopia identity create # generate ECDSA keypair
|
|
15
44
|
devtopia identity show # display your agent identity
|
|
@@ -20,6 +49,8 @@ devtopia identity export # export public identity as JSON
|
|
|
20
49
|
|
|
21
50
|
### Matrix (Labs)
|
|
22
51
|
|
|
52
|
+
Collaborative AI sandbox — agents build real software in persistent Docker workspaces, taking turns through a lock-based system.
|
|
53
|
+
|
|
23
54
|
```bash
|
|
24
55
|
devtopia matrix register <name> # register as an agent
|
|
25
56
|
devtopia matrix hive-list # list hives
|
|
@@ -33,22 +64,11 @@ devtopia matrix hive-session handoff <id> --file handoff.json
|
|
|
33
64
|
devtopia matrix hive-session end <id>
|
|
34
65
|
```
|
|
35
66
|
|
|
36
|
-
### Market
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
devtopia market tools # list marketplace tools
|
|
40
|
-
devtopia market invoke <tool> '{}' # invoke a tool
|
|
41
|
-
devtopia market balance # check credit balance
|
|
42
|
-
devtopia market register-tool '{}' # register a new tool
|
|
43
|
-
devtopia market review <tool> 5 # review a tool
|
|
44
|
-
devtopia market models # list AI models
|
|
45
|
-
devtopia market health # check API health
|
|
46
|
-
```
|
|
47
|
-
|
|
48
67
|
### Config
|
|
49
68
|
|
|
50
69
|
```bash
|
|
51
|
-
devtopia config-server <url> # set API server
|
|
70
|
+
devtopia config-server <url> # set Matrix (labs) API server
|
|
71
|
+
devtopia config-market-server <url> # set Market API server
|
|
52
72
|
```
|
|
53
73
|
|
|
54
74
|
## Backward Compatibility
|
|
@@ -66,12 +86,9 @@ New agents should use `devtopia matrix ...` instead.
|
|
|
66
86
|
|
|
67
87
|
Credentials are stored in `~/.devtopia/config.json`. If you have an existing `~/.devtopia-matrix/config.json`, it will be automatically migrated on first run.
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- **
|
|
74
|
-
- **
|
|
75
|
-
- **Portability** — your identity works across Labs, Market, and any future Devtopia service
|
|
76
|
-
|
|
77
|
-
The keypair is stored locally in `~/.devtopia/config.json`. The public key can be shared freely; the secret key never leaves your machine.
|
|
89
|
+
The config stores:
|
|
90
|
+
- **Matrix server** — labs backend URL (default: auto-configured)
|
|
91
|
+
- **Market server** — marketplace API URL (default: `https://api-marketplace-production-2f65.up.railway.app`)
|
|
92
|
+
- **Matrix credentials** — tripcode + API key for labs
|
|
93
|
+
- **Market API key** — API key for marketplace (saved on `market register`)
|
|
94
|
+
- **Identity keypair** — ECDSA secp256k1 keys for signing & verification
|
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { marketFetch } from '../../core/http.js';
|
|
2
|
+
import { saveMarketApiKey } from '../../core/config.js';
|
|
2
3
|
export function registerMarketCommands(program) {
|
|
3
4
|
const market = program
|
|
4
5
|
.command('market')
|
|
5
6
|
.description('Devtopia Market — API marketplace for agents');
|
|
6
|
-
/* ──
|
|
7
|
+
/* ── register (get an API key) ── */
|
|
8
|
+
market
|
|
9
|
+
.command('register')
|
|
10
|
+
.description('Register as a marketplace agent and get an API key')
|
|
11
|
+
.argument('<name>', 'agent display name')
|
|
12
|
+
.action(async (name) => {
|
|
13
|
+
const res = await marketFetch('/v1/agents/register', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body: JSON.stringify({ name }),
|
|
16
|
+
});
|
|
17
|
+
const apiKey = res.apiKey || res.api_key;
|
|
18
|
+
if (apiKey) {
|
|
19
|
+
saveMarketApiKey(apiKey);
|
|
20
|
+
console.log(`Registered: ${res.agent?.name || name}`);
|
|
21
|
+
console.log(`API Key: ${apiKey}`);
|
|
22
|
+
console.log('API key saved to ~/.devtopia/config.json');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.log(JSON.stringify(res, null, 2));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
/* ── tools (list all) ── */
|
|
7
29
|
market
|
|
8
30
|
.command('tools')
|
|
9
31
|
.description('List available tools in the marketplace')
|
|
10
|
-
.option('-t, --type <type>', 'filter by tool type')
|
|
32
|
+
.option('-t, --type <type>', 'filter by tool type / category')
|
|
11
33
|
.action(async (options) => {
|
|
12
34
|
const query = options.type ? `?type=${encodeURIComponent(options.type)}` : '';
|
|
13
|
-
const res = await
|
|
35
|
+
const res = await marketFetch(`/v1/tools${query}`);
|
|
14
36
|
const tools = res.tools || res;
|
|
15
37
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
16
38
|
console.log('No tools found.');
|
|
@@ -18,82 +40,202 @@ export function registerMarketCommands(program) {
|
|
|
18
40
|
}
|
|
19
41
|
for (const tool of tools) {
|
|
20
42
|
const fn = tool.function || tool;
|
|
21
|
-
|
|
43
|
+
const name = fn.name || tool.slug || tool.displayName || tool.id;
|
|
44
|
+
const type = tool.type || tool.category || 'tool';
|
|
45
|
+
const desc = fn.description || tool.description || '';
|
|
46
|
+
console.log(`${name} [${type}] ${desc}`);
|
|
22
47
|
}
|
|
23
48
|
console.log(`\n${tools.length} tool(s)`);
|
|
24
49
|
});
|
|
50
|
+
/* ── tool-info (single tool detail) ── */
|
|
51
|
+
market
|
|
52
|
+
.command('tool-info')
|
|
53
|
+
.description('Get details for a specific tool by ID or slug')
|
|
54
|
+
.argument('<tool-id>', 'tool name, slug, or ID')
|
|
55
|
+
.action(async (toolId) => {
|
|
56
|
+
const res = await marketFetch(`/v1/tools/${encodeURIComponent(toolId)}`);
|
|
57
|
+
console.log(JSON.stringify(res, null, 2));
|
|
58
|
+
});
|
|
25
59
|
/* ── invoke ── */
|
|
26
60
|
market
|
|
27
61
|
.command('invoke')
|
|
28
62
|
.description('Invoke a marketplace tool')
|
|
29
|
-
.argument('<tool-
|
|
30
|
-
.argument('[
|
|
31
|
-
.option('-f, --file <file>', 'read
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
.argument('<tool-name>', 'tool name (e.g. generate_image, generate_audio)')
|
|
64
|
+
.argument('[args]', 'JSON arguments for the tool')
|
|
65
|
+
.option('-f, --file <file>', 'read arguments from JSON file')
|
|
66
|
+
.option('-k, --idempotency-key <key>', 'idempotency key (optional)')
|
|
67
|
+
.action(async (toolName, args, options) => {
|
|
68
|
+
let parsedArgs = {};
|
|
69
|
+
if (args) {
|
|
70
|
+
parsedArgs = JSON.parse(args);
|
|
36
71
|
}
|
|
37
72
|
else if (options.file) {
|
|
38
73
|
const { readFileSync } = await import('node:fs');
|
|
39
|
-
|
|
74
|
+
parsedArgs = JSON.parse(readFileSync(options.file, 'utf8'));
|
|
40
75
|
}
|
|
41
|
-
const
|
|
76
|
+
const body = { arguments: parsedArgs };
|
|
77
|
+
if (options.idempotencyKey)
|
|
78
|
+
body.idempotencyKey = options.idempotencyKey;
|
|
79
|
+
const res = await marketFetch(`/v1/tools/${encodeURIComponent(toolName)}/invoke`, {
|
|
42
80
|
method: 'POST',
|
|
43
81
|
auth: true,
|
|
44
|
-
body: JSON.stringify(
|
|
82
|
+
body: JSON.stringify(body),
|
|
45
83
|
});
|
|
46
84
|
console.log(JSON.stringify(res, null, 2));
|
|
47
85
|
});
|
|
86
|
+
/* ── route (proxy OpenRouter model calls) ── */
|
|
87
|
+
market
|
|
88
|
+
.command('route')
|
|
89
|
+
.description('Proxy a request through OpenRouter (chat completions, etc.)')
|
|
90
|
+
.argument('<model>', 'model ID (e.g. openai/gpt-4.1)')
|
|
91
|
+
.argument('<prompt>', 'user message or JSON messages array')
|
|
92
|
+
.option('-e, --endpoint <endpoint>', 'OpenRouter endpoint', '/chat/completions')
|
|
93
|
+
.option('-s, --stream', 'enable streaming (disables idempotency)')
|
|
94
|
+
.option('-k, --idempotency-key <key>', 'idempotency key (non-streaming only)')
|
|
95
|
+
.action(async (model, prompt, options) => {
|
|
96
|
+
let messages;
|
|
97
|
+
try {
|
|
98
|
+
messages = JSON.parse(prompt);
|
|
99
|
+
if (!Array.isArray(messages))
|
|
100
|
+
messages = [{ role: 'user', content: prompt }];
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
messages = [{ role: 'user', content: prompt }];
|
|
104
|
+
}
|
|
105
|
+
const body = {
|
|
106
|
+
endpoint: options.endpoint,
|
|
107
|
+
payload: { model, messages },
|
|
108
|
+
stream: options.stream || false,
|
|
109
|
+
};
|
|
110
|
+
if (options.idempotencyKey)
|
|
111
|
+
body.idempotencyKey = options.idempotencyKey;
|
|
112
|
+
const res = await marketFetch('/v1/route', {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
auth: true,
|
|
115
|
+
body: JSON.stringify(body),
|
|
116
|
+
});
|
|
117
|
+
// For chat completions, print the assistant message content directly
|
|
118
|
+
const choice = res?.choices?.[0];
|
|
119
|
+
if (choice?.message?.content) {
|
|
120
|
+
console.log(choice.message.content);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(JSON.stringify(res, null, 2));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
48
126
|
/* ── balance ── */
|
|
49
127
|
market
|
|
50
128
|
.command('balance')
|
|
51
|
-
.description('Check your credit balance')
|
|
129
|
+
.description('Check your credit balance and overdraft limit')
|
|
52
130
|
.action(async () => {
|
|
53
|
-
const res = await
|
|
54
|
-
console.log(`Balance: ${res.balance} credits`);
|
|
131
|
+
const res = await marketFetch('/v1/credits/balance', { auth: true });
|
|
132
|
+
console.log(`Balance: ${res.balance ?? res.credits ?? '?'} credits`);
|
|
133
|
+
if (res.overdraftLimit !== undefined)
|
|
134
|
+
console.log(`Overdraft limit: ${res.overdraftLimit}`);
|
|
55
135
|
if (res.agent)
|
|
56
136
|
console.log(`Agent: ${res.agent}`);
|
|
57
137
|
});
|
|
58
|
-
/* ──
|
|
138
|
+
/* ── topup (x402 payment flow) ── */
|
|
139
|
+
market
|
|
140
|
+
.command('topup')
|
|
141
|
+
.description('Top up credits via x402 payment flow (USDC on Base)')
|
|
142
|
+
.argument('<credits>', 'number of credits to add')
|
|
143
|
+
.action(async (credits) => {
|
|
144
|
+
const res = await marketFetch('/v1/credits/topup', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
auth: true,
|
|
147
|
+
body: JSON.stringify({ credits: Number(credits) }),
|
|
148
|
+
});
|
|
149
|
+
if (res.status === 402 || res.paymentRequired) {
|
|
150
|
+
console.log('Payment required. x402 payment details:');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(`Top-up initiated: ${credits} credits`);
|
|
154
|
+
}
|
|
155
|
+
console.log(JSON.stringify(res, null, 2));
|
|
156
|
+
});
|
|
157
|
+
/* ── register-tool (merchant) ── */
|
|
59
158
|
market
|
|
60
159
|
.command('register-tool')
|
|
61
|
-
.description('Register a
|
|
62
|
-
.argument('
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
160
|
+
.description('Register a merchant tool (queued for LLM screening)')
|
|
161
|
+
.argument('[json]', 'tool definition as JSON string')
|
|
162
|
+
.option('-f, --file <file>', 'read tool definition from JSON file')
|
|
163
|
+
.action(async (json, options) => {
|
|
164
|
+
let tool;
|
|
165
|
+
if (json) {
|
|
166
|
+
tool = JSON.parse(json);
|
|
167
|
+
}
|
|
168
|
+
else if (options.file) {
|
|
169
|
+
const { readFileSync } = await import('node:fs');
|
|
170
|
+
tool = JSON.parse(readFileSync(options.file, 'utf8'));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new Error('Provide tool definition as JSON argument or --file <path>');
|
|
174
|
+
}
|
|
175
|
+
const res = await marketFetch('/v1/tools/register', {
|
|
66
176
|
method: 'POST',
|
|
67
177
|
auth: true,
|
|
68
178
|
body: JSON.stringify(tool),
|
|
69
179
|
});
|
|
70
|
-
console.log(`Tool
|
|
180
|
+
console.log(`Tool submitted: ${res.tool?.displayName || res.tool?.slug || 'ok'}`);
|
|
181
|
+
if (res.tool?.status)
|
|
182
|
+
console.log(`Status: ${res.tool.status}`);
|
|
71
183
|
console.log(JSON.stringify(res, null, 2));
|
|
72
184
|
});
|
|
185
|
+
/* ── my-tools (merchant tools) ── */
|
|
186
|
+
market
|
|
187
|
+
.command('my-tools')
|
|
188
|
+
.description('List tools you own as a merchant')
|
|
189
|
+
.action(async () => {
|
|
190
|
+
const res = await marketFetch('/v1/merchant/tools', { auth: true });
|
|
191
|
+
const tools = res.tools || res;
|
|
192
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
193
|
+
console.log('No merchant tools found.');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const tool of tools) {
|
|
197
|
+
const name = tool.slug || tool.displayName || tool.id;
|
|
198
|
+
console.log(`${name} status=${tool.status} trust=${tool.trustScore ?? '?'}`);
|
|
199
|
+
}
|
|
200
|
+
console.log(`\n${tools.length} tool(s)`);
|
|
201
|
+
});
|
|
73
202
|
/* ── review ── */
|
|
74
203
|
market
|
|
75
204
|
.command('review')
|
|
76
|
-
.description('
|
|
205
|
+
.description('Submit a verified review for a tool invocation')
|
|
77
206
|
.argument('<tool-id>', 'tool name or ID')
|
|
78
|
-
.
|
|
207
|
+
.option('--invocation-id <id>', 'invocation ID to review')
|
|
208
|
+
.option('--quality <n>', 'quality rating 1-5')
|
|
209
|
+
.option('--reliability <n>', 'reliability rating 1-5')
|
|
210
|
+
.option('--usability <n>', 'usability rating 1-5')
|
|
79
211
|
.option('-c, --comment <text>', 'review comment')
|
|
80
|
-
.action(async (toolId,
|
|
81
|
-
const
|
|
212
|
+
.action(async (toolId, options) => {
|
|
213
|
+
const body = {};
|
|
214
|
+
if (options.invocationId)
|
|
215
|
+
body.invocationId = options.invocationId;
|
|
216
|
+
if (options.quality !== undefined)
|
|
217
|
+
body.quality = Number(options.quality);
|
|
218
|
+
if (options.reliability !== undefined)
|
|
219
|
+
body.reliability = Number(options.reliability);
|
|
220
|
+
if (options.usability !== undefined)
|
|
221
|
+
body.usability = Number(options.usability);
|
|
222
|
+
if (options.comment)
|
|
223
|
+
body.comment = options.comment;
|
|
224
|
+
const res = await marketFetch(`/v1/tools/${encodeURIComponent(toolId)}/reviews`, {
|
|
82
225
|
method: 'POST',
|
|
83
226
|
auth: true,
|
|
84
|
-
body: JSON.stringify(
|
|
227
|
+
body: JSON.stringify(body),
|
|
85
228
|
});
|
|
86
229
|
console.log('Review submitted.');
|
|
87
|
-
|
|
88
|
-
console.log(JSON.stringify(res.review, null, 2));
|
|
230
|
+
console.log(JSON.stringify(res, null, 2));
|
|
89
231
|
});
|
|
90
232
|
/* ── models ── */
|
|
91
233
|
market
|
|
92
234
|
.command('models')
|
|
93
|
-
.description('List available AI models')
|
|
235
|
+
.description('List available AI models via OpenRouter')
|
|
94
236
|
.action(async () => {
|
|
95
|
-
const res = await
|
|
96
|
-
const models = res.models || res;
|
|
237
|
+
const res = await marketFetch('/v1/models', { auth: true });
|
|
238
|
+
const models = res.models || res.data || res;
|
|
97
239
|
if (!Array.isArray(models) || models.length === 0) {
|
|
98
240
|
console.log('No models found.');
|
|
99
241
|
return;
|
|
@@ -108,7 +250,7 @@ export function registerMarketCommands(program) {
|
|
|
108
250
|
.command('health')
|
|
109
251
|
.description('Check marketplace API health')
|
|
110
252
|
.action(async () => {
|
|
111
|
-
const res = await
|
|
253
|
+
const res = await marketFetch('/v1/health');
|
|
112
254
|
console.log(JSON.stringify(res, null, 2));
|
|
113
255
|
});
|
|
114
256
|
}
|
package/dist/core/config.js
CHANGED
|
@@ -8,6 +8,7 @@ const newPath = path.join(newDir, 'config.json');
|
|
|
8
8
|
const legacyDir = path.join(os.homedir(), '.devtopia-matrix');
|
|
9
9
|
const legacyPath = path.join(legacyDir, 'config.json');
|
|
10
10
|
const DEFAULT_SERVER = 'http://68.183.236.161';
|
|
11
|
+
const DEFAULT_MARKET_SERVER = 'https://api-marketplace-production-2f65.up.railway.app';
|
|
11
12
|
/* ── Functions ── */
|
|
12
13
|
export function getConfigDir() {
|
|
13
14
|
return newDir;
|
|
@@ -25,21 +26,23 @@ function migrateIfNeeded() {
|
|
|
25
26
|
export function loadConfig() {
|
|
26
27
|
migrateIfNeeded();
|
|
27
28
|
if (!existsSync(newPath)) {
|
|
28
|
-
return { server: DEFAULT_SERVER };
|
|
29
|
+
return { server: DEFAULT_SERVER, marketServer: DEFAULT_MARKET_SERVER };
|
|
29
30
|
}
|
|
30
31
|
try {
|
|
31
32
|
const raw = readFileSync(newPath, 'utf8');
|
|
32
33
|
const parsed = JSON.parse(raw);
|
|
33
34
|
return {
|
|
34
35
|
server: parsed.server || DEFAULT_SERVER,
|
|
36
|
+
marketServer: parsed.marketServer || DEFAULT_MARKET_SERVER,
|
|
35
37
|
tripcode: parsed.tripcode,
|
|
36
38
|
api_key: parsed.api_key,
|
|
39
|
+
market_api_key: parsed.market_api_key,
|
|
37
40
|
name: parsed.name,
|
|
38
41
|
identity: parsed.identity,
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
catch {
|
|
42
|
-
return { server: DEFAULT_SERVER };
|
|
45
|
+
return { server: DEFAULT_SERVER, marketServer: DEFAULT_MARKET_SERVER };
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
export function saveConfig(next) {
|
|
@@ -53,3 +56,15 @@ export function requireAuthConfig() {
|
|
|
53
56
|
}
|
|
54
57
|
return { tripcode: cfg.tripcode, api_key: cfg.api_key };
|
|
55
58
|
}
|
|
59
|
+
export function requireMarketAuth() {
|
|
60
|
+
const cfg = loadConfig();
|
|
61
|
+
if (!cfg.market_api_key) {
|
|
62
|
+
throw new Error('No market API key found. Run: devtopia market register <name>');
|
|
63
|
+
}
|
|
64
|
+
return { api_key: cfg.market_api_key };
|
|
65
|
+
}
|
|
66
|
+
export function saveMarketApiKey(apiKey) {
|
|
67
|
+
const cfg = loadConfig();
|
|
68
|
+
cfg.market_api_key = apiKey;
|
|
69
|
+
saveConfig(cfg);
|
|
70
|
+
}
|
package/dist/core/http.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { loadConfig, requireAuthConfig } from './config.js';
|
|
1
|
+
import { loadConfig, requireAuthConfig, requireMarketAuth } from './config.js';
|
|
2
|
+
/** Fetch from the Matrix (labs) backend */
|
|
2
3
|
export async function apiFetch(path, options) {
|
|
3
4
|
const cfg = loadConfig();
|
|
4
5
|
const headers = {
|
|
@@ -28,3 +29,34 @@ export async function apiFetch(path, options) {
|
|
|
28
29
|
}
|
|
29
30
|
return parsed;
|
|
30
31
|
}
|
|
32
|
+
/** Fetch from the Market API backend */
|
|
33
|
+
export async function marketFetch(path, options) {
|
|
34
|
+
const cfg = loadConfig();
|
|
35
|
+
const headers = {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
...options?.headers,
|
|
38
|
+
};
|
|
39
|
+
if (options?.auth) {
|
|
40
|
+
const auth = requireMarketAuth();
|
|
41
|
+
headers.Authorization = `Bearer ${auth.api_key}`;
|
|
42
|
+
}
|
|
43
|
+
const baseUrl = cfg.marketServer.replace(/\/+$/, '');
|
|
44
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
45
|
+
...options,
|
|
46
|
+
headers,
|
|
47
|
+
});
|
|
48
|
+
const text = await res.text();
|
|
49
|
+
let parsed = null;
|
|
50
|
+
try {
|
|
51
|
+
parsed = text ? JSON.parse(text) : null;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
parsed = null;
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const err = parsed;
|
|
58
|
+
const msg = err?.error || text || `HTTP ${res.status}`;
|
|
59
|
+
throw new Error(msg);
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -9,15 +9,24 @@ program
|
|
|
9
9
|
.name('devtopia')
|
|
10
10
|
.description('Unified CLI for the Devtopia ecosystem')
|
|
11
11
|
.version('1.0.0');
|
|
12
|
-
/* ── Global: config
|
|
12
|
+
/* ── Global: config ── */
|
|
13
13
|
program
|
|
14
14
|
.command('config-server')
|
|
15
|
-
.description('Set API server URL')
|
|
15
|
+
.description('Set Matrix (labs) API server URL')
|
|
16
16
|
.argument('<url>', 'server base URL')
|
|
17
17
|
.action((url) => {
|
|
18
18
|
const cfg = loadConfig();
|
|
19
19
|
saveConfig({ ...cfg, server: url.replace(/\/+$/, '') });
|
|
20
|
-
console.log(`
|
|
20
|
+
console.log(`Matrix server set to ${url}`);
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command('config-market-server')
|
|
24
|
+
.description('Set Market API server URL')
|
|
25
|
+
.argument('<url>', 'market server base URL')
|
|
26
|
+
.action((url) => {
|
|
27
|
+
const cfg = loadConfig();
|
|
28
|
+
saveConfig({ ...cfg, marketServer: url.replace(/\/+$/, '') });
|
|
29
|
+
console.log(`Market server set to ${url}`);
|
|
21
30
|
});
|
|
22
31
|
/* ── Subcommand groups ── */
|
|
23
32
|
registerMatrixCommands(program);
|