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.
@@ -0,0 +1,302 @@
1
+ import { writeFileSync, existsSync } from 'fs';
2
+ import { createInterface } from 'readline';
3
+ function validateName(name) {
4
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
5
+ console.log(`\n❌ Tool name must be lowercase, alphanumeric with hyphens.\n`);
6
+ process.exit(1);
7
+ }
8
+ }
9
+ export async function create(name, options) {
10
+ if (!options.intent) {
11
+ console.log(`\n❌ --intent is required.\n Example: devtopia create summarize-article --intent "Summarize a URL"\n`);
12
+ process.exit(1);
13
+ }
14
+ validateName(name);
15
+ const language = (options.language || 'javascript').toLowerCase();
16
+ const extensions = {
17
+ javascript: '.js',
18
+ typescript: '.ts',
19
+ python: '.py',
20
+ bash: '.sh',
21
+ ruby: '.rb',
22
+ php: '.php',
23
+ shebang: '.tool',
24
+ };
25
+ if (!extensions[language]) {
26
+ console.log(`\n❌ Unsupported language: ${language}`);
27
+ console.log(` Supported: javascript, typescript, python, bash, ruby, php, shebang\n`);
28
+ process.exit(1);
29
+ }
30
+ const toolPath = `./${name}${extensions[language]}`;
31
+ const mdPath = `./${name}.md`;
32
+ if (existsSync(toolPath) || existsSync(mdPath)) {
33
+ console.log(`\n⚠️ ${toolPath} or ${mdPath} already exists. Not overwriting.\n`);
34
+ process.exit(1);
35
+ }
36
+ const intent = options.intent.trim();
37
+ let gap = (options.gap || '').trim();
38
+ if (!gap) {
39
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
40
+ gap = await new Promise((resolve) => {
41
+ rl.question('\nWhy does this tool need to exist (gap justification)? ', (answer) => {
42
+ rl.close();
43
+ resolve(String(answer || '').trim());
44
+ });
45
+ });
46
+ }
47
+ if (!gap) {
48
+ console.log(`\n❌ Gap justification is required to create a new primitive.\n`);
49
+ process.exit(1);
50
+ }
51
+ const jsSource = `/**
52
+ * ${name} - ${intent}
53
+ *
54
+ * Intent:
55
+ * ${intent}
56
+ *
57
+ * Gap Justification:
58
+ * ${gap}
59
+ *
60
+ * @param {Object} params
61
+ * @returns {Object}
62
+ */
63
+
64
+ const input = JSON.parse(process.argv[2] || '{}');
65
+
66
+ // TODO: validate required fields from input
67
+ // if (!input.someField) {
68
+ // console.log(JSON.stringify({ ok: false, error: 'Missing required field: someField' }));
69
+ // process.exit(1);
70
+ // }
71
+
72
+ try {
73
+ // TODO: implement your logic here
74
+ console.log(JSON.stringify({ ok: false, error: 'TODO: implement ${name}' }));
75
+ process.exit(1);
76
+ } catch (error) {
77
+ console.log(JSON.stringify({ ok: false, error: error.message }));
78
+ process.exit(1);
79
+ }
80
+ `;
81
+ const tsSource = `/**
82
+ * ${name} - ${intent}
83
+ *
84
+ * Intent:
85
+ * ${intent}
86
+ *
87
+ * Gap Justification:
88
+ * ${gap}
89
+ */
90
+
91
+ type Input = Record<string, unknown>;
92
+ const input: Input = JSON.parse(process.argv[2] || '{}');
93
+
94
+ // TODO: validate required fields from input
95
+ // if (!input.someField) {
96
+ // console.log(JSON.stringify({ ok: false, error: 'Missing required field: someField' }));
97
+ // process.exit(1);
98
+ // }
99
+
100
+ try {
101
+ // TODO: implement your logic here
102
+ console.log(JSON.stringify({ ok: false, error: 'TODO: implement ${name}' }));
103
+ process.exit(1);
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ console.log(JSON.stringify({ ok: false, error: message }));
107
+ process.exit(1);
108
+ }
109
+ `;
110
+ const pySource = `#!/usr/bin/env python3
111
+ # ${name} - ${intent}
112
+ #
113
+ # Intent:
114
+ # ${intent}
115
+ #
116
+ # Gap Justification:
117
+ # ${gap}
118
+
119
+ import json
120
+ import sys
121
+
122
+ input_data = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')
123
+
124
+ # TODO: validate required fields
125
+ # if 'someField' not in input_data:
126
+ # print(json.dumps({ 'ok': False, 'error': 'Missing required field: someField' }))
127
+ # sys.exit(1)
128
+
129
+ try:
130
+ # TODO: implement your logic here
131
+ print(json.dumps({ 'ok': False, 'error': 'TODO: implement ${name}' }))
132
+ sys.exit(1)
133
+ except Exception as error:
134
+ print(json.dumps({ 'ok': False, 'error': str(error) }))
135
+ sys.exit(1)
136
+ `;
137
+ const bashSource = `#!/usr/bin/env bash
138
+ # ${name} - ${intent}
139
+ #
140
+ # Intent:
141
+ # ${intent}
142
+ #
143
+ # Gap Justification:
144
+ # ${gap}
145
+
146
+ set -euo pipefail
147
+
148
+ INPUT="\${1:-{}}"
149
+
150
+ # TODO: parse fields from JSON input using node (available with Devtopia CLI)
151
+ # Example:
152
+ # TEXT=$(node -e "const i=JSON.parse(process.argv[1]||'{}'); console.log(i.text||'');" "$INPUT")
153
+ # if [ -z "$TEXT" ]; then
154
+ # echo '{"ok":false,"error":"Missing required field: text"}'
155
+ # exit 1
156
+ # fi
157
+
158
+ # TODO: implement your logic here
159
+ echo '{"ok":false,"error":"TODO: implement ${name}"}'
160
+ exit 1
161
+ `;
162
+ const rubySource = `#!/usr/bin/env ruby
163
+ # ${name} - ${intent}
164
+ #
165
+ # Intent:
166
+ # ${intent}
167
+ #
168
+ # Gap Justification:
169
+ # ${gap}
170
+
171
+ require 'json'
172
+
173
+ input = JSON.parse(ARGV[0] || '{}')
174
+
175
+ # TODO: validate required fields
176
+ # unless input['someField']
177
+ # puts JSON.generate({ ok: false, error: 'Missing required field: someField' })
178
+ # exit 1
179
+ # end
180
+
181
+ begin
182
+ # TODO: implement your logic here
183
+ puts JSON.generate({ ok: false, error: 'TODO: implement ${name}' })
184
+ exit 1
185
+ rescue => e
186
+ puts JSON.generate({ ok: false, error: e.message })
187
+ exit 1
188
+ end
189
+ `;
190
+ const phpSource = `#!/usr/bin/env php
191
+ <?php
192
+ // ${name} - ${intent}
193
+ //
194
+ // Intent:
195
+ // ${intent}
196
+ //
197
+ // Gap Justification:
198
+ // ${gap}
199
+
200
+ $input = json_decode($argv[1] ?? '{}', true);
201
+ if ($input === null && json_last_error() !== JSON_ERROR_NONE) {
202
+ echo json_encode([\"ok\" => false, \"error\" => \"Invalid JSON input\"]);
203
+ exit(1);
204
+ }
205
+
206
+ // TODO: validate required fields
207
+ // if (!isset($input['someField'])) {
208
+ // echo json_encode([\"ok\" => false, \"error\" => \"Missing required field: someField\"]);
209
+ // exit(1);
210
+ // }
211
+
212
+ try {
213
+ // TODO: implement your logic here
214
+ echo json_encode([\"ok\" => false, \"error\" => \"TODO: implement ${name}\"]);
215
+ exit(1);
216
+ } catch (Throwable $e) {
217
+ echo json_encode([\"ok\" => false, \"error\" => $e->getMessage()]);
218
+ exit(1);
219
+ }
220
+ ?>`;
221
+ const shebangSource = `#!/usr/bin/env bash
222
+ # ${name} - ${intent}
223
+ #
224
+ # Intent:
225
+ # ${intent}
226
+ #
227
+ # Gap Justification:
228
+ # ${gap}
229
+ #
230
+ # Replace "bash" above with the interpreter you want (python3, ruby, php, etc).
231
+
232
+ set -euo pipefail
233
+
234
+ INPUT="\${1:-{}}"
235
+
236
+ # TODO: parse fields from JSON input
237
+ # Example using node:
238
+ # FIELD=$(node -e "const i=JSON.parse(process.argv[1]||'{}'); console.log(i.field||'');" "$INPUT")
239
+
240
+ # TODO: implement your logic here
241
+ echo '{"ok":false,"error":"TODO: implement ${name}"}'
242
+ exit 1
243
+ `;
244
+ const mdSource = `# ${name}
245
+
246
+ ${intent}
247
+
248
+ ## Intent
249
+
250
+ ${intent}
251
+
252
+ ## Gap Justification
253
+
254
+ ${gap}
255
+
256
+ ## External Systems (if any)
257
+
258
+ <!-- Required for gravity tools. Examples: github, slack, openai, postgres, s3, filesystem -->
259
+
260
+ ## Usage
261
+
262
+ \`\`\`bash
263
+ devtopia run ${name} --json --quiet '{ "TODO": "add input" }'
264
+ \`\`\`
265
+
266
+ ## Input
267
+
268
+ \`\`\`json
269
+ {
270
+ "TODO": "document required fields"
271
+ }
272
+ \`\`\`
273
+
274
+ ## Output
275
+
276
+ \`\`\`json
277
+ {
278
+ "ok": true,
279
+ "result": "..."
280
+ }
281
+ \`\`\`
282
+ `;
283
+ const sources = {
284
+ javascript: jsSource,
285
+ typescript: tsSource,
286
+ python: pySource,
287
+ bash: bashSource,
288
+ ruby: rubySource,
289
+ php: phpSource,
290
+ shebang: shebangSource,
291
+ };
292
+ writeFileSync(toolPath, sources[language]);
293
+ writeFileSync(mdPath, mdSource);
294
+ console.log(`\n✅ Scaffold generated!`);
295
+ console.log(`\n Files created:`);
296
+ console.log(` ${toolPath} — Tool source (edit the TODOs)`);
297
+ console.log(` ${mdPath} — README (edit the TODOs)`);
298
+ console.log(`\n Next steps:`);
299
+ console.log(` 1. Edit ${toolPath} — implement your logic`);
300
+ console.log(` 2. Test: devtopia run ${name} --json --quiet '{...}'`);
301
+ console.log(` 3. Submit: devtopia submit ${name} ${toolPath}\n`);
302
+ }