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.
- package/README.md +39 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.js +105 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +335 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.js +459 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.js +379 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +121 -0
- package/dist/commands/info.d.ts +9 -0
- package/dist/commands/info.js +122 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +458 -0
- package/dist/commands/list.d.ts +10 -0
- package/dist/commands/list.js +76 -0
- package/dist/commands/mcp-config.d.ts +10 -0
- package/dist/commands/mcp-config.js +49 -0
- package/dist/commands/remotes.d.ts +22 -0
- package/dist/commands/remotes.js +196 -0
- package/dist/commands/remove.d.ts +8 -0
- package/dist/commands/remove.js +61 -0
- package/dist/commands/routines.d.ts +29 -0
- package/dist/commands/routines.js +363 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +104 -0
- package/dist/commands/scheduler.d.ts +27 -0
- package/dist/commands/scheduler.js +350 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.js +159 -0
- package/dist/commands/secrets.d.ts +28 -0
- package/dist/commands/secrets.js +236 -0
- package/dist/commands/serve.d.ts +13 -0
- package/dist/commands/serve.js +49 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/update.d.ts +17 -0
- package/dist/commands/update.js +210 -0
- package/dist/core/config.d.ts +91 -0
- package/dist/core/config.js +738 -0
- package/dist/core/execute.d.ts +51 -0
- package/dist/core/execute.js +475 -0
- package/dist/core/link.d.ts +39 -0
- package/dist/core/link.js +214 -0
- package/dist/core/lockfile.d.ts +63 -0
- package/dist/core/lockfile.js +140 -0
- package/dist/core/manifest.d.ts +96 -0
- package/dist/core/manifest.js +224 -0
- package/dist/core/registry.d.ts +74 -0
- package/dist/core/registry.js +116 -0
- package/dist/core/remote-client.d.ts +98 -0
- package/dist/core/remote-client.js +252 -0
- package/dist/core/remotes.d.ts +88 -0
- package/dist/core/remotes.js +206 -0
- package/dist/core/routine-engine.d.ts +124 -0
- package/dist/core/routine-engine.js +699 -0
- package/dist/core/routines.d.ts +36 -0
- package/dist/core/routines.js +132 -0
- package/dist/core/scheduler-daemon.d.ts +10 -0
- package/dist/core/scheduler-daemon.js +77 -0
- package/dist/core/scheduler.d.ts +131 -0
- package/dist/core/scheduler.js +492 -0
- package/dist/core/secrets.d.ts +48 -0
- package/dist/core/secrets.js +384 -0
- package/dist/lib/cli.d.ts +84 -0
- package/dist/lib/cli.js +216 -0
- package/dist/mcp/adapter.d.ts +35 -0
- package/dist/mcp/adapter.js +94 -0
- package/dist/mcp/config-gen.d.ts +31 -0
- package/dist/mcp/config-gen.js +75 -0
- package/dist/mcp/server.d.ts +41 -0
- package/dist/mcp/server.js +296 -0
- package/dist/server/service.d.ts +85 -0
- package/dist/server/service.js +304 -0
- package/package.json +6 -3
- package/src/bin.ts +0 -118
- package/src/cli.ts +0 -412
- package/src/commands/add.ts +0 -562
- package/src/commands/browse.ts +0 -449
- package/src/commands/config.ts +0 -154
- package/src/commands/info.ts +0 -133
- package/src/commands/init.ts +0 -514
- package/src/commands/list.ts +0 -95
- package/src/commands/mcp-config.ts +0 -69
- package/src/commands/remotes.ts +0 -253
- package/src/commands/remove.ts +0 -78
- package/src/commands/routines.ts +0 -427
- package/src/commands/run.ts +0 -127
- package/src/commands/scheduler.ts +0 -438
- package/src/commands/search.ts +0 -185
- package/src/commands/secrets.ts +0 -292
- package/src/commands/serve.ts +0 -66
- package/src/commands/start.ts +0 -40
- package/src/commands/update.ts +0 -252
- package/src/core/config.ts +0 -845
- package/src/core/execute.ts +0 -569
- package/src/core/link.ts +0 -246
- package/src/core/lockfile.ts +0 -187
- package/src/core/manifest.ts +0 -327
- package/src/core/registry.ts +0 -165
- package/src/core/remote-client.ts +0 -419
- package/src/core/remotes.ts +0 -268
- package/src/core/routine-engine.ts +0 -895
- package/src/core/routines.ts +0 -171
- package/src/core/scheduler-daemon.ts +0 -94
- package/src/core/scheduler.ts +0 -606
- package/src/core/secrets.ts +0 -430
- package/src/lib/cli.ts +0 -261
- package/src/mcp/adapter.ts +0 -131
- package/src/mcp/config-gen.ts +0 -106
- package/src/mcp/server.ts +0 -365
- package/src/server/service.ts +0 -434
package/src/commands/info.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -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
|
-
}
|