Files
orca/src/cli/handler-group-manifest.ts
T
cedce2aa9f feat(cli): add orca ports to list a workspace's detected ports (#33016)
* feat(cli): add orca ports to list a workspace's detected ports

Scripts and agents had no way to read the ports Orca already detects for
a workspace, so finding a dev server on a random port meant redoing the
lsof scan by hand. `orca ports` reuses the host-scoped
workspacePorts.scanHost RPC, keeps the rows owned by the current (or
--worktree) workspace, and prints port, pid, process, connect host,
protocol, how the owner was matched, and the advertised URL. --all shows
every listener on that host. An older runtime without scanHost gets an
"update Orca" error instead of a raw method_not_found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): resolve folder workspaces in orca ports and match rows by path

Run inside a native folder, `orca ports` failed with selector_not_found
(or picked an enclosing git worktree) because it only searched the
worktree list. It now resolves the cwd like `agent launch`, native
folders first, and shows that host's listeners. Rows are matched on the
owner's path instead of its id, so every instance of a repo-backed
folder workspace keeps its ports even though attribution names only the
first instance as owner. `--all` adds the worktree id when two owners
share a display name, and the skill guide notes that only workspace
rows carry `owner` and `advertisedUrl`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): refuse orca ports for folder workspaces without --all; document the command

Co-authored-by: Manuel Meurer <manuel@meurer.io>

* fix(cli): recognise native folder path selectors in orca ports; note SSH scan limits

Co-authored-by: Manuel Meurer <manuel@meurer.io>

* fix(cli): refuse a folder path shared by several folder workspaces in orca ports

Co-authored-by: Manuel Meurer <manuel@meurer.io>

* fix(cli): refuse a path a worktree and a folder workspace share in orca ports

Co-authored-by: Manuel Meurer <manuel@meurer.io>

---------

Co-authored-by: Manuel Meurer <manuel@meurer.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:05:47 -07:00

296 lines
8.3 KiB
TypeScript

import type { CommandHandler } from './dispatch'
import { BROWSER_HANDLER_GROUPS } from './browser-handler-groups'
import { JIRA_HANDLER_GROUP } from './jira-handler-group'
import { LINEAR_HANDLER_GROUP } from './linear-handler-group'
import { REFERENCE_HANDLER_GROUP } from './reference-handler-group'
export type HandlerGroup = {
name: string
// Eager keys allow routing and duplicate checks without importing each handler graph.
keys: readonly string[]
load: () => Promise<Record<string, CommandHandler>>
}
// handler-group-manifest.test.ts checks keys against the real exports to prevent drift.
export const HANDLER_GROUPS: readonly HandlerGroup[] = [
JIRA_HANDLER_GROUP,
LINEAR_HANDLER_GROUP,
REFERENCE_HANDLER_GROUP,
{
name: 'core',
keys: ['claude-teams', 'open', 'serve', 'status'],
load: async () => (await import('./handlers/core.js')).CORE_HANDLERS
},
{
name: 'serve-restore',
keys: ['serve restore resume'],
load: async () => (await import('./handlers/serve-restore.js')).SERVE_RESTORE_HANDLERS
},
{
name: 'serve-service',
keys: ['serve service install', 'serve service status', 'serve service uninstall'],
load: async () => (await import('./handlers/serve-service.js')).SERVE_SERVICE_HANDLERS
},
{
name: 'account',
keys: ['account add', 'account list', 'account select', 'account rm'],
load: async () => (await import('./handlers/account.js')).ACCOUNT_HANDLERS
},
{
name: 'artifacts',
keys: [
'artifacts list',
'artifacts share',
'artifacts update',
'artifacts unshare',
'artifacts delete'
],
load: async () => (await import('./handlers/artifacts.js')).ARTIFACT_HANDLERS
},
{
name: 'automations',
keys: [
'automations list',
'automations show',
'automations create',
'automations edit',
'automations remove',
'automations run',
'automations runs'
],
load: async () => (await import('./handlers/automations.js')).AUTOMATION_HANDLERS
},
{
name: 'project',
keys: [
'project list',
'project setups',
'project setup-existing-folder',
'project setup-clone',
'project setup-create',
'project setup-update',
'project setup-delete'
],
load: async () => (await import('./handlers/project.js')).PROJECT_HANDLERS
},
{
name: 'repo',
keys: [
'repo list',
'repo add',
'repo show',
'repo set',
'repo set-base-ref',
'repo search-refs'
],
load: async () => (await import('./handlers/repo.js')).REPO_HANDLERS
},
{
name: 'worktree',
keys: [
'worktree ps',
'worktree list',
'worktree show',
'worktree current',
'worktree create',
'worktree set',
'worktree rm'
],
load: async () => (await import('./handlers/worktree.js')).WORKTREE_HANDLERS
},
{
name: 'file',
keys: ['file open', 'file diff', 'file open-changed'],
load: async () => (await import('./handlers/file.js')).FILE_HANDLERS
},
{
name: 'terminal',
keys: [
'terminal list',
'terminal show',
'terminal read',
'terminal send',
'terminal wait',
'terminal stop',
'terminal rename',
'terminal set-pane-title',
'terminal create',
'terminal switch',
'terminal close',
'terminal split'
],
load: async () => (await import('./handlers/terminal.js')).TERMINAL_HANDLERS
},
...BROWSER_HANDLER_GROUPS,
{
name: 'orchestration',
keys: [
'orchestration run-create',
'orchestration run-use',
'orchestration run-current',
'orchestration run-list',
'orchestration run-show',
'orchestration send',
'orchestration check',
'orchestration reply',
'orchestration inbox',
'orchestration task-create',
'orchestration task-list',
'orchestration task-update',
'orchestration worker-start',
'orchestration worker-show',
'orchestration worker-read',
'orchestration worker-stop',
'orchestration worker-abandon',
'orchestration worker-release',
'orchestration worker-retain',
'orchestration worker-list',
'orchestration dispatch',
'orchestration ask',
'orchestration dispatch-show',
'orchestration coordinator-start',
'orchestration coordinator-stop',
'orchestration request-show',
'orchestration gate-create',
'orchestration gate-resolve',
'orchestration gate-list',
'orchestration reset'
],
load: async () => (await import('./handlers/orchestration.js')).ORCHESTRATION_HANDLERS
},
{
name: 'emulator',
keys: [
'emulator list',
'emulator devices',
'emulator attach',
'emulator tap',
'emulator type',
'emulator gesture',
'emulator button',
'emulator rotate',
'emulator exec',
'emulator kill',
'emulator shutdown',
'emulator install',
'emulator launch',
'emulator permissions',
'emulator ax',
'emulator logcat'
],
load: async () => (await import('./handlers/emulator.js')).EMULATOR_HANDLERS
},
{
name: 'computer',
keys: [
'computer capabilities',
'computer list-apps',
'computer permissions',
'computer list-windows',
'computer get-app-state',
'computer click',
'computer perform-secondary-action',
'computer scroll',
'computer drag',
'computer type-text',
'computer press-key',
'computer hotkey',
'computer paste-text',
'computer set-value'
],
load: async () => (await import('./handlers/computer.js')).COMPUTER_HANDLERS
},
{
name: 'agent-hooks',
keys: ['agent hooks prepare-codex', 'agent hooks status', 'agent hooks off', 'agent hooks on'],
load: async () => (await import('./handlers/agent-hooks.js')).AGENT_HOOK_HANDLERS
},
{
name: 'agent-launch',
keys: ['agent launch'],
load: async () => (await import('./handlers/agent-launch.js')).AGENT_LAUNCH_HANDLERS
},
{
name: 'connector',
keys: [
'connector start',
'connector send',
'connector prompt',
'connector answer',
'connector cancel',
'connector watch'
],
load: async () => (await import('./handlers/connector.js')).CONNECTOR_HANDLERS
},
{
name: 'profile-state',
keys: ['profile state exports', 'profile state rollback'],
load: async () => (await import('./handlers/profile-state.js')).PROFILE_STATE_HANDLERS
},
{
name: 'diagnostics',
keys: ['diagnostics memory', 'terminal-daemon status', 'terminal-daemon restart'],
load: async () => (await import('./handlers/diagnostics.js')).DIAGNOSTICS_HANDLERS
},
{
name: 'speech',
keys: ['speech openai-key status', 'speech openai-key set', 'speech openai-key clear'],
load: async () => (await import('./handlers/speech.js')).SPEECH_HANDLERS
},
{
name: 'introspection',
keys: ['agent-context'],
load: async () => (await import('./handlers/introspection.js')).INTROSPECTION_HANDLERS
},
{
name: 'environment',
keys: [
'host name',
'host list',
'environment add',
'environment list',
'environment show',
'environment rm'
],
load: async () => (await import('./handlers/environment.js')).ENVIRONMENT_HANDLERS
},
{
name: 'managed-server',
keys: [
'environment status',
'environment update',
'environment rollback',
'environment recover',
'environment stop',
'environment cancel-stop',
'environment forget'
],
load: async () => (await import('./handlers/managed-server.js')).MANAGED_SERVER_HANDLERS
},
{
name: 'vm',
keys: ['vm recipe doctor'],
load: async () => (await import('./handlers/vm.js')).VM_HANDLERS
},
{
name: 'skill-sharing',
keys: ['skills installed', 'skills share'],
load: async () => (await import('./handlers/skill-sharing.js')).SKILL_SHARING_HANDLERS
},
{
name: 'skills',
keys: ['skills list', 'skills get', 'skills install', 'skills update'],
load: async () => (await import('./handlers/skills.js')).SKILL_HANDLERS
},
{
name: 'search',
keys: ['search'],
load: async () => (await import('./handlers/search.js')).SEARCH_HANDLERS
},
{
name: 'ports',
keys: ['ports'],
load: async () => (await import('./handlers/ports.js')).PORTS_HANDLERS
}
]