@compilr-dev/cli 0.5.12 → 0.5.14

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.
@@ -1,18 +1,14 @@
1
1
  /**
2
- * MCP Configuration Loader
2
+ * MCP Configuration Loader — CLI wrapper
3
3
  *
4
- * Loads MCP server configs from:
5
- * 1. Global: ~/.compilr-dev/mcp.json
6
- * 2. Per-project: <projectRoot>/.compilr-dev/mcp.json
7
- *
8
- * Project config overrides global config (by server name).
9
- * Format is compatible with Claude Desktop / Gemini CLI.
4
+ * Types and file I/O come from @compilr-dev/sdk.
5
+ * This module adds CLI-specific path helpers (global + per-project).
10
6
  */
11
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
12
7
  import * as path from 'path';
8
+ import { loadMCPServers, saveMCPServerEntry, getServerNames, } from '@compilr-dev/sdk';
13
9
  import { getDataPath } from './paths.js';
14
10
  // =============================================================================
15
- // Config Loading
11
+ // CLI-specific path helpers
16
12
  // =============================================================================
17
13
  /**
18
14
  * Get the global MCP config file path.
@@ -26,118 +22,36 @@ export function getMCPConfigPath() {
26
22
  export function getProjectMCPConfigPath(projectRoot) {
27
23
  return path.join(projectRoot, '.compilr-dev', 'mcp.json');
28
24
  }
29
- /**
30
- * Read and parse an mcp.json file. Returns empty servers on any error.
31
- */
32
- function readMCPConfigFile(filePath) {
33
- try {
34
- if (!existsSync(filePath)) {
35
- return {};
36
- }
37
- const data = readFileSync(filePath, 'utf-8');
38
- const parsed = JSON.parse(data);
39
- if (parsed.mcpServers && typeof parsed.mcpServers === 'object') {
40
- return parsed.mcpServers;
41
- }
42
- return {};
43
- }
44
- catch {
45
- return {};
46
- }
47
- }
48
- /**
49
- * Convert an MCPServerEntry to a ResolvedMCPServer.
50
- * Returns null if the entry is invalid (neither command nor url).
51
- */
52
- function resolveEntry(name, entry) {
53
- if (entry.disabled) {
54
- return null;
55
- }
56
- if (entry.command) {
57
- return {
58
- name,
59
- transport: 'stdio',
60
- command: entry.command,
61
- args: entry.args,
62
- env: entry.env,
63
- cwd: entry.cwd,
64
- timeout: entry.timeout,
65
- };
66
- }
67
- if (entry.url) {
68
- return {
69
- name,
70
- transport: 'http',
71
- url: entry.url,
72
- headers: entry.headers,
73
- timeout: entry.timeout,
74
- };
75
- }
76
- // Neither command nor url — invalid entry
77
- return null;
78
- }
25
+ // =============================================================================
26
+ // CLI-specific wrappers (delegate to SDK with resolved paths)
27
+ // =============================================================================
79
28
  /**
80
29
  * Load MCP server configs from global and project-level config files.
81
30
  * Project configs override global by server name.
82
- *
83
- * @param projectRoot - Optional project root for per-project config
84
- * @returns Array of resolved MCP server configs
85
31
  */
86
32
  export function loadMCPConfig(projectRoot) {
87
- // 1. Load global config
88
- const globalServers = readMCPConfigFile(getMCPConfigPath());
89
- // 2. Load project config (overrides global by name)
90
- const projectServers = projectRoot
91
- ? readMCPConfigFile(getProjectMCPConfigPath(projectRoot))
92
- : {};
93
- // 3. Merge: project overrides global
94
- const merged = { ...globalServers, ...projectServers };
95
- // 4. Resolve each entry
96
- const resolved = [];
97
- for (const [name, entry] of Object.entries(merged)) {
98
- const server = resolveEntry(name, entry);
99
- if (server) {
100
- resolved.push(server);
101
- }
33
+ const paths = [getMCPConfigPath()];
34
+ if (projectRoot) {
35
+ paths.push(getProjectMCPConfigPath(projectRoot));
102
36
  }
103
- return resolved;
37
+ return loadMCPServers(...paths);
104
38
  }
105
39
  /**
106
40
  * Get existing server names from all config files.
107
- * Useful for validating uniqueness when adding a new server.
108
41
  */
109
42
  export function getExistingServerNames(projectRoot) {
110
- const globalServers = readMCPConfigFile(getMCPConfigPath());
111
- const projectServers = projectRoot
112
- ? readMCPConfigFile(getProjectMCPConfigPath(projectRoot))
113
- : {};
114
- return new Set([...Object.keys(globalServers), ...Object.keys(projectServers)]);
43
+ const paths = [getMCPConfigPath()];
44
+ if (projectRoot) {
45
+ paths.push(getProjectMCPConfigPath(projectRoot));
46
+ }
47
+ return getServerNames(...paths);
115
48
  }
116
49
  /**
117
50
  * Save a new MCP server entry to the config file.
118
- *
119
- * @param name - Server name (unique identifier)
120
- * @param entry - Server configuration entry
121
- * @param scope - 'global' or 'project'
122
- * @param projectRoot - Required when scope is 'project'
123
51
  */
124
52
  export function saveMCPServer(name, entry, scope, projectRoot) {
125
53
  const configPath = scope === 'project' && projectRoot
126
54
  ? getProjectMCPConfigPath(projectRoot)
127
55
  : getMCPConfigPath();
128
- // Read existing config (or empty)
129
- let config = { mcpServers: {} };
130
- try {
131
- const raw = readFileSync(configPath, 'utf-8');
132
- const parsed = JSON.parse(raw);
133
- config = { mcpServers: parsed.mcpServers ?? {} };
134
- }
135
- catch {
136
- // File doesn't exist or invalid — start fresh
137
- }
138
- // Add/overwrite server entry
139
- config.mcpServers[name] = entry;
140
- // Ensure directory exists, write file
141
- mkdirSync(path.dirname(configPath), { recursive: true });
142
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
56
+ saveMCPServerEntry(configPath, name, entry);
143
57
  }
@@ -1,19 +1,9 @@
1
1
  /**
2
2
  * Platform Adapter
3
3
  *
4
- * Thin adapter that wraps the CLI's synchronous repositories and services as async
5
- * implementations of the SDK's platform interfaces, wires CLI-specific hooks,
4
+ * Uses the SDK's SQLite repository implementations (shared with Desktop),
5
+ * wires CLI-specific hooks and services (anchors, artifacts, episodes),
6
6
  * and delegates all 32 platform tool definitions to the SDK.
7
- *
8
- * This replaces ~2,900+ lines of duplicated tool logic that previously lived in:
9
- * - project-db.ts (4 tools)
10
- * - workitem-db.ts (9 tools)
11
- * - document-db.ts (4 tools)
12
- * - plan-tools.ts (5 tools)
13
- * - backlog-wrappers.ts (2 tools)
14
- * - anchor-tools.ts (3 tools)
15
- * - artifact-tools.ts (4 tools)
16
- * - recall-work-tool.ts (1 tool)
17
7
  */
18
8
  /**
19
9
  * All 32 platform tools from the SDK:
@@ -32,4 +22,4 @@ export declare const allPlatformTools: import("@compilr-dev/sdk").Tool<never>[];
32
22
  * - 3 model tools (app_model_get, app_model_update, app_model_validate)
33
23
  * - 2 factory tools (factory_scaffold, factory_list_toolkits)
34
24
  */
35
- export declare const allFactoryTools: import("@compilr-dev/agents").Tool<never>[];
25
+ export declare const allFactoryTools: import("@compilr-dev/sdk").Tool<never>[];
@@ -1,183 +1,64 @@
1
1
  /**
2
2
  * Platform Adapter
3
3
  *
4
- * Thin adapter that wraps the CLI's synchronous repositories and services as async
5
- * implementations of the SDK's platform interfaces, wires CLI-specific hooks,
4
+ * Uses the SDK's SQLite repository implementations (shared with Desktop),
5
+ * wires CLI-specific hooks and services (anchors, artifacts, episodes),
6
6
  * and delegates all 32 platform tool definitions to the SDK.
7
- *
8
- * This replaces ~2,900+ lines of duplicated tool logic that previously lived in:
9
- * - project-db.ts (4 tools)
10
- * - workitem-db.ts (9 tools)
11
- * - document-db.ts (4 tools)
12
- * - plan-tools.ts (5 tools)
13
- * - backlog-wrappers.ts (2 tools)
14
- * - anchor-tools.ts (3 tools)
15
- * - artifact-tools.ts (4 tools)
16
- * - recall-work-tool.ts (1 tool)
17
7
  */
18
- import { createPlatformTools, } from '@compilr-dev/sdk';
8
+ import { createPlatformTools, createSQLiteRepositories, ProjectAnchorStore, } from '@compilr-dev/sdk';
19
9
  import { createFactoryTools } from '@compilr-dev/factory';
20
- import { projectRepository, workItemRepository, documentRepository, planRepository, } from '../db/repositories/index.js';
10
+ import { getDb } from '../db/index.js';
11
+ import { getDataPath, getSessionsPath } from '../settings/paths.js';
12
+ import { existsSync, rmSync } from 'fs';
13
+ import { join } from 'path';
21
14
  import { getCurrentProject, setCurrentProject } from './project-db.js';
22
15
  import { awardFirstProject, awardWorkItemCompletion } from '../games/coins.js';
23
16
  import { getActiveSharedContext } from '@compilr-dev/sdk';
24
- import { getGlobalAnchorManager, getAnchorManager, } from '../anchors/index.js';
17
+ import { setAnchorStore } from '../anchors/index.js';
25
18
  import { getTeamCheckpointer, recordTeamActivity } from '../multi-agent/index.js';
26
19
  import { getGlobalEpisodeStore } from '../episodes/index.js';
27
20
  // =============================================================================
28
- // Async wrappers each method wraps a sync CLI repo call in Promise.resolve()
29
- // =============================================================================
30
- const asyncProjectRepo = {
31
- create: (input) => Promise.resolve(projectRepository.create(input)),
32
- getById: (id) => Promise.resolve(projectRepository.getById(id)),
33
- getByName: (name) => Promise.resolve(projectRepository.getByName(name)),
34
- getByPath: (path) => Promise.resolve(projectRepository.getByPath(path)),
35
- getByDocsPath: (docsPath) => Promise.resolve(projectRepository.getByDocsPath(docsPath)),
36
- findByPath: (searchPath) => Promise.resolve(projectRepository.findByPath(searchPath)),
37
- list: (options) => Promise.resolve(projectRepository.list(options)),
38
- update: (id, input) => Promise.resolve(projectRepository.update(id, input)),
39
- touch: (id) => { projectRepository.touch(id); return Promise.resolve(); },
40
- delete: (id) => Promise.resolve(projectRepository.delete(id)),
41
- archive: (id) => Promise.resolve(projectRepository.archive(id)),
42
- isNameAvailable: (name, excludeId) => Promise.resolve(projectRepository.isNameAvailable(name, excludeId)),
43
- getStatusCounts: () => Promise.resolve(projectRepository.getStatusCounts()),
44
- };
45
- const asyncWorkItemRepo = {
46
- create: (input) => Promise.resolve(workItemRepository.create(input)),
47
- getById: (id) => Promise.resolve(workItemRepository.getById(id)),
48
- getByItemId: (projectId, itemId) => Promise.resolve(workItemRepository.getByItemId(projectId, itemId)),
49
- query: (input) => Promise.resolve(workItemRepository.query(input)),
50
- getNext: (projectId, type) => Promise.resolve(workItemRepository.getNext(projectId, type)),
51
- update: (id, input) => Promise.resolve(workItemRepository.update(id, input)),
52
- delete: (id) => Promise.resolve(workItemRepository.delete(id)),
53
- getByOwner: (projectId, owner, status) => Promise.resolve(workItemRepository.getByOwner(projectId, owner, status)),
54
- getOwnerCounts: (projectId) => Promise.resolve(workItemRepository.getOwnerCounts(projectId)),
55
- getStatusCounts: (projectId) => Promise.resolve(workItemRepository.getStatusCounts(projectId)),
56
- getHistory: (workItemId) => Promise.resolve(workItemRepository.getHistory(workItemId)),
57
- bulkCreate: (projectId, items) => Promise.resolve(workItemRepository.bulkCreate(projectId, items)),
58
- };
59
- const asyncDocumentRepo = {
60
- upsert: (input) => Promise.resolve(documentRepository.upsert(input)),
61
- create: (input) => Promise.resolve(documentRepository.create(input)),
62
- getById: (id) => Promise.resolve(documentRepository.getById(id)),
63
- getByType: (projectId, docType) => Promise.resolve(documentRepository.getByType(projectId, docType)),
64
- listByProject: (projectId) => Promise.resolve(documentRepository.listByProject(projectId)),
65
- update: (id, input) => Promise.resolve(documentRepository.update(id, input)),
66
- delete: (id) => Promise.resolve(documentRepository.delete(id)),
67
- deleteByType: (projectId, docType) => Promise.resolve(documentRepository.deleteByType(projectId, docType)),
68
- getTypeCounts: (projectId) => Promise.resolve(documentRepository.getTypeCounts(projectId)),
69
- };
70
- const asyncPlanRepo = {
71
- create: (input) => Promise.resolve(planRepository.create(input)),
72
- getById: (id) => Promise.resolve(planRepository.getById(id)),
73
- getByName: (projectId, name) => Promise.resolve(planRepository.getByName(projectId, name)),
74
- getWithWorkItem: (id) => Promise.resolve(planRepository.getWithWorkItem(id)),
75
- update: (id, input) => Promise.resolve(planRepository.update(id, input)),
76
- delete: (id) => Promise.resolve(planRepository.delete(id)),
77
- list: (projectId, options) => Promise.resolve(planRepository.list(projectId, options)),
78
- countByStatus: (projectId) => Promise.resolve(planRepository.countByStatus(projectId)),
79
- getInProgress: (projectId) => Promise.resolve(planRepository.getInProgress(projectId)),
80
- hasInProgress: (projectId) => Promise.resolve(planRepository.hasInProgress(projectId)),
81
- linkWorkItem: (planId, workItemId) => Promise.resolve(planRepository.linkWorkItem(planId, workItemId)),
82
- unlinkWorkItem: (planId) => Promise.resolve(planRepository.unlinkWorkItem(planId)),
83
- };
84
- // =============================================================================
85
- // Anchor Service — wraps dual-scope AnchorManager (global + project)
21
+ // RepositoriesSDK's SQLite implementations, backed by CLI's existing DB
86
22
  // =============================================================================
87
- const anchorService = {
88
- add: (input) => {
89
- const activeProject = getCurrentProject();
90
- const priority = input.priority ?? 'info';
91
- let scope = input.scope;
92
- if (!scope) {
93
- scope = activeProject ? 'project' : 'global';
94
- }
95
- if (scope === 'project' && !activeProject) {
96
- throw new Error('Cannot add project-scoped anchor: no active project. Use scope: "global" instead.');
97
- }
98
- const projectId = scope === 'project' && activeProject
99
- ? String(activeProject.id)
100
- : undefined;
101
- const manager = scope === 'global'
102
- ? getGlobalAnchorManager()
103
- : getAnchorManager(projectId ?? '');
104
- const anchor = manager.add({
105
- content: input.content,
106
- priority,
107
- scope: 'persistent',
108
- tags: input.tags,
109
- projectId,
110
- });
111
- return Promise.resolve({
112
- id: anchor.id,
113
- content: anchor.content,
114
- priority: anchor.priority,
115
- scope,
116
- tags: anchor.tags,
117
- });
118
- },
119
- remove: (id) => {
120
- const activeProject = getCurrentProject();
121
- let removed = false;
122
- let scope;
123
- if (activeProject) {
124
- const projectManager = getAnchorManager(String(activeProject.id));
125
- if (projectManager.has(id)) {
126
- removed = projectManager.remove(id);
127
- scope = 'project';
23
+ const db = getDb();
24
+ const repos = createSQLiteRepositories(db, {
25
+ projectDeleteHooks: {
26
+ onDelete: (projectId) => {
27
+ // Clean up session files
28
+ try {
29
+ const sessionsPath = getSessionsPath();
30
+ const projectSessionPath = join(sessionsPath, `project-${String(projectId)}`);
31
+ if (existsSync(projectSessionPath)) {
32
+ rmSync(projectSessionPath, { recursive: true, force: true });
33
+ }
128
34
  }
129
- }
130
- if (!removed) {
131
- const globalManager = getGlobalAnchorManager();
132
- if (globalManager.has(id)) {
133
- removed = globalManager.remove(id);
134
- scope = 'global';
35
+ catch { /* best-effort */ }
36
+ // Clean up anchor files
37
+ try {
38
+ const anchorsPath = join(getSessionsPath(), '..', 'anchors');
39
+ const projectAnchorsPath = join(anchorsPath, `project-${String(projectId)}.json`);
40
+ if (existsSync(projectAnchorsPath)) {
41
+ rmSync(projectAnchorsPath, { force: true });
42
+ }
135
43
  }
136
- }
137
- return Promise.resolve({ removed, scope });
44
+ catch { /* best-effort */ }
45
+ },
138
46
  },
139
- list: (options) => {
140
- const activeProject = getCurrentProject();
141
- const scope = options?.scope ?? 'all';
142
- const anchors = [];
143
- if (scope === 'global' || scope === 'all') {
144
- const globalManager = getGlobalAnchorManager();
145
- for (const anchor of globalManager.getAll({ priority: options?.priority })) {
146
- anchors.push({
147
- id: anchor.id,
148
- content: anchor.content,
149
- priority: anchor.priority,
150
- scope: 'global',
151
- tags: anchor.tags,
152
- });
153
- }
154
- }
155
- if ((scope === 'project' || scope === 'all') && activeProject) {
156
- const projectManager = getAnchorManager(String(activeProject.id));
157
- for (const anchor of projectManager.getAll({ priority: options?.priority })) {
158
- anchors.push({
159
- id: anchor.id,
160
- content: anchor.content,
161
- priority: anchor.priority,
162
- scope: 'project',
163
- tags: anchor.tags,
164
- });
165
- }
166
- }
167
- // Sort by priority (critical first)
168
- const priorityOrder = { critical: 0, safety: 1, info: 2 };
169
- anchors.sort((a, b) => {
170
- const pa = priorityOrder[a.priority] ?? 2;
171
- const pb = priorityOrder[b.priority] ?? 2;
172
- return pa - pb;
173
- });
174
- return Promise.resolve({
175
- count: anchors.length,
176
- activeProjectName: activeProject?.displayName ?? null,
177
- anchors,
178
- });
47
+ });
48
+ // =============================================================================
49
+ // Anchor Service ProjectAnchorStore from SDK (shared with Desktop)
50
+ // =============================================================================
51
+ const anchorStore = new ProjectAnchorStore({
52
+ anchorsDir: join(getDataPath(), 'anchors'),
53
+ getCurrentProjectId: () => getCurrentProject()?.id,
54
+ getProjectName: () => {
55
+ // getCurrentProject() is always in memory — no async needed
56
+ return getCurrentProject()?.displayName ?? null;
179
57
  },
180
- };
58
+ });
59
+ // Register the store so CLI's anchors module delegates to it
60
+ setAnchorStore(anchorStore);
61
+ const anchorService = anchorStore.toService();
181
62
  // =============================================================================
182
63
  // Artifact Service — wraps ArtifactStore from TeamCheckpointer
183
64
  // =============================================================================
@@ -338,10 +219,10 @@ const episodeService = {
338
219
  // PlatformContext with getter/setter for currentProjectId
339
220
  // =============================================================================
340
221
  const context = {
341
- projects: asyncProjectRepo,
342
- workItems: asyncWorkItemRepo,
343
- documents: asyncDocumentRepo,
344
- plans: asyncPlanRepo,
222
+ projects: repos.projects,
223
+ workItems: repos.workItems,
224
+ documents: repos.documents,
225
+ plans: repos.plans,
345
226
  anchors: anchorService,
346
227
  artifacts: artifactService,
347
228
  episodes: episodeService,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/cli",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
4
4
  "description": "AI-powered coding assistant CLI using @compilr-dev/agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,7 +58,7 @@
58
58
  "@compilr-dev/agents-coding": "^1.0.4",
59
59
  "@compilr-dev/editor-core": "^0.0.2",
60
60
  "@compilr-dev/factory": "^0.1.12",
61
- "@compilr-dev/sdk": "^0.2.0",
61
+ "@compilr-dev/sdk": "^0.2.7",
62
62
  "@compilr-dev/ui-core": "^0.0.1",
63
63
  "@modelcontextprotocol/sdk": "^1.23.0",
64
64
  "better-sqlite3": "^12.5.0",
@@ -70,7 +70,8 @@
70
70
  "uuid": "^13.0.0"
71
71
  },
72
72
  "overrides": {
73
- "minimatch": ">=10.2.1"
73
+ "minimatch": ">=10.2.1",
74
+ "hono": ">=4.12.7"
74
75
  },
75
76
  "devDependencies": {
76
77
  "@eslint/js": "^9.39.1",