@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 +198 -26
- package/build/cli/commands/init.js +90 -58
- package/build/cli/commands/start.js +36 -68
- package/build/cli/commands/status.js +17 -7
- package/build/cli/commands/update-drg.js +188 -0
- package/build/daemon/tools-executor.js +19 -86
- package/build/index.js +144 -15
- package/build/mcp-server.js +105 -8
- package/build/utils/api-client.js +70 -35
- package/build/utils/auth-resolver.js +184 -0
- package/build/utils/prompts.js +45 -0
- package/build/utils/shiftignore.js +108 -0
- package/build/utils/tree-scanner.js +11 -2
- package/package.json +2 -1
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
|
-
###
|
|
17
|
+
### Guest mode (zero-config)
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
20
|
cd /path/to/your/project
|
|
17
|
-
shift-cli
|
|
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
|
-
|
|
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
|
-
###
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
shift-cli
|
|
76
|
-
shift-cli
|
|
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
|
-
|
|
107
|
+
### `shift-cli start`
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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 {
|
|
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:
|
|
36
|
+
// Step 1: Resolve API key
|
|
32
37
|
console.log('[Init] Step 1/5: Checking API key...');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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❌
|
|
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,
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
99
|
-
if (!
|
|
100
|
-
console.log('[Init]
|
|
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: ${
|
|
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:
|
|
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:
|
|
191
|
+
extension_version: `${version}-cli`,
|
|
162
192
|
scan_timestamp: new Date().toISOString(),
|
|
163
|
-
project_name:
|
|
193
|
+
project_name: project.projectName,
|
|
164
194
|
},
|
|
165
195
|
};
|
|
166
196
|
try {
|
|
167
|
-
const response = await sendInitScan(apiKey,
|
|
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 {
|
|
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 {
|
|
5
|
-
|
|
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:
|
|
12
|
+
// Step 1: Resolve API key
|
|
11
13
|
console.log('[Start] Step 1/4: Checking API key...');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
43
|
-
if (!
|
|
44
|
-
|
|
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,
|
|
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
|
}
|