fix(codex): register context7 in TS installer
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
import { appendBlock, findTomlSection } from "./toml-section-editor"
|
||||||
|
|
||||||
|
const CONTEXT7_MCP_SERVER_HEADER = "mcp_servers.context7"
|
||||||
|
const CONTEXT7_MCP_SERVER_BLOCK = [
|
||||||
|
`[${CONTEXT7_MCP_SERVER_HEADER}]`,
|
||||||
|
'command = "npx"',
|
||||||
|
'args = ["-y", "@upstash/context7-mcp", "--api-key", "YOUR_API_KEY"]',
|
||||||
|
"startup_timeout_sec = 20",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
|
||||||
|
export function ensureContext7McpServer(config: string): string {
|
||||||
|
if (findTomlSection(config, CONTEXT7_MCP_SERVER_HEADER)) return config
|
||||||
|
return appendBlock(config, CONTEXT7_MCP_SERVER_BLOCK)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { findTomlSection, removeSetting, replaceOrInsertSetting } from "./toml-section-editor"
|
||||||
|
|
||||||
|
export function ensureAutonomousPermissions(config: string): string {
|
||||||
|
let next = replaceOrInsertRootSetting(config, "approval_policy", JSON.stringify("never"))
|
||||||
|
next = replaceOrInsertRootSetting(next, "sandbox_mode", JSON.stringify("danger-full-access"))
|
||||||
|
next = replaceOrInsertRootSetting(next, "network_access", JSON.stringify("enabled"))
|
||||||
|
next = removeWindowsSandboxSetting(next)
|
||||||
|
next = ensureNoticeEnabled(next, "hide_full_access_warning")
|
||||||
|
return ensureNoticeEnabled(next, "hide_world_writable_warning")
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeWindowsSandboxSetting(config: string): string {
|
||||||
|
const section = findTomlSection(config, "windows")
|
||||||
|
if (!section) return config
|
||||||
|
return removeSetting(config, section, "sandbox")
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureNoticeEnabled(config: string, key: string): string {
|
||||||
|
const section = findTomlSection(config, "notice")
|
||||||
|
if (!section) return appendNoticeBlock(config, key)
|
||||||
|
return replaceOrInsertSetting(config, section, key, "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendNoticeBlock(config: string, key: string): string {
|
||||||
|
return `${config.trimEnd()}${config.trimEnd().length > 0 ? "\n\n" : ""}[notice]\n${key} = true\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceOrInsertRootSetting(config: string, key: string, value: string): string {
|
||||||
|
const sectionStart = findFirstTableStart(config)
|
||||||
|
const root = config.slice(0, sectionStart)
|
||||||
|
const suffix = config.slice(sectionStart)
|
||||||
|
const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m")
|
||||||
|
const replacement = linePattern.test(root)
|
||||||
|
? root.replace(linePattern, `${key} = ${value}`)
|
||||||
|
: `${root.trimEnd()}${root.trimEnd().length > 0 ? "\n" : ""}${key} = ${value}\n`
|
||||||
|
if (suffix.length === 0) return replacement
|
||||||
|
return `${replacement.trimEnd()}\n\n${suffix.trimStart()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstTableStart(config: string): number {
|
||||||
|
const match = config.match(/^[[].*$/m)
|
||||||
|
return match?.index ?? config.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRegExp(value: string): string {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||||
|
}
|
||||||
@@ -111,6 +111,61 @@ describe("codex-config-toml", () => {
|
|||||||
expect(content).not.toContain("max_concurrent_threads_per_session = 4")
|
expect(content).not.toContain("max_concurrent_threads_per_session = 4")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("#given empty Codex config #when updating config #then installs Context7 MCP server", async () => {
|
||||||
|
// given
|
||||||
|
const root = await mkdtemp(join(tmpdir(), "omo-codex-config-context7-"))
|
||||||
|
const configPath = join(root, "config.toml")
|
||||||
|
|
||||||
|
// when
|
||||||
|
await updateCodexConfig({
|
||||||
|
configPath,
|
||||||
|
repoRoot: "/repo/packages/omo-codex",
|
||||||
|
marketplaceName: "debug",
|
||||||
|
marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
|
||||||
|
pluginNames: ["omo"],
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
const content = await readFile(configPath, "utf8")
|
||||||
|
expect(content).toContain("[mcp_servers.context7]")
|
||||||
|
expect(content).toContain('command = "npx"')
|
||||||
|
expect(content).toContain('args = ["-y", "@upstash/context7-mcp", "--api-key", "YOUR_API_KEY"]')
|
||||||
|
expect(content).toContain("startup_timeout_sec = 20")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given existing Context7 MCP server #when updating config #then preserves user server settings", async () => {
|
||||||
|
// given
|
||||||
|
const root = await mkdtemp(join(tmpdir(), "omo-codex-config-context7-existing-"))
|
||||||
|
const configPath = join(root, "config.toml")
|
||||||
|
await writeFile(
|
||||||
|
configPath,
|
||||||
|
[
|
||||||
|
"[mcp_servers.context7]",
|
||||||
|
'command = "node"',
|
||||||
|
'args = ["/opt/context7/server.js"]',
|
||||||
|
"startup_timeout_sec = 40",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
await updateCodexConfig({
|
||||||
|
configPath,
|
||||||
|
repoRoot: "/repo/packages/omo-codex",
|
||||||
|
marketplaceName: "debug",
|
||||||
|
marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
|
||||||
|
pluginNames: ["omo"],
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
const content = await readFile(configPath, "utf8")
|
||||||
|
expect(content).toContain("[mcp_servers.context7]")
|
||||||
|
expect(content).toContain('command = "node"')
|
||||||
|
expect(content).toContain('args = ["/opt/context7/server.js"]')
|
||||||
|
expect(content).toContain("startup_timeout_sec = 40")
|
||||||
|
expect(content).not.toContain("YOUR_API_KEY")
|
||||||
|
})
|
||||||
|
|
||||||
test("#given legacy boolean MultiAgentV2 flag and table #when updating config #then normalizes to table config", async () => {
|
test("#given legacy boolean MultiAgentV2 flag and table #when updating config #then normalizes to table config", async () => {
|
||||||
// given
|
// given
|
||||||
const root = await mkdtemp(join(tmpdir(), "omo-codex-config-multi-agent-legacy-"))
|
const root = await mkdtemp(join(tmpdir(), "omo-codex-config-multi-agent-legacy-"))
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
||||||
import { dirname } from "node:path"
|
import { dirname } from "node:path"
|
||||||
|
import { ensureContext7McpServer } from "./codex-config-mcp"
|
||||||
|
import { ensureAutonomousPermissions } from "./codex-config-permissions"
|
||||||
import { ensureCodexMultiAgentV2Config } from "./codex-multi-agent-v2-config"
|
import { ensureCodexMultiAgentV2Config } from "./codex-multi-agent-v2-config"
|
||||||
import { appendBlock, findTomlSection, removeSetting, replaceOrInsertSetting } from "./toml-section-editor"
|
import { appendBlock, findTomlSection, replaceOrInsertSetting } from "./toml-section-editor"
|
||||||
import type { CodexAgentConfig, CodexMarketplaceSource, TrustedHookState } from "./types"
|
import type { CodexAgentConfig, CodexMarketplaceSource, TrustedHookState } from "./types"
|
||||||
|
|
||||||
const SISYPHUS_LEGACY_MARKETPLACES = ["lazycodex", "code-yeongyu-codex-plugins"] as const
|
const SISYPHUS_LEGACY_MARKETPLACES = ["lazycodex", "code-yeongyu-codex-plugins"] as const
|
||||||
@@ -43,6 +45,7 @@ export async function updateCodexConfig(input: {
|
|||||||
config = ensureFeatureEnabled(config, "plugins")
|
config = ensureFeatureEnabled(config, "plugins")
|
||||||
config = ensureFeatureEnabled(config, "plugin_hooks")
|
config = ensureFeatureEnabled(config, "plugin_hooks")
|
||||||
config = ensureCodexMultiAgentV2Config(config)
|
config = ensureCodexMultiAgentV2Config(config)
|
||||||
|
config = ensureContext7McpServer(config)
|
||||||
if (input.autonomousPermissions === true) config = ensureAutonomousPermissions(config)
|
if (input.autonomousPermissions === true) config = ensureAutonomousPermissions(config)
|
||||||
config = ensureMarketplaceBlock(config, input.marketplaceName, input.marketplaceSource)
|
config = ensureMarketplaceBlock(config, input.marketplaceName, input.marketplaceSource)
|
||||||
for (const pluginName of input.pluginNames) {
|
for (const pluginName of input.pluginNames) {
|
||||||
@@ -111,48 +114,6 @@ function ensureFeatureEnabled(config: string, featureName: string): string {
|
|||||||
return replaceOrInsertSetting(config, section, featureName, "true")
|
return replaceOrInsertSetting(config, section, featureName, "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureAutonomousPermissions(config: string): string {
|
|
||||||
let next = replaceOrInsertRootSetting(config, "approval_policy", JSON.stringify("never"))
|
|
||||||
next = replaceOrInsertRootSetting(next, "sandbox_mode", JSON.stringify("danger-full-access"))
|
|
||||||
next = replaceOrInsertRootSetting(next, "network_access", JSON.stringify("enabled"))
|
|
||||||
next = removeWindowsSandboxSetting(next)
|
|
||||||
next = ensureNoticeEnabled(next, "hide_full_access_warning")
|
|
||||||
return ensureNoticeEnabled(next, "hide_world_writable_warning")
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeWindowsSandboxSetting(config: string): string {
|
|
||||||
const section = findTomlSection(config, "windows")
|
|
||||||
if (!section) return config
|
|
||||||
return removeSetting(config, section, "sandbox")
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureNoticeEnabled(config: string, key: string): string {
|
|
||||||
const section = findTomlSection(config, "notice")
|
|
||||||
if (!section) return appendBlock(config, `[notice]\n${key} = true\n`)
|
|
||||||
return replaceOrInsertSetting(config, section, key, "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceOrInsertRootSetting(config: string, key: string, value: string): string {
|
|
||||||
const sectionStart = findFirstTableStart(config)
|
|
||||||
const root = config.slice(0, sectionStart)
|
|
||||||
const suffix = config.slice(sectionStart)
|
|
||||||
const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m")
|
|
||||||
const replacement = linePattern.test(root)
|
|
||||||
? root.replace(linePattern, `${key} = ${value}`)
|
|
||||||
: `${root.trimEnd()}${root.trimEnd().length > 0 ? "\n" : ""}${key} = ${value}\n`
|
|
||||||
if (suffix.length === 0) return replacement
|
|
||||||
return `${replacement.trimEnd()}\n\n${suffix.trimStart()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function findFirstTableStart(config: string): number {
|
|
||||||
const match = config.match(/^[[].*$/m)
|
|
||||||
return match?.index ?? config.length
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeRegExp(value: string): string {
|
|
||||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureMarketplaceBlock(config: string, marketplaceName: string, source: CodexMarketplaceSource): string {
|
function ensureMarketplaceBlock(config: string, marketplaceName: string, source: CodexMarketplaceSource): string {
|
||||||
const header = `marketplaces.${marketplaceName}`
|
const header = `marketplaces.${marketplaceName}`
|
||||||
const lines = [
|
const lines = [
|
||||||
|
|||||||
Reference in New Issue
Block a user