memorix 1.1.7 → 1.1.8
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/CHANGELOG.md +13 -0
- package/CLAUDE.md +6 -1
- package/README.md +21 -0
- package/README.zh-CN.md +21 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +730 -150
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/index.html +201 -201
- package/dist/dashboard/static/style.css +3584 -3584
- package/dist/index.js +15 -8
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +15 -8
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/SETUP.md +10 -0
- package/docs/dev-log/progress.txt +35 -26
- package/package.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/agent-integrations.ts +623 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +255 -255
- package/src/cli/commands/doctor.ts +23 -0
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +66 -66
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +73 -73
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +96 -96
- package/src/cli/commands/message.ts +121 -121
- package/src/cli/commands/poll.ts +70 -70
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +132 -132
- package/src/cli/commands/repair.ts +60 -0
- package/src/cli/commands/retention.ts +108 -108
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/setup.ts +3 -3
- package/src/cli/commands/skills.ts +123 -123
- package/src/cli/commands/task.ts +192 -192
- package/src/cli/commands/transfer.ts +73 -73
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Panels.tsx +632 -632
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +742 -742
- package/src/cli/tui/data.ts +547 -547
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/theme.ts +178 -178
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/context-pack.ts +5 -1
- package/src/codegraph/lite-provider.ts +5 -1
- package/src/codegraph/project-context.ts +5 -1
- package/src/compact/token-budget.ts +74 -74
- package/src/config/behavior.ts +59 -59
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/static/index.html +201 -201
- package/src/dashboard/static/style.css +3584 -3584
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/git/noise-filter.ts +210 -210
- package/src/hooks/installers/index.ts +4 -4
- package/src/hooks/official-skills.ts +1 -1
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/rules/memorix-agent-rules.md +2 -2
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +107 -107
- package/src/memory/consolidation.ts +302 -302
- package/src/memory/disclosure-policy.ts +141 -141
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +433 -433
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +523 -523
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +380 -380
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +219 -219
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/sdk.ts +327 -327
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/file-lock.ts +100 -100
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/obs-store.ts +255 -255
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/session-store.ts +259 -259
- package/src/store/sqlite-store.ts +339 -339
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +167 -167
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/wiki/generator.ts +237 -237
- package/src/wiki/knowledge-graph.ts +334 -334
- package/src/wiki/types.ts +85 -85
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/wiki/types.ts
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
export interface KnowledgeSourceRef {
|
|
2
|
-
kind: 'observation' | 'mini-skill' | 'git';
|
|
3
|
-
id: string;
|
|
4
|
-
title?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface KnowledgeItem {
|
|
8
|
-
title: string;
|
|
9
|
-
summary: string;
|
|
10
|
-
type: string;
|
|
11
|
-
entityName?: string;
|
|
12
|
-
refs: KnowledgeSourceRef[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface KnowledgeSection {
|
|
16
|
-
id: string;
|
|
17
|
-
title: string;
|
|
18
|
-
items: KnowledgeItem[];
|
|
19
|
-
empty?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ProjectKnowledgeOverview {
|
|
23
|
-
title: 'Knowledge Base';
|
|
24
|
-
subtitle: 'LLM Wiki';
|
|
25
|
-
projectId: string;
|
|
26
|
-
generatedAt: string;
|
|
27
|
-
sections: KnowledgeSection[];
|
|
28
|
-
stats: {
|
|
29
|
-
observationsUsed: number;
|
|
30
|
-
miniSkillsUsed: number;
|
|
31
|
-
refs: number;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// -- Knowledge Graph types --
|
|
36
|
-
|
|
37
|
-
export type SemanticEdgeType = 'supports' | 'relates_to' | 'mentions' | 'derived_from';
|
|
38
|
-
|
|
39
|
-
export interface SemanticNode {
|
|
40
|
-
id: string;
|
|
41
|
-
label: string;
|
|
42
|
-
/** Observation type or 'mini-skill' or 'section-cluster' */
|
|
43
|
-
nodeType: string;
|
|
44
|
-
/** Section this node belongs to (e.g. 'core-decisions') */
|
|
45
|
-
sectionId: string;
|
|
46
|
-
/** Entity name if applicable */
|
|
47
|
-
entityName?: string;
|
|
48
|
-
/** Number of provenance refs / evidence items */
|
|
49
|
-
evidenceCount: number;
|
|
50
|
-
/** Summary text (truncated) */
|
|
51
|
-
summary: string;
|
|
52
|
-
/** Source refs for inspector */
|
|
53
|
-
refs: KnowledgeSourceRef[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface SemanticEdge {
|
|
57
|
-
id: string;
|
|
58
|
-
source: string;
|
|
59
|
-
target: string;
|
|
60
|
-
edgeType: SemanticEdgeType;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface KnowledgeGraphCluster {
|
|
64
|
-
id: string;
|
|
65
|
-
label: string;
|
|
66
|
-
sectionId: string;
|
|
67
|
-
nodeCount: number;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface KnowledgeGraphStats {
|
|
71
|
-
totalNodes: number;
|
|
72
|
-
totalEdges: number;
|
|
73
|
-
clusterCount: number;
|
|
74
|
-
sectionCounts: Record<string, number>;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface ProjectKnowledgeGraph {
|
|
78
|
-
title: 'Knowledge Graph';
|
|
79
|
-
projectId: string;
|
|
80
|
-
generatedAt: string;
|
|
81
|
-
nodes: SemanticNode[];
|
|
82
|
-
edges: SemanticEdge[];
|
|
83
|
-
clusters: KnowledgeGraphCluster[];
|
|
84
|
-
stats: KnowledgeGraphStats;
|
|
85
|
-
}
|
|
1
|
+
export interface KnowledgeSourceRef {
|
|
2
|
+
kind: 'observation' | 'mini-skill' | 'git';
|
|
3
|
+
id: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface KnowledgeItem {
|
|
8
|
+
title: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
type: string;
|
|
11
|
+
entityName?: string;
|
|
12
|
+
refs: KnowledgeSourceRef[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface KnowledgeSection {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
items: KnowledgeItem[];
|
|
19
|
+
empty?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ProjectKnowledgeOverview {
|
|
23
|
+
title: 'Knowledge Base';
|
|
24
|
+
subtitle: 'LLM Wiki';
|
|
25
|
+
projectId: string;
|
|
26
|
+
generatedAt: string;
|
|
27
|
+
sections: KnowledgeSection[];
|
|
28
|
+
stats: {
|
|
29
|
+
observationsUsed: number;
|
|
30
|
+
miniSkillsUsed: number;
|
|
31
|
+
refs: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// -- Knowledge Graph types --
|
|
36
|
+
|
|
37
|
+
export type SemanticEdgeType = 'supports' | 'relates_to' | 'mentions' | 'derived_from';
|
|
38
|
+
|
|
39
|
+
export interface SemanticNode {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
/** Observation type or 'mini-skill' or 'section-cluster' */
|
|
43
|
+
nodeType: string;
|
|
44
|
+
/** Section this node belongs to (e.g. 'core-decisions') */
|
|
45
|
+
sectionId: string;
|
|
46
|
+
/** Entity name if applicable */
|
|
47
|
+
entityName?: string;
|
|
48
|
+
/** Number of provenance refs / evidence items */
|
|
49
|
+
evidenceCount: number;
|
|
50
|
+
/** Summary text (truncated) */
|
|
51
|
+
summary: string;
|
|
52
|
+
/** Source refs for inspector */
|
|
53
|
+
refs: KnowledgeSourceRef[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SemanticEdge {
|
|
57
|
+
id: string;
|
|
58
|
+
source: string;
|
|
59
|
+
target: string;
|
|
60
|
+
edgeType: SemanticEdgeType;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface KnowledgeGraphCluster {
|
|
64
|
+
id: string;
|
|
65
|
+
label: string;
|
|
66
|
+
sectionId: string;
|
|
67
|
+
nodeCount: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface KnowledgeGraphStats {
|
|
71
|
+
totalNodes: number;
|
|
72
|
+
totalEdges: number;
|
|
73
|
+
clusterCount: number;
|
|
74
|
+
sectionCounts: Record<string, number>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ProjectKnowledgeGraph {
|
|
78
|
+
title: 'Knowledge Graph';
|
|
79
|
+
projectId: string;
|
|
80
|
+
generatedAt: string;
|
|
81
|
+
nodes: SemanticNode[];
|
|
82
|
+
edges: SemanticEdge[];
|
|
83
|
+
clusters: KnowledgeGraphCluster[];
|
|
84
|
+
stats: KnowledgeGraphStats;
|
|
85
|
+
}
|
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Codex MCP config adapter.
|
|
7
|
-
* Format: TOML file at ~/.codex/config.toml or .codex/config.toml (project-level)
|
|
8
|
-
*
|
|
9
|
-
* Structure:
|
|
10
|
-
* [mcp_servers.<name>]
|
|
11
|
-
* command = "npx"
|
|
12
|
-
* args = ["-y", "memorix-mcp"]
|
|
13
|
-
* url = "https://..." # for HTTP servers
|
|
14
|
-
*
|
|
15
|
-
* [mcp_servers.<name>.env]
|
|
16
|
-
* KEY = "value"
|
|
17
|
-
*
|
|
18
|
-
* We implement a lightweight TOML parser (no external deps) sufficient
|
|
19
|
-
* for MCP server config blocks. This avoids adding a TOML dependency.
|
|
20
|
-
*/
|
|
21
|
-
export class CodexMCPAdapter implements MCPConfigAdapter {
|
|
22
|
-
readonly source = 'codex' as const;
|
|
23
|
-
|
|
24
|
-
parse(content: string): MCPServerEntry[] {
|
|
25
|
-
if (!content.trim()) return [];
|
|
26
|
-
|
|
27
|
-
const servers: MCPServerEntry[] = [];
|
|
28
|
-
const lines = content.split('\n');
|
|
29
|
-
|
|
30
|
-
let currentServer: string | null = null;
|
|
31
|
-
let isEnvBlock = false;
|
|
32
|
-
const serverMap = new Map<
|
|
33
|
-
string,
|
|
34
|
-
{ command: string; args: string[]; env: Record<string, string>; url?: string; enabled?: boolean }
|
|
35
|
-
>();
|
|
36
|
-
|
|
37
|
-
for (const rawLine of lines) {
|
|
38
|
-
const line = rawLine.trim();
|
|
39
|
-
|
|
40
|
-
// Skip comments and empty lines
|
|
41
|
-
if (!line || line.startsWith('#')) continue;
|
|
42
|
-
|
|
43
|
-
// Match [mcp_servers.<name>.env]
|
|
44
|
-
const envMatch = line.match(/^\[mcp_servers\.([^.\]]+)\.env\]$/);
|
|
45
|
-
if (envMatch) {
|
|
46
|
-
currentServer = envMatch[1];
|
|
47
|
-
isEnvBlock = true;
|
|
48
|
-
if (!serverMap.has(currentServer)) {
|
|
49
|
-
serverMap.set(currentServer, { command: '', args: [], env: {} });
|
|
50
|
-
}
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Match [mcp_servers.<name>]
|
|
55
|
-
const serverMatch = line.match(/^\[mcp_servers\.([^.\]]+)\]$/);
|
|
56
|
-
if (serverMatch) {
|
|
57
|
-
currentServer = serverMatch[1];
|
|
58
|
-
isEnvBlock = false;
|
|
59
|
-
if (!serverMap.has(currentServer)) {
|
|
60
|
-
serverMap.set(currentServer, { command: '', args: [], env: {} });
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Any other section header resets context
|
|
66
|
-
if (line.startsWith('[')) {
|
|
67
|
-
currentServer = null;
|
|
68
|
-
isEnvBlock = false;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Parse key = value within current server block
|
|
73
|
-
if (currentServer) {
|
|
74
|
-
const kvMatch = line.match(/^(\w+)\s*=\s*(.+)$/);
|
|
75
|
-
if (!kvMatch) continue;
|
|
76
|
-
|
|
77
|
-
const key = kvMatch[1];
|
|
78
|
-
const rawValue = kvMatch[2].trim();
|
|
79
|
-
const entry = serverMap.get(currentServer)!;
|
|
80
|
-
|
|
81
|
-
if (isEnvBlock) {
|
|
82
|
-
entry.env[key] = this.parseTomlString(rawValue);
|
|
83
|
-
} else if (key === 'command') {
|
|
84
|
-
entry.command = this.parseTomlString(rawValue);
|
|
85
|
-
} else if (key === 'args') {
|
|
86
|
-
entry.args = this.parseTomlArray(rawValue);
|
|
87
|
-
} else if (key === 'url') {
|
|
88
|
-
entry.url = this.parseTomlString(rawValue);
|
|
89
|
-
} else if (key === 'enabled') {
|
|
90
|
-
entry.enabled = rawValue === 'true';
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
for (const [name, entry] of serverMap) {
|
|
96
|
-
servers.push({
|
|
97
|
-
name,
|
|
98
|
-
command: entry.command,
|
|
99
|
-
args: entry.args,
|
|
100
|
-
...(Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
101
|
-
...(entry.url ? { url: entry.url } : {}),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return servers;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
generate(servers: MCPServerEntry[]): string {
|
|
109
|
-
const blocks: string[] = [];
|
|
110
|
-
|
|
111
|
-
for (const s of servers) {
|
|
112
|
-
const lines: string[] = [];
|
|
113
|
-
lines.push(`[mcp_servers.${s.name}]`);
|
|
114
|
-
|
|
115
|
-
if (s.url) {
|
|
116
|
-
lines.push(`url = ${this.toTomlString(s.url)}`);
|
|
117
|
-
} else {
|
|
118
|
-
lines.push(`command = ${this.toTomlString(s.command)}`);
|
|
119
|
-
lines.push(`args = [${s.args.map((a) => this.toTomlString(a)).join(', ')}]`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (s.env && Object.keys(s.env).length > 0) {
|
|
123
|
-
lines.push('');
|
|
124
|
-
lines.push(`[mcp_servers.${s.name}.env]`);
|
|
125
|
-
for (const [key, value] of Object.entries(s.env)) {
|
|
126
|
-
lines.push(`${key} = ${this.toTomlString(value)}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
blocks.push(lines.join('\n'));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return blocks.join('\n\n') + '\n';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
getConfigPath(projectRoot?: string): string {
|
|
137
|
-
if (projectRoot) {
|
|
138
|
-
return join(projectRoot, '.codex', 'config.toml');
|
|
139
|
-
}
|
|
140
|
-
return join(homedir(), '.codex', 'config.toml');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---- TOML helpers ----
|
|
144
|
-
|
|
145
|
-
private parseTomlString(raw: string): string {
|
|
146
|
-
const trimmed = raw.trim();
|
|
147
|
-
if (
|
|
148
|
-
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
149
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
150
|
-
) {
|
|
151
|
-
return trimmed.slice(1, -1);
|
|
152
|
-
}
|
|
153
|
-
return trimmed;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private parseTomlArray(raw: string): string[] {
|
|
157
|
-
const trimmed = raw.trim();
|
|
158
|
-
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return [];
|
|
159
|
-
const inner = trimmed.slice(1, -1);
|
|
160
|
-
const result: string[] = [];
|
|
161
|
-
// Simple CSV parse respecting quotes
|
|
162
|
-
let current = '';
|
|
163
|
-
let inQuote = false;
|
|
164
|
-
let quoteChar = '';
|
|
165
|
-
for (const ch of inner) {
|
|
166
|
-
if (inQuote) {
|
|
167
|
-
if (ch === quoteChar) {
|
|
168
|
-
inQuote = false;
|
|
169
|
-
} else {
|
|
170
|
-
current += ch;
|
|
171
|
-
}
|
|
172
|
-
} else if (ch === '"' || ch === "'") {
|
|
173
|
-
inQuote = true;
|
|
174
|
-
quoteChar = ch;
|
|
175
|
-
} else if (ch === ',') {
|
|
176
|
-
const val = current.trim();
|
|
177
|
-
if (val) result.push(val);
|
|
178
|
-
current = '';
|
|
179
|
-
} else {
|
|
180
|
-
current += ch;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
const last = current.trim();
|
|
184
|
-
if (last) result.push(last);
|
|
185
|
-
return result;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private toTomlString(value: string): string {
|
|
189
|
-
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
1
|
+
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Codex MCP config adapter.
|
|
7
|
+
* Format: TOML file at ~/.codex/config.toml or .codex/config.toml (project-level)
|
|
8
|
+
*
|
|
9
|
+
* Structure:
|
|
10
|
+
* [mcp_servers.<name>]
|
|
11
|
+
* command = "npx"
|
|
12
|
+
* args = ["-y", "memorix-mcp"]
|
|
13
|
+
* url = "https://..." # for HTTP servers
|
|
14
|
+
*
|
|
15
|
+
* [mcp_servers.<name>.env]
|
|
16
|
+
* KEY = "value"
|
|
17
|
+
*
|
|
18
|
+
* We implement a lightweight TOML parser (no external deps) sufficient
|
|
19
|
+
* for MCP server config blocks. This avoids adding a TOML dependency.
|
|
20
|
+
*/
|
|
21
|
+
export class CodexMCPAdapter implements MCPConfigAdapter {
|
|
22
|
+
readonly source = 'codex' as const;
|
|
23
|
+
|
|
24
|
+
parse(content: string): MCPServerEntry[] {
|
|
25
|
+
if (!content.trim()) return [];
|
|
26
|
+
|
|
27
|
+
const servers: MCPServerEntry[] = [];
|
|
28
|
+
const lines = content.split('\n');
|
|
29
|
+
|
|
30
|
+
let currentServer: string | null = null;
|
|
31
|
+
let isEnvBlock = false;
|
|
32
|
+
const serverMap = new Map<
|
|
33
|
+
string,
|
|
34
|
+
{ command: string; args: string[]; env: Record<string, string>; url?: string; enabled?: boolean }
|
|
35
|
+
>();
|
|
36
|
+
|
|
37
|
+
for (const rawLine of lines) {
|
|
38
|
+
const line = rawLine.trim();
|
|
39
|
+
|
|
40
|
+
// Skip comments and empty lines
|
|
41
|
+
if (!line || line.startsWith('#')) continue;
|
|
42
|
+
|
|
43
|
+
// Match [mcp_servers.<name>.env]
|
|
44
|
+
const envMatch = line.match(/^\[mcp_servers\.([^.\]]+)\.env\]$/);
|
|
45
|
+
if (envMatch) {
|
|
46
|
+
currentServer = envMatch[1];
|
|
47
|
+
isEnvBlock = true;
|
|
48
|
+
if (!serverMap.has(currentServer)) {
|
|
49
|
+
serverMap.set(currentServer, { command: '', args: [], env: {} });
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Match [mcp_servers.<name>]
|
|
55
|
+
const serverMatch = line.match(/^\[mcp_servers\.([^.\]]+)\]$/);
|
|
56
|
+
if (serverMatch) {
|
|
57
|
+
currentServer = serverMatch[1];
|
|
58
|
+
isEnvBlock = false;
|
|
59
|
+
if (!serverMap.has(currentServer)) {
|
|
60
|
+
serverMap.set(currentServer, { command: '', args: [], env: {} });
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Any other section header resets context
|
|
66
|
+
if (line.startsWith('[')) {
|
|
67
|
+
currentServer = null;
|
|
68
|
+
isEnvBlock = false;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse key = value within current server block
|
|
73
|
+
if (currentServer) {
|
|
74
|
+
const kvMatch = line.match(/^(\w+)\s*=\s*(.+)$/);
|
|
75
|
+
if (!kvMatch) continue;
|
|
76
|
+
|
|
77
|
+
const key = kvMatch[1];
|
|
78
|
+
const rawValue = kvMatch[2].trim();
|
|
79
|
+
const entry = serverMap.get(currentServer)!;
|
|
80
|
+
|
|
81
|
+
if (isEnvBlock) {
|
|
82
|
+
entry.env[key] = this.parseTomlString(rawValue);
|
|
83
|
+
} else if (key === 'command') {
|
|
84
|
+
entry.command = this.parseTomlString(rawValue);
|
|
85
|
+
} else if (key === 'args') {
|
|
86
|
+
entry.args = this.parseTomlArray(rawValue);
|
|
87
|
+
} else if (key === 'url') {
|
|
88
|
+
entry.url = this.parseTomlString(rawValue);
|
|
89
|
+
} else if (key === 'enabled') {
|
|
90
|
+
entry.enabled = rawValue === 'true';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const [name, entry] of serverMap) {
|
|
96
|
+
servers.push({
|
|
97
|
+
name,
|
|
98
|
+
command: entry.command,
|
|
99
|
+
args: entry.args,
|
|
100
|
+
...(Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
101
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return servers;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
generate(servers: MCPServerEntry[]): string {
|
|
109
|
+
const blocks: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (const s of servers) {
|
|
112
|
+
const lines: string[] = [];
|
|
113
|
+
lines.push(`[mcp_servers.${s.name}]`);
|
|
114
|
+
|
|
115
|
+
if (s.url) {
|
|
116
|
+
lines.push(`url = ${this.toTomlString(s.url)}`);
|
|
117
|
+
} else {
|
|
118
|
+
lines.push(`command = ${this.toTomlString(s.command)}`);
|
|
119
|
+
lines.push(`args = [${s.args.map((a) => this.toTomlString(a)).join(', ')}]`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
123
|
+
lines.push('');
|
|
124
|
+
lines.push(`[mcp_servers.${s.name}.env]`);
|
|
125
|
+
for (const [key, value] of Object.entries(s.env)) {
|
|
126
|
+
lines.push(`${key} = ${this.toTomlString(value)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
blocks.push(lines.join('\n'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return blocks.join('\n\n') + '\n';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getConfigPath(projectRoot?: string): string {
|
|
137
|
+
if (projectRoot) {
|
|
138
|
+
return join(projectRoot, '.codex', 'config.toml');
|
|
139
|
+
}
|
|
140
|
+
return join(homedir(), '.codex', 'config.toml');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- TOML helpers ----
|
|
144
|
+
|
|
145
|
+
private parseTomlString(raw: string): string {
|
|
146
|
+
const trimmed = raw.trim();
|
|
147
|
+
if (
|
|
148
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
149
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
150
|
+
) {
|
|
151
|
+
return trimmed.slice(1, -1);
|
|
152
|
+
}
|
|
153
|
+
return trimmed;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private parseTomlArray(raw: string): string[] {
|
|
157
|
+
const trimmed = raw.trim();
|
|
158
|
+
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return [];
|
|
159
|
+
const inner = trimmed.slice(1, -1);
|
|
160
|
+
const result: string[] = [];
|
|
161
|
+
// Simple CSV parse respecting quotes
|
|
162
|
+
let current = '';
|
|
163
|
+
let inQuote = false;
|
|
164
|
+
let quoteChar = '';
|
|
165
|
+
for (const ch of inner) {
|
|
166
|
+
if (inQuote) {
|
|
167
|
+
if (ch === quoteChar) {
|
|
168
|
+
inQuote = false;
|
|
169
|
+
} else {
|
|
170
|
+
current += ch;
|
|
171
|
+
}
|
|
172
|
+
} else if (ch === '"' || ch === "'") {
|
|
173
|
+
inQuote = true;
|
|
174
|
+
quoteChar = ch;
|
|
175
|
+
} else if (ch === ',') {
|
|
176
|
+
const val = current.trim();
|
|
177
|
+
if (val) result.push(val);
|
|
178
|
+
current = '';
|
|
179
|
+
} else {
|
|
180
|
+
current += ch;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const last = current.trim();
|
|
184
|
+
if (last) result.push(last);
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private toTomlString(value: string): string {
|
|
189
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
190
|
+
}
|
|
191
|
+
}
|