@latentforce/latentgraph 1.0.0

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/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # @latentforce/latentgraph
2
+
3
+ AI-powered code intelligence CLI and MCP server for Claude.
4
+
5
+ Shift indexes your codebase, builds a dependency relationship graph (DRG), and provides AI-powered insights via MCP tools — enabling Claude to understand your project's structure, dependencies, and blast radius of changes.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g @latentforce/latentgraph
11
+ ```
12
+
13
+ ## Getting an API Key
14
+
15
+ 1. Go to **https://dev-shift-lite.latentforce.ai/auth**
16
+ 2. **Sign up** for an account
17
+ 3. **Sign in** to your dashboard
18
+ 4. Copy your **API key** from the dashboard
19
+
20
+ From the dashboard you can also **create a project** and **select a migration template** — or you can do both directly from the CLI (see below).
21
+
22
+ ## Quick Start
23
+
24
+ All commands **must be run** from your project's root directory:
25
+
26
+ ```bash
27
+ cd /path/to/your/project
28
+ ```
29
+
30
+ ### Option A: Interactive (recommended)
31
+
32
+ ```bash
33
+ lgraph start # Step 1: Configure API key and project, launch daemon
34
+ lgraph init # Step 2: Scan and index project files
35
+ lgraph status # Step 3: Verify everything is connected
36
+ ```
37
+
38
+ When you run `lgraph start` interactively, it will:
39
+ 1. **Ask for your API key** — paste the key from your dashboard (or choose guest mode)
40
+ 2. **Ask to create or select a project** — you can either:
41
+ - **Select an existing project** from your account (if you created one on the dashboard)
42
+ - **Create a new project** from the CLI — it will prompt for a name (defaults to your directory name) and let you select a migration template
43
+
44
+ ### Option B: Non-interactive (CI/CD friendly)
45
+
46
+ ```bash
47
+ lgraph init --api-key YOUR_KEY --project-name "My App"
48
+ ```
49
+
50
+ If a project named "My App" already exists in your account, it will be reused. Otherwise a new one is created with the specified template.
51
+
52
+
53
+ ## CLI Commands
54
+
55
+ | Command | Description |
56
+ |---------|-------------|
57
+ | `lgraph start` | Start the daemon and configure the project |
58
+ | `lgraph init` | Scan and index project files |
59
+ | `lgraph status` | Show current status |
60
+ | `lgraph update-drg` | Update the dependency relationship graph |
61
+ | `lgraph stop` | Stop the daemon |
62
+ | `lgraph add <tool>` | Add Shift MCP server to an AI coding tool |
63
+ | `lgraph config` | Manage configuration |
64
+
65
+ ### `lgraph start`
66
+
67
+ Resolves authentication, configures the project, and launches a background daemon that maintains a WebSocket connection to the Shift backend.
68
+
69
+ **When run interactively (no flags):**
70
+ 1. Prompts you to enter your API key or choose guest mode
71
+ 2. Prompts you to **select an existing project** or **create a new one**
72
+ - If creating: asks for a project name and lets you pick a migration template
73
+ 3. Saves config to `.shift/config.json`
74
+ 4. Launches the background daemon
75
+
76
+ If you already created a project on the dashboard (https://dev-shift-lite.latentforce.ai), you can select it from the list. Otherwise, create one directly from the CLI.
77
+
78
+ | Flag | Description |
79
+ |------|-------------|
80
+ | `--guest` | Use guest authentication (auto-creates a temporary project) |
81
+ | `--api-key <key>` | Provide API key directly (skips the key prompt) |
82
+ | `--project-name <name>` | Create new project or match existing by name (skips project prompt) |
83
+ | `--project-id <id>` | Use existing project UUID (skips project prompt) |
84
+ | `--template <id>` | Migration template ID for project creation |
85
+
86
+ ```bash
87
+ lgraph start # Interactive setup (prompts for key + project)
88
+ lgraph start --guest # Quick start without API key
89
+ lgraph start --api-key <key> --project-name "My App" # Non-interactive
90
+ ```
91
+
92
+ ### `lgraph init`
93
+
94
+ Performs a full project scan — collects the file tree, categorizes files (source, config, assets), gathers git info, and sends everything to the Shift backend for indexing. Automatically starts the daemon if not running.
95
+
96
+ If the project is already indexed, you will be prompted to re-index. Use `--force` to skip the prompt.
97
+
98
+ | Flag | Description |
99
+ |------|-------------|
100
+ | `--guest` | Use guest authentication (auto-creates a temporary project) |
101
+ | `--api-key <key>` | Provide API key directly |
102
+ | `--project-name <name>` | Create new project or match existing by name |
103
+ | `--project-id <id>` | Use existing project UUID |
104
+ | `--template <id>` | Migration template ID for project creation |
105
+ | `-f, --force` | Force re-indexing even if already indexed |
106
+
107
+ ```bash
108
+ lgraph init # Interactive initialization
109
+ lgraph init --force # Force re-index without prompt
110
+ ```
111
+
112
+ ### `lgraph status`
113
+
114
+ Displays comprehensive information about the current Shift setup — API key status, project details, backend indexing state, and daemon health.
115
+
116
+ ```bash
117
+ lgraph status
118
+ ```
119
+
120
+ ### `lgraph update-drg`
121
+
122
+ Scans source files across all supported languages, detects git changes, and sends file contents to the backend for dependency analysis.
123
+
124
+ **Supported languages and extensions:**
125
+
126
+ | Language | Extensions |
127
+ |----------|------------|
128
+ | JavaScript / TypeScript | `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` |
129
+ | Python | `.py` |
130
+ | C# | `.cs` |
131
+ | C/C++ | `.cpp`, `.cc`, `.cxx`, `.h`, `.hpp`, `.c` |
132
+
133
+ | Flag | Description |
134
+ |------|-------------|
135
+ | `-m, --mode <mode>` | `baseline` (all files) or `incremental` (git-changed only, default) |
136
+
137
+ **Modes:**
138
+ - **incremental** (default) — sends only git-changed files for faster updates
139
+ - **baseline** — sends all source files for a full re-analysis
140
+
141
+ ```bash
142
+ lgraph update-drg # Incremental update (default)
143
+ lgraph update-drg -m baseline # Full re-analysis
144
+ ```
145
+
146
+ ### `lgraph stop`
147
+
148
+ Stops the background daemon process.
149
+
150
+ ```bash
151
+ lgraph stop
152
+ ```
153
+
154
+ ### `lgraph config`
155
+
156
+ Manage Shift configuration (URLs, API key).
157
+
158
+ | Action | Description |
159
+ |--------|-------------|
160
+ | `show` | Display current configuration (default) |
161
+ | `set <key> <value>` | Set a configuration value |
162
+ | `clear [key]` | Clear a specific key or all configuration |
163
+
164
+ **Configurable key:** `api-key`
165
+
166
+ ```bash
167
+ lgraph config # Show config
168
+ lgraph config set api-key sk-abc123 # Set API key
169
+ lgraph config clear api-key # Clear API key
170
+ lgraph config clear # Clear all config
171
+ ```
172
+ ## MCP Integration
173
+
174
+ After initializing your project, use `lgraph add` to configure Shift's MCP server in your AI coding tool:
175
+
176
+ ```bash
177
+ lgraph add <tool>
178
+ ```
179
+
180
+ ### Supported tools
181
+
182
+ | Tool | Command | Config method |
183
+ |------|---------|---------------|
184
+ | Shiftcode | `lgraph add shiftcode` | Writes `shiftcode.json` |
185
+ | Claude Code | `lgraph add claude-code` | Runs `claude mcp add-json` |
186
+ | Opencode | `lgraph add opencode` | Writes `opencode.json` |
187
+ | Codex | `lgraph add codex` | Runs `codex mcp add` |
188
+ | GitHub Copilot | `lgraph add copilot` | Writes `.vscode/mcp.json` |
189
+ | Factory Droid | `lgraph add droid` | Runs `droid mcp add` |
190
+
191
+ For CLI-based tools, if the CLI is not installed, the command will print manual setup instructions.
192
+
193
+ ### Claude Desktop (manual)
194
+
195
+ Add to your config file (`%APPDATA%\Claude\claude_desktop_config.json` on Windows, `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
196
+
197
+ ```json
198
+ {
199
+ "mcpServers": {
200
+ "shift": {
201
+ "command": "lgraph",
202
+ "args": ["mcp"],
203
+ "env": {
204
+ "SHIFT_PROJECT_ID": "YOUR_PROJECT_ID"
205
+ }
206
+ }
207
+ }
208
+ }
209
+ ```
210
+ ## `.shift/scan_target.json`
211
+
212
+ Shift uses a `scan_target.json` file inside the `.shift/` directory to let you specify which parts of your codebase to scan and in which language. This is especially useful for monorepos or projects with multiple languages.
213
+
214
+ A default template is **automatically created** the first time you run `lgraph init` or `lgraph start`. By default, it is unconfigured and the server will auto-detect scan targets. Edit the file to manually specify targets.
215
+
216
+ ### Valid Languages
217
+
218
+ ```
219
+ javascript, typescript, python, cpp, csharp
220
+ ```
221
+
222
+ ### Format
223
+
224
+ The file is a JSON array of objects, each with:
225
+
226
+ | Field | Type | Description |
227
+ |-------|------|-------------|
228
+ | `language` | `string \| null` | One of the valid languages above, or `null` for auto-detect |
229
+ | `path` | `string` | Relative path from project root (empty string `""` for root) |
230
+
231
+ ### Default Template (auto-generated)
232
+
233
+ ```json
234
+ [
235
+ {
236
+ "language": null,
237
+ "path": ""
238
+ }
239
+ ]
240
+ ```
241
+
242
+ When left as-is (single entry with `language: null` and `path: ""`), the server auto-detects scan targets.
243
+
244
+ ### Examples
245
+
246
+ **Single-language project (TypeScript at root):**
247
+
248
+ ```json
249
+ [
250
+ {
251
+ "language": "typescript",
252
+ "path": ""
253
+ }
254
+ ]
255
+ ```
256
+
257
+ **Monorepo with multiple languages:**
258
+
259
+ ```json
260
+ [
261
+ {
262
+ "language": "typescript",
263
+ "path": "frontend"
264
+ },
265
+ {
266
+ "language": "python",
267
+ "path": "backend"
268
+ },
269
+ {
270
+ "language": "csharp",
271
+ "path": "services/auth"
272
+ }
273
+ ]
274
+ ```
275
+
276
+ **C++ project in a subdirectory:**
277
+
278
+ ```json
279
+ [
280
+ {
281
+ "language": "cpp",
282
+ "path": "engine/src"
283
+ }
284
+ ]
285
+ ```
286
+
287
+ **Mixed JavaScript and Python at root:**
288
+
289
+ ```json
290
+ [
291
+ {
292
+ "language": "javascript",
293
+ "path": ""
294
+ },
295
+ {
296
+ "language": "python",
297
+ "path": ""
298
+ }
299
+ ]
300
+ ```
301
+
302
+ ## MCP Tools
303
+
304
+ | Tool | Description |
305
+ |------|-------------|
306
+ | `project_overview` | Get the highest-level context for the project: architecture, tech stack, entry points, and top-level modules |
307
+ | `file_summary` | Get a summary of a file with optional parent directory context |
308
+ | `module_summary` | Get full documentation for the module that owns a given file, including DRG cluster metadata and parent/child module relationships |
309
+ | `dependencies` | Get all dependencies for a file with relationship summaries |
310
+ | `blast_radius` | Analyze what files would be affected if a file is modified or deleted |
311
+
312
+ Each tool accepts an optional `project_id` parameter. If not provided, it falls back to the `SHIFT_PROJECT_ID` environment variable.
313
+
314
+ ## Configuration Files
315
+
316
+ | File | Location | Description |
317
+ |------|----------|-------------|
318
+ | Global config | `~/.shift/config.json` | API key and server URLs |
319
+ | Project config | `.shift/config.json` | Project ID, name, and agent info |
320
+ | Scan targets | `.shift/scan_target.json` | Language and path targets for scanning (auto-created) |
@@ -0,0 +1,350 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { getProjectId } from '../../utils/config.js';
6
+ const execAsync = promisify(exec);
7
+ const SUPPORTED_TOOLS = ['shiftcode', 'claude-code', 'opencode', 'codex', 'copilot', 'droid'];
8
+ function isNotFound(e) {
9
+ return e.code === 'ENOENT' || /ENOENT|not found|not recognized/.test(e.message);
10
+ }
11
+ function getStderr(e) {
12
+ return (e.stderr || e.message || '').trim();
13
+ }
14
+ function shellEscape(arg) {
15
+ if (/^[a-zA-Z0-9._\-=/]+$/.test(arg))
16
+ return arg;
17
+ return `"${arg.replace(/"/g, '\\"')}"`;
18
+ }
19
+ function buildCommand(bin, args) {
20
+ return [bin, ...args.map(shellEscape)].join(' ');
21
+ }
22
+ function getMcpConfig(projectId) {
23
+ return {
24
+ command: 'lgraph',
25
+ args: ['mcp'],
26
+ env: { SHIFT_PROJECT_ID: projectId },
27
+ };
28
+ }
29
+ const CLAUDE_MD_SECTION_MARKER = '<!-- shift-mcp-instructions -->';
30
+ const CLAUDE_MD_CONTENT = `${CLAUDE_MD_SECTION_MARKER}
31
+ ## Shift MCP Tools — MANDATORY USAGE RULES
32
+
33
+ This project has the **Shift MCP server** (\`shift\`) configured with a pre-built knowledge graph of the entire codebase. You MUST use its tools as the primary way to understand code. Do NOT rely solely on reading raw files.
34
+
35
+ ### Tools
36
+
37
+ | Tool | Purpose |
38
+ |------|---------|
39
+ | \`file_summary\` | Get an summary of any file |
40
+ | \`dependencies\` | List every file that a given file imports/depends on |
41
+ | \`blast_radius\` | List every file that would be affected if a given file changed |
42
+
43
+ ### Supported file types
44
+
45
+ The knowledge graph is built **only** from source files. The tools will return an error for any other file type.
46
+
47
+ **Indexed (use MCP tools):**
48
+ \`.js\` \`.jsx\` \`.ts\` \`.tsx\` \`.py\` \`.java\` \`.cpp\` \`.cs\` \`.go\` \`.c\` \`.h\` \`.css\` \`.scss\` \`.html\`
49
+
50
+ **NOT indexed (read directly with normal file tools):**
51
+ \`.json\` \`.yaml\` \`.yml\` \`.toml\` \`.env\` \`.md\` \`.txt\` \`.pdf\` \`.png\` \`.lock\` \`.xml\` \`.csv\` and any other non-source format.
52
+
53
+ Do NOT call \`file_summary\`, \`dependencies\`, or \`blast_radius\` on non-indexed files — it will fail. Read those files directly instead.
54
+
55
+ ---
56
+
57
+ ### RULE 1 — Codebase exploration (e.g. "explain the codebase", "how does this project work")
58
+
59
+ You MUST call \`file_summary\` on **multiple key files** to build a complete picture. Do not stop after one call.
60
+
61
+ Follow this protocol:
62
+ 1. Identify the likely entry points (e.g. \`main.ts\`, \`index.ts\`, \`app.py\`, \`server.ts\`, \`main.py\`, route files, CLI entry files).
63
+ 2. Call \`file_summary\` on each entry point (run calls in parallel where possible).
64
+ 3. Call \`dependencies\` on the most central files to discover the module graph.
65
+ 4. Call \`file_summary\` on the key modules discovered in step 3.
66
+ 5. Synthesise all results into a coherent explanation.
67
+
68
+ Never answer a "explain the whole project" question from a single \`file_summary\` call.
69
+
70
+ ---
71
+
72
+ ### RULE 2 — Before reading any file
73
+
74
+ ALWAYS call \`file_summary\` first. Only open the raw file if you need implementation details that the summary does not cover.
75
+
76
+ ---
77
+
78
+ ### RULE 3 — Before editing any file
79
+
80
+ ALWAYS call \`blast_radius\` on that file first. List the affected files in your plan before making any changes.
81
+
82
+ ---
83
+
84
+ ### RULE 4 — When tracing a bug or data flow
85
+
86
+ Use \`dependencies\` to follow the chain rather than grepping manually. Start from the file where the symptom appears and walk the dependency graph.
87
+
88
+ ---
89
+
90
+ ### Usage reference
91
+
92
+ \`\`\`
93
+ # Understand a file
94
+ file_summary(file_path="src/server.ts")
95
+
96
+ # Understand with folder context (level = number of parent dirs to include)
97
+ file_summary(file_path="src/server.ts", level=1)
98
+
99
+ # See what a file imports
100
+ dependencies(file_path="src/server.ts")
101
+
102
+ # See what breaks if this file changes
103
+ blast_radius(file_path="src/server.ts")
104
+ \`\`\`
105
+
106
+ > Run \`lgraph update-drg\` after significant code changes to keep the knowledge graph current.
107
+ <!-- end-shift-mcp-instructions -->
108
+ `;
109
+ const CLAUDE_MD_END_MARKER = '<!-- end-shift-mcp-instructions -->';
110
+ function createClaudeMd(projectRoot) {
111
+ const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
112
+ if (fs.existsSync(claudeMdPath)) {
113
+ const existing = fs.readFileSync(claudeMdPath, 'utf-8');
114
+ if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
115
+ // Replace the existing section (start marker to end marker inclusive)
116
+ const start = existing.indexOf(CLAUDE_MD_SECTION_MARKER);
117
+ const end = existing.indexOf(CLAUDE_MD_END_MARKER);
118
+ if (end !== -1) {
119
+ const before = existing.slice(0, start).trimEnd();
120
+ const after = existing.slice(end + CLAUDE_MD_END_MARKER.length).trimStart();
121
+ const updated = (before ? before + '\n\n' : '') + CLAUDE_MD_CONTENT + (after ? '\n\n' + after : '');
122
+ fs.writeFileSync(claudeMdPath, updated);
123
+ }
124
+ else {
125
+ // Malformed — just overwrite from the marker onwards
126
+ const before = existing.slice(0, existing.indexOf(CLAUDE_MD_SECTION_MARKER)).trimEnd();
127
+ fs.writeFileSync(claudeMdPath, (before ? before + '\n\n' : '') + CLAUDE_MD_CONTENT);
128
+ }
129
+ console.log(' Updated Shift MCP instructions in CLAUDE.md.');
130
+ return;
131
+ }
132
+ // Append section to existing file
133
+ fs.writeFileSync(claudeMdPath, existing.trimEnd() + '\n\n' + CLAUDE_MD_CONTENT);
134
+ console.log(' Updated CLAUDE.md with Shift MCP instructions.');
135
+ }
136
+ else {
137
+ fs.writeFileSync(claudeMdPath, CLAUDE_MD_CONTENT);
138
+ console.log(' Created CLAUDE.md with Shift MCP instructions.');
139
+ }
140
+ }
141
+ async function addClaudeCode(projectId, projectRoot) {
142
+ const jsonPayload = JSON.stringify({
143
+ type: 'stdio',
144
+ command: 'lgraph',
145
+ args: ['mcp'],
146
+ env: { SHIFT_PROJECT_ID: projectId },
147
+ });
148
+ try {
149
+ await execAsync(buildCommand('claude', ['mcp', 'add-json', 'shift', jsonPayload]));
150
+ console.log(' Added Shift MCP server to Claude Code.');
151
+ console.log('\n Verify with: claude mcp list');
152
+ }
153
+ catch (e) {
154
+ const err = e;
155
+ const stderr = getStderr(err);
156
+ if (/already exists/.test(stderr)) {
157
+ console.log(' Shift MCP server is already configured in Claude Code.');
158
+ console.log(' To update, remove it first: claude mcp remove shift');
159
+ }
160
+ else if (isNotFound(err)) {
161
+ console.log(' "claude" CLI not found. Add manually:\n');
162
+ console.log(` claude mcp add-json shift '${jsonPayload}'`);
163
+ }
164
+ else {
165
+ console.log(` Failed: ${stderr}`);
166
+ console.log('\n Try adding manually:\n');
167
+ console.log(` claude mcp add-json shift '${jsonPayload}'`);
168
+ }
169
+ }
170
+ createClaudeMd(projectRoot);
171
+ }
172
+ async function addCodex(projectId) {
173
+ const config = getMcpConfig(projectId);
174
+ const args = ['mcp', 'add', 'shift'];
175
+ for (const [k, v] of Object.entries(config.env)) {
176
+ args.push('--env', `${k}=${v}`);
177
+ }
178
+ args.push('--', config.command, ...config.args);
179
+ const manualCmd = `codex ${args.join(' ')}`;
180
+ try {
181
+ await execAsync(buildCommand('codex', args));
182
+ console.log(' Added Shift MCP server to Codex.');
183
+ console.log('\n Verify with: codex mcp list');
184
+ }
185
+ catch (e) {
186
+ const err = e;
187
+ const stderr = getStderr(err);
188
+ if (isNotFound(err)) {
189
+ console.log(' "codex" CLI not found. Make sure Codex CLI is installed and on your PATH.\n');
190
+ console.log(' Once installed, add manually:\n');
191
+ }
192
+ else if (/already exists/.test(stderr)) {
193
+ console.log(' Shift MCP server is already configured in Codex.');
194
+ console.log(' To update, remove it first: codex mcp remove shift');
195
+ return;
196
+ }
197
+ else {
198
+ console.log(` Failed: ${stderr}`);
199
+ console.log('\n Try adding manually:\n');
200
+ }
201
+ console.log(` ${manualCmd}`);
202
+ }
203
+ }
204
+ async function addFactoryDroid(projectId) {
205
+ const config = getMcpConfig(projectId);
206
+ const args = ['mcp', 'add', 'shift', config.command];
207
+ for (const [k, v] of Object.entries(config.env)) {
208
+ args.push('--env', `${k}=${v}`);
209
+ }
210
+ const manualCmd = `droid ${args.join(' ')}`;
211
+ try {
212
+ await execAsync(buildCommand('droid', args));
213
+ console.log(' Added Shift MCP server to Factory Droid.');
214
+ console.log('\n Verify: type /mcp within droid to see configured servers');
215
+ }
216
+ catch (e) {
217
+ const err = e;
218
+ console.log(` Failed: ${getStderr(err)}`);
219
+ console.log('\n Try adding manually:\n');
220
+ console.log(` ${manualCmd}`);
221
+ }
222
+ }
223
+ function mergeJsonConfig(filePath, serverKey, serverValue) {
224
+ let config = {};
225
+ if (fs.existsSync(filePath)) {
226
+ try {
227
+ config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
228
+ }
229
+ catch {
230
+ console.log(` Warning: Could not parse existing ${filePath}, creating new file.`);
231
+ config = {};
232
+ }
233
+ }
234
+ // Navigate/create nested keys
235
+ let obj = config;
236
+ for (let i = 0; i < serverKey.length - 1; i++) {
237
+ const key = serverKey[i];
238
+ if (typeof obj[key] !== 'object' || obj[key] === null) {
239
+ obj[key] = {};
240
+ }
241
+ obj = obj[key];
242
+ }
243
+ const finalKey = serverKey[serverKey.length - 1];
244
+ obj[finalKey] = serverValue;
245
+ // Ensure parent directory exists
246
+ const dir = path.dirname(filePath);
247
+ if (!fs.existsSync(dir)) {
248
+ fs.mkdirSync(dir, { recursive: true });
249
+ }
250
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
251
+ }
252
+ function addOpencode(projectId, projectRoot) {
253
+ const filePath = path.join(projectRoot, 'opencode.json');
254
+ let existing = {};
255
+ if (fs.existsSync(filePath)) {
256
+ try {
257
+ existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
258
+ }
259
+ catch { /* will be recreated */ }
260
+ }
261
+ if (!existing['$schema']) {
262
+ existing['$schema'] = 'https://opencode.ai/config.json';
263
+ const dir = path.dirname(filePath);
264
+ if (!fs.existsSync(dir))
265
+ fs.mkdirSync(dir, { recursive: true });
266
+ fs.writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\n');
267
+ }
268
+ mergeJsonConfig(filePath, ['mcp', 'shift'], {
269
+ type: 'local',
270
+ command: ["lgraph", "mcp"],
271
+ enabled: true,
272
+ environment: { "SHIFT_PROJECT_ID": projectId },
273
+ });
274
+ console.log(` Updated ${filePath}`);
275
+ console.log('\n Verify: check "mcp.shift" in opencode.json');
276
+ }
277
+ function addShiftcode(projectId, projectRoot) {
278
+ const filePath = path.join(projectRoot, 'shiftcode.json');
279
+ let existing = {};
280
+ if (fs.existsSync(filePath)) {
281
+ try {
282
+ existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
283
+ }
284
+ catch { /* will be recreated */ }
285
+ }
286
+ if (!existing['$schema']) {
287
+ existing['$schema'] = 'https://opencode.ai/config.json';
288
+ const dir = path.dirname(filePath);
289
+ if (!fs.existsSync(dir))
290
+ fs.mkdirSync(dir, { recursive: true });
291
+ fs.writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\n');
292
+ }
293
+ mergeJsonConfig(filePath, ['mcp', 'shift'], {
294
+ type: 'local',
295
+ command: ["lgraph", "mcp"],
296
+ enabled: true,
297
+ environment: { "SHIFT_PROJECT_ID": projectId },
298
+ });
299
+ console.log(` Updated ${filePath}`);
300
+ console.log('\n Verify: check "mcp.shift" in shiftcode.json');
301
+ }
302
+ function addCopilot(projectId, projectRoot) {
303
+ const config = getMcpConfig(projectId);
304
+ const filePath = path.join(projectRoot, '.vscode', 'mcp.json');
305
+ mergeJsonConfig(filePath, ['servers', 'shift'], {
306
+ command: config.command,
307
+ args: config.args,
308
+ env: config.env,
309
+ });
310
+ console.log(` Updated ${filePath}`);
311
+ console.log('\n Verify: check "servers.shift" in .vscode/mcp.json');
312
+ }
313
+ export async function addCommand(tool) {
314
+ if (!SUPPORTED_TOOLS.includes(tool)) {
315
+ console.error(`\n Unknown tool: "${tool}"\n`);
316
+ console.error(' Supported tools:');
317
+ for (const t of SUPPORTED_TOOLS) {
318
+ console.error(` - ${t}`);
319
+ }
320
+ process.exit(1);
321
+ }
322
+ const projectRoot = process.cwd();
323
+ const projectId = getProjectId(projectRoot);
324
+ if (!projectId) {
325
+ console.error('\n No project configured. Run "lgraph init" first to set up your project.\n');
326
+ process.exit(1);
327
+ }
328
+ console.log(`\n Configuring Shift MCP for ${tool}...\n`);
329
+ switch (tool) {
330
+ case 'shiftcode':
331
+ addShiftcode(projectId, projectRoot);
332
+ break;
333
+ case 'claude-code':
334
+ await addClaudeCode(projectId, projectRoot);
335
+ break;
336
+ case 'opencode':
337
+ addOpencode(projectId, projectRoot);
338
+ break;
339
+ case 'codex':
340
+ await addCodex(projectId);
341
+ break;
342
+ case 'copilot':
343
+ addCopilot(projectId, projectRoot);
344
+ break;
345
+ case 'droid':
346
+ await addFactoryDroid(projectId);
347
+ break;
348
+ }
349
+ console.log('');
350
+ }