memorix 1.2.2 → 1.2.3
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 +7 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +34 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +7 -0
- package/dist/sdk.js +17 -8
- package/dist/sdk.js.map +1 -1
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/dev-log/progress.txt +18 -8
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +54 -54
- 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 +66 -66
- 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 +82 -82
- package/src/cli/commands/message.ts +104 -104
- package/src/cli/commands/poll.ts +58 -58
- 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 +118 -118
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +15 -7
- package/src/cli/commands/skills.ts +114 -114
- package/src/cli/commands/task.ts +167 -167
- package/src/cli/commands/transfer.ts +47 -47
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- 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/Sidebar.tsx +179 -179
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/session-service.ts +3 -2
- 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/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- 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/hooks/pattern-detector.ts +173 -173
- 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/disclosure-policy.ts +135 -135
- 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/secret-filter.ts +79 -79
- package/src/memory/session.ts +24 -9
- 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 +343 -343
- 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/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 +299 -299
- 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/server.ts +7 -2
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- 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/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +161 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- 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
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Cursor MCP config adapter.
|
|
7
|
-
* Format: JSON file at ~/.cursor/mcp.json or .cursor/mcp.json (project-level)
|
|
8
|
-
* Structure: { mcpServers: { [name]: { command, args, env?, url? } } }
|
|
9
|
-
*/
|
|
10
|
-
export class CursorMCPAdapter implements MCPConfigAdapter {
|
|
11
|
-
readonly source = 'cursor' as const;
|
|
12
|
-
|
|
13
|
-
parse(content: string): MCPServerEntry[] {
|
|
14
|
-
try {
|
|
15
|
-
const config = JSON.parse(content);
|
|
16
|
-
const servers = config.mcpServers ?? {};
|
|
17
|
-
return Object.entries(servers).map(([name, entry]: [string, any]) => ({
|
|
18
|
-
name,
|
|
19
|
-
command: entry.command ?? '',
|
|
20
|
-
args: entry.args ?? [],
|
|
21
|
-
...(entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
22
|
-
...(entry.url ? { url: entry.url } : {}),
|
|
23
|
-
}));
|
|
24
|
-
} catch {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
generate(servers: MCPServerEntry[]): string {
|
|
30
|
-
const mcpServers: Record<string, any> = {};
|
|
31
|
-
for (const s of servers) {
|
|
32
|
-
const entry: Record<string, any> = {};
|
|
33
|
-
if (s.url) {
|
|
34
|
-
entry.url = s.url;
|
|
35
|
-
} else {
|
|
36
|
-
entry.command = s.command;
|
|
37
|
-
entry.args = s.args;
|
|
38
|
-
}
|
|
39
|
-
if (s.env && Object.keys(s.env).length > 0) {
|
|
40
|
-
entry.env = s.env;
|
|
41
|
-
}
|
|
42
|
-
mcpServers[s.name] = entry;
|
|
43
|
-
}
|
|
44
|
-
return JSON.stringify({ mcpServers }, null, 2);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getConfigPath(projectRoot?: string): string {
|
|
48
|
-
if (projectRoot) {
|
|
49
|
-
return join(projectRoot, '.cursor', 'mcp.json');
|
|
50
|
-
}
|
|
51
|
-
return join(homedir(), '.cursor', 'mcp.json');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cursor MCP config adapter.
|
|
7
|
+
* Format: JSON file at ~/.cursor/mcp.json or .cursor/mcp.json (project-level)
|
|
8
|
+
* Structure: { mcpServers: { [name]: { command, args, env?, url? } } }
|
|
9
|
+
*/
|
|
10
|
+
export class CursorMCPAdapter implements MCPConfigAdapter {
|
|
11
|
+
readonly source = 'cursor' as const;
|
|
12
|
+
|
|
13
|
+
parse(content: string): MCPServerEntry[] {
|
|
14
|
+
try {
|
|
15
|
+
const config = JSON.parse(content);
|
|
16
|
+
const servers = config.mcpServers ?? {};
|
|
17
|
+
return Object.entries(servers).map(([name, entry]: [string, any]) => ({
|
|
18
|
+
name,
|
|
19
|
+
command: entry.command ?? '',
|
|
20
|
+
args: entry.args ?? [],
|
|
21
|
+
...(entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
22
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
23
|
+
}));
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
generate(servers: MCPServerEntry[]): string {
|
|
30
|
+
const mcpServers: Record<string, any> = {};
|
|
31
|
+
for (const s of servers) {
|
|
32
|
+
const entry: Record<string, any> = {};
|
|
33
|
+
if (s.url) {
|
|
34
|
+
entry.url = s.url;
|
|
35
|
+
} else {
|
|
36
|
+
entry.command = s.command;
|
|
37
|
+
entry.args = s.args;
|
|
38
|
+
}
|
|
39
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
40
|
+
entry.env = s.env;
|
|
41
|
+
}
|
|
42
|
+
mcpServers[s.name] = entry;
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify({ mcpServers }, null, 2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getConfigPath(projectRoot?: string): string {
|
|
48
|
+
if (projectRoot) {
|
|
49
|
+
return join(projectRoot, '.cursor', 'mcp.json');
|
|
50
|
+
}
|
|
51
|
+
return join(homedir(), '.cursor', 'mcp.json');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Kiro MCP config adapter.
|
|
7
|
-
* Format: JSON file at .kiro/settings/mcp.json (project-level)
|
|
8
|
-
* or ~/.kiro/settings/mcp.json (user-level)
|
|
9
|
-
* Structure: { mcpServers: { ... }, powers?: { mcpServers: { ... } } }
|
|
10
|
-
*
|
|
11
|
-
* Kiro stores user-added servers in `mcpServers` and power-installed
|
|
12
|
-
* servers (context7, figma, postman, supabase etc.) in `powers.mcpServers`.
|
|
13
|
-
* Both sections use the same entry format.
|
|
14
|
-
*
|
|
15
|
-
* Source: Kiro official MCP documentation.
|
|
16
|
-
*/
|
|
17
|
-
export class KiroMCPAdapter implements MCPConfigAdapter {
|
|
18
|
-
readonly source = 'kiro' as const;
|
|
19
|
-
|
|
20
|
-
parse(content: string): MCPServerEntry[] {
|
|
21
|
-
try {
|
|
22
|
-
const config = JSON.parse(content);
|
|
23
|
-
// Merge top-level mcpServers and powers.mcpServers (dedup by name)
|
|
24
|
-
const topLevel = config.mcpServers ?? {};
|
|
25
|
-
const powers = config.powers?.mcpServers ?? {};
|
|
26
|
-
const merged = { ...powers, ...topLevel };
|
|
27
|
-
return Object.entries(merged).map(([name, entry]: [string, any]) => ({
|
|
28
|
-
name,
|
|
29
|
-
command: entry.command ?? '',
|
|
30
|
-
args: entry.args ?? [],
|
|
31
|
-
...(entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
32
|
-
...(entry.url ? { url: entry.url } : {}),
|
|
33
|
-
...(entry.disabled === true ? { disabled: true } : {}),
|
|
34
|
-
}));
|
|
35
|
-
} catch {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
generate(servers: MCPServerEntry[]): string {
|
|
41
|
-
const mcpServers: Record<string, any> = {};
|
|
42
|
-
for (const s of servers) {
|
|
43
|
-
const entry: Record<string, any> = {};
|
|
44
|
-
if (s.url) {
|
|
45
|
-
entry.url = s.url;
|
|
46
|
-
} else {
|
|
47
|
-
entry.command = s.command;
|
|
48
|
-
entry.args = s.args;
|
|
49
|
-
}
|
|
50
|
-
if (s.env && Object.keys(s.env).length > 0) {
|
|
51
|
-
entry.env = s.env;
|
|
52
|
-
}
|
|
53
|
-
mcpServers[s.name] = entry;
|
|
54
|
-
}
|
|
55
|
-
return JSON.stringify({ mcpServers }, null, 2);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getConfigPath(projectRoot?: string): string {
|
|
59
|
-
if (projectRoot) {
|
|
60
|
-
return join(projectRoot, '.kiro', 'settings', 'mcp.json');
|
|
61
|
-
}
|
|
62
|
-
return join(homedir(), '.kiro', 'settings', 'mcp.json');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kiro MCP config adapter.
|
|
7
|
+
* Format: JSON file at .kiro/settings/mcp.json (project-level)
|
|
8
|
+
* or ~/.kiro/settings/mcp.json (user-level)
|
|
9
|
+
* Structure: { mcpServers: { ... }, powers?: { mcpServers: { ... } } }
|
|
10
|
+
*
|
|
11
|
+
* Kiro stores user-added servers in `mcpServers` and power-installed
|
|
12
|
+
* servers (context7, figma, postman, supabase etc.) in `powers.mcpServers`.
|
|
13
|
+
* Both sections use the same entry format.
|
|
14
|
+
*
|
|
15
|
+
* Source: Kiro official MCP documentation.
|
|
16
|
+
*/
|
|
17
|
+
export class KiroMCPAdapter implements MCPConfigAdapter {
|
|
18
|
+
readonly source = 'kiro' as const;
|
|
19
|
+
|
|
20
|
+
parse(content: string): MCPServerEntry[] {
|
|
21
|
+
try {
|
|
22
|
+
const config = JSON.parse(content);
|
|
23
|
+
// Merge top-level mcpServers and powers.mcpServers (dedup by name)
|
|
24
|
+
const topLevel = config.mcpServers ?? {};
|
|
25
|
+
const powers = config.powers?.mcpServers ?? {};
|
|
26
|
+
const merged = { ...powers, ...topLevel };
|
|
27
|
+
return Object.entries(merged).map(([name, entry]: [string, any]) => ({
|
|
28
|
+
name,
|
|
29
|
+
command: entry.command ?? '',
|
|
30
|
+
args: entry.args ?? [],
|
|
31
|
+
...(entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {}),
|
|
32
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
33
|
+
...(entry.disabled === true ? { disabled: true } : {}),
|
|
34
|
+
}));
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
generate(servers: MCPServerEntry[]): string {
|
|
41
|
+
const mcpServers: Record<string, any> = {};
|
|
42
|
+
for (const s of servers) {
|
|
43
|
+
const entry: Record<string, any> = {};
|
|
44
|
+
if (s.url) {
|
|
45
|
+
entry.url = s.url;
|
|
46
|
+
} else {
|
|
47
|
+
entry.command = s.command;
|
|
48
|
+
entry.args = s.args;
|
|
49
|
+
}
|
|
50
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
51
|
+
entry.env = s.env;
|
|
52
|
+
}
|
|
53
|
+
mcpServers[s.name] = entry;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify({ mcpServers }, null, 2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getConfigPath(projectRoot?: string): string {
|
|
59
|
+
if (projectRoot) {
|
|
60
|
+
return join(projectRoot, '.kiro', 'settings', 'mcp.json');
|
|
61
|
+
}
|
|
62
|
+
return join(homedir(), '.kiro', 'settings', 'mcp.json');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* OpenCode MCP Configuration Adapter.
|
|
7
|
-
*
|
|
8
|
-
* OpenCode uses JSON config files for MCP servers:
|
|
9
|
-
* 1. Project-level: opencode.json in project root
|
|
10
|
-
* 2. Global: ~/.config/opencode/opencode.json
|
|
11
|
-
*
|
|
12
|
-
* Format:
|
|
13
|
-
* {
|
|
14
|
-
* "$schema": "https://opencode.ai/config.json",
|
|
15
|
-
* "mcp": {
|
|
16
|
-
* "name": {
|
|
17
|
-
* "type": "local",
|
|
18
|
-
* "command": ["memorix", "serve"],
|
|
19
|
-
* "environment": { "KEY": "value" },
|
|
20
|
-
* "enabled": true
|
|
21
|
-
* }
|
|
22
|
-
* }
|
|
23
|
-
* }
|
|
24
|
-
*
|
|
25
|
-
* Remote (HTTP) servers:
|
|
26
|
-
* {
|
|
27
|
-
* "mcp": {
|
|
28
|
-
* "name": {
|
|
29
|
-
* "type": "remote",
|
|
30
|
-
* "url": "https://...",
|
|
31
|
-
* "headers": { "Authorization": "Bearer ..." }
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
*
|
|
36
|
-
* Source: https://opencode.ai/docs/mcp-servers/
|
|
37
|
-
*/
|
|
38
|
-
export class OpenCodeMCPAdapter implements MCPConfigAdapter {
|
|
39
|
-
readonly source = 'opencode' as const;
|
|
40
|
-
|
|
41
|
-
parse(content: string): MCPServerEntry[] {
|
|
42
|
-
try {
|
|
43
|
-
const config = JSON.parse(content);
|
|
44
|
-
const servers = config.mcp ?? {};
|
|
45
|
-
return Object.entries(servers).map(([name, entry]: [string, any]) => {
|
|
46
|
-
const result: MCPServerEntry = {
|
|
47
|
-
name,
|
|
48
|
-
command: '',
|
|
49
|
-
args: [],
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
if (entry.type === 'remote' && entry.url) {
|
|
53
|
-
// HTTP transport
|
|
54
|
-
result.url = entry.url;
|
|
55
|
-
if (entry.headers && typeof entry.headers === 'object' && Object.keys(entry.headers).length > 0) {
|
|
56
|
-
result.headers = entry.headers;
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
// Local (stdio) transport — command is an array in OpenCode
|
|
60
|
-
if (Array.isArray(entry.command) && entry.command.length > 0) {
|
|
61
|
-
result.command = entry.command[0];
|
|
62
|
-
result.args = entry.command.slice(1);
|
|
63
|
-
} else if (typeof entry.command === 'string') {
|
|
64
|
-
result.command = entry.command;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Environment variables (OpenCode uses "environment" not "env")
|
|
69
|
-
const env = entry.environment ?? entry.env;
|
|
70
|
-
if (env && typeof env === 'object' && Object.keys(env).length > 0) {
|
|
71
|
-
result.env = env;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Disabled flag
|
|
75
|
-
if (entry.enabled === false) {
|
|
76
|
-
result.disabled = true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return result;
|
|
80
|
-
});
|
|
81
|
-
} catch {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
generate(servers: MCPServerEntry[]): string {
|
|
87
|
-
const mcp: Record<string, any> = {};
|
|
88
|
-
for (const s of servers) {
|
|
89
|
-
const entry: Record<string, any> = {};
|
|
90
|
-
|
|
91
|
-
if (s.url) {
|
|
92
|
-
// HTTP transport
|
|
93
|
-
entry.type = 'remote';
|
|
94
|
-
entry.url = s.url;
|
|
95
|
-
if (s.headers && Object.keys(s.headers).length > 0) {
|
|
96
|
-
entry.headers = s.headers;
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
// stdio transport — OpenCode uses command as array
|
|
100
|
-
entry.type = 'local';
|
|
101
|
-
entry.command = [s.command, ...s.args];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (s.env && Object.keys(s.env).length > 0) {
|
|
105
|
-
entry.environment = s.env;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (s.disabled === true) {
|
|
109
|
-
entry.enabled = false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
mcp[s.name] = entry;
|
|
113
|
-
}
|
|
114
|
-
return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
getConfigPath(projectRoot?: string): string {
|
|
118
|
-
if (projectRoot) {
|
|
119
|
-
return join(projectRoot, 'opencode.json');
|
|
120
|
-
}
|
|
121
|
-
return join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OpenCode MCP Configuration Adapter.
|
|
7
|
+
*
|
|
8
|
+
* OpenCode uses JSON config files for MCP servers:
|
|
9
|
+
* 1. Project-level: opencode.json in project root
|
|
10
|
+
* 2. Global: ~/.config/opencode/opencode.json
|
|
11
|
+
*
|
|
12
|
+
* Format:
|
|
13
|
+
* {
|
|
14
|
+
* "$schema": "https://opencode.ai/config.json",
|
|
15
|
+
* "mcp": {
|
|
16
|
+
* "name": {
|
|
17
|
+
* "type": "local",
|
|
18
|
+
* "command": ["memorix", "serve"],
|
|
19
|
+
* "environment": { "KEY": "value" },
|
|
20
|
+
* "enabled": true
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Remote (HTTP) servers:
|
|
26
|
+
* {
|
|
27
|
+
* "mcp": {
|
|
28
|
+
* "name": {
|
|
29
|
+
* "type": "remote",
|
|
30
|
+
* "url": "https://...",
|
|
31
|
+
* "headers": { "Authorization": "Bearer ..." }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* Source: https://opencode.ai/docs/mcp-servers/
|
|
37
|
+
*/
|
|
38
|
+
export class OpenCodeMCPAdapter implements MCPConfigAdapter {
|
|
39
|
+
readonly source = 'opencode' as const;
|
|
40
|
+
|
|
41
|
+
parse(content: string): MCPServerEntry[] {
|
|
42
|
+
try {
|
|
43
|
+
const config = JSON.parse(content);
|
|
44
|
+
const servers = config.mcp ?? {};
|
|
45
|
+
return Object.entries(servers).map(([name, entry]: [string, any]) => {
|
|
46
|
+
const result: MCPServerEntry = {
|
|
47
|
+
name,
|
|
48
|
+
command: '',
|
|
49
|
+
args: [],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (entry.type === 'remote' && entry.url) {
|
|
53
|
+
// HTTP transport
|
|
54
|
+
result.url = entry.url;
|
|
55
|
+
if (entry.headers && typeof entry.headers === 'object' && Object.keys(entry.headers).length > 0) {
|
|
56
|
+
result.headers = entry.headers;
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// Local (stdio) transport — command is an array in OpenCode
|
|
60
|
+
if (Array.isArray(entry.command) && entry.command.length > 0) {
|
|
61
|
+
result.command = entry.command[0];
|
|
62
|
+
result.args = entry.command.slice(1);
|
|
63
|
+
} else if (typeof entry.command === 'string') {
|
|
64
|
+
result.command = entry.command;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Environment variables (OpenCode uses "environment" not "env")
|
|
69
|
+
const env = entry.environment ?? entry.env;
|
|
70
|
+
if (env && typeof env === 'object' && Object.keys(env).length > 0) {
|
|
71
|
+
result.env = env;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Disabled flag
|
|
75
|
+
if (entry.enabled === false) {
|
|
76
|
+
result.disabled = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
});
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
generate(servers: MCPServerEntry[]): string {
|
|
87
|
+
const mcp: Record<string, any> = {};
|
|
88
|
+
for (const s of servers) {
|
|
89
|
+
const entry: Record<string, any> = {};
|
|
90
|
+
|
|
91
|
+
if (s.url) {
|
|
92
|
+
// HTTP transport
|
|
93
|
+
entry.type = 'remote';
|
|
94
|
+
entry.url = s.url;
|
|
95
|
+
if (s.headers && Object.keys(s.headers).length > 0) {
|
|
96
|
+
entry.headers = s.headers;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// stdio transport — OpenCode uses command as array
|
|
100
|
+
entry.type = 'local';
|
|
101
|
+
entry.command = [s.command, ...s.args];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
105
|
+
entry.environment = s.env;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (s.disabled === true) {
|
|
109
|
+
entry.enabled = false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
mcp[s.name] = entry;
|
|
113
|
+
}
|
|
114
|
+
return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getConfigPath(projectRoot?: string): string {
|
|
118
|
+
if (projectRoot) {
|
|
119
|
+
return join(projectRoot, 'opencode.json');
|
|
120
|
+
}
|
|
121
|
+
return join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
122
|
+
}
|
|
123
|
+
}
|