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/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
- export function getInterpreter(language) {
23
+ function getToolExtension(language) {
22
24
  switch (language) {
23
25
  case 'typescript':
24
- return { cmd: 'npx', args: ['tsx'], ext: '.ts' };
26
+ return '.ts';
25
27
  case 'javascript':
26
- return { cmd: 'node', args: [], ext: '.js' };
28
+ return '.js';
27
29
  case 'python':
28
- return { cmd: 'python3', args: [], ext: '.py' };
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 { execSync } = require('child_process');
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 cliCmd = process.env.DEVTOPIA_CLI || 'npx devtopia';
241
+ const { cmd, args } = resolveCli();
89
242
  try {
90
- const stdout = execSync(
91
- \`\${cliCmd} run \${toolName} '\${inputStr.replace(/'/g, "'\\\\''")}' --json --quiet\`,
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
- (cli_cmd.split() + ['run', tool_name, input_str, '--json', '--quiet']),
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 { cmd, args, ext } = getInterpreter(language);
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, toolFile, '{}'], {
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 { cmd, args, ext } = getInterpreter(tool.language);
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 proc = spawn(cmd, [...args, toolFile, inputStr], {
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: { ...process.env, INPUT: inputStr },
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.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 ls Discover existing tools
37
- 4. devtopia cat <tool> Read tool source & understand
38
- 5. devtopia submit <n> <file> Build & submit (use --builds-on!)
39
- 6. devtopia run <tool> '{...}' Test tools locally
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 needed (standalone tools)
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 (no logs). Errors return {"error":"..."}')
168
- .option('--quiet', 'Suppress status logs (prints only tool output)')
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.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": [