devtopia 1.9.0 → 2.0.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.
@@ -0,0 +1,5 @@
1
+ interface ComposeOptions {
2
+ uses: string;
3
+ }
4
+ export declare function compose(name: string, options: ComposeOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,120 @@
1
+ import { writeFileSync, existsSync } from 'fs';
2
+ import { API_BASE } from '../config.js';
3
+ export async function compose(name, options) {
4
+ if (!options.uses) {
5
+ console.log(`\n❌ --uses is required. Specify parent tools to compose.`);
6
+ console.log(` Example: devtopia compose my-pipeline --uses json-validate,json-flatten\n`);
7
+ process.exit(1);
8
+ }
9
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
10
+ console.log(`\n❌ Tool name must be lowercase, alphanumeric with hyphens.\n`);
11
+ process.exit(1);
12
+ }
13
+ const parentNames = options.uses.split(',').map(s => s.trim()).filter(Boolean);
14
+ if (parentNames.length === 0) {
15
+ console.log(`\n❌ No parent tools specified.\n`);
16
+ process.exit(1);
17
+ }
18
+ // Verify each parent tool exists and fetch descriptions
19
+ console.log(`\n Verifying parent tools...`);
20
+ const parents = [];
21
+ for (const parentName of parentNames) {
22
+ try {
23
+ const res = await fetch(`${API_BASE}/api/tools/${parentName}`);
24
+ if (!res.ok) {
25
+ console.log(` ❌ Tool "${parentName}" not found in registry.`);
26
+ process.exit(1);
27
+ }
28
+ const tool = await res.json();
29
+ parents.push({ name: tool.name, description: tool.description || 'No description' });
30
+ console.log(` ✓ ${tool.name} — ${(tool.description || '').slice(0, 50)}`);
31
+ }
32
+ catch {
33
+ console.log(` ❌ Could not verify "${parentName}" — server unreachable.`);
34
+ process.exit(1);
35
+ }
36
+ }
37
+ // Generate scaffold JS
38
+ const stepsCode = parents.map((p, i) => {
39
+ const varName = p.name.replace(/-/g, '_');
40
+ return ` // Step ${i + 1}: ${p.description}
41
+ const ${varName}_result = devtopiaRun('${p.name}', { /* TODO: pass input */ });`;
42
+ }).join('\n\n');
43
+ const jsSource = `/**
44
+ * ${name} - [TODO: describe what this pipeline does]
45
+ * Builds on: ${parentNames.join(', ')} (via devtopia-runtime)
46
+ *
47
+ ${parents.map(p => ` * Composes ${p.name}: ${p.description}`).join('\n')}
48
+ *
49
+ * @param {Object} params
50
+ * @returns {Object} Pipeline result
51
+ */
52
+
53
+ const { devtopiaRun } = require('./devtopia-runtime');
54
+ const input = JSON.parse(process.argv[2] || '{}');
55
+
56
+ // TODO: validate required input fields
57
+ // if (!input.someField) {
58
+ // console.log(JSON.stringify({ error: 'Missing required field: someField' }));
59
+ // process.exit(1);
60
+ // }
61
+
62
+ try {
63
+ ${stepsCode}
64
+
65
+ // TODO: combine results and produce final output
66
+ console.log(JSON.stringify({
67
+ success: true,
68
+ // result: ...,
69
+ steps: ${JSON.stringify(parentNames)},
70
+ }));
71
+ } catch (error) {
72
+ console.log(JSON.stringify({ error: error.message }));
73
+ process.exit(1);
74
+ }
75
+ `;
76
+ const mdSource = `# ${name}
77
+
78
+ [TODO: describe what this pipeline does]
79
+
80
+ ## Composes
81
+
82
+ ${parents.map(p => `- \`${p.name}\` — ${p.description}`).join('\n')}
83
+
84
+ ## Usage
85
+
86
+ \`\`\`bash
87
+ devtopia run ${name} '{"TODO": "add input"}'
88
+ \`\`\`
89
+
90
+ ## Input
91
+
92
+ [TODO: document input fields]
93
+
94
+ ## Output
95
+
96
+ \`\`\`json
97
+ {
98
+ "success": true,
99
+ "steps": ${JSON.stringify(parentNames)}
100
+ }
101
+ \`\`\`
102
+ `;
103
+ // Write files
104
+ const jsPath = `./${name}.js`;
105
+ const mdPath = `./${name}.md`;
106
+ if (existsSync(jsPath)) {
107
+ console.log(`\n⚠️ ${jsPath} already exists. Not overwriting.\n`);
108
+ process.exit(1);
109
+ }
110
+ writeFileSync(jsPath, jsSource);
111
+ writeFileSync(mdPath, mdSource);
112
+ console.log(`\n✅ Scaffold generated!`);
113
+ console.log(`\n Files created:`);
114
+ console.log(` ${jsPath} — Tool source (edit the TODOs)`);
115
+ console.log(` ${mdPath} — README (edit the TODOs)`);
116
+ console.log(`\n Next steps:`);
117
+ console.log(` 1. Edit ${jsPath} — fill in the TODOs with your logic`);
118
+ console.log(` 2. Test: devtopia run ${name} '{"test": "input"}'`);
119
+ console.log(` 3. Submit: devtopia submit ${name} ./${name}.js --builds-on ${parentNames.join(',')}\n`);
120
+ }
@@ -785,7 +785,7 @@ Available documentation:
785
785
  devtopia docs tool-format → Tool structure and format specification
786
786
  devtopia docs faq → Frequently asked questions
787
787
 
788
- These docs match the GitHub repository: https://github.com/Devtopia/Devtopia
788
+ These docs match the GitHub repository: https://github.com/DevtopiaHub/Devtopia
789
789
 
790
790
  For the full onboarding experience, run: devtopia start
791
791
  `);
@@ -1 +1,6 @@
1
- export declare function run(toolName: string, inputArg?: string): Promise<void>;
1
+ interface RunOptions {
2
+ json?: boolean;
3
+ quiet?: boolean;
4
+ }
5
+ export declare function run(toolName: string, inputArg?: string, options?: RunOptions): Promise<void>;
6
+ export {};
@@ -1,5 +1,21 @@
1
1
  import { executeTool } from '../executor.js';
2
- export async function run(toolName, inputArg) {
2
+ import { API_BASE } from '../config.js';
3
+ import { existsSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ export async function run(toolName, inputArg, options = {}) {
7
+ if (!process.env.DEVTOPIA_CLI) {
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const indexJs = join(__dirname, '..', 'index.js');
10
+ const indexTs = join(__dirname, '..', 'index.ts');
11
+ const quotePath = (p) => (p.includes(' ') ? `"${p.replace(/"/g, '\\"')}"` : p);
12
+ if (existsSync(indexJs)) {
13
+ process.env.DEVTOPIA_CLI = `node ${quotePath(indexJs)}`;
14
+ }
15
+ else if (existsSync(indexTs)) {
16
+ process.env.DEVTOPIA_CLI = `npx tsx ${quotePath(indexTs)}`;
17
+ }
18
+ }
3
19
  // Parse input
4
20
  let input = {};
5
21
  if (inputArg) {
@@ -7,19 +23,54 @@ export async function run(toolName, inputArg) {
7
23
  input = JSON.parse(inputArg);
8
24
  }
9
25
  catch {
10
- console.log(`\n❌ Invalid JSON input: ${inputArg}\n`);
11
- process.exit(1);
26
+ if (options.json) {
27
+ process.stdout.write(JSON.stringify({ ok: false, error: `Invalid JSON input: ${inputArg}` }) + '\n');
28
+ process.exit(0);
29
+ }
30
+ else {
31
+ console.log(`\n❌ Invalid JSON input: ${inputArg}\n`);
32
+ process.exit(1);
33
+ }
34
+ }
35
+ }
36
+ if (!options.json && !options.quiet) {
37
+ console.log(`\n⚡ Running /${toolName} locally...`);
38
+ }
39
+ const result = await executeTool(toolName, input, { strictJson: options.json });
40
+ // Fire-and-forget: track execution (never blocks, never fails visibly)
41
+ fetch(`${API_BASE}/api/runs`, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({
45
+ tool_name: toolName,
46
+ success: result.success,
47
+ duration_ms: result.durationMs,
48
+ }),
49
+ }).catch(() => { }); // silently ignore
50
+ if (options.json) {
51
+ const outputIsObject = result.output && typeof result.output === 'object';
52
+ const outputHasError = outputIsObject && (Object.prototype.hasOwnProperty.call(result.output, 'error') ||
53
+ (Object.prototype.hasOwnProperty.call(result.output, 'ok') && result.output.ok === false));
54
+ if (result.success && !outputHasError) {
55
+ process.stdout.write(JSON.stringify(result.output ?? null) + '\n');
12
56
  }
57
+ else {
58
+ const errMsg = outputIsObject && result.output.error
59
+ ? String(result.output.error)
60
+ : (result.error || 'Execution failed');
61
+ process.stdout.write(JSON.stringify({ ok: false, error: errMsg }) + '\n');
62
+ }
63
+ process.exit(0);
13
64
  }
14
- console.log(`\n⚡ Running /${toolName} locally...`);
15
- const result = await executeTool(toolName, input);
16
65
  if (!result.success) {
17
66
  console.log(`\n❌ Execution failed`);
18
67
  console.log(` Error: ${result.error}`);
19
68
  console.log(` Duration: ${result.durationMs}ms\n`);
20
69
  process.exit(1);
21
70
  }
22
- console.log(`\n✅ Success (${result.durationMs}ms)\n`);
71
+ if (!options.quiet) {
72
+ console.log(`\n✅ Success (${result.durationMs}ms)\n`);
73
+ }
23
74
  // Pretty print output
24
75
  if (typeof result.output === 'object') {
25
76
  console.log(JSON.stringify(result.output, null, 2));
@@ -0,0 +1,5 @@
1
+ interface SearchOptions {
2
+ limit?: string;
3
+ }
4
+ export declare function search(query: string, options?: SearchOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,52 @@
1
+ import { API_BASE } from '../config.js';
2
+ export async function search(query, options = {}) {
3
+ const limit = parseInt(options.limit || '20');
4
+ try {
5
+ // Try server-side search first
6
+ let results = [];
7
+ try {
8
+ const res = await fetch(`${API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`);
9
+ if (res.ok) {
10
+ const data = await res.json();
11
+ results = data.results || [];
12
+ }
13
+ else {
14
+ throw new Error('search endpoint unavailable');
15
+ }
16
+ }
17
+ catch {
18
+ // Fallback: fetch all tools and filter client-side
19
+ console.log(` (using fallback search — server search unavailable)\n`);
20
+ const res = await fetch(`${API_BASE}/api/tools`);
21
+ const data = await res.json();
22
+ const q = query.toLowerCase();
23
+ results = (data.tools || [])
24
+ .filter((t) => t.name.toLowerCase().includes(q) ||
25
+ (t.description && t.description.toLowerCase().includes(q)))
26
+ .slice(0, limit)
27
+ .map((t) => ({
28
+ name: t.name,
29
+ description: t.description,
30
+ language: t.language,
31
+ category: t.category,
32
+ }));
33
+ }
34
+ if (results.length === 0) {
35
+ console.log(`\n No results for "${query}"\n`);
36
+ return;
37
+ }
38
+ console.log(`\n Search: "${query}" (${results.length} results)\n`);
39
+ for (const tool of results) {
40
+ const name = `/${tool.name}`.padEnd(25);
41
+ const desc = (tool.description || 'No description').slice(0, 40).padEnd(40);
42
+ const lang = (tool.language || '').slice(0, 4);
43
+ console.log(` ${name} ${desc} ${lang}`);
44
+ }
45
+ console.log(`\n View source: devtopia cat <tool>`);
46
+ console.log(` Run locally: devtopia run <tool> '{...}'\n`);
47
+ }
48
+ catch (err) {
49
+ console.log(`\n❌ Could not connect to server at ${API_BASE}\n`);
50
+ process.exit(1);
51
+ }
52
+ }
@@ -1 +1 @@
1
- export declare function start(): void;
1
+ export declare function start(): Promise<void>;