@octocodeai/octocode-engine 16.5.1 → 16.6.2

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.
Files changed (41) hide show
  1. package/README.md +153 -235
  2. package/dist/lsp/client.d.ts +6 -0
  3. package/dist/lsp/client.js +31 -0
  4. package/dist/lsp/config.d.ts +29 -1
  5. package/dist/lsp/config.js +156 -1
  6. package/dist/lsp/ideContext.d.ts +18 -0
  7. package/dist/lsp/ideContext.js +29 -0
  8. package/dist/lsp/index.d.ts +8 -3
  9. package/dist/lsp/index.js +7 -2
  10. package/dist/lsp/manager.d.ts +11 -0
  11. package/dist/lsp/manager.js +75 -13
  12. package/dist/lsp/native.d.ts +5 -0
  13. package/dist/lsp/platform.d.ts +20 -0
  14. package/dist/lsp/platform.js +64 -0
  15. package/dist/lsp/serverDiscovery.d.ts +63 -0
  16. package/dist/lsp/serverDiscovery.js +226 -0
  17. package/dist/lsp/serverManifest.d.ts +70 -0
  18. package/dist/lsp/serverManifest.js +78 -0
  19. package/dist/lsp/serverManifestData.d.ts +2 -0
  20. package/dist/lsp/serverManifestData.js +33 -0
  21. package/dist/lsp/serverProvisioner.d.ts +19 -0
  22. package/dist/lsp/serverProvisioner.js +253 -0
  23. package/dist/lsp/types.d.ts +7 -0
  24. package/dist/lsp/workspaceRoot.d.ts +1 -1
  25. package/dist/lsp/workspaceRoot.js +3 -1
  26. package/dist/security/commandValidator.js +10 -10
  27. package/dist/security/discoveryFilter.js +7 -3
  28. package/dist/security/filePatterns.js +6 -0
  29. package/dist/security/ignoredPathFilter.js +5 -0
  30. package/dist/security/mask.js +5 -22
  31. package/dist/security/maskUtils.d.ts +6 -0
  32. package/dist/security/maskUtils.js +22 -0
  33. package/dist/security/native.js +5 -22
  34. package/dist/security/pathPatterns.js +5 -0
  35. package/dist/security/pathValidator.js +21 -18
  36. package/dist/security/withSecurityValidation.d.ts +0 -3
  37. package/dist/security/withSecurityValidation.js +3 -21
  38. package/index.cjs +32 -2
  39. package/index.d.ts +113 -0
  40. package/index.js +5 -0
  41. package/package.json +49 -8
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Platformized language-server discovery.
3
+ *
4
+ * The native layer (`config.rs`) already resolves explicit overrides
5
+ * (`OCTOCODE_*_SERVER_PATH`, `.octocode/lsp-servers.json`) and anything on
6
+ * `PATH` via `which`. This module covers the gap that bites in production: a
7
+ * server IS installed, but in a per-ecosystem directory that a GUI-launched
8
+ * IDE's (or a bare `spawn`'s) impoverished `PATH` never sees — `~/.cargo/bin`,
9
+ * `~/go/bin`, `~/.local/bin`, the npm/pnpm global prefixes, the Neovim `mason`
10
+ * dir, the Homebrew prefix, version-manager shims, and the per-OS Windows
11
+ * shim dirs.
12
+ *
13
+ * ## Performance (CLI-critical)
14
+ *
15
+ * `lsp-server list` calls `discoverServer` for every known server with the
16
+ * same workspaceRoot, so cold-start cost matters. Three caches work together:
17
+ *
18
+ * 1. `_discoveryCache` — result per `(command, workspaceRoot)`. After the
19
+ * first call, every subsequent call for the same pair is O(1): zero fs ops.
20
+ *
21
+ * 2. `_existingEcoDirs` — ecosystem dirs filtered to only those that
22
+ * exist on disk. Computed once per process (or after `clearDiscoveryCache`).
23
+ * Turns ~15 dirs × N servers into ~15 stats once + only real-dir probes.
24
+ *
25
+ * 3. `_projectLocalDirs` — resolved project-local path lists per workspace
26
+ * root (avoids re-walking the directory tree).
27
+ *
28
+ * Call `clearDiscoveryCache()` after installing a server mid-session.
29
+ *
30
+ * Search order (first hit wins): project-local → known ecosystem dirs →
31
+ * IDE-bundled extension servers. Returns an absolute path plus a provenance
32
+ * label, or `null` if nothing on the machine provides the command.
33
+ */
34
+ export type DiscoverySource = 'project-local' | `ecosystem:${string}` | 'ide-bundled';
35
+ export interface DiscoveredServer {
36
+ command: string;
37
+ source: DiscoverySource;
38
+ }
39
+ /**
40
+ * Invalidate all discovery caches. Call after programmatically installing a
41
+ * server (e.g. `provisionServer`) or in tests between scenarios.
42
+ */
43
+ export declare function clearDiscoveryCache(): void;
44
+ /**
45
+ * Locate `command` on this machine outside of `PATH`. `command` is the bare
46
+ * server name (e.g. `rust-analyzer`, `gopls`); absolute commands are returned
47
+ * by the caller before this is reached.
48
+ *
49
+ * Results are memoised for the process lifetime. Call `clearDiscoveryCache()`
50
+ * after installing a server mid-session.
51
+ */
52
+ export declare function discoverServer(command: string, workspaceRoot: string): DiscoveredServer | null;
53
+ /**
54
+ * Discover multiple servers in one call, sharing the ecosystem-dir pre-filter
55
+ * and project-local dir computation across all commands. Results are written
56
+ * into the shared cache, so subsequent `discoverServer` calls for the same
57
+ * `(command, workspaceRoot)` pair are O(1).
58
+ *
59
+ * Equivalent to calling `discoverServer` for each command individually, but
60
+ * avoids redundant work when the caller needs results for many commands against
61
+ * the same workspace (e.g. the `lsp-server list` CLI command).
62
+ */
63
+ export declare function discoverServerBatch(commands: string[], workspaceRoot: string): Record<string, DiscoveredServer | null>;
@@ -0,0 +1,226 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { detectIdeContext } from './ideContext.js';
5
+ import { firstExecutableIn } from './platform.js';
6
+ /** Per-command-per-workspace result. `undefined` = not yet looked up. */
7
+ const _discoveryCache = new Map();
8
+ /** Ecosystem dirs that actually exist; `null` = not yet computed. */
9
+ let _existingEcoDirs = null;
10
+ /** Project-local dir lists per resolved workspace root. */
11
+ const _projectLocalDirs = new Map();
12
+ /**
13
+ * Invalidate all discovery caches. Call after programmatically installing a
14
+ * server (e.g. `provisionServer`) or in tests between scenarios.
15
+ */
16
+ export function clearDiscoveryCache() {
17
+ _discoveryCache.clear();
18
+ _existingEcoDirs = null;
19
+ _projectLocalDirs.clear();
20
+ }
21
+ const HOME = homedir();
22
+ const join = path.join;
23
+ function envPath(...segments) {
24
+ return join(...segments);
25
+ }
26
+ /**
27
+ * Every ecosystem `bin` directory worth probing on this OS, paired with a
28
+ * label for provenance. Computed from env vars + canonical defaults — no
29
+ * subprocess spawning, so it stays cheap to call per resolution.
30
+ */
31
+ function ecosystemBinDirs() {
32
+ const env = process.env;
33
+ const out = [];
34
+ const add = (dir, label) => {
35
+ if (dir)
36
+ out.push({ dir, label });
37
+ };
38
+ if (process.platform === 'win32') {
39
+ const appData = env.APPDATA;
40
+ const localAppData = env.LOCALAPPDATA;
41
+ add(env.USERPROFILE && envPath(env.USERPROFILE, '.cargo', 'bin'), 'cargo');
42
+ add(env.USERPROFILE && envPath(env.USERPROFILE, 'go', 'bin'), 'go');
43
+ // Go Windows installer default: C:\Go\bin (distinct from %USERPROFILE%\go\bin)
44
+ add('C:\\Go\\bin', 'go');
45
+ // LLVM/clangd: official Windows installer defaults
46
+ add('C:\\Program Files\\LLVM\\bin', 'llvm');
47
+ add('C:\\Program Files (x86)\\LLVM\\bin', 'llvm');
48
+ // .NET global tools (csharp-ls, OmniSharp, etc.)
49
+ add(env.USERPROFILE && envPath(env.USERPROFILE, '.dotnet', 'tools'), 'dotnet');
50
+ add(appData && envPath(appData, 'npm'), 'npm-global');
51
+ add(localAppData && envPath(localAppData, 'pnpm'), 'pnpm');
52
+ add(env.USERPROFILE && envPath(env.USERPROFILE, 'scoop', 'shims'), 'scoop');
53
+ add(env.ProgramData && envPath(env.ProgramData, 'chocolatey', 'bin'), 'choco');
54
+ add(localAppData && envPath(localAppData, 'Microsoft', 'WinGet', 'Links'), 'winget');
55
+ add(localAppData && envPath(localAppData, 'nvim-data', 'mason', 'bin'), 'mason');
56
+ return out;
57
+ }
58
+ // Rust
59
+ add(envPath(HOME, '.cargo', 'bin'), 'cargo');
60
+ // Go
61
+ add(env.GOBIN, 'go');
62
+ add(env.GOPATH ? envPath(env.GOPATH, 'bin') : undefined, 'go');
63
+ add(envPath(HOME, 'go', 'bin'), 'go');
64
+ // Python user installs
65
+ add(envPath(HOME, '.local', 'bin'), 'python-user');
66
+ // Node global (env-driven prefixes + canonical defaults)
67
+ add(env.npm_config_prefix ? envPath(env.npm_config_prefix, 'bin') : undefined, 'npm-global');
68
+ add(env.PNPM_HOME, 'pnpm');
69
+ add(envPath(HOME, '.volta', 'bin'), 'volta');
70
+ add(envPath(HOME, '.asdf', 'shims'), 'asdf');
71
+ add(envPath(HOME, '.local', 'share', 'mise', 'shims'), 'mise');
72
+ // Neovim mason (high-value on dev machines)
73
+ add(envPath(HOME, '.local', 'share', 'nvim', 'mason', 'bin'), 'mason');
74
+ // Homebrew (Apple Silicon first — the #1 "installed but not found" cause)
75
+ add('/opt/homebrew/bin', 'homebrew');
76
+ add('/usr/local/bin', 'homebrew');
77
+ add('/home/linuxbrew/.linuxbrew/bin', 'homebrew');
78
+ // System binary dirs: clangd ships in /usr/bin on macOS (Xcode CLI tools) and
79
+ // on Linux distros that install it via the package manager. Check after homebrew
80
+ // so a homebrew-managed version takes precedence over the system one.
81
+ add('/usr/bin', 'system');
82
+ // macOS framework Python: ~/Library/Python/<X.Y>/bin
83
+ if (process.platform === 'darwin') {
84
+ add(envPath(HOME, 'Library', 'Python'), 'python-framework');
85
+ }
86
+ // .NET global tools (csharp-ls, OmniSharp, etc.)
87
+ add(envPath(HOME, '.dotnet', 'tools'), 'dotnet');
88
+ // Active virtualenv / conda
89
+ add(env.VIRTUAL_ENV ? envPath(env.VIRTUAL_ENV, 'bin') : undefined, 'venv');
90
+ add(env.CONDA_PREFIX ? envPath(env.CONDA_PREFIX, 'bin') : undefined, 'conda');
91
+ return out;
92
+ }
93
+ /**
94
+ * Ecosystem dirs that exist on this machine, cached for the process lifetime.
95
+ * Pre-filtering eliminates stat calls for non-existent dirs on every lookup —
96
+ * on a typical dev machine this shrinks the probe list from ~15 to ~5.
97
+ */
98
+ function existingEcoDirs() {
99
+ if (!_existingEcoDirs) {
100
+ _existingEcoDirs = ecosystemBinDirs().filter(({ dir }) => existsSync(dir));
101
+ }
102
+ return _existingEcoDirs;
103
+ }
104
+ /** Project-local server dirs, walking up from the workspace root. */
105
+ function buildProjectLocalDirs(workspaceRoot) {
106
+ const dirs = [];
107
+ let current = workspaceRoot; // already resolved by callers
108
+ for (;;) {
109
+ dirs.push(join(current, 'node_modules', '.bin'));
110
+ const parent = path.dirname(current);
111
+ if (parent === current)
112
+ break;
113
+ current = parent;
114
+ }
115
+ const venvBin = process.platform === 'win32' ? 'Scripts' : 'bin';
116
+ dirs.push(join(workspaceRoot, '.venv', venvBin));
117
+ if (process.env.VIRTUAL_ENV)
118
+ dirs.push(join(process.env.VIRTUAL_ENV, venvBin));
119
+ return dirs;
120
+ }
121
+ function cachedProjectLocalDirs(resolvedRoot) {
122
+ let dirs = _projectLocalDirs.get(resolvedRoot);
123
+ if (!dirs) {
124
+ dirs = buildProjectLocalDirs(resolvedRoot);
125
+ _projectLocalDirs.set(resolvedRoot, dirs);
126
+ }
127
+ return dirs;
128
+ }
129
+ /**
130
+ * VS Code (and fork) extension roots whose `<ext>/server/` dirs may hold a
131
+ * reusable server. Skipped unless we detect an IDE host.
132
+ */
133
+ function ideExtensionRoots() {
134
+ const { isIde, host } = detectIdeContext();
135
+ if (!isIde)
136
+ return [];
137
+ const roots = {
138
+ vscode: join(HOME, '.vscode', 'extensions'),
139
+ cursor: join(HOME, '.cursor', 'extensions'),
140
+ windsurf: join(HOME, '.windsurf', 'extensions'),
141
+ };
142
+ const candidates = [roots[host], join(HOME, '.vscode-server', 'extensions')].filter((dir) => Boolean(dir));
143
+ return candidates.filter(existsSync);
144
+ }
145
+ function resolveCommand(command) {
146
+ return path.basename(command);
147
+ }
148
+ function resolveRoot(workspaceRoot) {
149
+ return path.resolve(workspaceRoot);
150
+ }
151
+ function cacheKey(command, resolvedRoot) {
152
+ return `${command}\0${resolvedRoot}`;
153
+ }
154
+ /**
155
+ * Core lookup — runs only on cache miss. Assumes `command` is a basename and
156
+ * `resolvedRoot` is an absolute path.
157
+ */
158
+ function scan(command, resolvedRoot) {
159
+ if (!command)
160
+ return null;
161
+ // 1) Project-local — matches the project's pinned toolchain.
162
+ for (const dir of cachedProjectLocalDirs(resolvedRoot)) {
163
+ const hit = firstExecutableIn(dir, command, join);
164
+ if (hit)
165
+ return { command: hit, source: 'project-local' };
166
+ }
167
+ // 2) Known ecosystem dirs (pre-filtered to existing).
168
+ for (const { dir, label } of existingEcoDirs()) {
169
+ const hit = firstExecutableIn(dir, command, join);
170
+ if (hit)
171
+ return { command: hit, source: `ecosystem:${label}` };
172
+ }
173
+ // 3) IDE-bundled extension servers (only when hosted in an IDE).
174
+ for (const root of ideExtensionRoots()) {
175
+ if (existsSync(root)) {
176
+ // No safe generic launch — see LSP_GUIDE.md "IDE reuse".
177
+ break;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ /**
183
+ * Locate `command` on this machine outside of `PATH`. `command` is the bare
184
+ * server name (e.g. `rust-analyzer`, `gopls`); absolute commands are returned
185
+ * by the caller before this is reached.
186
+ *
187
+ * Results are memoised for the process lifetime. Call `clearDiscoveryCache()`
188
+ * after installing a server mid-session.
189
+ */
190
+ export function discoverServer(command, workspaceRoot) {
191
+ const base = resolveCommand(command);
192
+ const resolved = resolveRoot(workspaceRoot);
193
+ const key = cacheKey(base, resolved);
194
+ if (_discoveryCache.has(key)) {
195
+ return _discoveryCache.get(key) ?? null;
196
+ }
197
+ const result = scan(base, resolved);
198
+ _discoveryCache.set(key, result);
199
+ return result;
200
+ }
201
+ /**
202
+ * Discover multiple servers in one call, sharing the ecosystem-dir pre-filter
203
+ * and project-local dir computation across all commands. Results are written
204
+ * into the shared cache, so subsequent `discoverServer` calls for the same
205
+ * `(command, workspaceRoot)` pair are O(1).
206
+ *
207
+ * Equivalent to calling `discoverServer` for each command individually, but
208
+ * avoids redundant work when the caller needs results for many commands against
209
+ * the same workspace (e.g. the `lsp-server list` CLI command).
210
+ */
211
+ export function discoverServerBatch(commands, workspaceRoot) {
212
+ const resolved = resolveRoot(workspaceRoot);
213
+ const results = {};
214
+ for (const command of commands) {
215
+ const base = resolveCommand(command);
216
+ const key = cacheKey(base, resolved);
217
+ if (_discoveryCache.has(key)) {
218
+ results[command] = _discoveryCache.get(key) ?? null;
219
+ continue;
220
+ }
221
+ const result = scan(base, resolved);
222
+ _discoveryCache.set(key, result);
223
+ results[command] = result;
224
+ }
225
+ return results;
226
+ }
@@ -0,0 +1,70 @@
1
+ import { type PlatformId } from './platform.js';
2
+ /**
3
+ * Download manifest for portable, toolchain-free language servers (the
4
+ * AUTO-DOWNLOAD-OK class). `serverManifest.json` is the human-authored source;
5
+ * `serverManifestData.ts` mirrors it as a TS module so the compiled dist needs
6
+ * no JSON-copy step. This module is the typed loader + per-platform selector +
7
+ * managed-cache locator.
8
+ *
9
+ * Provisioning policy (see docs/context/LSP_GUIDE.md):
10
+ * OCTOCODE_LSP_AUTO_INSTALL = off (default) | prompt | auto
11
+ * Live network download is gated and requires a pinned `sha256` per asset.
12
+ * Until SHAs are pinned the manifest still drives (a) honest detect-and-instruct
13
+ * guidance and (b) reuse of a server a user/CI has pre-populated into the
14
+ * managed cache `~/.octocode/lsp/<server>/<releaseTag>/<binName>`.
15
+ */
16
+ export type ArchiveKind = 'none' | 'gz' | 'zip' | 'tar.gz' | 'tar.xz';
17
+ export interface ManifestAsset {
18
+ url: string;
19
+ archive: ArchiveKind;
20
+ binName: string;
21
+ /** Path of the executable inside the archive (zip/tar); absent for gz/none. */
22
+ binPath?: string;
23
+ /** SHA-256 of the downloaded asset; download is refused while this is null. */
24
+ sha256: string | null;
25
+ }
26
+ export interface ManifestServer {
27
+ languageId: string;
28
+ repo: string;
29
+ releaseTag: string;
30
+ launchArgs?: string[];
31
+ downloadHost?: string;
32
+ platforms: Partial<Record<PlatformId, ManifestAsset>>;
33
+ unsupportedPlatforms?: Partial<Record<PlatformId, string>>;
34
+ }
35
+ export interface ManifestFile {
36
+ $comment?: string;
37
+ version: number;
38
+ servers: Record<string, ManifestServer>;
39
+ }
40
+ export type ProvisionMode = 'off' | 'prompt' | 'auto';
41
+ /** The configured auto-install policy. Defaults to `prompt` when unset (asks before downloading). Set OCTOCODE_LSP_AUTO_INSTALL=off to disable all downloads, or =auto to skip the prompt. */
42
+ export declare function provisionMode(env?: NodeJS.ProcessEnv): ProvisionMode;
43
+ /** The manifest entry for a server, keyed by its bare command name. */
44
+ export declare function manifestServer(serverName: string): ManifestServer | null;
45
+ /** Every auto-downloadable server in the manifest (for `lsp-server list`). */
46
+ export declare function listManifestServers(): Array<{
47
+ name: string;
48
+ languageId: string;
49
+ releaseTag: string;
50
+ }>;
51
+ /** Whether the manifest can, in principle, auto-provide this server. */
52
+ export declare function isAutoDownloadable(serverName: string): boolean;
53
+ /**
54
+ * Root of the managed server cache. Defaults to `~/.octocode/lsp` (consistent
55
+ * with the rest of octocode's home), overridable via `OCTOCODE_LSP_CACHE_DIR`
56
+ * for read-only/ephemeral sandbox HOMEs or to point at a pre-baked image path.
57
+ */
58
+ export declare function managedCacheRoot(env?: NodeJS.ProcessEnv): string;
59
+ /** Where a provisioned binary lives once installed/extracted. */
60
+ export declare function cachedServerBinPath(serverName: string, platformId?: PlatformId): string | null;
61
+ /**
62
+ * If a managed binary is already present AND verified in the cache (downloaded
63
+ * by a prior run, by CI, or pre-baked), return its absolute path. A binary is
64
+ * only trusted when its sibling `<binName>.ok` completion marker exists — a
65
+ * half-written binary from an interrupted install has no marker and is ignored,
66
+ * so it never resolves as "installed". Read-only — always safe to call.
67
+ */
68
+ export declare function resolveCachedServer(serverName: string, platformId?: PlatformId): string | null;
69
+ /** Human-readable provisioning guidance for a server with no resolved binary. */
70
+ export declare function manifestInstallHint(serverName: string): string | null;
@@ -0,0 +1,78 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { detectPlatformId } from './platform.js';
5
+ import { MANIFEST } from './serverManifestData.js';
6
+ function loadManifest() {
7
+ return MANIFEST;
8
+ }
9
+ /** The configured auto-install policy. Defaults to `prompt` when unset (asks before downloading). Set OCTOCODE_LSP_AUTO_INSTALL=off to disable all downloads, or =auto to skip the prompt. */
10
+ export function provisionMode(env = process.env) {
11
+ const raw = (env.OCTOCODE_LSP_AUTO_INSTALL ?? '').trim().toLowerCase();
12
+ if (raw === 'auto' || raw === 'off')
13
+ return raw;
14
+ return 'prompt';
15
+ }
16
+ /** The manifest entry for a server, keyed by its bare command name. */
17
+ export function manifestServer(serverName) {
18
+ return loadManifest().servers[path.basename(serverName)] ?? null;
19
+ }
20
+ /** Every auto-downloadable server in the manifest (for `lsp-server list`). */
21
+ export function listManifestServers() {
22
+ return Object.entries(loadManifest().servers).map(([name, server]) => ({
23
+ name,
24
+ languageId: server.languageId,
25
+ releaseTag: server.releaseTag,
26
+ }));
27
+ }
28
+ /** Whether the manifest can, in principle, auto-provide this server. */
29
+ export function isAutoDownloadable(serverName) {
30
+ return manifestServer(serverName) != null;
31
+ }
32
+ /**
33
+ * Root of the managed server cache. Defaults to `~/.octocode/lsp` (consistent
34
+ * with the rest of octocode's home), overridable via `OCTOCODE_LSP_CACHE_DIR`
35
+ * for read-only/ephemeral sandbox HOMEs or to point at a pre-baked image path.
36
+ */
37
+ export function managedCacheRoot(env = process.env) {
38
+ const override = env.OCTOCODE_LSP_CACHE_DIR?.trim();
39
+ if (override)
40
+ return path.resolve(override);
41
+ return path.join(homedir(), '.octocode', 'lsp');
42
+ }
43
+ /** Where a provisioned binary lives once installed/extracted. */
44
+ export function cachedServerBinPath(serverName, platformId = detectPlatformId()) {
45
+ const server = manifestServer(serverName);
46
+ const asset = server?.platforms[platformId];
47
+ if (!server || !asset)
48
+ return null;
49
+ return path.join(managedCacheRoot(), path.basename(serverName), server.releaseTag, asset.binName);
50
+ }
51
+ /**
52
+ * If a managed binary is already present AND verified in the cache (downloaded
53
+ * by a prior run, by CI, or pre-baked), return its absolute path. A binary is
54
+ * only trusted when its sibling `<binName>.ok` completion marker exists — a
55
+ * half-written binary from an interrupted install has no marker and is ignored,
56
+ * so it never resolves as "installed". Read-only — always safe to call.
57
+ */
58
+ export function resolveCachedServer(serverName, platformId = detectPlatformId()) {
59
+ const binPath = cachedServerBinPath(serverName, platformId);
60
+ if (!binPath)
61
+ return null;
62
+ return existsSync(binPath) && existsSync(`${binPath}.ok`) ? binPath : null;
63
+ }
64
+ /** Human-readable provisioning guidance for a server with no resolved binary. */
65
+ export function manifestInstallHint(serverName) {
66
+ const server = manifestServer(serverName);
67
+ if (!server)
68
+ return null;
69
+ const platformId = detectPlatformId();
70
+ const unsupported = server.unsupportedPlatforms?.[platformId];
71
+ if (unsupported)
72
+ return unsupported;
73
+ const mode = provisionMode();
74
+ if (mode === 'off') {
75
+ return `${serverName} can be auto-downloaded from ${server.repo} (${server.releaseTag}). Re-enable with OCTOCODE_LSP_AUTO_INSTALL=prompt (or =auto), or run \`lsp-server install ${serverName}\`.`;
76
+ }
77
+ return `${serverName} (${server.releaseTag}) will be auto-downloaded on first use from ${server.repo}.`;
78
+ }
@@ -0,0 +1,2 @@
1
+ import type { ManifestFile } from './serverManifest.js';
2
+ export declare const MANIFEST: ManifestFile;
@@ -0,0 +1,33 @@
1
+ export const MANIFEST = {
2
+ "$comment": "Auto-download manifest for portable, toolchain-free language servers. Verified 2026-06-28 against live GitHub Releases. SHA-256 pinned per asset — download is refused if the checksum mismatches. Scope: Rust (rust-analyzer, gz, auto-download ready), C/C++ (clangd, zip, auto-download ready). Toolchain-coupled servers (gopls/go, jdtls/jre) are detect-and-instruct; pure-JS servers (typescript-language-server, pyright, yaml/json/html/css) are bundled npm deps. Markdown/MDX handled by the MINIFIER, NOT LSP.",
3
+ "version": 1,
4
+ "servers": {
5
+ "rust-analyzer": {
6
+ "languageId": "rust",
7
+ "repo": "rust-lang/rust-analyzer",
8
+ "releaseTag": "2026-06-22",
9
+ "platforms": {
10
+ "darwin-arm64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-aarch64-apple-darwin.gz", "archive": "gz", "binName": "rust-analyzer", "sha256": "c8cdf6d5e488752b907d5ee15e31768b59a78d992e9a54b9f9660e1bfdf39f27" },
11
+ "darwin-x64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-x86_64-apple-darwin.gz", "archive": "gz", "binName": "rust-analyzer", "sha256": "feb7c170d2c1a2e4b8a88ac73f937eddb576828e3821b0a63ee0e64bd0bc9440" },
12
+ "linux-x64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-x86_64-unknown-linux-gnu.gz", "archive": "gz", "binName": "rust-analyzer", "sha256": "9602ca5b24dcaa07a5a021274763bed367d8a32da9a226fe3e139de3306569cb" },
13
+ "linux-x64-musl": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-x86_64-unknown-linux-musl.gz", "archive": "gz", "binName": "rust-analyzer", "sha256": "fe1d7b0e9733f7a439e4b6f27b8c4cc7afd87ae28fc5b496eb8df31d674b78dd" },
14
+ "linux-arm64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-aarch64-unknown-linux-gnu.gz", "archive": "gz", "binName": "rust-analyzer", "sha256": "bf65b0d4586f127ab11bf33476dd6aac82dad173946c5d3b1cede19d63ae85ed" },
15
+ "win32-x64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-x86_64-pc-windows-msvc.zip", "archive": "zip", "binPath": "rust-analyzer.exe", "binName": "rust-analyzer.exe", "sha256": "6071dc5b28aa6d22c715f63c08d75b827c066be4ea866796587e52ed48b2922f" },
16
+ "win32-arm64": { "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2026-06-22/rust-analyzer-aarch64-pc-windows-msvc.zip", "archive": "zip", "binPath": "rust-analyzer.exe", "binName": "rust-analyzer.exe", "sha256": "30f873713ea3663db10999c23e95b74fe19968c893d5c0e9b8a896b31dbf8cf8" }
17
+ }
18
+ },
19
+ "clangd": {
20
+ "languageId": "cpp",
21
+ "repo": "clangd/clangd",
22
+ "releaseTag": "22.1.0",
23
+ "launchArgs": [],
24
+ "platforms": {
25
+ "darwin-arm64": { "url": "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-mac-22.1.0.zip", "archive": "zip", "binPath": "clangd_22.1.0/bin/clangd", "binName": "clangd", "sha256": "e31e271fe11f6dcd7cf87ca74be4a12788ff8ce5a0b07762583e335c058e939a" },
26
+ "darwin-x64": { "url": "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-mac-22.1.0.zip", "archive": "zip", "binPath": "clangd_22.1.0/bin/clangd", "binName": "clangd", "sha256": "e31e271fe11f6dcd7cf87ca74be4a12788ff8ce5a0b07762583e335c058e939a" },
27
+ "linux-x64": { "url": "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-linux-22.1.0.zip", "archive": "zip", "binPath": "clangd_22.1.0/bin/clangd", "binName": "clangd", "sha256": "71eddc5303da9a5bc5e8b509488b5b2c5acf45f20e33b8394e71a12a56d67198" },
28
+ "win32-x64": { "url": "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-windows-22.1.0.zip", "archive": "zip", "binPath": "clangd_22.1.0/bin/clangd.exe", "binName": "clangd.exe", "sha256": "c54e57dbff3ccc9e8352367ddb7030ad3f624073ec58c7477424e7919f578572" }
29
+ },
30
+ "unsupportedPlatforms": { "linux-arm64": "clangd publishes no linux-arm64 release asset; install via the system package manager." }
31
+ }
32
+ }
33
+ };
@@ -0,0 +1,19 @@
1
+ import { type PlatformId } from './platform.js';
2
+ import { provisionMode } from './serverManifest.js';
3
+ export interface ProvisionResult {
4
+ ok: boolean;
5
+ path?: string;
6
+ source?: 'already-present' | 'downloaded';
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Provision `serverName` into the managed cache. Idempotent: returns the
11
+ * already-present path when a verified copy exists. Pure-data inputs come from
12
+ * `serverManifest.json`; nothing is executed during install.
13
+ */
14
+ export declare function provisionServer(serverName: string, options?: {
15
+ mode?: ReturnType<typeof provisionMode>;
16
+ signal?: AbortSignal;
17
+ }): Promise<ProvisionResult>;
18
+ /** Remove a server from the managed cache only (never touches external installs). */
19
+ export declare function uninstallServer(serverName: string, platformId?: PlatformId): boolean;