@jmoyers/harness 0.1.10 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -35
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3721
- package/scripts/control-plane-daemon.ts +24 -2
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3007
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/default-gateway-pointer.ts +193 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +361 -10
- package/src/config/harness-paths.ts +4 -7
- package/src/config/harness-runtime-migration.ts +142 -19
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/config/secrets-core.ts +92 -4
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/prompt/thread-title-namer.ts +49 -23
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-background.ts +18 -2
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +943 -80
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/conversations.ts +11 -7
- package/src/domain/workspace.ts +76 -4
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +22 -112
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-parsing.ts +16 -0
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +106 -8
- package/src/mux/live-mux/modal-overlays.ts +210 -31
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +19 -82
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +82 -30
- package/src/services/runtime-conversation-starter.ts +80 -48
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -70
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +396 -56
- package/src/store/event-store.ts +397 -3
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -189
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -119
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -278
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -238
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function readCliValue(argv: readonly string[], index: number, flag: string): string {
|
|
2
|
+
const value = argv[index + 1];
|
|
3
|
+
if (value === undefined) {
|
|
4
|
+
throw new Error(`missing value for ${flag}`);
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parsePortFlag(value: string, flag: string): number {
|
|
10
|
+
const parsed = Number.parseInt(value, 10);
|
|
11
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
12
|
+
throw new Error(`invalid ${flag} value: ${value}`);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parsePositiveIntFlag(value: string, flag: string): number {
|
|
18
|
+
const parsed = Number.parseInt(value, 10);
|
|
19
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1) {
|
|
20
|
+
throw new Error(`invalid ${flag} value: ${value}`);
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readCliValue } from './flags.ts';
|
|
2
|
+
|
|
3
|
+
const SESSION_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
4
|
+
|
|
5
|
+
interface ParsedGlobalCliOptions {
|
|
6
|
+
readonly sessionName: string | null;
|
|
7
|
+
readonly argv: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SessionCliParser {
|
|
11
|
+
public constructor() {}
|
|
12
|
+
|
|
13
|
+
public parseSessionName(rawValue: string): string {
|
|
14
|
+
const trimmed = rawValue.trim();
|
|
15
|
+
if (!SESSION_NAME_PATTERN.test(trimmed)) {
|
|
16
|
+
throw new Error(`invalid --session value: ${rawValue}`);
|
|
17
|
+
}
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public parseGlobalCliOptions(argv: readonly string[]): ParsedGlobalCliOptions {
|
|
22
|
+
if (argv.length < 2 || argv[0] !== '--session') {
|
|
23
|
+
return {
|
|
24
|
+
sessionName: null,
|
|
25
|
+
argv,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const sessionName = this.parseSessionName(readCliValue(argv, 0, '--session'));
|
|
29
|
+
return {
|
|
30
|
+
sessionName,
|
|
31
|
+
argv: argv.slice(2),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseSessionName(rawValue: string): string {
|
|
37
|
+
return new SessionCliParser().parseSessionName(rawValue);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseGlobalCliOptions(argv: readonly string[]): ParsedGlobalCliOptions {
|
|
41
|
+
return new SessionCliParser().parseGlobalCliOptions(argv);
|
|
42
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_GATEWAY_DB_PATH,
|
|
5
|
+
resolveGatewayLockPath,
|
|
6
|
+
resolveGatewayLogPath,
|
|
7
|
+
resolveGatewayRecordPath,
|
|
8
|
+
resolveInvocationDirectory,
|
|
9
|
+
} from '../gateway-record.ts';
|
|
10
|
+
import { loadHarnessConfig } from '../../config/config-core.ts';
|
|
11
|
+
import {
|
|
12
|
+
resolveHarnessRuntimePath,
|
|
13
|
+
resolveHarnessWorkspaceDirectory,
|
|
14
|
+
} from '../../config/harness-paths.ts';
|
|
15
|
+
import { migrateLegacyHarnessLayout } from '../../config/harness-runtime-migration.ts';
|
|
16
|
+
import { loadHarnessSecrets } from '../../config/secrets-core.ts';
|
|
17
|
+
import {
|
|
18
|
+
resolveDefaultStatusTimelineOutputPath,
|
|
19
|
+
resolveStatusTimelineStatePath,
|
|
20
|
+
} from '../../mux/live-mux/status-timeline-state.ts';
|
|
21
|
+
import {
|
|
22
|
+
resolveDefaultRenderTraceOutputPath,
|
|
23
|
+
resolveRenderTraceStatePath,
|
|
24
|
+
} from '../../mux/live-mux/render-trace-state.ts';
|
|
25
|
+
|
|
26
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const PROJECT_ROOT = resolve(MODULE_DIR, '../../..');
|
|
28
|
+
|
|
29
|
+
const DEFAULT_DAEMON_SCRIPT_PATH = resolve(PROJECT_ROOT, 'scripts/control-plane-daemon.ts');
|
|
30
|
+
const DEFAULT_MUX_SCRIPT_PATH = resolve(PROJECT_ROOT, 'scripts/harness-core.ts');
|
|
31
|
+
|
|
32
|
+
const DEFAULT_SESSION_ROOT_PATH = 'sessions';
|
|
33
|
+
const DEFAULT_PROFILE_ROOT_PATH = 'profiles';
|
|
34
|
+
|
|
35
|
+
interface RuntimeInspectOptions {
|
|
36
|
+
readonly gatewayRuntimeArgs: readonly string[];
|
|
37
|
+
readonly clientRuntimeArgs: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface HarnessRuntimeContext {
|
|
41
|
+
readonly invocationDirectory: string;
|
|
42
|
+
readonly daemonScriptPath: string;
|
|
43
|
+
readonly muxScriptPath: string;
|
|
44
|
+
readonly runtimeOptions: RuntimeInspectOptions;
|
|
45
|
+
readonly sessionName: string | null;
|
|
46
|
+
readonly gatewayRecordPath: string;
|
|
47
|
+
readonly gatewayLogPath: string;
|
|
48
|
+
readonly gatewayLockPath: string;
|
|
49
|
+
readonly gatewayDefaultStateDbPath: string;
|
|
50
|
+
readonly profileDir: string;
|
|
51
|
+
readonly profileStatePath: string;
|
|
52
|
+
readonly statusTimelineStatePath: string;
|
|
53
|
+
readonly defaultStatusTimelineOutputPath: string;
|
|
54
|
+
readonly renderTraceStatePath: string;
|
|
55
|
+
readonly defaultRenderTraceOutputPath: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveScriptPath(envValue: string | undefined, fallback: string, cwd: string): string {
|
|
59
|
+
if (typeof envValue !== 'string' || envValue.trim().length === 0) {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
const trimmed = envValue.trim();
|
|
63
|
+
if (trimmed.startsWith('/')) {
|
|
64
|
+
return trimmed;
|
|
65
|
+
}
|
|
66
|
+
return resolve(cwd, trimmed);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveInspectRuntimeOptions(
|
|
70
|
+
invocationDirectory: string,
|
|
71
|
+
env: NodeJS.ProcessEnv,
|
|
72
|
+
): RuntimeInspectOptions {
|
|
73
|
+
const loadedConfig = loadHarnessConfig({ cwd: invocationDirectory, env });
|
|
74
|
+
const debugConfig = loadedConfig.config.debug;
|
|
75
|
+
if (!debugConfig.enabled || !debugConfig.inspect.enabled) {
|
|
76
|
+
return {
|
|
77
|
+
gatewayRuntimeArgs: [],
|
|
78
|
+
clientRuntimeArgs: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
gatewayRuntimeArgs: [
|
|
83
|
+
`--inspect=localhost:${String(debugConfig.inspect.gatewayPort)}/harness-gateway`,
|
|
84
|
+
],
|
|
85
|
+
clientRuntimeArgs: [
|
|
86
|
+
`--inspect=localhost:${String(debugConfig.inspect.clientPort)}/harness-client`,
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveSessionPaths(
|
|
92
|
+
invocationDirectory: string,
|
|
93
|
+
sessionName: string | null,
|
|
94
|
+
env: NodeJS.ProcessEnv,
|
|
95
|
+
): Pick<
|
|
96
|
+
HarnessRuntimeContext,
|
|
97
|
+
| 'gatewayRecordPath'
|
|
98
|
+
| 'gatewayLogPath'
|
|
99
|
+
| 'gatewayLockPath'
|
|
100
|
+
| 'gatewayDefaultStateDbPath'
|
|
101
|
+
| 'profileDir'
|
|
102
|
+
| 'profileStatePath'
|
|
103
|
+
| 'statusTimelineStatePath'
|
|
104
|
+
| 'defaultStatusTimelineOutputPath'
|
|
105
|
+
| 'renderTraceStatePath'
|
|
106
|
+
| 'defaultRenderTraceOutputPath'
|
|
107
|
+
> {
|
|
108
|
+
const workspaceDirectory = resolveHarnessWorkspaceDirectory(invocationDirectory, env);
|
|
109
|
+
const statusTimelineStatePath = resolveStatusTimelineStatePath(
|
|
110
|
+
invocationDirectory,
|
|
111
|
+
sessionName,
|
|
112
|
+
env,
|
|
113
|
+
);
|
|
114
|
+
const defaultStatusTimelineOutputPath = resolveDefaultStatusTimelineOutputPath(
|
|
115
|
+
invocationDirectory,
|
|
116
|
+
sessionName,
|
|
117
|
+
env,
|
|
118
|
+
);
|
|
119
|
+
const renderTraceStatePath = resolveRenderTraceStatePath(invocationDirectory, sessionName, env);
|
|
120
|
+
const defaultRenderTraceOutputPath = resolveDefaultRenderTraceOutputPath(
|
|
121
|
+
invocationDirectory,
|
|
122
|
+
sessionName,
|
|
123
|
+
env,
|
|
124
|
+
);
|
|
125
|
+
if (sessionName === null) {
|
|
126
|
+
return {
|
|
127
|
+
gatewayRecordPath: resolveGatewayRecordPath(invocationDirectory, env),
|
|
128
|
+
gatewayLogPath: resolveGatewayLogPath(invocationDirectory, env),
|
|
129
|
+
gatewayLockPath: resolveGatewayLockPath(invocationDirectory, env),
|
|
130
|
+
gatewayDefaultStateDbPath: resolveHarnessRuntimePath(
|
|
131
|
+
invocationDirectory,
|
|
132
|
+
DEFAULT_GATEWAY_DB_PATH,
|
|
133
|
+
env,
|
|
134
|
+
),
|
|
135
|
+
profileDir: resolve(workspaceDirectory, DEFAULT_PROFILE_ROOT_PATH),
|
|
136
|
+
profileStatePath: resolve(workspaceDirectory, 'active-profile.json'),
|
|
137
|
+
statusTimelineStatePath,
|
|
138
|
+
defaultStatusTimelineOutputPath,
|
|
139
|
+
renderTraceStatePath,
|
|
140
|
+
defaultRenderTraceOutputPath,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const sessionRoot = resolve(workspaceDirectory, DEFAULT_SESSION_ROOT_PATH, sessionName);
|
|
144
|
+
return {
|
|
145
|
+
gatewayRecordPath: resolve(sessionRoot, 'gateway.json'),
|
|
146
|
+
gatewayLogPath: resolve(sessionRoot, 'gateway.log'),
|
|
147
|
+
gatewayLockPath: resolve(sessionRoot, 'gateway.lock'),
|
|
148
|
+
gatewayDefaultStateDbPath: resolve(sessionRoot, 'control-plane.sqlite'),
|
|
149
|
+
profileDir: resolve(workspaceDirectory, DEFAULT_PROFILE_ROOT_PATH, sessionName),
|
|
150
|
+
profileStatePath: resolve(sessionRoot, 'active-profile.json'),
|
|
151
|
+
statusTimelineStatePath,
|
|
152
|
+
defaultStatusTimelineOutputPath,
|
|
153
|
+
renderTraceStatePath,
|
|
154
|
+
defaultRenderTraceOutputPath,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class HarnessRuntimeContextFactory {
|
|
159
|
+
constructor(
|
|
160
|
+
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
161
|
+
private readonly cwd: string = process.cwd(),
|
|
162
|
+
private readonly writeStdout: (text: string) => void = (text) => {
|
|
163
|
+
process.stdout.write(text);
|
|
164
|
+
},
|
|
165
|
+
) {}
|
|
166
|
+
|
|
167
|
+
public create(sessionName: string | null): HarnessRuntimeContext {
|
|
168
|
+
const invocationDirectory = resolveInvocationDirectory(this.env, this.cwd);
|
|
169
|
+
const migration = migrateLegacyHarnessLayout(invocationDirectory, this.env);
|
|
170
|
+
if (migration.migrated) {
|
|
171
|
+
this.writeStdout(
|
|
172
|
+
`[migration] local .harness migrated to global runtime layout (${String(migration.migratedEntries)} entries, configCopied=${String(migration.configCopied)}, secretsCopied=${String(migration.secretsCopied)}, legacyRootRemoved=${String(migration.legacyRootRemoved)})\n`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
loadHarnessSecrets({ cwd: invocationDirectory, env: this.env });
|
|
176
|
+
return {
|
|
177
|
+
invocationDirectory,
|
|
178
|
+
daemonScriptPath: resolveScriptPath(
|
|
179
|
+
this.env.HARNESS_DAEMON_SCRIPT_PATH,
|
|
180
|
+
DEFAULT_DAEMON_SCRIPT_PATH,
|
|
181
|
+
invocationDirectory,
|
|
182
|
+
),
|
|
183
|
+
muxScriptPath: resolveScriptPath(
|
|
184
|
+
this.env.HARNESS_MUX_SCRIPT_PATH,
|
|
185
|
+
DEFAULT_MUX_SCRIPT_PATH,
|
|
186
|
+
invocationDirectory,
|
|
187
|
+
),
|
|
188
|
+
runtimeOptions: resolveInspectRuntimeOptions(invocationDirectory, this.env),
|
|
189
|
+
sessionName,
|
|
190
|
+
...resolveSessionPaths(invocationDirectory, sessionName, this.env),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { diffUiUsage, runDiffUiCli } from '../../diff-ui/index.ts';
|
|
5
|
+
import {
|
|
6
|
+
buildCursorManagedHookRelayCommand,
|
|
7
|
+
ensureManagedCursorHooksInstalled,
|
|
8
|
+
uninstallManagedCursorHooks,
|
|
9
|
+
} from '../../cursor/managed-hooks.ts';
|
|
10
|
+
import { AuthRuntimeService } from '../auth/runtime.ts';
|
|
11
|
+
import { GatewayRuntimeService } from '../gateway/runtime.ts';
|
|
12
|
+
import { readCliValue } from '../parsing/flags.ts';
|
|
13
|
+
import { HarnessRuntimeContextFactory, type HarnessRuntimeContext } from '../runtime/context.ts';
|
|
14
|
+
import { WorkflowRuntimeService } from '../workflows/runtime.ts';
|
|
15
|
+
|
|
16
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PROJECT_ROOT = resolve(MODULE_DIR, '../../..');
|
|
18
|
+
const DEFAULT_CURSOR_HOOK_RELAY_SCRIPT_PATH = resolve(PROJECT_ROOT, 'scripts/cursor-hook-relay.ts');
|
|
19
|
+
const DEFAULT_HARNESS_UPDATE_PACKAGE = '@jmoyers/harness@latest';
|
|
20
|
+
|
|
21
|
+
type ExecFileSyncFn = (
|
|
22
|
+
file: string,
|
|
23
|
+
args: readonly string[],
|
|
24
|
+
options: {
|
|
25
|
+
cwd: string;
|
|
26
|
+
env: NodeJS.ProcessEnv;
|
|
27
|
+
encoding: 'utf8';
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'];
|
|
29
|
+
},
|
|
30
|
+
) => string;
|
|
31
|
+
type BuildCursorManagedHookRelayCommandFn = typeof buildCursorManagedHookRelayCommand;
|
|
32
|
+
type EnsureManagedCursorHooksInstalledFn = typeof ensureManagedCursorHooksInstalled;
|
|
33
|
+
type UninstallManagedCursorHooksFn = typeof uninstallManagedCursorHooks;
|
|
34
|
+
type DiffUiUsageFn = typeof diffUiUsage;
|
|
35
|
+
type RunDiffUiCliFn = (deps: {
|
|
36
|
+
argv: readonly string[];
|
|
37
|
+
cwd: string;
|
|
38
|
+
env: NodeJS.ProcessEnv;
|
|
39
|
+
}) => Promise<{ exitCode: number }>;
|
|
40
|
+
|
|
41
|
+
interface ParsedCursorHooksCommand {
|
|
42
|
+
readonly type: 'install' | 'uninstall';
|
|
43
|
+
readonly hooksFilePath: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface RuntimeServices {
|
|
47
|
+
readonly runtime: HarnessRuntimeContext;
|
|
48
|
+
readonly authRuntime: AuthRuntimeService;
|
|
49
|
+
readonly gatewayRuntime: GatewayRuntimeService;
|
|
50
|
+
readonly workflowRuntime: WorkflowRuntimeService;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface HarnessRuntimeContextProvider {
|
|
54
|
+
create(sessionName: string | null): HarnessRuntimeContext;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveScriptPath(envValue: string | undefined, fallback: string, cwd: string): string {
|
|
58
|
+
if (typeof envValue !== 'string' || envValue.trim().length === 0) {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
const trimmed = envValue.trim();
|
|
62
|
+
return isAbsolute(trimmed) ? trimmed : resolve(cwd, trimmed);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function harnessRuntimeUsageText(): string {
|
|
66
|
+
return [
|
|
67
|
+
'usage:',
|
|
68
|
+
' harness [--session <name>] [mux-args...]',
|
|
69
|
+
' harness [--session <name>] gateway start [--host <host>] [--port <port>] [--auth-token <token>] [--state-db-path <path>]',
|
|
70
|
+
' harness [--session <name>] gateway run [--host <host>] [--port <port>] [--auth-token <token>] [--state-db-path <path>]',
|
|
71
|
+
' harness [--session <name>] gateway stop [--force] [--timeout-ms <ms>] [--cleanup-orphans|--no-cleanup-orphans]',
|
|
72
|
+
' harness [--session <name>] gateway status',
|
|
73
|
+
' harness gateway list',
|
|
74
|
+
' harness [--session <name>] gateway restart [--host <host>] [--port <port>] [--auth-token <token>] [--state-db-path <path>]',
|
|
75
|
+
' harness [--session <name>] gateway call --json \'{"type":"session.list"}\'',
|
|
76
|
+
' harness [--session <name>] gateway gc [--older-than-days <days>]',
|
|
77
|
+
' harness [--session <name>] profile start [--profile-dir <path>]',
|
|
78
|
+
' harness [--session <name>] profile stop [--timeout-ms <ms>]',
|
|
79
|
+
' harness [--session <name>] profile run [--profile-dir <path>] [mux-args...]',
|
|
80
|
+
' harness [--session <name>] profile [--profile-dir <path>] [mux-args...]',
|
|
81
|
+
' harness [--session <name>] status-timeline start [--output-path <path>]',
|
|
82
|
+
' harness [--session <name>] status-timeline stop',
|
|
83
|
+
' harness [--session <name>] status-timeline [--output-path <path>]',
|
|
84
|
+
' harness [--session <name>] render-trace start [--output-path <path>] [--conversation-id <id>]',
|
|
85
|
+
' harness [--session <name>] render-trace stop',
|
|
86
|
+
' harness [--session <name>] render-trace [--output-path <path>] [--conversation-id <id>]',
|
|
87
|
+
' harness update',
|
|
88
|
+
' harness upgrade',
|
|
89
|
+
' harness auth status',
|
|
90
|
+
' harness auth login <github|linear> [--no-browser] [--timeout-ms <ms>] [--scopes <list>] [--callback-port <port>]',
|
|
91
|
+
' harness auth refresh [github|linear|all]',
|
|
92
|
+
' harness auth logout [github|linear|all]',
|
|
93
|
+
' harness cursor-hooks install [--hooks-file <path>]',
|
|
94
|
+
' harness cursor-hooks uninstall [--hooks-file <path>]',
|
|
95
|
+
' harness animate [--fps <fps>] [--frames <count>] [--duration-ms <ms>] [--seed <seed>] [--no-color]',
|
|
96
|
+
'',
|
|
97
|
+
'session naming:',
|
|
98
|
+
' --session accepts [A-Za-z0-9][A-Za-z0-9._-]{0,63} and isolates gateway record/log/db paths.',
|
|
99
|
+
].join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class CursorHooksCommandParser {
|
|
103
|
+
public constructor() {}
|
|
104
|
+
|
|
105
|
+
private parseOptions(argv: readonly string[]): { hooksFilePath: string | null } {
|
|
106
|
+
let hooksFilePath: string | null = null;
|
|
107
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
108
|
+
const arg = argv[index]!;
|
|
109
|
+
if (arg === '--hooks-file') {
|
|
110
|
+
hooksFilePath = readCliValue(argv, index, '--hooks-file');
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`unknown cursor-hooks option: ${arg}`);
|
|
115
|
+
}
|
|
116
|
+
return { hooksFilePath };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public parse(argv: readonly string[]): ParsedCursorHooksCommand {
|
|
120
|
+
if (argv.length === 0) {
|
|
121
|
+
throw new Error('missing cursor-hooks subcommand');
|
|
122
|
+
}
|
|
123
|
+
const subcommand = argv[0]!;
|
|
124
|
+
const options = this.parseOptions(argv.slice(1));
|
|
125
|
+
if (subcommand === 'install') {
|
|
126
|
+
return { type: 'install', hooksFilePath: options.hooksFilePath };
|
|
127
|
+
}
|
|
128
|
+
if (subcommand === 'uninstall') {
|
|
129
|
+
return { type: 'uninstall', hooksFilePath: options.hooksFilePath };
|
|
130
|
+
}
|
|
131
|
+
throw new Error(`unknown cursor-hooks subcommand: ${subcommand}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class HarnessRuntimeScopeFactory {
|
|
136
|
+
constructor(
|
|
137
|
+
private readonly contextFactory: HarnessRuntimeContextProvider = new HarnessRuntimeContextFactory(),
|
|
138
|
+
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
139
|
+
) {}
|
|
140
|
+
|
|
141
|
+
public create(sessionName: string | null): RuntimeServices {
|
|
142
|
+
const runtime = this.contextFactory.create(sessionName);
|
|
143
|
+
const authRuntime = new AuthRuntimeService(runtime.invocationDirectory, this.env);
|
|
144
|
+
const gatewayRuntime = new GatewayRuntimeService({
|
|
145
|
+
invocationDirectory: runtime.invocationDirectory,
|
|
146
|
+
sessionName: runtime.sessionName,
|
|
147
|
+
daemonScriptPath: runtime.daemonScriptPath,
|
|
148
|
+
muxScriptPath: runtime.muxScriptPath,
|
|
149
|
+
gatewayRecordPath: runtime.gatewayRecordPath,
|
|
150
|
+
gatewayLogPath: runtime.gatewayLogPath,
|
|
151
|
+
gatewayLockPath: runtime.gatewayLockPath,
|
|
152
|
+
gatewayDefaultStateDbPath: runtime.gatewayDefaultStateDbPath,
|
|
153
|
+
runtimeOptions: runtime.runtimeOptions,
|
|
154
|
+
authRuntime,
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
runtime,
|
|
158
|
+
authRuntime,
|
|
159
|
+
gatewayRuntime,
|
|
160
|
+
workflowRuntime: new WorkflowRuntimeService(runtime, gatewayRuntime),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class HarnessUpdateInstaller {
|
|
166
|
+
constructor(
|
|
167
|
+
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
168
|
+
private readonly writeStdout: (text: string) => void = (text) => {
|
|
169
|
+
process.stdout.write(text);
|
|
170
|
+
},
|
|
171
|
+
private readonly writeStderr: (text: string) => void = (text) => {
|
|
172
|
+
process.stderr.write(text);
|
|
173
|
+
},
|
|
174
|
+
private readonly execFile: ExecFileSyncFn = (file, args, options) =>
|
|
175
|
+
execFileSync(file, args, options) as string,
|
|
176
|
+
) {}
|
|
177
|
+
|
|
178
|
+
private resolvePackageSpec(): string {
|
|
179
|
+
const configured = this.env.HARNESS_UPDATE_PACKAGE;
|
|
180
|
+
if (typeof configured !== 'string') {
|
|
181
|
+
return DEFAULT_HARNESS_UPDATE_PACKAGE;
|
|
182
|
+
}
|
|
183
|
+
const trimmed = configured.trim();
|
|
184
|
+
return trimmed.length > 0 ? trimmed : DEFAULT_HARNESS_UPDATE_PACKAGE;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private formatExecErrorOutput(value: unknown): string {
|
|
188
|
+
if (typeof value === 'string') {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
if (value instanceof Buffer) {
|
|
192
|
+
return value.toString('utf8');
|
|
193
|
+
}
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public run(invocationDirectory: string, argv: readonly string[]): number {
|
|
198
|
+
if (argv.length > 0) {
|
|
199
|
+
throw new Error(`unknown update option: ${argv[0]}`);
|
|
200
|
+
}
|
|
201
|
+
const packageSpec = this.resolvePackageSpec();
|
|
202
|
+
this.writeStdout(`updating Harness package: ${packageSpec}\n`);
|
|
203
|
+
try {
|
|
204
|
+
const stdout = this.execFile('bun', ['add', '-g', '--trust', packageSpec], {
|
|
205
|
+
cwd: invocationDirectory,
|
|
206
|
+
env: this.env,
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
209
|
+
});
|
|
210
|
+
if (stdout.length > 0) {
|
|
211
|
+
this.writeStdout(stdout);
|
|
212
|
+
}
|
|
213
|
+
this.writeStdout(`harness update complete: ${packageSpec}\n`);
|
|
214
|
+
return 0;
|
|
215
|
+
} catch (error: unknown) {
|
|
216
|
+
const typed = error as NodeJS.ErrnoException & {
|
|
217
|
+
readonly stdout?: unknown;
|
|
218
|
+
readonly stderr?: unknown;
|
|
219
|
+
readonly status?: number | null;
|
|
220
|
+
};
|
|
221
|
+
const stdout = this.formatExecErrorOutput(typed.stdout);
|
|
222
|
+
const stderr = this.formatExecErrorOutput(typed.stderr);
|
|
223
|
+
if (stdout.length > 0) {
|
|
224
|
+
this.writeStdout(stdout);
|
|
225
|
+
}
|
|
226
|
+
if (stderr.length > 0) {
|
|
227
|
+
this.writeStderr(stderr);
|
|
228
|
+
}
|
|
229
|
+
const statusText =
|
|
230
|
+
typeof typed.status === 'number' ? `exit=${String(typed.status)}` : 'exit=unknown';
|
|
231
|
+
throw new Error(`harness update command failed (${statusText})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export class CursorHooksCliRunner {
|
|
237
|
+
private readonly parser = new CursorHooksCommandParser();
|
|
238
|
+
|
|
239
|
+
constructor(
|
|
240
|
+
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
241
|
+
private readonly writeStdout: (text: string) => void = (text) => {
|
|
242
|
+
process.stdout.write(text);
|
|
243
|
+
},
|
|
244
|
+
private readonly buildRelayCommand: BuildCursorManagedHookRelayCommandFn = buildCursorManagedHookRelayCommand,
|
|
245
|
+
private readonly installManagedHooks: EnsureManagedCursorHooksInstalledFn = ensureManagedCursorHooksInstalled,
|
|
246
|
+
private readonly uninstallManagedHooks: UninstallManagedCursorHooksFn = uninstallManagedCursorHooks,
|
|
247
|
+
) {}
|
|
248
|
+
|
|
249
|
+
public run(invocationDirectory: string, argv: readonly string[]): number {
|
|
250
|
+
const command = this.parser.parse(argv);
|
|
251
|
+
const hooksFilePath =
|
|
252
|
+
command.hooksFilePath === null
|
|
253
|
+
? undefined
|
|
254
|
+
: resolve(invocationDirectory, command.hooksFilePath);
|
|
255
|
+
if (command.type === 'install') {
|
|
256
|
+
const relayScriptPath = resolveScriptPath(
|
|
257
|
+
this.env.HARNESS_CURSOR_HOOK_RELAY_SCRIPT_PATH,
|
|
258
|
+
DEFAULT_CURSOR_HOOK_RELAY_SCRIPT_PATH,
|
|
259
|
+
invocationDirectory,
|
|
260
|
+
);
|
|
261
|
+
const result = this.installManagedHooks({
|
|
262
|
+
relayCommand: this.buildRelayCommand(relayScriptPath),
|
|
263
|
+
...(hooksFilePath === undefined ? {} : { hooksFilePath }),
|
|
264
|
+
});
|
|
265
|
+
this.writeStdout(
|
|
266
|
+
`cursor hooks install: ${result.changed ? 'updated' : 'already up-to-date'} file=${result.filePath} removed=${String(result.removedCount)} added=${String(result.addedCount)}\n`,
|
|
267
|
+
);
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
const result = this.uninstallManagedHooks(hooksFilePath === undefined ? {} : { hooksFilePath });
|
|
271
|
+
this.writeStdout(
|
|
272
|
+
`cursor hooks uninstall: ${result.changed ? 'updated' : 'no changes'} file=${result.filePath} removed=${String(result.removedCount)}\n`,
|
|
273
|
+
);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export class DiffCliRunner {
|
|
279
|
+
constructor(
|
|
280
|
+
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
281
|
+
private readonly cwd: string = process.cwd(),
|
|
282
|
+
private readonly writeStdout: (text: string) => void = (text) => {
|
|
283
|
+
process.stdout.write(text);
|
|
284
|
+
},
|
|
285
|
+
private readonly usage: DiffUiUsageFn = diffUiUsage,
|
|
286
|
+
private readonly runDiffCli: RunDiffUiCliFn = runDiffUiCli,
|
|
287
|
+
) {}
|
|
288
|
+
|
|
289
|
+
public async run(argv: readonly string[]): Promise<number> {
|
|
290
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
291
|
+
this.writeStdout(`${this.usage()}\n`);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
const result = await this.runDiffCli({
|
|
295
|
+
argv,
|
|
296
|
+
cwd: this.cwd,
|
|
297
|
+
env: this.env,
|
|
298
|
+
});
|
|
299
|
+
return result.exitCode;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export class HarnessRuntimeApplication {
|
|
304
|
+
constructor(
|
|
305
|
+
private readonly scopeFactory: HarnessRuntimeScopeFactory = new HarnessRuntimeScopeFactory(),
|
|
306
|
+
private readonly updateInstaller: HarnessUpdateInstaller = new HarnessUpdateInstaller(),
|
|
307
|
+
private readonly cursorHooksRunner: CursorHooksCliRunner = new CursorHooksCliRunner(),
|
|
308
|
+
private readonly diffCliRunner: DiffCliRunner = new DiffCliRunner(),
|
|
309
|
+
private readonly writeStdout: (text: string) => void = (text) => {
|
|
310
|
+
process.stdout.write(text);
|
|
311
|
+
},
|
|
312
|
+
) {}
|
|
313
|
+
|
|
314
|
+
private printUsage(): void {
|
|
315
|
+
this.writeStdout(`${harnessRuntimeUsageText()}\n`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private shouldPrintUsage(argv: readonly string[]): boolean {
|
|
319
|
+
return argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public async runGatewayCli(args: readonly string[], sessionName: string | null): Promise<number> {
|
|
323
|
+
const services = this.scopeFactory.create(sessionName);
|
|
324
|
+
const command = services.gatewayRuntime.parseCommand(args);
|
|
325
|
+
return await services.gatewayRuntime.run(command);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public async runProfileCli(args: readonly string[], sessionName: string | null): Promise<number> {
|
|
329
|
+
if (this.shouldPrintUsage(args)) {
|
|
330
|
+
this.printUsage();
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
const services = this.scopeFactory.create(sessionName);
|
|
334
|
+
return await services.workflowRuntime.runProfileCli(args);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public async runStatusTimelineCli(
|
|
338
|
+
args: readonly string[],
|
|
339
|
+
sessionName: string | null,
|
|
340
|
+
): Promise<number> {
|
|
341
|
+
if (this.shouldPrintUsage(args)) {
|
|
342
|
+
this.printUsage();
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
const services = this.scopeFactory.create(sessionName);
|
|
346
|
+
return await services.workflowRuntime.runStatusTimelineCli(args);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public async runRenderTraceCli(
|
|
350
|
+
args: readonly string[],
|
|
351
|
+
sessionName: string | null,
|
|
352
|
+
): Promise<number> {
|
|
353
|
+
if (this.shouldPrintUsage(args)) {
|
|
354
|
+
this.printUsage();
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
const services = this.scopeFactory.create(sessionName);
|
|
358
|
+
return await services.workflowRuntime.runRenderTraceCli(args);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public async runAuthCli(args: readonly string[], sessionName: string | null): Promise<number> {
|
|
362
|
+
const services = this.scopeFactory.create(sessionName);
|
|
363
|
+
return await services.authRuntime.run(args);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
public runUpdateCli(args: readonly string[], sessionName: string | null): number {
|
|
367
|
+
const services = this.scopeFactory.create(sessionName);
|
|
368
|
+
return this.updateInstaller.run(services.runtime.invocationDirectory, args);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
public async runCursorHooksCli(
|
|
372
|
+
args: readonly string[],
|
|
373
|
+
sessionName: string | null,
|
|
374
|
+
): Promise<number> {
|
|
375
|
+
const services = this.scopeFactory.create(sessionName);
|
|
376
|
+
return this.cursorHooksRunner.run(services.runtime.invocationDirectory, args);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public async runClientCli(args: readonly string[], sessionName: string | null): Promise<number> {
|
|
380
|
+
const services = this.scopeFactory.create(sessionName);
|
|
381
|
+
return await services.workflowRuntime.runDefaultClient(args);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
public async runDiffCli(args: readonly string[], _sessionName: string | null): Promise<number> {
|
|
385
|
+
void _sessionName;
|
|
386
|
+
return await this.diffCliRunner.run(args);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function createDefaultHarnessRuntimeApplication(): HarnessRuntimeApplication {
|
|
391
|
+
return new HarnessRuntimeApplication();
|
|
392
|
+
}
|