@latentforce/shift 1.0.2 → 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 +73 -10
- package/build/cli/commands/config.js +136 -0
- package/build/cli/commands/init.js +26 -6
- package/build/cli/commands/start.js +25 -6
- package/build/cli/commands/status.js +7 -2
- package/build/index.js +7 -0
- package/build/utils/api-client.js +16 -0
- package/build/utils/config.js +81 -4
- package/build/utils/prompts.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,17 @@ npm install -g @latentforce/shift
|
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
-
### Step 1:
|
|
16
|
+
### Step 1: Configure backend URLs (if using hosted service)
|
|
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://api.latentforce.ai
|
|
22
|
+
shift config set orch-url https://orch.latentforce.ai
|
|
23
|
+
shift config set ws-url wss://ws.latentforce.ai
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Step 2: Start and configure your project
|
|
17
27
|
|
|
18
28
|
```bash
|
|
19
29
|
cd /path/to/your/project
|
|
@@ -21,11 +31,11 @@ shift start
|
|
|
21
31
|
```
|
|
22
32
|
|
|
23
33
|
This will:
|
|
24
|
-
- Prompt for your Shift API key
|
|
34
|
+
- Prompt for your Shift API key or let you continue as guest
|
|
25
35
|
- Let you select or enter your project ID
|
|
26
36
|
- Start a background daemon that connects to the backend
|
|
27
37
|
|
|
28
|
-
### Step
|
|
38
|
+
### Step 3: Index your project
|
|
29
39
|
|
|
30
40
|
```bash
|
|
31
41
|
shift init
|
|
@@ -33,7 +43,7 @@ shift init
|
|
|
33
43
|
|
|
34
44
|
This scans your project files and sends the structure to the backend for indexing.
|
|
35
45
|
|
|
36
|
-
### Step
|
|
46
|
+
### Step 4: Configure MCP for Claude
|
|
37
47
|
|
|
38
48
|
Now Claude can use the MCP tools. See [Claude Desktop setup](#claude-desktop-setup) or [Claude Code setup](#claude-code-setup) below.
|
|
39
49
|
|
|
@@ -45,8 +55,18 @@ Now Claude can use the MCP tools. See [Claude Desktop setup](#claude-desktop-set
|
|
|
45
55
|
| `shift init` | Scan and index project files to backend |
|
|
46
56
|
| `shift stop` | Stop the running daemon |
|
|
47
57
|
| `shift status` | Show current status (API key, project, daemon, connection) |
|
|
58
|
+
| `shift config` | Show/set configuration (URLs, API key) |
|
|
48
59
|
| `shift` or `shift mcp` | Start MCP server on stdio (used by Claude) |
|
|
49
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
|
+
|
|
50
70
|
## MCP Tools
|
|
51
71
|
|
|
52
72
|
The MCP server exposes these tools to Claude:
|
|
@@ -57,15 +77,58 @@ The MCP server exposes these tools to Claude:
|
|
|
57
77
|
| `dependencies` | Get all dependencies for a file (direct and transitive) |
|
|
58
78
|
| `file_summary` | Generate a summary of a file with optional parent context |
|
|
59
79
|
|
|
60
|
-
##
|
|
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`:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
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)
|
|
101
|
+
```
|
|
102
|
+
|
|
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):
|
|
61
108
|
|
|
62
109
|
| Variable | Description | Default |
|
|
63
110
|
|----------|-------------|---------|
|
|
64
|
-
| `SHIFT_PROJECT_ID` | Default project UUID (
|
|
65
|
-
| `SHIFT_BACKEND_URL` | Backend API URL | `http://127.0.0.1:9000` |
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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://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
|
|
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)
|
|
69
132
|
|
|
70
133
|
## Claude Code Setup
|
|
71
134
|
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
-
import { getApiKey, setApiKey, readProjectConfig, writeProjectConfig } from '../../utils/config.js';
|
|
4
|
-
import { promptApiKey } from '../../utils/prompts.js';
|
|
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';
|
|
5
6
|
import { getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
6
7
|
import { getProjectTree, extractAllFilePaths, categorizeFiles } from '../../utils/tree-scanner.js';
|
|
7
8
|
import { sendInitScan } from '../../utils/api-client.js';
|
|
@@ -11,13 +12,32 @@ export async function initCommand() {
|
|
|
11
12
|
console.log('╔═══════════════════════════════════════════════╗');
|
|
12
13
|
console.log('║ Initializing Shift Project ║');
|
|
13
14
|
console.log('╚═══════════════════════════════════════════════╝\n');
|
|
14
|
-
// Step 1: Check API key
|
|
15
|
+
// Step 1: Check API key (with guest flow support)
|
|
15
16
|
console.log('[Init] Step 1/5: Checking API key...');
|
|
16
17
|
let apiKey = getApiKey();
|
|
17
18
|
if (!apiKey) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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');
|
|
21
41
|
}
|
|
22
42
|
else {
|
|
23
43
|
console.log('[Init] ✓ API key found\n');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getApiKey, setApiKey, setProject, readProjectConfig } from '../../utils/config.js';
|
|
2
|
-
import { promptApiKey, promptProjectId, promptSelectProject } from '../../utils/prompts.js';
|
|
1
|
+
import { getApiKey, setApiKey, setGuestKey, isGuestKey, setProject, readProjectConfig } from '../../utils/config.js';
|
|
2
|
+
import { promptApiKey, promptProjectId, promptSelectProject, promptKeyChoice } from '../../utils/prompts.js';
|
|
3
3
|
import { startDaemon, getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
4
|
-
import { fetchProjects } from '../../utils/api-client.js';
|
|
4
|
+
import { fetchProjects, requestGuestKey } from '../../utils/api-client.js';
|
|
5
5
|
export async function startCommand() {
|
|
6
6
|
const projectRoot = process.cwd();
|
|
7
7
|
console.log('╔════════════════════════════════════════════╗');
|
|
@@ -11,9 +11,28 @@ export async function startCommand() {
|
|
|
11
11
|
console.log('[Start] Step 1/4: Checking API key...');
|
|
12
12
|
let apiKey = getApiKey();
|
|
13
13
|
if (!apiKey) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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');
|
|
17
36
|
}
|
|
18
37
|
else {
|
|
19
38
|
console.log('[Start] ✓ API key found\n');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getApiKey, readProjectConfig } from '../../utils/config.js';
|
|
1
|
+
import { getApiKey, readProjectConfig, isGuestKey } from '../../utils/config.js';
|
|
2
2
|
import { getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
3
3
|
export async function statusCommand() {
|
|
4
4
|
const projectRoot = process.cwd();
|
|
@@ -12,7 +12,12 @@ export async function statusCommand() {
|
|
|
12
12
|
console.log('\nRun "shift start" to configure your API key.\n');
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
if (isGuestKey()) {
|
|
16
|
+
console.log('API Key: ✓ Guest key');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.log('API Key: ✓ Configured');
|
|
20
|
+
}
|
|
16
21
|
// Check project config
|
|
17
22
|
const projectConfig = readProjectConfig(projectRoot);
|
|
18
23
|
if (!projectConfig) {
|
package/build/index.js
CHANGED
|
@@ -42,4 +42,11 @@ program
|
|
|
42
42
|
const { statusCommand } = await import('./cli/commands/status.js');
|
|
43
43
|
await statusCommand();
|
|
44
44
|
});
|
|
45
|
+
program
|
|
46
|
+
.command('config [action] [key] [value]')
|
|
47
|
+
.description('Manage Shift configuration (URLs, API key)')
|
|
48
|
+
.action(async (action, key, value) => {
|
|
49
|
+
const { configCommand } = await import('./cli/commands/config.js');
|
|
50
|
+
await configCommand(action, key, value);
|
|
51
|
+
});
|
|
45
52
|
program.parse();
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { API_BASE_URL, API_BASE_URL_ORCH } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Request a guest API key
|
|
4
|
+
*/
|
|
5
|
+
export async function requestGuestKey() {
|
|
6
|
+
const response = await fetch(`${API_BASE_URL}/api/guest-key`, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
const text = await response.text();
|
|
14
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
15
|
+
}
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
2
18
|
/**
|
|
3
19
|
* Fetch available projects for the user
|
|
4
20
|
* Matching extension's fetchProjects function in api-client.js
|
package/build/utils/config.js
CHANGED
|
@@ -9,10 +9,39 @@ const LOCAL_SHIFT_DIR = '.shift';
|
|
|
9
9
|
const LOCAL_CONFIG_FILE = 'config.json'; // Changed from project.json to match extension
|
|
10
10
|
const DAEMON_PID_FILE = 'daemon.pid';
|
|
11
11
|
const DAEMON_STATUS_FILE = 'daemon.status.json';
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// Default URLs
|
|
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
|
+
// Helper to get URL from: 1) env var, 2) global config, 3) default
|
|
17
|
+
function getConfiguredUrl(envVar, configKey, defaultUrl) {
|
|
18
|
+
// Priority: Environment variable > Global config > Default
|
|
19
|
+
if (process.env[envVar]) {
|
|
20
|
+
return process.env[envVar];
|
|
21
|
+
}
|
|
22
|
+
const config = readGlobalConfigInternal();
|
|
23
|
+
if (config?.[configKey]) {
|
|
24
|
+
return config[configKey];
|
|
25
|
+
}
|
|
26
|
+
return defaultUrl;
|
|
27
|
+
}
|
|
28
|
+
// Internal read to avoid circular dependency
|
|
29
|
+
function readGlobalConfigInternal() {
|
|
30
|
+
try {
|
|
31
|
+
if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
32
|
+
const content = fs.readFileSync(GLOBAL_CONFIG_FILE, 'utf-8');
|
|
33
|
+
return JSON.parse(content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Invalid config, treat as not existing
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// API URLs - Priority: env var > global config > default
|
|
42
|
+
export const API_BASE_URL = getConfiguredUrl('SHIFT_API_URL', 'api_url', DEFAULT_API_URL);
|
|
43
|
+
export const API_BASE_URL_ORCH = getConfiguredUrl('SHIFT_ORCH_URL', 'orch_url', DEFAULT_ORCH_URL);
|
|
44
|
+
export const WS_URL = getConfiguredUrl('SHIFT_WS_URL', 'ws_url', DEFAULT_WS_URL);
|
|
16
45
|
// --- Global Config Functions ---
|
|
17
46
|
export function ensureGlobalShiftDir() {
|
|
18
47
|
if (!fs.existsSync(GLOBAL_SHIFT_DIR)) {
|
|
@@ -42,8 +71,56 @@ export function getApiKey() {
|
|
|
42
71
|
export function setApiKey(apiKey) {
|
|
43
72
|
const config = readGlobalConfig() || { api_key: '' };
|
|
44
73
|
config.api_key = apiKey;
|
|
74
|
+
delete config.key_type;
|
|
45
75
|
writeGlobalConfig(config);
|
|
46
76
|
}
|
|
77
|
+
export function setGuestKey(apiKey) {
|
|
78
|
+
const config = {
|
|
79
|
+
api_key: apiKey,
|
|
80
|
+
key_type: 'guest',
|
|
81
|
+
};
|
|
82
|
+
writeGlobalConfig(config);
|
|
83
|
+
}
|
|
84
|
+
export function isGuestKey() {
|
|
85
|
+
const config = readGlobalConfig();
|
|
86
|
+
return config?.key_type === 'guest';
|
|
87
|
+
}
|
|
88
|
+
export function clearApiKey() {
|
|
89
|
+
const config = readGlobalConfig() || { api_key: '' };
|
|
90
|
+
config.api_key = '';
|
|
91
|
+
delete config.key_type;
|
|
92
|
+
writeGlobalConfig(config);
|
|
93
|
+
}
|
|
94
|
+
// --- URL Configuration Functions ---
|
|
95
|
+
export function setApiUrl(url) {
|
|
96
|
+
const config = readGlobalConfig() || { api_key: '' };
|
|
97
|
+
config.api_url = url;
|
|
98
|
+
writeGlobalConfig(config);
|
|
99
|
+
}
|
|
100
|
+
export function setOrchUrl(url) {
|
|
101
|
+
const config = readGlobalConfig() || { api_key: '' };
|
|
102
|
+
config.orch_url = url;
|
|
103
|
+
writeGlobalConfig(config);
|
|
104
|
+
}
|
|
105
|
+
export function setWsUrl(url) {
|
|
106
|
+
const config = readGlobalConfig() || { api_key: '' };
|
|
107
|
+
config.ws_url = url;
|
|
108
|
+
writeGlobalConfig(config);
|
|
109
|
+
}
|
|
110
|
+
export function clearUrls() {
|
|
111
|
+
const config = readGlobalConfig() || { api_key: '' };
|
|
112
|
+
delete config.api_url;
|
|
113
|
+
delete config.orch_url;
|
|
114
|
+
delete config.ws_url;
|
|
115
|
+
writeGlobalConfig(config);
|
|
116
|
+
}
|
|
117
|
+
export function getConfiguredUrls() {
|
|
118
|
+
return {
|
|
119
|
+
api_url: API_BASE_URL,
|
|
120
|
+
orch_url: API_BASE_URL_ORCH,
|
|
121
|
+
ws_url: WS_URL,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
47
124
|
// --- Local Config Functions ---
|
|
48
125
|
export function getLocalShiftDir(projectRoot) {
|
|
49
126
|
const root = projectRoot || process.cwd();
|
package/build/utils/prompts.js
CHANGED
|
@@ -48,3 +48,22 @@ export async function promptSelectProject(projects) {
|
|
|
48
48
|
}
|
|
49
49
|
return projects[selection - 1];
|
|
50
50
|
}
|
|
51
|
+
export async function promptKeyChoice() {
|
|
52
|
+
console.log('\nNo Shift API key found.');
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log(' 1. I have an API key');
|
|
55
|
+
console.log(' 2. Continue as guest');
|
|
56
|
+
console.log('');
|
|
57
|
+
const answer = await prompt('Select an option (1 or 2): ');
|
|
58
|
+
const selection = parseInt(answer, 10);
|
|
59
|
+
if (selection === 1) {
|
|
60
|
+
return 'paid';
|
|
61
|
+
}
|
|
62
|
+
else if (selection === 2) {
|
|
63
|
+
return 'guest';
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error('Invalid selection.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|