@orchagent/cli 0.3.61 → 0.3.62
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/dist/commands/publish.js +1 -0
- package/dist/commands/service.js +251 -0
- package/package.json +1 -1
package/dist/commands/publish.js
CHANGED
|
@@ -632,6 +632,7 @@ function registerPublishCommand(program) {
|
|
|
632
632
|
sdk_compatible: sdkCompatible || undefined,
|
|
633
633
|
// Orchestration manifest (includes dependencies)
|
|
634
634
|
manifest: manifest.manifest,
|
|
635
|
+
required_secrets: manifest.required_secrets,
|
|
635
636
|
default_skills: skillsFromFlag || manifest.default_skills,
|
|
636
637
|
skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
|
|
637
638
|
allow_local_download: options.localDownload || false,
|
package/dist/commands/service.js
CHANGED
|
@@ -92,6 +92,7 @@ function registerServiceCommand(program) {
|
|
|
92
92
|
.option('--secret <NAME>', 'Workspace secret name (repeatable)', collectArray, [])
|
|
93
93
|
.option('--command <cmd>', 'Override entrypoint command')
|
|
94
94
|
.option('--arg <value>', 'Command argument (repeatable)', collectArray, [])
|
|
95
|
+
.option('--pin', 'Pin to deployed version (disable auto-update on publish)')
|
|
95
96
|
.option('--json', 'Output as JSON')
|
|
96
97
|
.action(async (agentArg, options) => {
|
|
97
98
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -160,6 +161,7 @@ function registerServiceCommand(program) {
|
|
|
160
161
|
args: options.arg.length > 0 ? options.arg : null,
|
|
161
162
|
env: Object.keys(options.env).length > 0 ? options.env : null,
|
|
162
163
|
secret_names: options.secret.length > 0 ? options.secret : null,
|
|
164
|
+
...(options.pin ? { auto_update: false } : {}),
|
|
163
165
|
}),
|
|
164
166
|
headers: { 'Content-Type': 'application/json' },
|
|
165
167
|
});
|
|
@@ -175,6 +177,9 @@ function registerServiceCommand(program) {
|
|
|
175
177
|
process.stdout.write(` ${chalk_1.default.bold('Agent:')} ${svc.agent_name}@${svc.agent_version}\n`);
|
|
176
178
|
process.stdout.write(` ${chalk_1.default.bold('State:')} ${stateColor(svc.current_state)}\n`);
|
|
177
179
|
process.stdout.write(` ${chalk_1.default.bold('URL:')} ${svc.provider_url || svc.cloud_run_url || '-'}\n`);
|
|
180
|
+
if (options.pin) {
|
|
181
|
+
process.stdout.write(` ${chalk_1.default.bold('Pinned:')} ${chalk_1.default.yellow(`yes (won't auto-update on publish)`)}\n`);
|
|
182
|
+
}
|
|
178
183
|
process.stdout.write(`\n`);
|
|
179
184
|
process.stdout.write(chalk_1.default.gray(`View logs: orch service logs ${svc.id}\n`));
|
|
180
185
|
}
|
|
@@ -344,6 +349,28 @@ function registerServiceCommand(program) {
|
|
|
344
349
|
if (svc.alert_webhook_url) {
|
|
345
350
|
process.stdout.write(` Alert URL: ${svc.alert_webhook_url.slice(0, 50)}...\n`);
|
|
346
351
|
}
|
|
352
|
+
// Environment variables
|
|
353
|
+
const envKeys = Object.keys(svc.env_json || {});
|
|
354
|
+
if (envKeys.length > 0) {
|
|
355
|
+
process.stdout.write(`\n${chalk_1.default.bold('Environment Variables')} (${envKeys.length})\n`);
|
|
356
|
+
for (const key of envKeys) {
|
|
357
|
+
process.stdout.write(` ${key}=${svc.env_json[key]}\n`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
process.stdout.write(`\n Env Vars: ${chalk_1.default.gray('none')}\n`);
|
|
362
|
+
}
|
|
363
|
+
// Attached secrets
|
|
364
|
+
const secrets = svc.secret_names || [];
|
|
365
|
+
if (secrets.length > 0) {
|
|
366
|
+
process.stdout.write(`\n${chalk_1.default.bold('Attached Secrets')} (${secrets.length})\n`);
|
|
367
|
+
for (const name of secrets) {
|
|
368
|
+
process.stdout.write(` ${name}\n`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
process.stdout.write(` Secrets: ${chalk_1.default.gray('none')}\n`);
|
|
373
|
+
}
|
|
347
374
|
// Events timeline
|
|
348
375
|
const events = svc.events || [];
|
|
349
376
|
if (events.length > 0) {
|
|
@@ -383,6 +410,230 @@ function registerServiceCommand(program) {
|
|
|
383
410
|
throw e;
|
|
384
411
|
}
|
|
385
412
|
});
|
|
413
|
+
// ============================================
|
|
414
|
+
// orch service env — manage environment variables
|
|
415
|
+
// ============================================
|
|
416
|
+
const envCmd = service
|
|
417
|
+
.command('env')
|
|
418
|
+
.description('Manage service environment variables');
|
|
419
|
+
// orch service env set <service-id> <KEY=VALUE...>
|
|
420
|
+
envCmd
|
|
421
|
+
.command('set <service-id> <pairs...>')
|
|
422
|
+
.description('Set environment variables on a service (KEY=VALUE pairs)')
|
|
423
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
424
|
+
.option('--json', 'Output as JSON')
|
|
425
|
+
.action(async (serviceId, pairs, options) => {
|
|
426
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
427
|
+
if (!config.apiKey) {
|
|
428
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
429
|
+
}
|
|
430
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
431
|
+
// Parse KEY=VALUE pairs
|
|
432
|
+
const newEnv = {};
|
|
433
|
+
for (const pair of pairs) {
|
|
434
|
+
const idx = pair.indexOf('=');
|
|
435
|
+
if (idx < 0) {
|
|
436
|
+
throw new errors_1.CliError(`Invalid format: '${pair}'. Use KEY=VALUE.`);
|
|
437
|
+
}
|
|
438
|
+
newEnv[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
439
|
+
}
|
|
440
|
+
// Fetch current service to merge env
|
|
441
|
+
const current = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/services/${serviceId}`);
|
|
442
|
+
const currentEnv = current.service.env_json || {};
|
|
443
|
+
const mergedEnv = { ...currentEnv, ...newEnv };
|
|
444
|
+
const spinner = (0, spinner_1.createSpinner)('Updating environment...');
|
|
445
|
+
spinner.start();
|
|
446
|
+
try {
|
|
447
|
+
const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/services/${serviceId}`, {
|
|
448
|
+
body: JSON.stringify({ env: mergedEnv }),
|
|
449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
450
|
+
});
|
|
451
|
+
spinner.succeed('Environment updated');
|
|
452
|
+
if (options.json) {
|
|
453
|
+
(0, output_1.printJson)(result.service);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const addedKeys = Object.keys(newEnv);
|
|
457
|
+
process.stdout.write(`${chalk_1.default.green('\u2713')} Set ${addedKeys.length} variable${addedKeys.length !== 1 ? 's' : ''}: ${addedKeys.join(', ')}\n`);
|
|
458
|
+
if (current.service.desired_state !== 'stopped') {
|
|
459
|
+
process.stdout.write(chalk_1.default.gray('Service restarted to apply changes.\n'));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
spinner.fail('Failed to update environment');
|
|
464
|
+
throw e;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
// orch service env unset <service-id> <KEY...>
|
|
468
|
+
envCmd
|
|
469
|
+
.command('unset <service-id> <keys...>')
|
|
470
|
+
.description('Remove environment variables from a service')
|
|
471
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
472
|
+
.option('--json', 'Output as JSON')
|
|
473
|
+
.action(async (serviceId, keys, options) => {
|
|
474
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
475
|
+
if (!config.apiKey) {
|
|
476
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
477
|
+
}
|
|
478
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
479
|
+
// Fetch current service
|
|
480
|
+
const current = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/services/${serviceId}`);
|
|
481
|
+
const currentEnv = { ...(current.service.env_json || {}) };
|
|
482
|
+
// Remove specified keys
|
|
483
|
+
const removed = [];
|
|
484
|
+
for (const key of keys) {
|
|
485
|
+
if (key in currentEnv) {
|
|
486
|
+
delete currentEnv[key];
|
|
487
|
+
removed.push(key);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (removed.length === 0) {
|
|
491
|
+
process.stdout.write(chalk_1.default.yellow('No matching environment variables found.\n'));
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const spinner = (0, spinner_1.createSpinner)('Updating environment...');
|
|
495
|
+
spinner.start();
|
|
496
|
+
try {
|
|
497
|
+
const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/services/${serviceId}`, {
|
|
498
|
+
body: JSON.stringify({ env: currentEnv }),
|
|
499
|
+
headers: { 'Content-Type': 'application/json' },
|
|
500
|
+
});
|
|
501
|
+
spinner.succeed('Environment updated');
|
|
502
|
+
if (options.json) {
|
|
503
|
+
(0, output_1.printJson)(result.service);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
process.stdout.write(`${chalk_1.default.green('\u2713')} Removed ${removed.length} variable${removed.length !== 1 ? 's' : ''}: ${removed.join(', ')}\n`);
|
|
507
|
+
if (current.service.desired_state !== 'stopped') {
|
|
508
|
+
process.stdout.write(chalk_1.default.gray('Service restarted to apply changes.\n'));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
spinner.fail('Failed to update environment');
|
|
513
|
+
throw e;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
// orch service env list <service-id>
|
|
517
|
+
envCmd
|
|
518
|
+
.command('list <service-id>')
|
|
519
|
+
.description('List environment variables for a service')
|
|
520
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
521
|
+
.option('--json', 'Output as JSON')
|
|
522
|
+
.action(async (serviceId, options) => {
|
|
523
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
524
|
+
if (!config.apiKey) {
|
|
525
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
526
|
+
}
|
|
527
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
528
|
+
const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/services/${serviceId}`);
|
|
529
|
+
const envJson = result.service.env_json || {};
|
|
530
|
+
if (options.json) {
|
|
531
|
+
(0, output_1.printJson)(envJson);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const keys = Object.keys(envJson);
|
|
535
|
+
if (keys.length === 0) {
|
|
536
|
+
process.stdout.write('No environment variables set.\n');
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
for (const key of keys) {
|
|
540
|
+
process.stdout.write(`${key}=${envJson[key]}\n`);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
// ============================================
|
|
544
|
+
// orch service secret — manage attached workspace secrets
|
|
545
|
+
// ============================================
|
|
546
|
+
const secretCmd = service
|
|
547
|
+
.command('secret')
|
|
548
|
+
.description('Manage workspace secrets attached to a service');
|
|
549
|
+
// orch service secret add <service-id> <NAME...>
|
|
550
|
+
secretCmd
|
|
551
|
+
.command('add <service-id> <names...>')
|
|
552
|
+
.description('Attach workspace secrets to a service')
|
|
553
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
554
|
+
.option('--json', 'Output as JSON')
|
|
555
|
+
.action(async (serviceId, names, options) => {
|
|
556
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
557
|
+
if (!config.apiKey) {
|
|
558
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
559
|
+
}
|
|
560
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
561
|
+
// Fetch current service and merge
|
|
562
|
+
const current = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/services/${serviceId}`);
|
|
563
|
+
const currentSecrets = current.service.secret_names || [];
|
|
564
|
+
const merged = [...new Set([...currentSecrets, ...names])];
|
|
565
|
+
const spinner = (0, spinner_1.createSpinner)('Attaching secrets...');
|
|
566
|
+
spinner.start();
|
|
567
|
+
try {
|
|
568
|
+
const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/services/${serviceId}`, {
|
|
569
|
+
body: JSON.stringify({ secret_names: merged }),
|
|
570
|
+
headers: { 'Content-Type': 'application/json' },
|
|
571
|
+
});
|
|
572
|
+
spinner.succeed('Secrets attached');
|
|
573
|
+
if (options.json) {
|
|
574
|
+
(0, output_1.printJson)(result.service);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const added = names.filter(n => !currentSecrets.includes(n));
|
|
578
|
+
if (added.length > 0) {
|
|
579
|
+
process.stdout.write(`${chalk_1.default.green('\u2713')} Attached ${added.length} secret${added.length !== 1 ? 's' : ''}: ${added.join(', ')}\n`);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
process.stdout.write(chalk_1.default.yellow('All specified secrets were already attached.\n'));
|
|
583
|
+
}
|
|
584
|
+
if (current.service.desired_state !== 'stopped') {
|
|
585
|
+
process.stdout.write(chalk_1.default.gray('Service restarted to apply changes.\n'));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch (e) {
|
|
589
|
+
spinner.fail('Failed to attach secrets');
|
|
590
|
+
throw e;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
// orch service secret remove <service-id> <NAME...>
|
|
594
|
+
secretCmd
|
|
595
|
+
.command('remove <service-id> <names...>')
|
|
596
|
+
.description('Detach workspace secrets from a service')
|
|
597
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
598
|
+
.option('--json', 'Output as JSON')
|
|
599
|
+
.action(async (serviceId, names, options) => {
|
|
600
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
601
|
+
if (!config.apiKey) {
|
|
602
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
603
|
+
}
|
|
604
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
605
|
+
// Fetch current service and filter
|
|
606
|
+
const current = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/services/${serviceId}`);
|
|
607
|
+
const currentSecrets = current.service.secret_names || [];
|
|
608
|
+
const namesToRemove = new Set(names);
|
|
609
|
+
const filtered = currentSecrets.filter(n => !namesToRemove.has(n));
|
|
610
|
+
const removed = currentSecrets.filter(n => namesToRemove.has(n));
|
|
611
|
+
if (removed.length === 0) {
|
|
612
|
+
process.stdout.write(chalk_1.default.yellow('No matching secrets found on this service.\n'));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const spinner = (0, spinner_1.createSpinner)('Detaching secrets...');
|
|
616
|
+
spinner.start();
|
|
617
|
+
try {
|
|
618
|
+
const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/services/${serviceId}`, {
|
|
619
|
+
body: JSON.stringify({ secret_names: filtered }),
|
|
620
|
+
headers: { 'Content-Type': 'application/json' },
|
|
621
|
+
});
|
|
622
|
+
spinner.succeed('Secrets detached');
|
|
623
|
+
if (options.json) {
|
|
624
|
+
(0, output_1.printJson)(result.service);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
process.stdout.write(`${chalk_1.default.green('\u2713')} Detached ${removed.length} secret${removed.length !== 1 ? 's' : ''}: ${removed.join(', ')}\n`);
|
|
628
|
+
if (current.service.desired_state !== 'stopped') {
|
|
629
|
+
process.stdout.write(chalk_1.default.gray('Service restarted to apply changes.\n'));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
spinner.fail('Failed to detach secrets');
|
|
634
|
+
throw e;
|
|
635
|
+
}
|
|
636
|
+
});
|
|
386
637
|
}
|
|
387
638
|
// ============================================
|
|
388
639
|
// OPTION COLLECTORS
|
package/package.json
CHANGED