@latentforce/shift 1.0.8 → 1.0.10

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
@@ -2,35 +2,57 @@
2
2
 
3
3
  AI-powered code intelligence CLI and MCP server for Claude.
4
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
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
8
10
  npm install -g @latentforce/shift
9
11
  ```
10
12
 
13
+ Requires Node.js >= 18.
14
+
11
15
  ## Quick Start
12
16
 
13
- ### 1. Initialize your project
17
+ ### Guest mode (zero-config)
14
18
 
15
19
  ```bash
16
20
  cd /path/to/your/project
17
- shift-cli start # Configure API key and project
21
+ shift-cli init --guest # Auto-creates guest key + project, scans, and indexes
22
+ ```
23
+
24
+ ### With an API key
25
+
26
+ ```bash
27
+ # Create a new project and index it (prompts for template selection)
28
+ shift-cli init --api-key YOUR_KEY --project-name "My App"
29
+
30
+ # Fully non-interactive (CI/CD friendly)
31
+ shift-cli init --api-key YOUR_KEY --project-name "My App" --template TEMPLATE_ID
32
+ ```
33
+
34
+ ### Interactive (original behavior)
35
+
36
+ ```bash
37
+ shift-cli start # Configure API key and project interactively
18
38
  shift-cli init # Index project files
19
39
  ```
20
40
 
21
- ### 2. Add to Claude Code
41
+ ## MCP Integration
42
+
43
+ ### Add to Claude Code
22
44
 
23
45
  ```bash
24
46
  claude mcp add-json shift '{"type":"stdio","command":"shift-cli","args":["mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
25
47
  ```
26
48
 
27
- Or using npx:
49
+ Or using npx (no global install needed):
28
50
 
29
51
  ```bash
30
52
  claude mcp add-json shift '{"type":"stdio","command":"npx","args":["@latentforce/shift","mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
31
53
  ```
32
54
 
33
- ### 3. Add to Claude Desktop
55
+ ### Add to Claude Desktop
34
56
 
35
57
  Add to your config file (`%APPDATA%\Claude\claude_desktop_config.json` on Windows, `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
36
58
 
@@ -48,39 +70,189 @@ Add to your config file (`%APPDATA%\Claude\claude_desktop_config.json` on Window
48
70
  }
49
71
  ```
50
72
 
51
- ## MCP Tools
52
-
53
- | Tool | Description |
54
- |------|-------------|
55
- | `blast_radius` | Analyze what would be affected if a file is modified |
56
- | `dependencies` | Get all dependencies for a file |
57
- | `file_summary` | Generate a summary of a file |
58
-
59
73
  ## CLI Commands
60
74
 
61
75
  | Command | Description |
62
76
  |---------|-------------|
63
- | `shift-cli start` | Start daemon and configure project |
64
- | `shift-cli init` | Index project files |
77
+ | `shift-cli start` | Start the daemon and configure the project |
78
+ | `shift-cli init` | Scan and index project files |
79
+ | `shift-cli update-drg` | Update the dependency relationship graph |
65
80
  | `shift-cli stop` | Stop the daemon |
66
81
  | `shift-cli status` | Show current status |
67
82
  | `shift-cli config` | Manage configuration |
68
83
 
69
- ## Custom Backend URLs
84
+ ### `shift-cli init`
85
+
86
+ 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 and creates a default `.shiftignore` if one doesn't exist.
70
87
 
71
- **CLI** (set once globally):
88
+ If the project is already indexed, you will be prompted to re-index. Use `--force` to skip the prompt.
89
+
90
+ | Flag | Description |
91
+ |------|-------------|
92
+ | `--guest` | Use guest authentication (auto-creates a temporary project) |
93
+ | `--api-key <key>` | Provide API key directly |
94
+ | `--project-name <name>` | Create new project or match existing by name |
95
+ | `--project-id <id>` | Use existing project UUID |
96
+ | `--template <id>` | Migration template ID for project creation |
97
+ | `-f, --force` | Force re-indexing even if already indexed |
98
+ | `--no-ignore` | Skip `.shiftignore` rules for this run (send all files) |
72
99
 
73
100
  ```bash
74
- shift-cli config set api-url https://your-api.example.com
75
- shift-cli config set orch-url https://your-orch.example.com
76
- shift-cli config set ws-url wss://your-orch.example.com
101
+ shift-cli init # Interactive initialization
102
+ shift-cli init --force # Force re-index without prompt
103
+ shift-cli init --no-ignore # Index all files, skip .shiftignore
104
+ shift-cli init --guest # Quick init with guest auth
77
105
  ```
78
106
 
79
- **MCP Server** (add to env in Claude config):
107
+ ### `shift-cli start`
80
108
 
81
- ```json
82
- "env": {
83
- "SHIFT_PROJECT_ID": "YOUR_PROJECT_ID",
84
- "SHIFT_BACKEND_URL": "https://your-api.example.com"
85
- }
109
+ Resolves authentication, configures the project, and launches a background daemon that maintains a WebSocket connection to the Shift backend. Creates a default `.shiftignore` if one doesn't exist.
110
+
111
+ | Flag | Description |
112
+ |------|-------------|
113
+ | `--guest` | Use guest authentication (auto-creates a temporary project) |
114
+ | `--api-key <key>` | Provide API key directly |
115
+ | `--project-name <name>` | Create new project or match existing by name |
116
+ | `--project-id <id>` | Use existing project UUID |
117
+ | `--template <id>` | Migration template ID for project creation |
118
+
119
+ ```bash
120
+ shift-cli start # Interactive setup
121
+ shift-cli start --guest # Quick start without API key
122
+ shift-cli start --api-key <key> --project-name "My App"
123
+ ```
124
+
125
+ ### `shift-cli update-drg`
126
+
127
+ Scans JavaScript/TypeScript files (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`), detects git changes, and sends file contents to the backend for dependency analysis. Respects `.shiftignore` rules.
128
+
129
+ | Flag | Description |
130
+ |------|-------------|
131
+ | `-m, --mode <mode>` | `baseline` (all files) or `incremental` (git-changed only, default) |
132
+ | `--no-ignore` | Skip `.shiftignore` rules for this run (send all files) |
133
+
134
+ **Modes:**
135
+ - **incremental** (default) — sends only git-changed files for faster updates
136
+ - **baseline** — sends all JS/TS files for a full re-analysis
137
+
138
+ ```bash
139
+ shift-cli update-drg # Incremental update (default)
140
+ shift-cli update-drg -m baseline # Full re-analysis
141
+ shift-cli update-drg --no-ignore # Include ignored files
142
+ ```
143
+
144
+ ### `shift-cli config`
145
+
146
+ Manage Shift configuration (URLs, API key).
147
+
148
+ | Action | Description |
149
+ |--------|-------------|
150
+ | `show` | Display current configuration (default) |
151
+ | `set <key> <value>` | Set a configuration value |
152
+ | `clear [key]` | Clear a specific key or all configuration |
153
+
154
+ **Configurable keys:** `api-key`, `api-url`, `orch-url`, `ws-url`
155
+
156
+ URLs can also be set via environment variables: `SHIFT_API_URL`, `SHIFT_ORCH_URL`, `SHIFT_WS_URL`.
157
+
158
+ ```bash
159
+ shift-cli config # Show config
160
+ shift-cli config set api-key sk-abc123 # Set API key
161
+ shift-cli config set api-url https://api.shift.ai # Set API URL
162
+ shift-cli config clear api-key # Clear API key
163
+ shift-cli config clear # Clear all config
164
+ ```
165
+
166
+ ## `.shiftignore`
167
+
168
+ Shift uses a `.shiftignore` file to control which files and directories are excluded from indexing and dependency analysis. It follows the same syntax as `.gitignore`.
169
+
170
+ A default `.shiftignore` is **automatically created** the first time you run `shift-cli init` or `shift-cli start`. You can edit it to customize which files are excluded.
171
+
172
+ ### Syntax
173
+
174
+ - Blank lines are ignored
175
+ - Lines starting with `#` are comments
176
+ - Standard glob patterns (`*`, `**`, `?`)
177
+ - Trailing `/` matches directories only
178
+ - Leading `/` anchors to the project root
179
+ - `!` negates a pattern (re-includes a previously ignored path)
180
+
181
+ ### Default `.shiftignore`
182
+
183
+ The auto-generated file excludes common non-source directories and files:
184
+
185
+ ```gitignore
186
+ # Dependencies
187
+ node_modules/
188
+ vendor/
189
+ bower_components/
190
+
191
+ # Build output
192
+ dist/
193
+ build/
194
+ out/
195
+ .next/
196
+
197
+ # Test & coverage
198
+ coverage/
199
+ .nyc_output/
200
+
201
+ # Environment & secrets
202
+ .env
203
+ .env.*
204
+ *.pem
205
+ *.key
206
+
207
+ # Logs
208
+ *.log
209
+ logs/
210
+
211
+ # OS files
212
+ .DS_Store
213
+ Thumbs.db
214
+
215
+ # IDE
216
+ .vscode/
217
+ .idea/
218
+ *.swp
219
+ *.swo
220
+
221
+ # Python
222
+ __pycache__/
223
+ *.pyc
224
+ venv/
225
+ .venv/
226
+
227
+ # Misc
228
+ *.min.js
229
+ *.min.css
230
+ *.map
231
+ ```
232
+
233
+ ### Bypassing `.shiftignore`
234
+
235
+ Use the `--no-ignore` flag to skip `.shiftignore` rules for a single run:
236
+
237
+ ```bash
238
+ shift-cli init --no-ignore # Index all files
239
+ shift-cli update-drg --no-ignore # Include ignored files in DRG update
86
240
  ```
241
+
242
+ ## MCP Tools
243
+
244
+ | Tool | Description |
245
+ |------|-------------|
246
+ | `blast_radius` | Analyze what files would be affected if a file is modified or deleted |
247
+ | `dependencies` | Get all dependencies for a file with relationship summaries |
248
+ | `file_summary` | Get a summary of a file with optional parent directory context |
249
+
250
+ Each tool accepts an optional `project_id` parameter. If not provided, it falls back to the `SHIFT_PROJECT_ID` environment variable.
251
+
252
+ ## Configuration Files
253
+
254
+ | File | Location | Description |
255
+ |------|----------|-------------|
256
+ | Global config | `~/.shift/config.json` | API key and server URLs |
257
+ | Project config | `.shift/config.json` | Project ID, name, and agent info |
258
+ | Ignore rules | `.shiftignore` | Files/directories to exclude (auto-created) |
@@ -1,12 +1,15 @@
1
1
  import { exec } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { createInterface } from 'readline';
4
- import { getApiKey, setApiKey, setGuestKey, isGuestKey, readProjectConfig, writeProjectConfig } from '../../utils/config.js';
5
- import { promptApiKey, promptKeyChoice } from '../../utils/prompts.js';
6
- import { requestGuestKey, fetchProjectStatus } from '../../utils/api-client.js';
7
- import { getDaemonStatus } from '../../daemon/daemon-manager.js';
4
+ import { createRequire } from 'module';
5
+ import { readProjectConfig, writeProjectConfig } from '../../utils/config.js';
6
+ import { fetchProjectStatus, sendInitScan } from '../../utils/api-client.js';
7
+ import { resolveApiKey, resolveProject } from '../../utils/auth-resolver.js';
8
+ import { getDaemonStatus, startDaemon } from '../../daemon/daemon-manager.js';
8
9
  import { getProjectTree, extractAllFilePaths, categorizeFiles } from '../../utils/tree-scanner.js';
9
- import { sendInitScan } from '../../utils/api-client.js';
10
+ import { loadShiftIgnore, scaffoldShiftIgnore } from '../../utils/shiftignore.js';
11
+ const require = createRequire(import.meta.url);
12
+ const { version } = require('../../../package.json');
10
13
  const execAsync = promisify(exec);
11
14
  /**
12
15
  * Prompt user to confirm re-indexing an already indexed project
@@ -25,64 +28,64 @@ async function promptForReindex() {
25
28
  }
26
29
  export async function initCommand(options = {}) {
27
30
  const projectRoot = process.cwd();
31
+ const isAuthInteractive = !options.guest && !options.apiKey;
32
+ const isProjectInteractive = !!(process.stdin.isTTY);
28
33
  console.log('╔═══════════════════════════════════════════════╗');
29
34
  console.log('║ Initializing Shift Project ║');
30
35
  console.log('╚═══════════════════════════════════════════════╝\n');
31
- // Step 1: Check API key (with guest flow support)
36
+ // Step 1: Resolve API key
32
37
  console.log('[Init] Step 1/5: Checking API key...');
33
- let apiKey = getApiKey();
34
- if (!apiKey) {
35
- const choice = await promptKeyChoice();
36
- if (choice === 'paid') {
37
- apiKey = await promptApiKey();
38
- setApiKey(apiKey);
39
- console.log('[Init] API key saved to ~/.shift/config.json\n');
40
- }
41
- else {
42
- console.log('\n[Init] Requesting guest session...');
43
- try {
44
- const guestResponse = await requestGuestKey();
45
- setGuestKey(guestResponse.api_key);
46
- apiKey = guestResponse.api_key;
47
- console.log('[Init] ✓ Guest session started\n');
48
- }
49
- catch (error) {
50
- console.error(`\n❌ Failed to get guest key: ${error.message}`);
51
- process.exit(1);
52
- }
53
- }
54
- }
55
- else if (isGuestKey()) {
56
- console.log('[Init] ✓ Guest key found\n');
57
- }
58
- else {
59
- console.log('[Init] ✓ API key found\n');
60
- }
61
- // Step 2: Check project config
38
+ const authResult = await resolveApiKey({
39
+ guest: options.guest,
40
+ apiKey: options.apiKey,
41
+ interactive: isAuthInteractive,
42
+ commandLabel: 'Init',
43
+ });
44
+ const apiKey = authResult.apiKey;
45
+ // Step 2: Resolve project
62
46
  console.log('[Init] Step 2/5: Checking project configuration...');
47
+ // If guest flow returned a project_id, save it before resolving
48
+ if (authResult.projectId && !readProjectConfig(projectRoot)) {
49
+ const { setProject } = await import('../../utils/config.js');
50
+ setProject(authResult.projectId, options.projectName || 'Guest Project', projectRoot);
51
+ }
52
+ const project = await resolveProject({
53
+ apiKey,
54
+ projectId: options.projectId,
55
+ projectName: options.projectName,
56
+ template: options.template,
57
+ interactive: isProjectInteractive,
58
+ commandLabel: 'Init',
59
+ projectRoot,
60
+ });
61
+ // Re-read project config (may have been written by resolveProject)
63
62
  const projectConfig = readProjectConfig(projectRoot);
64
63
  if (!projectConfig) {
65
- console.error('\n❌ No project configured for this directory.');
66
- console.log('Run "shift start" first to configure the project.\n');
64
+ console.error('\n❌ Failed to configure project.');
67
65
  process.exit(1);
68
66
  }
69
- console.log(`[Init] ✓ Project: ${projectConfig.project_name} (${projectConfig.project_id})\n`);
70
67
  // Check if project is already indexed (skip check if --force flag is used)
71
68
  if (!options.force) {
72
69
  try {
73
- const projectStatus = await fetchProjectStatus(apiKey, projectConfig.project_id);
70
+ const projectStatus = await fetchProjectStatus(apiKey, project.projectId);
74
71
  if (projectStatus.indexed) {
75
72
  console.log(`[Init] ✓ Project already indexed (${projectStatus.file_count} files)\n`);
76
- const shouldReindex = await promptForReindex();
77
- if (!shouldReindex) {
78
- console.log('\n╔═══════════════════════════════════════════════╗');
79
- console.log('║ Project Already Initialized ║');
80
- console.log('╚═══════════════════════════════════════════════╝');
81
- console.log('\nUse "shift status" to check the current status.');
82
- console.log('Use "shift init --force" to force re-indexing.\n');
73
+ if (isProjectInteractive) {
74
+ const shouldReindex = await promptForReindex();
75
+ if (!shouldReindex) {
76
+ console.log('\n╔═══════════════════════════════════════════════╗');
77
+ console.log('║ Project Already Initialized ║');
78
+ console.log('╚═══════════════════════════════════════════════╝');
79
+ console.log('\nUse "shift-cli status" to check the current status.');
80
+ console.log('Use "shift-cli init --force" to force re-indexing.\n');
81
+ return;
82
+ }
83
+ console.log('\n[Init] Proceeding with re-indexing...\n');
84
+ }
85
+ else {
86
+ console.log('[Init] Already indexed. Use --force to re-index.\n');
83
87
  return;
84
88
  }
85
- console.log('\n[Init] Proceeding with re-indexing...\n');
86
89
  }
87
90
  }
88
91
  catch {
@@ -93,17 +96,43 @@ export async function initCommand(options = {}) {
93
96
  else {
94
97
  console.log('[Init] Force flag detected, skipping indexing status check...\n');
95
98
  }
96
- // Step 3: Check daemon status (warn if not running)
99
+ // Step 3: Check daemon status start it automatically if not running
97
100
  console.log('[Init] Step 3/5: Checking daemon status...');
98
- const status = getDaemonStatus(projectRoot);
99
- if (!status.running) {
100
- console.log('[Init] ⚠️ Warning: Daemon is not running. Run "shift start" to start it.\n');
101
+ const daemonStatus = getDaemonStatus(projectRoot);
102
+ if (!daemonStatus.running) {
103
+ console.log('[Init] Daemon not running. Starting it...');
104
+ try {
105
+ const daemonResult = await startDaemon(projectRoot, project.projectId, apiKey);
106
+ if (daemonResult.success) {
107
+ console.log(`[Init] ✓ Daemon started (PID: ${daemonResult.pid})\n`);
108
+ }
109
+ else {
110
+ console.log(`[Init] ⚠️ Could not start daemon: ${daemonResult.error}`);
111
+ console.log('[Init] Continuing without daemon — run "shift-cli start" later.\n');
112
+ }
113
+ }
114
+ catch (err) {
115
+ console.log(`[Init] ⚠️ Could not start daemon: ${err.message}`);
116
+ console.log('[Init] Continuing without daemon — run "shift-cli start" later.\n');
117
+ }
101
118
  }
102
119
  else {
103
- console.log(`[Init] ✓ Daemon running (PID: ${status.pid}, Connected: ${status.connected})\n`);
120
+ console.log(`[Init] ✓ Daemon running (PID: ${daemonStatus.pid}, Connected: ${daemonStatus.connected})\n`);
104
121
  }
105
122
  // Step 4: Scan project structure (matching extension's Step 6)
106
123
  console.log('[Init] Step 4/5: Scanning project structure...');
124
+ // Scaffold .shiftignore if it doesn't exist
125
+ if (scaffoldShiftIgnore(projectRoot)) {
126
+ console.log('[Init] ✓ Created default .shiftignore (edit it to customize ignored files)\n');
127
+ }
128
+ // Load .shiftignore (skip if --no-ignore)
129
+ const shiftIgnore = options.noIgnore ? null : loadShiftIgnore(projectRoot);
130
+ if (options.noIgnore) {
131
+ console.log('[Init] ⚠️ --no-ignore flag set — skipping .shiftignore rules\n');
132
+ }
133
+ else if (shiftIgnore) {
134
+ console.log('[Init] ✓ Applying .shiftignore rules\n');
135
+ }
107
136
  // Get project tree (matching extension's getProjectTree)
108
137
  const treeData = getProjectTree(projectRoot, {
109
138
  depth: 0, // Unlimited depth
@@ -120,6 +149,7 @@ export async function initCommand(options = {}) {
120
149
  'venv',
121
150
  'env',
122
151
  ],
152
+ shiftIgnore,
123
153
  });
124
154
  console.log(`[Init] Files: ${treeData.file_count}`);
125
155
  console.log(`[Init] Directories: ${treeData.dir_count}`);
@@ -150,7 +180,7 @@ export async function initCommand(options = {}) {
150
180
  // Step 5: Send scan to backend (matching extension's Step 9)
151
181
  console.log('[Init] Step 5/5: Sending scan to backend...');
152
182
  const payload = {
153
- project_id: projectConfig.project_id,
183
+ project_id: project.projectId,
154
184
  project_tree: treeData,
155
185
  git_info: gitInfo,
156
186
  file_manifest: {
@@ -158,13 +188,13 @@ export async function initCommand(options = {}) {
158
188
  categorized: categorized,
159
189
  },
160
190
  metadata: {
161
- extension_version: '1.0.2-cli', // CLI version
191
+ extension_version: `${version}-cli`,
162
192
  scan_timestamp: new Date().toISOString(),
163
- project_name: projectConfig.project_name,
193
+ project_name: project.projectName,
164
194
  },
165
195
  };
166
196
  try {
167
- const response = await sendInitScan(apiKey, projectConfig.project_id, payload);
197
+ const response = await sendInitScan(apiKey, project.projectId, payload);
168
198
  console.log('[Init] ✓ Backend initialization completed');
169
199
  console.log(`[Init] Files read: ${response.files_read}`);
170
200
  console.log(`[Init] Files failed: ${response.files_failed}`);
@@ -181,15 +211,17 @@ export async function initCommand(options = {}) {
181
211
  writeProjectConfig(projectConfig, projectRoot);
182
212
  console.log(`[Init] ✓ Agent info saved: ${agentInfo.agent_id}`);
183
213
  }
214
+ if (response.files_read === 0 && response.files_failed > 0) {
215
+ console.log(`\n[Init] ⚠️ No files were read (${response.files_failed} failed).`);
216
+ }
184
217
  console.log('\n╔═══════════════════════════════════════════════╗');
185
218
  console.log('║ ✓ Project Initialization Complete ║');
186
219
  console.log('╚═══════════════════════════════════════════════╝');
220
+ console.log('\nIndexing is running in the background. Use "shift-cli status" to check progress.');
187
221
  if (response.next_steps) {
188
222
  console.log('\nNext steps:');
189
223
  response.next_steps.forEach((step) => console.log(` - ${step}`));
190
224
  }
191
- console.log('\nFile indexing has been triggered. This may take a few moments.');
192
- console.log('Use "shift status" to check the daemon connection.\n');
193
225
  }
194
226
  catch (error) {
195
227
  console.error(`\n❌ Failed to initialize project: ${error.message}`);
@@ -1,79 +1,47 @@
1
- import { getApiKey, setApiKey, setGuestKey, isGuestKey, setProject, readProjectConfig } from '../../utils/config.js';
2
- import { promptApiKey, promptProjectId, promptSelectProject, promptKeyChoice } from '../../utils/prompts.js';
1
+ import { readProjectConfig, setProject } from '../../utils/config.js';
3
2
  import { startDaemon, getDaemonStatus } from '../../daemon/daemon-manager.js';
4
- import { fetchProjects, requestGuestKey } from '../../utils/api-client.js';
5
- export async function startCommand() {
3
+ import { resolveApiKey, resolveProject } from '../../utils/auth-resolver.js';
4
+ import { scaffoldShiftIgnore } from '../../utils/shiftignore.js';
5
+ export async function startCommand(options = {}) {
6
6
  const projectRoot = process.cwd();
7
+ const isAuthInteractive = !options.guest && !options.apiKey;
8
+ const isProjectInteractive = !!(process.stdin.isTTY);
7
9
  console.log('╔════════════════════════════════════════════╗');
8
10
  console.log('║ Starting Shift ║');
9
11
  console.log('╚════════════════════════════════════════════╝\n');
10
- // Step 1: Ensure API key exists
12
+ // Step 1: Resolve API key
11
13
  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
14
+ const authResult = await resolveApiKey({
15
+ guest: options.guest,
16
+ apiKey: options.apiKey,
17
+ interactive: isAuthInteractive,
18
+ commandLabel: 'Start',
19
+ });
20
+ const apiKey = authResult.apiKey;
21
+ // Step 2: Resolve project
41
22
  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`);
23
+ // If guest flow returned a project_id, save it before resolving
24
+ if (authResult.projectId && !readProjectConfig(projectRoot)) {
25
+ setProject(authResult.projectId, options.projectName || 'Guest Project', projectRoot);
72
26
  }
27
+ const project = await resolveProject({
28
+ apiKey,
29
+ projectId: options.projectId,
30
+ projectName: options.projectName,
31
+ template: options.template,
32
+ interactive: isProjectInteractive,
33
+ commandLabel: 'Start',
34
+ projectRoot,
35
+ });
36
+ const projectConfig = readProjectConfig(projectRoot);
73
37
  if (!projectConfig) {
74
38
  console.error('❌ Failed to configure project');
75
39
  process.exit(1);
76
40
  }
41
+ // Scaffold .shiftignore if it doesn't exist
42
+ if (scaffoldShiftIgnore(projectRoot)) {
43
+ console.log('[Start] ✓ Created default .shiftignore (edit it to customize ignored files)\n');
44
+ }
77
45
  // Step 3: Check if daemon is already running
78
46
  console.log('[Start] Step 3/4: Checking daemon status...');
79
47
  const status = getDaemonStatus(projectRoot);
@@ -85,7 +53,7 @@ export async function startCommand() {
85
53
  console.log('[Start] Daemon not running\n');
86
54
  // Step 4: Start daemon
87
55
  console.log('[Start] Step 4/4: Starting daemon...');
88
- const result = await startDaemon(projectRoot, projectConfig.project_id, apiKey);
56
+ const result = await startDaemon(projectRoot, project.projectId, apiKey);
89
57
  if (!result.success) {
90
58
  console.error(`\n❌ Failed to start daemon: ${result.error}`);
91
59
  process.exit(1);
@@ -94,7 +62,7 @@ export async function startCommand() {
94
62
  console.log('\n╔════════════════════════════════════════════╗');
95
63
  console.log('║ Shift is now running! ║');
96
64
  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');
65
+ console.log('\nUse "shift-cli status" to check connection status.');
66
+ console.log('Use "shift-cli init" to scan and index the project.');
67
+ console.log('Use "shift-cli stop" to stop the daemon.\n');
100
68
  }