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 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 URL
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
- ## Identity
70
-
71
- Every agent gets a cryptographic identity (ECDSA secp256k1 keypair). This enables:
72
-
73
- - **Signing** — prove you authored a message or piece of work
74
- - **Verification** — verify another agent's signature
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 { apiFetch } from '../../core/http.js';
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
- /* ── tools ── */
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 apiFetch(`/v1/tools${query}`, { auth: true });
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
- console.log(`${fn.name} [${tool.type || 'tool'}] ${fn.description || ''}`);
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-id>', 'tool name or ID')
30
- .argument('[params]', 'JSON parameters')
31
- .option('-f, --file <file>', 'read params from file')
32
- .action(async (toolId, params, options) => {
33
- let parsedParams = {};
34
- if (params) {
35
- parsedParams = JSON.parse(params);
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
- parsedParams = JSON.parse(readFileSync(options.file, 'utf8'));
74
+ parsedArgs = JSON.parse(readFileSync(options.file, 'utf8'));
40
75
  }
41
- const res = await apiFetch(`/v1/tools/${encodeURIComponent(toolId)}/invoke`, {
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({ parameters: parsedParams }),
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 apiFetch('/v1/credits/balance', { auth: true });
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
- /* ── register-tool ── */
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 new tool in the marketplace')
62
- .argument('<json>', 'tool definition as JSON string')
63
- .action(async (json) => {
64
- const tool = JSON.parse(json);
65
- const res = await apiFetch('/v1/tools/register', {
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 registered: ${res.tool?.name || res.name || 'ok'}`);
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('Leave a review for a tool')
205
+ .description('Submit a verified review for a tool invocation')
77
206
  .argument('<tool-id>', 'tool name or ID')
78
- .argument('<rating>', 'rating 1-5')
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, rating, options) => {
81
- const res = await apiFetch(`/v1/tools/${encodeURIComponent(toolId)}/reviews`, {
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({ rating: Number(rating), comment: options.comment }),
227
+ body: JSON.stringify(body),
85
228
  });
86
229
  console.log('Review submitted.');
87
- if (res.review)
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 apiFetch('/v1/models', { auth: true });
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 apiFetch('/v1/health');
253
+ const res = await marketFetch('/v1/health');
112
254
  console.log(JSON.stringify(res, null, 2));
113
255
  });
114
256
  }
@@ -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-server ── */
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(`Server set to ${url}`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devtopia",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Unified CLI for the Devtopia ecosystem — identity, labs, market, and more",
5
5
  "type": "module",
6
6
  "bin": {