@latentforce/shift 1.0.1 → 1.0.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/README.md CHANGED
@@ -1,103 +1,171 @@
1
- # shift-mcp
1
+ # @latentforce/shift
2
2
 
3
- A minimal utility server built for the Model Context Protocol (MCP). It exposes tools that call the Shift Lite webapp backend for file summary, dependencies, and blast radius (using the project's knowledge graph).
3
+ A CLI and MCP server for AI-powered code intelligence. It provides:
4
4
 
5
- ## Environment variables
5
+ 1. **CLI commands** - Index and manage your project
6
+ 2. **MCP server** - Expose tools (blast_radius, dependencies, file_summary) to Claude Desktop/Code
6
7
 
7
- - **`SHIFT_PROJECT_ID`** (optional if you pass per call): The default Shift Lite project UUID. If set, all tools use this project unless overridden by the `project_id` parameter in a tool call.
8
- - Example: `export SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e`
9
- - **`SHIFT_BACKEND_URL`** (optional): Webapp backend base URL. Default: `http://127.0.0.1:9000`.
8
+ ## Installation
10
9
 
11
- ## Project ID: env vs per-call
10
+ ```bash
11
+ npm install -g @latentforce/shift
12
+ ```
12
13
 
13
- - **Default:** Set `SHIFT_PROJECT_ID` in the MCP config (or env). All tools use that project.
14
- - **Override per call:** Each tool (`blast_radius`, `dependencies`, `file_summary`) accepts an optional **`project_id`** parameter. If you pass it, that call uses that project; otherwise the tool uses `SHIFT_PROJECT_ID`. So you can use one MCP for multiple projects by passing `project_id` in the call (e.g. "blast radius for project abc-123").
14
+ ## Quick Start
15
15
 
16
- ## Installation
16
+ ### Step 1: Configure backend URLs (if using hosted service)
17
17
 
18
- ### Option 1: Install globally (simple, persistent)
18
+ If you're connecting to a hosted Shift backend (not localhost), configure the URLs first:
19
19
 
20
20
  ```bash
21
- npm install -g @latentforce/shift
21
+ shift config set api-url https://api.latentforce.ai
22
+ shift config set orch-url https://orch.latentforce.ai
23
+ shift config set ws-url wss://ws.latentforce.ai
22
24
  ```
23
25
 
24
- Verify it works:
26
+ ### Step 2: Start and configure your project
27
+
25
28
  ```bash
26
- shift
29
+ cd /path/to/your/project
30
+ shift start
27
31
  ```
28
32
 
29
- You should see:
33
+ This will:
34
+ - Prompt for your Shift API key or let you continue as guest
35
+ - Let you select or enter your project ID
36
+ - Start a background daemon that connects to the backend
37
+
38
+ ### Step 3: Index your project
39
+
30
40
  ```bash
31
- Shift-lite MCP Server running on stdio
41
+ shift init
32
42
  ```
33
43
 
44
+ This scans your project files and sends the structure to the backend for indexing.
34
45
 
35
- ## Claude Code setup
46
+ ### Step 4: Configure MCP for Claude
36
47
 
37
- Claude Code does not auto-discover MCP servers. You must register it manually.
48
+ Now Claude can use the MCP tools. See [Claude Desktop setup](#claude-desktop-setup) or [Claude Code setup](#claude-code-setup) below.
38
49
 
39
- ### Install the Package (if not already)
40
- You can use this package without installing it globally by using npx, or install it globally for faster access.
50
+ ## CLI Commands
41
51
 
42
- ```bash
43
- npm install -g @latentforce/shift
44
- ```
52
+ | Command | Description |
53
+ |---------|-------------|
54
+ | `shift start` | Start the daemon and configure the project |
55
+ | `shift init` | Scan and index project files to backend |
56
+ | `shift stop` | Stop the running daemon |
57
+ | `shift status` | Show current status (API key, project, daemon, connection) |
58
+ | `shift config` | Show/set configuration (URLs, API key) |
59
+ | `shift` or `shift mcp` | Start MCP server on stdio (used by Claude) |
45
60
 
46
- ### Add to Claude Code
47
- - Replace SHIFT_BACKEND_URL and SHIFT_PROJECT_ID with correct values.
48
- - SHIFT_PROJECT_ID may be ignored as every tool accepts an optional **`project_id`** parameter.
49
- For Windows Users
50
- ```bash
51
- claude mcp add --scope user --transport stdio --env SHIFT_BACKEND_URL=BACKEND_URL --env SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e shift -- cmd /c npx -y @latentforce/shift
52
- ```
61
+ ## Authentication
53
62
 
54
- For macOS/Linux Users
55
- ```bash
56
- claude mcp add --scope user --transport stdio --env SHIFT_BACKEND_URL=BACKEND_URL --env SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e shift -- npx -y @latentforce/shift
57
- ```
63
+ When running `shift start` or `shift init`, you'll be prompted to choose:
58
64
 
59
- ### Verify Installation
60
- ```bash
61
- claude mcp list
62
- ```
65
+ 1. **API Key** - Enter your Shift API key for full access
66
+ 2. **Guest Mode** - Continue without an API key (limited features)
67
+
68
+ Both options work with all CLI commands. Your choice is saved to `~/.shift/config.json`.
69
+
70
+ ## MCP Tools
71
+
72
+ The MCP server exposes these tools to Claude:
73
+
74
+ | Tool | Description |
75
+ |------|-------------|
76
+ | `blast_radius` | Analyze what would be affected if a file is modified |
77
+ | `dependencies` | Get all dependencies for a file (direct and transitive) |
78
+ | `file_summary` | Generate a summary of a file with optional parent context |
79
+
80
+ ## Configuration
81
+
82
+ ### Using the Config Command (Recommended for Global Install)
83
+
84
+ The easiest way to configure Shift after installing globally with `npm i -g @latentforce/shift`:
63
85
 
64
- ### Use It
65
- Start a Claude Code session:
66
86
  ```bash
67
- claude
87
+ # Show current configuration
88
+ shift config
89
+
90
+ # Set backend URLs
91
+ shift config set api-url https://api.latentforce.ai
92
+ shift config set orch-url https://orch.latentforce.ai
93
+ shift config set ws-url wss://ws.latentforce.ai
94
+
95
+ # Set API key directly (alternative to interactive prompt)
96
+ shift config set api-key your-api-key-here
97
+
98
+ # Clear configuration
99
+ shift config clear urls # Reset URLs to defaults
100
+ shift config clear # Clear all config (prompts for confirmation)
68
101
  ```
69
102
 
70
- ## Claude Desktop setup
103
+ Configuration is saved to `~/.shift/config.json`.
104
+
105
+ ### Using Environment Variables
106
+
107
+ You can also set environment variables (they take priority over config file):
71
108
 
72
- Claude Desktop does not auto-discover MCP servers. You must register it manually.
109
+ | Variable | Description | Default |
110
+ |----------|-------------|---------|
111
+ | `SHIFT_PROJECT_ID` | Default project UUID (MCP only) | - |
112
+ | `SHIFT_BACKEND_URL` | Backend API URL (MCP only) | `http://127.0.0.1:9000` |
73
113
 
74
- ### Config file location
114
+ Example using environment variables:
75
115
 
76
- **macOS (Claude Desktop app):**
77
116
  ```bash
78
- ~/Library/Application Support/Claude/claude_desktop_config.json
117
+ # Set for current session
118
+ export SHIFT_API_URL=https://api.latentforce.ai
119
+ export SHIFT_ORCH_URL=https://orch.latentforce.ai
120
+ export SHIFT_WS_URL=wss://ws.latentforce.ai
121
+
122
+ # Or inline when running commands
123
+ SHIFT_API_URL=https://api.latentforce.ai shift start
79
124
  ```
80
- *(In Claude Desktop: Claude menu → Settings… → Developer → Edit Config)*
81
125
 
82
- **Linux (if different):**
126
+ ### Priority Order
127
+
128
+ URLs are resolved in this order:
129
+ 1. Environment variables (highest priority)
130
+ 2. Config file (`~/.shift/config.json`)
131
+ 3. Defaults (localhost)
132
+
133
+ ## Claude Code Setup
134
+
135
+ ### Add MCP Server
136
+
83
137
  ```bash
84
- ~/.config/claude-desktop/claude_desktop_config.json
138
+ claude mcp add-json shift '{"type":"stdio","command":"shift","args":[],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID_HERE","SHIFT_BACKEND_URL":"https://dev-shift-lite.latentforce.ai"}}'
85
139
  ```
86
140
 
87
- **Windows:**
141
+ ### Verify
142
+
88
143
  ```bash
89
- %APPDATA%\Claude\claude_desktop_config.json
144
+ claude mcp list
90
145
  ```
91
146
 
92
- Add this (replace `YOUR_PROJECT_UUID` with your Shift Lite project ID):
147
+ ## Claude Desktop Setup
148
+
149
+ ### Config File Location
150
+
151
+ | OS | Path |
152
+ |----|------|
153
+ | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
154
+ | Linux | `~/.config/claude-desktop/claude_desktop_config.json` |
155
+ | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
156
+
157
+ ### Configuration
158
+
159
+ Add to your config file:
160
+
93
161
  ```json
94
162
  {
95
163
  "mcpServers": {
96
164
  "shift": {
97
165
  "command": "shift",
98
166
  "env": {
99
- "SHIFT_BACKEND_URL": "BACKEND URL",
100
- "SHIFT_PROJECT_ID": "YOUR_PROJECT_UUID"
167
+ "SHIFT_BACKEND_URL": "https://dev-shift-lite.latentforce.ai",
168
+ "SHIFT_PROJECT_ID": "YOUR_PROJECT_ID_HERE"
101
169
  }
102
170
  }
103
171
  }
@@ -106,16 +174,55 @@ Add this (replace `YOUR_PROJECT_UUID` with your Shift Lite project ID):
106
174
 
107
175
  Restart Claude Desktop after saving.
108
176
 
177
+ ## Project ID
178
+
179
+ You can set the project ID in two ways:
180
+
181
+ 1. **Environment variable**: Set `SHIFT_PROJECT_ID` in MCP config
182
+ 2. **Per-call override**: Pass `project_id` parameter in each tool call
183
+
184
+ This allows one MCP server to work with multiple projects.
185
+
109
186
  ## Development
110
187
 
111
- Clone and build:
112
188
  ```bash
189
+ # Clone and install
113
190
  npm install
191
+
192
+ # Build
114
193
  npm run build
115
- ```
116
194
 
117
- Run locally:
118
- ```bash
195
+ # Run locally
119
196
  node build/index.js
197
+
198
+ # Test CLI commands
199
+ node build/index.js start
200
+ node build/index.js init
201
+ node build/index.js status
202
+ node build/index.js stop
120
203
  ```
121
204
 
205
+ ## How It All Works Together
206
+
207
+ ```
208
+ ┌─────────────────────────────────────────────────────────────────┐
209
+ │ User's Machine │
210
+ │ │
211
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
212
+ │ │ shift start │────▶│ Daemon │────▶│ Backend (WS) │ │
213
+ │ │ shift init │ │ (background)│ │ │ │
214
+ │ │ shift stop │ └─────────────┘ │ │ │
215
+ │ │ shift status│ │ │ │
216
+ │ └─────────────┘ │ Shift Lite │ │
217
+ │ │ Backend │ │
218
+ │ ┌─────────────┐ ┌─────────────┐ │ │ │
219
+ │ │ Claude │────▶│ shift (MCP) │────▶│ (REST API) │ │
220
+ │ │ Desktop/Code│ │ Server │ │ │ │
221
+ │ └─────────────┘ └─────────────┘ └─────────────────┘ │
222
+ │ │
223
+ └─────────────────────────────────────────────────────────────────┘
224
+
225
+ 1. `shift start` - Starts daemon, connects to backend via WebSocket
226
+ 2. `shift init` - Indexes project files to backend
227
+ 3. Claude calls MCP tools - MCP server queries backend REST API
228
+ ```
@@ -0,0 +1,136 @@
1
+ import { readGlobalConfig, setApiKey, setApiUrl, setOrchUrl, setWsUrl, clearUrls, clearApiKey, getConfiguredUrls, isGuestKey, } from '../../utils/config.js';
2
+ import { prompt } from '../../utils/prompts.js';
3
+ export async function configCommand(action, key, value) {
4
+ const parsedAction = (action || 'show');
5
+ switch (parsedAction) {
6
+ case 'show':
7
+ showConfig();
8
+ break;
9
+ case 'set':
10
+ await setConfigValue(key, value);
11
+ break;
12
+ case 'clear':
13
+ await clearConfig(key);
14
+ break;
15
+ default:
16
+ console.log('Usage: shift config [show|set|clear] [key] [value]');
17
+ console.log('');
18
+ console.log('Commands:');
19
+ console.log(' show Show current configuration');
20
+ console.log(' set <key> <value> Set a configuration value');
21
+ console.log(' clear [key] Clear configuration (all or specific key)');
22
+ console.log('');
23
+ console.log('Keys:');
24
+ console.log(' api-key Your Shift API key');
25
+ console.log(' api-url SHIFT_API_URL (default: http://localhost:9000)');
26
+ console.log(' orch-url SHIFT_ORCH_URL (default: http://localhost:9999)');
27
+ console.log(' ws-url SHIFT_WS_URL (default: ws://localhost:9999)');
28
+ console.log('');
29
+ console.log('Examples:');
30
+ console.log(' shift config');
31
+ console.log(' shift config set api-url https://api.latentforce.ai');
32
+ console.log(' shift config set orch-url https://orch.latentforce.ai');
33
+ console.log(' shift config set ws-url wss://ws.latentforce.ai');
34
+ console.log(' shift config clear urls');
35
+ break;
36
+ }
37
+ }
38
+ function showConfig() {
39
+ console.log('╔════════════════════════════════════════════╗');
40
+ console.log('║ Shift Configuration ║');
41
+ console.log('╚════════════════════════════════════════════╝\n');
42
+ const globalConfig = readGlobalConfig();
43
+ const urls = getConfiguredUrls();
44
+ // API Key
45
+ console.log('API Key:');
46
+ if (globalConfig?.api_key) {
47
+ const keyType = isGuestKey() ? '(guest)' : '(paid)';
48
+ const maskedKey = globalConfig.api_key.substring(0, 8) + '...' + globalConfig.api_key.slice(-4);
49
+ console.log(` ${maskedKey} ${keyType}`);
50
+ }
51
+ else {
52
+ console.log(' Not configured');
53
+ }
54
+ console.log('');
55
+ // URLs
56
+ console.log('URLs:');
57
+ console.log(` API URL: ${urls.api_url}`);
58
+ console.log(` Orch URL: ${urls.orch_url}`);
59
+ console.log(` WS URL: ${urls.ws_url}`);
60
+ console.log('');
61
+ // Source info
62
+ console.log('Config file: ~/.shift/config.json');
63
+ console.log('');
64
+ console.log('Tip: URLs can also be set via environment variables:');
65
+ console.log(' SHIFT_API_URL, SHIFT_ORCH_URL, SHIFT_WS_URL');
66
+ }
67
+ async function setConfigValue(key, value) {
68
+ if (!key) {
69
+ console.error('Error: Key is required. Run "shift config" to see available keys.');
70
+ process.exit(1);
71
+ }
72
+ // If no value provided, prompt for it
73
+ if (!value) {
74
+ value = await prompt(`Enter value for ${key}: `);
75
+ if (!value) {
76
+ console.error('Error: Value is required.');
77
+ process.exit(1);
78
+ }
79
+ }
80
+ switch (key) {
81
+ case 'api-key':
82
+ setApiKey(value);
83
+ console.log('✓ API key saved');
84
+ break;
85
+ case 'api-url':
86
+ setApiUrl(value);
87
+ console.log(`✓ API URL set to: ${value}`);
88
+ break;
89
+ case 'orch-url':
90
+ setOrchUrl(value);
91
+ console.log(`✓ Orch URL set to: ${value}`);
92
+ break;
93
+ case 'ws-url':
94
+ setWsUrl(value);
95
+ console.log(`✓ WS URL set to: ${value}`);
96
+ break;
97
+ default:
98
+ console.error(`Unknown key: ${key}`);
99
+ console.log('Available keys: api-key, api-url, orch-url, ws-url');
100
+ process.exit(1);
101
+ }
102
+ console.log('\nNote: Restart any running daemon for URL changes to take effect.');
103
+ }
104
+ async function clearConfig(key) {
105
+ if (!key) {
106
+ // Clear everything
107
+ const answer = await prompt('Clear all configuration? (y/N): ');
108
+ if (answer.toLowerCase() !== 'y') {
109
+ console.log('Cancelled.');
110
+ return;
111
+ }
112
+ clearApiKey();
113
+ clearUrls();
114
+ console.log('✓ All configuration cleared');
115
+ return;
116
+ }
117
+ switch (key) {
118
+ case 'api-key':
119
+ clearApiKey();
120
+ console.log('✓ API key cleared');
121
+ break;
122
+ case 'urls':
123
+ clearUrls();
124
+ console.log('✓ URLs cleared (will use defaults)');
125
+ break;
126
+ case 'api-url':
127
+ case 'orch-url':
128
+ case 'ws-url':
129
+ console.log('Use "shift config clear urls" to clear all URLs');
130
+ break;
131
+ default:
132
+ console.error(`Unknown key: ${key}`);
133
+ console.log('Available keys: api-key, urls');
134
+ process.exit(1);
135
+ }
136
+ }
@@ -0,0 +1,156 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { getApiKey, setApiKey, setGuestKey, isGuestKey, readProjectConfig, writeProjectConfig } from '../../utils/config.js';
4
+ import { promptApiKey, promptKeyChoice } from '../../utils/prompts.js';
5
+ import { requestGuestKey } from '../../utils/api-client.js';
6
+ import { getDaemonStatus } from '../../daemon/daemon-manager.js';
7
+ import { getProjectTree, extractAllFilePaths, categorizeFiles } from '../../utils/tree-scanner.js';
8
+ import { sendInitScan } from '../../utils/api-client.js';
9
+ const execAsync = promisify(exec);
10
+ export async function initCommand() {
11
+ const projectRoot = process.cwd();
12
+ console.log('╔═══════════════════════════════════════════════╗');
13
+ console.log('║ Initializing Shift Project ║');
14
+ console.log('╚═══════════════════════════════════════════════╝\n');
15
+ // Step 1: Check API key (with guest flow support)
16
+ console.log('[Init] Step 1/5: Checking API key...');
17
+ let apiKey = getApiKey();
18
+ if (!apiKey) {
19
+ const choice = await promptKeyChoice();
20
+ if (choice === 'paid') {
21
+ apiKey = await promptApiKey();
22
+ setApiKey(apiKey);
23
+ console.log('[Init] ✓ API key saved to ~/.shift/config.json\n');
24
+ }
25
+ else {
26
+ console.log('\n[Init] Requesting guest session...');
27
+ try {
28
+ const guestResponse = await requestGuestKey();
29
+ setGuestKey(guestResponse.api_key);
30
+ apiKey = guestResponse.api_key;
31
+ console.log('[Init] ✓ Guest session started\n');
32
+ }
33
+ catch (error) {
34
+ console.error(`\n❌ Failed to get guest key: ${error.message}`);
35
+ process.exit(1);
36
+ }
37
+ }
38
+ }
39
+ else if (isGuestKey()) {
40
+ console.log('[Init] ✓ Guest key found\n');
41
+ }
42
+ else {
43
+ console.log('[Init] ✓ API key found\n');
44
+ }
45
+ // Step 2: Check project config
46
+ console.log('[Init] Step 2/5: Checking project configuration...');
47
+ const projectConfig = readProjectConfig(projectRoot);
48
+ if (!projectConfig) {
49
+ console.error('\n❌ No project configured for this directory.');
50
+ console.log('Run "shift start" first to configure the project.\n');
51
+ process.exit(1);
52
+ }
53
+ console.log(`[Init] ✓ Project: ${projectConfig.project_name} (${projectConfig.project_id})\n`);
54
+ // Step 3: Check daemon status (warn if not running)
55
+ console.log('[Init] Step 3/5: Checking daemon status...');
56
+ const status = getDaemonStatus(projectRoot);
57
+ if (!status.running) {
58
+ console.log('[Init] ⚠️ Warning: Daemon is not running. Run "shift start" to start it.\n');
59
+ }
60
+ else {
61
+ console.log(`[Init] ✓ Daemon running (PID: ${status.pid}, Connected: ${status.connected})\n`);
62
+ }
63
+ // Step 4: Scan project structure (matching extension's Step 6)
64
+ console.log('[Init] Step 4/5: Scanning project structure...');
65
+ // Get project tree (matching extension's getProjectTree)
66
+ const treeData = getProjectTree(projectRoot, {
67
+ depth: 0, // Unlimited depth
68
+ exclude_patterns: [
69
+ '.git',
70
+ 'node_modules',
71
+ '__pycache__',
72
+ '.vscode',
73
+ 'dist',
74
+ 'build',
75
+ '.shift',
76
+ '.next',
77
+ 'coverage',
78
+ 'venv',
79
+ 'env',
80
+ ],
81
+ });
82
+ console.log(`[Init] Files: ${treeData.file_count}`);
83
+ console.log(`[Init] Directories: ${treeData.dir_count}`);
84
+ console.log(`[Init] Total size: ${treeData.total_size_mb} MB\n`);
85
+ // Get git info (matching extension)
86
+ let gitInfo = {
87
+ current_branch: '',
88
+ original_branch: 'shift_original',
89
+ migrate_branch: 'shift_migrated',
90
+ has_uncommitted_changes: false,
91
+ };
92
+ try {
93
+ const { stdout: currentBranch } = await execAsync('git branch --show-current', { cwd: projectRoot });
94
+ const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: projectRoot });
95
+ gitInfo.current_branch = currentBranch.trim();
96
+ gitInfo.has_uncommitted_changes = statusOutput.trim().length > 0;
97
+ console.log(`[Init] ✓ Git info: branch=${gitInfo.current_branch}, uncommitted=${gitInfo.has_uncommitted_changes}\n`);
98
+ }
99
+ catch {
100
+ console.log('[Init] ⚠️ Not a git repository or git not available\n');
101
+ }
102
+ // Extract file paths and categorize (matching extension)
103
+ const allFiles = extractAllFilePaths(treeData.tree);
104
+ const categorized = categorizeFiles(treeData.tree);
105
+ console.log(`[Init] Source files: ${categorized.source_files.length}`);
106
+ console.log(`[Init] Config files: ${categorized.config_files.length}`);
107
+ console.log(`[Init] Asset files: ${categorized.asset_files.length}\n`);
108
+ // Step 5: Send scan to backend (matching extension's Step 9)
109
+ console.log('[Init] Step 5/5: Sending scan to backend...');
110
+ const payload = {
111
+ project_id: projectConfig.project_id,
112
+ project_tree: treeData,
113
+ git_info: gitInfo,
114
+ file_manifest: {
115
+ all_files: allFiles,
116
+ categorized: categorized,
117
+ },
118
+ metadata: {
119
+ extension_version: '1.0.2-cli', // CLI version
120
+ scan_timestamp: new Date().toISOString(),
121
+ project_name: projectConfig.project_name,
122
+ },
123
+ };
124
+ try {
125
+ const response = await sendInitScan(apiKey, projectConfig.project_id, payload);
126
+ console.log('[Init] ✓ Backend initialization completed');
127
+ console.log(`[Init] Files read: ${response.files_read}`);
128
+ console.log(`[Init] Files failed: ${response.files_failed}`);
129
+ // Update local config with agent info (matching extension)
130
+ if (response.agents_created?.theme_planner) {
131
+ const agentInfo = {
132
+ agent_id: response.agents_created.theme_planner.agent_id,
133
+ agent_name: response.agents_created.theme_planner.agent_name,
134
+ agent_type: 'theme_planner',
135
+ created_at: new Date().toISOString(),
136
+ };
137
+ projectConfig.agents = projectConfig.agents || [];
138
+ projectConfig.agents.push(agentInfo);
139
+ writeProjectConfig(projectConfig, projectRoot);
140
+ console.log(`[Init] ✓ Agent info saved: ${agentInfo.agent_id}`);
141
+ }
142
+ console.log('\n╔═══════════════════════════════════════════════╗');
143
+ console.log('║ ✓ Project Initialization Complete ║');
144
+ console.log('╚═══════════════════════════════════════════════╝');
145
+ if (response.next_steps) {
146
+ console.log('\nNext steps:');
147
+ response.next_steps.forEach((step) => console.log(` - ${step}`));
148
+ }
149
+ console.log('\nFile indexing has been triggered. This may take a few moments.');
150
+ console.log('Use "shift status" to check the daemon connection.\n');
151
+ }
152
+ catch (error) {
153
+ console.error(`\n❌ Failed to initialize project: ${error.message}`);
154
+ process.exit(1);
155
+ }
156
+ }
@@ -0,0 +1,100 @@
1
+ import { getApiKey, setApiKey, setGuestKey, isGuestKey, setProject, readProjectConfig } from '../../utils/config.js';
2
+ import { promptApiKey, promptProjectId, promptSelectProject, promptKeyChoice } from '../../utils/prompts.js';
3
+ import { startDaemon, getDaemonStatus } from '../../daemon/daemon-manager.js';
4
+ import { fetchProjects, requestGuestKey } from '../../utils/api-client.js';
5
+ export async function startCommand() {
6
+ const projectRoot = process.cwd();
7
+ console.log('╔════════════════════════════════════════════╗');
8
+ console.log('║ Starting Shift ║');
9
+ console.log('╚════════════════════════════════════════════╝\n');
10
+ // Step 1: Ensure API key exists
11
+ console.log('[Start] Step 1/4: Checking API key...');
12
+ let apiKey = getApiKey();
13
+ if (!apiKey) {
14
+ const choice = await promptKeyChoice();
15
+ if (choice === 'paid') {
16
+ apiKey = await promptApiKey();
17
+ setApiKey(apiKey);
18
+ console.log('[Start] ✓ API key saved to ~/.shift/config.json\n');
19
+ }
20
+ else {
21
+ console.log('\n[Start] Requesting guest session...');
22
+ try {
23
+ const guestResponse = await requestGuestKey();
24
+ setGuestKey(guestResponse.api_key);
25
+ apiKey = guestResponse.api_key;
26
+ console.log('[Start] ✓ Guest session started\n');
27
+ }
28
+ catch (error) {
29
+ console.error(`\n❌ Failed to get guest key: ${error.message}`);
30
+ process.exit(1);
31
+ }
32
+ }
33
+ }
34
+ else if (isGuestKey()) {
35
+ console.log('[Start] ✓ Guest key found\n');
36
+ }
37
+ else {
38
+ console.log('[Start] ✓ API key found\n');
39
+ }
40
+ // Step 2: Check/create project config
41
+ console.log('[Start] Step 2/4: Checking project configuration...');
42
+ let projectConfig = readProjectConfig(projectRoot);
43
+ if (!projectConfig) {
44
+ // Fetch available projects
45
+ console.log('[Start] Fetching your projects...');
46
+ try {
47
+ const projects = await fetchProjects(apiKey);
48
+ if (!projects || projects.length === 0) {
49
+ console.error('\n❌ No projects found. Create one in the Shift dashboard first.');
50
+ process.exit(1);
51
+ }
52
+ console.log(`[Start] Found ${projects.length} project(s)\n`);
53
+ // Let user select a project
54
+ const selected = await promptSelectProject(projects);
55
+ // Save project config (matching extension's .shift/config.json structure)
56
+ setProject(selected.project_id, selected.project_name, projectRoot);
57
+ console.log(`[Start] ✓ Project "${selected.project_name}" saved to .shift/config.json\n`);
58
+ projectConfig = readProjectConfig(projectRoot);
59
+ }
60
+ catch (error) {
61
+ // If fetching fails, fall back to manual entry
62
+ console.error(`\n⚠️ Could not fetch projects: ${error.message}`);
63
+ console.log('Falling back to manual project ID entry...\n');
64
+ const projectId = await promptProjectId();
65
+ setProject(projectId, 'Unknown Project', projectRoot);
66
+ console.log(`[Start] ✓ Project ID saved to .shift/config.json\n`);
67
+ projectConfig = readProjectConfig(projectRoot);
68
+ }
69
+ }
70
+ else {
71
+ console.log(`[Start] ✓ Project found: ${projectConfig.project_name} (${projectConfig.project_id})\n`);
72
+ }
73
+ if (!projectConfig) {
74
+ console.error('❌ Failed to configure project');
75
+ process.exit(1);
76
+ }
77
+ // Step 3: Check if daemon is already running
78
+ console.log('[Start] Step 3/4: Checking daemon status...');
79
+ const status = getDaemonStatus(projectRoot);
80
+ if (status.running) {
81
+ console.log(`[Start] ✓ Daemon is already running (PID: ${status.pid})`);
82
+ console.log(`[Start] WebSocket: ${status.connected ? 'Connected' : 'Connecting...'}\n`);
83
+ return;
84
+ }
85
+ console.log('[Start] Daemon not running\n');
86
+ // Step 4: Start daemon
87
+ console.log('[Start] Step 4/4: Starting daemon...');
88
+ const result = await startDaemon(projectRoot, projectConfig.project_id, apiKey);
89
+ if (!result.success) {
90
+ console.error(`\n❌ Failed to start daemon: ${result.error}`);
91
+ process.exit(1);
92
+ }
93
+ console.log(`[Start] ✓ Daemon started (PID: ${result.pid})`);
94
+ console.log('\n╔════════════════════════════════════════════╗');
95
+ console.log('║ Shift is now running! ║');
96
+ console.log('╚════════════════════════════════════════════╝');
97
+ console.log('\nUse "shift status" to check connection status.');
98
+ console.log('Use "shift init" to scan and index the project.');
99
+ console.log('Use "shift stop" to stop the daemon.\n');
100
+ }