@orchagent/cli 0.3.60 → 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.
@@ -165,30 +165,27 @@ license: MIT
165
165
  Instructions and guidance for AI agents...
166
166
  `;
167
167
  function resolveInitFlavor(typeOption) {
168
- const normalized = (typeOption || 'agent').trim().toLowerCase();
168
+ const normalized = (typeOption || 'prompt').trim().toLowerCase();
169
169
  if (normalized === 'skill') {
170
170
  return { type: 'skill' };
171
171
  }
172
- if (normalized === 'agent') {
173
- return { type: 'agent', flavor: 'direct_llm' };
174
- }
175
172
  if (normalized === 'prompt') {
176
- return { type: 'agent', flavor: 'direct_llm' };
173
+ return { type: 'prompt', flavor: 'direct_llm' };
177
174
  }
178
- if (normalized === 'agentic') {
175
+ if (normalized === 'agent' || normalized === 'agentic') {
179
176
  return { type: 'agent', flavor: 'managed_loop' };
180
177
  }
181
178
  if (normalized === 'tool' || normalized === 'code') {
182
- return { type: 'agent', flavor: 'code_runtime' };
179
+ return { type: 'tool', flavor: 'code_runtime' };
183
180
  }
184
- throw new errors_1.CliError(`Unknown --type '${typeOption}'. Use 'agent' or 'skill' (legacy: prompt, tool, agentic, code).`);
181
+ throw new errors_1.CliError(`Unknown --type '${typeOption}'. Use 'prompt', 'tool', 'agent', or 'skill' (legacy aliases: agentic, code).`);
185
182
  }
186
183
  function registerInitCommand(program) {
187
184
  program
188
185
  .command('init')
189
186
  .description('Initialize a new agent project')
190
187
  .argument('[name]', 'Agent name (default: current directory name)')
191
- .option('--type <type>', 'Type: agent or skill (legacy: prompt, tool, agentic, code)', 'agent')
188
+ .option('--type <type>', 'Type: prompt, tool, agent, or skill (legacy aliases: agentic, code)', 'prompt')
192
189
  .option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
193
190
  .action(async (name, options) => {
194
191
  const cwd = process.cwd();
@@ -247,13 +244,13 @@ function registerInitCommand(program) {
247
244
  throw err;
248
245
  }
249
246
  }
250
- if (initMode.flavor === 'direct_llm' && runMode === 'always_on') {
251
- throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }).");
247
+ if (initMode.flavor !== 'code_runtime' && runMode === 'always_on') {
248
+ throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool for code-runtime agents.");
252
249
  }
253
250
  // Create manifest and type-specific files
254
251
  const manifest = JSON.parse(MANIFEST_TEMPLATE);
255
252
  manifest.name = agentName;
256
- manifest.type = 'agent';
253
+ manifest.type = initMode.type;
257
254
  manifest.run_mode = runMode;
258
255
  if (initMode.flavor === 'managed_loop') {
259
256
  manifest.description = 'An AI agent with tool use';
@@ -163,13 +163,17 @@ async function collectSkillFiles(skillDir, maxFiles = 20, maxTotalSize = 500_000
163
163
  }
164
164
  function canonicalizeManifestType(typeValue) {
165
165
  const rawType = (typeValue || 'agent').trim().toLowerCase();
166
- if (rawType === 'skill') {
167
- return { canonicalType: 'skill', rawType };
166
+ if (['prompt', 'tool', 'agent', 'skill'].includes(rawType)) {
167
+ return { canonicalType: rawType, rawType };
168
168
  }
169
- if (['agent', 'prompt', 'tool', 'agentic', 'code'].includes(rawType)) {
169
+ // Legacy aliases
170
+ if (rawType === 'agentic') {
170
171
  return { canonicalType: 'agent', rawType };
171
172
  }
172
- throw new errors_1.CliError(`Invalid type '${typeValue}'. Use 'agent' or 'skill' (legacy values accepted: prompt, tool, agentic, code).`);
173
+ if (rawType === 'code') {
174
+ return { canonicalType: 'tool', rawType };
175
+ }
176
+ throw new errors_1.CliError(`Invalid type '${typeValue}'. Use 'prompt', 'tool', 'agent', or 'skill' (legacy aliases: agentic, code).`);
173
177
  }
174
178
  function normalizeRunMode(runMode) {
175
179
  const normalized = (runMode || 'on_demand').trim().toLowerCase();
@@ -628,6 +632,7 @@ function registerPublishCommand(program) {
628
632
  sdk_compatible: sdkCompatible || undefined,
629
633
  // Orchestration manifest (includes dependencies)
630
634
  manifest: manifest.manifest,
635
+ required_secrets: manifest.required_secrets,
631
636
  default_skills: skillsFromFlag || manifest.default_skills,
632
637
  skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
633
638
  allow_local_download: options.localDownload || false,
@@ -726,6 +731,10 @@ function registerPublishCommand(program) {
726
731
  process.stdout.write(` ${chalk_1.default.cyan('Using workspace default environment')}\n`);
727
732
  }
728
733
  }
734
+ // Show service auto-update info
735
+ if (uploadResult.services_updated && uploadResult.services_updated > 0) {
736
+ process.stdout.write(` ${chalk_1.default.green(`Updated ${uploadResult.services_updated} service(s) to ${assignedVersion}`)}\n`);
737
+ }
729
738
  }
730
739
  finally {
731
740
  // Clean up temp files
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.60",
3
+ "version": "0.3.62",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",