@platless/axom-cli 0.5.9 → 0.6.52
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 +25 -6
- package/dist/axom-patch/cli/README.md +129 -12
- package/dist/axom-patch/cli/account-limits/generate.py +15 -12
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
- package/dist/axom-patch/cli/claude-account-limits.json +9 -9
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-account-limits.json +11 -11
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +55 -13
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +19 -19
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -10
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
- package/dist/cli.js +11 -45
- package/dist/commands/auth.js +4 -1
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +126 -3
- package/dist/commands/workstation.js +101 -164
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +15 -15
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +113 -102
- package/dist/workstation/acp/acpClient.js +26 -0
- package/dist/workstation/acp/builds/install/npm.js +99 -10
- package/dist/workstation/acp/initializeResponse.js +6 -1
- package/dist/workstation/acp/managedPatches.js +189 -25
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/patchAssets.js +40 -0
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +26 -34
- package/dist/workstation/acp/processor/control.js +7 -80
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/runtime.js +5 -81
- package/dist/workstation/acp/processor/session.js +54 -82
- package/dist/workstation/acp/processor/state.js +7 -13
- package/dist/workstation/acp/processor/step.js +3 -20
- package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +156 -105
- package/dist/workstation/acp/processorSession.js +20 -173
- package/dist/workstation/acp/registry/catalog.js +65 -39
- package/dist/workstation/acp/registry/launch.js +16 -1
- package/dist/workstation/acp/switching.js +0 -3
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +32 -3
- package/dist/workstation/acp/test/raw/sources.js +16 -130
- package/dist/workstation/acp/test/realAdapter.js +1 -47
- package/dist/workstation/acp/test/scriptAgent.js +11 -3
- package/dist/workstation/agentFlow.js +1 -1
- package/dist/workstation/agentSessions.js +114 -166
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +130 -15
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +33 -9
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +204 -0
- package/dist/workstation/control/commands.js +125 -0
- package/dist/workstation/control/daemonIdentity.js +143 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +110 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +152 -0
- package/dist/workstation/control/runtimeHost.js +235 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +266 -264
- package/dist/workstation/daemonctl.js +226 -25
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +93 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +602 -0
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +131 -0
- package/dist/workstation/desktopControl/macProvider.js +156 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +311 -0
- package/dist/workstation/desktopControl/provider.js +1 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1774 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +98 -0
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/files/text.js +42 -8
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/bundle.js +21 -17
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +123 -7
- package/dist/workstation/keepers/kit/paths.js +2 -1
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/agent.js +2 -15
- package/dist/workstation/protocol/management.js +113 -5
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +120 -0
- package/dist/workstation/rd/control.js +117 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +256 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +57 -0
- package/dist/workstation/rd/update.js +168 -0
- package/dist/workstation/rd/upkeep.js +80 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +197 -14
- package/dist/workstation/service/launchd.js +2 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +47 -92
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +186 -0
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +51 -25
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +10 -3
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +29806 -333
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +11 -7
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
- package/dist/runtime-deps/qoder-sdk/package.json +0 -8
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/processor/sideChannel.js +0 -80
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
- package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
- package/dist/workstation/qoderAdapter/history.js +0 -85
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -95
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
- package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -85
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { KeeperMethodError } from '../kit/protocol.js';
|
|
6
|
+
import { TERMINAL_GRID_LIMITS as limits, } from './gridProtocol.js';
|
|
7
|
+
import { TerminalFrames } from './frames.js';
|
|
8
|
+
import { TerminalScreen } from './screen.js';
|
|
9
|
+
import { createTerminalInput } from './input.js';
|
|
10
|
+
import { createTerminalMetadata } from './metadata.js';
|
|
11
|
+
export const NATIVE_PTY_OPERATIONS = new Set(['attach', 'poll', 'resize', 'input', 'scroll', 'detach', 'close']);
|
|
12
|
+
const bad = (message) => { throw new KeeperMethodError('bad_request', message); };
|
|
13
|
+
const fail = (code, message) => { throw new KeeperMethodError(code, message); };
|
|
14
|
+
const ok = { ok: true };
|
|
15
|
+
const nativeScheduler = {
|
|
16
|
+
schedule(callback, delay) { const timer = setTimeout(callback, delay); timer.unref(); return { dispose: () => clearTimeout(timer) }; },
|
|
17
|
+
};
|
|
18
|
+
const basename = (name) => path.basename(name.trim()).replace(/^-/, '');
|
|
19
|
+
/** Keeper-owned sessions: ordered within a PTY, independent between PTYs. */
|
|
20
|
+
export class NativePtySessions {
|
|
21
|
+
options;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
now;
|
|
24
|
+
scheduler;
|
|
25
|
+
module;
|
|
26
|
+
unavailable;
|
|
27
|
+
disposed = false;
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
this.now = options.now ?? Date.now;
|
|
31
|
+
this.scheduler = options.scheduler ?? nativeScheduler;
|
|
32
|
+
try {
|
|
33
|
+
this.module = (options.loadPty ?? (() => createRequire(import.meta.url)('node-pty')))();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
this.unavailable = 'Terminal native dependency unavailable; reinstall CLI with optional node-pty support';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
return { schema: 1, limits: { ...limits }, sessions: [...this.sessions.values()].map(s => this.descriptor(s)) };
|
|
41
|
+
}
|
|
42
|
+
async create(params, launch) {
|
|
43
|
+
this.available();
|
|
44
|
+
request(params);
|
|
45
|
+
keys(params, ['id', 'cwd', 'registrationId']);
|
|
46
|
+
const id = identifier(params, 'id');
|
|
47
|
+
if (params.registrationId !== undefined)
|
|
48
|
+
identifier(params, 'registrationId');
|
|
49
|
+
if (params.cwd !== undefined && (typeof params.cwd !== 'string' || params.cwd.includes('\0')))
|
|
50
|
+
bad('Invalid cwd');
|
|
51
|
+
const existing = this.sessions.get(id);
|
|
52
|
+
if (existing)
|
|
53
|
+
return this.descriptor(existing);
|
|
54
|
+
this.checkLimit();
|
|
55
|
+
const resolved = await launch();
|
|
56
|
+
this.available();
|
|
57
|
+
const concurrent = this.sessions.get(id);
|
|
58
|
+
if (concurrent)
|
|
59
|
+
return this.descriptor(concurrent);
|
|
60
|
+
this.checkLimit();
|
|
61
|
+
const cols = 100;
|
|
62
|
+
const rows = 30;
|
|
63
|
+
const screen = new TerminalScreen(cols, rows);
|
|
64
|
+
let pty;
|
|
65
|
+
try {
|
|
66
|
+
pty = this.module.spawn(resolved.shell, ['-l'], { name: 'xterm-256color', cwd: resolved.cwd, cols, rows, env: { ...resolved.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' } });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
screen.dispose();
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const createdAt = this.now();
|
|
73
|
+
const shell = basename(resolved.shell);
|
|
74
|
+
const s = {
|
|
75
|
+
value: { id, ...(typeof params.registrationId === 'string' ? { registrationId: params.registrationId } : {}), incarnation: randomUUID(), title: 'Terminal', cwd: resolved.cwd, cwdSource: 'launch', cols, rows, createdAt, exited: false, exitCode: null },
|
|
76
|
+
shell, shellNames: new Set([shell]), shellNameStarted: false, pty, screen, frames: new TerminalFrames(), attachments: new Map(), geometry: 1,
|
|
77
|
+
closed: false, queue: Promise.resolve(), rendered: Promise.resolve(), pendingBytes: 0, dirty: true, capturedAt: -Infinity,
|
|
78
|
+
listeners: [], input: createTerminalInput(screen), exitedAt: 0, observed: { label: shell, state: 'idle', since: createdAt },
|
|
79
|
+
};
|
|
80
|
+
const context = { pty, screen, session: () => this.descriptor(s), changed: () => this.dirty(s) };
|
|
81
|
+
try {
|
|
82
|
+
s.input = this.options.createInput?.(context) ?? s.input;
|
|
83
|
+
s.metadata = (this.options.createMetadata ?? createTerminalMetadata)(context);
|
|
84
|
+
this.capture(s, true);
|
|
85
|
+
s.listeners.push(disposable(pty.onData(data => this.output(s, data))), disposable(pty.onExit(event => {
|
|
86
|
+
if (s.closed)
|
|
87
|
+
return;
|
|
88
|
+
s.shellNameTimer?.dispose();
|
|
89
|
+
s.shellNameTimer = undefined;
|
|
90
|
+
s.value.exited = true;
|
|
91
|
+
s.value.exitCode = event.exitCode;
|
|
92
|
+
s.exitedAt = this.now();
|
|
93
|
+
this.dirty(s);
|
|
94
|
+
})), screen.onReply(data => { if (!s.closed && !s.value.exited) {
|
|
95
|
+
try {
|
|
96
|
+
pty.write(data);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.failed(s, error);
|
|
100
|
+
}
|
|
101
|
+
} }));
|
|
102
|
+
this.sessions.set(id, s);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await this.close(s);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return this.descriptor(s);
|
|
109
|
+
}
|
|
110
|
+
async operate(op, params) {
|
|
111
|
+
request(params);
|
|
112
|
+
const id = identifier(params, 'id');
|
|
113
|
+
const incarnation = identifier(params, 'incarnation');
|
|
114
|
+
const s = this.sessions.get(id);
|
|
115
|
+
if (!s) {
|
|
116
|
+
if (op === 'close' || op === 'detach')
|
|
117
|
+
return ok;
|
|
118
|
+
return fail('pty_not_found', 'Terminal session not found; keeper restart loses live PTYs');
|
|
119
|
+
}
|
|
120
|
+
if (s.value.incarnation !== incarnation)
|
|
121
|
+
return fail('pty_incarnation_mismatch', 'Terminal incarnation no longer matches');
|
|
122
|
+
return this.ordered(s, async () => {
|
|
123
|
+
if (s.closed) {
|
|
124
|
+
if (op === 'close' || op === 'detach')
|
|
125
|
+
return ok;
|
|
126
|
+
return fail('pty_not_found', 'Terminal session is closed');
|
|
127
|
+
}
|
|
128
|
+
if (op === 'close') {
|
|
129
|
+
keys(params, ['id', 'incarnation']);
|
|
130
|
+
await this.close(s);
|
|
131
|
+
return ok;
|
|
132
|
+
}
|
|
133
|
+
await this.expire(s);
|
|
134
|
+
const clientId = identifier(params, 'clientId');
|
|
135
|
+
const ref = ['id', 'incarnation', 'clientId'];
|
|
136
|
+
if (op === 'detach') {
|
|
137
|
+
keys(params, ref);
|
|
138
|
+
this.release(s, clientId);
|
|
139
|
+
await this.capacity(s);
|
|
140
|
+
return ok;
|
|
141
|
+
}
|
|
142
|
+
if (op === 'attach') {
|
|
143
|
+
keys(params, [...ref, 'cols', 'rows']);
|
|
144
|
+
const cols = integer(params, 'cols', limits.minCols, limits.maxCols);
|
|
145
|
+
const rows = integer(params, 'rows', limits.minRows, limits.maxRows);
|
|
146
|
+
if (!s.attachments.has(clientId) && s.attachments.size >= limits.maxAttachments)
|
|
147
|
+
return fail('pty_limit', 'Terminal attachment limit exceeded');
|
|
148
|
+
const previous = s.attachments.get(clientId);
|
|
149
|
+
s.attachments.set(clientId, { cols, rows, expires: this.now() + limits.leaseMs });
|
|
150
|
+
try {
|
|
151
|
+
await this.capacity(s);
|
|
152
|
+
await this.settle(s);
|
|
153
|
+
this.capture(s, true);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (previous)
|
|
157
|
+
s.attachments.set(clientId, previous);
|
|
158
|
+
else
|
|
159
|
+
this.release(s, clientId);
|
|
160
|
+
try {
|
|
161
|
+
await this.capacity(s);
|
|
162
|
+
}
|
|
163
|
+
catch (rollback) {
|
|
164
|
+
this.failed(s, rollback);
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
this.scheduleLease(s);
|
|
169
|
+
return s.frames.poll(0);
|
|
170
|
+
}
|
|
171
|
+
const attachment = s.attachments.get(clientId);
|
|
172
|
+
if (!attachment)
|
|
173
|
+
return fail('pty_attachment_expired', 'Terminal attachment expired; attach again');
|
|
174
|
+
if (op === 'poll') {
|
|
175
|
+
keys(params, [...ref, 'after']);
|
|
176
|
+
const after = integer(params, 'after', 0, Number.MAX_SAFE_INTEGER);
|
|
177
|
+
attachment.expires = this.now() + limits.leaseMs;
|
|
178
|
+
this.scheduleLease(s);
|
|
179
|
+
await this.captureDue(s);
|
|
180
|
+
return s.frames.poll(after);
|
|
181
|
+
}
|
|
182
|
+
if (op === 'resize') {
|
|
183
|
+
keys(params, [...ref, 'cols', 'rows']);
|
|
184
|
+
const cols = integer(params, 'cols', limits.minCols, limits.maxCols);
|
|
185
|
+
const rows = integer(params, 'rows', limits.minRows, limits.maxRows);
|
|
186
|
+
const previous = { ...attachment };
|
|
187
|
+
attachment.cols = cols;
|
|
188
|
+
attachment.rows = rows;
|
|
189
|
+
try {
|
|
190
|
+
await this.capacity(s);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
Object.assign(attachment, previous);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (op === 'input') {
|
|
198
|
+
const event = input(params, ref);
|
|
199
|
+
await this.settle(s);
|
|
200
|
+
this.assertRunning(s);
|
|
201
|
+
if (event.kind === 'pointer' || event.kind === 'button')
|
|
202
|
+
this.coordinates(s, event);
|
|
203
|
+
const data = s.input.input(event, clientId, this.inputContext(s));
|
|
204
|
+
if (data)
|
|
205
|
+
s.pty.write(data);
|
|
206
|
+
}
|
|
207
|
+
else if (op === 'scroll') {
|
|
208
|
+
keys(params, [...ref, 'lines', 'x', 'y', 'modifiers', 'geometry']);
|
|
209
|
+
const gesture = { lines: integer(params, 'lines', -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), ...position(params), modifiers: integer(params, 'modifiers', 0, 15) };
|
|
210
|
+
await this.settle(s);
|
|
211
|
+
this.coordinates(s, gesture);
|
|
212
|
+
const data = s.input.scroll(gesture, clientId, this.inputContext(s));
|
|
213
|
+
if (data) {
|
|
214
|
+
this.assertRunning(s);
|
|
215
|
+
s.pty.write(data);
|
|
216
|
+
}
|
|
217
|
+
else if (data === undefined && s.screen.state().buffer === 'normal') {
|
|
218
|
+
s.screen.scroll(gesture.lines);
|
|
219
|
+
this.dirty(s);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else
|
|
223
|
+
return bad('Unsupported terminal operation');
|
|
224
|
+
attachment.expires = this.now() + limits.leaseMs;
|
|
225
|
+
this.scheduleLease(s);
|
|
226
|
+
return ok;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
activity() {
|
|
230
|
+
const metrics = { ptyOpen: 0, ptyActive: 0, ptyExited: 0 };
|
|
231
|
+
const items = [...this.sessions.values()].map(s => {
|
|
232
|
+
if (s.value.exited) {
|
|
233
|
+
metrics.ptyExited++;
|
|
234
|
+
return { type: 'pty', id: s.value.id, label: s.shell.slice(0, 64), state: 'exited', sinceEpochMs: s.exitedAt };
|
|
235
|
+
}
|
|
236
|
+
metrics.ptyOpen++;
|
|
237
|
+
let foreground = '';
|
|
238
|
+
try {
|
|
239
|
+
foreground = basename(s.pty.process);
|
|
240
|
+
}
|
|
241
|
+
catch { /* process can disappear during exit */ }
|
|
242
|
+
const active = foreground !== '' && !s.shellNames.has(foreground);
|
|
243
|
+
const label = (active ? foreground : s.shell).slice(0, 64);
|
|
244
|
+
const state = active ? 'active' : 'idle';
|
|
245
|
+
if (active)
|
|
246
|
+
metrics.ptyActive++;
|
|
247
|
+
if (s.observed.label !== label || s.observed.state !== state)
|
|
248
|
+
s.observed = { label, state, since: this.now() };
|
|
249
|
+
return { type: 'pty', id: s.value.id, label, state, sinceEpochMs: s.observed.since };
|
|
250
|
+
});
|
|
251
|
+
return { metrics, items };
|
|
252
|
+
}
|
|
253
|
+
async dispose() {
|
|
254
|
+
if (this.disposed)
|
|
255
|
+
return;
|
|
256
|
+
this.disposed = true;
|
|
257
|
+
await Promise.all([...this.sessions.values()].map(s => this.ordered(s, () => this.close(s))));
|
|
258
|
+
}
|
|
259
|
+
descriptor(s) { return { ...s.value, ...s.metadata?.read() }; }
|
|
260
|
+
available() {
|
|
261
|
+
if (this.disposed)
|
|
262
|
+
fail('pty_unavailable', 'Terminal host is disposed');
|
|
263
|
+
if (!this.module)
|
|
264
|
+
fail('pty_unavailable', this.unavailable ?? 'Terminal native dependency unavailable');
|
|
265
|
+
}
|
|
266
|
+
checkLimit() { if (this.sessions.size >= limits.maxSessions)
|
|
267
|
+
fail('pty_limit', 'Terminal session limit exceeded'); }
|
|
268
|
+
scheduleShellName(s, attempt = 0) {
|
|
269
|
+
const live = () => !s.closed && !s.value.exited && !this.disposed;
|
|
270
|
+
if (!live())
|
|
271
|
+
return;
|
|
272
|
+
s.shellNameTimer = this.scheduler.schedule(() => {
|
|
273
|
+
s.shellNameTimer = undefined;
|
|
274
|
+
if (!live())
|
|
275
|
+
return;
|
|
276
|
+
const retry = () => { if (attempt < 2)
|
|
277
|
+
this.scheduleShellName(s, attempt + 1); };
|
|
278
|
+
// Query the original shell PID: the foreground process may be a running command.
|
|
279
|
+
void Promise.resolve().then(() => live() ? (this.options.shellProcessName ?? defaultShellProcessName)(s.pty.pid) : null).then(name => {
|
|
280
|
+
if (!live())
|
|
281
|
+
return;
|
|
282
|
+
const shell = name && basename(name);
|
|
283
|
+
if (shell && shell !== 'spawn-helper')
|
|
284
|
+
s.shellNames.add(shell);
|
|
285
|
+
else
|
|
286
|
+
retry();
|
|
287
|
+
}, retry);
|
|
288
|
+
}, this.options.shellNameDelayMs ?? 250);
|
|
289
|
+
}
|
|
290
|
+
ordered(s, action) {
|
|
291
|
+
const operation = s.queue.then(action);
|
|
292
|
+
s.queue = operation.catch(() => { });
|
|
293
|
+
return operation;
|
|
294
|
+
}
|
|
295
|
+
async settle(s) {
|
|
296
|
+
for (;;) {
|
|
297
|
+
const rendered = s.rendered;
|
|
298
|
+
await rendered;
|
|
299
|
+
if (rendered === s.rendered)
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
if (s.failure)
|
|
303
|
+
throw s.failure;
|
|
304
|
+
}
|
|
305
|
+
output(s, data) {
|
|
306
|
+
if (s.closed || !data || s.failure)
|
|
307
|
+
return;
|
|
308
|
+
// node-pty initially runs a spawn-helper trampoline at this PID. First output
|
|
309
|
+
// starts the bounded lookup after shell startup, once per session.
|
|
310
|
+
if (!s.shellNameStarted) {
|
|
311
|
+
s.shellNameStarted = true;
|
|
312
|
+
this.scheduleShellName(s);
|
|
313
|
+
}
|
|
314
|
+
const bytes = Buffer.byteLength(data);
|
|
315
|
+
s.pendingBytes += bytes;
|
|
316
|
+
try {
|
|
317
|
+
if (s.pendingBytes > 256 * 1024)
|
|
318
|
+
s.pty.pause();
|
|
319
|
+
s.metadata?.onData?.(data);
|
|
320
|
+
// Submit directly to xterm's ordered parser; chaining writes costs one timer per PTY chunk.
|
|
321
|
+
s.rendered = s.screen.write(data);
|
|
322
|
+
void s.rendered.then(() => {
|
|
323
|
+
s.pendingBytes -= bytes;
|
|
324
|
+
try {
|
|
325
|
+
if (!s.closed && !s.value.exited && s.pendingBytes < 64 * 1024)
|
|
326
|
+
s.pty.resume();
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.failed(s, error);
|
|
330
|
+
}
|
|
331
|
+
this.dirty(s);
|
|
332
|
+
}, error => this.failed(s, error));
|
|
333
|
+
this.dirty(s);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
this.failed(s, error);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
failed(s, error) { if (!s.closed) {
|
|
340
|
+
s.failure = error;
|
|
341
|
+
s.captureTimer?.dispose();
|
|
342
|
+
s.captureTimer = undefined;
|
|
343
|
+
} }
|
|
344
|
+
dirty(s) {
|
|
345
|
+
if (s.closed || s.failure)
|
|
346
|
+
return;
|
|
347
|
+
s.dirty = true;
|
|
348
|
+
if (!s.captureTimer)
|
|
349
|
+
s.captureTimer = this.scheduler.schedule(() => {
|
|
350
|
+
s.captureTimer = undefined;
|
|
351
|
+
void this.ordered(s, async () => { if (s.closed)
|
|
352
|
+
return; await this.settle(s); this.capture(s); }).catch(error => { if (!(error instanceof KeeperMethodError && error.code === 'pty_snapshot_too_large'))
|
|
353
|
+
this.failed(s, error); });
|
|
354
|
+
}, Math.max(0, 50 - (this.now() - s.capturedAt)));
|
|
355
|
+
}
|
|
356
|
+
async captureDue(s) {
|
|
357
|
+
if (s.dirty && this.now() - s.capturedAt < 50) {
|
|
358
|
+
s.captureTimer?.dispose();
|
|
359
|
+
s.captureTimer = undefined;
|
|
360
|
+
await new Promise(resolve => this.scheduler.schedule(resolve, 50 - (this.now() - s.capturedAt)));
|
|
361
|
+
}
|
|
362
|
+
await this.settle(s);
|
|
363
|
+
this.capture(s, true);
|
|
364
|
+
}
|
|
365
|
+
capture(s, force = false) {
|
|
366
|
+
if (!s.dirty || (!force && this.now() - s.capturedAt < 50))
|
|
367
|
+
return;
|
|
368
|
+
s.frames.publish(s.screen.capture(), this.descriptor(s), s.geometry);
|
|
369
|
+
s.dirty = false;
|
|
370
|
+
s.capturedAt = this.now();
|
|
371
|
+
s.captureTimer?.dispose();
|
|
372
|
+
s.captureTimer = undefined;
|
|
373
|
+
}
|
|
374
|
+
async capacity(s) {
|
|
375
|
+
if (!s.attachments.size)
|
|
376
|
+
return;
|
|
377
|
+
const cols = Math.min(...[...s.attachments.values()].map(a => a.cols));
|
|
378
|
+
const rows = Math.min(...[...s.attachments.values()].map(a => a.rows));
|
|
379
|
+
if (cols === s.value.cols && rows === s.value.rows)
|
|
380
|
+
return;
|
|
381
|
+
await this.settle(s);
|
|
382
|
+
if (!s.value.exited)
|
|
383
|
+
s.pty.resize(cols, rows);
|
|
384
|
+
try {
|
|
385
|
+
await s.screen.resize(cols, rows);
|
|
386
|
+
await this.settle(s);
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
// A native emulator failure may have changed part of the grid. Never publish or accept input against uncertain geometry.
|
|
390
|
+
this.failed(s, error);
|
|
391
|
+
try {
|
|
392
|
+
if (!s.value.exited)
|
|
393
|
+
s.pty.resize(s.value.cols, s.value.rows);
|
|
394
|
+
}
|
|
395
|
+
catch (rollback) {
|
|
396
|
+
this.failed(s, rollback);
|
|
397
|
+
}
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
s.value.cols = cols;
|
|
401
|
+
s.value.rows = rows;
|
|
402
|
+
s.geometry++;
|
|
403
|
+
this.dirty(s);
|
|
404
|
+
}
|
|
405
|
+
inputContext(s) { return { cols: s.value.cols, rows: s.value.rows, geometry: s.geometry, modes: s.screen.state().modes }; }
|
|
406
|
+
coordinates(s, event) {
|
|
407
|
+
if (event.geometry !== s.geometry)
|
|
408
|
+
fail('pty_geometry_mismatch', 'Terminal geometry changed');
|
|
409
|
+
if (event.x >= s.value.cols || event.y >= s.value.rows)
|
|
410
|
+
bad('Terminal coordinates outside geometry');
|
|
411
|
+
}
|
|
412
|
+
assertRunning(s) { if (s.value.exited)
|
|
413
|
+
fail('pty_exited', 'Terminal has exited'); }
|
|
414
|
+
release(s, clientId) {
|
|
415
|
+
if (s.attachments.delete(clientId))
|
|
416
|
+
s.input.release(clientId);
|
|
417
|
+
this.scheduleLease(s);
|
|
418
|
+
}
|
|
419
|
+
async expire(s) {
|
|
420
|
+
let changed = false;
|
|
421
|
+
for (const [id, attachment] of s.attachments)
|
|
422
|
+
if (attachment.expires <= this.now()) {
|
|
423
|
+
this.release(s, id);
|
|
424
|
+
changed = true;
|
|
425
|
+
}
|
|
426
|
+
if (changed)
|
|
427
|
+
await this.capacity(s);
|
|
428
|
+
}
|
|
429
|
+
scheduleLease(s) {
|
|
430
|
+
s.leaseTimer?.dispose();
|
|
431
|
+
s.leaseTimer = undefined;
|
|
432
|
+
if (s.closed || this.disposed || !s.attachments.size)
|
|
433
|
+
return;
|
|
434
|
+
const expires = Math.min(...[...s.attachments.values()].map(attachment => attachment.expires));
|
|
435
|
+
s.leaseTimer = this.scheduler.schedule(() => {
|
|
436
|
+
s.leaseTimer = undefined;
|
|
437
|
+
void this.ordered(s, () => this.expire(s)).catch(error => this.failed(s, error)).finally(() => this.scheduleLease(s));
|
|
438
|
+
}, Math.max(0, expires - this.now()));
|
|
439
|
+
}
|
|
440
|
+
async close(s) {
|
|
441
|
+
if (s.closed)
|
|
442
|
+
return;
|
|
443
|
+
s.closed = true;
|
|
444
|
+
if (this.sessions.get(s.value.id) === s)
|
|
445
|
+
this.sessions.delete(s.value.id);
|
|
446
|
+
s.shellNameTimer?.dispose();
|
|
447
|
+
s.shellNameTimer = undefined;
|
|
448
|
+
s.captureTimer?.dispose();
|
|
449
|
+
s.captureTimer = undefined;
|
|
450
|
+
s.leaseTimer?.dispose();
|
|
451
|
+
s.leaseTimer = undefined;
|
|
452
|
+
for (const listener of s.listeners)
|
|
453
|
+
listener.dispose();
|
|
454
|
+
for (const id of [...s.attachments.keys()])
|
|
455
|
+
this.release(s, id);
|
|
456
|
+
s.input.dispose();
|
|
457
|
+
s.metadata?.dispose?.();
|
|
458
|
+
try {
|
|
459
|
+
if (!s.value.exited)
|
|
460
|
+
s.pty.kill();
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
s.screen.dispose();
|
|
464
|
+
await s.rendered.catch(() => { });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function disposable(value) {
|
|
469
|
+
return value && typeof value.dispose === 'function' ? value : { dispose: () => { } };
|
|
470
|
+
}
|
|
471
|
+
function request(params) { if (Buffer.byteLength(JSON.stringify(params)) > limits.maxRequestBytes)
|
|
472
|
+
bad('Terminal request exceeds limit'); }
|
|
473
|
+
function keys(params, allowed) { if (Object.keys(params).some(key => !allowed.includes(key)))
|
|
474
|
+
bad('Unexpected terminal parameter'); }
|
|
475
|
+
function identifier(params, name) {
|
|
476
|
+
const value = params[name];
|
|
477
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_.:-]{1,128}$/.test(value))
|
|
478
|
+
return bad(`Invalid ${name}`);
|
|
479
|
+
return value;
|
|
480
|
+
}
|
|
481
|
+
function integer(params, name, min, max) {
|
|
482
|
+
const value = params[name];
|
|
483
|
+
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
484
|
+
return bad(`Invalid ${name}`);
|
|
485
|
+
return value;
|
|
486
|
+
}
|
|
487
|
+
function position(params) {
|
|
488
|
+
return { x: integer(params, 'x', 0, limits.maxCols - 1), y: integer(params, 'y', 0, limits.maxRows - 1), geometry: integer(params, 'geometry', 1, Number.MAX_SAFE_INTEGER) };
|
|
489
|
+
}
|
|
490
|
+
function input(params, ref) {
|
|
491
|
+
switch (params.kind) {
|
|
492
|
+
case 'text':
|
|
493
|
+
case 'paste':
|
|
494
|
+
keys(params, [...ref, 'kind', 'text']);
|
|
495
|
+
if (typeof params.text !== 'string' || Buffer.byteLength(params.text) > limits.maxInputBytes)
|
|
496
|
+
return bad('Terminal input exceeds limit');
|
|
497
|
+
return { kind: params.kind, text: params.text };
|
|
498
|
+
case 'key':
|
|
499
|
+
keys(params, [...ref, 'kind', 'usage', 'down', 'modifiers']);
|
|
500
|
+
if (typeof params.down !== 'boolean')
|
|
501
|
+
return bad('Invalid down');
|
|
502
|
+
return { kind: 'key', usage: integer(params, 'usage', 0, 65535), down: params.down, modifiers: integer(params, 'modifiers', 0, 15) };
|
|
503
|
+
case 'pointer':
|
|
504
|
+
case 'button': {
|
|
505
|
+
keys(params, [...ref, 'kind', 'x', 'y', 'geometry', 'modifiers', ...(params.kind === 'button' ? ['button', 'down'] : [])]);
|
|
506
|
+
const point = { ...position(params), modifiers: integer(params, 'modifiers', 0, 15) };
|
|
507
|
+
if (params.kind === 'pointer')
|
|
508
|
+
return { kind: 'pointer', ...point };
|
|
509
|
+
if (typeof params.down !== 'boolean')
|
|
510
|
+
return bad('Invalid down');
|
|
511
|
+
return { kind: 'button', ...point, button: integer(params, 'button', 0, 2), down: params.down };
|
|
512
|
+
}
|
|
513
|
+
case 'release':
|
|
514
|
+
keys(params, [...ref, 'kind']);
|
|
515
|
+
return { kind: 'release' };
|
|
516
|
+
default: return bad('Invalid terminal input kind');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/** This lookup is never awaited by poll/input/activity and never exposes process arguments. */
|
|
520
|
+
function defaultShellProcessName(pid) {
|
|
521
|
+
if (process.platform !== 'darwin')
|
|
522
|
+
return Promise.resolve(null);
|
|
523
|
+
return new Promise(resolve => {
|
|
524
|
+
execFile('ps', ['-o', 'ucomm=', '-p', String(pid)], { timeout: 1000 }, (error, stdout) => resolve(error ? null : stdout.trim() || null));
|
|
525
|
+
});
|
|
526
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { LocalControlError } from './control/protocol.js';
|
|
5
|
+
const schema = z.object({ schemaVersion: z.literal(1), source: z.literal('managed'), prefix: z.string(), nodePath: z.string(), npmPath: z.string(), cliPath: z.string(), entryPath: z.string(), runtimeVersion: z.string().regex(/^24\.\d+\.\d+$/), architecture: z.enum(['arm64', 'x64']) }).strict();
|
|
6
|
+
function plannedRealPath(value) {
|
|
7
|
+
try {
|
|
8
|
+
return realpathSync(value);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code !== 'ENOENT' || path.dirname(value) === value)
|
|
12
|
+
throw error;
|
|
13
|
+
return path.join(plannedRealPath(path.dirname(value)), path.basename(value));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** This synchronous variant is shared by the existing synchronous service-context constructors. */
|
|
17
|
+
export function loadManagedCliInstallSync(packageRoot) {
|
|
18
|
+
let root;
|
|
19
|
+
try {
|
|
20
|
+
root = realpathSync(packageRoot);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT')
|
|
24
|
+
return null;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const prefix = path.resolve(root, '../../../..');
|
|
28
|
+
if (root !== path.join(prefix, 'lib/node_modules/@platless/axom-cli'))
|
|
29
|
+
return null;
|
|
30
|
+
const file = path.join(prefix, 'install.json');
|
|
31
|
+
let found = false;
|
|
32
|
+
try {
|
|
33
|
+
const stat = lstatSync(file);
|
|
34
|
+
found = true;
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16384 || (process.getuid && stat.uid !== process.getuid()) || (stat.mode & 0o077))
|
|
36
|
+
throw Error('Unsafe metadata');
|
|
37
|
+
const value = schema.parse(JSON.parse(readFileSync(file, 'utf8')));
|
|
38
|
+
const home = path.resolve(prefix, '../..');
|
|
39
|
+
const runtime = path.join(home, '.axom/runtime/node', `${value.runtimeVersion}-darwin-${value.architecture}`);
|
|
40
|
+
if (prefix !== path.join(home, '.axom/cli') || realpathSync(value.prefix) !== prefix || realpathSync(value.cliPath) !== path.join(root, 'dist/cli.js') || realpathSync(value.nodePath) !== path.join(runtime, 'bin/node') || realpathSync(value.npmPath) !== path.join(runtime, 'bin/npm') || plannedRealPath(value.entryPath) !== path.join(home, '.local/bin/axom'))
|
|
41
|
+
throw Error('Metadata does not describe this installation');
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!found && error.code === 'ENOENT')
|
|
46
|
+
return null;
|
|
47
|
+
throw new LocalControlError('UNSUPPORTED_INSTALLATION', 'The managed CLI installation needs repair');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function loadManagedCliInstall(packageRoot) { return loadManagedCliInstallSync(packageRoot); }
|
|
51
|
+
export function npmExecutionContext(install, inherited) {
|
|
52
|
+
if (!install)
|
|
53
|
+
return { ...inherited };
|
|
54
|
+
const result = { ...inherited, NPM_CONFIG_PREFIX: install.prefix, PATH: `${path.dirname(install.nodePath)}:${inherited.PATH ?? '/usr/bin:/bin'}` };
|
|
55
|
+
delete result.npm_config_prefix;
|
|
56
|
+
return result;
|
|
57
|
+
}
|