cli4ai 1.1.5 → 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 -409
  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 -102
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -72
  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 -148
  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
package/src/core/link.ts DELETED
@@ -1,246 +0,0 @@
1
- /**
2
- * PATH linking for global packages
3
- *
4
- * Creates executable symlinks in ~/.cli4ai/bin/ that can be added to PATH
5
- */
6
-
7
- import { existsSync, mkdirSync, symlinkSync, unlinkSync, writeFileSync, chmodSync } from 'fs';
8
- import { resolve, join } from 'path';
9
- import { homedir } from 'os';
10
- import { type Manifest } from './manifest.js';
11
-
12
- // ═══════════════════════════════════════════════════════════════════════════
13
- // SECURITY: Shell escaping for safe script generation
14
- // ═══════════════════════════════════════════════════════════════════════════
15
-
16
- /**
17
- * Validate that a string is safe for shell script embedding.
18
- * Only allows alphanumeric, hyphens, underscores, and dots.
19
- * This is a strict allowlist approach for defense in depth.
20
- */
21
- function isShellSafe(value: string): boolean {
22
- return /^[a-zA-Z0-9._-]+$/.test(value);
23
- }
24
-
25
- /**
26
- * Escape a string for safe embedding in shell scripts.
27
- * Uses single quotes which prevent all shell interpretation except for single quotes themselves.
28
- */
29
- function shellEscape(value: string): string {
30
- // Single quotes prevent shell interpretation; escape any single quotes in the value
31
- // by ending the single-quoted string, adding an escaped single quote, and starting a new single-quoted string
32
- return "'" + value.replace(/'/g, "'\\''") + "'";
33
- }
34
-
35
- // ═══════════════════════════════════════════════════════════════════════════
36
- // PATHS
37
- // ═══════════════════════════════════════════════════════════════════════════
38
-
39
- export const C4AI_BIN = resolve(homedir(), '.cli4ai', 'bin');
40
-
41
- // ═══════════════════════════════════════════════════════════════════════════
42
- // FUNCTIONS
43
- // ═══════════════════════════════════════════════════════════════════════════
44
-
45
- /**
46
- * Ensure bin directory exists
47
- */
48
- export function ensureBinDir(): void {
49
- if (!existsSync(C4AI_BIN)) {
50
- mkdirSync(C4AI_BIN, { recursive: true });
51
- }
52
- }
53
-
54
- /**
55
- * Create executable wrapper script for a package
56
- *
57
- * Creates a shell script that invokes `cli4ai run <package> [args]`
58
- */
59
- export function linkPackage(manifest: Manifest, packagePath: string): string {
60
- ensureBinDir();
61
-
62
- // SECURITY: Validate manifest values before embedding in shell script
63
- if (!isShellSafe(manifest.name)) {
64
- throw new Error(`Invalid package name for shell script: ${manifest.name}`);
65
- }
66
- if (!isShellSafe(manifest.version)) {
67
- throw new Error(`Invalid package version for shell script: ${manifest.version}`);
68
- }
69
-
70
- const binPath = join(C4AI_BIN, manifest.name);
71
-
72
- // Create wrapper script with escaped values for defense in depth
73
- const safeName = shellEscape(manifest.name);
74
- const safeVersion = shellEscape(manifest.version);
75
-
76
- const script = `#!/bin/sh
77
- # cli4ai wrapper for ${safeName}@${safeVersion}
78
- # Auto-generated - do not edit
79
-
80
- exec cli4ai run ${safeName} "$@"
81
- `;
82
-
83
- writeFileSync(binPath, script);
84
- if (process.platform !== 'win32') {
85
- chmodSync(binPath, 0o755);
86
- }
87
-
88
- // Windows compatibility: generate .cmd and .ps1 launchers
89
- if (process.platform === 'win32') {
90
- const cmdContent = `@echo off\r\ncli4ai run ${manifest.name} %*\r\nexit /b %errorlevel%\r\n`;
91
- writeFileSync(binPath + '.cmd', cmdContent);
92
-
93
- const ps1Content = `& cli4ai run ${manifest.name} @args\nexit $LASTEXITCODE\n`;
94
- writeFileSync(binPath + '.ps1', ps1Content);
95
- }
96
-
97
- return binPath;
98
- }
99
-
100
- /**
101
- * Create direct executable wrapper that runs the tool directly
102
- *
103
- * This is faster than going through cli4ai run
104
- */
105
- export function linkPackageDirect(manifest: Manifest, packagePath: string): string {
106
- ensureBinDir();
107
-
108
- // SECURITY: Validate manifest values before embedding in shell script
109
- if (!isShellSafe(manifest.name)) {
110
- throw new Error(`Invalid package name for shell script: ${manifest.name}`);
111
- }
112
- if (!isShellSafe(manifest.version)) {
113
- throw new Error(`Invalid package version for shell script: ${manifest.version}`);
114
- }
115
-
116
- const binPath = join(C4AI_BIN, manifest.name);
117
- const entryPath = resolve(packagePath, manifest.entry);
118
-
119
- // SECURITY: Shell-escape the entry path to prevent injection
120
- const safeEntryPath = shellEscape(entryPath);
121
- const safeName = shellEscape(manifest.name);
122
- const safeVersion = shellEscape(manifest.version);
123
-
124
- // Build runtime command - use tsx for TypeScript, node for JavaScript
125
- let execCommand: string;
126
- if (entryPath.endsWith('.ts') || entryPath.endsWith('.tsx')) {
127
- execCommand = `npx tsx ${safeEntryPath}`;
128
- } else {
129
- execCommand = `node ${safeEntryPath}`;
130
- }
131
-
132
- // Create wrapper script that runs the tool directly
133
- const script = `#!/bin/sh
134
- # cli4ai wrapper for ${safeName}@${safeVersion}
135
- # Auto-generated - do not edit
136
-
137
- exec ${execCommand} "$@"
138
- `;
139
-
140
- writeFileSync(binPath, script);
141
- if (process.platform !== 'win32') {
142
- chmodSync(binPath, 0o755);
143
- }
144
-
145
- // Windows compatibility: generate .cmd and .ps1 launchers
146
- if (process.platform === 'win32') {
147
- const quotedEntry = `"${entryPath.replaceAll('"', '""')}"`;
148
-
149
- let cmdContent: string;
150
- let ps1Content: string;
151
- if (entryPath.endsWith('.ts') || entryPath.endsWith('.tsx')) {
152
- cmdContent = `@echo off\r\nnpx tsx ${quotedEntry} %*\r\nexit /b %errorlevel%\r\n`;
153
- ps1Content = `& npx tsx ${quotedEntry} @args\nexit $LASTEXITCODE\n`;
154
- } else {
155
- cmdContent = `@echo off\r\nnode ${quotedEntry} %*\r\nexit /b %errorlevel%\r\n`;
156
- ps1Content = `& node ${quotedEntry} @args\nexit $LASTEXITCODE\n`;
157
- }
158
- writeFileSync(binPath + '.cmd', cmdContent);
159
- writeFileSync(binPath + '.ps1', ps1Content);
160
- }
161
-
162
- return binPath;
163
- }
164
-
165
- /**
166
- * Remove executable link for a package
167
- */
168
- export function unlinkPackage(packageName: string): boolean {
169
- // SECURITY: Validate package name to prevent path traversal
170
- if (!isShellSafe(packageName) || packageName.includes('..')) {
171
- throw new Error(`Invalid package name: ${packageName}`);
172
- }
173
-
174
- const basePath = join(C4AI_BIN, packageName);
175
- const candidates = process.platform === 'win32'
176
- ? [basePath, basePath + '.cmd', basePath + '.ps1']
177
- : [basePath];
178
-
179
- let removed = false;
180
- for (const path of candidates) {
181
- if (existsSync(path)) {
182
- unlinkSync(path);
183
- removed = true;
184
- }
185
- }
186
-
187
- return removed;
188
- }
189
-
190
- /**
191
- * Check if a package is linked
192
- */
193
- export function isPackageLinked(packageName: string): boolean {
194
- const basePath = join(C4AI_BIN, packageName);
195
- if (existsSync(basePath)) return true;
196
- if (process.platform === 'win32') {
197
- if (existsSync(basePath + '.cmd')) return true;
198
- if (existsSync(basePath + '.ps1')) return true;
199
- }
200
- return false;
201
- }
202
-
203
- /**
204
- * Get PATH setup instructions
205
- */
206
- export function getPathInstructions(): string {
207
- if (process.platform === 'win32') {
208
- return `
209
- To use globally installed cli4ai packages from anywhere, add the bin directory to your PATH:
210
-
211
- ${C4AI_BIN}
212
-
213
- On Windows, you can:
214
- 1. Use System Settings > Edit environment variables (GUI)
215
- 2. Or run in PowerShell (current session): $env:PATH += ";${C4AI_BIN}"
216
- 3. Or run in PowerShell (permanent): [Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";${C4AI_BIN}", "User")
217
-
218
- Then restart your terminal.
219
- `.trim();
220
- }
221
-
222
- const shell = process.env.SHELL || '/bin/bash';
223
- const isZsh = shell.includes('zsh');
224
- const rcFile = isZsh ? '~/.zshrc' : '~/.bashrc';
225
-
226
- return `
227
- To use globally installed cli4ai packages from anywhere, add this to your ${rcFile}:
228
-
229
- export PATH="${C4AI_BIN}:$PATH"
230
-
231
- Then reload your shell:
232
-
233
- source ${rcFile}
234
-
235
- Or start a new terminal session.
236
- `.trim();
237
- }
238
-
239
- /**
240
- * Check if bin directory is in PATH
241
- */
242
- export function isBinInPath(): boolean {
243
- const pathEnv = process.env.PATH || '';
244
- const separator = process.platform === 'win32' ? ';' : ':';
245
- return pathEnv.split(separator).some(p => resolve(p) === C4AI_BIN);
246
- }
@@ -1,187 +0,0 @@
1
- /**
2
- * Lock file management (cli4ai.lock)
3
- *
4
- * Tracks installed packages with exact versions and sources
5
- * for reproducible installations.
6
- */
7
-
8
- import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs';
9
- import { resolve } from 'path';
10
-
11
- // ═══════════════════════════════════════════════════════════════════════════
12
- // TYPES
13
- // ═══════════════════════════════════════════════════════════════════════════
14
-
15
- export interface LockedPackage {
16
- name: string;
17
- version: string;
18
- resolved: string; // Source path or registry URL
19
- integrity?: string; // Future: content hash
20
- dependencies?: Record<string, string>;
21
- }
22
-
23
- export interface Lockfile {
24
- lockfileVersion: number;
25
- packages: Record<string, LockedPackage>;
26
- }
27
-
28
- // ═══════════════════════════════════════════════════════════════════════════
29
- // CONSTANTS
30
- // ═══════════════════════════════════════════════════════════════════════════
31
-
32
- export const LOCKFILE_NAME = 'cli4ai.lock';
33
- export const LOCKFILE_VERSION = 1;
34
-
35
- // ═══════════════════════════════════════════════════════════════════════════
36
- // FUNCTIONS
37
- // ═══════════════════════════════════════════════════════════════════════════
38
-
39
- /**
40
- * Get lock file path for a project
41
- */
42
- export function getLockfilePath(projectDir: string): string {
43
- return resolve(projectDir, LOCKFILE_NAME);
44
- }
45
-
46
- /**
47
- * Check if lock file exists
48
- */
49
- export function lockfileExists(projectDir: string): boolean {
50
- return existsSync(getLockfilePath(projectDir));
51
- }
52
-
53
- /**
54
- * Load lock file, returns empty lockfile if doesn't exist
55
- */
56
- export function loadLockfile(projectDir: string): Lockfile {
57
- const lockfilePath = getLockfilePath(projectDir);
58
-
59
- if (!existsSync(lockfilePath)) {
60
- return {
61
- lockfileVersion: LOCKFILE_VERSION,
62
- packages: {}
63
- };
64
- }
65
-
66
- try {
67
- const content = readFileSync(lockfilePath, 'utf-8');
68
- const data = JSON.parse(content) as Lockfile;
69
-
70
- // Validate version
71
- if (data.lockfileVersion !== LOCKFILE_VERSION) {
72
- console.error(`Warning: Lock file version mismatch (${data.lockfileVersion} vs ${LOCKFILE_VERSION})`);
73
- }
74
-
75
- return data;
76
- } catch (err) {
77
- console.error('Warning: Could not parse lock file, starting fresh');
78
- return {
79
- lockfileVersion: LOCKFILE_VERSION,
80
- packages: {}
81
- };
82
- }
83
- }
84
-
85
- /**
86
- * Save lock file
87
- */
88
- export function saveLockfile(projectDir: string, lockfile: Lockfile): void {
89
- const lockfilePath = getLockfilePath(projectDir);
90
- const content = JSON.stringify(lockfile, null, 2) + '\n';
91
- const tmpPath = lockfilePath + '.tmp';
92
- writeFileSync(tmpPath, content);
93
- renameSync(tmpPath, lockfilePath);
94
- }
95
-
96
- /**
97
- * Add or update a package in the lock file
98
- */
99
- export function lockPackage(
100
- projectDir: string,
101
- pkg: LockedPackage
102
- ): void {
103
- const lockfile = loadLockfile(projectDir);
104
- lockfile.packages[pkg.name] = pkg;
105
- saveLockfile(projectDir, lockfile);
106
- }
107
-
108
- /**
109
- * Remove a package from the lock file
110
- */
111
- export function unlockPackage(projectDir: string, packageName: string): void {
112
- const lockfile = loadLockfile(projectDir);
113
-
114
- if (lockfile.packages[packageName]) {
115
- delete lockfile.packages[packageName];
116
- saveLockfile(projectDir, lockfile);
117
- }
118
- }
119
-
120
- /**
121
- * Get a locked package by name
122
- */
123
- export function getLockedPackage(
124
- projectDir: string,
125
- packageName: string
126
- ): LockedPackage | null {
127
- const lockfile = loadLockfile(projectDir);
128
- return lockfile.packages[packageName] || null;
129
- }
130
-
131
- /**
132
- * Get all locked packages
133
- */
134
- export function getLockedPackages(projectDir: string): LockedPackage[] {
135
- const lockfile = loadLockfile(projectDir);
136
- return Object.values(lockfile.packages);
137
- }
138
-
139
- /**
140
- * Check if a package is locked
141
- */
142
- export function isPackageLocked(projectDir: string, packageName: string): boolean {
143
- const lockfile = loadLockfile(projectDir);
144
- return packageName in lockfile.packages;
145
- }
146
-
147
- /**
148
- * Clear all packages from lock file
149
- */
150
- export function clearLockfile(projectDir: string): void {
151
- saveLockfile(projectDir, {
152
- lockfileVersion: LOCKFILE_VERSION,
153
- packages: {}
154
- });
155
- }
156
-
157
- /**
158
- * Format lock file as human-readable string
159
- */
160
- export function formatLockfile(lockfile: Lockfile): string {
161
- const lines: string[] = [
162
- `# cli4ai.lock - Auto-generated, do not edit`,
163
- `# lockfileVersion: ${lockfile.lockfileVersion}`,
164
- ``
165
- ];
166
-
167
- const sortedPackages = Object.values(lockfile.packages).sort((a, b) =>
168
- a.name.localeCompare(b.name)
169
- );
170
-
171
- for (const pkg of sortedPackages) {
172
- lines.push(`${pkg.name}@${pkg.version}:`);
173
- lines.push(` resolved: "${pkg.resolved}"`);
174
- if (pkg.integrity) {
175
- lines.push(` integrity: ${pkg.integrity}`);
176
- }
177
- if (pkg.dependencies && Object.keys(pkg.dependencies).length > 0) {
178
- lines.push(` dependencies:`);
179
- for (const [depName, depVersion] of Object.entries(pkg.dependencies)) {
180
- lines.push(` ${depName}: "${depVersion}"`);
181
- }
182
- }
183
- lines.push(``);
184
- }
185
-
186
- return lines.join('\n');
187
- }