@zonko-ai/harbor 0.1.0

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 (86) hide show
  1. package/README.md +15 -0
  2. package/dist/api.d.ts +53 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +842 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.d.ts.map +1 -0
  8. package/dist/capabilities.js +308 -0
  9. package/dist/capabilities.js.map +1 -0
  10. package/dist/constants.d.ts +4 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +4 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/dev.d.ts +14 -0
  15. package/dist/dev.d.ts.map +1 -0
  16. package/dist/dev.js +203 -0
  17. package/dist/dev.js.map +1 -0
  18. package/dist/foreground.d.ts +13 -0
  19. package/dist/foreground.d.ts.map +1 -0
  20. package/dist/foreground.js +54 -0
  21. package/dist/foreground.js.map +1 -0
  22. package/dist/import-openapi.d.ts +12 -0
  23. package/dist/import-openapi.d.ts.map +1 -0
  24. package/dist/import-openapi.js +488 -0
  25. package/dist/import-openapi.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +941 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/local-config.d.ts +12 -0
  31. package/dist/local-config.d.ts.map +1 -0
  32. package/dist/local-config.js +93 -0
  33. package/dist/local-config.js.map +1 -0
  34. package/dist/local-daemon-entry.d.ts +2 -0
  35. package/dist/local-daemon-entry.d.ts.map +1 -0
  36. package/dist/local-daemon-entry.js +545 -0
  37. package/dist/local-daemon-entry.js.map +1 -0
  38. package/dist/local-daemon.d.ts +58 -0
  39. package/dist/local-daemon.d.ts.map +1 -0
  40. package/dist/local-daemon.js +385 -0
  41. package/dist/local-daemon.js.map +1 -0
  42. package/dist/mcp/api-client.d.ts +72 -0
  43. package/dist/mcp/api-client.d.ts.map +1 -0
  44. package/dist/mcp/api-client.js +210 -0
  45. package/dist/mcp/api-client.js.map +1 -0
  46. package/dist/mcp/index.d.ts +4 -0
  47. package/dist/mcp/index.d.ts.map +1 -0
  48. package/dist/mcp/index.js +18 -0
  49. package/dist/mcp/index.js.map +1 -0
  50. package/dist/mcp/schema.d.ts +3 -0
  51. package/dist/mcp/schema.d.ts.map +1 -0
  52. package/dist/mcp/schema.js +95 -0
  53. package/dist/mcp/schema.js.map +1 -0
  54. package/dist/mcp/server.d.ts +5 -0
  55. package/dist/mcp/server.d.ts.map +1 -0
  56. package/dist/mcp/server.js +57 -0
  57. package/dist/mcp/server.js.map +1 -0
  58. package/dist/mcp/tools.d.ts +12 -0
  59. package/dist/mcp/tools.d.ts.map +1 -0
  60. package/dist/mcp/tools.js +54 -0
  61. package/dist/mcp/tools.js.map +1 -0
  62. package/dist/output-download.d.ts +8 -0
  63. package/dist/output-download.d.ts.map +1 -0
  64. package/dist/output-download.js +210 -0
  65. package/dist/output-download.js.map +1 -0
  66. package/dist/output.d.ts +124 -0
  67. package/dist/output.d.ts.map +1 -0
  68. package/dist/output.js +752 -0
  69. package/dist/output.js.map +1 -0
  70. package/dist/passthrough.d.ts +15 -0
  71. package/dist/passthrough.d.ts.map +1 -0
  72. package/dist/passthrough.js +68 -0
  73. package/dist/passthrough.js.map +1 -0
  74. package/dist/runtime-attribution.d.ts +5 -0
  75. package/dist/runtime-attribution.d.ts.map +1 -0
  76. package/dist/runtime-attribution.js +86 -0
  77. package/dist/runtime-attribution.js.map +1 -0
  78. package/dist/state.d.ts +56 -0
  79. package/dist/state.d.ts.map +1 -0
  80. package/dist/state.js +374 -0
  81. package/dist/state.js.map +1 -0
  82. package/dist/types.d.ts +284 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +2 -0
  85. package/dist/types.js.map +1 -0
  86. package/package.json +35 -0
package/dist/index.js ADDED
@@ -0,0 +1,941 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { Writable } from 'node:stream';
5
+ import { goke } from 'goke';
6
+ import { runHarborMcpCli } from './mcp/index.js';
7
+ import { ApiError, approveRequest, claimAgent, createComposioLinkSession, deleteComposioServiceRoute, deleteSecretReference, ensureLocalOwnerSession, fetchCapability, fetchCatalog, fetchComposioLinkSession, fetchComposioService, fetchHealth, fetchWhoAmI, getSecretReference, listSecretReferences, setComposioServiceRoute, setSecretReference, startExecution, } from './api.js';
8
+ import { findCapability, registerCapabilityCommands } from './capabilities.js';
9
+ import { streamExecutionForeground } from './foreground.js';
10
+ import { CliInputError, getOutputMode, renderApprove, renderClaim, renderComposioLinkSession, renderComposioRoute, renderComposioService, renderDevDown, renderDevUp, renderError, renderExecutionStart, renderInspect, renderLocalDaemonLog, renderLocalDaemonStatus, renderLs, renderProfileCleanup, renderProfileCurrent, renderProfileList, renderSecretReference, renderSecretReferenceDeleted, renderSecretReferenceList, renderSetup, renderStatus, renderWhoAmI, withOwner, } from './output.js';
11
+ import { buildCapabilityPassthroughInput, buildCapabilityPassthroughUsage, isCapabilitySlugCandidate, passthroughAllowsJson, shouldBootstrapCapabilitiesForCommand, supportsArgvPassthrough, } from './passthrough.js';
12
+ import { classifyCommandForLocalRuntime, ensureLocalDevCommandProfile, ensureLocalRuntimeForCommand, fetchLocalDaemonStatus, persistLocalProfileSessionState, readLocalDaemonLog, stopLocalRuntime, } from './local-daemon.js';
13
+ import { clearProfileSession, deleteProfile, getActiveProfileSlug, getCatalog, getProfile, listProfiles, normalizeProfileSlug, profileExists, saveCatalog, saveProfile, setActiveProfile, } from './state.js';
14
+ import { CLI_PACKAGE_VERSION } from './constants.js';
15
+ const VERSION = CLI_PACKAGE_VERSION;
16
+ const rawUserArgv = () => process.argv.slice(2);
17
+ const commandArgIndex = (command, argv = rawUserArgv()) => argv.indexOf(command);
18
+ const bootstrapModeFromArgvForCommand = (command) => {
19
+ const argv = rawUserArgv();
20
+ const commandIndex = command ? commandArgIndex(command, argv) : -1;
21
+ const modeArgs = commandIndex >= 0 ? argv.slice(0, commandIndex) : argv;
22
+ if (modeArgs.includes('--json')) {
23
+ return 'json';
24
+ }
25
+ if (modeArgs.includes('--plain')) {
26
+ return 'plain';
27
+ }
28
+ return 'human';
29
+ };
30
+ const commandTokensFromArgv = (argv = rawUserArgv()) => {
31
+ const tokens = [];
32
+ for (let index = 0; index < argv.length; index += 1) {
33
+ const arg = argv[index];
34
+ if (arg === '--profile') {
35
+ index += 1;
36
+ continue;
37
+ }
38
+ if (arg.startsWith('--profile=')) {
39
+ continue;
40
+ }
41
+ if (arg.startsWith('-')) {
42
+ continue;
43
+ }
44
+ tokens.push(arg);
45
+ }
46
+ return tokens;
47
+ };
48
+ const bootstrapModeFromArgv = () => bootstrapModeFromArgvForCommand(firstCommandArg());
49
+ const firstCommandArg = () => commandTokensFromArgv()[0] ?? null;
50
+ const loadBootstrapCatalog = async () => {
51
+ try {
52
+ return await fetchCatalog();
53
+ }
54
+ catch {
55
+ return getCatalog()?.capabilities ?? [];
56
+ }
57
+ };
58
+ const bootstrapCapabilitiesForCommand = async (command) => {
59
+ const cachedCapabilities = getCatalog()?.capabilities ?? [];
60
+ if (!shouldBootstrapCapabilitiesForCommand(command)) {
61
+ return cachedCapabilities;
62
+ }
63
+ if (command && findCapability(cachedCapabilities, command)) {
64
+ return cachedCapabilities;
65
+ }
66
+ return await loadBootstrapCatalog();
67
+ };
68
+ const withCommandErrorHandling = (handler) => async (...args) => {
69
+ const options = (args.length > 0 ? args[args.length - 1] : {});
70
+ const mode = getOutputMode(options);
71
+ try {
72
+ await handler(...args);
73
+ }
74
+ catch (error) {
75
+ renderError(mode, error);
76
+ process.exitCode = 1;
77
+ }
78
+ };
79
+ const resolveOptionValue = (positionalValue, optionValue, flagName, message, invocation, note) => {
80
+ if (optionValue === true) {
81
+ throw new CliInputError(`Flag ${flagName} requires a value.`, invocation, note);
82
+ }
83
+ const value = positionalValue ?? optionValue;
84
+ if (typeof value !== 'string' || value.length === 0) {
85
+ throw new CliInputError(message, invocation, note);
86
+ }
87
+ return value;
88
+ };
89
+ const stripTrailingLineEnding = (value) => value.replace(/\r?\n$/, '');
90
+ const readStdin = async () => {
91
+ let value = '';
92
+ process.stdin.setEncoding('utf8');
93
+ for await (const chunk of process.stdin) {
94
+ value += chunk;
95
+ }
96
+ return stripTrailingLineEnding(value);
97
+ };
98
+ const promptHidden = async (prompt) => {
99
+ let muted = false;
100
+ const output = new Writable({
101
+ write(chunk, encoding, callback) {
102
+ if (!muted) {
103
+ process.stderr.write(chunk, encoding);
104
+ }
105
+ callback();
106
+ },
107
+ });
108
+ const readline = createInterface({
109
+ input: process.stdin,
110
+ output,
111
+ terminal: true,
112
+ });
113
+ try {
114
+ process.stderr.write(prompt);
115
+ muted = true;
116
+ const value = await readline.question('');
117
+ process.stderr.write('\n');
118
+ return value;
119
+ }
120
+ finally {
121
+ muted = false;
122
+ readline.close();
123
+ }
124
+ };
125
+ const readSecretValue = async (options, invocation) => {
126
+ if (options.value === true) {
127
+ throw new CliInputError('Flag --value requires a value.', invocation);
128
+ }
129
+ if (typeof options.value === 'string') {
130
+ process.stderr.write('Warning: --value exposes the secret in your shell history. Prefer stdin when possible.\n');
131
+ return options.value;
132
+ }
133
+ if (process.stdin.isTTY === false) {
134
+ return readStdin();
135
+ }
136
+ return promptHidden('Secret value: ');
137
+ };
138
+ const SETUP_INVOCATION = 'harbor setup [api-url]';
139
+ const promptWithDefault = async (label, defaultValue) => {
140
+ if (process.stdin.isTTY === false) {
141
+ return defaultValue;
142
+ }
143
+ const readline = createInterface({
144
+ input: process.stdin,
145
+ output: process.stderr,
146
+ terminal: true,
147
+ });
148
+ try {
149
+ const answer = await readline.question(`${label} [${defaultValue}]: `);
150
+ const value = answer.trim();
151
+ return value.length > 0 ? value : defaultValue;
152
+ }
153
+ finally {
154
+ readline.close();
155
+ }
156
+ };
157
+ const resolveSetupValue = async (explicitValue, envValue, label, defaultValue, flagName) => {
158
+ if (explicitValue === true && flagName) {
159
+ throw new CliInputError(`Flag ${flagName} requires a value.`, SETUP_INVOCATION);
160
+ }
161
+ if (typeof explicitValue === 'string' && explicitValue.trim().length > 0) {
162
+ return explicitValue.trim();
163
+ }
164
+ if (typeof envValue === 'string' && envValue.trim().length > 0) {
165
+ return envValue.trim();
166
+ }
167
+ return promptWithDefault(label, defaultValue);
168
+ };
169
+ const resolveSetupMetadataUrl = (target) => {
170
+ const trimmed = target.trim().replace(/\/$/, '');
171
+ if (trimmed.length === 0) {
172
+ throw new CliInputError('Missing Harbor API URL.', SETUP_INVOCATION);
173
+ }
174
+ if (trimmed.endsWith('/.well-known/harbor.json')) {
175
+ return trimmed;
176
+ }
177
+ return `${trimmed}/.well-known/harbor.json`;
178
+ };
179
+ const readRequiredManifestString = (record, key) => {
180
+ const value = record[key];
181
+ if (typeof value !== 'string' || value.length === 0) {
182
+ throw new Error(`Harbor manifest is missing required field: ${key}`);
183
+ }
184
+ return value;
185
+ };
186
+ const readOptionalManifestString = (record, key) => {
187
+ const value = record[key];
188
+ return typeof value === 'string' && value.length > 0 ? value : null;
189
+ };
190
+ const fetchSetupManifest = async (target) => {
191
+ const metadataUrl = resolveSetupMetadataUrl(target);
192
+ let response;
193
+ try {
194
+ response = await fetch(metadataUrl);
195
+ }
196
+ catch (error) {
197
+ throw new Error(`Failed to fetch Harbor manifest from ${metadataUrl}: ${error instanceof Error ? error.message : String(error)}`);
198
+ }
199
+ if (!response.ok) {
200
+ throw new Error(`Failed to fetch Harbor manifest from ${metadataUrl}: HTTP ${response.status}`);
201
+ }
202
+ const payload = await response.json();
203
+ if (typeof payload !== 'object' || payload === null) {
204
+ throw new Error('Harbor manifest response is not a JSON object');
205
+ }
206
+ const record = payload;
207
+ const schemaVersion = readRequiredManifestString(record, 'schema_version');
208
+ if (schemaVersion !== 'harbor.compatibility.v2' && schemaVersion !== 'harbor.deployment.v1') {
209
+ throw new Error(`Unsupported Harbor manifest schema version: ${schemaVersion}`);
210
+ }
211
+ return {
212
+ schema_version: schemaVersion,
213
+ environment: readRequiredManifestString(record, 'environment'),
214
+ api_url: readRequiredManifestString(record, 'api_url'),
215
+ dashboard_url: readRequiredManifestString(record, 'dashboard_url'),
216
+ auth_url: readRequiredManifestString(record, 'auth_url'),
217
+ metadata_url: readRequiredManifestString(record, 'metadata_url'),
218
+ catalog_stage: readRequiredManifestString(record, 'catalog_stage'),
219
+ runner_mode: readRequiredManifestString(record, 'runner_mode'),
220
+ deployment_revision: readOptionalManifestString(record, 'deployment_revision'),
221
+ package_version: readOptionalManifestString(record, 'package_version'),
222
+ minimum_cli_version: readOptionalManifestString(record, 'minimum_cli_version'),
223
+ minimum_runner_version: readOptionalManifestString(record, 'minimum_runner_version'),
224
+ api_protocol: record.api_protocol,
225
+ supported_daemon_protocols: record.supported_daemon_protocols,
226
+ supported_runner_protocols: record.supported_runner_protocols,
227
+ };
228
+ };
229
+ const writeSetupProfile = (manifest, profileSlug) => {
230
+ const existing = getProfile(profileSlug);
231
+ const sameApi = existing.api_url === manifest.api_url;
232
+ saveProfile({
233
+ profile_slug: profileSlug,
234
+ api_url: manifest.api_url,
235
+ auth_url: manifest.auth_url,
236
+ dashboard_url: manifest.dashboard_url,
237
+ metadata_url: manifest.metadata_url,
238
+ environment: manifest.environment,
239
+ deployment_revision: manifest.deployment_revision,
240
+ catalog_stage: manifest.catalog_stage,
241
+ runner_mode: manifest.runner_mode,
242
+ api_protocol: manifest.api_protocol,
243
+ supported_daemon_protocols: manifest.supported_daemon_protocols,
244
+ supported_runner_protocols: manifest.supported_runner_protocols,
245
+ package_version: manifest.package_version,
246
+ minimum_cli_version: manifest.minimum_cli_version,
247
+ minimum_runner_version: manifest.minimum_runner_version,
248
+ agent_id: sameApi ? existing.agent_id : null,
249
+ owner_scope_id: sameApi ? existing.owner_scope_id : null,
250
+ owner: sameApi ? existing.owner : null,
251
+ owner_display_name: sameApi ? existing.owner_display_name : null,
252
+ owner_token: sameApi ? existing.owner_token : null,
253
+ owner_token_expires_at: sameApi ? existing.owner_token_expires_at : null,
254
+ agent_token: sameApi ? existing.agent_token : null,
255
+ token_expires_at: sameApi ? existing.token_expires_at : null,
256
+ pending_claim_display_name: sameApi ? existing.pending_claim_display_name : null,
257
+ pending_claim_approval_id: sameApi ? existing.pending_claim_approval_id : null,
258
+ }, profileSlug);
259
+ setActiveProfile(profileSlug);
260
+ };
261
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
262
+ const openBrowser = (url) => {
263
+ if (!url) {
264
+ return;
265
+ }
266
+ if (process.platform === 'darwin') {
267
+ spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
268
+ return;
269
+ }
270
+ if (process.platform === 'win32') {
271
+ spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true, shell: false }).unref();
272
+ return;
273
+ }
274
+ spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
275
+ };
276
+ const parseWaitSeconds = (value, invocation) => {
277
+ if (value === undefined) {
278
+ return 180;
279
+ }
280
+ if (value === true) {
281
+ throw new CliInputError('Flag --wait-seconds requires a value.', invocation);
282
+ }
283
+ const seconds = Number.parseInt(String(value), 10);
284
+ if (!Number.isFinite(seconds) || seconds <= 0) {
285
+ throw new CliInputError('Flag --wait-seconds must be a positive integer.', invocation);
286
+ }
287
+ return seconds;
288
+ };
289
+ const parseLogLines = (value, invocation) => {
290
+ if (value === undefined) {
291
+ return 40;
292
+ }
293
+ if (value === true) {
294
+ throw new CliInputError('Flag --lines requires a value.', invocation);
295
+ }
296
+ const lines = Number.parseInt(String(value), 10);
297
+ if (!Number.isFinite(lines) || lines <= 0) {
298
+ throw new CliInputError('Flag --lines must be a positive integer.', invocation);
299
+ }
300
+ return lines;
301
+ };
302
+ const waitForComposioLinkSession = async (linkSessionId, waitSeconds) => {
303
+ const deadline = Date.now() + waitSeconds * 1000;
304
+ let session = await fetchComposioLinkSession(linkSessionId);
305
+ while (session.status === 'pending' && Date.now() < deadline) {
306
+ await sleep(2_000);
307
+ session = await fetchComposioLinkSession(linkSessionId);
308
+ }
309
+ return session;
310
+ };
311
+ const parseApprovalConflictStatus = (error) => {
312
+ if (error.status !== 409) {
313
+ return null;
314
+ }
315
+ const match = error.message.match(/^Approval is already ([a-z_]+)\.$/i);
316
+ return match?.[1] ?? null;
317
+ };
318
+ const persistSessionState = async (updates) => {
319
+ if (await persistLocalProfileSessionState(updates)) {
320
+ return;
321
+ }
322
+ saveProfile(updates);
323
+ };
324
+ const encodePosixToken = (token) => {
325
+ if (token.length === 0) {
326
+ return "''";
327
+ }
328
+ if (/^[A-Za-z0-9_./:-]+$/.test(token)) {
329
+ return token;
330
+ }
331
+ return `'${token.replace(/'/g, `'"'"'`)}'`;
332
+ };
333
+ const encodePosixCommand = (tokens) => tokens.map(encodePosixToken).join(' ');
334
+ const renderCapabilityPassthroughUsage = (capability) => {
335
+ process.stdout.write(buildCapabilityPassthroughUsage(capability, VERSION));
336
+ };
337
+ const runCapabilityPassthrough = async (capability) => {
338
+ const mode = bootstrapModeFromArgvForCommand(capability.slug);
339
+ if (mode === 'json' && !passthroughAllowsJson(capability)) {
340
+ throw new CliInputError(`JSON output is not supported for harbor ${capability.slug}.`, `harbor ${capability.slug}`, 'This passthrough capability streams foreground output. Use default output or `--plain`.');
341
+ }
342
+ const argv = rawUserArgv();
343
+ const capabilityIndex = commandArgIndex(capability.slug, argv);
344
+ const capabilityArgv = capabilityIndex >= 0 ? argv.slice(capabilityIndex + 1) : [];
345
+ if (capabilityArgv.length === 0 || (capabilityArgv.length === 1 && (capabilityArgv[0] === '--help' || capabilityArgv[0] === '-h'))) {
346
+ renderCapabilityPassthroughUsage(capability);
347
+ return;
348
+ }
349
+ const result = await startExecution(capability.slug, buildCapabilityPassthroughInput(capability, encodePosixCommand(capabilityArgv)));
350
+ if (result.status !== 'accepted' || !result.execution_id) {
351
+ renderExecutionStart(mode, result);
352
+ if (result.status !== 'accepted') {
353
+ process.exitCode = 1;
354
+ }
355
+ return;
356
+ }
357
+ const status = await streamExecutionForeground(mode, result.execution_id);
358
+ if (status.state === 'failed' || status.state === 'cancelled') {
359
+ if (status.error_message) {
360
+ process.stderr.write(`${status.error_message}\n`);
361
+ }
362
+ process.exitCode = typeof status.exit_code === 'number' && status.exit_code !== 0 ? status.exit_code : 1;
363
+ }
364
+ };
365
+ const buildCli = (capabilities) => {
366
+ const cli = goke('harbor');
367
+ cli.option('--plain', 'Stable key=value output');
368
+ cli.option('--json', 'JSON output');
369
+ cli.option('--profile [profile]', 'Use a named Harbor profile for this command');
370
+ cli.version(VERSION);
371
+ cli.help();
372
+ cli
373
+ .example('harbor auth --help')
374
+ .example('harbor ls --plain');
375
+ cli
376
+ .command('mcp', 'Expose Harbor capabilities as an MCP server (stdio only)')
377
+ .example('harbor mcp')
378
+ .action(withCommandErrorHandling(async () => {
379
+ await runHarborMcpCli();
380
+ }));
381
+ cli
382
+ .command('dev', 'Manage the local Harbor daemon.\n\nSubcommands:\n up Start or adopt the local Harbor runtime\n down Stop the local Harbor runtime\n status Show local daemon state\n logs Read daemon-managed local logs')
383
+ .example('harbor dev up --plain')
384
+ .example('harbor dev status --plain');
385
+ cli
386
+ .command('dev up', 'Start or adopt the local Harbor runtime')
387
+ .example('harbor dev up --plain')
388
+ .action(withCommandErrorHandling(async (options) => {
389
+ ensureLocalDevCommandProfile();
390
+ const mode = getOutputMode(options);
391
+ const status = await fetchLocalDaemonStatus(true);
392
+ if (!status) {
393
+ throw new Error('Local Harbor daemon did not return status.');
394
+ }
395
+ renderDevUp(mode, {
396
+ status: status.ready_state === 'ready' ? 'ok' : status.ready_state,
397
+ profile: status.profile_slug,
398
+ api_url: status.api_url,
399
+ auth_url: status.auth_url,
400
+ });
401
+ }));
402
+ cli
403
+ .command('dev down', 'Stop the local Harbor runtime')
404
+ .example('harbor dev down --plain')
405
+ .action(withCommandErrorHandling(async (options) => {
406
+ ensureLocalDevCommandProfile();
407
+ const mode = getOutputMode(options);
408
+ const result = await stopLocalRuntime();
409
+ renderDevDown(mode, {
410
+ status: typeof result.status === 'string' ? result.status : 'stopped',
411
+ profile: getProfile().profile_slug,
412
+ });
413
+ }));
414
+ cli
415
+ .command('dev status', 'Show local daemon state')
416
+ .example('harbor dev status --plain')
417
+ .action(withCommandErrorHandling(async (options) => {
418
+ ensureLocalDevCommandProfile();
419
+ const mode = getOutputMode(options);
420
+ const status = await fetchLocalDaemonStatus(false);
421
+ if (!status) {
422
+ renderDevDown(mode, { status: 'stopped', profile: getProfile().profile_slug });
423
+ return;
424
+ }
425
+ renderLocalDaemonStatus(mode, {
426
+ profile: status.profile_slug,
427
+ ready_state: status.ready_state,
428
+ api_url: status.api_url,
429
+ auth_url: status.auth_url,
430
+ daemon_package_version: status.daemon_package_version,
431
+ selected_daemon_protocol: status.selected_daemon_protocol,
432
+ state_schema_version: status.state_schema_version,
433
+ api_manifest_revision: status.api_manifest_revision,
434
+ last_ready_at: status.last_ready_at,
435
+ managed_children: status.managed_children,
436
+ paths: status.paths,
437
+ });
438
+ }));
439
+ cli
440
+ .command('dev logs [component]', 'Read local daemon logs')
441
+ .option('--lines [lines]', 'Number of trailing lines to print')
442
+ .example('harbor dev logs daemon --plain')
443
+ .example('harbor dev logs runner --lines 80')
444
+ .action(withCommandErrorHandling(async (component, options) => {
445
+ ensureLocalDevCommandProfile();
446
+ const mode = getOutputMode(options);
447
+ const selected = component ?? 'daemon';
448
+ if (selected !== 'daemon' && selected !== 'api' && selected !== 'runner') {
449
+ throw new CliInputError('Component must be one of: daemon, api, runner.', 'harbor dev logs [daemon|api|runner] [--lines <lines>]');
450
+ }
451
+ const log = readLocalDaemonLog(selected, parseLogLines(options.lines, 'harbor dev logs [daemon|api|runner] [--lines <lines>]'));
452
+ renderLocalDaemonLog(mode, {
453
+ component: selected,
454
+ path: log.path,
455
+ lines: log.lines,
456
+ });
457
+ }));
458
+ cli
459
+ .command('profile', 'Manage Harbor CLI profiles.\n\nSubcommands:\n current Show the selected Harbor profile\n ls List saved Harbor profiles\n use Select a Harbor profile for future commands\n cleanup Clear saved owner/agent session state for a profile\n delete Delete a saved Harbor profile and its cached state')
460
+ .example('harbor profile current --plain')
461
+ .example('harbor profile ls --plain')
462
+ .example('harbor profile use staging');
463
+ cli
464
+ .command('profile current', 'Show the selected Harbor profile')
465
+ .example('harbor profile current --plain')
466
+ .action(withCommandErrorHandling(async (options) => {
467
+ const mode = getOutputMode(options);
468
+ const profile = getProfile();
469
+ renderProfileCurrent(mode, {
470
+ profile: profile.profile_slug,
471
+ active_profile: getActiveProfileSlug(),
472
+ api_url: profile.api_url,
473
+ owner: profile.owner,
474
+ agent_id: profile.agent_id,
475
+ });
476
+ }));
477
+ cli
478
+ .command('profile ls', 'List saved Harbor profiles')
479
+ .example('harbor profile ls --plain')
480
+ .action(withCommandErrorHandling(async (options) => {
481
+ const mode = getOutputMode(options);
482
+ renderProfileList(mode, listProfiles());
483
+ }));
484
+ cli
485
+ .command('profile use [profile]', 'Select a Harbor profile for future commands')
486
+ .example('harbor profile use local-alice')
487
+ .action(withCommandErrorHandling(async (profileArg, options) => {
488
+ const mode = getOutputMode(options);
489
+ const requestedProfile = resolveOptionValue(profileArg, undefined, '<profile>', 'Missing required profile slug.', 'harbor profile use <profile>', 'Use letters, numbers, dots, underscores, or hyphens.');
490
+ const nextProfileSlug = normalizeProfileSlug(requestedProfile);
491
+ if (!nextProfileSlug) {
492
+ throw new CliInputError('Invalid profile slug. Use letters, numbers, dots, underscores, or hyphens.', 'harbor profile use <profile>');
493
+ }
494
+ if (!profileExists(nextProfileSlug)) {
495
+ const currentProfile = getProfile();
496
+ saveProfile({
497
+ ...currentProfile,
498
+ profile_slug: nextProfileSlug,
499
+ agent_id: null,
500
+ agent_token: null,
501
+ token_expires_at: null,
502
+ }, nextProfileSlug);
503
+ const currentCatalog = getCatalog();
504
+ if (currentCatalog) {
505
+ saveCatalog(currentCatalog.capabilities, currentCatalog.etag, nextProfileSlug);
506
+ }
507
+ }
508
+ setActiveProfile(nextProfileSlug);
509
+ const profile = getProfile(nextProfileSlug);
510
+ renderProfileCurrent(mode, {
511
+ profile: profile.profile_slug,
512
+ active_profile: getActiveProfileSlug(),
513
+ api_url: profile.api_url,
514
+ owner: profile.owner,
515
+ agent_id: profile.agent_id,
516
+ });
517
+ }));
518
+ cli
519
+ .command('profile cleanup [profile]', 'Clear saved owner and agent session state for a profile without deleting its API config')
520
+ .example('harbor profile cleanup local --plain')
521
+ .action(withCommandErrorHandling(async (profileArg, options) => {
522
+ const mode = getOutputMode(options);
523
+ const targetProfile = profileArg ? resolveOptionValue(profileArg, undefined, '<profile>', 'Missing required profile slug.', 'harbor profile cleanup <profile>') : getProfile().profile_slug;
524
+ clearProfileSession(targetProfile);
525
+ renderProfileCleanup(mode, {
526
+ profile: normalizeProfileSlug(targetProfile) ?? getProfile().profile_slug,
527
+ deleted: false,
528
+ cleared_session: true,
529
+ active_profile: getActiveProfileSlug(),
530
+ });
531
+ }));
532
+ cli
533
+ .command('profile delete [profile]', 'Delete a saved Harbor profile and its cached state')
534
+ .example('harbor profile delete preview --plain')
535
+ .action(withCommandErrorHandling(async (profileArg, options) => {
536
+ const mode = getOutputMode(options);
537
+ const targetProfile = resolveOptionValue(profileArg, undefined, '<profile>', 'Missing required profile slug.', 'harbor profile delete <profile>');
538
+ const normalized = normalizeProfileSlug(targetProfile);
539
+ if (!normalized) {
540
+ throw new CliInputError('Invalid profile slug. Use letters, numbers, dots, underscores, or hyphens.', 'harbor profile delete <profile>');
541
+ }
542
+ deleteProfile(normalized);
543
+ renderProfileCleanup(mode, {
544
+ profile: normalized,
545
+ deleted: true,
546
+ cleared_session: true,
547
+ active_profile: getActiveProfileSlug(),
548
+ });
549
+ }));
550
+ cli
551
+ .command('setup [apiUrl]', 'Install a Harbor profile and claim or restore this agent identity')
552
+ .option('--display-name [displayName]', 'Override the agent display name for this claim request')
553
+ .example('harbor setup https://api.tryharbor.ai --profile production --display-name "Harbor CLI" --plain')
554
+ .action(withCommandErrorHandling(async (apiUrlArg, options) => {
555
+ const mode = getOutputMode(options);
556
+ const apiUrl = await resolveSetupValue(apiUrlArg, process.env.HARBOR_API_URL, 'Harbor API URL', 'https://api.tryharbor.ai');
557
+ const requestedProfile = await resolveSetupValue(options.profile, process.env.HARBOR_PROFILE, 'Harbor profile', 'production', '--profile');
558
+ const profileSlug = normalizeProfileSlug(requestedProfile);
559
+ if (!profileSlug) {
560
+ throw new CliInputError('Invalid profile slug. Use letters, numbers, dots, underscores, or hyphens.', SETUP_INVOCATION);
561
+ }
562
+ const displayName = await resolveSetupValue(options.displayName, process.env.HARBOR_AGENT_NAME, 'Agent display name', 'Harbor CLI', '--display-name');
563
+ const manifest = await fetchSetupManifest(apiUrl);
564
+ writeSetupProfile(manifest, profileSlug);
565
+ try {
566
+ await ensureLocalOwnerSession();
567
+ }
568
+ catch (error) {
569
+ if (!(error instanceof ApiError) || error.status !== 404) {
570
+ throw error;
571
+ }
572
+ }
573
+ const claim = await claimAgent(displayName);
574
+ let owner = claim.owner ?? null;
575
+ let agentId = claim.agent_id ?? null;
576
+ let agentName = claim.agent_display_name ?? displayName;
577
+ let health = null;
578
+ let capabilityCount = null;
579
+ if (claim.agent_token) {
580
+ const whoami = await fetchWhoAmI();
581
+ await persistSessionState({
582
+ owner_scope_id: whoami.owner_scope_id,
583
+ owner: whoami.owner,
584
+ owner_display_name: whoami.owner_display_name,
585
+ agent_id: whoami.agent_id,
586
+ });
587
+ health = await fetchHealth();
588
+ owner = whoami.owner ?? owner;
589
+ agentId = whoami.agent_id ?? agentId;
590
+ agentName = whoami.agent_display_name ?? agentName;
591
+ capabilityCount = health.published_capabilities ?? (getCatalog()?.capabilities.length ?? capabilities.length);
592
+ }
593
+ renderSetup(mode, {
594
+ profile: profileSlug,
595
+ api_url: manifest.api_url,
596
+ status: claim.result,
597
+ owner,
598
+ agent_id: agentId,
599
+ agent_name: agentName,
600
+ approval_url: claim.approval_url ?? null,
601
+ health: health?.status ?? null,
602
+ capabilities: capabilityCount,
603
+ pending_approvals: health?.pending_approvals ?? null,
604
+ recent_executions: health?.recent_executions ?? null,
605
+ });
606
+ }));
607
+ cli
608
+ .command('auth', 'Manage Harbor identity.\n\nSubcommands:\n claim Claim or restore the local Harbor agent identity\n whoami Show the current Harbor identity\n logout Clear saved owner/agent tokens for the selected profile')
609
+ .example('harbor auth claim --plain')
610
+ .example('harbor auth whoami --plain');
611
+ cli
612
+ .command('auth claim', 'Claim or restore the local Harbor agent identity')
613
+ .option('--display-name [displayName]', 'Override the agent display name for this claim request')
614
+ .example('harbor auth claim --plain')
615
+ .example('harbor auth claim --display-name "py agent" --plain')
616
+ .example('harbor auth claim --json')
617
+ .action(withCommandErrorHandling(async (options) => {
618
+ const mode = getOutputMode(options);
619
+ try {
620
+ await ensureLocalOwnerSession();
621
+ }
622
+ catch (error) {
623
+ if (!(error instanceof ApiError) || error.status !== 404) {
624
+ throw error;
625
+ }
626
+ }
627
+ if (options.displayName === true) {
628
+ throw new CliInputError('Flag --display-name requires a value.', 'harbor auth claim [--display-name <display-name>]');
629
+ }
630
+ const claim = await claimAgent(typeof options.displayName === 'string' && options.displayName.length > 0 ? options.displayName : 'Harbor CLI');
631
+ let whoami = null;
632
+ if (claim.agent_token) {
633
+ try {
634
+ whoami = await fetchWhoAmI();
635
+ await persistSessionState({
636
+ owner_scope_id: whoami.owner_scope_id,
637
+ owner: whoami.owner,
638
+ owner_display_name: whoami.owner_display_name,
639
+ agent_id: whoami.agent_id,
640
+ });
641
+ }
642
+ catch {
643
+ whoami = null;
644
+ }
645
+ }
646
+ renderClaim(mode, withOwner(claim, whoami, getProfile()));
647
+ }));
648
+ cli
649
+ .command('auth whoami', 'Show the current Harbor identity')
650
+ .example('harbor auth whoami --plain')
651
+ .example('harbor auth whoami --json')
652
+ .action(withCommandErrorHandling(async (options) => {
653
+ const mode = getOutputMode(options);
654
+ const profile = getProfile();
655
+ const whoami = await fetchWhoAmI();
656
+ await persistSessionState({
657
+ owner_scope_id: whoami.owner_scope_id,
658
+ owner: whoami.owner,
659
+ owner_display_name: whoami.owner_display_name,
660
+ agent_id: whoami.agent_id,
661
+ });
662
+ renderWhoAmI(mode, { ...whoami, profile: whoami.profile_slug ?? profile.profile_slug });
663
+ }));
664
+ cli
665
+ .command('auth logout', 'Clear saved owner and agent tokens for the selected profile')
666
+ .example('harbor auth logout --plain')
667
+ .action(withCommandErrorHandling(async (options) => {
668
+ const mode = getOutputMode(options);
669
+ const profile = getProfile();
670
+ clearProfileSession(profile.profile_slug);
671
+ renderProfileCleanup(mode, {
672
+ profile: profile.profile_slug,
673
+ deleted: false,
674
+ cleared_session: true,
675
+ active_profile: getActiveProfileSlug(),
676
+ });
677
+ }));
678
+ cli
679
+ .command('status', 'Show Harbor health and summary')
680
+ .example('harbor status --plain')
681
+ .example('harbor status --json')
682
+ .action(withCommandErrorHandling(async (options) => {
683
+ const mode = getOutputMode(options);
684
+ const profile = getProfile();
685
+ const [health, whoami] = await Promise.all([
686
+ fetchHealth(),
687
+ fetchWhoAmI(),
688
+ ]);
689
+ renderStatus(mode, {
690
+ profile: profile.profile_slug,
691
+ whoami,
692
+ health,
693
+ capabilityCount: health.published_capabilities ?? (getCatalog()?.capabilities.length ?? capabilities.length),
694
+ });
695
+ }));
696
+ cli
697
+ .command('ls', 'List published capabilities')
698
+ .example('harbor ls --plain')
699
+ .example('harbor ls --json')
700
+ .action(withCommandErrorHandling(async (options) => {
701
+ const mode = getOutputMode(options);
702
+ const liveCatalog = await fetchCatalog().catch(() => getCatalog()?.capabilities ?? capabilities);
703
+ renderLs(mode, liveCatalog);
704
+ }));
705
+ cli
706
+ .command('inspect [capability]', 'Inspect one capability')
707
+ .option('--capability [capability]', 'Capability slug')
708
+ .example('harbor inspect compute.modal')
709
+ .example('harbor inspect --capability compute.modal --plain')
710
+ .action(withCommandErrorHandling(async (capability, options) => {
711
+ const mode = getOutputMode(options);
712
+ const requestedCapabilitySlug = resolveOptionValue(capability, options.capability, '--capability', 'Missing required capability slug.', 'harbor inspect --capability <capability>', 'Find available slugs with `harbor ls --plain`.');
713
+ const capabilitySlug = requestedCapabilitySlug;
714
+ const snapshot = await fetchCapability(capabilitySlug).catch(() => {
715
+ const cached = findCapability(getCatalog()?.capabilities ?? capabilities, capabilitySlug);
716
+ if (cached?.snapshot) {
717
+ return cached.snapshot;
718
+ }
719
+ throw new ApiError(404, 'capability_not_found', `Capability ${capabilitySlug} was not found`);
720
+ });
721
+ renderInspect(mode, snapshot);
722
+ }));
723
+ cli
724
+ .command('approve [approvalId]', 'Approve a pending request (owner action)')
725
+ .option('--approval-id [approvalId]', 'Approval ID')
726
+ .option('--yes', 'Skip confirmation prompts')
727
+ .example('harbor approve apr_01HQ --yes --plain')
728
+ .action(withCommandErrorHandling(async (approvalId, options) => {
729
+ const mode = getOutputMode(options);
730
+ const approvalIdValue = resolveOptionValue(approvalId, options.approvalId, '--approval-id', 'Missing required approval id.', 'harbor approve --approval-id <approval-id>', 'Find the approval_id in `harbor auth claim --plain` or command output.');
731
+ try {
732
+ const response = await approveRequest(approvalIdValue);
733
+ renderApprove(mode, {
734
+ approval_id: approvalIdValue,
735
+ status: typeof response.status === 'string' ? response.status : 'approved',
736
+ });
737
+ }
738
+ catch (error) {
739
+ if (error instanceof ApiError) {
740
+ const status = parseApprovalConflictStatus(error);
741
+ if (status) {
742
+ renderApprove(mode, { approval_id: approvalIdValue, status });
743
+ return;
744
+ }
745
+ }
746
+ throw error;
747
+ }
748
+ }));
749
+ cli
750
+ .command('secrets', 'Manage owner secret references')
751
+ .example('harbor secrets ls --plain')
752
+ .example('echo $OPENAI_API_KEY | harbor secrets set OPENAI_API_KEY --plain');
753
+ cli
754
+ .command('secrets ls', 'List owner secret references')
755
+ .example('harbor secrets ls --plain')
756
+ .example('harbor secrets ls --json')
757
+ .action(withCommandErrorHandling(async (options) => {
758
+ const mode = getOutputMode(options);
759
+ const secretReferences = await listSecretReferences();
760
+ renderSecretReferenceList(mode, secretReferences);
761
+ }));
762
+ cli
763
+ .command('secrets inspect [name]', 'Inspect one owner secret reference')
764
+ .option('--name [name]', 'Secret reference name')
765
+ .example('harbor secrets inspect OPENAI_API_KEY')
766
+ .example('harbor secrets inspect --name OPENAI_API_KEY --plain')
767
+ .action(withCommandErrorHandling(async (name, options) => {
768
+ const mode = getOutputMode(options);
769
+ const secretName = resolveOptionValue(name, options.name, '--name', 'Missing required secret reference name.', 'harbor secrets inspect --name <name>');
770
+ const secretReference = await getSecretReference(secretName);
771
+ renderSecretReference(mode, secretReference);
772
+ }));
773
+ cli
774
+ .command('secrets set [name]', 'Create or update one owner secret reference')
775
+ .option('--name [name]', 'Secret reference name')
776
+ .option('--value [value]', 'Secret value (warning: appears in shell history)')
777
+ .option('--display-name [displayName]', 'Override the secret display name')
778
+ .option('--kind [kind]', 'Secret kind')
779
+ .example('echo sk-live | harbor secrets set OPENAI_API_KEY --plain')
780
+ .example('harbor secrets set --name OPENAI_API_KEY --value sk-live --display-name "OpenAI API key"')
781
+ .action(withCommandErrorHandling(async (name, options) => {
782
+ const mode = getOutputMode(options);
783
+ const invocation = 'harbor secrets set --name <name> [--value <value>] [--display-name <display-name>] [--kind <kind>]';
784
+ const secretName = resolveOptionValue(name, options.name, '--name', 'Missing required secret reference name.', invocation);
785
+ const value = await readSecretValue(options, invocation);
786
+ if (options.displayName === true) {
787
+ throw new CliInputError('Flag --display-name requires a value.', invocation);
788
+ }
789
+ if (options.kind === true) {
790
+ throw new CliInputError('Flag --kind requires a value.', invocation);
791
+ }
792
+ const secretReference = await setSecretReference(secretName, value, {
793
+ display_name: typeof options.displayName === 'string' && options.displayName.length > 0 ? options.displayName : undefined,
794
+ kind: typeof options.kind === 'string' && options.kind.length > 0 ? options.kind : undefined,
795
+ });
796
+ renderSecretReference(mode, secretReference);
797
+ }));
798
+ cli
799
+ .command('secrets delete [name]', 'Delete one owner secret reference')
800
+ .option('--name [name]', 'Secret reference name')
801
+ .example('harbor secrets delete OPENAI_API_KEY --plain')
802
+ .action(withCommandErrorHandling(async (name, options) => {
803
+ const mode = getOutputMode(options);
804
+ const secretName = resolveOptionValue(name, options.name, '--name', 'Missing required secret reference name.', 'harbor secrets delete --name <name>');
805
+ const result = await deleteSecretReference(secretName);
806
+ renderSecretReferenceDeleted(mode, { name: secretName, ok: result.ok });
807
+ }));
808
+ cli
809
+ .command('services', 'Manage owner control-plane service setup flows')
810
+ .example('harbor services inspect composio --plain');
811
+ cli
812
+ .command('services connect composio', 'Start a Harbor-owned Composio link flow for an owner-managed route target')
813
+ .option('--toolkit [toolkit]', 'Toolkit slug')
814
+ .option('--auth-config [authConfig]', 'Composio auth config id')
815
+ .option('--shared', 'Use the owner-shared route target')
816
+ .option('--agent [agent]', 'Use an agent-specific route target under the current owner')
817
+ .option('--open', 'Open the returned redirect URL in your browser')
818
+ .option('--wait', 'Poll the link session until it is no longer pending')
819
+ .option('--wait-seconds [waitSeconds]', 'Maximum wait time in seconds when --wait is used')
820
+ .example('harbor services connect composio --toolkit github --auth-config ac_123 --shared --open --wait')
821
+ .example('harbor services connect composio --toolkit slack --auth-config ac_456 --agent agt_123')
822
+ .action(withCommandErrorHandling(async (options) => {
823
+ const mode = getOutputMode(options);
824
+ const invocation = 'harbor services connect composio --toolkit <toolkit> --auth-config <auth-config> --shared [--open --wait]';
825
+ const toolkitSlug = resolveOptionValue(undefined, options.toolkit, '--toolkit', 'Missing required toolkit slug.', invocation);
826
+ const authConfigId = resolveOptionValue(undefined, options.authConfig, '--auth-config', 'Missing required auth config id.', invocation);
827
+ const ownerSession = await ensureLocalOwnerSession();
828
+ const agentId = typeof options.agent === 'string' && options.agent.length > 0 ? options.agent : null;
829
+ const shared = options.shared === true;
830
+ if ((shared && agentId) || (!shared && !agentId)) {
831
+ throw new CliInputError('Choose exactly one route target: --shared or --agent <agent-id>.', invocation);
832
+ }
833
+ const created = await createComposioLinkSession({
834
+ toolkit_slug: toolkitSlug,
835
+ auth_config_id: authConfigId,
836
+ subject_kind: shared ? 'owner' : 'agent',
837
+ subject_id: shared ? ownerSession?.owner_scope_id ?? '' : agentId,
838
+ });
839
+ if (options.open === true && created.redirect_url) {
840
+ openBrowser(created.redirect_url);
841
+ }
842
+ const linkSession = options.wait === true
843
+ ? await waitForComposioLinkSession(created.link_session_id, parseWaitSeconds(options.waitSeconds, invocation))
844
+ : created;
845
+ renderComposioLinkSession(mode, linkSession);
846
+ }));
847
+ cli
848
+ .command('services inspect composio', 'Inspect the Harbor-owned Composio principal and owner-managed routes')
849
+ .example('harbor services inspect composio --json')
850
+ .action(withCommandErrorHandling(async (options) => {
851
+ const mode = getOutputMode(options);
852
+ const service = await fetchComposioService();
853
+ renderComposioService(mode, service);
854
+ }));
855
+ cli
856
+ .command('services route composio', 'Bind an owner-managed Harbor Composio route target to an existing connected account')
857
+ .option('--toolkit [toolkit]', 'Toolkit slug')
858
+ .option('--connected-account [connectedAccount]', 'Composio connected account id')
859
+ .option('--shared', 'Set the owner-shared route target')
860
+ .option('--agent [agent]', 'Set an agent-specific route target under the current owner')
861
+ .example('harbor services route composio --toolkit github --connected-account ca_123 --shared')
862
+ .example('harbor services route composio --toolkit github --connected-account ca_456 --agent agt_123')
863
+ .action(withCommandErrorHandling(async (options) => {
864
+ const mode = getOutputMode(options);
865
+ const toolkitSlug = resolveOptionValue(undefined, options.toolkit, '--toolkit', 'Missing required toolkit slug.', 'harbor services route composio --toolkit <toolkit> --connected-account <connected-account> --shared');
866
+ const connectedAccountId = resolveOptionValue(undefined, options.connectedAccount, '--connected-account', 'Missing required connected account id.', 'harbor services route composio --toolkit <toolkit> --connected-account <connected-account> --shared');
867
+ const ownerSession = await ensureLocalOwnerSession();
868
+ const agentId = typeof options.agent === 'string' && options.agent.length > 0 ? options.agent : null;
869
+ const shared = options.shared === true;
870
+ if ((shared && agentId) || (!shared && !agentId)) {
871
+ throw new CliInputError('Choose exactly one route target: --shared or --agent <agent-id>.', 'harbor services route composio --toolkit <toolkit> --connected-account <connected-account> --shared');
872
+ }
873
+ const route = await setComposioServiceRoute({
874
+ toolkit_slug: toolkitSlug,
875
+ connected_account_id: connectedAccountId,
876
+ subject_kind: shared ? 'owner' : 'agent',
877
+ subject_id: shared ? ownerSession?.owner_scope_id ?? '' : agentId,
878
+ });
879
+ renderComposioRoute(mode, route);
880
+ }));
881
+ cli
882
+ .command('services unmount composio', 'Remove an owner-managed Harbor Composio route binding')
883
+ .option('--route-id [routeId]', 'Delete one route by route id')
884
+ .option('--toolkit [toolkit]', 'Toolkit slug')
885
+ .option('--auth-config [authConfig]', 'Narrow deletion to one auth config id')
886
+ .option('--shared', 'Delete the owner-shared route target')
887
+ .option('--agent [agent]', 'Delete an agent-specific route target under the current owner')
888
+ .example('harbor services unmount composio --route-id cmprt_123 --plain')
889
+ .example('harbor services unmount composio --toolkit github --agent agt_123 --auth-config ac_456')
890
+ .action(withCommandErrorHandling(async (options) => {
891
+ const mode = getOutputMode(options);
892
+ const routeId = typeof options.routeId === 'string' && options.routeId.length > 0 ? options.routeId : null;
893
+ const ownerSession = await ensureLocalOwnerSession();
894
+ const agentId = typeof options.agent === 'string' && options.agent.length > 0 ? options.agent : null;
895
+ const shared = options.shared === true;
896
+ if (!routeId) {
897
+ const toolkitSlug = resolveOptionValue(undefined, options.toolkit, '--toolkit', 'Missing required toolkit slug.', 'harbor services unmount composio --toolkit <toolkit> --shared');
898
+ if ((shared && agentId) || (!shared && !agentId)) {
899
+ throw new CliInputError('Choose exactly one route target: --shared or --agent <agent-id>.', 'harbor services unmount composio --toolkit <toolkit> --shared');
900
+ }
901
+ const authConfigId = typeof options.authConfig === 'string' && options.authConfig.length > 0 ? options.authConfig : undefined;
902
+ const route = await deleteComposioServiceRoute({
903
+ toolkit_slug: toolkitSlug,
904
+ auth_config_id: authConfigId,
905
+ subject_kind: shared ? 'owner' : 'agent',
906
+ subject_id: shared ? ownerSession?.owner_scope_id ?? '' : agentId,
907
+ });
908
+ renderComposioRoute(mode, route);
909
+ return;
910
+ }
911
+ const route = await deleteComposioServiceRoute({ route_id: routeId });
912
+ renderComposioRoute(mode, route);
913
+ }));
914
+ registerCapabilityCommands(cli, capabilities);
915
+ return cli;
916
+ };
917
+ const main = async () => {
918
+ const mode = bootstrapModeFromArgv();
919
+ const firstArg = firstCommandArg();
920
+ if (classifyCommandForLocalRuntime(rawUserArgv()) === 'local_runtime') {
921
+ await ensureLocalRuntimeForCommand(rawUserArgv());
922
+ }
923
+ const capabilities = await bootstrapCapabilitiesForCommand(firstArg);
924
+ if (firstArg && isCapabilitySlugCandidate(firstArg) && !findCapability(capabilities, firstArg)) {
925
+ renderError(mode, new ApiError(404, 'capability_not_found', `Capability ${firstArg} was not found`));
926
+ process.exit(1);
927
+ return;
928
+ }
929
+ const passthroughCapability = firstArg ? findCapability(capabilities, firstArg) : null;
930
+ if (passthroughCapability && supportsArgvPassthrough(passthroughCapability)) {
931
+ await runCapabilityPassthrough(passthroughCapability);
932
+ return;
933
+ }
934
+ const cli = buildCli(capabilities);
935
+ cli.parse(process.argv);
936
+ };
937
+ main().catch((error) => {
938
+ renderError(bootstrapModeFromArgv(), error);
939
+ process.exit(1);
940
+ });
941
+ //# sourceMappingURL=index.js.map