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.
- package/dist/commands/compose.d.ts +5 -0
- package/dist/commands/compose.js +120 -0
- package/dist/commands/docs.js +1 -1
- package/dist/commands/run.d.ts +6 -1
- package/dist/commands/run.js +57 -6
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +52 -0
- package/dist/commands/start.d.ts +1 -1
- package/dist/commands/start.js +141 -256
- package/dist/commands/submit.d.ts +2 -0
- package/dist/commands/submit.js +167 -122
- package/dist/executor.d.ts +28 -2
- package/dist/executor.js +237 -67
- package/dist/index.js +27 -4
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/commands/docs.js
CHANGED
|
@@ -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/
|
|
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
|
`);
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { executeTool } from '../executor.js';
|
|
2
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function start(): void
|
|
1
|
+
export declare function start(): Promise<void>;
|