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.
- 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 -409
- 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 -102
- package/src/commands/init.ts +0 -514
- package/src/commands/list.ts +0 -72
- 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 -148
- 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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli4ai.json manifest validation and parsing
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { resolve, dirname, basename } from 'path';
|
|
6
|
+
import { outputError } from '../lib/cli.js';
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
// ERRORS
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
export class ManifestValidationError extends Error {
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.details = details;
|
|
15
|
+
this.name = 'ManifestValidationError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
|
+
// VALIDATION
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
const NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
22
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
|
|
23
|
+
export function validateManifest(manifest, source) {
|
|
24
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
25
|
+
throw new ManifestValidationError('Manifest must be an object', { source });
|
|
26
|
+
}
|
|
27
|
+
const m = manifest;
|
|
28
|
+
// Required: name
|
|
29
|
+
if (typeof m.name !== 'string' || !NAME_PATTERN.test(m.name)) {
|
|
30
|
+
throw new ManifestValidationError('Invalid or missing "name" (lowercase letters, numbers, hyphens)', {
|
|
31
|
+
source,
|
|
32
|
+
got: m.name
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Required: version
|
|
36
|
+
if (typeof m.version !== 'string' || !VERSION_PATTERN.test(m.version)) {
|
|
37
|
+
throw new ManifestValidationError('Invalid or missing "version" (semver format: x.y.z)', {
|
|
38
|
+
source,
|
|
39
|
+
got: m.version
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// Required: entry
|
|
43
|
+
if (typeof m.entry !== 'string' || m.entry.length === 0) {
|
|
44
|
+
throw new ManifestValidationError('Invalid or missing "entry" (path to main file)', {
|
|
45
|
+
source,
|
|
46
|
+
got: m.entry
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// SECURITY: Validate entry is a relative path that stays within package directory
|
|
50
|
+
if (m.entry.startsWith('/') || m.entry.startsWith('\\') || m.entry.includes('..')) {
|
|
51
|
+
throw new ManifestValidationError('Invalid "entry" - must be a relative path without ".." (security: path traversal)', { source, got: m.entry });
|
|
52
|
+
}
|
|
53
|
+
// Optional: runtime (deno not supported)
|
|
54
|
+
if (m.runtime !== undefined && !['bun', 'node'].includes(m.runtime)) {
|
|
55
|
+
throw new ManifestValidationError('Invalid "runtime" (must be bun or node)', {
|
|
56
|
+
source,
|
|
57
|
+
got: m.runtime
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Optional: commands
|
|
61
|
+
if (m.commands !== undefined) {
|
|
62
|
+
if (typeof m.commands !== 'object' || m.commands === null) {
|
|
63
|
+
throw new ManifestValidationError('Invalid "commands" (must be an object)', { source });
|
|
64
|
+
}
|
|
65
|
+
for (const [cmdName, cmdDef] of Object.entries(m.commands)) {
|
|
66
|
+
if (typeof cmdDef !== 'object' || cmdDef === null) {
|
|
67
|
+
throw new ManifestValidationError(`Invalid command definition for "${cmdName}"`, { source });
|
|
68
|
+
}
|
|
69
|
+
const def = cmdDef;
|
|
70
|
+
if (typeof def.description !== 'string') {
|
|
71
|
+
throw new ManifestValidationError(`Command "${cmdName}" missing description`, { source });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return manifest;
|
|
76
|
+
}
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
+
// LOADING
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
export const MANIFEST_FILENAME = 'cli4ai.json';
|
|
81
|
+
export class ManifestNotFoundError extends Error {
|
|
82
|
+
path;
|
|
83
|
+
constructor(path) {
|
|
84
|
+
super(`No ${MANIFEST_FILENAME} found at ${path}`);
|
|
85
|
+
this.path = path;
|
|
86
|
+
this.name = 'ManifestNotFoundError';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export class ManifestParseError extends Error {
|
|
90
|
+
path;
|
|
91
|
+
constructor(path, message) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.path = path;
|
|
94
|
+
this.name = 'ManifestParseError';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Load and validate cli4ai.json from a directory (throws on error)
|
|
99
|
+
* Use this for programmatic/testable usage
|
|
100
|
+
*/
|
|
101
|
+
export function loadManifestOrThrow(dir) {
|
|
102
|
+
const manifestPath = resolve(dir, MANIFEST_FILENAME);
|
|
103
|
+
if (!existsSync(manifestPath)) {
|
|
104
|
+
throw new ManifestNotFoundError(manifestPath);
|
|
105
|
+
}
|
|
106
|
+
let content;
|
|
107
|
+
try {
|
|
108
|
+
content = readFileSync(manifestPath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
throw new ManifestParseError(manifestPath, `Failed to read: ${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
}
|
|
113
|
+
let data;
|
|
114
|
+
try {
|
|
115
|
+
data = JSON.parse(content);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
throw new ManifestParseError(manifestPath, 'Invalid JSON');
|
|
119
|
+
}
|
|
120
|
+
return validateManifest(data, manifestPath);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Load manifest from package.json (fallback for npm packages)
|
|
124
|
+
*/
|
|
125
|
+
export function loadFromPackageJson(dir) {
|
|
126
|
+
const pkgJsonPath = resolve(dir, 'package.json');
|
|
127
|
+
if (!existsSync(pkgJsonPath))
|
|
128
|
+
return null;
|
|
129
|
+
try {
|
|
130
|
+
const content = readFileSync(pkgJsonPath, 'utf-8');
|
|
131
|
+
const pkg = JSON.parse(content);
|
|
132
|
+
// Extract name without @cli4ai/ scope
|
|
133
|
+
const name = pkg.name?.replace('@cli4ai/', '') || basename(dir);
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
version: pkg.version || '1.0.0',
|
|
137
|
+
entry: pkg.main || 'run.ts',
|
|
138
|
+
description: pkg.description,
|
|
139
|
+
author: pkg.author,
|
|
140
|
+
license: pkg.license,
|
|
141
|
+
runtime: 'node',
|
|
142
|
+
keywords: pkg.keywords
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Load and validate cli4ai.json from a directory
|
|
151
|
+
* Falls back to package.json for npm packages
|
|
152
|
+
* Exits with error on failure (for CLI usage)
|
|
153
|
+
*/
|
|
154
|
+
export function loadManifest(dir) {
|
|
155
|
+
// Try cli4ai.json first
|
|
156
|
+
try {
|
|
157
|
+
return loadManifestOrThrow(dir);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (err instanceof ManifestNotFoundError) {
|
|
161
|
+
// Fallback to package.json
|
|
162
|
+
const fromPkgJson = loadFromPackageJson(dir);
|
|
163
|
+
if (fromPkgJson)
|
|
164
|
+
return fromPkgJson;
|
|
165
|
+
outputError('NOT_FOUND', `No ${MANIFEST_FILENAME} found`, {
|
|
166
|
+
path: err.path,
|
|
167
|
+
hint: 'Run "cli4ai init" to create one'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (err instanceof ManifestParseError) {
|
|
171
|
+
outputError('PARSE_ERROR', err.message, { path: err.path });
|
|
172
|
+
}
|
|
173
|
+
if (err instanceof ManifestValidationError) {
|
|
174
|
+
outputError('MANIFEST_ERROR', err.message, err.details);
|
|
175
|
+
}
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Try to load manifest, return null if not found
|
|
181
|
+
*/
|
|
182
|
+
export function tryLoadManifest(dir) {
|
|
183
|
+
const manifestPath = resolve(dir, MANIFEST_FILENAME);
|
|
184
|
+
if (!existsSync(manifestPath))
|
|
185
|
+
return null;
|
|
186
|
+
try {
|
|
187
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
188
|
+
const data = JSON.parse(content);
|
|
189
|
+
return validateManifest(data, manifestPath);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Find manifest by walking up directory tree
|
|
197
|
+
*/
|
|
198
|
+
export function findManifest(startDir) {
|
|
199
|
+
let dir = startDir || process.cwd();
|
|
200
|
+
for (let i = 0; i < 10; i++) {
|
|
201
|
+
const manifest = tryLoadManifest(dir);
|
|
202
|
+
if (manifest) {
|
|
203
|
+
return { manifest, dir };
|
|
204
|
+
}
|
|
205
|
+
const parent = dirname(dir);
|
|
206
|
+
if (parent === dir)
|
|
207
|
+
break;
|
|
208
|
+
dir = parent;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Create a minimal manifest
|
|
214
|
+
*/
|
|
215
|
+
export function createManifest(name, options = {}) {
|
|
216
|
+
return {
|
|
217
|
+
name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
218
|
+
version: '1.0.0',
|
|
219
|
+
entry: 'run.ts',
|
|
220
|
+
runtime: 'node',
|
|
221
|
+
description: options.description || `${name} tool`,
|
|
222
|
+
...options
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Registry
|
|
3
|
+
*
|
|
4
|
+
* Fetches and caches package metadata from cli4ai.com registry.
|
|
5
|
+
* Provides integrity hashes for verification.
|
|
6
|
+
*/
|
|
7
|
+
interface RegistryPackage {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
description: string;
|
|
11
|
+
integrity: string;
|
|
12
|
+
readme: string;
|
|
13
|
+
commands: Record<string, {
|
|
14
|
+
description: string;
|
|
15
|
+
args?: Array<{
|
|
16
|
+
name: string;
|
|
17
|
+
required: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
}>;
|
|
20
|
+
dependencies: Record<string, string>;
|
|
21
|
+
env: Record<string, {
|
|
22
|
+
required: boolean;
|
|
23
|
+
description: string;
|
|
24
|
+
}>;
|
|
25
|
+
category: string;
|
|
26
|
+
keywords: string[];
|
|
27
|
+
author: string;
|
|
28
|
+
license: string;
|
|
29
|
+
runtime: string;
|
|
30
|
+
mcp: boolean;
|
|
31
|
+
repository: string;
|
|
32
|
+
homepage: string;
|
|
33
|
+
npm: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}
|
|
36
|
+
interface Registry {
|
|
37
|
+
version: number;
|
|
38
|
+
generatedAt: string;
|
|
39
|
+
packages: RegistryPackage[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fetch registry from cli4ai.com
|
|
43
|
+
*/
|
|
44
|
+
export declare function fetchRegistry(): Promise<Registry | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Get package info from registry
|
|
47
|
+
*/
|
|
48
|
+
export declare function getRegistryPackage(name: string): Promise<RegistryPackage | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Get integrity hash for a package from registry
|
|
51
|
+
*/
|
|
52
|
+
export declare function getRegistryIntegrity(name: string): Promise<string | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Get all package names from registry
|
|
55
|
+
*/
|
|
56
|
+
export declare function getRegistryPackageNames(): Promise<string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Compute SHA-512 integrity hash for a directory
|
|
59
|
+
* Same algorithm as used by generate-registry.ts
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeDirectoryIntegrity(dirPath: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Verify package integrity against registry
|
|
64
|
+
*/
|
|
65
|
+
export declare function verifyPackageIntegrity(packageName: string, packagePath: string): Promise<{
|
|
66
|
+
valid: boolean;
|
|
67
|
+
expected: string | null;
|
|
68
|
+
actual: string;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Clear the registry cache
|
|
72
|
+
*/
|
|
73
|
+
export declare function clearRegistryCache(): void;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Registry
|
|
3
|
+
*
|
|
4
|
+
* Fetches and caches package metadata from cli4ai.com registry.
|
|
5
|
+
* Provides integrity hashes for verification.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import { readdirSync, readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
const REGISTRY_URL = 'https://cli4ai.com/registry/packages.json';
|
|
11
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
// In-memory cache
|
|
13
|
+
let cachedRegistry = null;
|
|
14
|
+
let cacheTimestamp = 0;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch registry from cli4ai.com
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchRegistry() {
|
|
19
|
+
// Return cached if fresh
|
|
20
|
+
if (cachedRegistry && Date.now() - cacheTimestamp < CACHE_TTL) {
|
|
21
|
+
return cachedRegistry;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(REGISTRY_URL, {
|
|
25
|
+
headers: { 'Accept': 'application/json' },
|
|
26
|
+
signal: AbortSignal.timeout(10000), // 10 second timeout
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const registry = await response.json();
|
|
32
|
+
cachedRegistry = registry;
|
|
33
|
+
cacheTimestamp = Date.now();
|
|
34
|
+
return registry;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Return cached even if stale, or null if no cache
|
|
38
|
+
return cachedRegistry;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get package info from registry
|
|
43
|
+
*/
|
|
44
|
+
export async function getRegistryPackage(name) {
|
|
45
|
+
const registry = await fetchRegistry();
|
|
46
|
+
if (!registry)
|
|
47
|
+
return null;
|
|
48
|
+
return registry.packages.find(p => p.name === name) || null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get integrity hash for a package from registry
|
|
52
|
+
*/
|
|
53
|
+
export async function getRegistryIntegrity(name) {
|
|
54
|
+
const pkg = await getRegistryPackage(name);
|
|
55
|
+
return pkg?.integrity || null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get all package names from registry
|
|
59
|
+
*/
|
|
60
|
+
export async function getRegistryPackageNames() {
|
|
61
|
+
const registry = await fetchRegistry();
|
|
62
|
+
if (!registry)
|
|
63
|
+
return [];
|
|
64
|
+
return registry.packages.map(p => p.name);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute SHA-512 integrity hash for a directory
|
|
68
|
+
* Same algorithm as used by generate-registry.ts
|
|
69
|
+
*/
|
|
70
|
+
export function computeDirectoryIntegrity(dirPath) {
|
|
71
|
+
const hash = createHash('sha512');
|
|
72
|
+
function processDir(dir) {
|
|
73
|
+
const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = join(dir, entry.name);
|
|
76
|
+
// Skip node_modules and hidden files
|
|
77
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
processDir(fullPath);
|
|
82
|
+
}
|
|
83
|
+
else if (entry.isFile()) {
|
|
84
|
+
// Include relative path in hash for structure integrity
|
|
85
|
+
const relativePath = fullPath.slice(dirPath.length + 1);
|
|
86
|
+
hash.update(relativePath);
|
|
87
|
+
hash.update(readFileSync(fullPath));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
processDir(dirPath);
|
|
92
|
+
return `sha512-${hash.digest('base64')}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Verify package integrity against registry
|
|
96
|
+
*/
|
|
97
|
+
export async function verifyPackageIntegrity(packageName, packagePath) {
|
|
98
|
+
const expected = await getRegistryIntegrity(packageName);
|
|
99
|
+
const actual = computeDirectoryIntegrity(packagePath);
|
|
100
|
+
if (!expected) {
|
|
101
|
+
// Package not in registry, can't verify
|
|
102
|
+
return { valid: true, expected: null, actual };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
valid: expected === actual,
|
|
106
|
+
expected,
|
|
107
|
+
actual,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Clear the registry cache
|
|
112
|
+
*/
|
|
113
|
+
export function clearRegistryCache() {
|
|
114
|
+
cachedRegistry = null;
|
|
115
|
+
cacheTimestamp = 0;
|
|
116
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote client for executing cli4ai commands on remote hosts.
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions to call remote cli4ai services
|
|
5
|
+
* configured via `cli4ai remotes add`.
|
|
6
|
+
*/
|
|
7
|
+
import type { ScopeLevel } from './execute.js';
|
|
8
|
+
import type { RoutineRunSummary } from './routine-engine.js';
|
|
9
|
+
export interface RemoteRunOptions {
|
|
10
|
+
/** Package name to execute */
|
|
11
|
+
package: string;
|
|
12
|
+
/** Command within the package */
|
|
13
|
+
command?: string;
|
|
14
|
+
/** Arguments to pass */
|
|
15
|
+
args?: string[];
|
|
16
|
+
/** Environment variables */
|
|
17
|
+
env?: Record<string, string>;
|
|
18
|
+
/** Standard input to pass */
|
|
19
|
+
stdin?: string;
|
|
20
|
+
/** Timeout in milliseconds */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
/** Scope level for execution */
|
|
23
|
+
scope?: ScopeLevel;
|
|
24
|
+
}
|
|
25
|
+
export interface RemoteRunResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
exitCode: number;
|
|
28
|
+
stdout?: string;
|
|
29
|
+
stderr?: string;
|
|
30
|
+
durationMs: number;
|
|
31
|
+
error?: {
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
details?: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface RemoteHealthResult {
|
|
38
|
+
status: 'ok';
|
|
39
|
+
hostname: string;
|
|
40
|
+
version: string;
|
|
41
|
+
uptime: number;
|
|
42
|
+
}
|
|
43
|
+
export interface RemotePackageInfo {
|
|
44
|
+
name: string;
|
|
45
|
+
version: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
commands?: Record<string, {
|
|
48
|
+
description: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export interface RemotePackageList {
|
|
52
|
+
packages: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
version: string;
|
|
55
|
+
path: string;
|
|
56
|
+
source: 'local' | 'registry';
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
export declare class RemoteConnectionError extends Error {
|
|
60
|
+
remoteName: string;
|
|
61
|
+
url: string;
|
|
62
|
+
constructor(remoteName: string, url: string, message: string);
|
|
63
|
+
}
|
|
64
|
+
export declare class RemoteApiError extends Error {
|
|
65
|
+
remoteName: string;
|
|
66
|
+
statusCode: number;
|
|
67
|
+
code: string;
|
|
68
|
+
details?: Record<string, unknown> | undefined;
|
|
69
|
+
constructor(remoteName: string, statusCode: number, code: string, message: string, details?: Record<string, unknown> | undefined);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check health of a remote service
|
|
73
|
+
*/
|
|
74
|
+
export declare function remoteHealth(remoteName: string): Promise<RemoteHealthResult>;
|
|
75
|
+
/**
|
|
76
|
+
* List packages on a remote
|
|
77
|
+
*/
|
|
78
|
+
export declare function remoteListPackages(remoteName: string): Promise<RemotePackageList>;
|
|
79
|
+
/**
|
|
80
|
+
* Get package info from a remote
|
|
81
|
+
*/
|
|
82
|
+
export declare function remotePackageInfo(remoteName: string, packageName: string): Promise<RemotePackageInfo | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Run a tool on a remote
|
|
85
|
+
*/
|
|
86
|
+
export declare function remoteRunTool(remoteName: string, options: RemoteRunOptions): Promise<RemoteRunResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Run a routine on a remote
|
|
89
|
+
*/
|
|
90
|
+
export declare function remoteRunRoutine(remoteName: string, routineName: string, vars?: Record<string, string>): Promise<RoutineRunSummary>;
|
|
91
|
+
/**
|
|
92
|
+
* Test connection to a remote
|
|
93
|
+
*/
|
|
94
|
+
export declare function testRemoteConnection(remoteName: string): Promise<{
|
|
95
|
+
success: boolean;
|
|
96
|
+
message: string;
|
|
97
|
+
details?: Record<string, unknown>;
|
|
98
|
+
}>;
|