@latentforce/shift 1.0.5 → 1.0.7
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 +39 -156
- package/build/cli/commands/init.js +44 -2
- package/build/cli/commands/status.js +15 -0
- package/build/index.js +12 -4
- package/build/mcp-server.js +1 -1
- package/build/utils/api-client.js +38 -1
- package/build/utils/machine-id.js +46 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# @latentforce/shift
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
1. **CLI commands** - Index and manage your project
|
|
6
|
-
2. **MCP server** - Expose tools (blast_radius, dependencies, file_summary) to Claude Desktop/Code
|
|
3
|
+
AI-powered code intelligence CLI and MCP server for Claude.
|
|
7
4
|
|
|
8
5
|
## Installation
|
|
9
6
|
|
|
@@ -13,191 +10,77 @@ npm install -g @latentforce/shift
|
|
|
13
10
|
|
|
14
11
|
## Quick Start
|
|
15
12
|
|
|
16
|
-
###
|
|
17
|
-
|
|
18
|
-
If you're connecting to a hosted Shift backend (not localhost), configure the URLs first:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
shift config set api-url https://dev-shift-lite.latentforce.ai
|
|
22
|
-
shift config set orch-url https://agent-orch.latentforce.ai
|
|
23
|
-
shift config set ws-url wss://agent-orch.latentforce.ai
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Step 2: Start and configure your project
|
|
13
|
+
### 1. Initialize your project
|
|
27
14
|
|
|
28
15
|
```bash
|
|
29
16
|
cd /path/to/your/project
|
|
30
|
-
shift start
|
|
17
|
+
shift-cli start # Configure API key and project
|
|
18
|
+
shift-cli init # Index project files
|
|
31
19
|
```
|
|
32
20
|
|
|
33
|
-
|
|
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
|
|
21
|
+
### 2. Add to Claude Code
|
|
39
22
|
|
|
40
23
|
```bash
|
|
41
|
-
shift
|
|
24
|
+
claude mcp add-json shift '{"type":"stdio","command":"shift-cli","args":["mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
|
|
42
25
|
```
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### Step 4: Configure MCP for Claude
|
|
47
|
-
|
|
48
|
-
Now Claude can use the MCP tools. See [Claude Desktop setup](#claude-desktop-setup) or [Claude Code setup](#claude-code-setup) below.
|
|
49
|
-
|
|
50
|
-
## CLI Commands
|
|
51
|
-
|
|
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) |
|
|
60
|
-
|
|
61
|
-
## Authentication
|
|
62
|
-
|
|
63
|
-
When running `shift start` or `shift init`, you'll be prompted to choose:
|
|
64
|
-
|
|
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`:
|
|
27
|
+
Or using npx:
|
|
85
28
|
|
|
86
29
|
```bash
|
|
87
|
-
|
|
88
|
-
shift config
|
|
89
|
-
|
|
90
|
-
# Set backend URLs
|
|
91
|
-
shift config set api-url https://dev-shift-lite.latentforce.ai
|
|
92
|
-
shift config set orch-url https://agent-orch.latentforce.ai
|
|
93
|
-
shift config set ws-url wss://agent-orch.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)
|
|
30
|
+
claude mcp add-json shift '{"type":"stdio","command":"npx","args":["@latentforce/shift","mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
|
|
101
31
|
```
|
|
102
32
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
### Using Environment Variables
|
|
106
|
-
|
|
107
|
-
You can also set environment variables (they take priority over config file):
|
|
33
|
+
### 3. Add to Claude Desktop
|
|
108
34
|
|
|
109
|
-
|
|
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` |
|
|
113
|
-
|
|
114
|
-
Example using environment variables:
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
# Set for current session
|
|
118
|
-
export SHIFT_API_URL=https://dev-shift-lite.latentforce.ai
|
|
119
|
-
export SHIFT_ORCH_URL=https://agent-orch.latentforce.ai
|
|
120
|
-
export SHIFT_WS_URL=wss://agent-orch.latentforce.ai
|
|
121
|
-
|
|
122
|
-
# Or inline when running commands
|
|
123
|
-
SHIFT_API_URL=https://dev-shift-lite.latentforce.ai shift start
|
|
124
|
-
```
|
|
125
|
-
|
|
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
|
-
|
|
137
|
-
```bash
|
|
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"}}'
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Verify
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
claude mcp list
|
|
145
|
-
```
|
|
146
|
-
|
|
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:
|
|
35
|
+
Add to your config file (`%APPDATA%\Claude\claude_desktop_config.json` on Windows, `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
160
36
|
|
|
161
37
|
```json
|
|
162
38
|
{
|
|
163
39
|
"mcpServers": {
|
|
164
40
|
"shift": {
|
|
165
|
-
"command": "shift",
|
|
41
|
+
"command": "shift-cli",
|
|
42
|
+
"args": ["mcp"],
|
|
166
43
|
"env": {
|
|
167
|
-
"
|
|
168
|
-
"SHIFT_PROJECT_ID": "YOUR_PROJECT_ID_HERE"
|
|
44
|
+
"SHIFT_PROJECT_ID": "YOUR_PROJECT_ID"
|
|
169
45
|
}
|
|
170
46
|
}
|
|
171
47
|
}
|
|
172
48
|
}
|
|
173
49
|
```
|
|
174
50
|
|
|
175
|
-
|
|
51
|
+
## MCP Tools
|
|
176
52
|
|
|
177
|
-
|
|
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 |
|
|
178
58
|
|
|
179
|
-
|
|
59
|
+
## CLI Commands
|
|
180
60
|
|
|
181
|
-
|
|
182
|
-
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `shift-cli start` | Start daemon and configure project |
|
|
64
|
+
| `shift-cli init` | Index project files |
|
|
65
|
+
| `shift-cli stop` | Stop the daemon |
|
|
66
|
+
| `shift-cli status` | Show current status |
|
|
67
|
+
| `shift-cli config` | Manage configuration |
|
|
183
68
|
|
|
184
|
-
|
|
69
|
+
## Custom Backend URLs
|
|
185
70
|
|
|
186
|
-
|
|
71
|
+
**CLI** (set once globally):
|
|
187
72
|
|
|
188
73
|
```bash
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
npm run build
|
|
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
|
|
77
|
+
```
|
|
194
78
|
|
|
195
|
-
|
|
196
|
-
node build/index.js
|
|
79
|
+
**MCP Server** (add to env in Claude config):
|
|
197
80
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
81
|
+
```json
|
|
82
|
+
"env": {
|
|
83
|
+
"SHIFT_PROJECT_ID": "YOUR_PROJECT_ID",
|
|
84
|
+
"SHIFT_BACKEND_URL": "https://your-api.example.com"
|
|
85
|
+
}
|
|
203
86
|
```
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
+
import { createInterface } from 'readline';
|
|
3
4
|
import { getApiKey, setApiKey, setGuestKey, isGuestKey, readProjectConfig, writeProjectConfig } from '../../utils/config.js';
|
|
4
5
|
import { promptApiKey, promptKeyChoice } from '../../utils/prompts.js';
|
|
5
|
-
import { requestGuestKey } from '../../utils/api-client.js';
|
|
6
|
+
import { requestGuestKey, fetchProjectStatus } from '../../utils/api-client.js';
|
|
6
7
|
import { getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
7
8
|
import { getProjectTree, extractAllFilePaths, categorizeFiles } from '../../utils/tree-scanner.js';
|
|
8
9
|
import { sendInitScan } from '../../utils/api-client.js';
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Prompt user to confirm re-indexing an already indexed project
|
|
13
|
+
*/
|
|
14
|
+
async function promptForReindex() {
|
|
15
|
+
const rl = createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
});
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
rl.question('Do you want to re-index the project? (y/N): ', (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function initCommand(options = {}) {
|
|
11
27
|
const projectRoot = process.cwd();
|
|
12
28
|
console.log('╔═══════════════════════════════════════════════╗');
|
|
13
29
|
console.log('║ Initializing Shift Project ║');
|
|
@@ -51,6 +67,32 @@ export async function initCommand() {
|
|
|
51
67
|
process.exit(1);
|
|
52
68
|
}
|
|
53
69
|
console.log(`[Init] ✓ Project: ${projectConfig.project_name} (${projectConfig.project_id})\n`);
|
|
70
|
+
// Check if project is already indexed (skip check if --force flag is used)
|
|
71
|
+
if (!options.force) {
|
|
72
|
+
try {
|
|
73
|
+
const projectStatus = await fetchProjectStatus(apiKey, projectConfig.project_id);
|
|
74
|
+
if (projectStatus.indexed) {
|
|
75
|
+
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');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
console.log('\n[Init] Proceeding with re-indexing...\n');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// If we can't check status, continue with init (server might be unavailable)
|
|
90
|
+
console.log('[Init] ⚠️ Could not check indexing status, proceeding with initialization...\n');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log('[Init] Force flag detected, skipping indexing status check...\n');
|
|
95
|
+
}
|
|
54
96
|
// Step 3: Check daemon status (warn if not running)
|
|
55
97
|
console.log('[Init] Step 3/5: Checking daemon status...');
|
|
56
98
|
const status = getDaemonStatus(projectRoot);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getApiKey, readProjectConfig, isGuestKey } from '../../utils/config.js';
|
|
2
2
|
import { getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
3
|
+
import { fetchProjectStatus } from '../../utils/api-client.js';
|
|
3
4
|
export async function statusCommand() {
|
|
4
5
|
const projectRoot = process.cwd();
|
|
5
6
|
console.log('\n╔════════════════════════════════════════════╗');
|
|
@@ -34,6 +35,20 @@ export async function statusCommand() {
|
|
|
34
35
|
console.log(` - ${agent.agent_name} (${agent.agent_type})`);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
38
|
+
// Check project indexing status (work done)
|
|
39
|
+
try {
|
|
40
|
+
const projectStatus = await fetchProjectStatus(apiKey, projectConfig.project_id);
|
|
41
|
+
if (projectStatus.indexed) {
|
|
42
|
+
console.log(`Indexed: ✓ Complete (${projectStatus.file_count} files)`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log('Indexed: ❌ Not indexed');
|
|
46
|
+
console.log(' Run "shift-cli init" to scan the project.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.log('Indexed: ⚠️ Unable to check (server unavailable)');
|
|
51
|
+
}
|
|
37
52
|
// Check daemon status
|
|
38
53
|
const status = getDaemonStatus(projectRoot);
|
|
39
54
|
if (!status.running) {
|
package/build/index.js
CHANGED
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
const program = new Command();
|
|
4
4
|
program
|
|
5
|
-
.name('shift')
|
|
5
|
+
.name('shift-cli')
|
|
6
6
|
.description('Shift CLI - AI-powered code intelligence')
|
|
7
|
-
.version('1.0.
|
|
7
|
+
.version('1.0.6');
|
|
8
8
|
// MCP server mode (default when run via MCP host)
|
|
9
9
|
program
|
|
10
10
|
.command('mcp', { isDefault: true, hidden: true })
|
|
11
11
|
.description('Start MCP server on stdio')
|
|
12
12
|
.action(async () => {
|
|
13
|
+
// Check if 'mcp' was explicitly passed as argument
|
|
14
|
+
const mcpExplicitlyRequested = process.argv.includes('mcp');
|
|
15
|
+
// If running interactively (TTY) and mcp wasn't explicitly requested, show help
|
|
16
|
+
if (process.stdin.isTTY && !mcpExplicitlyRequested) {
|
|
17
|
+
program.outputHelp();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
13
20
|
const { startMcpServer } = await import('./mcp-server.js');
|
|
14
21
|
await startMcpServer();
|
|
15
22
|
});
|
|
@@ -24,9 +31,10 @@ program
|
|
|
24
31
|
program
|
|
25
32
|
.command('init')
|
|
26
33
|
.description('Initialize and scan the project for file indexing')
|
|
27
|
-
.
|
|
34
|
+
.option('-f, --force', 'Force re-indexing even if project is already indexed')
|
|
35
|
+
.action(async (options) => {
|
|
28
36
|
const { initCommand } = await import('./cli/commands/init.js');
|
|
29
|
-
await initCommand();
|
|
37
|
+
await initCommand({ force: options.force ?? false });
|
|
30
38
|
});
|
|
31
39
|
program
|
|
32
40
|
.command('stop')
|
package/build/mcp-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
const BASE_URL = process.env.SHIFT_BACKEND_URL || "
|
|
4
|
+
const BASE_URL = process.env.SHIFT_BACKEND_URL || "https://dev-shift-lite.latentforce.ai";
|
|
5
5
|
function getProjectIdFromEnv() {
|
|
6
6
|
const projectId = process.env.SHIFT_PROJECT_ID;
|
|
7
7
|
if (!projectId || projectId.trim() === "") {
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { API_BASE_URL, API_BASE_URL_ORCH } from './config.js';
|
|
2
|
+
import { getMachineFingerprint } from './machine-id.js';
|
|
2
3
|
/**
|
|
3
|
-
* Request a guest API key
|
|
4
|
+
* Request a guest API key bound to this machine
|
|
5
|
+
* The backend will:
|
|
6
|
+
* - Create a new guest key if this machine_id hasn't been seen before
|
|
7
|
+
* - Return the existing guest key if this machine_id already has one
|
|
8
|
+
* - Optionally enforce usage limits per machine
|
|
4
9
|
*/
|
|
5
10
|
export async function requestGuestKey() {
|
|
11
|
+
const fingerprint = getMachineFingerprint();
|
|
6
12
|
const response = await fetch(`${API_BASE_URL}/api/guest-key`, {
|
|
7
13
|
method: 'POST',
|
|
8
14
|
headers: {
|
|
9
15
|
'Content-Type': 'application/json',
|
|
10
16
|
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
machine_id: fingerprint.machine_id,
|
|
19
|
+
platform: fingerprint.platform,
|
|
20
|
+
}),
|
|
11
21
|
});
|
|
12
22
|
if (!response.ok) {
|
|
13
23
|
const text = await response.text();
|
|
@@ -64,3 +74,30 @@ export async function sendInitScan(apiKey, projectId, payload) {
|
|
|
64
74
|
throw error;
|
|
65
75
|
}
|
|
66
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch project indexing/work status
|
|
79
|
+
* Returns whether the knowledge graph has been built for this project
|
|
80
|
+
*/
|
|
81
|
+
export async function fetchProjectStatus(apiKey, projectId) {
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(`${API_BASE_URL}/api/projects/${projectId}/status`, {
|
|
86
|
+
method: 'GET',
|
|
87
|
+
headers: {
|
|
88
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
89
|
+
},
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
});
|
|
92
|
+
clearTimeout(timeoutId);
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const text = await response.text();
|
|
95
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
96
|
+
}
|
|
97
|
+
return await response.json();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Get the primary MAC address (first non-internal interface)
|
|
5
|
+
*/
|
|
6
|
+
function getPrimaryMacAddress() {
|
|
7
|
+
const interfaces = os.networkInterfaces();
|
|
8
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
9
|
+
if (!addrs)
|
|
10
|
+
continue;
|
|
11
|
+
for (const addr of addrs) {
|
|
12
|
+
// Skip internal/loopback and addresses without MAC
|
|
13
|
+
if (addr.internal || !addr.mac || addr.mac === '00:00:00:00:00:00') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
return addr.mac;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return 'unknown';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate a unique, deterministic machine fingerprint
|
|
23
|
+
* Uses MAC address + platform for stability
|
|
24
|
+
* - MAC is globally unique per network interface
|
|
25
|
+
* - Platform helps distinguish dual-boot scenarios
|
|
26
|
+
*/
|
|
27
|
+
export function generateMachineId() {
|
|
28
|
+
const mac = getPrimaryMacAddress();
|
|
29
|
+
const platform = os.platform();
|
|
30
|
+
// Combine MAC + platform
|
|
31
|
+
const fingerprintData = `${mac}|${platform}`;
|
|
32
|
+
// Hash for privacy and fixed length
|
|
33
|
+
return crypto.createHash('sha256').update(fingerprintData).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate a shorter machine ID (first 16 chars of full hash)
|
|
37
|
+
*/
|
|
38
|
+
export function generateShortMachineId() {
|
|
39
|
+
return generateMachineId().substring(0, 16);
|
|
40
|
+
}
|
|
41
|
+
export function getMachineFingerprint() {
|
|
42
|
+
return {
|
|
43
|
+
machine_id: generateMachineId(),
|
|
44
|
+
platform: os.platform(),
|
|
45
|
+
};
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latentforce/shift",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Shift CLI - AI-powered code intelligence with MCP support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".": "./build/index.js"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"shift": "./build/index.js"
|
|
11
|
+
"shift-cli": "./build/index.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"build"
|