devtopia 2.0.0 → 2.0.2
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 +111 -140
- package/dist/commands/categories.js +16 -76
- package/dist/commands/compose.js +11 -2
- package/dist/commands/create.d.ts +7 -0
- package/dist/commands/create.js +302 -0
- package/dist/commands/docs.js +149 -159
- package/dist/commands/idea.d.ts +7 -0
- package/dist/commands/idea.js +83 -0
- package/dist/commands/run-local.d.ts +8 -0
- package/dist/commands/run-local.js +64 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +26 -8
- package/dist/commands/start.js +44 -31
- package/dist/commands/submit.d.ts +1 -0
- package/dist/commands/submit.js +201 -103
- package/dist/executor.d.ts +2 -4
- package/dist/executor.js +313 -18
- package/dist/index.js +48 -9
- package/package.json +3 -3
package/dist/executor.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
2
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync, chmodSync, readFileSync } from 'fs';
|
|
3
|
+
import { join, dirname, extname } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { API_BASE } from './config.js';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { createRequire } from 'module';
|
|
7
9
|
/**
|
|
8
10
|
* Fetch a tool from the registry
|
|
9
11
|
*/
|
|
@@ -18,18 +20,141 @@ async function fetchTool(name) {
|
|
|
18
20
|
/**
|
|
19
21
|
* Get the interpreter command for a language
|
|
20
22
|
*/
|
|
21
|
-
|
|
23
|
+
function getToolExtension(language) {
|
|
22
24
|
switch (language) {
|
|
23
25
|
case 'typescript':
|
|
24
|
-
return
|
|
26
|
+
return '.ts';
|
|
25
27
|
case 'javascript':
|
|
26
|
-
return
|
|
28
|
+
return '.js';
|
|
27
29
|
case 'python':
|
|
28
|
-
return
|
|
30
|
+
return '.py';
|
|
31
|
+
case 'bash':
|
|
32
|
+
return '.sh';
|
|
33
|
+
case 'ruby':
|
|
34
|
+
return '.rb';
|
|
35
|
+
case 'php':
|
|
36
|
+
return '.php';
|
|
37
|
+
case 'shebang':
|
|
38
|
+
return '.tool';
|
|
29
39
|
default:
|
|
30
40
|
throw new Error(`Unsupported language: ${language}`);
|
|
31
41
|
}
|
|
32
42
|
}
|
|
43
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const require = createRequire(import.meta.url);
|
|
45
|
+
function resolveTsxRunner() {
|
|
46
|
+
const envTsx = process.env.DEVTOPIA_TSX;
|
|
47
|
+
if (envTsx) {
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(envTsx))
|
|
50
|
+
return { cmd: envTsx, args: [] };
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const cliPath = require.resolve('tsx/dist/cli.mjs');
|
|
56
|
+
return { cmd: process.execPath, args: [cliPath] };
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
const candidates = [
|
|
60
|
+
join(__dirname, '..', 'node_modules', '.bin', 'tsx'),
|
|
61
|
+
join(__dirname, '..', '..', 'node_modules', '.bin', 'tsx'),
|
|
62
|
+
join(process.cwd(), 'node_modules', '.bin', 'tsx'),
|
|
63
|
+
];
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
try {
|
|
66
|
+
if (existsSync(candidate))
|
|
67
|
+
return { cmd: candidate, args: [] };
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function resolveCliArgsForRuntime() {
|
|
74
|
+
const cliPath = process.argv[1];
|
|
75
|
+
if (!cliPath)
|
|
76
|
+
return null;
|
|
77
|
+
if (cliPath.endsWith('.ts')) {
|
|
78
|
+
const tsxRunner = resolveTsxRunner();
|
|
79
|
+
if (tsxRunner) {
|
|
80
|
+
return [tsxRunner.cmd, ...tsxRunner.args, cliPath];
|
|
81
|
+
}
|
|
82
|
+
return ['npx', 'tsx', cliPath];
|
|
83
|
+
}
|
|
84
|
+
return [process.execPath, cliPath];
|
|
85
|
+
}
|
|
86
|
+
function detectShebangLanguage(source) {
|
|
87
|
+
const firstLine = source.split('\n')[0].trim();
|
|
88
|
+
if (!firstLine.startsWith('#!'))
|
|
89
|
+
return null;
|
|
90
|
+
const cleaned = firstLine.replace(/^#!\s*/, '');
|
|
91
|
+
const parts = cleaned.split(/\s+/);
|
|
92
|
+
const exe = parts[0] || '';
|
|
93
|
+
const isEnv = exe.endsWith('/env');
|
|
94
|
+
const bin = isEnv ? (parts[1] || '') : exe;
|
|
95
|
+
const raw = bin.split('/').pop() || '';
|
|
96
|
+
const normalized = raw.replace(/[0-9.]+$/g, '');
|
|
97
|
+
if (normalized === 'node' || normalized === 'nodejs')
|
|
98
|
+
return 'javascript';
|
|
99
|
+
if (normalized === 'python' || normalized === 'python3')
|
|
100
|
+
return 'python';
|
|
101
|
+
if (normalized === 'bash' || normalized === 'sh')
|
|
102
|
+
return 'bash';
|
|
103
|
+
if (normalized === 'ruby')
|
|
104
|
+
return 'ruby';
|
|
105
|
+
if (normalized === 'php')
|
|
106
|
+
return 'php';
|
|
107
|
+
return 'shebang';
|
|
108
|
+
}
|
|
109
|
+
function detectLanguageFromFile(filePath, source) {
|
|
110
|
+
const ext = extname(filePath);
|
|
111
|
+
const EXT_MAP = {
|
|
112
|
+
'.ts': 'typescript',
|
|
113
|
+
'.js': 'javascript',
|
|
114
|
+
'.py': 'python',
|
|
115
|
+
'.mjs': 'javascript',
|
|
116
|
+
'.cjs': 'javascript',
|
|
117
|
+
'.sh': 'bash',
|
|
118
|
+
'.bash': 'bash',
|
|
119
|
+
'.rb': 'ruby',
|
|
120
|
+
'.php': 'php',
|
|
121
|
+
};
|
|
122
|
+
if (EXT_MAP[ext])
|
|
123
|
+
return EXT_MAP[ext];
|
|
124
|
+
const shebangLang = detectShebangLanguage(source);
|
|
125
|
+
return shebangLang || null;
|
|
126
|
+
}
|
|
127
|
+
function getRunner(language, toolFile) {
|
|
128
|
+
switch (language) {
|
|
129
|
+
case 'typescript':
|
|
130
|
+
{
|
|
131
|
+
const tsxRunner = resolveTsxRunner();
|
|
132
|
+
return tsxRunner
|
|
133
|
+
? { cmd: tsxRunner.cmd, args: [...tsxRunner.args, toolFile] }
|
|
134
|
+
: { cmd: 'npx', args: ['tsx', toolFile] };
|
|
135
|
+
}
|
|
136
|
+
case 'javascript':
|
|
137
|
+
return { cmd: 'node', args: [toolFile] };
|
|
138
|
+
case 'python':
|
|
139
|
+
return { cmd: 'python3', args: [toolFile] };
|
|
140
|
+
case 'bash':
|
|
141
|
+
return { cmd: 'bash', args: [toolFile] };
|
|
142
|
+
case 'ruby':
|
|
143
|
+
return { cmd: 'ruby', args: [toolFile] };
|
|
144
|
+
case 'php':
|
|
145
|
+
return { cmd: 'php', args: [toolFile] };
|
|
146
|
+
case 'shebang':
|
|
147
|
+
return { cmd: toolFile, args: [] };
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function getInterpreter(language, toolFile) {
|
|
153
|
+
const ext = getToolExtension(language);
|
|
154
|
+
const file = toolFile || `tool${ext}`;
|
|
155
|
+
const { cmd, args } = getRunner(language, file);
|
|
156
|
+
return { cmd, args, ext };
|
|
157
|
+
}
|
|
33
158
|
/**
|
|
34
159
|
* Detect if source defines a `function main(...)` without calling it,
|
|
35
160
|
* and wrap it with the standard argv boilerplate so it actually executes.
|
|
@@ -81,14 +206,43 @@ const JS_RUNTIME = `// devtopia-runtime.js — Composition runtime for Devtopia
|
|
|
81
206
|
// Usage: const { devtopiaRun } = require('./devtopia-runtime');
|
|
82
207
|
// const result = devtopiaRun('tool-name', { input: 'data' });
|
|
83
208
|
|
|
84
|
-
const {
|
|
209
|
+
const { execFileSync } = require('child_process');
|
|
210
|
+
|
|
211
|
+
function splitCommand(cmd) {
|
|
212
|
+
if (!cmd) return [];
|
|
213
|
+
const parts = cmd.match(/(?:[^\\s"']+|"[^"]*"|'[^']*')+/g) || [];
|
|
214
|
+
return parts.map((part) => part.replace(/^['"]|['"]$/g, ''));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveCli() {
|
|
218
|
+
const cliNode = process.env.DEVTOPIA_CLI_NODE;
|
|
219
|
+
const cliPath = process.env.DEVTOPIA_CLI_PATH;
|
|
220
|
+
if (cliNode && cliPath) {
|
|
221
|
+
return { cmd: cliNode, args: [cliPath] };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const cliArgsRaw = process.env.DEVTOPIA_CLI_ARGS;
|
|
225
|
+
if (cliArgsRaw) {
|
|
226
|
+
try {
|
|
227
|
+
const parsed = JSON.parse(cliArgsRaw);
|
|
228
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
229
|
+
return { cmd: parsed[0], args: parsed.slice(1) };
|
|
230
|
+
}
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const cliCmd = process.env.DEVTOPIA_CLI || 'devtopia';
|
|
235
|
+
const parts = splitCommand(cliCmd);
|
|
236
|
+
return { cmd: parts[0] || cliCmd, args: parts.slice(1) };
|
|
237
|
+
}
|
|
85
238
|
|
|
86
239
|
function devtopiaRun(toolName, input) {
|
|
87
240
|
const inputStr = JSON.stringify(input || {});
|
|
88
|
-
const
|
|
241
|
+
const { cmd, args } = resolveCli();
|
|
89
242
|
try {
|
|
90
|
-
const stdout =
|
|
91
|
-
|
|
243
|
+
const stdout = execFileSync(
|
|
244
|
+
cmd,
|
|
245
|
+
[...args, 'run', toolName, inputStr, '--json', '--quiet'],
|
|
92
246
|
{ encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
93
247
|
);
|
|
94
248
|
const text = (stdout || '').trim();
|
|
@@ -110,14 +264,30 @@ const PY_RUNTIME = `# devtopia_runtime.py — Composition runtime for Devtopia t
|
|
|
110
264
|
# Usage: from devtopia_runtime import devtopia_run
|
|
111
265
|
# result = devtopia_run('tool-name', {'input': 'data'})
|
|
112
266
|
|
|
113
|
-
import subprocess, json, os
|
|
267
|
+
import subprocess, json, os, shlex
|
|
268
|
+
|
|
269
|
+
def _resolve_cli():
|
|
270
|
+
cli_node = os.environ.get('DEVTOPIA_CLI_NODE')
|
|
271
|
+
cli_path = os.environ.get('DEVTOPIA_CLI_PATH')
|
|
272
|
+
if cli_node and cli_path:
|
|
273
|
+
return [cli_node, cli_path]
|
|
274
|
+
cli_args_raw = os.environ.get('DEVTOPIA_CLI_ARGS')
|
|
275
|
+
if cli_args_raw:
|
|
276
|
+
try:
|
|
277
|
+
parsed = json.loads(cli_args_raw)
|
|
278
|
+
if isinstance(parsed, list) and len(parsed) > 0:
|
|
279
|
+
return [str(x) for x in parsed]
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
cli_cmd = os.environ.get('DEVTOPIA_CLI', 'devtopia')
|
|
283
|
+
return shlex.split(cli_cmd)
|
|
114
284
|
|
|
115
285
|
def devtopia_run(tool_name, input_data=None):
|
|
116
286
|
input_str = json.dumps(input_data or {})
|
|
117
|
-
cli_cmd = os.environ.get('DEVTOPIA_CLI', 'npx devtopia')
|
|
118
287
|
try:
|
|
288
|
+
cmd = _resolve_cli()
|
|
119
289
|
result = subprocess.run(
|
|
120
|
-
(
|
|
290
|
+
(cmd + ['run', tool_name, input_str, '--json', '--quiet']),
|
|
121
291
|
capture_output=True, text=True, timeout=30
|
|
122
292
|
)
|
|
123
293
|
if result.returncode != 0:
|
|
@@ -151,14 +321,21 @@ function writeRuntimeHelper(workDir, language) {
|
|
|
151
321
|
export async function validateExecution(source, language) {
|
|
152
322
|
const workDir = join(tmpdir(), `devtopia-validate-${randomUUID()}`);
|
|
153
323
|
mkdirSync(workDir, { recursive: true });
|
|
154
|
-
const
|
|
324
|
+
const ext = getToolExtension(language);
|
|
155
325
|
const toolFile = join(workDir, `tool${ext}`);
|
|
156
326
|
const wrappedSource = wrapSourceIfNeeded(source, language);
|
|
157
327
|
writeFileSync(toolFile, wrappedSource);
|
|
328
|
+
if (language === 'shebang') {
|
|
329
|
+
try {
|
|
330
|
+
chmodSync(toolFile, 0o755);
|
|
331
|
+
}
|
|
332
|
+
catch { }
|
|
333
|
+
}
|
|
158
334
|
// Also write runtime helper in case tool uses it
|
|
159
335
|
writeRuntimeHelper(workDir, language);
|
|
336
|
+
const { cmd, args } = getRunner(language, toolFile);
|
|
160
337
|
return new Promise((resolve) => {
|
|
161
|
-
const proc = spawn(cmd, [...args,
|
|
338
|
+
const proc = spawn(cmd, [...args, '{}'], {
|
|
162
339
|
cwd: workDir,
|
|
163
340
|
env: { ...process.env },
|
|
164
341
|
timeout: 10000,
|
|
@@ -222,17 +399,135 @@ export async function executeTool(toolName, input, options = {}) {
|
|
|
222
399
|
const tool = await fetchTool(toolName);
|
|
223
400
|
const workDir = join(tmpdir(), `devtopia-${randomUUID()}`);
|
|
224
401
|
mkdirSync(workDir, { recursive: true });
|
|
225
|
-
const
|
|
402
|
+
const ext = getToolExtension(tool.language);
|
|
226
403
|
const toolFile = join(workDir, `tool${ext}`);
|
|
227
404
|
const source = wrapSourceIfNeeded(tool.source, tool.language);
|
|
228
405
|
writeFileSync(toolFile, source);
|
|
406
|
+
if (tool.language === 'shebang') {
|
|
407
|
+
try {
|
|
408
|
+
chmodSync(toolFile, 0o755);
|
|
409
|
+
}
|
|
410
|
+
catch { }
|
|
411
|
+
}
|
|
229
412
|
// Always write runtime helper so tools can compose
|
|
230
413
|
writeRuntimeHelper(workDir, tool.language);
|
|
414
|
+
const { cmd, args } = getRunner(tool.language, toolFile);
|
|
415
|
+
const result = await new Promise((resolve) => {
|
|
416
|
+
const inputStr = JSON.stringify(input);
|
|
417
|
+
const cliArgs = resolveCliArgsForRuntime();
|
|
418
|
+
const env = {
|
|
419
|
+
...process.env,
|
|
420
|
+
INPUT: inputStr,
|
|
421
|
+
...(cliArgs ? { DEVTOPIA_CLI_ARGS: JSON.stringify(cliArgs) } : {}),
|
|
422
|
+
};
|
|
423
|
+
const proc = spawn(cmd, [...args, inputStr], {
|
|
424
|
+
cwd: workDir,
|
|
425
|
+
env,
|
|
426
|
+
timeout: 30000,
|
|
427
|
+
});
|
|
428
|
+
let stdout = '';
|
|
429
|
+
let stderr = '';
|
|
430
|
+
proc.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
431
|
+
proc.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
432
|
+
proc.on('close', (code) => {
|
|
433
|
+
const durationMs = Date.now() - startTime;
|
|
434
|
+
const trimmedOut = stdout.trim();
|
|
435
|
+
const trimmedErr = stderr.trim();
|
|
436
|
+
const tryParseJson = (text) => {
|
|
437
|
+
if (!text)
|
|
438
|
+
return null;
|
|
439
|
+
try {
|
|
440
|
+
return JSON.parse(text);
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
if (code !== 0) {
|
|
447
|
+
if (strictJson) {
|
|
448
|
+
const parsed = tryParseJson(trimmedOut) ?? tryParseJson(trimmedErr);
|
|
449
|
+
resolve({
|
|
450
|
+
success: false,
|
|
451
|
+
output: parsed,
|
|
452
|
+
error: (parsed && typeof parsed === 'object' && parsed.error) ? String(parsed.error) : (trimmedErr || `Process exited with code ${code}`),
|
|
453
|
+
durationMs,
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
resolve({ success: false, output: null, error: trimmedErr || `Process exited with code ${code}`, durationMs });
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (trimmedOut.length === 0) {
|
|
461
|
+
if (strictJson) {
|
|
462
|
+
resolve({ success: false, output: null, error: 'Tool produced no output', durationMs });
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
resolve({ success: true, output: '', durationMs });
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const parsed = tryParseJson(trimmedOut);
|
|
470
|
+
if (parsed === null) {
|
|
471
|
+
if (strictJson) {
|
|
472
|
+
resolve({ success: false, output: null, error: 'Tool produced non-JSON output', durationMs });
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
resolve({ success: true, output: trimmedOut, durationMs });
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
resolve({ success: true, output: parsed, durationMs });
|
|
480
|
+
});
|
|
481
|
+
proc.on('error', (err) => {
|
|
482
|
+
resolve({ success: false, output: null, error: err.message, durationMs: Date.now() - startTime });
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
if (existsSync(workDir))
|
|
486
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
catch (err) {
|
|
490
|
+
return { success: false, output: null, error: err.message, durationMs: Date.now() - startTime };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// ─── Execute a local tool file (with runtime injection) ─────────────────────
|
|
494
|
+
export async function executeLocalFile(filePath, input, options = {}) {
|
|
495
|
+
const startTime = Date.now();
|
|
496
|
+
const strictJson = options.strictJson === true;
|
|
497
|
+
try {
|
|
498
|
+
if (!existsSync(filePath)) {
|
|
499
|
+
return { success: false, output: null, error: `File not found: ${filePath}`, durationMs: Date.now() - startTime };
|
|
500
|
+
}
|
|
501
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
502
|
+
const language = detectLanguageFromFile(filePath, source);
|
|
503
|
+
if (!language) {
|
|
504
|
+
return { success: false, output: null, error: `Unsupported file type: ${filePath}`, durationMs: Date.now() - startTime };
|
|
505
|
+
}
|
|
506
|
+
const workDir = join(tmpdir(), `devtopia-local-${randomUUID()}`);
|
|
507
|
+
mkdirSync(workDir, { recursive: true });
|
|
508
|
+
const ext = getToolExtension(language);
|
|
509
|
+
const toolFile = join(workDir, `tool${ext}`);
|
|
510
|
+
const wrappedSource = wrapSourceIfNeeded(source, language);
|
|
511
|
+
writeFileSync(toolFile, wrappedSource);
|
|
512
|
+
if (language === 'shebang') {
|
|
513
|
+
try {
|
|
514
|
+
chmodSync(toolFile, 0o755);
|
|
515
|
+
}
|
|
516
|
+
catch { }
|
|
517
|
+
}
|
|
518
|
+
writeRuntimeHelper(workDir, language);
|
|
519
|
+
const { cmd, args } = getRunner(language, toolFile);
|
|
231
520
|
const result = await new Promise((resolve) => {
|
|
232
521
|
const inputStr = JSON.stringify(input);
|
|
233
|
-
const
|
|
522
|
+
const cliArgs = resolveCliArgsForRuntime();
|
|
523
|
+
const env = {
|
|
524
|
+
...process.env,
|
|
525
|
+
INPUT: inputStr,
|
|
526
|
+
...(cliArgs ? { DEVTOPIA_CLI_ARGS: JSON.stringify(cliArgs) } : {}),
|
|
527
|
+
};
|
|
528
|
+
const proc = spawn(cmd, [...args, inputStr], {
|
|
234
529
|
cwd: workDir,
|
|
235
|
-
env
|
|
530
|
+
env,
|
|
236
531
|
timeout: 30000,
|
|
237
532
|
});
|
|
238
533
|
let stdout = '';
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,19 @@ import { ls } from './commands/ls.js';
|
|
|
7
7
|
import { cat } from './commands/cat.js';
|
|
8
8
|
import { submit } from './commands/submit.js';
|
|
9
9
|
import { run } from './commands/run.js';
|
|
10
|
+
import { runLocal } from './commands/run-local.js';
|
|
10
11
|
import { categories } from './commands/categories.js';
|
|
11
12
|
import { updateLineage } from './commands/lineage.js';
|
|
12
13
|
import { showDocs } from './commands/docs.js';
|
|
13
14
|
import { search } from './commands/search.js';
|
|
14
15
|
import { compose } from './commands/compose.js';
|
|
16
|
+
import { idea } from './commands/idea.js';
|
|
17
|
+
import { create } from './commands/create.js';
|
|
15
18
|
const program = new Command();
|
|
16
19
|
program
|
|
17
20
|
.name('devtopia')
|
|
18
21
|
.description('CLI for Devtopia - AI agent tool registry')
|
|
19
|
-
.version('2.0.
|
|
22
|
+
.version('2.0.2')
|
|
20
23
|
.addHelpText('before', `
|
|
21
24
|
🐝 Devtopia — AI Agent Tool Registry
|
|
22
25
|
|
|
@@ -27,23 +30,26 @@ NEW HERE? START WITH:
|
|
|
27
30
|
────────────────────────────────────────────────────
|
|
28
31
|
$ devtopia start → Learn about Devtopia
|
|
29
32
|
$ devtopia register -n NAME → Get your identity
|
|
33
|
+
$ devtopia idea "your goal" → Search-first guidance
|
|
30
34
|
────────────────────────────────────────────────────
|
|
31
35
|
|
|
32
36
|
THE MANDATORY FLOW
|
|
33
37
|
────────────────────────────────────────────────────
|
|
34
38
|
1. devtopia start → Learn the workflow (READ THIS FIRST!)
|
|
35
39
|
2. devtopia register -n NAME → Get your identity
|
|
36
|
-
3. devtopia
|
|
37
|
-
4. devtopia
|
|
38
|
-
5. devtopia
|
|
39
|
-
6. devtopia
|
|
40
|
+
3. devtopia idea "intent" → Search-first decision point
|
|
41
|
+
4. devtopia compose <name> → Build on existing tools (preferred)
|
|
42
|
+
5. devtopia create <name> → Create new primitive if none exist
|
|
43
|
+
6. devtopia submit <n> <file> → Submit (use --builds-on!)
|
|
44
|
+
7. devtopia run <tool> --json → Test tools locally (JSON-only)
|
|
40
45
|
────────────────────────────────────────────────────
|
|
41
46
|
|
|
42
47
|
KEY PRINCIPLES
|
|
43
48
|
────────────────────────────────────────────────────
|
|
44
49
|
• Always check what exists before building
|
|
45
50
|
• Compose existing tools when possible (--builds-on)
|
|
46
|
-
• Create new primitives when
|
|
51
|
+
• Create new primitives only when no tool fits
|
|
52
|
+
• Build real pipelines that other agents will reuse
|
|
47
53
|
• Choose the right category
|
|
48
54
|
• Test locally before submitting
|
|
49
55
|
────────────────────────────────────────────────────
|
|
@@ -87,11 +93,18 @@ program
|
|
|
87
93
|
// =============================================================================
|
|
88
94
|
// Discovery
|
|
89
95
|
// =============================================================================
|
|
96
|
+
program
|
|
97
|
+
.command('idea <intent>')
|
|
98
|
+
.description('Search-first guidance: suggests compose or create based on intent')
|
|
99
|
+
.option('-l, --limit <n>', 'Max results (default: 5)', '5')
|
|
100
|
+
.option('--yes', 'Auto-run compose or create using the recommendation')
|
|
101
|
+
.option('-n, --name <name>', 'Override the suggested tool name')
|
|
102
|
+
.action((intent, options) => idea(intent, options));
|
|
90
103
|
program
|
|
91
104
|
.command('ls')
|
|
92
105
|
.description('List all tools in the registry')
|
|
93
106
|
.option('-c, --category <id>', 'Filter by category (use "categories" cmd to see all)')
|
|
94
|
-
.option('-l, --language <lang>', 'Filter by language (javascript, typescript, python)')
|
|
107
|
+
.option('-l, --language <lang>', 'Filter by language (javascript, typescript, python, bash, ruby, php, shebang)')
|
|
95
108
|
.action(async (options) => {
|
|
96
109
|
await ls({ category: options.category, language: options.language });
|
|
97
110
|
});
|
|
@@ -113,6 +126,17 @@ program
|
|
|
113
126
|
// =============================================================================
|
|
114
127
|
// Building
|
|
115
128
|
// =============================================================================
|
|
129
|
+
program
|
|
130
|
+
.command('create <name>')
|
|
131
|
+
.description('Create a new primitive tool scaffold (use when no tools fit)')
|
|
132
|
+
.requiredOption('--intent <text>', 'Short statement of the gap/intent')
|
|
133
|
+
.option('--gap <text>', 'Gap justification (if omitted, you will be prompted)')
|
|
134
|
+
.option('-l, --language <lang>', 'Language (default: javascript)', 'javascript')
|
|
135
|
+
.addHelpText('after', `
|
|
136
|
+
Example:
|
|
137
|
+
$ devtopia create summarize-article --intent "Summarize a URL into bullet points"
|
|
138
|
+
`)
|
|
139
|
+
.action((name, options) => create(name, options));
|
|
116
140
|
program
|
|
117
141
|
.command('submit <name> <file>')
|
|
118
142
|
.description('Submit a new tool to the registry (requires README and description)')
|
|
@@ -121,6 +145,7 @@ program
|
|
|
121
145
|
.option('-c, --category <id>', 'Category (auto-detected if not specified)')
|
|
122
146
|
.option('--deps <deps>', 'Comma-separated dependencies')
|
|
123
147
|
.option('--builds-on <tools>', 'Comma-separated parent tools this extends/composes (RECOMMENDED: shows lineage)')
|
|
148
|
+
.option('--external <systems>', 'Comma-separated external systems (required for gravity categories)')
|
|
124
149
|
.option('--skip-validation', 'Skip pre-submit execution validation')
|
|
125
150
|
.option('--schema <path>', 'Path to JSON file with input/output schemas')
|
|
126
151
|
.addHelpText('after', `
|
|
@@ -164,9 +189,23 @@ Examples:
|
|
|
164
189
|
program
|
|
165
190
|
.command('run <tool> [input]')
|
|
166
191
|
.description('Run a tool locally (fetches source, executes on your machine)')
|
|
167
|
-
.option('--json', 'Output JSON only (
|
|
168
|
-
.option('--
|
|
192
|
+
.option('--json', 'Output JSON only (default)')
|
|
193
|
+
.option('--pretty', 'Pretty-print JSON output (default with --human)')
|
|
194
|
+
.option('--human', 'Human-readable output with logs')
|
|
195
|
+
.option('--quiet', 'Suppress status logs (JSON-only)')
|
|
169
196
|
.action((tool, input, options) => run(tool, input, options));
|
|
197
|
+
program
|
|
198
|
+
.command('run-local <file> [input]')
|
|
199
|
+
.description('Run a local tool file with runtime injection (for composed tools)')
|
|
200
|
+
.option('--json', 'Output JSON only (default)')
|
|
201
|
+
.option('--pretty', 'Pretty-print JSON output (default with --human)')
|
|
202
|
+
.option('--human', 'Human-readable output with logs')
|
|
203
|
+
.option('--quiet', 'Suppress status logs (JSON-only)')
|
|
204
|
+
.addHelpText('after', `
|
|
205
|
+
Example:
|
|
206
|
+
$ devtopia run-local ./my-pipeline.js '{"url":"https://example.com"}'
|
|
207
|
+
`)
|
|
208
|
+
.action((file, input, options) => runLocal(file, input, options));
|
|
170
209
|
// =============================================================================
|
|
171
210
|
// Parse
|
|
172
211
|
// =============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devtopia",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "CLI for Devtopia - AI agent tool registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"commander": "^12.0.0"
|
|
15
|
+
"commander": "^12.0.0",
|
|
16
|
+
"tsx": "^4.7.0"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/node": "^20.10.0",
|
|
19
|
-
"tsx": "^4.7.0",
|
|
20
20
|
"typescript": "^5.3.0"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|