@the-open-engine/zeroshot 6.38.0 → 6.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/event-copy.js +8 -7
- package/cli/event-copy.ts +12 -0
- package/cli/index.js +7 -31
- package/cli/json-export.js +61 -64
- package/cli/json-export.ts +112 -0
- package/cli/message-formatter-utils.js +68 -56
- package/cli/message-formatter-utils.ts +121 -0
- package/cli/message-formatters-normal.js +124 -259
- package/cli/message-formatters-normal.ts +250 -0
- package/cli/message-formatters-watch.js +81 -84
- package/cli/message-formatters-watch.ts +189 -0
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +3 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/cluster/generated/protocol-schema.cjs +1 -1
- package/lib/cluster/generated/protocol-schema.mjs +1 -1
- package/lib/cluster/generated/protocol.d.cts +8 -0
- package/lib/cluster/generated/protocol.d.mts +8 -0
- package/lib/cluster/generated/protocol.d.ts +8 -0
- package/lib/clusters-registry.d.ts +23 -0
- package/lib/clusters-registry.js +17 -29
- package/lib/completion.d.ts +12 -0
- package/lib/completion.js +84 -94
- package/lib/compose-utils.d.ts +32 -0
- package/lib/compose-utils.js +57 -72
- package/lib/detached-startup.d.ts +83 -0
- package/lib/detached-startup.js +231 -302
- package/lib/docker-config.d.ts +43 -0
- package/lib/docker-config.js +150 -325
- package/lib/docker-env-auth.d.ts +17 -0
- package/lib/docker-env-auth.js +75 -0
- package/lib/git-remote-utils.d.ts +48 -0
- package/lib/git-remote-utils.js +147 -213
- package/lib/id-detector.d.ts +15 -0
- package/lib/id-detector.js +37 -41
- package/lib/path-check.d.ts +30 -0
- package/lib/path-check.js +42 -45
- package/lib/process-liveness.d.ts +16 -0
- package/lib/process-liveness.js +19 -12
- package/lib/provider-credential-path.d.ts +16 -0
- package/lib/provider-credential-path.js +31 -29
- package/lib/provider-defaults.d.ts +30 -0
- package/lib/provider-defaults.js +47 -42
- package/lib/provider-detection.d.ts +11 -0
- package/lib/provider-detection.js +91 -54
- package/lib/provider-names.d.ts +38 -0
- package/lib/provider-names.js +56 -64
- package/lib/repo-settings.d.ts +28 -0
- package/lib/repo-settings.js +83 -59
- package/lib/run-mode.d.ts +21 -0
- package/lib/run-mode.js +21 -20
- package/lib/run-plan.d.ts +28 -0
- package/lib/run-plan.js +17 -16
- package/lib/settings/claude-auth.d.ts +35 -0
- package/lib/settings/claude-auth.js +30 -47
- package/lib/settings-defaults.d.ts +20 -0
- package/lib/settings-defaults.js +149 -0
- package/lib/settings-error.d.ts +7 -0
- package/lib/settings-error.js +8 -0
- package/lib/settings-issue-providers.d.ts +10 -0
- package/lib/settings-issue-providers.js +12 -0
- package/lib/settings-models.d.ts +22 -0
- package/lib/settings-models.js +93 -0
- package/lib/settings-storage.d.ts +23 -0
- package/lib/settings-storage.js +205 -0
- package/lib/settings-validation.d.ts +7 -0
- package/lib/settings-validation.js +182 -0
- package/lib/settings.d.ts +29 -0
- package/lib/settings.js +41 -736
- package/lib/setup-apply-types.d.ts +66 -0
- package/lib/setup-apply-types.js +2 -0
- package/lib/setup-apply.d.ts +39 -0
- package/lib/setup-apply.js +267 -264
- package/lib/setup-journal.d.ts +41 -0
- package/lib/setup-journal.js +119 -76
- package/lib/setup-plan-types.d.ts +102 -0
- package/lib/setup-plan-types.js +2 -0
- package/lib/setup-plan.d.ts +30 -0
- package/lib/setup-plan.js +300 -324
- package/lib/setup-undo.d.ts +44 -0
- package/lib/setup-undo.js +61 -74
- package/lib/start-cluster-config.d.ts +26 -0
- package/lib/start-cluster-config.js +56 -0
- package/lib/start-cluster-environment.d.ts +40 -0
- package/lib/start-cluster-environment.js +134 -0
- package/lib/start-cluster-run-options.d.ts +53 -0
- package/lib/start-cluster-run-options.js +147 -0
- package/lib/start-cluster.d.ts +64 -0
- package/lib/start-cluster.js +98 -454
- package/lib/stream-json-parser.d.ts +17 -0
- package/lib/stream-json-parser.js +51 -52
- package/npm-shrinkwrap.json +2 -2
- package/package.json +12 -6
- package/scripts/postinstall.js +27 -0
- package/scripts/rust-distribution.js +23 -14
- package/src/agent/agent-command-proofs-context.js +35 -45
- package/src/agent/agent-command-proofs-context.ts +87 -0
- package/src/agent/agent-config-settings.js +19 -0
- package/src/agent/agent-config-settings.ts +41 -0
- package/src/agent/agent-config.js +122 -231
- package/src/agent/agent-config.ts +285 -0
- package/src/agent/agent-context-builder.js +38 -273
- package/src/agent/agent-context-builder.ts +51 -0
- package/src/agent/agent-context-environment-sections.js +105 -0
- package/src/agent/agent-context-environment-sections.ts +134 -0
- package/src/agent/agent-context-packs.js +165 -0
- package/src/agent/agent-context-packs.ts +296 -0
- package/src/agent/agent-context-prompt-sections.js +100 -0
- package/src/agent/agent-context-prompt-sections.ts +148 -0
- package/src/agent/agent-context-sections.js +25 -334
- package/src/agent/agent-context-sections.ts +36 -0
- package/src/agent/agent-context-sources.js +96 -149
- package/src/agent/agent-context-sources.ts +253 -0
- package/src/agent/agent-context-types.js +2 -0
- package/src/agent/agent-context-types.ts +132 -0
- package/src/agent/agent-context-validation-sections.js +73 -0
- package/src/agent/agent-context-validation-sections.ts +129 -0
- package/src/agent/agent-input-injector.js +99 -118
- package/src/agent/agent-input-injector.ts +195 -0
- package/src/agent/agent-liveness-poll.js +74 -78
- package/src/agent/agent-liveness-poll.ts +163 -0
- package/src/agent/agent-quality-gate-schema.js +77 -86
- package/src/agent/agent-quality-gate-schema.ts +135 -0
- package/src/agent/agent-quality-gates-context.js +44 -50
- package/src/agent/agent-quality-gates-context.ts +91 -0
- package/src/agent/agent-stuck-detector.js +171 -239
- package/src/agent/agent-stuck-detector.ts +288 -0
- package/src/agent/agent-trigger-evaluator.js +29 -46
- package/src/agent/agent-trigger-evaluator.ts +98 -0
- package/src/agent/context-metrics.js +110 -141
- package/src/agent/context-metrics.ts +261 -0
- package/src/agent/context-pack-builder.js +72 -358
- package/src/agent/context-pack-builder.ts +105 -0
- package/src/agent/context-pack-core.js +70 -0
- package/src/agent/context-pack-core.ts +96 -0
- package/src/agent/context-pack-selection.js +119 -0
- package/src/agent/context-pack-selection.ts +187 -0
- package/src/agent/context-pack-truncation.js +139 -0
- package/src/agent/context-pack-truncation.ts +208 -0
- package/src/agent/context-pack-types.js +2 -0
- package/src/agent/context-pack-types.ts +98 -0
- package/src/agent/context-replay-policy.js +30 -39
- package/src/agent/context-replay-policy.ts +71 -0
- package/src/agent/critical-agent-policy.js +6 -10
- package/src/agent/critical-agent-policy.ts +20 -0
- package/src/agent/guidance-queue.js +74 -87
- package/src/agent/guidance-queue.ts +166 -0
- package/src/agent/output-extraction-error-detail.js +45 -0
- package/src/agent/output-extraction-error-detail.ts +49 -0
- package/src/agent/output-extraction-failures.js +125 -0
- package/src/agent/output-extraction-failures.ts +149 -0
- package/src/agent/output-extraction-json.js +234 -0
- package/src/agent/output-extraction-json.ts +235 -0
- package/src/agent/output-extraction-pi.js +124 -0
- package/src/agent/output-extraction-pi.ts +137 -0
- package/src/agent/output-extraction-types.js +2 -0
- package/src/agent/output-extraction-types.ts +49 -0
- package/src/agent/output-extraction.js +17 -617
- package/src/agent/output-extraction.ts +30 -0
- package/src/agent/output-reformatter-errors.js +83 -0
- package/src/agent/output-reformatter-errors.ts +106 -0
- package/src/agent/output-reformatter-types.js +2 -0
- package/src/agent/output-reformatter-types.ts +86 -0
- package/src/agent/output-reformatter.js +140 -185
- package/src/agent/output-reformatter.ts +238 -0
- package/src/agent/pi-terminal-lifecycle-types.js +2 -0
- package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
- package/src/agent/pi-terminal-lifecycle.js +186 -206
- package/src/agent/pi-terminal-lifecycle.ts +297 -0
- package/src/agent/provider-control-plane.js +22 -21
- package/src/agent/provider-control-plane.ts +52 -0
- package/src/agent/provider-terminal-failure-types.js +2 -0
- package/src/agent/provider-terminal-failure-types.ts +60 -0
- package/src/agent/provider-terminal-failure.js +194 -191
- package/src/agent/provider-terminal-failure.ts +262 -0
- package/src/agent/rate-limit-backoff.js +31 -74
- package/src/agent/rate-limit-backoff.ts +71 -0
- package/src/agent/schema-utils.js +85 -125
- package/src/agent/schema-utils.ts +148 -0
- package/src/agent/structured-output-error.js +34 -33
- package/src/agent/structured-output-error.ts +105 -0
- package/src/agent/validation-platform.js +27 -27
- package/src/agent/validation-platform.ts +49 -0
- package/src/agent-cli-provider/adapters/codex.ts +3 -1
- package/src/attach/attach-client.js +232 -405
- package/src/attach/attach-client.ts +300 -0
- package/src/attach/attach-server-cleanup.js +43 -0
- package/src/attach/attach-server-cleanup.ts +39 -0
- package/src/attach/attach-server-clients.js +117 -0
- package/src/attach/attach-server-clients.ts +122 -0
- package/src/attach/attach-server-events.js +38 -0
- package/src/attach/attach-server-events.ts +36 -0
- package/src/attach/attach-server-pty.js +45 -0
- package/src/attach/attach-server-pty.ts +64 -0
- package/src/attach/attach-server-runtime.js +73 -0
- package/src/attach/attach-server-runtime.ts +62 -0
- package/src/attach/attach-server-socket.js +28 -0
- package/src/attach/attach-server-socket.ts +23 -0
- package/src/attach/attach-server-types.js +2 -0
- package/src/attach/attach-server-types.ts +82 -0
- package/src/attach/attach-server.js +106 -523
- package/src/attach/attach-server.ts +158 -0
- package/src/attach/index.js +18 -35
- package/src/attach/index.ts +18 -0
- package/src/attach/protocol.js +95 -184
- package/src/attach/protocol.ts +181 -0
- package/src/attach/ring-buffer.js +76 -102
- package/src/attach/ring-buffer.ts +109 -0
- package/src/attach/send-input.js +64 -75
- package/src/attach/send-input.ts +94 -0
- package/src/attach/socket-discovery.js +164 -218
- package/src/attach/socket-discovery.ts +230 -0
- package/src/attach/socket-paths.js +42 -61
- package/src/attach/socket-paths.ts +81 -0
- package/src/claude-credentials.js +51 -54
- package/src/claude-credentials.ts +89 -0
- package/src/cluster/generated/protocol-schema.ts +1 -1
- package/src/cluster/generated/protocol.ts +2 -1
- package/src/command-proofs.js +110 -141
- package/src/command-proofs.ts +216 -0
- package/src/config-router.js +81 -73
- package/src/config-router.ts +131 -0
- package/src/copy-worker.js +53 -29
- package/src/copy-worker.ts +75 -0
- package/src/darwin-keychain-boundary.js +80 -111
- package/src/darwin-keychain-boundary.ts +148 -0
- package/src/guidance-topics.js +4 -5
- package/src/guidance-topics.ts +10 -0
- package/src/input-helpers.js +33 -54
- package/src/input-helpers.ts +58 -0
- package/src/ledger-sequence.js +45 -48
- package/src/ledger-sequence.ts +63 -0
- package/src/legacy-lib/clusters-registry.ts +40 -0
- package/src/legacy-lib/completion.ts +148 -0
- package/src/legacy-lib/compose-utils.ts +102 -0
- package/src/legacy-lib/detached-startup.ts +449 -0
- package/src/legacy-lib/docker-config.ts +282 -0
- package/src/legacy-lib/docker-env-auth.ts +116 -0
- package/src/legacy-lib/git-remote-utils.ts +244 -0
- package/src/legacy-lib/id-detector.ts +57 -0
- package/src/legacy-lib/js-yaml.d.ts +3 -0
- package/src/legacy-lib/omelette.d.ts +15 -0
- package/src/legacy-lib/path-check.ts +89 -0
- package/src/legacy-lib/process-liveness.ts +36 -0
- package/src/legacy-lib/provider-credential-path.ts +57 -0
- package/src/legacy-lib/provider-defaults.ts +114 -0
- package/src/legacy-lib/provider-detection.ts +67 -0
- package/src/legacy-lib/provider-names.ts +126 -0
- package/src/legacy-lib/repo-settings.ts +88 -0
- package/src/legacy-lib/run-mode.ts +58 -0
- package/src/legacy-lib/run-plan.ts +49 -0
- package/src/legacy-lib/settings/claude-auth.ts +80 -0
- package/src/legacy-lib/settings-defaults.ts +212 -0
- package/src/legacy-lib/settings-error.ts +8 -0
- package/src/legacy-lib/settings-issue-providers.ts +18 -0
- package/src/legacy-lib/settings-models.ts +124 -0
- package/src/legacy-lib/settings-storage.ts +277 -0
- package/src/legacy-lib/settings-validation.ts +220 -0
- package/src/legacy-lib/settings.ts +87 -0
- package/src/legacy-lib/setup-apply-types.ts +70 -0
- package/src/legacy-lib/setup-apply.ts +456 -0
- package/src/legacy-lib/setup-journal.ts +185 -0
- package/src/legacy-lib/setup-plan-types.ts +96 -0
- package/src/legacy-lib/setup-plan.ts +500 -0
- package/src/legacy-lib/setup-undo.ts +142 -0
- package/src/legacy-lib/start-cluster-config.ts +124 -0
- package/src/legacy-lib/start-cluster-environment.ts +165 -0
- package/src/legacy-lib/start-cluster-run-options.ts +279 -0
- package/src/legacy-lib/start-cluster.ts +237 -0
- package/src/legacy-lib/stream-json-parser.ts +88 -0
- package/src/message-buffer.js +55 -71
- package/src/message-buffer.ts +107 -0
- package/src/message-bus-bridge.js +92 -133
- package/src/message-bus-bridge.ts +149 -0
- package/src/name-generator-shared.js +220 -0
- package/src/name-generator-shared.ts +231 -0
- package/src/name-generator.js +8 -217
- package/src/name-generator.ts +27 -0
- package/src/omp-blob-root.js +83 -64
- package/src/omp-blob-root.ts +116 -0
- package/src/omp-config-overlay.js +65 -38
- package/src/omp-config-overlay.ts +110 -0
- package/src/omp-execution-fingerprint.js +38 -43
- package/src/omp-execution-fingerprint.ts +92 -0
- package/src/omp-session-limits.js +10 -15
- package/src/omp-session-limits.ts +53 -0
- package/src/omp-session-partition.js +2 -2
- package/src/omp-session-verifier.js +1 -1
- package/src/orchestrator.js +19 -4
- package/src/providers/anthropic/index.js +3 -1
- package/src/providers/anthropic/index.ts +13 -0
- package/src/providers/capabilities.js +19 -27
- package/src/providers/capabilities.ts +44 -0
- package/src/providers/google/index.js +3 -1
- package/src/providers/google/index.ts +13 -0
- package/src/providers/openai/index.js +3 -1
- package/src/providers/openai/index.ts +13 -0
- package/src/providers/opencode/index.js +3 -1
- package/src/providers/opencode/index.ts +13 -0
- package/src/quality-gates.js +67 -127
- package/src/quality-gates.ts +149 -0
- package/src/repo-settings-access.js +40 -0
- package/src/repo-settings-access.ts +64 -0
- package/src/schemas/sub-cluster.js +127 -183
- package/src/schemas/sub-cluster.ts +238 -0
- package/src/state-snapshot-normalization.js +224 -0
- package/src/state-snapshot-normalization.ts +222 -0
- package/src/state-snapshot.js +192 -359
- package/src/state-snapshot.ts +291 -0
- package/src/state-snapshotter.js +118 -130
- package/src/state-snapshotter.ts +167 -0
- package/src/sub-cluster-wrapper.js +4 -13
- package/src/task-run-model-args.js +87 -124
- package/src/task-run-model-args.ts +204 -0
- package/src/task-runner.js +5 -25
- package/src/task-runner.ts +28 -0
- package/src/task-startup-error.js +73 -58
- package/src/task-startup-error.ts +126 -0
- package/src/template-validation/consensus-gate-contracts.js +2 -0
- package/src/template-validation/consensus-gate-contracts.ts +70 -0
- package/src/template-validation/consensus-gate-scenarios.js +76 -0
- package/src/template-validation/consensus-gate-scenarios.ts +130 -0
- package/src/template-validation/consensus-gate-stage.js +34 -0
- package/src/template-validation/consensus-gate-stage.ts +56 -0
- package/src/template-validation/random-topology-contracts.js +2 -0
- package/src/template-validation/random-topology-contracts.ts +54 -0
- package/src/template-validation/random-topology-dispatch.js +25 -0
- package/src/template-validation/random-topology-dispatch.ts +33 -0
- package/src/template-validation/random-topology-message-loop.js +53 -0
- package/src/template-validation/random-topology-message-loop.ts +73 -0
- package/src/template-validation/random-topology-operations.js +79 -0
- package/src/template-validation/random-topology-operations.ts +101 -0
- package/src/template-validation/random-topology-runtime.js +25 -0
- package/src/template-validation/random-topology-runtime.ts +43 -0
- package/src/template-validation/random-topology-scenario.js +52 -0
- package/src/template-validation/random-topology-scenario.ts +57 -0
- package/src/template-validation/random-topology-schema.js +159 -0
- package/src/template-validation/random-topology-schema.ts +190 -0
- package/src/template-validation/random-topology-state.js +28 -0
- package/src/template-validation/random-topology-state.ts +30 -0
- package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
- package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
- package/src/template-validation/random-topology-trigger.js +75 -0
- package/src/template-validation/random-topology-trigger.ts +88 -0
- package/src/template-validation/report-formatter.js +40 -46
- package/src/template-validation/report-formatter.ts +101 -0
- package/src/template-validation/simulate-consensus-gates.js +111 -302
- package/src/template-validation/simulate-consensus-gates.ts +196 -0
- package/src/template-validation/simulate-random-topology.js +26 -519
- package/src/template-validation/simulate-random-topology.ts +57 -0
- package/src/template-validation/simulate-two-stage-validation.js +41 -265
- package/src/template-validation/simulate-two-stage-validation.ts +55 -0
- package/src/template-validation/simulation-agent-runtime.js +25 -0
- package/src/template-validation/simulation-agent-runtime.ts +40 -0
- package/src/template-validation/simulation-agent.js +29 -0
- package/src/template-validation/simulation-agent.ts +73 -0
- package/src/template-validation/simulation-runtime.js +34 -0
- package/src/template-validation/simulation-runtime.ts +72 -0
- package/src/template-validation/two-stage-contracts.js +2 -0
- package/src/template-validation/two-stage-contracts.ts +41 -0
- package/src/template-validation/two-stage-inputs.js +21 -0
- package/src/template-validation/two-stage-inputs.ts +29 -0
- package/src/template-validation/two-stage-results.js +30 -0
- package/src/template-validation/two-stage-results.ts +40 -0
- package/src/template-validation/two-stage-scenario.js +86 -0
- package/src/template-validation/two-stage-scenario.ts +120 -0
- package/src/worktree-claude-config.js +182 -220
- package/src/worktree-claude-config.ts +272 -0
- package/src/worktree-tooling-env.js +103 -125
- package/src/worktree-tooling-env.ts +142 -0
- package/task-lib/completion.js +2 -3
- package/task-lib/completion.ts +9 -0
- package/task-lib/config.js +1 -4
- package/task-lib/config.ts +11 -0
- package/task-lib/name-generator.js +4 -226
- package/task-lib/name-generator.ts +11 -0
- package/task-lib/omp-session-cleanup.js +1 -1
- package/task-lib/omp-storage-root.js +8 -12
- package/task-lib/omp-storage-root.ts +62 -0
- package/task-lib/process-termination.js +143 -166
- package/task-lib/process-termination.ts +268 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket Discovery - Utilities for socket path management
|
|
3
|
+
*
|
|
4
|
+
* Socket locations are allocated by socket-paths.js under a short, per-user
|
|
5
|
+
* runtime directory so long HOME paths cannot exceed Unix socket limits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import net from 'node:net';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
import socketPaths from './socket-paths';
|
|
13
|
+
|
|
14
|
+
interface ClustersRegistryModule {
|
|
15
|
+
readClustersFileSync(storageDir: string): unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isClustersRegistryModule(value: unknown): value is ClustersRegistryModule {
|
|
19
|
+
return (
|
|
20
|
+
typeof value === 'object' &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
'readClustersFileSync' in value &&
|
|
23
|
+
typeof value.readClustersFileSync === 'function'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const clustersRegistryModule: unknown = require('../../lib/clusters-registry');
|
|
28
|
+
if (!isClustersRegistryModule(clustersRegistryModule)) {
|
|
29
|
+
throw new TypeError('clusters registry must export readClustersFileSync');
|
|
30
|
+
}
|
|
31
|
+
const { readClustersFileSync } = clustersRegistryModule;
|
|
32
|
+
|
|
33
|
+
const ZEROSHOT_DIR = path.join(socketPaths.resolveHomeDir(), '.zeroshot');
|
|
34
|
+
const SOCKET_DIR = socketPaths.getSocketDir();
|
|
35
|
+
|
|
36
|
+
/** Check if an ID is a known cluster by looking up clusters.json. */
|
|
37
|
+
function isKnownCluster(id: string): boolean {
|
|
38
|
+
try {
|
|
39
|
+
const clusters = readClustersFileSync(ZEROSHOT_DIR);
|
|
40
|
+
return typeof clusters === 'object' && clusters !== null && id in clusters;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Ensure socket directory exists. */
|
|
47
|
+
function ensureSocketDir(): void {
|
|
48
|
+
socketPaths.ensureSocketDir();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Get socket path for a task. */
|
|
52
|
+
function getTaskSocketPath(taskId: string): string {
|
|
53
|
+
return socketPaths.getTaskSocketPath(taskId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Get socket path for a cluster agent. */
|
|
57
|
+
function getAgentSocketPath(clusterId: string, agentId: string): string {
|
|
58
|
+
return socketPaths.getAgentSocketPath(clusterId, agentId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Get socket path for any ID, auto-detecting task versus cluster. */
|
|
62
|
+
function getSocketPath(id: string, agentId: string | null = null): string {
|
|
63
|
+
if (id.startsWith('task-')) {
|
|
64
|
+
return getTaskSocketPath(id);
|
|
65
|
+
}
|
|
66
|
+
// Check for explicit 'cluster-' prefix OR known cluster in registry
|
|
67
|
+
// Cluster IDs are generated without prefix (e.g., 'steady-pulse-42')
|
|
68
|
+
if (id.startsWith('cluster-') || isKnownCluster(id)) {
|
|
69
|
+
if (agentId) {
|
|
70
|
+
return getAgentSocketPath(id, agentId);
|
|
71
|
+
}
|
|
72
|
+
// Cluster-level socket (future use)
|
|
73
|
+
return socketPaths.getClusterSocketPath(id);
|
|
74
|
+
}
|
|
75
|
+
// Unknown format, treat as task
|
|
76
|
+
return getTaskSocketPath(id);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Check if a socket exists and is connectable. */
|
|
80
|
+
function isSocketAlive(socketPath: string): Promise<boolean> {
|
|
81
|
+
if (!fs.existsSync(socketPath)) {
|
|
82
|
+
return Promise.resolve(false);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const socket = net.createConnection(socketPath);
|
|
87
|
+
const timeout = setTimeout(() => {
|
|
88
|
+
socket.destroy();
|
|
89
|
+
resolve(false);
|
|
90
|
+
}, 1000);
|
|
91
|
+
|
|
92
|
+
socket.on('connect', () => {
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
socket.end();
|
|
95
|
+
resolve(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
socket.on('error', () => {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
resolve(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Remove a stale socket file if it is not connectable. */
|
|
106
|
+
async function cleanupStaleSocket(socketPath: string): Promise<boolean> {
|
|
107
|
+
if (!fs.existsSync(socketPath)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const alive = await isSocketAlive(socketPath);
|
|
112
|
+
if (!alive) {
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(socketPath);
|
|
115
|
+
return true;
|
|
116
|
+
} catch {
|
|
117
|
+
// Ignore errors (file may have been removed already)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** List task IDs with live sockets. */
|
|
124
|
+
async function listAttachableTasks(): Promise<string[]> {
|
|
125
|
+
ensureSocketDir();
|
|
126
|
+
const entries = fs.readdirSync(SOCKET_DIR, { withFileTypes: true });
|
|
127
|
+
const tasks: string[] = [];
|
|
128
|
+
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
// Check socket files (Unix sockets report isSocket(), not isFile())
|
|
131
|
+
// Also accept regular files for compatibility
|
|
132
|
+
const isSocketFile = (entry.isSocket() || entry.isFile()) && entry.name.endsWith('.sock');
|
|
133
|
+
if (isSocketFile && !entry.isDirectory()) {
|
|
134
|
+
const id = entry.name.slice(0, -5); // Remove .sock
|
|
135
|
+
|
|
136
|
+
// Skip cluster-level sockets (cluster-xxx.sock)
|
|
137
|
+
if (id.startsWith('cluster-')) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const socketPath = path.join(SOCKET_DIR, entry.name);
|
|
142
|
+
if (await isSocketAlive(socketPath)) {
|
|
143
|
+
tasks.push(id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return tasks;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** List agent IDs with live sockets for a cluster. */
|
|
152
|
+
async function listAttachableAgents(clusterId: string): Promise<string[]> {
|
|
153
|
+
const clusterDir = path.join(SOCKET_DIR, clusterId);
|
|
154
|
+
if (!fs.existsSync(clusterDir)) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const files = fs.readdirSync(clusterDir);
|
|
159
|
+
const agents: string[] = [];
|
|
160
|
+
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (file.endsWith('.sock')) {
|
|
163
|
+
const agentId = file.slice(0, -5); // Remove .sock
|
|
164
|
+
const socketPath = path.join(clusterDir, file);
|
|
165
|
+
if (await isSocketAlive(socketPath)) {
|
|
166
|
+
agents.push(agentId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return agents;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** List cluster IDs with at least one live agent socket. */
|
|
175
|
+
async function listAttachableClusters(): Promise<string[]> {
|
|
176
|
+
ensureSocketDir();
|
|
177
|
+
const entries = fs.readdirSync(SOCKET_DIR, { withFileTypes: true });
|
|
178
|
+
const clusters: string[] = [];
|
|
179
|
+
|
|
180
|
+
// Check socket directories (both 'cluster-' prefix and plain cluster IDs)
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
// Accept directories with 'cluster-' prefix OR that are known clusters
|
|
184
|
+
if (entry.name.startsWith('cluster-') || isKnownCluster(entry.name)) {
|
|
185
|
+
const agents = await listAttachableAgents(entry.name);
|
|
186
|
+
if (agents.length > 0) {
|
|
187
|
+
clusters.push(entry.name);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return clusters;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Cleanup all sockets for a cluster when it stops. */
|
|
197
|
+
function cleanupClusterSockets(clusterId: string): void {
|
|
198
|
+
const clusterDir = path.join(SOCKET_DIR, clusterId);
|
|
199
|
+
if (fs.existsSync(clusterDir)) {
|
|
200
|
+
const files = fs.readdirSync(clusterDir);
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
try {
|
|
203
|
+
fs.unlinkSync(path.join(clusterDir, file));
|
|
204
|
+
} catch {
|
|
205
|
+
// Ignore
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
fs.rmdirSync(clusterDir);
|
|
210
|
+
} catch {
|
|
211
|
+
// Ignore
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export = {
|
|
217
|
+
ZEROSHOT_DIR,
|
|
218
|
+
SOCKET_DIR,
|
|
219
|
+
ensureSocketDir,
|
|
220
|
+
getTaskSocketPath,
|
|
221
|
+
getAgentSocketPath,
|
|
222
|
+
getSocketPath,
|
|
223
|
+
isSocketAlive,
|
|
224
|
+
cleanupStaleSocket,
|
|
225
|
+
listAttachableTasks,
|
|
226
|
+
listAttachableAgents,
|
|
227
|
+
listAttachableClusters,
|
|
228
|
+
cleanupClusterSockets,
|
|
229
|
+
isKnownCluster,
|
|
230
|
+
};
|
|
@@ -1,85 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* per OS user and Zeroshot home.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const crypto = require('crypto');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const os = require('os');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
14
6
|
const SOCKET_ROOT = process.platform === 'win32' ? null : '/tmp';
|
|
15
7
|
const SOCKET_DIR_MODE = 0o700;
|
|
16
|
-
|
|
17
8
|
function shortHash(value) {
|
|
18
|
-
|
|
9
|
+
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
19
10
|
}
|
|
20
|
-
|
|
21
11
|
function userNamespace() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
12
|
+
if (typeof process.getuid === 'function') {
|
|
13
|
+
return String(process.getuid());
|
|
14
|
+
}
|
|
15
|
+
return shortHash(os.userInfo().username);
|
|
26
16
|
}
|
|
27
|
-
|
|
28
17
|
function resolveHomeDir(env = process.env) {
|
|
29
|
-
|
|
18
|
+
return env.ZEROSHOT_HOME || env.HOME || env.USERPROFILE || os.homedir();
|
|
30
19
|
}
|
|
31
|
-
|
|
32
20
|
function getSocketDir(homeDir = resolveHomeDir()) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
if (SOCKET_ROOT === null) {
|
|
22
|
+
return path.join(homeDir, '.zeroshot', 'sockets');
|
|
23
|
+
}
|
|
24
|
+
return path.join(SOCKET_ROOT, `zeroshot-${userNamespace()}-${shortHash(homeDir)}`);
|
|
37
25
|
}
|
|
38
|
-
|
|
39
26
|
function assertSafeSocketDir(socketDir) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
const stat = fs.lstatSync(socketDir);
|
|
28
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
29
|
+
throw new Error(`Attach socket path is not a directory: ${socketDir}`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
|
|
32
|
+
throw new Error(`Attach socket directory is not owned by the current user: ${socketDir}`);
|
|
33
|
+
}
|
|
47
34
|
}
|
|
48
|
-
|
|
49
35
|
function ensureOwnedDirectory(socketDir) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
fs.mkdirSync(socketDir, { recursive: true, mode: SOCKET_DIR_MODE });
|
|
37
|
+
assertSafeSocketDir(socketDir);
|
|
38
|
+
if (process.platform !== 'win32') {
|
|
39
|
+
fs.chmodSync(socketDir, SOCKET_DIR_MODE);
|
|
40
|
+
}
|
|
41
|
+
return socketDir;
|
|
56
42
|
}
|
|
57
|
-
|
|
58
43
|
function ensureSocketDir(homeDir = resolveHomeDir()) {
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
const socketDir = getSocketDir(homeDir);
|
|
45
|
+
return ensureOwnedDirectory(socketDir);
|
|
61
46
|
}
|
|
62
|
-
|
|
63
47
|
function getTaskSocketPath(taskId, homeDir = resolveHomeDir()) {
|
|
64
|
-
|
|
48
|
+
return path.join(ensureSocketDir(homeDir), `${taskId}.sock`);
|
|
65
49
|
}
|
|
66
|
-
|
|
67
50
|
function getAgentSocketPath(clusterId, agentId, homeDir = resolveHomeDir()) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
const clusterDir = path.join(ensureSocketDir(homeDir), clusterId);
|
|
52
|
+
ensureOwnedDirectory(clusterDir);
|
|
53
|
+
return path.join(clusterDir, `${agentId}.sock`);
|
|
71
54
|
}
|
|
72
|
-
|
|
73
55
|
function getClusterSocketPath(clusterId, homeDir = resolveHomeDir()) {
|
|
74
|
-
|
|
56
|
+
return path.join(ensureSocketDir(homeDir), `${clusterId}.sock`);
|
|
75
57
|
}
|
|
76
|
-
|
|
77
58
|
module.exports = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
59
|
+
SOCKET_DIR_MODE,
|
|
60
|
+
resolveHomeDir,
|
|
61
|
+
getSocketDir,
|
|
62
|
+
ensureSocketDir,
|
|
63
|
+
getTaskSocketPath,
|
|
64
|
+
getAgentSocketPath,
|
|
65
|
+
getClusterSocketPath,
|
|
85
66
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import crypto = require('crypto');
|
|
2
|
+
import fs = require('fs');
|
|
3
|
+
import os = require('os');
|
|
4
|
+
import path = require('path');
|
|
5
|
+
|
|
6
|
+
const SOCKET_ROOT = process.platform === 'win32' ? null : '/tmp';
|
|
7
|
+
const SOCKET_DIR_MODE = 0o700;
|
|
8
|
+
|
|
9
|
+
function shortHash(value: string): string {
|
|
10
|
+
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function userNamespace(): string {
|
|
14
|
+
if (typeof process.getuid === 'function') {
|
|
15
|
+
return String(process.getuid());
|
|
16
|
+
}
|
|
17
|
+
return shortHash(os.userInfo().username);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveHomeDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
21
|
+
return env.ZEROSHOT_HOME || env.HOME || env.USERPROFILE || os.homedir();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getSocketDir(homeDir = resolveHomeDir()): string {
|
|
25
|
+
if (SOCKET_ROOT === null) {
|
|
26
|
+
return path.join(homeDir, '.zeroshot', 'sockets');
|
|
27
|
+
}
|
|
28
|
+
return path.join(SOCKET_ROOT, `zeroshot-${userNamespace()}-${shortHash(homeDir)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assertSafeSocketDir(socketDir: string): void {
|
|
32
|
+
const stat = fs.lstatSync(socketDir);
|
|
33
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
34
|
+
throw new Error(`Attach socket path is not a directory: ${socketDir}`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
|
|
37
|
+
throw new Error(`Attach socket directory is not owned by the current user: ${socketDir}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ensureOwnedDirectory(socketDir: string): string {
|
|
42
|
+
fs.mkdirSync(socketDir, { recursive: true, mode: SOCKET_DIR_MODE });
|
|
43
|
+
assertSafeSocketDir(socketDir);
|
|
44
|
+
if (process.platform !== 'win32') {
|
|
45
|
+
fs.chmodSync(socketDir, SOCKET_DIR_MODE);
|
|
46
|
+
}
|
|
47
|
+
return socketDir;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ensureSocketDir(homeDir = resolveHomeDir()): string {
|
|
51
|
+
const socketDir = getSocketDir(homeDir);
|
|
52
|
+
return ensureOwnedDirectory(socketDir);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTaskSocketPath(taskId: string, homeDir = resolveHomeDir()): string {
|
|
56
|
+
return path.join(ensureSocketDir(homeDir), `${taskId}.sock`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getAgentSocketPath(
|
|
60
|
+
clusterId: string,
|
|
61
|
+
agentId: string,
|
|
62
|
+
homeDir = resolveHomeDir()
|
|
63
|
+
): string {
|
|
64
|
+
const clusterDir = path.join(ensureSocketDir(homeDir), clusterId);
|
|
65
|
+
ensureOwnedDirectory(clusterDir);
|
|
66
|
+
return path.join(clusterDir, `${agentId}.sock`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getClusterSocketPath(clusterId: string, homeDir = resolveHomeDir()): string {
|
|
70
|
+
return path.join(ensureSocketDir(homeDir), `${clusterId}.sock`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export = {
|
|
74
|
+
SOCKET_DIR_MODE,
|
|
75
|
+
resolveHomeDir,
|
|
76
|
+
getSocketDir,
|
|
77
|
+
ensureSocketDir,
|
|
78
|
+
getTaskSocketPath,
|
|
79
|
+
getAgentSocketPath,
|
|
80
|
+
getClusterSocketPath,
|
|
81
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Provisions Claude Code credentials into an isolated CLAUDE_CONFIG_DIR.
|
|
3
4
|
*
|
|
@@ -6,70 +7,66 @@
|
|
|
6
7
|
* a Keychain-only (OAuth subscription) login must be materialized to a file or
|
|
7
8
|
* every isolated agent fails auth even though the host is logged in.
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const os = require("os");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
function isSafeExecModule(value) {
|
|
14
|
+
return (typeof value === 'object' &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
'execSync' in value &&
|
|
17
|
+
typeof value.execSync === 'function');
|
|
18
|
+
}
|
|
19
|
+
const safeExecModule = require('./lib/safe-exec');
|
|
20
|
+
if (!isSafeExecModule(safeExecModule)) {
|
|
21
|
+
throw new TypeError('safe-exec must export execSync');
|
|
22
|
+
}
|
|
23
|
+
const { execSync } = safeExecModule;
|
|
15
24
|
const CREDENTIALS_BASENAME = '.credentials.json';
|
|
16
25
|
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Read the Claude Code OAuth credentials JSON from the macOS Keychain.
|
|
20
|
-
* @returns {string|null} Raw credentials JSON, or null if unavailable/invalid/non-darwin.
|
|
21
|
-
*/
|
|
26
|
+
/** Read the Claude Code OAuth credentials JSON from the macOS Keychain. */
|
|
22
27
|
function readKeychainCredentials() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
if (os.platform() !== 'darwin') {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync(`security find-generic-password -s "${KEYCHAIN_SERVICE}" -w`, {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
35
|
+
timeout: 2000,
|
|
36
|
+
});
|
|
37
|
+
const trimmed = output.trim();
|
|
38
|
+
if (!trimmed) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
JSON.parse(trimmed); // reject garbage output that isn't real credentials
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
36
46
|
}
|
|
37
|
-
JSON.parse(trimmed); // reject garbage output that isn't real credentials
|
|
38
|
-
return trimmed;
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
47
|
}
|
|
43
|
-
|
|
44
48
|
/**
|
|
45
49
|
* Copy an existing credentials file into an isolated config dir, or materialize
|
|
46
50
|
* one from the macOS Keychain when no source file exists. Resynced on every call
|
|
47
51
|
* so an expired-then-refreshed OAuth token in the Keychain is picked up per run.
|
|
48
|
-
* @param {{ sourceDir: string, destDir: string }} options
|
|
49
|
-
* @returns {boolean} true if a credentials file was written into destDir
|
|
50
52
|
*/
|
|
51
|
-
function provisionClaudeCredentials({ sourceDir, destDir }) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return false;
|
|
53
|
+
function provisionClaudeCredentials({ sourceDir, destDir, }) {
|
|
54
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
55
|
+
const destPath = path.join(destDir, CREDENTIALS_BASENAME);
|
|
56
|
+
const sourcePath = path.join(sourceDir, CREDENTIALS_BASENAME);
|
|
57
|
+
if (fs.existsSync(sourcePath)) {
|
|
58
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
59
|
+
fs.chmodSync(destPath, 0o600);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const keychainCredentials = readKeychainCredentials();
|
|
63
|
+
if (keychainCredentials) {
|
|
64
|
+
fs.writeFileSync(destPath, keychainCredentials, { mode: 0o600 });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
70
68
|
}
|
|
71
|
-
|
|
72
69
|
module.exports = {
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
readKeychainCredentials,
|
|
71
|
+
provisionClaudeCredentials,
|
|
75
72
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisions Claude Code credentials into an isolated CLAUDE_CONFIG_DIR.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code only reads the macOS Keychain when using the DEFAULT config dir.
|
|
5
|
+
* Isolated agent runs (--worktree, --docker) use a custom CLAUDE_CONFIG_DIR, so
|
|
6
|
+
* a Keychain-only (OAuth subscription) login must be materialized to a file or
|
|
7
|
+
* every isolated agent fails auth even though the host is logged in.
|
|
8
|
+
*/
|
|
9
|
+
import fs = require('fs');
|
|
10
|
+
import os = require('os');
|
|
11
|
+
import path = require('path');
|
|
12
|
+
interface SafeExecModule {
|
|
13
|
+
execSync(
|
|
14
|
+
command: string,
|
|
15
|
+
options: {
|
|
16
|
+
encoding: 'utf8';
|
|
17
|
+
stdio: ['ignore', 'pipe', 'ignore'];
|
|
18
|
+
timeout: number;
|
|
19
|
+
}
|
|
20
|
+
): string;
|
|
21
|
+
}
|
|
22
|
+
interface ProvisionClaudeCredentialsOptions {
|
|
23
|
+
sourceDir: string;
|
|
24
|
+
destDir: string;
|
|
25
|
+
}
|
|
26
|
+
function isSafeExecModule(value: unknown): value is SafeExecModule {
|
|
27
|
+
return (
|
|
28
|
+
typeof value === 'object' &&
|
|
29
|
+
value !== null &&
|
|
30
|
+
'execSync' in value &&
|
|
31
|
+
typeof value.execSync === 'function'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const safeExecModule: unknown = require('./lib/safe-exec');
|
|
35
|
+
if (!isSafeExecModule(safeExecModule)) {
|
|
36
|
+
throw new TypeError('safe-exec must export execSync');
|
|
37
|
+
}
|
|
38
|
+
const { execSync } = safeExecModule;
|
|
39
|
+
const CREDENTIALS_BASENAME = '.credentials.json';
|
|
40
|
+
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
41
|
+
/** Read the Claude Code OAuth credentials JSON from the macOS Keychain. */
|
|
42
|
+
function readKeychainCredentials(): string | null {
|
|
43
|
+
if (os.platform() !== 'darwin') {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const output = execSync(`security find-generic-password -s "${KEYCHAIN_SERVICE}" -w`, {
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
50
|
+
timeout: 2000,
|
|
51
|
+
});
|
|
52
|
+
const trimmed = output.trim();
|
|
53
|
+
if (!trimmed) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
JSON.parse(trimmed); // reject garbage output that isn't real credentials
|
|
57
|
+
return trimmed;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Copy an existing credentials file into an isolated config dir, or materialize
|
|
64
|
+
* one from the macOS Keychain when no source file exists. Resynced on every call
|
|
65
|
+
* so an expired-then-refreshed OAuth token in the Keychain is picked up per run.
|
|
66
|
+
*/
|
|
67
|
+
function provisionClaudeCredentials({
|
|
68
|
+
sourceDir,
|
|
69
|
+
destDir,
|
|
70
|
+
}: ProvisionClaudeCredentialsOptions): boolean {
|
|
71
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
72
|
+
const destPath = path.join(destDir, CREDENTIALS_BASENAME);
|
|
73
|
+
const sourcePath = path.join(sourceDir, CREDENTIALS_BASENAME);
|
|
74
|
+
if (fs.existsSync(sourcePath)) {
|
|
75
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
76
|
+
fs.chmodSync(destPath, 0o600);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const keychainCredentials = readKeychainCredentials();
|
|
80
|
+
if (keychainCredentials) {
|
|
81
|
+
fs.writeFileSync(destPath, keychainCredentials, { mode: 0o600 });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
export = {
|
|
87
|
+
readKeychainCredentials,
|
|
88
|
+
provisionClaudeCredentials,
|
|
89
|
+
};
|