@orchagent/cli 0.3.64 → 0.3.65

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.
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerSecretsCommand = registerSecretsCommand;
7
+ const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("../lib/config");
10
+ const api_1 = require("../lib/api");
11
+ const errors_1 = require("../lib/errors");
12
+ const output_1 = require("../lib/output");
13
+ // ============================================
14
+ // HELPERS
15
+ // ============================================
16
+ const SECRET_NAME_REGEX = /^[A-Z][A-Z0-9_]*$/;
17
+ async function resolveWorkspaceId(config, slug) {
18
+ const configFile = await (0, config_1.loadConfig)();
19
+ const targetSlug = slug ?? configFile.workspace;
20
+ if (!targetSlug) {
21
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
22
+ }
23
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
24
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
25
+ if (!workspace) {
26
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
27
+ }
28
+ return workspace.id;
29
+ }
30
+ function formatDate(iso) {
31
+ if (!iso)
32
+ return '-';
33
+ return new Date(iso).toLocaleString();
34
+ }
35
+ function validateSecretName(name) {
36
+ if (!name || name.length > 128) {
37
+ throw new errors_1.CliError('Secret name must be 1-128 characters.');
38
+ }
39
+ if (!SECRET_NAME_REGEX.test(name)) {
40
+ throw new errors_1.CliError(`Invalid secret name '${name}'.\n\n` +
41
+ 'Secret names must:\n' +
42
+ ' - Start with an uppercase letter (A-Z)\n' +
43
+ ' - Contain only uppercase letters, digits, and underscores\n\n' +
44
+ 'Examples: STRIPE_SECRET_KEY, DISCORD_TOKEN, MY_API_KEY_2');
45
+ }
46
+ }
47
+ async function findSecretByName(config, workspaceId, name) {
48
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/secrets`);
49
+ return result.secrets.find((s) => s.name === name);
50
+ }
51
+ // ============================================
52
+ // COMMAND REGISTRATION
53
+ // ============================================
54
+ function registerSecretsCommand(program) {
55
+ const secrets = program
56
+ .command('secrets')
57
+ .description('Manage workspace secrets (injected as env vars into agent sandboxes)');
58
+ // orch secrets list
59
+ secrets
60
+ .command('list')
61
+ .description('List secrets in your workspace (names and metadata, never values)')
62
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
63
+ .option('--json', 'Output as JSON')
64
+ .action(async (options) => {
65
+ const config = await (0, config_1.getResolvedConfig)();
66
+ if (!config.apiKey) {
67
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
68
+ }
69
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
70
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/secrets`);
71
+ if (options.json) {
72
+ (0, output_1.printJson)(result);
73
+ return;
74
+ }
75
+ if (result.secrets.length === 0) {
76
+ process.stdout.write('No secrets found in this workspace.\n');
77
+ process.stdout.write(chalk_1.default.gray('\nAdd one with: orch secrets set MY_SECRET_NAME my-secret-value\n'));
78
+ return;
79
+ }
80
+ const table = new cli_table3_1.default({
81
+ head: [
82
+ chalk_1.default.bold('Name'),
83
+ chalk_1.default.bold('Type'),
84
+ chalk_1.default.bold('Description'),
85
+ chalk_1.default.bold('Updated'),
86
+ ],
87
+ });
88
+ for (const s of result.secrets) {
89
+ table.push([
90
+ s.name,
91
+ s.secret_type === 'llm_key'
92
+ ? chalk_1.default.cyan(`llm_key (${s.llm_provider ?? '?'})`)
93
+ : chalk_1.default.gray('custom'),
94
+ s.description ? s.description.slice(0, 40) + (s.description.length > 40 ? '...' : '') : chalk_1.default.gray('-'),
95
+ formatDate(s.updated_at),
96
+ ]);
97
+ }
98
+ process.stdout.write(`\n${table.toString()}\n`);
99
+ process.stdout.write(chalk_1.default.gray(`\n${result.secrets.length} secret(s)\n`));
100
+ });
101
+ // orch secrets set <NAME> <VALUE>
102
+ secrets
103
+ .command('set <name> <value>')
104
+ .description('Create or update a workspace secret')
105
+ .option('--description <text>', 'Description of what this secret is for')
106
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
107
+ .action(async (name, value, options) => {
108
+ const config = await (0, config_1.getResolvedConfig)();
109
+ if (!config.apiKey) {
110
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
111
+ }
112
+ validateSecretName(name);
113
+ if (!value) {
114
+ throw new errors_1.CliError('Secret value cannot be empty.');
115
+ }
116
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
117
+ // Check if secret already exists (by name)
118
+ const existing = await findSecretByName(config, workspaceId, name);
119
+ if (existing) {
120
+ // Update existing secret
121
+ const body = { value };
122
+ if (options.description !== undefined) {
123
+ body.description = options.description;
124
+ }
125
+ const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/secrets/${existing.id}`, {
126
+ body: JSON.stringify(body),
127
+ headers: { 'Content-Type': 'application/json' },
128
+ });
129
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Updated secret ${chalk_1.default.bold(name)}\n`);
130
+ if (result.restarted_services && result.restarted_services.length > 0) {
131
+ process.stdout.write(chalk_1.default.yellow('\n Restarted running services that use this secret:\n'));
132
+ for (const svc of result.restarted_services) {
133
+ process.stdout.write(` - ${svc.service_name}\n`);
134
+ }
135
+ }
136
+ }
137
+ else {
138
+ // Create new secret
139
+ const body = {
140
+ name,
141
+ value,
142
+ secret_type: 'custom',
143
+ };
144
+ if (options.description !== undefined) {
145
+ body.description = options.description;
146
+ }
147
+ await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/secrets`, {
148
+ body: JSON.stringify(body),
149
+ headers: { 'Content-Type': 'application/json' },
150
+ });
151
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Created secret ${chalk_1.default.bold(name)}\n`);
152
+ }
153
+ });
154
+ // orch secrets delete <NAME>
155
+ secrets
156
+ .command('delete <name>')
157
+ .description('Delete a workspace secret')
158
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
159
+ .action(async (name, options) => {
160
+ const config = await (0, config_1.getResolvedConfig)();
161
+ if (!config.apiKey) {
162
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
163
+ }
164
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
165
+ // Resolve name → ID
166
+ const existing = await findSecretByName(config, workspaceId, name);
167
+ if (!existing) {
168
+ throw new errors_1.CliError(`Secret '${name}' not found in this workspace.\n\n` +
169
+ 'Run `orch secrets list` to see available secrets.');
170
+ }
171
+ await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/secrets/${existing.id}`);
172
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Deleted secret ${chalk_1.default.bold(name)}\n`);
173
+ });
174
+ }