fix(codex): install lsp from local cache
This commit is contained in:
+1
-1
Submodule packages/lsp-tools-mcp updated: 83e634172e...605ecfe3bc
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { mkdir, writeFile } from "node:fs/promises";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join, resolve } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -99,10 +100,17 @@ export async function installMarketplaceLocally(options = {}) {
|
|||||||
for (const legacyMarketplaceName of legacyCacheMarketplaces(marketplace.name)) {
|
for (const legacyMarketplaceName of legacyCacheMarketplaces(marketplace.name)) {
|
||||||
await pruneMarketplacePluginCaches({ codexHome, marketplaceName: legacyMarketplaceName, pluginNames });
|
await pruneMarketplacePluginCaches({ codexHome, marketplaceName: legacyMarketplaceName, pluginNames });
|
||||||
}
|
}
|
||||||
|
const marketplaceRoot = join(codexHome, "plugins", "cache", marketplace.name);
|
||||||
|
await writeCachedMarketplaceManifest({
|
||||||
|
marketplaceName: marketplace.name,
|
||||||
|
marketplaceRoot,
|
||||||
|
plugins: installed,
|
||||||
|
});
|
||||||
await updateCodexConfig({
|
await updateCodexConfig({
|
||||||
configPath: join(codexHome, "config.toml"),
|
configPath: join(codexHome, "config.toml"),
|
||||||
repoRoot: codexPackageRoot,
|
repoRoot: codexPackageRoot,
|
||||||
marketplaceName: marketplace.name,
|
marketplaceName: marketplace.name,
|
||||||
|
marketplaceSource: { sourceType: "local", source: marketplaceRoot },
|
||||||
pluginNames,
|
pluginNames,
|
||||||
trustedHookStates,
|
trustedHookStates,
|
||||||
agentConfigs: [...agentConfigs.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
agentConfigs: [...agentConfigs.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
||||||
@@ -119,6 +127,25 @@ function agentNameFromToml(fileName) {
|
|||||||
return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName;
|
return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeCachedMarketplaceManifest({ marketplaceName, marketplaceRoot, plugins }) {
|
||||||
|
const marketplaceDir = join(marketplaceRoot, ".agents", "plugins");
|
||||||
|
await mkdir(marketplaceDir, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
join(marketplaceDir, "marketplace.json"),
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
name: marketplaceName,
|
||||||
|
plugins: plugins.map((plugin) => ({
|
||||||
|
name: plugin.name,
|
||||||
|
source: { source: "local", path: `./${plugin.name}/${plugin.version}` },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
"\t",
|
||||||
|
)}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function nonEmptyEnvValue(env, key) {
|
function nonEmptyEnvValue(env, key) {
|
||||||
const value = env[key];
|
const value = env[key];
|
||||||
if (typeof value !== "string") return undefined;
|
if (typeof value !== "string") return undefined;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ test("#given local marketplace #when installing #then copies versioned plugins a
|
|||||||
assert.doesNotMatch(config, /stale@debug-marketplace/);
|
assert.doesNotMatch(config, /stale@debug-marketplace/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#given sisyphuslabs marketplace #when installing #then registers sisyphuslabs omo git source", async () => {
|
test("#given sisyphuslabs marketplace #when installing #then registers the local built marketplace cache", async () => {
|
||||||
const repoRoot = await makeTempDir();
|
const repoRoot = await makeTempDir();
|
||||||
const codexHome = await makeTempDir();
|
const codexHome = await makeTempDir();
|
||||||
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
||||||
@@ -216,6 +216,17 @@ test("#given sisyphuslabs marketplace #when installing #then registers sisyphusl
|
|||||||
plugins: [{ name: "omo", source: "./plugins/omo" }],
|
plugins: [{ name: "omo", source: "./plugins/omo" }],
|
||||||
});
|
});
|
||||||
await writePluginAt(join(codexPackageRoot, "plugin"), "omo", "0.1.0");
|
await writePluginAt(join(codexPackageRoot, "plugin"), "omo", "0.1.0");
|
||||||
|
await mkdir(join(codexPackageRoot, "plugin", "components", "lsp", "dist"), { recursive: true });
|
||||||
|
await writeFile(join(codexPackageRoot, "plugin", "components", "lsp", "dist", "cli.js"), "#!/usr/bin/env node\n");
|
||||||
|
await writeJson(join(codexPackageRoot, "plugin", ".mcp.json"), {
|
||||||
|
mcpServers: {
|
||||||
|
lsp: {
|
||||||
|
command: "node",
|
||||||
|
args: ["./components/lsp/dist/cli.js", "mcp"],
|
||||||
|
cwd: ".",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
await mkdir(join(codexHome, "plugins", "cache", legacyCodexPluginMarketplace, "omo", "0.1.0"), {
|
await mkdir(join(codexHome, "plugins", "cache", legacyCodexPluginMarketplace, "omo", "0.1.0"), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
});
|
});
|
||||||
@@ -257,14 +268,25 @@ test("#given sisyphuslabs marketplace #when installing #then registers sisyphusl
|
|||||||
|
|
||||||
const config = await readFile(join(codexHome, "config.toml"), "utf8");
|
const config = await readFile(join(codexHome, "config.toml"), "utf8");
|
||||||
assert.match(config, /\[marketplaces\.sisyphuslabs\]/);
|
assert.match(config, /\[marketplaces\.sisyphuslabs\]/);
|
||||||
assert.match(config, /source_type = "git"/);
|
assert.match(config, /source_type = "local"/);
|
||||||
assert.match(config, /source = "https:\/\/github\.com\/sisyphuslabs\/omo\.git"/);
|
assert.match(config, new RegExp(`source = ${JSON.stringify(join(codexHome, "plugins", "cache", "sisyphuslabs")).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
||||||
assert.match(config, /ref = "main"/);
|
assert.doesNotMatch(config, /ref = "main"/);
|
||||||
assert.match(config, /\[plugins\."omo@sisyphuslabs"\]\nenabled = true/);
|
assert.match(config, /\[plugins\."omo@sisyphuslabs"\]\nenabled = true/);
|
||||||
assert.doesNotMatch(config, /\[marketplaces\.lazycodex\]/);
|
assert.doesNotMatch(config, /\[marketplaces\.lazycodex\]/);
|
||||||
assert.doesNotMatch(config, new RegExp(legacyCodexPluginMarketplace));
|
assert.doesNotMatch(config, new RegExp(legacyCodexPluginMarketplace));
|
||||||
assert.doesNotMatch(config, /lazycodex\.git/);
|
assert.doesNotMatch(config, /lazycodex\.git/);
|
||||||
assert.doesNotMatch(config, /source_type = "local"/);
|
const marketplace = JSON.parse(
|
||||||
|
await readFile(join(codexHome, "plugins", "cache", "sisyphuslabs", ".agents", "plugins", "marketplace.json"), "utf8"),
|
||||||
|
);
|
||||||
|
assert.deepEqual(marketplace.plugins, [{ name: "omo", source: { source: "local", path: "./omo/0.1.0" } }]);
|
||||||
|
const cachedMcp = JSON.parse(
|
||||||
|
await readFile(join(codexHome, "plugins", "cache", "sisyphuslabs", "omo", "0.1.0", ".mcp.json"), "utf8"),
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
cachedMcp.mcpServers.lsp.args[0],
|
||||||
|
join(codexHome, "plugins", "cache", "sisyphuslabs", "omo", "0.1.0", "components", "lsp", "dist", "cli.js"),
|
||||||
|
);
|
||||||
|
assert.equal((await stat(cachedMcp.mcpServers.lsp.args[0])).isFile(), true);
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
stat(join(codexHome, "plugins", "cache", legacyCodexPluginMarketplace, "omo")),
|
stat(join(codexHome, "plugins", "cache", legacyCodexPluginMarketplace, "omo")),
|
||||||
/code: 'ENOENT'|ENOENT/,
|
/code: 'ENOENT'|ENOENT/,
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ describe("install-codex", () => {
|
|||||||
const configContent = await readFile(join(codexHome, "config.toml"), "utf8")
|
const configContent = await readFile(join(codexHome, "config.toml"), "utf8")
|
||||||
expect(configContent).toContain("[features]")
|
expect(configContent).toContain("[features]")
|
||||||
expect(configContent).toContain("[marketplaces.sisyphuslabs]")
|
expect(configContent).toContain("[marketplaces.sisyphuslabs]")
|
||||||
expect(configContent).toContain('source_type = "git"')
|
expect(configContent).toContain('source_type = "local"')
|
||||||
expect(configContent).toContain('source = "https://github.com/code-yeongyu/lazycodex.git"')
|
expect(configContent).toContain(`source = "${join(codexHome, "plugins", "cache", "sisyphuslabs")}"`)
|
||||||
expect(configContent).toContain('ref = "main"')
|
expect(configContent).not.toContain('ref = "main"')
|
||||||
expect(configContent).toContain("[plugins.\"omo@sisyphuslabs\"]")
|
expect(configContent).toContain("[plugins.\"omo@sisyphuslabs\"]")
|
||||||
expect(configContent).toContain("[hooks.state.")
|
expect(configContent).toContain("[hooks.state.")
|
||||||
expect(configContent).toContain("[agents.explorer]")
|
expect(configContent).toContain("[agents.explorer]")
|
||||||
@@ -87,9 +87,18 @@ describe("install-codex", () => {
|
|||||||
expect(pluginPath).toContain(join("plugins", "cache", "sisyphuslabs", "omo"))
|
expect(pluginPath).toContain(join("plugins", "cache", "sisyphuslabs", "omo"))
|
||||||
const stats = await stat(pluginPath ?? "")
|
const stats = await stat(pluginPath ?? "")
|
||||||
expect(stats.isDirectory()).toBe(true)
|
expect(stats.isDirectory()).toBe(true)
|
||||||
|
const mcpManifest = JSON.parse(await readFile(join(pluginPath ?? "", ".mcp.json"), "utf8")) as {
|
||||||
|
mcpServers: { lsp: { args: string[] } }
|
||||||
|
}
|
||||||
|
expect(mcpManifest.mcpServers.lsp.args[0]).toBe(join(pluginPath ?? "", "components", "lsp", "dist", "cli.js"))
|
||||||
|
expect((await stat(mcpManifest.mcpServers.lsp.args[0] ?? "")).isFile()).toBe(true)
|
||||||
expect((await stat(join(codexHome, "agents", "explorer.toml"))).isFile()).toBe(true)
|
expect((await stat(join(codexHome, "agents", "explorer.toml"))).isFile()).toBe(true)
|
||||||
expect((await stat(join(codexHome, "agents", "librarian.toml"))).isFile()).toBe(true)
|
expect((await stat(join(codexHome, "agents", "librarian.toml"))).isFile()).toBe(true)
|
||||||
expect((await stat(join(codexHome, "agents", "plan.toml"))).isFile()).toBe(true)
|
expect((await stat(join(codexHome, "agents", "plan.toml"))).isFile()).toBe(true)
|
||||||
|
const marketplace = JSON.parse(
|
||||||
|
await readFile(join(codexHome, "plugins", "cache", "sisyphuslabs", ".agents", "plugins", "marketplace.json"), "utf8"),
|
||||||
|
) as { plugins: Array<{ name: string; source: { source: string; path: string } }> }
|
||||||
|
expect(marketplace.plugins).toEqual([{ name: "omo", source: { source: "local", path: "./omo/0.1.0" } }])
|
||||||
await expect(stat(join(codexHome, "plugins", "cache", "code-yeongyu-codex-plugins", "omo"))).rejects.toThrow()
|
await expect(stat(join(codexHome, "plugins", "cache", "code-yeongyu-codex-plugins", "omo"))).rejects.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { homedir } from "node:os"
|
import { homedir } from "node:os"
|
||||||
import { join, resolve } from "node:path"
|
import { join, resolve } from "node:path"
|
||||||
import { existsSync } from "node:fs"
|
import { existsSync } from "node:fs"
|
||||||
|
import { mkdir, writeFile } from "node:fs/promises"
|
||||||
import { installCachedPlugin, linkCachedPluginBins, pruneMarketplaceCache, pruneMarketplacePluginCaches } from "./codex-cache"
|
import { installCachedPlugin, linkCachedPluginBins, pruneMarketplaceCache, pruneMarketplacePluginCaches } from "./codex-cache"
|
||||||
import { updateCodexConfig } from "./codex-config-toml"
|
import { updateCodexConfig } from "./codex-config-toml"
|
||||||
import { trustedHookStatesForPlugin } from "./codex-hook-trust"
|
import { trustedHookStatesForPlugin } from "./codex-hook-trust"
|
||||||
@@ -9,11 +10,6 @@ import { readMarketplace, readPluginManifest, resolvePluginSource, validatePathS
|
|||||||
import { defaultRunCommand } from "./codex-process"
|
import { defaultRunCommand } from "./codex-process"
|
||||||
import type { CodexInstallOptions, CodexInstallResult, InstalledPlugin } from "./types"
|
import type { CodexInstallOptions, CodexInstallResult, InstalledPlugin } from "./types"
|
||||||
|
|
||||||
const LAZYCODEX_MARKETPLACE_SOURCE = {
|
|
||||||
sourceType: "git",
|
|
||||||
source: "https://github.com/code-yeongyu/lazycodex.git",
|
|
||||||
ref: "main",
|
|
||||||
} as const
|
|
||||||
const SISYPHUS_LEGACY_CACHE_MARKETPLACES = ["lazycodex", "code-yeongyu-codex-plugins"] as const
|
const SISYPHUS_LEGACY_CACHE_MARKETPLACES = ["lazycodex", "code-yeongyu-codex-plugins"] as const
|
||||||
|
|
||||||
export async function runCodexInstaller(options: CodexInstallOptions = {}): Promise<CodexInstallResult> {
|
export async function runCodexInstaller(options: CodexInstallOptions = {}): Promise<CodexInstallResult> {
|
||||||
@@ -90,12 +86,19 @@ export async function runCodexInstaller(options: CodexInstallOptions = {}): Prom
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const marketplaceRoot = join(codexHome, "plugins", "cache", marketplace.name)
|
||||||
|
await writeCachedMarketplaceManifest({
|
||||||
|
marketplaceName: marketplace.name,
|
||||||
|
marketplaceRoot,
|
||||||
|
plugins: installed,
|
||||||
|
})
|
||||||
|
|
||||||
const configPath = join(codexHome, "config.toml")
|
const configPath = join(codexHome, "config.toml")
|
||||||
await updateCodexConfig({
|
await updateCodexConfig({
|
||||||
configPath,
|
configPath,
|
||||||
repoRoot: codexPackageRoot,
|
repoRoot: codexPackageRoot,
|
||||||
marketplaceName: marketplace.name,
|
marketplaceName: marketplace.name,
|
||||||
marketplaceSource: LAZYCODEX_MARKETPLACE_SOURCE,
|
marketplaceSource: { sourceType: "local", source: marketplaceRoot },
|
||||||
pluginNames: marketplace.plugins.map((plugin) => plugin.name),
|
pluginNames: marketplace.plugins.map((plugin) => plugin.name),
|
||||||
trustedHookStates,
|
trustedHookStates,
|
||||||
agentConfigs: [...agentConfigs.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
agentConfigs: [...agentConfigs.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
||||||
@@ -131,6 +134,29 @@ function agentNameFromToml(fileName: string): string {
|
|||||||
return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName
|
return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeCachedMarketplaceManifest(input: {
|
||||||
|
readonly marketplaceName: string
|
||||||
|
readonly marketplaceRoot: string
|
||||||
|
readonly plugins: readonly InstalledPlugin[]
|
||||||
|
}): Promise<void> {
|
||||||
|
const marketplaceDir = join(input.marketplaceRoot, ".agents", "plugins")
|
||||||
|
await mkdir(marketplaceDir, { recursive: true })
|
||||||
|
await writeFile(
|
||||||
|
join(marketplaceDir, "marketplace.json"),
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
name: input.marketplaceName,
|
||||||
|
plugins: input.plugins.map((plugin) => ({
|
||||||
|
name: plugin.name,
|
||||||
|
source: { source: "local", path: `./${plugin.name}/${plugin.version}` },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
"\t",
|
||||||
|
)}\n`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function legacyCacheMarketplaces(marketplaceName: string): readonly string[] {
|
function legacyCacheMarketplaces(marketplaceName: string): readonly string[] {
|
||||||
return marketplaceName === "sisyphuslabs" ? SISYPHUS_LEGACY_CACHE_MARKETPLACES : []
|
return marketplaceName === "sisyphuslabs" ? SISYPHUS_LEGACY_CACHE_MARKETPLACES : []
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user