@latentforce/shift 1.0.6 → 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/build/cli/commands/init.js +44 -2
- package/build/cli/commands/status.js +15 -0
- package/build/index.js +3 -2
- package/build/utils/api-client.js +38 -1
- package/build/utils/config.js +3 -3
- package/build/utils/machine-id.js +46 -0
- package/package.json +1 -1
|
@@ -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
|
@@ -31,9 +31,10 @@ program
|
|
|
31
31
|
program
|
|
32
32
|
.command('init')
|
|
33
33
|
.description('Initialize and scan the project for file indexing')
|
|
34
|
-
.
|
|
34
|
+
.option('-f, --force', 'Force re-indexing even if project is already indexed')
|
|
35
|
+
.action(async (options) => {
|
|
35
36
|
const { initCommand } = await import('./cli/commands/init.js');
|
|
36
|
-
await initCommand();
|
|
37
|
+
await initCommand({ force: options.force ?? false });
|
|
37
38
|
});
|
|
38
39
|
program
|
|
39
40
|
.command('stop')
|
|
@@ -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
|
+
}
|
package/build/utils/config.js
CHANGED
|
@@ -10,9 +10,9 @@ const LOCAL_CONFIG_FILE = 'config.json'; // Changed from project.json to match e
|
|
|
10
10
|
const DAEMON_PID_FILE = 'daemon.pid';
|
|
11
11
|
const DAEMON_STATUS_FILE = 'daemon.status.json';
|
|
12
12
|
// Default URLs
|
|
13
|
-
const DEFAULT_API_URL = '
|
|
14
|
-
const DEFAULT_ORCH_URL = '
|
|
15
|
-
const DEFAULT_WS_URL = '
|
|
13
|
+
const DEFAULT_API_URL = 'http://localhost:9000';
|
|
14
|
+
const DEFAULT_ORCH_URL = 'http://localhost:9999';
|
|
15
|
+
const DEFAULT_WS_URL = 'ws://localhost:9999';
|
|
16
16
|
// Helper to get URL from: 1) env var, 2) global config, 3) default
|
|
17
17
|
function getConfiguredUrl(envVar, configKey, defaultUrl) {
|
|
18
18
|
// Priority: Environment variable > Global config > Default
|
|
@@ -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
|
+
}
|