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
@@ -1,427 +0,0 @@
1
- /**
2
- * cli4ai routines - Manage and run routines
3
- */
4
-
5
- import { spawn, spawnSync } from 'child_process';
6
- import { readFileSync, writeFileSync, existsSync, chmodSync, rmSync } from 'fs';
7
- import { resolve } from 'path';
8
- import { stringify as stringifyYaml } from 'yaml';
9
- import { output, outputError, log } from '../lib/cli.js';
10
- import { ensureCli4aiHome, ensureLocalDir, ROUTINES_DIR, LOCAL_ROUTINES_DIR } from '../core/config.js';
11
- import { getGlobalRoutines, getLocalRoutines, resolveRoutine, validateRoutineName, type RoutineInfo } from '../core/routines.js';
12
- import { loadRoutineDefinition, dryRunRoutine, runRoutine, RoutineParseError, RoutineValidationError, RoutineTemplateError } from '../core/routine-engine.js';
13
-
14
- interface RoutinesListOptions {
15
- global?: boolean;
16
- json?: boolean;
17
- }
18
-
19
- interface RoutinesRunOptions {
20
- global?: boolean;
21
- var?: string[];
22
- dryRun?: boolean;
23
- }
24
-
25
- interface RoutinesCreateOptions {
26
- global?: boolean;
27
- type?: 'yaml' | 'json' | 'bash';
28
- }
29
-
30
- function ensureValidRoutineName(name: string): void {
31
- try {
32
- validateRoutineName(name);
33
- } catch (err) {
34
- outputError('INVALID_INPUT', err instanceof Error ? err.message : String(err), { name });
35
- }
36
- }
37
-
38
- function normalizeEnvSegment(value: string): string {
39
- return value
40
- .trim()
41
- .toUpperCase()
42
- .replace(/[^A-Z0-9]+/g, '_')
43
- .replace(/^_+|_+$/g, '');
44
- }
45
-
46
- function parseVars(vars: string[] | undefined): Record<string, string> {
47
- const result: Record<string, string> = {};
48
- if (!vars) return result;
49
-
50
- for (const entry of vars) {
51
- const eq = entry.indexOf('=');
52
- if (eq <= 0) {
53
- outputError('INVALID_INPUT', `Invalid --var (expected KEY=value): ${entry}`);
54
- }
55
- const key = entry.slice(0, eq).trim();
56
- const value = entry.slice(eq + 1);
57
- if (!key) {
58
- outputError('INVALID_INPUT', `Invalid --var (empty key): ${entry}`);
59
- }
60
- result[key] = value;
61
- }
62
-
63
- return result;
64
- }
65
-
66
- function varsToEnv(vars: Record<string, string>): Record<string, string> {
67
- const env: Record<string, string> = {};
68
- for (const [key, value] of Object.entries(vars)) {
69
- const envKey = `CLI4AI_VAR_${normalizeEnvSegment(key)}`;
70
- env[envKey] = value;
71
- }
72
- return env;
73
- }
74
-
75
- function collectStream(stream: NodeJS.ReadableStream): Promise<string> {
76
- return new Promise((resolve, reject) => {
77
- const chunks: Buffer[] = [];
78
- stream.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
79
- stream.on('error', reject);
80
- stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
81
- });
82
- }
83
-
84
- export async function routinesListCommand(options: RoutinesListOptions): Promise<void> {
85
- const local = options.global ? [] : getLocalRoutines(process.cwd());
86
- const global = getGlobalRoutines();
87
-
88
- const routines: RoutineInfo[] = [];
89
- const seen = new Set<string>();
90
-
91
- for (const r of local) {
92
- routines.push(r);
93
- seen.add(r.name);
94
- }
95
-
96
- for (const r of global) {
97
- if (!seen.has(r.name)) {
98
- routines.push(r);
99
- seen.add(r.name);
100
- }
101
- }
102
-
103
- if (options.json || !process.stdout.isTTY) {
104
- output({
105
- routines: routines.map(r => ({
106
- name: r.name,
107
- kind: r.kind,
108
- scope: r.scope,
109
- path: r.path
110
- })),
111
- count: routines.length
112
- });
113
- return;
114
- }
115
-
116
- if (routines.length === 0) {
117
- console.log('No routines found');
118
- console.log('\nCreate one: cli4ai routines create <name>\n');
119
- return;
120
- }
121
-
122
- console.log(`\nRoutines (${routines.length}):\n`);
123
- for (const r of routines) {
124
- const scopeTag = r.scope === 'local' ? '' : ' (global)';
125
- console.log(` ${r.name}${scopeTag} [${r.kind}]`);
126
- console.log(` ${r.path}`);
127
- }
128
- console.log('');
129
- }
130
-
131
- function getTargetDir(global: boolean, projectDir: string): string {
132
- if (global) {
133
- ensureCli4aiHome();
134
- return ROUTINES_DIR;
135
- }
136
- ensureLocalDir(projectDir);
137
- return resolve(projectDir, LOCAL_ROUTINES_DIR);
138
- }
139
-
140
- export async function routinesCreateCommand(name: string, options: RoutinesCreateOptions): Promise<void> {
141
- ensureValidRoutineName(name);
142
- const projectDir = process.cwd();
143
- const type = options.type ?? 'yaml';
144
- if (type !== 'yaml' && type !== 'bash' && type !== 'json') {
145
- outputError('INVALID_INPUT', `Invalid routine type: ${type}`, {
146
- allowed: ['yaml', 'json', 'bash']
147
- });
148
- }
149
- const dir = getTargetDir(!!options.global, projectDir);
150
-
151
- const suffixMap: Record<string, string> = {
152
- yaml: '.routine.yaml',
153
- json: '.routine.json',
154
- bash: '.routine.sh'
155
- };
156
- const suffix = suffixMap[type];
157
- const filePath = resolve(dir, `${name}${suffix}`);
158
-
159
- if (existsSync(filePath)) {
160
- outputError('INVALID_INPUT', `Routine already exists: ${name}`, { path: filePath });
161
- }
162
-
163
- if (type === 'yaml') {
164
- const template = {
165
- version: 1,
166
- name,
167
- description: 'TODO: describe this routine',
168
- vars: {},
169
- steps: [],
170
- result: '{{steps}}'
171
- };
172
- writeFileSync(filePath, stringifyYaml(template));
173
- } else if (type === 'json') {
174
- const template = {
175
- version: 1,
176
- name,
177
- description: 'TODO: describe this routine',
178
- vars: {},
179
- steps: [],
180
- result: '{{steps}}'
181
- };
182
- writeFileSync(filePath, JSON.stringify(template, null, 2) + '\n');
183
- } else {
184
- const template = `#!/usr/bin/env bash
185
- set -euo pipefail
186
-
187
- # Example:
188
- # cli4ai run github trending
189
- #
190
- # Vars passed via --var KEY=value are available as env vars:
191
- # CLI4AI_VAR_KEY=value
192
-
193
- echo "TODO: implement ${name}" 1>&2
194
- `;
195
- writeFileSync(filePath, template);
196
- try {
197
- chmodSync(filePath, 0o755);
198
- } catch {}
199
- }
200
-
201
- output({
202
- action: 'create',
203
- name,
204
- type,
205
- scope: options.global ? 'global' : 'local',
206
- path: filePath
207
- });
208
- }
209
-
210
- export async function routinesRunCommand(
211
- name: string,
212
- args: string[],
213
- options: RoutinesRunOptions
214
- ): Promise<void> {
215
- ensureValidRoutineName(name);
216
- const routine = resolveRoutine(name, process.cwd(), { globalOnly: options.global });
217
- if (!routine) {
218
- outputError('NOT_FOUND', `Routine not found: ${name}`, {
219
- hint: options.global
220
- ? 'Create a global routine in ~/.cli4ai/routines'
221
- : 'Create a routine in ./.cli4ai/routines or ~/.cli4ai/routines'
222
- });
223
- }
224
-
225
- const vars = parseVars(options.var);
226
-
227
- // Structured routines (YAML or JSON) use the routine engine
228
- const isStructured = routine.kind === 'yaml' || routine.kind === 'json';
229
-
230
- if (options.dryRun) {
231
- if (isStructured) {
232
- try {
233
- const def = loadRoutineDefinition(routine.path);
234
- const plan = await dryRunRoutine(def, vars, process.cwd());
235
- output({
236
- kind: routine.kind,
237
- scope: routine.scope,
238
- path: routine.path,
239
- ...plan
240
- });
241
- return;
242
- } catch (err) {
243
- if (err instanceof RoutineParseError) {
244
- outputError('PARSE_ERROR', err.message, { path: err.path });
245
- }
246
- if (err instanceof RoutineValidationError || err instanceof RoutineTemplateError) {
247
- outputError('INVALID_INPUT', err.message, err.details);
248
- }
249
- throw err;
250
- }
251
- }
252
-
253
- const plan = {
254
- routine: routine.name,
255
- kind: routine.kind,
256
- scope: routine.scope,
257
- path: routine.path,
258
- vars,
259
- exec: { cmd: 'bash', args: [routine.path, ...args] }
260
- };
261
-
262
- if (!process.stdout.isTTY) {
263
- output(plan);
264
- return;
265
- }
266
-
267
- console.log(`\nRoutine: ${routine.name} [${routine.kind}]`);
268
- console.log(`Path: ${routine.path}`);
269
- console.log(`Vars: ${Object.keys(vars).length ? JSON.stringify(vars) : '(none)'}`);
270
- console.log(`\nWould run:\n bash ${routine.path}${args.length ? ' ' + args.join(' ') : ''}\n`);
271
- return;
272
- }
273
-
274
- if (isStructured) {
275
- try {
276
- const def = loadRoutineDefinition(routine.path);
277
- const summary = await runRoutine(def, vars, process.cwd());
278
- output({
279
- kind: routine.kind,
280
- scope: routine.scope,
281
- path: routine.path,
282
- ...summary
283
- });
284
- process.exit(summary.exitCode);
285
- } catch (err) {
286
- if (err instanceof RoutineParseError) {
287
- outputError('PARSE_ERROR', err.message, { path: err.path });
288
- }
289
- if (err instanceof RoutineValidationError || err instanceof RoutineTemplateError) {
290
- outputError('INVALID_INPUT', err.message, err.details);
291
- }
292
- throw err;
293
- }
294
- }
295
-
296
- const startTime = Date.now();
297
- const isTTY = process.stdout.isTTY;
298
- const varEnv = varsToEnv(vars);
299
-
300
- const child = spawn('bash', [routine.path, ...args], {
301
- cwd: process.cwd(),
302
- stdio: isTTY ? 'inherit' : ['pipe', 'pipe', 'pipe'],
303
- env: {
304
- ...process.env,
305
- ...varEnv,
306
- C4AI_ROUTINE_NAME: routine.name,
307
- C4AI_ROUTINE_PATH: routine.path
308
- }
309
- });
310
-
311
- if (isTTY) {
312
- const exitCode = await new Promise<number>((resolve) => {
313
- child.on('close', (code) => resolve(code ?? 0));
314
- child.on('error', (err) => outputError('API_ERROR', `Failed to execute routine: ${err.message}`));
315
- });
316
- process.exit(exitCode);
317
- }
318
-
319
- // Non-TTY: capture and emit single JSON summary.
320
- const stdoutPromise = child.stdout ? collectStream(child.stdout) : Promise.resolve('');
321
- const stderrPromise = child.stderr ? collectStream(child.stderr) : Promise.resolve('');
322
-
323
- if (child.stderr) {
324
- child.stderr.on('data', (chunk) => {
325
- process.stderr.write(chunk);
326
- });
327
- }
328
-
329
- const exitCode = await new Promise<number>((resolve) => {
330
- child.on('close', (code) => resolve(code ?? 0));
331
- child.on('error', (err) => outputError('API_ERROR', `Failed to execute routine: ${err.message}`));
332
- });
333
-
334
- const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
335
-
336
- output({
337
- routine: routine.name,
338
- kind: routine.kind,
339
- scope: routine.scope,
340
- status: exitCode === 0 ? 'success' : 'failed',
341
- exitCode,
342
- durationMs: Date.now() - startTime,
343
- stdout,
344
- stderr
345
- });
346
-
347
- process.exit(exitCode);
348
- }
349
-
350
- export async function routinesShowCommand(name: string, options: { global?: boolean }): Promise<void> {
351
- ensureValidRoutineName(name);
352
- const routine = resolveRoutine(name, process.cwd(), { globalOnly: options.global });
353
- if (!routine) {
354
- outputError('NOT_FOUND', `Routine not found: ${name}`);
355
- }
356
-
357
- const content = readFileSync(routine.path, 'utf-8');
358
-
359
- if (!process.stdout.isTTY) {
360
- output({ routine: routine.name, kind: routine.kind, scope: routine.scope, path: routine.path, content });
361
- return;
362
- }
363
-
364
- log(`\n${routine.path}\n`);
365
- process.stdout.write(content);
366
- if (!content.endsWith('\n')) process.stdout.write('\n');
367
- }
368
-
369
- export async function routinesRemoveCommand(name: string, options: { global?: boolean }): Promise<void> {
370
- ensureValidRoutineName(name);
371
- const routine = resolveRoutine(name, process.cwd(), { globalOnly: options.global });
372
- if (!routine) {
373
- outputError('NOT_FOUND', `Routine not found: ${name}`);
374
- }
375
-
376
- rmSync(routine.path, { force: true });
377
-
378
- output({
379
- action: 'remove',
380
- name: routine.name,
381
- scope: routine.scope,
382
- kind: routine.kind,
383
- path: routine.path
384
- });
385
- }
386
-
387
- export async function routinesEditCommand(name: string, options: { global?: boolean }): Promise<void> {
388
- ensureValidRoutineName(name);
389
- const routine = resolveRoutine(name, process.cwd(), { globalOnly: options.global });
390
- if (!routine) {
391
- outputError('NOT_FOUND', `Routine not found: ${name}`);
392
- }
393
-
394
- const editor = process.env.VISUAL || process.env.EDITOR;
395
-
396
- if (!editor || !process.stdout.isTTY || !process.stdin.isTTY) {
397
- output({
398
- action: 'edit',
399
- name: routine.name,
400
- path: routine.path,
401
- hint: editor ? 'Run your editor with the path above' : 'Set $EDITOR (or $VISUAL) to enable `cli4ai routines edit`'
402
- });
403
- return;
404
- }
405
-
406
- // SECURITY: Parse editor command to prevent shell injection
407
- // Common editors may have arguments like "code --wait" or "vim -c 'set nu'"
408
- // We split on spaces but this is a simple approach; complex cases should use exec wrapper
409
- const editorParts = editor.trim().split(/\s+/);
410
- const editorCmd = editorParts[0];
411
- const editorArgs = [...editorParts.slice(1), routine.path];
412
-
413
- // Validate editor command doesn't contain shell metacharacters in the base command
414
- if (!/^[a-zA-Z0-9_.\-\/]+$/.test(editorCmd)) {
415
- outputError('INVALID_INPUT', 'Invalid $EDITOR value - contains unsafe characters', {
416
- editor: editorCmd,
417
- hint: 'Set $EDITOR to a simple command like "vim", "nano", or "code --wait"'
418
- });
419
- }
420
-
421
- const result = spawnSync(editorCmd, editorArgs, {
422
- stdio: 'inherit'
423
- // SECURITY: shell: false (default) prevents command injection
424
- });
425
-
426
- process.exit(result.status ?? 1);
427
- }
@@ -1,127 +0,0 @@
1
- /**
2
- * cli4ai run - Execute a tool command
3
- */
4
-
5
- import { output, outputError, log } from '../lib/cli.js';
6
- import { executeTool, ExecuteToolError, type ScopeLevel } from '../core/execute.js';
7
- import { remoteRunTool, RemoteConnectionError, RemoteApiError } from '../core/remote-client.js';
8
- import { getRemote } from '../core/remotes.js';
9
-
10
- interface RunOptions {
11
- env?: string[];
12
- scope?: string;
13
- sandbox?: boolean;
14
- remote?: string;
15
- timeout?: string;
16
- }
17
-
18
- export async function runCommand(
19
- packageName: string,
20
- command: string | undefined,
21
- args: string[],
22
- options: RunOptions
23
- ): Promise<void> {
24
- // Parse environment variables from options (-e KEY=value)
25
- const extraEnv: Record<string, string> = {};
26
- if (options.env) {
27
- for (const envVar of options.env) {
28
- const eqIndex = envVar.indexOf('=');
29
- if (eqIndex > 0) {
30
- extraEnv[envVar.slice(0, eqIndex)] = envVar.slice(eqIndex + 1);
31
- }
32
- }
33
- }
34
-
35
- // Validate scope option
36
- let scope: ScopeLevel = 'full';
37
- if (options.scope) {
38
- const validScopes: ScopeLevel[] = ['read', 'write', 'full'];
39
- if (!validScopes.includes(options.scope as ScopeLevel)) {
40
- outputError('INVALID_INPUT', `Invalid scope: ${options.scope}`, {
41
- validScopes,
42
- hint: 'Use --scope read, --scope write, or --scope full'
43
- });
44
- }
45
- scope = options.scope as ScopeLevel;
46
- }
47
-
48
- // Parse timeout
49
- let timeout: number | undefined;
50
- if (options.timeout) {
51
- timeout = parseInt(options.timeout, 10);
52
- if (isNaN(timeout) || timeout < 0) {
53
- outputError('INVALID_INPUT', 'Timeout must be a positive number (milliseconds)');
54
- }
55
- }
56
-
57
- // Remote execution
58
- if (options.remote) {
59
- const remote = getRemote(options.remote);
60
- if (!remote) {
61
- outputError('NOT_FOUND', `Remote "${options.remote}" not found`, {
62
- hint: 'Use "cli4ai remotes add <name> <url>" to configure a remote'
63
- });
64
- }
65
-
66
- log(`Executing on remote: ${remote.name} (${remote.url})`);
67
-
68
- try {
69
- const result = await remoteRunTool(options.remote, {
70
- package: packageName,
71
- command,
72
- args,
73
- env: Object.keys(extraEnv).length > 0 ? extraEnv : undefined,
74
- timeout,
75
- scope
76
- });
77
-
78
- // Output stdout/stderr
79
- if (result.stdout) {
80
- process.stdout.write(result.stdout);
81
- }
82
- if (result.stderr) {
83
- process.stderr.write(result.stderr);
84
- }
85
-
86
- process.exitCode = result.exitCode;
87
-
88
- if (!result.success && result.error) {
89
- log(`Remote error: ${result.error.message}`);
90
- }
91
-
92
- return;
93
- } catch (err) {
94
- if (err instanceof RemoteConnectionError) {
95
- outputError('NETWORK_ERROR', err.message, { remote: options.remote, url: remote.url });
96
- }
97
- if (err instanceof RemoteApiError) {
98
- outputError(err.code, err.message, err.details);
99
- }
100
- const message = err instanceof Error ? err.message : String(err);
101
- outputError('API_ERROR', message);
102
- }
103
- }
104
-
105
- // Local execution
106
- try {
107
- const result = await executeTool({
108
- packageName,
109
- command,
110
- args,
111
- cwd: process.cwd(),
112
- env: extraEnv,
113
- capture: 'inherit',
114
- timeoutMs: timeout,
115
- scope,
116
- sandbox: options.sandbox ?? false
117
- });
118
- process.exitCode = result.exitCode;
119
- return;
120
- } catch (err) {
121
- if (err instanceof ExecuteToolError) {
122
- outputError(err.code, err.message, err.details);
123
- }
124
- const message = err instanceof Error ? err.message : String(err);
125
- outputError('API_ERROR', message);
126
- }
127
- }