| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { isAbsolute } from 'node:path'; |
| import { |
| isProductReleaseVersion, |
| isSha512PackageIntegrity, |
| } from '@maka/runtime-host/operator/update-package-evidence'; |
| import { |
| createRuntimeHostLegacyPosixOperatorCommand, |
| decodeRuntimeHostPosixOperatorCommand, |
| decodeRuntimeHostWebRtcStunPolicy, |
| type RuntimeHostManagedUpdatePolicy, |
| type RuntimeHostPosixOperatorCommand, |
| type RuntimeHostWebRtcStunPolicy, |
| } from '@maka/runtime-host/operator'; |
| import { |
| canonicalProjectDirectoryRootSpec, |
| isCanonicalRuntimeHostWebSocketPath, |
| PROJECT_DIRECTORY_MAX_ROOTS, |
| projectDirectoryPosixRootSpecValid, |
| projectDirectoryRootSpecValid, |
| } from '@maka/runtime-host/protocol'; |
| import type { RuntimeHostManagedServiceTarget } from './runtime-host-service-manager.js'; |
| import { hasEphemeralRuntimeHostPeerPort } from './runtime-host-peer-artifact.js'; |
| import type { RuntimeHostInstalledUpdateExpectedSource } from './runtime-host-installed-update-coordinator.js'; |
| import { |
| installedUpdateHelpText, |
| isRuntimeHostHelpTopic, |
| runtimeHostCommandHelpText, |
| runtimeHostHelpText, |
| } from './runtime-host-help.js'; |
| |
| type RuntimeHostCliError = { kind: 'error'; message: string; exitCode: number }; |
| type RuntimeHostCliHelp = { kind: 'help'; text: string }; |
| |
| export type RuntimeHostUpdateSelector = |
| | { readonly kind: 'channel'; readonly channel: 'latest' | 'next' } |
| | { readonly kind: 'exact'; readonly version: string }; |
| |
| export interface RuntimeHostExpectedHost { |
| /** Freshness fence for admitting a canonical supervised-deployment mutation. */ |
| readonly hostEpoch: string; |
| readonly pid: number; |
| } |
| |
| export type RuntimeHostCliCommand = |
| | { |
| kind: 'runtime-host-managed-activate'; |
| rootId: string; |
| framed: true; |
| repairRootAfterRemount?: true; |
| } |
| | { |
| kind: 'runtime-host-managed-connect'; |
| rootId: string; |
| framed: true; |
| repairRootAfterRemount?: true; |
| } |
| | { |
| kind: 'runtime-host-installed-update'; |
| selector: RuntimeHostUpdateSelector; |
| allowInterruptActiveTasks: boolean; |
| } |
| | { |
| kind: 'runtime-host-local-update-apply'; |
| rootPath: string; |
| archivePath: string; |
| installedPackageRoot: string; |
| installedCliPath: string; |
| currentVersion: string; |
| targetVersion: string; |
| targetIntegrity: string; |
| targetCompatibility?: number; |
| allowInterruptActiveTasks: boolean; |
| expectedSource?: RuntimeHostInstalledUpdateExpectedSource; |
| } |
| | { |
| kind: 'runtime-host-local-update-activate'; |
| rootPath: string; |
| expectedRootId: string; |
| generation: string; |
| candidateEntrypoint: string; |
| takeoverHostEpoch?: string; |
| awaitCoordinatorCommit: boolean; |
| expectedOwnerInstallationId?: string; |
| targetVersion?: string; |
| targetIntegrity?: string; |
| } |
| | { |
| kind: 'runtime-host-local-source-retire'; |
| rootPath: string; |
| expectedRootId: string; |
| expectedHostEpoch: string; |
| allowInterruptActiveTasks: boolean; |
| } |
| | { |
| kind: 'runtime-host-serve'; |
| rootPath?: string; |
| managedServiceConfigPath?: string; |
| managedDeployment?: { |
| rootId: string; |
| deploymentId: string; |
| configRevision: number; |
| }; |
| json: boolean; |
| projectDirectoryRoots?: { label: string; path: string }[]; |
| websocket?: { |
| host: string; |
| port: number; |
| path?: string; |
| tlsCertificatePath?: string; |
| tlsPrivateKeyPath?: string; |
| allowedOrigins?: string[]; |
| allowInsecureRemote?: boolean; |
| }; |
| peer?: { |
| nativePath: string; |
| keyPath: string; |
| expectedPeerId?: string; |
| listenAddresses?: string[]; |
| coordinationRelays?: string[]; |
| webRtcStunUrls?: string[]; |
| }; |
| } |
| | { |
| kind: 'runtime-host-setup'; |
| json: boolean; |
| principalId: string; |
| preset: 'desktop-client' | 'terminal-client'; |
| lifecycle: 'supervised' | 'on_demand'; |
| deferPairingCommit: boolean; |
| bindPairingToClient?: true; |
| repairRootAfterRemount?: true; |
| updateExisting?: true; |
| reuseExistingEnvironment?: true; |
| allowInterruptActiveTasks?: true; |
| clientDataRoot?: string; |
| rootPath?: string; |
| projectDirectoryRoots?: { label: string; path: string }[]; |
| websocketPort?: number; |
| websocketPath?: string; |
| directPeer?: { |
| coordinationRelays: string[]; |
| }; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| } |
| | { |
| kind: 'runtime-host-service-manage'; |
| action: |
| | 'install' |
| | 'configure' |
| | 'status' |
| | 'start' |
| | 'stop' |
| | 'restart' |
| | 'retire' |
| | 'logs' |
| | 'uninstall'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| rootPath?: string; |
| projectDirectoryRoots?: { label: string; path: string }[]; |
| websocketPort?: number; |
| websocketPath?: string; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| expectedConfigFingerprint?: string; |
| expectedHost?: RuntimeHostExpectedHost; |
| retainManagedDeployment?: true; |
| allowInterruptActiveTasks?: true; |
| } |
| | { |
| kind: 'runtime-host-service-peer'; |
| action: 'enable' | 'disable' | 'status' | 'rotate' | 'descriptor'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId: string; |
| operatorDeploymentId: string; |
| listenAddresses: string[]; |
| coordinationRelays?: string[]; |
| automaticRelayDiscovery?: boolean; |
| relayDiscoveryStatus?: true; |
| webRtcStunPolicy?: RuntimeHostWebRtcStunPolicy; |
| webRtcStunStatus?: true; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| allowInterruptActiveTasks?: true; |
| } |
| | { |
| kind: 'runtime-host-service-peer-mesh'; |
| action: |
| | 'status' |
| | 'create' |
| | 'invite' |
| | 'join' |
| | 'remove' |
| | 'leave' |
| | 'close' |
| | 'reconcile' |
| | 'transit' |
| | 'rename' |
| | 'rename-mesh'; |
| json: boolean; |
| framed?: true; |
| managedRootId: string; |
| operatorDeploymentId: string; |
| expectedTarget: RuntimeHostManagedServiceTarget; |
| meshId?: string | null; |
| peerId?: string; |
| displayName?: string | null; |
| } |
| | { |
| kind: 'runtime-host-service-check-update'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| selector: RuntimeHostUpdateSelector; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| } |
| | { |
| kind: 'runtime-host-service-update'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| expectedTarget: RuntimeHostManagedServiceTarget; |
| expectedHost?: RuntimeHostExpectedHost; |
| expectedConfigFingerprint?: string; |
| expectedSourceVersion?: string; |
| selector?: RuntimeHostUpdateSelector; |
| allowManualUpdate?: true; |
| allowInterruptActiveTasks?: true; |
| } |
| | { |
| kind: 'runtime-host-service-update-policy'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| policy?: RuntimeHostManagedUpdatePolicy; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| } |
| | { |
| kind: 'runtime-host-service-reconcile-update'; |
| json: boolean; |
| framed?: true; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| expectedTarget?: RuntimeHostManagedServiceTarget; |
| } |
| | { |
| kind: 'runtime-host-managed-deployment-cleanup'; |
| clientDataRoot?: string; |
| managedRootId?: string; |
| operatorDeploymentId?: string; |
| finalize?: true; |
| expectedTarget: RuntimeHostManagedServiceTarget; |
| } |
| | { |
| kind: 'runtime-host-access-issue'; |
| rootPath?: string; |
| expectedRootId?: string; |
| principalKind: 'remote_owner' | 'capability_provider'; |
| principalId: string; |
| operationGrants: string[]; |
| canPublishClientCapabilities: boolean; |
| canUseHostPaths: boolean; |
| capabilityOwnerCredentialId?: string; |
| preset?: 'desktop-client' | 'terminal-client'; |
| } |
| | { |
| kind: 'runtime-host-access-prepare'; |
| rootPath?: string; |
| expectedRootId?: string; |
| currentCredentialFingerprint: string; |
| } |
| | { |
| kind: 'runtime-host-access-list'; |
| rootPath?: string; |
| expectedRootId?: string; |
| framed: boolean; |
| } |
| | { |
| kind: 'runtime-host-access-connection-code'; |
| rootPath?: string; |
| expectedRootId?: string; |
| name?: string; |
| framed: boolean; |
| } |
| | { |
| kind: 'runtime-host-access-revoke'; |
| rootPath?: string; |
| expectedRootId?: string; |
| credentialId: string; |
| currentCredentialFingerprint?: string; |
| framed: boolean; |
| } |
| | { kind: 'runtime-host-project-list'; rootPath?: string } |
| | { |
| kind: 'runtime-host-project-add'; |
| rootPath?: string; |
| path: string; |
| prefer: boolean; |
| } |
| | { |
| kind: 'runtime-host-plugin'; |
| rootPath?: string; |
| action: |
| | 'status' |
| | 'list' |
| | 'inspect' |
| | 'failures' |
| | 'install' |
| | 'uninstall' |
| | 'reload' |
| | 'export' |
| | 'apply' |
| | 'reconcile'; |
| subject?: string; |
| targetPath?: string; |
| rootId?: string; |
| cursor?: string; |
| limit?: number; |
| } |
| | { |
| kind: 'runtime-host-capability-provider-serve'; |
| url: string; |
| mcpConfigPath: string; |
| expectedRootId: string; |
| credentialEnv?: string; |
| clientIdentityPath?: string; |
| } |
| | { kind: 'runtime-host-profile-list' } |
| | { |
| kind: 'runtime-host-profile-set'; |
| id: string; |
| name: string; |
| transport: |
| | { kind: 'tls'; url: string } |
| | { |
| kind: 'plaintext'; |
| url: string; |
| acknowledgement: 'plaintext-bearer-v1'; |
| } |
| | { |
| kind: 'ssh'; |
| destination: string; |
| sshPort?: number; |
| remotePort: number; |
| websocketPath: string; |
| }; |
| expectedRootId: string; |
| credentialEnv?: string; |
| } |
| | { |
| kind: 'runtime-host-profile-set-environment'; |
| id: string; |
| name: string; |
| distribution: string; |
| operator: RuntimeHostPosixOperatorCommand; |
| expectedRootId: string; |
| } |
| | { kind: 'runtime-host-profile-remove'; id: string } |
| | RuntimeHostCliHelp |
| | RuntimeHostCliError; |
| |
| export function parseRuntimeHostCommand( |
| argv: string[], |
| cliCommand = 'maka', |
| ): RuntimeHostCliCommand { |
| if (!argv[0] || argv[0] === '--help' || argv[0] === '-h') { |
| return { kind: 'help', text: runtimeHostHelpText(cliCommand) }; |
| } |
| if ((argv[1] === '--help' || argv[1] === '-h') && isRuntimeHostHelpTopic(argv[0])) { |
| return { kind: 'help', text: runtimeHostCommandHelpText(cliCommand, argv[0]) }; |
| } |
| if (argv[0] === 'activate' || argv[0] === 'connect') { |
| return parseManagedRootFramedCommand(argv[0], argv.slice(1)); |
| } |
| if (argv[0] === 'local-update-apply') return parseLocalUpdateApply(argv.slice(1)); |
| if (argv[0] === 'local-update-activate') return parseLocalUpdateActivate(argv.slice(1)); |
| if (argv[0] === 'local-source-retire') return parseLocalSourceRetire(argv.slice(1)); |
| if (argv[0] === 'serve') return parseServeCommand(argv.slice(1)); |
| if (argv[0] === 'setup') return parseSetupCommand(argv.slice(1)); |
| if (argv[0] === 'service') return parseServiceManagementCommand(argv.slice(1)); |
| if (argv[0] === 'access') return parseAccessCommand(argv.slice(1)); |
| if (argv[0] === 'project') return parseProjectCommand(argv.slice(1)); |
| if (argv[0] === 'plugin') return parsePluginCommand(argv.slice(1)); |
| if (argv[0] === 'capability-provider') { |
| return parseCapabilityProviderCommand(argv.slice(1)); |
| } |
| if (argv[0] === 'profile') return parseProfileCommand(argv.slice(1)); |
| return error(`Unexpected runtime-host command: ${argv[0]}`); |
| } |
| |
| function parseManagedRootFramedCommand( |
| action: 'activate' | 'connect', |
| argv: string[], |
| ): RuntimeHostCliCommand { |
| let rootId: string | undefined; |
| let framed = false; |
| let repairRootAfterRemount = false; |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--framed') { |
| if (framed) return error('Duplicate --framed'); |
| framed = true; |
| continue; |
| } |
| if (argument === '--root-id') { |
| if (rootId !== undefined) return error('Duplicate --root-id'); |
| rootId = argv[index + 1]; |
| index += 1; |
| if (rootId === undefined) return error('--root-id requires a value'); |
| continue; |
| } |
| if (argument === '--repair-root-after-remount') { |
| if (repairRootAfterRemount) return error('Duplicate --repair-root-after-remount'); |
| repairRootAfterRemount = true; |
| continue; |
| } |
| return error(`Unexpected runtime-host ${action} option: ${String(argument)}`); |
| } |
| if (!framed) return error(`runtime-host ${action} requires --framed`); |
| if (!rootId || !/^[a-f0-9]{64}$/u.test(rootId)) { |
| return error(`runtime-host ${action} requires a valid --root-id`); |
| } |
| return { |
| kind: action === 'activate' ? 'runtime-host-managed-activate' : 'runtime-host-managed-connect', |
| rootId, |
| framed: true, |
| ...(repairRootAfterRemount ? { repairRootAfterRemount: true } : {}), |
| }; |
| } |
| |
| export function parseRuntimeHostInstalledUpdateCommand( |
| argv: string[], |
| cliCommand = 'maka', |
| ): RuntimeHostCliCommand { |
| if (argv[0] === '--help' || argv[0] === '-h') { |
| return { kind: 'help', text: installedUpdateHelpText(cliCommand) }; |
| } |
| let target: string | undefined; |
| let allowInterruptActiveTasks = false; |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--target') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (target !== undefined) return error('Duplicate --target'); |
| target = parsed; |
| index += 1; |
| continue; |
| } |
| if (argument === '--allow-interrupt-active-tasks') { |
| if (allowInterruptActiveTasks) { |
| return error('Duplicate --allow-interrupt-active-tasks'); |
| } |
| allowInterruptActiveTasks = true; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if (!target) return error('update requires --target <latest|next|version>'); |
| const selector = parseUpdateSelector(target, 'update'); |
| if ('kind' in selector && selector.kind === 'error') return selector; |
| return { kind: 'runtime-host-installed-update', selector, allowInterruptActiveTasks }; |
| } |
| |
| function parseLocalUpdateApply(argv: string[]): RuntimeHostCliCommand { |
| const values = new Map<string, string>(); |
| let allowInterruptActiveTasks = false; |
| const valueOptions = new Set([ |
| '--root', |
| '--archive', |
| '--installed-package-root', |
| '--installed-cli-path', |
| '--current-version', |
| '--target-version', |
| '--target-integrity', |
| '--target-compatibility', |
| '--expected-root-id', |
| '--expected-deployment-revision', |
| '--expected-owner-installation-id', |
| '--expected-host-epoch', |
| ]); |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--allow-interrupt-active-tasks') { |
| if (allowInterruptActiveTasks) return error(`Duplicate ${argument}`); |
| allowInterruptActiveTasks = true; |
| continue; |
| } |
| if (!argument || !valueOptions.has(argument)) |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| if (values.has(argument)) return error(`Duplicate ${argument}`); |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| values.set(argument, parsed); |
| index += 1; |
| } |
| const required = (name: string): string | RuntimeHostCliError => { |
| const value = values.get(name); |
| return value ? value : error(`runtime-host local-update-apply requires ${name}`); |
| }; |
| const rootPath = required('--root'); |
| if (typeof rootPath !== 'string') return rootPath; |
| const archivePath = required('--archive'); |
| if (typeof archivePath !== 'string') return archivePath; |
| const installedPackageRoot = required('--installed-package-root'); |
| if (typeof installedPackageRoot !== 'string') return installedPackageRoot; |
| const installedCliPath = required('--installed-cli-path'); |
| if (typeof installedCliPath !== 'string') return installedCliPath; |
| if (![rootPath, archivePath, installedPackageRoot, installedCliPath].every(isSafeAbsolutePath)) { |
| return error('runtime-host local-update-apply paths must be absolute'); |
| } |
| const currentVersion = required('--current-version'); |
| if (typeof currentVersion !== 'string') return currentVersion; |
| const targetVersion = required('--target-version'); |
| if (typeof targetVersion !== 'string') return targetVersion; |
| const targetIntegrity = required('--target-integrity'); |
| if (typeof targetIntegrity !== 'string') return targetIntegrity; |
| if (!isProductReleaseVersion(currentVersion) || !isProductReleaseVersion(targetVersion)) { |
| return error('runtime-host local-update-apply versions are invalid'); |
| } |
| if (!isSha512PackageIntegrity(targetIntegrity)) { |
| return error('runtime-host local-update-apply integrity is invalid'); |
| } |
| const rawCompatibility = values.get('--target-compatibility'); |
| const targetCompatibility = rawCompatibility === undefined ? undefined : Number(rawCompatibility); |
| if ( |
| targetCompatibility !== undefined && |
| (!Number.isSafeInteger(targetCompatibility) || targetCompatibility <= 0) |
| ) { |
| return error('runtime-host local-update-apply compatibility is invalid'); |
| } |
| const expectedValues = [ |
| '--expected-root-id', |
| '--expected-deployment-revision', |
| '--expected-owner-installation-id', |
| '--expected-host-epoch', |
| ].map((name) => values.get(name)); |
| let expectedSource: RuntimeHostInstalledUpdateExpectedSource | undefined; |
| if (expectedValues.some((value) => value !== undefined)) { |
| if (!expectedValues.every((value) => value !== undefined && isSafeIdentity(value))) { |
| return error( |
| 'runtime-host local-update-apply requires one complete expected source identity', |
| ); |
| } |
| const [rootId, deploymentRevision, ownerInstallationId, hostEpoch] = expectedValues as string[]; |
| if (!/^[a-f0-9]{64}$/u.test(rootId!)) { |
| return error('runtime-host local-update-apply expected Root identity is invalid'); |
| } |
| expectedSource = { |
| rootId: rootId!, |
| deploymentRevision: deploymentRevision!, |
| ownerInstallationId: ownerInstallationId!, |
| hostEpoch: hostEpoch!, |
| }; |
| } |
| return { |
| kind: 'runtime-host-local-update-apply', |
| rootPath, |
| archivePath, |
| installedPackageRoot, |
| installedCliPath, |
| currentVersion, |
| targetVersion, |
| targetIntegrity, |
| ...(targetCompatibility === undefined ? {} : { targetCompatibility }), |
| allowInterruptActiveTasks, |
| ...(expectedSource ? { expectedSource } : {}), |
| }; |
| } |
| |
| function parseLocalUpdateActivate(argv: string[]): RuntimeHostCliCommand { |
| const values = new Map<string, string>(); |
| const options = new Set([ |
| '--root', |
| '--expected-root-id', |
| '--generation', |
| '--candidate-entrypoint', |
| '--takeover-host-epoch', |
| '--await-coordinator-commit', |
| '--expected-owner-installation-id', |
| '--target-version', |
| '--target-integrity', |
| ]); |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (!argument || !options.has(argument)) return error(`Unexpected argument: ${argument ?? ''}`); |
| if (values.has(argument)) return error(`Duplicate ${argument}`); |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| values.set(argument, parsed); |
| index += 1; |
| } |
| const rootPath = values.get('--root'); |
| const expectedRootId = values.get('--expected-root-id'); |
| const generation = values.get('--generation'); |
| const candidateEntrypoint = values.get('--candidate-entrypoint'); |
| const takeoverHostEpoch = values.get('--takeover-host-epoch'); |
| const awaitCoordinatorCommit = values.get('--await-coordinator-commit') === 'true'; |
| const expectedOwnerInstallationId = values.get('--expected-owner-installation-id'); |
| const targetVersion = values.get('--target-version'); |
| const targetIntegrity = values.get('--target-integrity'); |
| if (!rootPath || !expectedRootId || !generation || !candidateEntrypoint) { |
| return error('runtime-host local-update-activate requires its exact target identity'); |
| } |
| if (!isSafeAbsolutePath(rootPath) || !isSafeAbsolutePath(candidateEntrypoint)) { |
| return error('runtime-host local-update-activate paths must be absolute'); |
| } |
| if (!/^[a-f0-9]{64}$/u.test(expectedRootId)) { |
| return error('runtime-host local-update-activate root identity is invalid'); |
| } |
| if ( |
| [ |
| generation, |
| takeoverHostEpoch, |
| expectedOwnerInstallationId, |
| targetVersion, |
| targetIntegrity, |
| ].some((value) => value !== undefined && !isSafeIdentity(value)) |
| ) { |
| return error('runtime-host local-update-activate generation is invalid'); |
| } |
| if ( |
| (values.has('--await-coordinator-commit') && !awaitCoordinatorCommit) || |
| (awaitCoordinatorCommit && |
| (!expectedOwnerInstallationId || !targetVersion || !targetIntegrity)) || |
| (!awaitCoordinatorCommit && |
| (expectedOwnerInstallationId !== undefined || |
| targetVersion !== undefined || |
| targetIntegrity !== undefined)) |
| ) { |
| return error('runtime-host local-update-activate coordinator expectation is invalid'); |
| } |
| return { |
| kind: 'runtime-host-local-update-activate', |
| rootPath, |
| expectedRootId, |
| generation, |
| candidateEntrypoint, |
| awaitCoordinatorCommit, |
| ...(takeoverHostEpoch ? { takeoverHostEpoch } : {}), |
| ...(expectedOwnerInstallationId ? { expectedOwnerInstallationId } : {}), |
| ...(targetVersion ? { targetVersion } : {}), |
| ...(targetIntegrity ? { targetIntegrity } : {}), |
| }; |
| } |
| |
| function parseLocalSourceRetire(argv: string[]): RuntimeHostCliCommand { |
| const values = new Map<string, string>(); |
| let allowInterruptActiveTasks = false; |
| const options = new Set(['--root', '--expected-root-id', '--expected-host-epoch']); |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--allow-interrupt-active-tasks') { |
| if (allowInterruptActiveTasks) return error(`Duplicate ${argument}`); |
| allowInterruptActiveTasks = true; |
| continue; |
| } |
| if (!argument || !options.has(argument)) return error(`Unexpected argument: ${argument ?? ''}`); |
| if (values.has(argument)) return error(`Duplicate ${argument}`); |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| values.set(argument, parsed); |
| index += 1; |
| } |
| const rootPath = values.get('--root'); |
| const expectedRootId = values.get('--expected-root-id'); |
| const expectedHostEpoch = values.get('--expected-host-epoch'); |
| if (!rootPath || !expectedRootId || !expectedHostEpoch) { |
| return error('runtime-host local-source-retire requires its exact source Host identity'); |
| } |
| if (!isSafeAbsolutePath(rootPath)) { |
| return error('runtime-host local-source-retire root path must be absolute'); |
| } |
| if (!/^[a-f0-9]{64}$/u.test(expectedRootId) || !isSafeIdentity(expectedHostEpoch)) { |
| return error('runtime-host local-source-retire Host identity is invalid'); |
| } |
| return { |
| kind: 'runtime-host-local-source-retire', |
| rootPath, |
| expectedRootId, |
| expectedHostEpoch, |
| allowInterruptActiveTasks, |
| }; |
| } |
| |
| function isSafeIdentity(value: string): boolean { |
| return value.length <= 512 && !/[\u0000-\u001f\u007f]/u.test(value); |
| } |
| |
| function parseSetupCommand(argv: string[]): RuntimeHostCliCommand { |
| let principalId: string | undefined; |
| let preset: 'desktop-client' | 'terminal-client' | undefined; |
| let lifecycle: 'supervised' | 'on_demand' = 'supervised'; |
| let lifecycleProvided = false; |
| let deferPairingCommit = false; |
| let bindPairingToClient = false; |
| let repairRootAfterRemount = false; |
| let updateExisting = false; |
| let reuseExistingEnvironment = false; |
| let allowInterruptActiveTasks = false; |
| let clientDataRoot: string | undefined; |
| let enableDirectPeer = false; |
| const coordinationRelays: string[] = []; |
| const options = parseManagedServiceOptions(argv, { |
| valueOptions: { |
| '--client-data-root': (value) => { |
| if (clientDataRoot !== undefined) return error('Duplicate --client-data-root'); |
| if (!isSafeAbsolutePath(value)) return error('--client-data-root must be an absolute path'); |
| clientDataRoot = value; |
| }, |
| '--coordination-relay': (value) => { |
| coordinationRelays.push(value); |
| }, |
| '--principal': (value) => { |
| principalId = value; |
| }, |
| '--preset': (value) => { |
| if (value !== 'desktop-client' && value !== 'terminal-client') { |
| return error('--preset must be desktop-client or terminal-client'); |
| } |
| preset = value; |
| }, |
| '--lifecycle': (value) => { |
| if (lifecycleProvided) return error('Duplicate --lifecycle'); |
| if (value !== 'supervised' && value !== 'on-demand') { |
| return error('--lifecycle must be supervised or on-demand'); |
| } |
| lifecycleProvided = true; |
| lifecycle = value === 'on-demand' ? 'on_demand' : 'supervised'; |
| }, |
| }, |
| flagOptions: { |
| '--enable-direct-peer': () => { |
| if (enableDirectPeer) return error('Duplicate --enable-direct-peer'); |
| enableDirectPeer = true; |
| }, |
| '--defer-pairing-commit': () => { |
| if (deferPairingCommit) return error('Duplicate --defer-pairing-commit'); |
| deferPairingCommit = true; |
| }, |
| '--bind-pairing-to-client': () => { |
| if (bindPairingToClient) return error('Duplicate --bind-pairing-to-client'); |
| bindPairingToClient = true; |
| }, |
| '--repair-root-after-remount': () => { |
| if (repairRootAfterRemount) return error('Duplicate --repair-root-after-remount'); |
| repairRootAfterRemount = true; |
| }, |
| '--allow-interrupt-active-tasks': () => { |
| if (allowInterruptActiveTasks) return error('Duplicate --allow-interrupt-active-tasks'); |
| allowInterruptActiveTasks = true; |
| }, |
| '--reuse-existing-environment': () => { |
| if (reuseExistingEnvironment) return error('Duplicate --reuse-existing-environment'); |
| reuseExistingEnvironment = true; |
| }, |
| '--update-existing': () => { |
| if (updateExisting) return error('Duplicate --update-existing'); |
| updateExisting = true; |
| }, |
| }, |
| }); |
| if ('kind' in options) return options; |
| if ( |
| reuseExistingEnvironment && |
| ((lifecycle as string) !== 'on_demand' || |
| updateExisting || |
| enableDirectPeer || |
| deferPairingCommit || |
| bindPairingToClient) |
| ) { |
| return error( |
| '--reuse-existing-environment requires on-demand local setup without update or remote pairing options', |
| ); |
| } |
| if (allowInterruptActiveTasks && !updateExisting) { |
| return error('--allow-interrupt-active-tasks requires --update-existing'); |
| } |
| if (!principalId || !/^[A-Za-z0-9_.:-]{1,128}$/u.test(principalId)) { |
| return error('runtime-host setup requires a valid --principal'); |
| } |
| if (!preset) return error('runtime-host setup requires --preset'); |
| if (!enableDirectPeer && coordinationRelays.length > 0) { |
| return error('--coordination-relay requires --enable-direct-peer'); |
| } |
| if (bindPairingToClient && !deferPairingCommit) { |
| return error('--bind-pairing-to-client requires --defer-pairing-commit'); |
| } |
| return { |
| kind: 'runtime-host-setup', |
| ...options, |
| principalId, |
| preset, |
| lifecycle, |
| deferPairingCommit, |
| ...(bindPairingToClient ? { bindPairingToClient: true } : {}), |
| ...(repairRootAfterRemount ? { repairRootAfterRemount: true } : {}), |
| ...(updateExisting ? { updateExisting: true } : {}), |
| ...(reuseExistingEnvironment ? { reuseExistingEnvironment: true } : {}), |
| ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(enableDirectPeer ? { directPeer: { coordinationRelays } } : {}), |
| }; |
| } |
| |
| function parseServiceManagementCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if (action === 'peer') return parseServicePeerCommand(argv.slice(1)); |
| if (action === 'mesh') return parseServicePeerMeshCommand(argv.slice(1)); |
| if (action === 'cleanup-deployment') { |
| let clientDataRoot: string | undefined; |
| let finalize = false; |
| const options = parseManagedServiceOptions(argv.slice(1), { |
| allowConfiguration: false, |
| flagOptions: { |
| '--finalize': () => { |
| if (finalize) return error('Duplicate --finalize'); |
| finalize = true; |
| }, |
| }, |
| valueOptions: { |
| '--client-data-root': (value) => { |
| if (clientDataRoot !== undefined) return error('Duplicate --client-data-root'); |
| if (!isSafeAbsolutePath(value)) |
| return error('--client-data-root must be an absolute path'); |
| clientDataRoot = value; |
| }, |
| }, |
| }); |
| if ('kind' in options) return options; |
| if (options.json) return error('Unexpected argument: --json'); |
| if (!options.expectedTarget) { |
| return error('runtime-host service cleanup-deployment requires an expected target'); |
| } |
| return { |
| kind: 'runtime-host-managed-deployment-cleanup', |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(options.operatorDeploymentId |
| ? { operatorDeploymentId: options.operatorDeploymentId } |
| : {}), |
| ...(finalize ? { finalize: true } : {}), |
| expectedTarget: options.expectedTarget, |
| }; |
| } |
| if ( |
| action !== 'install' && |
| action !== 'configure' && |
| action !== 'status' && |
| action !== 'start' && |
| action !== 'stop' && |
| action !== 'restart' && |
| action !== 'retire' && |
| action !== 'check-update' && |
| action !== 'update' && |
| action !== 'update-policy' && |
| action !== 'reconcile-update' && |
| action !== 'logs' && |
| action !== 'uninstall' |
| ) { |
| return error( |
| action |
| ? `Unexpected runtime-host service command: ${action}` |
| : 'runtime-host service requires install, configure, status, start, stop, restart, retire, peer, mesh, check-update, update, update-policy, reconcile-update, logs, or uninstall', |
| ); |
| } |
| |
| let retainManagedDeployment = false; |
| let allowInterruptActiveTasks = false; |
| let allowManualUpdate = false; |
| let clientDataRoot: string | undefined; |
| let updateTarget: string | undefined; |
| let expectedHost: RuntimeHostExpectedHost | undefined; |
| let expectedConfigFingerprint: string | undefined; |
| let expectedSourceVersion: string | undefined; |
| const flagOptions: Readonly<Record<string, () => void | RuntimeHostCliError>> = |
| action === 'uninstall' |
| ? { |
| '--retain-managed-deployment': () => { |
| if (retainManagedDeployment) return error('Duplicate --retain-managed-deployment'); |
| retainManagedDeployment = true; |
| }, |
| '--allow-interrupt-active-tasks': () => { |
| if (allowInterruptActiveTasks) { |
| return error('Duplicate --allow-interrupt-active-tasks'); |
| } |
| allowInterruptActiveTasks = true; |
| }, |
| } |
| : action === 'update' |
| ? { |
| '--allow-interrupt-active-tasks': () => { |
| if (allowInterruptActiveTasks) { |
| return error('Duplicate --allow-interrupt-active-tasks'); |
| } |
| allowInterruptActiveTasks = true; |
| }, |
| '--allow-manual-update': () => { |
| if (allowManualUpdate) return error('Duplicate --allow-manual-update'); |
| allowManualUpdate = true; |
| }, |
| } |
| : action === 'restart' || action === 'retire' || action === 'configure' |
| ? { |
| '--allow-interrupt-active-tasks': () => { |
| if (allowInterruptActiveTasks) { |
| return error('Duplicate --allow-interrupt-active-tasks'); |
| } |
| allowInterruptActiveTasks = true; |
| }, |
| } |
| : {}; |
| const options = parseManagedServiceOptions(argv.slice(1), { |
| allowConfiguration: action === 'install' || action === 'configure', |
| allowFramed: true, |
| valueOptions: { |
| '--client-data-root': (value) => { |
| if (clientDataRoot !== undefined) return error('Duplicate --client-data-root'); |
| if (!isSafeAbsolutePath(value)) return error('--client-data-root must be an absolute path'); |
| clientDataRoot = value; |
| }, |
| ...(action === 'check-update' || action === 'update' || action === 'update-policy' |
| ? { |
| '--target': (value: string) => { |
| if (updateTarget !== undefined) return error('Duplicate --target'); |
| updateTarget = value; |
| }, |
| } |
| : {}), |
| ...(action === 'update' |
| ? { |
| '--expected-source-version': (value: string) => { |
| if (expectedSourceVersion !== undefined) |
| return error('Duplicate --expected-source-version'); |
| if (!isProductReleaseVersion(value)) |
| return error('--expected-source-version must be an exact package version'); |
| expectedSourceVersion = value; |
| }, |
| } |
| : {}), |
| ...(action === 'update' || action === 'restart' |
| ? { |
| '--expected-host-json': (value: string) => { |
| if (expectedHost !== undefined) return error('Duplicate --expected-host-json'); |
| const parsed = parseExpectedHost(value); |
| if ('kind' in parsed) return parsed; |
| expectedHost = parsed; |
| }, |
| } |
| : {}), |
| ...(action === 'configure' || action === 'update' |
| ? { |
| '--expected-config-fingerprint': (value: string) => { |
| if (expectedConfigFingerprint !== undefined) { |
| return error('Duplicate --expected-config-fingerprint'); |
| } |
| if (!/^sha256:[a-f0-9]{64}$/u.test(value)) { |
| return error( |
| '--expected-config-fingerprint must be a service configuration fingerprint', |
| ); |
| } |
| expectedConfigFingerprint = value; |
| }, |
| } |
| : {}), |
| }, |
| flagOptions, |
| }); |
| if ('kind' in options) return options; |
| if ( |
| (action === 'retire' || |
| action === 'update' || |
| action === 'configure' || |
| action === 'uninstall') && |
| !options.expectedTarget |
| ) { |
| return error(`runtime-host service ${action} requires an expected target`); |
| } |
| if (action === 'configure' && !expectedConfigFingerprint) { |
| return error('runtime-host service configure requires --expected-config-fingerprint'); |
| } |
| if (action === 'configure' && options.projectDirectoryRoots === undefined) { |
| return error('runtime-host service configure requires --project-root or --no-project-roots'); |
| } |
| if (action === 'update-policy') { |
| const policy = updateTarget === undefined ? undefined : parseUpdatePolicy(updateTarget); |
| if (policy && 'exitCode' in policy) return policy; |
| if (policy && policy.kind !== 'manual' && !options.expectedTarget) { |
| return error('runtime-host service update-policy requires an expected target'); |
| } |
| return { |
| kind: 'runtime-host-service-update-policy', |
| json: options.json, |
| ...(options.framed ? { framed: true } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(options.operatorDeploymentId |
| ? { operatorDeploymentId: options.operatorDeploymentId } |
| : {}), |
| ...(policy ? { policy } : {}), |
| ...(options.expectedTarget ? { expectedTarget: options.expectedTarget } : {}), |
| }; |
| } |
| if (action === 'reconcile-update') { |
| return { |
| kind: 'runtime-host-service-reconcile-update', |
| json: options.json, |
| ...(options.framed ? { framed: true } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(options.operatorDeploymentId |
| ? { operatorDeploymentId: options.operatorDeploymentId } |
| : {}), |
| ...(options.expectedTarget ? { expectedTarget: options.expectedTarget } : {}), |
| }; |
| } |
| if (action === 'check-update') { |
| const selector = parseUpdateSelector(updateTarget); |
| if ('kind' in selector && selector.kind === 'error') return selector; |
| return { |
| kind: 'runtime-host-service-check-update', |
| json: options.json, |
| ...(options.framed ? { framed: true } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(options.operatorDeploymentId |
| ? { operatorDeploymentId: options.operatorDeploymentId } |
| : {}), |
| selector, |
| ...(options.expectedTarget ? { expectedTarget: options.expectedTarget } : {}), |
| }; |
| } |
| if (action === 'update') { |
| if ( |
| (expectedHost || expectedSourceVersion || expectedConfigFingerprint) && |
| !options.managedRootId |
| ) { |
| return error('Observed Host and deployment fences require --managed-root-id'); |
| } |
| const selector = |
| updateTarget === undefined ? undefined : parseUpdateSelector(updateTarget, 'update'); |
| if (selector && 'kind' in selector && selector.kind === 'error') return selector; |
| if ( |
| allowManualUpdate && |
| (selector?.kind !== 'exact' || |
| !options.managedRootId || |
| !options.expectedTarget?.deploymentId) |
| ) { |
| return error( |
| '--allow-manual-update requires an exact --target, --managed-root-id, and --expected-deployment-id', |
| ); |
| } |
| return { |
| kind: 'runtime-host-service-update', |
| json: options.json, |
| ...(options.framed ? { framed: true } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(options.operatorDeploymentId |
| ? { operatorDeploymentId: options.operatorDeploymentId } |
| : {}), |
| expectedTarget: options.expectedTarget!, |
| ...(expectedHost ? { expectedHost } : {}), |
| ...(expectedConfigFingerprint ? { expectedConfigFingerprint } : {}), |
| ...(expectedSourceVersion ? { expectedSourceVersion } : {}), |
| ...(selector ? { selector } : {}), |
| ...(allowManualUpdate ? { allowManualUpdate: true } : {}), |
| ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}), |
| }; |
| } |
| if (expectedHost && !options.managedRootId) |
| return error('--expected-host-json requires --managed-root-id'); |
| return { |
| kind: 'runtime-host-service-manage', |
| action, |
| ...options, |
| ...(expectedHost ? { expectedHost } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| ...(options.managedRootId ? { managedRootId: options.managedRootId } : {}), |
| ...(retainManagedDeployment ? { retainManagedDeployment: true } : {}), |
| ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}), |
| ...(action === 'configure' |
| ? { |
| expectedConfigFingerprint: expectedConfigFingerprint!, |
| projectDirectoryRoots: options.projectDirectoryRoots!, |
| } |
| : {}), |
| }; |
| } |
| |
| function parseServicePeerCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if ( |
| action !== 'enable' && |
| action !== 'disable' && |
| action !== 'status' && |
| action !== 'rotate' && |
| action !== 'descriptor' |
| ) { |
| return error( |
| action |
| ? `Unexpected runtime-host service peer command: ${action}` |
| : 'runtime-host service peer requires enable, disable, status, rotate, or descriptor', |
| ); |
| } |
| let clientDataRoot: string | undefined; |
| const listenAddresses: string[] = []; |
| const coordinationRelays: string[] = []; |
| let clearCoordinationRelays = false; |
| let automaticRelayDiscovery: boolean | undefined; |
| let relayDiscoveryStatus = false; |
| let defaultPublicStun = false; |
| let noPublicStun = false; |
| let webRtcStunStatus = false; |
| const webRtcStunUrls: string[] = []; |
| let allowInterruptActiveTasks = false; |
| const options = parseManagedServiceOptions(argv.slice(1), { |
| allowConfiguration: false, |
| allowFramed: true, |
| flagOptions: { |
| '--clear-coordination-relays': () => { |
| if (clearCoordinationRelays) return error('Duplicate --clear-coordination-relays'); |
| clearCoordinationRelays = true; |
| }, |
| '--allow-interrupt-active-tasks': () => { |
| if (allowInterruptActiveTasks) return error('Duplicate --allow-interrupt-active-tasks'); |
| allowInterruptActiveTasks = true; |
| }, |
| '--automatic-relay-discovery': () => { |
| if (automaticRelayDiscovery !== undefined) { |
| return error('Relay discovery mode was specified more than once'); |
| } |
| automaticRelayDiscovery = true; |
| }, |
| '--no-automatic-relay-discovery': () => { |
| if (automaticRelayDiscovery !== undefined) { |
| return error('Relay discovery mode was specified more than once'); |
| } |
| automaticRelayDiscovery = false; |
| }, |
| '--relay-discovery-status': () => { |
| if (relayDiscoveryStatus) return error('Duplicate --relay-discovery-status'); |
| relayDiscoveryStatus = true; |
| }, |
| '--default-public-stun': () => { |
| if (defaultPublicStun) return error('Duplicate --default-public-stun'); |
| defaultPublicStun = true; |
| }, |
| '--no-public-stun': () => { |
| if (noPublicStun) return error('Duplicate --no-public-stun'); |
| noPublicStun = true; |
| }, |
| '--webrtc-stun-status': () => { |
| if (webRtcStunStatus) return error('Duplicate --webrtc-stun-status'); |
| webRtcStunStatus = true; |
| }, |
| }, |
| valueOptions: { |
| '--client-data-root': (value) => { |
| if (clientDataRoot !== undefined) return error('Duplicate --client-data-root'); |
| if (!isSafeAbsolutePath(value)) return error('--client-data-root must be an absolute path'); |
| clientDataRoot = value; |
| }, |
| '--listen': (value) => { |
| listenAddresses.push(value); |
| }, |
| '--coordination-relay': (value) => { |
| coordinationRelays.push(value); |
| }, |
| '--webrtc-stun': (value) => { |
| webRtcStunUrls.push(value); |
| }, |
| }, |
| }); |
| if ('kind' in options) return options; |
| if (options.framed && (action === 'rotate' || action === 'descriptor')) { |
| return error(`runtime-host service peer ${action} does not support --framed`); |
| } |
| if (relayDiscoveryStatus && !options.framed) { |
| return error('--relay-discovery-status requires --framed'); |
| } |
| if (webRtcStunStatus && !options.framed) { |
| return error('--webrtc-stun-status requires --framed'); |
| } |
| if (Number(defaultPublicStun) + Number(noPublicStun) + Number(webRtcStunUrls.length > 0) > 1) { |
| return error('Specify only one WebRTC STUN policy'); |
| } |
| let webRtcStunPolicy: RuntimeHostWebRtcStunPolicy | undefined; |
| try { |
| webRtcStunPolicy = defaultPublicStun |
| ? { kind: 'default' } |
| : noPublicStun |
| ? { kind: 'disabled' } |
| : webRtcStunUrls.length > 0 |
| ? decodeRuntimeHostWebRtcStunPolicy({ kind: 'custom', urls: webRtcStunUrls }) |
| : undefined; |
| } catch (failure) { |
| return error(failure instanceof Error ? failure.message : String(failure)); |
| } |
| if (listenAddresses.some(hasEphemeralRuntimeHostPeerPort)) { |
| return error('--listen requires a stable non-zero transport port'); |
| } |
| if (clearCoordinationRelays && coordinationRelays.length > 0) { |
| return error('--clear-coordination-relays cannot be combined with --coordination-relay'); |
| } |
| if ( |
| action !== 'enable' && |
| (listenAddresses.length > 0 || |
| coordinationRelays.length > 0 || |
| clearCoordinationRelays || |
| automaticRelayDiscovery !== undefined || |
| webRtcStunPolicy !== undefined) |
| ) { |
| return error('Peer listener options are only valid with peer enable'); |
| } |
| if (allowInterruptActiveTasks && action !== 'enable' && action !== 'disable') { |
| return error('--allow-interrupt-active-tasks is only valid with peer enable or peer disable'); |
| } |
| if (!options.managedRootId || !options.operatorDeploymentId) { |
| return error( |
| 'runtime-host service peer requires --managed-root-id and --operator-deployment-id', |
| ); |
| } |
| if ( |
| (action === 'enable' || |
| action === 'disable' || |
| action === 'rotate' || |
| action === 'descriptor') && |
| !options.expectedTarget |
| ) { |
| return error(`runtime-host service peer ${action} requires an expected target`); |
| } |
| return { |
| kind: 'runtime-host-service-peer', |
| action, |
| json: options.json, |
| ...(options.framed ? { framed: true as const } : {}), |
| ...(clientDataRoot ? { clientDataRoot } : {}), |
| managedRootId: options.managedRootId, |
| operatorDeploymentId: options.operatorDeploymentId, |
| listenAddresses, |
| ...(clearCoordinationRelays |
| ? { coordinationRelays: [] } |
| : coordinationRelays.length > 0 |
| ? { coordinationRelays } |
| : {}), |
| ...(automaticRelayDiscovery === undefined ? {} : { automaticRelayDiscovery }), |
| ...(relayDiscoveryStatus ? { relayDiscoveryStatus: true as const } : {}), |
| ...(webRtcStunPolicy ? { webRtcStunPolicy } : {}), |
| ...(webRtcStunStatus ? { webRtcStunStatus: true as const } : {}), |
| ...(options.expectedTarget ? { expectedTarget: options.expectedTarget } : {}), |
| ...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}), |
| }; |
| } |
| |
| function parseServicePeerMeshCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if ( |
| action !== 'status' && |
| action !== 'create' && |
| action !== 'invite' && |
| action !== 'join' && |
| action !== 'remove' && |
| action !== 'leave' && |
| action !== 'close' && |
| action !== 'reconcile' && |
| action !== 'transit' && |
| action !== 'rename' && |
| action !== 'rename-mesh' |
| ) { |
| return error( |
| action |
| ? `Unexpected runtime-host service mesh command: ${action}` |
| : 'runtime-host service mesh requires status, create, invite, join, remove, leave, close, reconcile, transit, rename, or rename-mesh', |
| ); |
| } |
| let meshId: string | null | undefined; |
| let peerId: string | undefined; |
| let displayName: string | null | undefined; |
| let clientDataRoot: string | undefined; |
| const options = parseManagedServiceOptions(argv.slice(1), { |
| allowConfiguration: false, |
| allowFramed: true, |
| valueOptions: { |
| '--client-data-root': (value) => { |
| if (clientDataRoot !== undefined) return error('Duplicate --client-data-root'); |
| if (!isSafeAbsolutePath(value)) return error('--client-data-root must be an absolute path'); |
| clientDataRoot = value; |
| }, |
| '--mesh': (value) => { |
| if (meshId !== undefined) return error('Duplicate --mesh'); |
| if (!value || value.length > 128) return error('--mesh requires a valid Mesh ID'); |
| meshId = value; |
| }, |
| '--peer': (value) => { |
| if (peerId !== undefined) return error('Duplicate --peer'); |
| if (!value || value.length > 256) return error('--peer requires a valid Peer ID'); |
| peerId = value; |
| }, |
| '--name': (value) => { |
| if (displayName !== undefined) return error('Duplicate --name'); |
| if (!value.trim() || value.trim().length > 80) { |
| return error('--name requires a display name of at most 80 characters'); |
| } |
| displayName = value.trim(); |
| }, |
| }, |
| flagOptions: { |
| '--off': () => { |
| if (meshId !== undefined) return error('mesh transit accepts either --mesh or --off'); |
| meshId = null; |
| }, |
| '--clear-name': () => { |
| if (displayName !== undefined) return error('mesh rename accepts --name or --clear-name'); |
| displayName = null; |
| }, |
| }, |
| }); |
| if ('kind' in options) return options; |
| if (!options.managedRootId || !options.operatorDeploymentId || !options.expectedTarget) { |
| return error( |
| 'runtime-host service mesh requires --managed-root-id, --operator-deployment-id, and an expected target', |
| ); |
| } |
| const needsMesh = |
| action === 'invite' || |
| action === 'remove' || |
| action === 'leave' || |
| action === 'close' || |
| action === 'rename-mesh'; |
| if (needsMesh && typeof meshId !== 'string') { |
| return error(`runtime-host service mesh ${action} requires --mesh`); |
| } |
| if (!needsMesh && action !== 'transit' && typeof meshId === 'string') { |
| return error('--mesh is only valid with mesh invite, remove, leave, close, or transit'); |
| } |
| if ((action === 'remove') !== (peerId !== undefined)) { |
| return error( |
| action === 'remove' |
| ? 'runtime-host service mesh remove requires --peer' |
| : '--peer is only valid with mesh remove', |
| ); |
| } |
| if (meshId === null && action !== 'transit') { |
| return error('--off is only valid with mesh transit'); |
| } |
| if (action === 'transit' && meshId === undefined) { |
| return error('runtime-host service mesh transit requires --mesh or --off'); |
| } |
| if ((action === 'rename' || action === 'rename-mesh') !== (displayName !== undefined)) { |
| return error( |
| action === 'rename' |
| ? 'runtime-host service mesh rename requires --name or --clear-name' |
| : '--name and --clear-name are only valid with mesh rename or rename-mesh', |
| ); |
| } |
| return { |
| kind: 'runtime-host-service-peer-mesh', |
| action, |
| json: options.json, |
| ...(options.framed ? { framed: true as const } : {}), |
| managedRootId: options.managedRootId, |
| operatorDeploymentId: options.operatorDeploymentId, |
| expectedTarget: options.expectedTarget, |
| ...(meshId !== undefined ? { meshId } : {}), |
| ...(peerId ? { peerId } : {}), |
| ...(displayName !== undefined ? { displayName } : {}), |
| }; |
| } |
| |
| function parseUpdatePolicy(value: string): RuntimeHostManagedUpdatePolicy | RuntimeHostCliError { |
| if (value === 'manual') return { kind: 'manual' }; |
| const selector = parseUpdateSelector(value, 'update-policy'); |
| if ('exitCode' in selector) return selector; |
| return selector.kind === 'exact' ? { kind: 'fixed', version: selector.version } : selector; |
| } |
| |
| function parseUpdateSelector( |
| value: string | undefined, |
| action: 'check-update' | 'update' | 'update-policy' = 'check-update', |
| ): RuntimeHostUpdateSelector | RuntimeHostCliError { |
| if (!value) return error(`runtime-host service ${action} requires --target`); |
| if (value === 'latest' || value === 'next') { |
| return { kind: 'channel', channel: value }; |
| } |
| if (!isProductReleaseVersion(value)) { |
| return error('--target must be latest, next, or an exact Maka version'); |
| } |
| return { kind: 'exact', version: value }; |
| } |
| |
| function parseExpectedHost(value: string): RuntimeHostExpectedHost | RuntimeHostCliError { |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(value) as unknown; |
| } catch { |
| return error('--expected-host-json must be valid JSON'); |
| } |
| if ( |
| typeof parsed !== 'object' || |
| parsed === null || |
| Array.isArray(parsed) || |
| Object.keys(parsed).length !== 2 || |
| typeof (parsed as { hostEpoch?: unknown }).hostEpoch !== 'string' || |
| (parsed as { hostEpoch: string }).hostEpoch.length === 0 || |
| Buffer.byteLength((parsed as { hostEpoch: string }).hostEpoch, 'utf8') > 128 || |
| /[\u0000-\u001f\u007f]/u.test((parsed as { hostEpoch: string }).hostEpoch) || |
| !Number.isSafeInteger((parsed as { pid?: unknown }).pid) || |
| (parsed as { pid: number }).pid <= 0 |
| ) { |
| return error('--expected-host-json must contain one valid hostEpoch and pid'); |
| } |
| return { |
| hostEpoch: (parsed as { hostEpoch: string }).hostEpoch, |
| pid: (parsed as { pid: number }).pid, |
| }; |
| } |
| |
| interface ManagedServiceOptions { |
| readonly json: boolean; |
| readonly framed?: true; |
| readonly managedRootId?: string; |
| readonly operatorDeploymentId?: string; |
| readonly rootPath?: string; |
| readonly projectDirectoryRoots?: { |
| readonly label: string; |
| readonly path: string; |
| }[]; |
| readonly websocketPort?: number; |
| readonly websocketPath?: string; |
| readonly expectedTarget?: RuntimeHostManagedServiceTarget; |
| } |
| |
| function parseManagedServiceOptions( |
| argv: string[], |
| input: { |
| readonly valueOptions?: Readonly<Record<string, (value: string) => RuntimeHostCliError | void>>; |
| readonly flagOptions?: Readonly<Record<string, () => RuntimeHostCliError | void>>; |
| readonly allowConfiguration?: boolean; |
| readonly allowFramed?: boolean; |
| } = {}, |
| ): ManagedServiceOptions | RuntimeHostCliError { |
| let json = false; |
| let framed = false; |
| let rootPath: string | undefined; |
| let websocketPort: number | undefined; |
| let websocketPath: string | undefined; |
| let expectedServiceId: string | undefined; |
| let expectedRootPath: string | undefined; |
| let expectedRootId: string | undefined; |
| let expectedDeploymentId: string | undefined; |
| let managedRootId: string | undefined; |
| let operatorDeploymentId: string | undefined; |
| let projectDirectoryPolicySpecified = false; |
| const projectDirectoryRoots: { label: string; path: string }[] = []; |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--json') { |
| if (json) return error('Duplicate --json'); |
| if (framed) return error('--json and --framed cannot be used together'); |
| json = true; |
| continue; |
| } |
| if (argument === '--framed') { |
| if (!input.allowFramed) return error(`Unexpected argument: ${argument}`); |
| if (framed) return error('Duplicate --framed'); |
| if (json) return error('--json and --framed cannot be used together'); |
| framed = true; |
| continue; |
| } |
| if (Object.hasOwn(input.flagOptions ?? {}, argument ?? '')) { |
| const optionError = input.flagOptions?.[argument ?? '']?.(); |
| if (optionError) return optionError; |
| continue; |
| } |
| if (argument === '--no-project-roots') { |
| if (input.allowConfiguration === false) return error(`Unexpected argument: ${argument}`); |
| if (projectDirectoryPolicySpecified) { |
| return error('--no-project-roots cannot be combined with --project-root'); |
| } |
| projectDirectoryPolicySpecified = true; |
| continue; |
| } |
| const isTargetOption = |
| argument === '--expected-service-id' || |
| argument === '--expected-root-path' || |
| argument === '--expected-root-id' || |
| argument === '--expected-deployment-id' || |
| argument === '--operator-deployment-id' || |
| argument === '--managed-root-id'; |
| const isExplicitlyAllowedOption = Object.hasOwn(input.valueOptions ?? {}, argument ?? ''); |
| if (input.allowConfiguration === false && !isTargetOption && !isExplicitlyAllowedOption) { |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if ( |
| argument === '--root' || |
| argument === '--websocket-port' || |
| argument === '--websocket-path' || |
| argument === '--project-root' || |
| argument === '--project-root-json' || |
| argument === '--expected-service-id' || |
| argument === '--expected-root-path' || |
| argument === '--expected-root-id' || |
| argument === '--expected-deployment-id' || |
| argument === '--operator-deployment-id' || |
| argument === '--managed-root-id' || |
| Object.hasOwn(input.valueOptions ?? {}, argument ?? '') |
| ) { |
| const parsed = optionValue(argv, index, argument ?? ''); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--root') rootPath = parsed; |
| else if (argument === '--websocket-port') websocketPort = Number(parsed); |
| else if (argument === '--websocket-path') websocketPath = parsed; |
| else if (argument === '--expected-service-id') expectedServiceId = parsed; |
| else if (argument === '--expected-root-path') expectedRootPath = parsed; |
| else if (argument === '--expected-root-id') expectedRootId = parsed; |
| else if (argument === '--expected-deployment-id') expectedDeploymentId = parsed; |
| else if (argument === '--operator-deployment-id') operatorDeploymentId = parsed; |
| else if (argument === '--managed-root-id') managedRootId = parsed; |
| else if (argument === '--project-root' || argument === '--project-root-json') { |
| if (projectDirectoryPolicySpecified && projectDirectoryRoots.length === 0) { |
| return error('--project-root cannot be combined with --no-project-roots'); |
| } |
| projectDirectoryPolicySpecified = true; |
| const root = |
| argument === '--project-root' |
| ? parseProjectRoot(parsed, 'posix') |
| : parseProjectRootJson(parsed, 'posix'); |
| if ('kind' in root) return root; |
| if (projectDirectoryRoots.length >= PROJECT_DIRECTORY_MAX_ROOTS) { |
| return error( |
| `--project-root may be provided at most ${PROJECT_DIRECTORY_MAX_ROOTS} times`, |
| ); |
| } |
| if (projectDirectoryRoots.some((candidate) => candidate.label === root.label)) { |
| return error(`Duplicate --project-root label: ${root.label}`); |
| } |
| projectDirectoryRoots.push(root); |
| } else { |
| const optionError = input.valueOptions?.[argument ?? '']?.(parsed); |
| if (optionError) return optionError; |
| } |
| index += 1; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if ( |
| websocketPort !== undefined && |
| (!Number.isInteger(websocketPort) || websocketPort < 1 || websocketPort > 65_535) |
| ) { |
| return error('--websocket-port must be an integer between 1 and 65535'); |
| } |
| if (websocketPath !== undefined && !isCanonicalRuntimeHostWebSocketPath(websocketPath)) { |
| return error('--websocket-path must be a canonical absolute URL path'); |
| } |
| if (expectedServiceId !== undefined && !/^[a-f0-9]{64}$/u.test(expectedServiceId)) { |
| return error('--expected-service-id must be a Runtime Host managed service identity'); |
| } |
| if (expectedRootId !== undefined && !/^[a-f0-9]{64}$/u.test(expectedRootId)) { |
| return error('--expected-root-id must be a Runtime Host State Root identity'); |
| } |
| if ( |
| expectedDeploymentId !== undefined && |
| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test( |
| expectedDeploymentId, |
| ) |
| ) { |
| return error('--expected-deployment-id must be a Runtime Host deployment identity'); |
| } |
| if (managedRootId !== undefined && !/^[a-f0-9]{64}$/u.test(managedRootId)) { |
| return error('--managed-root-id must be a Runtime Host State Root identity'); |
| } |
| if ( |
| operatorDeploymentId !== undefined && |
| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test( |
| operatorDeploymentId, |
| ) |
| ) { |
| return error('--operator-deployment-id must be a Runtime Host deployment identity'); |
| } |
| if ( |
| expectedRootPath !== undefined && |
| (expectedRootPath.length === 0 || |
| Buffer.byteLength(expectedRootPath, 'utf8') > 4 * 1024 || |
| /[\u0000-\u001f\u007f]/u.test(expectedRootPath)) |
| ) { |
| return error('--expected-root-path is invalid'); |
| } |
| const hasExpectedTarget = |
| expectedServiceId !== undefined || |
| expectedRootPath !== undefined || |
| expectedRootId !== undefined || |
| expectedDeploymentId !== undefined; |
| if (hasExpectedTarget && (!expectedServiceId || !expectedRootPath || !expectedRootId)) { |
| return error( |
| '--expected-service-id, --expected-root-path, and --expected-root-id must be provided together', |
| ); |
| } |
| return { |
| json, |
| ...(framed ? { framed: true as const } : {}), |
| ...(managedRootId ? { managedRootId } : {}), |
| ...(operatorDeploymentId ? { operatorDeploymentId } : {}), |
| ...(rootPath ? { rootPath } : {}), |
| ...(projectDirectoryPolicySpecified ? { projectDirectoryRoots } : {}), |
| ...(websocketPort === undefined ? {} : { websocketPort }), |
| ...(websocketPath === undefined ? {} : { websocketPath }), |
| ...(expectedServiceId === undefined |
| ? {} |
| : { |
| expectedTarget: { |
| serviceId: expectedServiceId, |
| rootPath: expectedRootPath!, |
| rootId: expectedRootId!, |
| ...(expectedDeploymentId ? { deploymentId: expectedDeploymentId } : {}), |
| }, |
| }), |
| }; |
| } |
| |
| function isSafeAbsolutePath(value: string): boolean { |
| return isAbsolute(value) && !/[\u0000-\u001f\u007f]/u.test(value); |
| } |
| |
| function parseProjectCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if (action !== 'list' && action !== 'add') { |
| return error( |
| action |
| ? `Unexpected runtime-host project command: ${action}` |
| : 'runtime-host project requires the list or add command', |
| ); |
| } |
| let rootPath: string | undefined; |
| let path: string | undefined; |
| let prefer = false; |
| for (let index = 1; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--root') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| rootPath = parsed; |
| index += 1; |
| continue; |
| } |
| if (action === 'add' && argument === '--prefer') { |
| prefer = true; |
| continue; |
| } |
| if (action === 'add' && path === undefined) { |
| path = argument; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if (action === 'list') { |
| return { |
| kind: 'runtime-host-project-list', |
| ...(rootPath ? { rootPath } : {}), |
| }; |
| } |
| if (!path) return error('runtime-host project add requires a path'); |
| return { |
| kind: 'runtime-host-project-add', |
| path, |
| prefer, |
| ...(rootPath ? { rootPath } : {}), |
| }; |
| } |
| |
| function parsePluginCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| const actions = [ |
| 'status', |
| 'list', |
| 'inspect', |
| 'failures', |
| 'install', |
| 'uninstall', |
| 'reload', |
| 'export', |
| 'apply', |
| 'reconcile', |
| ] as const; |
| if (!actions.includes(action as (typeof actions)[number])) { |
| return error( |
| action |
| ? `Unexpected runtime-host plugin command: ${action}` |
| : 'runtime-host plugin requires an action', |
| ); |
| } |
| let rootPath: string | undefined; |
| let rootId: string | undefined; |
| let cursor: string | undefined; |
| let limit: number | undefined; |
| const positional: string[] = []; |
| for (let index = 1; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if ( |
| argument === '--root' || |
| argument === '--scope' || |
| argument === '--cursor' || |
| argument === '--limit' |
| ) { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--root') rootPath = parsed; |
| else if (argument === '--scope') rootId = parsed; |
| else if (argument === '--cursor') cursor = parsed; |
| else { |
| const numeric = Number(parsed); |
| if (!Number.isSafeInteger(numeric) || numeric < 0 || numeric < 1 || numeric > 64) { |
| return error('--limit requires an integer between 1 and 64'); |
| } |
| limit = numeric; |
| } |
| index += 1; |
| continue; |
| } |
| positional.push(argument ?? ''); |
| } |
| const selected = action as (typeof actions)[number]; |
| const expected = |
| selected === 'export' |
| ? 2 |
| : ['install', 'uninstall', 'reload', 'apply'].includes(selected) |
| ? 1 |
| : 0; |
| if (positional.length !== expected) { |
| return error( |
| `runtime-host plugin ${selected} requires ${expected} target${expected === 1 ? '' : 's'}`, |
| ); |
| } |
| if (rootId && selected !== 'inspect') return error('--scope is only valid for plugin inspect'); |
| if ( |
| (cursor !== undefined || limit !== undefined) && |
| !['list', 'inspect', 'failures'].includes(selected) |
| ) { |
| return error('--cursor and --limit require a paged Plugin query'); |
| } |
| return { |
| kind: 'runtime-host-plugin', |
| action: selected, |
| ...(rootPath ? { rootPath } : {}), |
| ...(positional[0] ? { subject: positional[0] } : {}), |
| ...(positional[1] ? { targetPath: positional[1] } : {}), |
| ...(rootId ? { rootId } : {}), |
| ...(cursor ? { cursor } : {}), |
| ...(limit === undefined ? {} : { limit }), |
| }; |
| } |
| |
| function parseProfileCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if (action === 'list') { |
| return argv.length === 1 |
| ? { kind: 'runtime-host-profile-list' } |
| : error(`Unexpected argument: ${argv[1] ?? ''}`); |
| } |
| if (action === 'remove') { |
| if (argv[1] !== '--id') return error('runtime-host profile remove requires --id'); |
| const id = optionValue(argv, 1, '--id'); |
| if (typeof id !== 'string') return id; |
| return argv.length === 3 |
| ? { kind: 'runtime-host-profile-remove', id } |
| : error(`Unexpected argument: ${argv[3] ?? ''}`); |
| } |
| if (action !== 'set') { |
| return error( |
| action |
| ? `Unexpected runtime-host profile command: ${action}` |
| : 'runtime-host profile requires the list, set, or remove command', |
| ); |
| } |
| let id: string | undefined; |
| let name: string | undefined; |
| let tlsUrl: string | undefined; |
| let plaintextUrl: string | undefined; |
| let acknowledgePlaintext = false; |
| let sshDestination: string | undefined; |
| let sshPort: number | undefined; |
| let sshRemotePort: number | undefined; |
| let sshWebSocketPath = '/runtime-host'; |
| let sshWebSocketPathConfigured = false; |
| let wslDistribution: string | undefined; |
| let operator: RuntimeHostPosixOperatorCommand | undefined; |
| let operatorPath: string | undefined; |
| let expectedRootId: string | undefined; |
| let credentialEnv: string | undefined; |
| for (let index = 1; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if ( |
| argument !== '--id' && |
| argument !== '--name' && |
| argument !== '--tls-url' && |
| argument !== '--plaintext-url' && |
| argument !== '--ssh-destination' && |
| argument !== '--ssh-port' && |
| argument !== '--ssh-remote-port' && |
| argument !== '--ssh-websocket-path' && |
| argument !== '--wsl-distribution' && |
| argument !== '--operator-command' && |
| argument !== '--operator-path' && |
| argument !== '--expected-root' && |
| argument !== '--credential-env' && |
| argument !== '--acknowledge-plaintext' |
| ) { |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if (argument === '--acknowledge-plaintext') { |
| acknowledgePlaintext = true; |
| continue; |
| } |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--id') id = parsed; |
| if (argument === '--name') name = parsed; |
| if (argument === '--tls-url') tlsUrl = parsed; |
| if (argument === '--plaintext-url') plaintextUrl = parsed; |
| if (argument === '--ssh-destination') sshDestination = parsed; |
| if (argument === '--ssh-port') sshPort = Number(parsed); |
| if (argument === '--ssh-remote-port') sshRemotePort = Number(parsed); |
| if (argument === '--ssh-websocket-path') { |
| sshWebSocketPath = parsed; |
| sshWebSocketPathConfigured = true; |
| } |
| if (argument === '--wsl-distribution') wslDistribution = parsed; |
| if (argument === '--operator-command') { |
| try { |
| operator = decodeRuntimeHostPosixOperatorCommand(JSON.parse(parsed)); |
| } catch { |
| return error('--operator-command must be a valid Runtime Host operator command JSON value'); |
| } |
| } |
| if (argument === '--operator-path') operatorPath = parsed; |
| if (argument === '--expected-root') expectedRootId = parsed; |
| if (argument === '--credential-env') credentialEnv = parsed; |
| index += 1; |
| } |
| if (!id) return error('--id is required'); |
| if (!name) return error('--name is required'); |
| if (operator && operatorPath) { |
| return error('--operator-command and --operator-path cannot be combined'); |
| } |
| if (operatorPath) { |
| try { |
| operator = createRuntimeHostLegacyPosixOperatorCommand(operatorPath); |
| } catch { |
| return error('--operator-path must be an absolute POSIX path'); |
| } |
| } |
| if ( |
| (tlsUrl ? 1 : 0) + |
| (plaintextUrl ? 1 : 0) + |
| (sshDestination ? 1 : 0) + |
| (wslDistribution ? 1 : 0) !== |
| 1 |
| ) { |
| return error( |
| 'exactly one of --tls-url, --plaintext-url, --ssh-destination, or --wsl-distribution is required', |
| ); |
| } |
| if (wslDistribution && !operator) { |
| return error('--wsl-distribution requires --operator-command or --operator-path'); |
| } |
| if (!wslDistribution && operatorPath) return error('--operator-path requires --wsl-distribution'); |
| if (!wslDistribution && operator) return error('--operator-command requires --wsl-distribution'); |
| if (wslDistribution && credentialEnv) { |
| return error('WSL environment profiles do not accept --credential-env'); |
| } |
| if (plaintextUrl && !acknowledgePlaintext) { |
| return error('--plaintext-url requires --acknowledge-plaintext'); |
| } |
| if (!plaintextUrl && acknowledgePlaintext) { |
| return error('--acknowledge-plaintext requires --plaintext-url'); |
| } |
| if ( |
| !sshDestination && |
| (sshPort !== undefined || sshRemotePort !== undefined || sshWebSocketPathConfigured) |
| ) { |
| return error('SSH options require --ssh-destination'); |
| } |
| if (sshDestination && !sshRemotePort) { |
| return error('--ssh-destination requires --ssh-remote-port'); |
| } |
| if (sshPort !== undefined && (!Number.isInteger(sshPort) || sshPort < 1 || sshPort > 65_535)) { |
| return error('--ssh-port must be an integer between 1 and 65535'); |
| } |
| if ( |
| sshRemotePort !== undefined && |
| (!Number.isInteger(sshRemotePort) || sshRemotePort < 1 || sshRemotePort > 65_535) |
| ) { |
| return error('--ssh-remote-port must be an integer between 1 and 65535'); |
| } |
| if (!expectedRootId) return error('--expected-root is required'); |
| if (wslDistribution) { |
| return { |
| kind: 'runtime-host-profile-set-environment', |
| id, |
| name, |
| distribution: wslDistribution, |
| operator: operator!, |
| expectedRootId, |
| }; |
| } |
| const transport = tlsUrl |
| ? ({ kind: 'tls', url: tlsUrl } as const) |
| : plaintextUrl |
| ? ({ |
| kind: 'plaintext', |
| url: plaintextUrl, |
| acknowledgement: 'plaintext-bearer-v1', |
| } as const) |
| : ({ |
| kind: 'ssh', |
| destination: sshDestination!, |
| ...(sshPort === undefined ? {} : { sshPort }), |
| remotePort: sshRemotePort!, |
| websocketPath: sshWebSocketPath, |
| } as const); |
| return { |
| kind: 'runtime-host-profile-set', |
| id, |
| name, |
| transport, |
| expectedRootId, |
| ...(credentialEnv ? { credentialEnv } : {}), |
| }; |
| } |
| |
| function parseCapabilityProviderCommand(argv: string[]): RuntimeHostCliCommand { |
| if (argv[0] !== 'serve') { |
| return error( |
| argv[0] |
| ? `Unexpected runtime-host capability-provider command: ${argv[0]}` |
| : 'runtime-host capability-provider requires the serve command', |
| ); |
| } |
| let url: string | undefined; |
| let mcpConfigPath: string | undefined; |
| let expectedRootId: string | undefined; |
| let credentialEnv: string | undefined; |
| let clientIdentityPath: string | undefined; |
| for (let index = 1; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if ( |
| argument === '--url' || |
| argument === '--mcp-config' || |
| argument === '--expected-root' || |
| argument === '--credential-env' || |
| argument === '--client-identity' |
| ) { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--url') url = parsed; |
| if (argument === '--mcp-config') mcpConfigPath = parsed; |
| if (argument === '--expected-root') expectedRootId = parsed; |
| if (argument === '--credential-env') credentialEnv = parsed; |
| if (argument === '--client-identity') clientIdentityPath = parsed; |
| index += 1; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if (!url) return error('--url is required'); |
| if (!mcpConfigPath) return error('--mcp-config is required'); |
| if (!expectedRootId) return error('--expected-root is required'); |
| return { |
| kind: 'runtime-host-capability-provider-serve', |
| url, |
| mcpConfigPath, |
| expectedRootId, |
| ...(credentialEnv ? { credentialEnv } : {}), |
| ...(clientIdentityPath ? { clientIdentityPath } : {}), |
| }; |
| } |
| |
| function parseServeCommand(argv: string[]): RuntimeHostCliCommand { |
| let rootPath: string | undefined; |
| let managedServiceConfigPath: string | undefined; |
| let managedRootId: string | undefined; |
| let managedDeploymentId: string | undefined; |
| let managedConfigRevision: number | undefined; |
| let json = false; |
| let websocketHost = '127.0.0.1'; |
| let websocketConfigured = false; |
| let websocketPort: number | undefined; |
| let websocketPath: string | undefined; |
| let tlsCertificatePath: string | undefined; |
| let tlsPrivateKeyPath: string | undefined; |
| let allowInsecureRemote = false; |
| let peerNativePath: string | undefined; |
| let peerKeyPath: string | undefined; |
| let peerId: string | undefined; |
| const allowedOrigins: string[] = []; |
| const peerListenAddresses: string[] = []; |
| const peerCoordinationRelays: string[] = []; |
| const projectDirectoryRoots: { label: string; path: string }[] = []; |
| let projectDirectoryPolicySpecified = false; |
| for (let index = 0; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--json') { |
| json = true; |
| continue; |
| } |
| if (argument === '--allow-insecure-remote') { |
| allowInsecureRemote = true; |
| websocketConfigured = true; |
| continue; |
| } |
| if (argument === '--root') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| rootPath = parsed; |
| index += 1; |
| continue; |
| } |
| if (argument === '--managed-service-config') { |
| if (managedServiceConfigPath !== undefined) { |
| return error('Duplicate --managed-service-config'); |
| } |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (!isSafeAbsolutePath(parsed)) { |
| return error('--managed-service-config must be an absolute path'); |
| } |
| managedServiceConfigPath = parsed; |
| index += 1; |
| continue; |
| } |
| if ( |
| argument === '--root-id' || |
| argument === '--deployment-id' || |
| argument === '--config-revision' |
| ) { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--root-id') { |
| if (managedRootId !== undefined) return error('Duplicate --root-id'); |
| managedRootId = parsed; |
| } else if (argument === '--deployment-id') { |
| if (managedDeploymentId !== undefined) return error('Duplicate --deployment-id'); |
| managedDeploymentId = parsed; |
| } else { |
| if (managedConfigRevision !== undefined) return error('Duplicate --config-revision'); |
| managedConfigRevision = Number(parsed); |
| } |
| index += 1; |
| continue; |
| } |
| if (argument === '--project-root' || argument === '--project-root-json') { |
| if (projectDirectoryPolicySpecified && projectDirectoryRoots.length === 0) { |
| return error('--project-root cannot be combined with --no-project-roots'); |
| } |
| projectDirectoryPolicySpecified = true; |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| const root = |
| argument === '--project-root' |
| ? parseProjectRoot(parsed, 'native') |
| : parseProjectRootJson(parsed, 'native'); |
| if ('kind' in root) return root; |
| if (projectDirectoryRoots.length >= PROJECT_DIRECTORY_MAX_ROOTS) { |
| return error(`--project-root may be provided at most ${PROJECT_DIRECTORY_MAX_ROOTS} times`); |
| } |
| if (projectDirectoryRoots.some((candidate) => candidate.label === root.label)) { |
| return error(`Duplicate --project-root label: ${root.label}`); |
| } |
| projectDirectoryRoots.push(root); |
| index += 1; |
| continue; |
| } |
| if (argument === '--no-project-roots') { |
| if (projectDirectoryPolicySpecified) { |
| return error('--no-project-roots cannot be combined with --project-root'); |
| } |
| projectDirectoryPolicySpecified = true; |
| continue; |
| } |
| if (argument === '--websocket-host') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| websocketHost = parsed; |
| websocketConfigured = true; |
| index += 1; |
| continue; |
| } |
| if (argument === '--websocket-port') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| websocketPort = Number(parsed); |
| websocketConfigured = true; |
| if (!Number.isInteger(websocketPort) || websocketPort < 1 || websocketPort > 65_535) { |
| return error('--websocket-port must be an integer between 1 and 65535'); |
| } |
| index += 1; |
| continue; |
| } |
| if (argument === '--websocket-path') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| websocketPath = parsed; |
| websocketConfigured = true; |
| index += 1; |
| continue; |
| } |
| if (argument === '--tls-certificate' || argument === '--tls-private-key') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--tls-certificate') tlsCertificatePath = parsed; |
| else tlsPrivateKeyPath = parsed; |
| websocketConfigured = true; |
| index += 1; |
| continue; |
| } |
| if (argument === '--allow-origin') { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| allowedOrigins.push(parsed); |
| websocketConfigured = true; |
| index += 1; |
| continue; |
| } |
| if ( |
| argument === '--peer-native-path' || |
| argument === '--peer-key' || |
| argument === '--peer-id' || |
| argument === '--peer-listen' || |
| argument === '--peer-coordination-relay' |
| ) { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--peer-native-path') peerNativePath = parsed; |
| if (argument === '--peer-key') peerKeyPath = parsed; |
| if (argument === '--peer-id') peerId = parsed; |
| if (argument === '--peer-listen') peerListenAddresses.push(parsed); |
| if (argument === '--peer-coordination-relay') peerCoordinationRelays.push(parsed); |
| index += 1; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if ((tlsCertificatePath === undefined) !== (tlsPrivateKeyPath === undefined)) { |
| return error('--tls-certificate and --tls-private-key must be provided together'); |
| } |
| if (allowInsecureRemote && tlsCertificatePath !== undefined) { |
| return error('--allow-insecure-remote cannot be combined with TLS'); |
| } |
| if (websocketConfigured && websocketPort === undefined) { |
| return error('--websocket-port is required for WebSocket options'); |
| } |
| if (websocketPath !== undefined && !isCanonicalRuntimeHostWebSocketPath(websocketPath)) { |
| return error('--websocket-path must be a canonical absolute URL path'); |
| } |
| if ((peerNativePath === undefined) !== (peerKeyPath === undefined)) { |
| return error('--peer-native-path and --peer-key must be provided together'); |
| } |
| if ( |
| peerNativePath === undefined && |
| (peerId !== undefined || peerListenAddresses.length > 0 || peerCoordinationRelays.length > 0) |
| ) { |
| return error('peer listener options require --peer-native-path and --peer-key'); |
| } |
| if ( |
| managedServiceConfigPath !== undefined && |
| (rootPath !== undefined || |
| projectDirectoryPolicySpecified || |
| websocketConfigured || |
| peerNativePath !== undefined) |
| ) { |
| return error('--managed-service-config cannot be combined with Runtime Host settings'); |
| } |
| const managedDeploymentSpecified = |
| managedRootId !== undefined || |
| managedDeploymentId !== undefined || |
| managedConfigRevision !== undefined; |
| if ( |
| managedDeploymentSpecified && |
| (managedRootId === undefined || |
| managedDeploymentId === undefined || |
| managedConfigRevision === undefined) |
| ) { |
| return error('--root-id, --deployment-id, and --config-revision must be provided together'); |
| } |
| if ( |
| managedRootId !== undefined && |
| (!/^[a-f0-9]{64}$/u.test(managedRootId) || |
| !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test( |
| managedDeploymentId!, |
| ) || |
| !Number.isSafeInteger(managedConfigRevision) || |
| managedConfigRevision! < 1) |
| ) { |
| return error('Managed deployment identity is invalid'); |
| } |
| if ( |
| managedRootId !== undefined && |
| (managedServiceConfigPath !== undefined || |
| rootPath !== undefined || |
| projectDirectoryPolicySpecified || |
| websocketConfigured || |
| peerNativePath !== undefined) |
| ) { |
| return error('Managed deployment identity cannot be combined with Runtime Host settings'); |
| } |
| return { |
| kind: 'runtime-host-serve', |
| json, |
| ...(managedServiceConfigPath ? { managedServiceConfigPath } : {}), |
| ...(managedRootId |
| ? { |
| managedDeployment: { |
| rootId: managedRootId, |
| deploymentId: managedDeploymentId!, |
| configRevision: managedConfigRevision!, |
| }, |
| } |
| : {}), |
| ...(rootPath ? { rootPath } : {}), |
| ...(projectDirectoryPolicySpecified ? { projectDirectoryRoots } : {}), |
| ...(websocketPort === undefined |
| ? {} |
| : { |
| websocket: { |
| host: websocketHost, |
| port: websocketPort, |
| ...(websocketPath ? { path: websocketPath } : {}), |
| ...(tlsCertificatePath ? { tlsCertificatePath } : {}), |
| ...(tlsPrivateKeyPath ? { tlsPrivateKeyPath } : {}), |
| ...(allowedOrigins.length > 0 ? { allowedOrigins } : {}), |
| ...(allowInsecureRemote ? { allowInsecureRemote: true } : {}), |
| }, |
| }), |
| ...(peerNativePath === undefined |
| ? {} |
| : { |
| peer: { |
| nativePath: peerNativePath, |
| keyPath: peerKeyPath!, |
| ...(peerId ? { expectedPeerId: peerId } : {}), |
| ...(peerListenAddresses.length > 0 ? { listenAddresses: peerListenAddresses } : {}), |
| ...(peerCoordinationRelays.length > 0 |
| ? { coordinationRelays: peerCoordinationRelays } |
| : {}), |
| }, |
| }), |
| }; |
| } |
| |
| function parseProjectRoot( |
| value: string, |
| pathKind: 'native' | 'posix', |
| ): { label: string; path: string } | RuntimeHostCliError { |
| const separator = value.indexOf('='); |
| if (separator <= 0 || separator === value.length - 1) { |
| return error('--project-root must use <label>=<absolute-path>'); |
| } |
| const root = canonicalProjectDirectoryRootSpec({ |
| label: value.slice(0, separator), |
| path: value.slice(separator + 1), |
| }); |
| if (!projectRootValid(root, pathKind)) { |
| return error( |
| `--project-root must use a valid label and absolute ${pathKind === 'posix' ? 'POSIX' : 'Host'} path`, |
| ); |
| } |
| return root; |
| } |
| |
| function parseProjectRootJson( |
| value: string, |
| pathKind: 'native' | 'posix', |
| ): { label: string; path: string } | RuntimeHostCliError { |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(value); |
| } catch { |
| return error('--project-root-json must be a JSON object with label and path'); |
| } |
| if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { |
| return error('--project-root-json must be a JSON object with label and path'); |
| } |
| const root = parsed as Record<string, unknown>; |
| if ( |
| Object.keys(root).length !== 2 || |
| typeof root.label !== 'string' || |
| typeof root.path !== 'string' |
| ) { |
| return error('--project-root-json must be a JSON object with label and path'); |
| } |
| const canonical = canonicalProjectDirectoryRootSpec({ |
| label: root.label, |
| path: root.path, |
| }); |
| if (!projectRootValid(canonical, pathKind)) { |
| return error( |
| `--project-root-json must use a valid label and absolute ${pathKind === 'posix' ? 'POSIX' : 'Host'} path`, |
| ); |
| } |
| return canonical; |
| } |
| |
| function projectRootValid( |
| root: { readonly label: string; readonly path: string }, |
| pathKind: 'native' | 'posix', |
| ): boolean { |
| return pathKind === 'posix' |
| ? projectDirectoryPosixRootSpecValid(root) |
| : projectDirectoryRootSpecValid(root) && isAbsolute(root.path); |
| } |
| |
| function parseAccessCommand(argv: string[]): RuntimeHostCliCommand { |
| const action = argv[0]; |
| if ( |
| action !== 'list' && |
| action !== 'issue' && |
| action !== 'prepare' && |
| action !== 'revoke' && |
| action !== 'connection-code' |
| ) { |
| return error( |
| action |
| ? `Unexpected runtime-host access command: ${action}` |
| : 'runtime-host access requires connection-code, list, issue, prepare, or revoke', |
| ); |
| } |
| let rootPath: string | undefined; |
| let expectedRootId: string | undefined; |
| let framed = false; |
| let principalId: string | undefined; |
| let principalKind: 'remote_owner' | 'capability_provider' = 'remote_owner'; |
| let principalKindSpecified = false; |
| let credentialId: string | undefined; |
| let capabilityOwnerCredentialId: string | undefined; |
| let currentCredentialFingerprint: string | undefined; |
| let name: string | undefined; |
| const operationGrants: string[] = []; |
| let canPublishClientCapabilities = false; |
| let canUseHostPaths = false; |
| let preset: 'desktop-client' | 'terminal-client' | undefined; |
| for (let index = 1; index < argv.length; index += 1) { |
| const argument = argv[index]; |
| if (argument === '--framed') { |
| if (framed) return error('Duplicate --framed'); |
| framed = true; |
| continue; |
| } |
| if (argument === '--publish-client-capabilities') { |
| canPublishClientCapabilities = true; |
| continue; |
| } |
| if (argument === '--allow-host-paths') { |
| canUseHostPaths = true; |
| continue; |
| } |
| if ( |
| argument === '--root' || |
| argument === '--expected-root' || |
| argument === '--name' || |
| argument === '--kind' || |
| argument === '--preset' || |
| argument === '--principal' || |
| argument === '--grant' || |
| argument === '--credential' || |
| argument === '--capability-owner-credential' || |
| argument === '--current-fingerprint' |
| ) { |
| const parsed = optionValue(argv, index, argument); |
| if (typeof parsed !== 'string') return parsed; |
| if (argument === '--root') rootPath = parsed; |
| if (argument === '--expected-root') expectedRootId = parsed; |
| if (argument === '--name') name = parsed; |
| if (argument === '--kind') { |
| if (parsed !== 'remote-owner' && parsed !== 'capability-provider') { |
| return error('--kind must be remote-owner or capability-provider'); |
| } |
| principalKind = parsed === 'remote-owner' ? 'remote_owner' : 'capability_provider'; |
| principalKindSpecified = true; |
| } |
| if (argument === '--preset') { |
| if (parsed !== 'desktop-client' && parsed !== 'terminal-client') { |
| return error('--preset must be desktop-client or terminal-client'); |
| } |
| preset = parsed; |
| } |
| if (argument === '--principal') principalId = parsed; |
| if (argument === '--grant') operationGrants.push(parsed); |
| if (argument === '--credential') credentialId = parsed; |
| if (argument === '--capability-owner-credential') capabilityOwnerCredentialId = parsed; |
| if (argument === '--current-fingerprint') currentCredentialFingerprint = parsed; |
| index += 1; |
| continue; |
| } |
| return error(`Unexpected argument: ${argument ?? ''}`); |
| } |
| if (expectedRootId && !/^[a-f0-9]{64}$/u.test(expectedRootId)) { |
| return error('--expected-root must be a Runtime Host root identity'); |
| } |
| if (action === 'connection-code') { |
| if ( |
| principalId || |
| principalKindSpecified || |
| operationGrants.length > 0 || |
| canPublishClientCapabilities || |
| canUseHostPaths || |
| preset || |
| credentialId || |
| capabilityOwnerCredentialId || |
| currentCredentialFingerprint |
| ) { |
| return error('Credential management options are not valid for access connection-code'); |
| } |
| return { |
| kind: 'runtime-host-access-connection-code', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| ...(name ? { name } : {}), |
| framed, |
| }; |
| } |
| if (action === 'list') { |
| if ( |
| principalId || |
| principalKindSpecified || |
| operationGrants.length > 0 || |
| canPublishClientCapabilities || |
| canUseHostPaths || |
| preset || |
| credentialId || |
| capabilityOwnerCredentialId || |
| currentCredentialFingerprint || |
| name |
| ) { |
| return error('Credential mutation options are not valid for access list'); |
| } |
| return { |
| kind: 'runtime-host-access-list', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| framed, |
| }; |
| } |
| if (action === 'prepare') { |
| if (!framed) return error('access prepare is reserved for framed operator management'); |
| if (!currentCredentialFingerprint) return error('--current-fingerprint is required'); |
| if (!/^[a-f0-9]{32}$/u.test(currentCredentialFingerprint)) { |
| return error('--current-fingerprint must be a Runtime Host credential fingerprint'); |
| } |
| if ( |
| principalId || |
| principalKindSpecified || |
| operationGrants.length > 0 || |
| canPublishClientCapabilities || |
| canUseHostPaths || |
| preset || |
| credentialId || |
| capabilityOwnerCredentialId || |
| name |
| ) { |
| return error('Credential issue options are not valid for access prepare'); |
| } |
| return { |
| kind: 'runtime-host-access-prepare', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| currentCredentialFingerprint, |
| }; |
| } |
| if (action === 'issue') { |
| if (framed) return error('--framed is only valid for access management'); |
| if (!principalId) return error('--principal is required'); |
| if (credentialId || currentCredentialFingerprint || name) { |
| return error('Credential target options are only valid for access revoke'); |
| } |
| if ( |
| preset && |
| (principalKindSpecified || |
| operationGrants.length > 0 || |
| canPublishClientCapabilities || |
| canUseHostPaths) |
| ) { |
| return error('--preset cannot be combined with --kind, --grant, or authority flags'); |
| } |
| if (preset) { |
| if (capabilityOwnerCredentialId) { |
| return error('--capability-owner-credential requires --kind capability-provider'); |
| } |
| return { |
| kind: 'runtime-host-access-issue', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| principalKind: 'remote_owner', |
| principalId, |
| operationGrants, |
| canPublishClientCapabilities: false, |
| canUseHostPaths: false, |
| preset, |
| }; |
| } |
| if (principalKind === 'capability_provider') { |
| const requiredGrants = ['client.capability.replace', 'client.capability.unregister']; |
| if (canUseHostPaths) return error('A capability provider cannot use Host paths'); |
| if (operationGrants.length === 0) operationGrants.push(...requiredGrants); |
| if ( |
| operationGrants.length !== requiredGrants.length || |
| requiredGrants.some((grant) => !operationGrants.includes(grant)) |
| ) { |
| return error('A capability provider may grant only Client Capability publication'); |
| } |
| canPublishClientCapabilities = true; |
| } else { |
| if (capabilityOwnerCredentialId) { |
| return error('--capability-owner-credential requires --kind capability-provider'); |
| } |
| if (operationGrants.length === 0) return error('At least one --grant is required'); |
| } |
| return { |
| kind: 'runtime-host-access-issue', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| principalKind, |
| principalId, |
| operationGrants, |
| canPublishClientCapabilities, |
| canUseHostPaths, |
| ...(capabilityOwnerCredentialId ? { capabilityOwnerCredentialId } : {}), |
| }; |
| } |
| if (capabilityOwnerCredentialId) { |
| return error('--capability-owner-credential is only valid for access issue'); |
| } |
| if (!credentialId) return error('--credential is required'); |
| if (framed && !currentCredentialFingerprint) { |
| return error('--current-fingerprint is required for framed access revoke'); |
| } |
| if (currentCredentialFingerprint && !/^[a-f0-9]{32}$/u.test(currentCredentialFingerprint)) { |
| return error('--current-fingerprint must be a Runtime Host credential fingerprint'); |
| } |
| if ( |
| principalId || |
| principalKindSpecified || |
| operationGrants.length > 0 || |
| canPublishClientCapabilities || |
| canUseHostPaths || |
| preset || |
| name |
| ) { |
| return error('Issue-only access options are not valid for revoke'); |
| } |
| return { |
| kind: 'runtime-host-access-revoke', |
| ...(rootPath ? { rootPath } : {}), |
| ...(expectedRootId ? { expectedRootId } : {}), |
| credentialId, |
| ...(currentCredentialFingerprint ? { currentCredentialFingerprint } : {}), |
| framed, |
| }; |
| } |
| |
| function optionValue(argv: string[], index: number, option: string): string | RuntimeHostCliError { |
| const value = argv[index + 1]; |
| return !value || value.startsWith('-') ? error(`${option} requires a value`) : value; |
| } |
| |
| function error(message: string): RuntimeHostCliError { |
| return { kind: 'error', message, exitCode: 2 }; |
| } |