cli4ai 1.2.0 → 1.2.1

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.
Files changed (113) hide show
  1. package/README.md +39 -0
  2. package/dist/bin.d.ts +6 -0
  3. package/dist/bin.js +105 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +335 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.js +459 -0
  8. package/dist/commands/browse.d.ts +4 -0
  9. package/dist/commands/browse.js +379 -0
  10. package/dist/commands/config.d.ts +10 -0
  11. package/dist/commands/config.js +121 -0
  12. package/dist/commands/info.d.ts +9 -0
  13. package/dist/commands/info.js +122 -0
  14. package/dist/commands/init.d.ts +10 -0
  15. package/dist/commands/init.js +458 -0
  16. package/dist/commands/list.d.ts +10 -0
  17. package/dist/commands/list.js +76 -0
  18. package/dist/commands/mcp-config.d.ts +10 -0
  19. package/dist/commands/mcp-config.js +49 -0
  20. package/dist/commands/remotes.d.ts +22 -0
  21. package/dist/commands/remotes.js +196 -0
  22. package/dist/commands/remove.d.ts +8 -0
  23. package/dist/commands/remove.js +61 -0
  24. package/dist/commands/routines.d.ts +29 -0
  25. package/dist/commands/routines.js +363 -0
  26. package/dist/commands/run.d.ts +12 -0
  27. package/dist/commands/run.js +104 -0
  28. package/dist/commands/scheduler.d.ts +27 -0
  29. package/dist/commands/scheduler.js +350 -0
  30. package/dist/commands/search.d.ts +9 -0
  31. package/dist/commands/search.js +159 -0
  32. package/dist/commands/secrets.d.ts +28 -0
  33. package/dist/commands/secrets.js +236 -0
  34. package/dist/commands/serve.d.ts +13 -0
  35. package/dist/commands/serve.js +49 -0
  36. package/dist/commands/start.d.ts +8 -0
  37. package/dist/commands/start.js +27 -0
  38. package/dist/commands/update.d.ts +17 -0
  39. package/dist/commands/update.js +210 -0
  40. package/dist/core/config.d.ts +91 -0
  41. package/dist/core/config.js +738 -0
  42. package/dist/core/execute.d.ts +51 -0
  43. package/dist/core/execute.js +475 -0
  44. package/dist/core/link.d.ts +39 -0
  45. package/dist/core/link.js +214 -0
  46. package/dist/core/lockfile.d.ts +63 -0
  47. package/dist/core/lockfile.js +140 -0
  48. package/dist/core/manifest.d.ts +96 -0
  49. package/dist/core/manifest.js +224 -0
  50. package/dist/core/registry.d.ts +74 -0
  51. package/dist/core/registry.js +116 -0
  52. package/dist/core/remote-client.d.ts +98 -0
  53. package/dist/core/remote-client.js +252 -0
  54. package/dist/core/remotes.d.ts +88 -0
  55. package/dist/core/remotes.js +206 -0
  56. package/dist/core/routine-engine.d.ts +124 -0
  57. package/dist/core/routine-engine.js +699 -0
  58. package/dist/core/routines.d.ts +36 -0
  59. package/dist/core/routines.js +132 -0
  60. package/dist/core/scheduler-daemon.d.ts +10 -0
  61. package/dist/core/scheduler-daemon.js +77 -0
  62. package/dist/core/scheduler.d.ts +131 -0
  63. package/dist/core/scheduler.js +492 -0
  64. package/dist/core/secrets.d.ts +48 -0
  65. package/dist/core/secrets.js +384 -0
  66. package/dist/lib/cli.d.ts +84 -0
  67. package/dist/lib/cli.js +216 -0
  68. package/dist/mcp/adapter.d.ts +35 -0
  69. package/dist/mcp/adapter.js +94 -0
  70. package/dist/mcp/config-gen.d.ts +31 -0
  71. package/dist/mcp/config-gen.js +75 -0
  72. package/dist/mcp/server.d.ts +41 -0
  73. package/dist/mcp/server.js +296 -0
  74. package/dist/server/service.d.ts +85 -0
  75. package/dist/server/service.js +304 -0
  76. package/package.json +6 -3
  77. package/src/bin.ts +0 -118
  78. package/src/cli.ts +0 -412
  79. package/src/commands/add.ts +0 -562
  80. package/src/commands/browse.ts +0 -449
  81. package/src/commands/config.ts +0 -154
  82. package/src/commands/info.ts +0 -133
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -95
  85. package/src/commands/mcp-config.ts +0 -69
  86. package/src/commands/remotes.ts +0 -253
  87. package/src/commands/remove.ts +0 -78
  88. package/src/commands/routines.ts +0 -427
  89. package/src/commands/run.ts +0 -127
  90. package/src/commands/scheduler.ts +0 -438
  91. package/src/commands/search.ts +0 -185
  92. package/src/commands/secrets.ts +0 -292
  93. package/src/commands/serve.ts +0 -66
  94. package/src/commands/start.ts +0 -40
  95. package/src/commands/update.ts +0 -252
  96. package/src/core/config.ts +0 -845
  97. package/src/core/execute.ts +0 -569
  98. package/src/core/link.ts +0 -246
  99. package/src/core/lockfile.ts +0 -187
  100. package/src/core/manifest.ts +0 -327
  101. package/src/core/registry.ts +0 -165
  102. package/src/core/remote-client.ts +0 -419
  103. package/src/core/remotes.ts +0 -268
  104. package/src/core/routine-engine.ts +0 -895
  105. package/src/core/routines.ts +0 -171
  106. package/src/core/scheduler-daemon.ts +0 -94
  107. package/src/core/scheduler.ts +0 -606
  108. package/src/core/secrets.ts +0 -430
  109. package/src/lib/cli.ts +0 -261
  110. package/src/mcp/adapter.ts +0 -131
  111. package/src/mcp/config-gen.ts +0 -106
  112. package/src/mcp/server.ts +0 -365
  113. package/src/server/service.ts +0 -434
@@ -1,133 +0,0 @@
1
- /**
2
- * cli4ai info - Show package information
3
- */
4
-
5
- import { resolve } from 'path';
6
- import { spawnSync } from 'child_process';
7
- import { output, outputError, log } from '../lib/cli.js';
8
- import { findPackage, loadConfig } from '../core/config.js';
9
- import { loadManifest, tryLoadManifest, type Manifest } from '../core/manifest.js';
10
- import { existsSync } from 'fs';
11
- import { remotePackageInfo, RemoteConnectionError, RemoteApiError } from '../core/remote-client.js';
12
-
13
- interface InfoOptions {
14
- versions?: boolean;
15
- remote?: string;
16
- }
17
-
18
- export async function infoCommand(packageName: string, options: InfoOptions): Promise<void> {
19
- // Handle remote info
20
- if (options.remote) {
21
- try {
22
- const info = await remotePackageInfo(options.remote, packageName);
23
- if (!info) {
24
- outputError('NOT_FOUND', `Package not found on remote: ${packageName}`, {
25
- remote: options.remote
26
- });
27
- return;
28
- }
29
- output({
30
- remote: options.remote,
31
- name: info.name,
32
- version: info.version,
33
- description: info.description,
34
- commands: info.commands ? Object.keys(info.commands) : [],
35
- commandDetails: info.commands
36
- });
37
- } catch (err) {
38
- if (err instanceof RemoteConnectionError) {
39
- outputError('NETWORK_ERROR', err.message, { remote: err.remoteName, url: err.url });
40
- } else if (err instanceof RemoteApiError) {
41
- outputError(err.code, err.message, { remote: err.remoteName, details: err.details });
42
- } else {
43
- throw err;
44
- }
45
- }
46
- return;
47
- }
48
- // First check if it's installed
49
- const installed = findPackage(packageName, process.cwd());
50
-
51
- if (installed) {
52
- const manifest = loadManifest(installed.path);
53
- outputPackageInfo(manifest, installed.path, true);
54
- return;
55
- }
56
-
57
- // Search in local registries
58
- const config = loadConfig();
59
- for (const registryPath of config.localRegistries) {
60
- const pkgPath = resolve(registryPath, packageName);
61
- const manifest = tryLoadManifest(pkgPath);
62
- if (manifest) {
63
- outputPackageInfo(manifest, pkgPath, false);
64
- return;
65
- }
66
- }
67
-
68
- // Try npm with @cli4ai scope
69
- const scopedName = packageName.startsWith('@cli4ai/') ? packageName : `@cli4ai/${packageName}`;
70
- try {
71
- log(`Fetching ${scopedName} from npm...`);
72
- // Use spawnSync with argument array to prevent command injection
73
- const result = spawnSync('npm', ['view', scopedName, '--json'], {
74
- encoding: 'utf-8',
75
- timeout: 10000,
76
- stdio: ['pipe', 'pipe', 'pipe']
77
- });
78
-
79
- if (result.status === 0 && result.stdout) {
80
- const pkg = JSON.parse(result.stdout);
81
- outputNpmPackageInfo(pkg, packageName);
82
- return;
83
- }
84
- } catch {
85
- // npm fetch failed
86
- }
87
-
88
- outputError('NOT_FOUND', `Package not found: ${packageName}`, {
89
- hint: `Tried @cli4ai/${packageName} on npm. Check the package name or add a local registry with "cli4ai config --add-registry <path>"`
90
- });
91
- }
92
-
93
- function outputPackageInfo(manifest: Manifest, path: string, installed: boolean): void {
94
- output({
95
- name: manifest.name,
96
- version: manifest.version,
97
- description: manifest.description,
98
- author: manifest.author,
99
- license: manifest.license,
100
- runtime: manifest.runtime || 'node',
101
- entry: manifest.entry,
102
- path,
103
- installed,
104
- commands: manifest.commands ? Object.keys(manifest.commands) : [],
105
- commandDetails: manifest.commands,
106
- env: manifest.env,
107
- peerDependencies: manifest.peerDependencies,
108
- mcp: manifest.mcp,
109
- repository: manifest.repository,
110
- homepage: manifest.homepage,
111
- keywords: manifest.keywords
112
- });
113
- }
114
-
115
- function outputNpmPackageInfo(pkg: Record<string, unknown>, requestedName: string): void {
116
- const shortName = typeof pkg.name === 'string' ? pkg.name.replace('@cli4ai/', '') : requestedName;
117
- output({
118
- name: shortName,
119
- npmName: pkg.name,
120
- version: pkg.version,
121
- description: pkg.description,
122
- author: typeof pkg.author === 'object' ? (pkg.author as Record<string, string>)?.name : pkg.author,
123
- license: pkg.license,
124
- source: 'npm',
125
- installed: false,
126
- install: `cli4ai add ${shortName} -g`,
127
- repository: pkg.repository,
128
- homepage: pkg.homepage,
129
- keywords: pkg.keywords,
130
- dependencies: pkg.dependencies,
131
- versions: pkg.versions
132
- });
133
- }
@@ -1,514 +0,0 @@
1
- /**
2
- * cli4ai init - Create a new tool project
3
- */
4
-
5
- import { writeFileSync, existsSync, mkdirSync } from 'fs';
6
- import { resolve, basename } from 'path';
7
- import { output, outputError, log } from '../lib/cli.js';
8
- import { createManifest, MANIFEST_FILENAME, type Manifest } from '../core/manifest.js';
9
-
10
- interface InitOptions {
11
- template?: string;
12
- runtime?: 'node' | 'bun';
13
- yes?: boolean;
14
- }
15
-
16
- export async function initCommand(name: string | undefined, options: InitOptions): Promise<void> {
17
- const projectName = name || basename(process.cwd());
18
- const targetDir = name ? resolve(process.cwd(), name) : process.cwd();
19
- const manifestPath = resolve(targetDir, MANIFEST_FILENAME);
20
- const templateName = options.template || 'basic';
21
- const runtime = options.runtime || 'node';
22
-
23
- // Check if cli4ai.json already exists
24
- if (existsSync(manifestPath)) {
25
- outputError('INVALID_INPUT', `${MANIFEST_FILENAME} already exists`, {
26
- path: manifestPath,
27
- hint: 'Remove it first or use a different directory'
28
- });
29
- }
30
-
31
- // Create directory if needed
32
- if (name && !existsSync(targetDir)) {
33
- mkdirSync(targetDir, { recursive: true });
34
- log(`Created directory: ${targetDir}`);
35
- }
36
-
37
- const manifest = createManifest(projectName, {
38
- ...getBaseManifest(templateName, runtime),
39
- ...getTemplateManifest(templateName, runtime),
40
- });
41
-
42
- // Write manifest
43
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
44
- log(`Created ${MANIFEST_FILENAME}`);
45
-
46
- // Create entry file if it doesn't exist
47
- const entryPath = resolve(targetDir, manifest.entry);
48
- if (!existsSync(entryPath)) {
49
- const template = getTemplate(templateName, manifest);
50
- writeFileSync(entryPath, template);
51
- log(`Created ${manifest.entry}`);
52
- }
53
-
54
- // Create helpful extras (non-destructive)
55
- const readmePath = resolve(targetDir, 'README.md');
56
- if (!existsSync(readmePath)) {
57
- writeFileSync(readmePath, getReadmeTemplate(templateName, manifest));
58
- log('Created README.md');
59
- }
60
-
61
- const gitignorePath = resolve(targetDir, '.gitignore');
62
- if (!existsSync(gitignorePath)) {
63
- writeFileSync(gitignorePath, getGitignoreTemplate());
64
- log('Created .gitignore');
65
- }
66
-
67
- const pkgJsonPath = resolve(targetDir, 'package.json');
68
- if (!existsSync(pkgJsonPath)) {
69
- const pkgJson = getDevPackageJson(templateName, manifest);
70
- writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
71
- log('Created package.json');
72
- }
73
-
74
- // Create test file
75
- const testPath = resolve(targetDir, manifest.entry.replace(/\.ts$/, '.test.ts'));
76
- if (!existsSync(testPath)) {
77
- writeFileSync(testPath, getTestTemplate(templateName, manifest));
78
- log(`Created ${basename(testPath)}`);
79
- }
80
-
81
- // Create tsconfig.json for TypeScript support
82
- const tsconfigPath = resolve(targetDir, 'tsconfig.json');
83
- if (!existsSync(tsconfigPath)) {
84
- writeFileSync(tsconfigPath, getTsconfigTemplate());
85
- log('Created tsconfig.json');
86
- }
87
-
88
- // Create vitest.config.ts
89
- const vitestConfigPath = resolve(targetDir, 'vitest.config.ts');
90
- if (!existsSync(vitestConfigPath)) {
91
- writeFileSync(vitestConfigPath, getVitestConfigTemplate());
92
- log('Created vitest.config.ts');
93
- }
94
-
95
- // Template-specific extras
96
- if (templateName === 'api') {
97
- const envExamplePath = resolve(targetDir, '.env.example');
98
- if (!existsSync(envExamplePath)) {
99
- writeFileSync(envExamplePath, 'API_KEY=\n');
100
- log('Created .env.example');
101
- }
102
- }
103
-
104
- const nextSteps = getNextSteps(name, targetDir, templateName, manifest);
105
-
106
- output({
107
- created: {
108
- manifest: manifestPath,
109
- entry: entryPath,
110
- test: testPath,
111
- readme: readmePath,
112
- gitignore: gitignorePath,
113
- tsconfig: tsconfigPath,
114
- vitestConfig: vitestConfigPath,
115
- packageJson: existsSync(resolve(targetDir, 'package.json')) ? resolve(targetDir, 'package.json') : undefined,
116
- },
117
- name: manifest.name,
118
- version: manifest.version,
119
- runtime: manifest.runtime,
120
- hint: nextSteps[0]?.command,
121
- nextSteps
122
- });
123
- }
124
-
125
- function guessAuthor(): string | undefined {
126
- const candidates = [
127
- process.env.GIT_AUTHOR_NAME,
128
- process.env.GIT_COMMITTER_NAME,
129
- process.env.USER,
130
- process.env.USERNAME,
131
- ].filter((v): v is string => Boolean(v));
132
-
133
- return candidates[0];
134
- }
135
-
136
- function getBaseManifest(templateName: string, runtime: 'node' | 'bun'): Partial<Manifest> {
137
- const author = guessAuthor();
138
- const keywords = Array.from(new Set([
139
- 'cli4ai',
140
- 'cli4ai',
141
- templateName,
142
- ]));
143
-
144
- return {
145
- entry: 'run.ts',
146
- runtime,
147
- description: `${templateName === 'basic' ? 'CLI' : templateName} tool`,
148
- author,
149
- license: 'BUSL-1.1',
150
- keywords,
151
- mcp: { enabled: true, transport: 'stdio' },
152
- };
153
- }
154
-
155
- function getTemplateManifest(templateName: string, runtime: 'node' | 'bun'): Partial<Manifest> {
156
- switch (templateName) {
157
- case 'api':
158
- return {
159
- commands: {
160
- fetch: {
161
- description: 'Fetch JSON from an API endpoint',
162
- args: [{ name: 'endpoint', required: true }],
163
- }
164
- },
165
- env: {
166
- API_KEY: {
167
- required: true,
168
- description: 'API key for the service you are calling (stored via `cli4ai secrets`)',
169
- }
170
- },
171
- dependencies: getDefaultDependencies(templateName),
172
- };
173
- case 'browser':
174
- return {
175
- commands: {
176
- screenshot: {
177
- description: 'Take a screenshot of a webpage',
178
- args: [
179
- { name: 'url', required: true },
180
- { name: 'output', required: false },
181
- ],
182
- options: [
183
- { name: 'full-page', type: 'boolean', description: 'Capture full page' },
184
- ]
185
- }
186
- },
187
- dependencies: getDefaultDependencies(templateName),
188
- };
189
- default:
190
- return {
191
- commands: {
192
- hello: {
193
- description: 'Say hello',
194
- args: [{ name: 'name', required: false }]
195
- }
196
- },
197
- dependencies: getDefaultDependencies(templateName),
198
- };
199
- }
200
- }
201
-
202
- function getDefaultDependencies(templateName: string): Record<string, string> | undefined {
203
- const deps: Record<string, string> = {
204
- '@cli4ai/lib': '^1.0.0',
205
- 'commander': '^14.0.0',
206
- };
207
-
208
- if (templateName === 'browser') {
209
- deps['puppeteer'] = '^24.0.0';
210
- }
211
-
212
- return deps;
213
- }
214
-
215
- function getTemplate(templateName: string, manifest: Manifest): string {
216
- switch (templateName) {
217
- case 'api':
218
- return getApiTemplate(manifest);
219
- case 'browser':
220
- return getBrowserTemplate(manifest);
221
- default:
222
- return getBasicTemplate(manifest);
223
- }
224
- }
225
-
226
- function getBasicTemplate(manifest: Manifest): string {
227
- return `#!/usr/bin/env npx tsx
228
- /**
229
- * ${manifest.name} - ${manifest.description || 'A cli4ai tool'}
230
- */
231
-
232
- import { cli, output } from '@cli4ai/lib';
233
-
234
- const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
235
-
236
- program
237
- .command('hello [name]')
238
- .description('Say hello')
239
- .action((name?: string) => {
240
- const who = name?.trim() || 'world';
241
- output({ message: \`Hello, \${who}!\` });
242
- });
243
-
244
- program.parse();
245
- `;
246
- }
247
-
248
- function getApiTemplate(manifest: Manifest): string {
249
- return `#!/usr/bin/env npx tsx
250
- /**
251
- * ${manifest.name} - ${manifest.description || 'API wrapper tool'}
252
- */
253
-
254
- import { cli, env, output, outputError, withErrorHandling } from '@cli4ai/lib';
255
-
256
- const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
257
-
258
- program
259
- .command('fetch <endpoint>')
260
- .description('Fetch data from API')
261
- .action(withErrorHandling(async (endpoint: string) => {
262
- // Use \`cli4ai secrets set API_KEY\` to store this securely (cli4ai injects it at runtime).
263
- const apiKey = env('API_KEY');
264
-
265
- const res = await fetch(\`https://api.example.com/\${endpoint}\`, {
266
- headers: { 'Authorization': \`Bearer \${apiKey}\` }
267
- });
268
-
269
- if (!res.ok) {
270
- outputError('API_ERROR', \`HTTP \${res.status}: \${res.statusText}\`);
271
- }
272
-
273
- output(await res.json());
274
- }));
275
-
276
- program.parse();
277
- `;
278
- }
279
-
280
- function getBrowserTemplate(manifest: Manifest): string {
281
- return `#!/usr/bin/env npx tsx
282
- /**
283
- * ${manifest.name} - ${manifest.description || 'Browser automation tool'}
284
- */
285
-
286
- import puppeteer from 'puppeteer';
287
- import { cli, output, outputError, withErrorHandling } from '@cli4ai/lib';
288
-
289
- const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
290
-
291
- program
292
- .command('screenshot <url>')
293
- .description('Take a screenshot of a webpage')
294
- .option('-o, --output <file>', 'Output file', 'screenshot.png')
295
- .option('--full-page', 'Capture full page')
296
- .action(withErrorHandling(async (url: string, options: { output: string; fullPage?: boolean }) => {
297
- const browser = await puppeteer.launch({ headless: true });
298
-
299
- try {
300
- const page = await browser.newPage();
301
- await page.goto(url, { waitUntil: 'networkidle2' });
302
- await page.screenshot({ path: options.output, fullPage: options.fullPage });
303
-
304
- output({
305
- url,
306
- screenshot: options.output,
307
- timestamp: new Date().toISOString()
308
- });
309
- } finally {
310
- await browser.close();
311
- }
312
- }));
313
-
314
- program.parse();
315
- `;
316
- }
317
-
318
- function getReadmeTemplate(templateName: string, manifest: Manifest): string {
319
- const exampleCmd =
320
- templateName === 'api' ? 'fetch users' :
321
- templateName === 'browser' ? 'screenshot https://example.com' :
322
- 'hello world';
323
-
324
- return `# ${manifest.name}
325
-
326
- ${manifest.description || ''}
327
-
328
- ## Setup
329
-
330
- \`\`\`bash
331
- npm install
332
- \`\`\`
333
-
334
- ## Run directly
335
-
336
- \`\`\`bash
337
- npx tsx run.ts ${exampleCmd}
338
- \`\`\`
339
-
340
- ## Install with cli4ai (project-scoped)
341
-
342
- \`\`\`bash
343
- # From the project you want to use this tool in:
344
- cli4ai add --local /path/to/${manifest.name} -y
345
- cli4ai run ${manifest.name} ${exampleCmd}
346
- \`\`\`
347
-
348
- ## Testing
349
-
350
- \`\`\`bash
351
- npm test
352
- \`\`\`
353
-
354
- ## MCP
355
-
356
- \`\`\`bash
357
- cli4ai start ${manifest.name}
358
- \`\`\`
359
- `;
360
- }
361
-
362
- function getGitignoreTemplate(): string {
363
- return `node_modules
364
- .cli4ai
365
- .env
366
- .env.*
367
- dist
368
- .DS_Store
369
- *.log
370
- coverage
371
- `;
372
- }
373
-
374
- function getTsconfigTemplate(): string {
375
- return `{
376
- "compilerOptions": {
377
- "target": "ES2022",
378
- "module": "NodeNext",
379
- "moduleResolution": "NodeNext",
380
- "strict": true,
381
- "esModuleInterop": true,
382
- "skipLibCheck": true,
383
- "forceConsistentCasingInFileNames": true,
384
- "resolveJsonModule": true,
385
- "declaration": false,
386
- "noEmit": true
387
- },
388
- "include": ["*.ts", "lib/**/*.ts"],
389
- "exclude": ["node_modules"]
390
- }
391
- `;
392
- }
393
-
394
- function getVitestConfigTemplate(): string {
395
- return `import { defineConfig } from 'vitest/config';
396
-
397
- export default defineConfig({
398
- test: {
399
- globals: true,
400
- },
401
- });
402
- `;
403
- }
404
-
405
- function getTestTemplate(templateName: string, manifest: Manifest): string {
406
- const testCmd = templateName === 'api' ? 'fetch' : templateName === 'browser' ? 'screenshot' : 'hello';
407
- const envSetup = templateName === 'api' ? `
408
- // Set required env vars before importing
409
- process.env.API_KEY = 'test-api-key';
410
- ` : '';
411
-
412
- return `import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
413
- ${envSetup}
414
- describe('${manifest.name}', () => {
415
- let consoleSpy: ReturnType<typeof vi.spyOn>;
416
- let logs: string[];
417
-
418
- beforeEach(() => {
419
- logs = [];
420
- consoleSpy = vi.spyOn(console, 'log').mockImplementation((msg: string) => {
421
- logs.push(msg);
422
- });
423
- });
424
-
425
- afterEach(() => {
426
- consoleSpy.mockRestore();
427
- });
428
-
429
- describe('${testCmd} command', () => {
430
- test('should output valid JSON', async () => {
431
- // TODO: Import and test your command handler
432
- // Example:
433
- // import { ${testCmd}Command } from './run';
434
- // await ${testCmd}Command('test-arg');
435
- // expect(logs.length).toBeGreaterThan(0);
436
- // const result = JSON.parse(logs[0]);
437
- // expect(result).toBeDefined();
438
-
439
- expect(true).toBe(true); // Placeholder - replace with real test
440
- });
441
-
442
- test('should handle invalid input', async () => {
443
- // TODO: Test error handling
444
- // Example:
445
- // expect(() => ${testCmd}Command('')).toThrow();
446
-
447
- expect(true).toBe(true); // Placeholder - replace with real test
448
- });
449
- });
450
- });
451
- `;
452
- }
453
-
454
- function getDevPackageJson(
455
- templateName: string,
456
- manifest: Manifest
457
- ): Record<string, unknown> {
458
- const deps = manifest.dependencies || {};
459
-
460
- return {
461
- name: manifest.name,
462
- version: manifest.version,
463
- private: true,
464
- type: 'module',
465
- bin: {
466
- [manifest.name]: `./run.ts`,
467
- },
468
- dependencies: deps,
469
- devDependencies: {
470
- 'typescript': '^5.0.0',
471
- 'tsx': '^4.0.0',
472
- 'vitest': '^2.0.0',
473
- '@types/node': '^22.0.0',
474
- },
475
- scripts: {
476
- dev: 'tsx run.ts',
477
- test: 'vitest run',
478
- 'test:watch': 'vitest',
479
- },
480
- };
481
- }
482
-
483
- function getNextSteps(
484
- nameArg: string | undefined,
485
- targetDir: string,
486
- templateName: string,
487
- manifest: Manifest
488
- ): Array<{ description: string; command: string }> {
489
- const exampleArgs =
490
- templateName === 'api' ? 'fetch users' :
491
- templateName === 'browser' ? 'screenshot https://example.com' :
492
- 'hello world';
493
-
494
- const installPath = nameArg ? `./${basename(targetDir)}` : '.';
495
-
496
- return [
497
- {
498
- description: 'Install dependencies',
499
- command: 'npm install',
500
- },
501
- {
502
- description: 'Run the tool directly',
503
- command: `npx tsx run.ts ${exampleArgs}`,
504
- },
505
- {
506
- description: 'Run tests',
507
- command: 'npm test',
508
- },
509
- {
510
- description: 'Install into a project and run via cli4ai',
511
- command: `cli4ai add --local ${installPath} -y && cli4ai run ${manifest.name} ${exampleArgs}`,
512
- }
513
- ];
514
- }