@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 +320 -0
- package/build/cli/commands/add.js +350 -0
- package/build/cli/commands/config.js +142 -0
- package/build/cli/commands/init.js +285 -0
- package/build/cli/commands/start.js +65 -0
- package/build/cli/commands/status.js +76 -0
- package/build/cli/commands/stop.js +18 -0
- package/build/cli/commands/update-drg.js +194 -0
- package/build/daemon/daemon-manager.js +136 -0
- package/build/daemon/daemon.js +119 -0
- package/build/daemon/tools-executor.js +462 -0
- package/build/daemon/websocket-client.js +334 -0
- package/build/index.js +205 -0
- package/build/mcp-server.js +484 -0
- package/build/utils/api-client.js +147 -0
- package/build/utils/auth-resolver.js +184 -0
- package/build/utils/config.js +260 -0
- package/build/utils/machine-id.js +46 -0
- package/build/utils/prompts.js +114 -0
- package/build/utils/shiftignore.js +108 -0
- package/build/utils/tree-scanner.js +165 -0
- package/package.json +49 -0
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
|
+
}
|