bortexcode 1.2.9 → 1.3.0
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 +15 -0
- package/bin/bortex.js +317 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ bortexcode
|
|
|
48
48
|
bortexcode "explain this function"
|
|
49
49
|
bortexcode --agent "refactor src/utils.js"
|
|
50
50
|
bortexcode remote-control
|
|
51
|
+
bortexcode remote-control --cloud
|
|
51
52
|
bortexcode --remote-control
|
|
52
53
|
```
|
|
53
54
|
|
|
@@ -67,6 +68,7 @@ Common commands:
|
|
|
67
68
|
/llm-config sync
|
|
68
69
|
/remote-control [name]
|
|
69
70
|
/remote-control --lan
|
|
71
|
+
/remote-control --cloud
|
|
70
72
|
/rc
|
|
71
73
|
/exit
|
|
72
74
|
```
|
|
@@ -87,11 +89,19 @@ For phone access on the same LAN:
|
|
|
87
89
|
bortexcode remote-control --remote-lan
|
|
88
90
|
```
|
|
89
91
|
|
|
92
|
+
For remote access without opening an inbound port, use the bortex.site relay:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
bortexcode remote-control --cloud --name "My Project"
|
|
96
|
+
bortexcode --remote-cloud "My Project"
|
|
97
|
+
```
|
|
98
|
+
|
|
90
99
|
Inside the REPL:
|
|
91
100
|
|
|
92
101
|
```text
|
|
93
102
|
/remote-control My Project
|
|
94
103
|
/remote-control --lan
|
|
104
|
+
/remote-control --cloud
|
|
95
105
|
/remote-control stop
|
|
96
106
|
```
|
|
97
107
|
|
|
@@ -115,6 +125,10 @@ Inside the REPL:
|
|
|
115
125
|
Disable startup update check
|
|
116
126
|
--remote-control, --rc [name]
|
|
117
127
|
Enable browser remote control
|
|
128
|
+
--remote-cloud, --cloud
|
|
129
|
+
Use bortex.site relay instead of opening a local port
|
|
130
|
+
--remote-relay <url>
|
|
131
|
+
Remote relay base URL
|
|
118
132
|
--remote-lan Bind remote control to 0.0.0.0 for LAN/mobile access
|
|
119
133
|
--remote-host <host>, --remote-port <port>
|
|
120
134
|
Remote control bind address
|
|
@@ -141,4 +155,5 @@ BORTEX_CLI_ICONS=1
|
|
|
141
155
|
BORTEX_NO_UPDATE_CHECK=1
|
|
142
156
|
BORTEX_REMOTE_HOST
|
|
143
157
|
BORTEX_REMOTE_PORT
|
|
158
|
+
BORTEX_REMOTE_RELAY_URL
|
|
144
159
|
```
|
package/bin/bortex.js
CHANGED
|
@@ -33,6 +33,8 @@ function parseArgs(argv) {
|
|
|
33
33
|
_apiKeyExplicit: false,
|
|
34
34
|
remoteControl: false,
|
|
35
35
|
remoteControlServerMode: false,
|
|
36
|
+
remoteControlCloud: false,
|
|
37
|
+
remoteControlRelayUrl: process.env.BORTEX_REMOTE_RELAY_URL || '',
|
|
36
38
|
remoteControlName: '',
|
|
37
39
|
remoteControlHost: process.env.BORTEX_REMOTE_HOST || '127.0.0.1',
|
|
38
40
|
remoteControlPort: Number(process.env.BORTEX_REMOTE_PORT || 0) || 0,
|
|
@@ -51,6 +53,10 @@ function parseArgs(argv) {
|
|
|
51
53
|
opts.remoteControlServerMode = true;
|
|
52
54
|
continue;
|
|
53
55
|
}
|
|
56
|
+
if ((a === 'cloud' || a === 'relay') && opts.remoteControlServerMode) {
|
|
57
|
+
opts.remoteControlCloud = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
54
60
|
if (a === '--agent' || a === '-a') {
|
|
55
61
|
opts.agent = true;
|
|
56
62
|
continue;
|
|
@@ -150,6 +156,28 @@ function parseArgs(argv) {
|
|
|
150
156
|
opts.remoteControlName = a.slice('--remote-control='.length);
|
|
151
157
|
continue;
|
|
152
158
|
}
|
|
159
|
+
if (a === '--remote-cloud' || a === '--cloud' || a === '--relay') {
|
|
160
|
+
opts.remoteControl = true;
|
|
161
|
+
opts.remoteControlCloud = true;
|
|
162
|
+
if (argv[i + 1] && !String(argv[i + 1]).startsWith('-')) {
|
|
163
|
+
opts.remoteControlName = argv[i + 1];
|
|
164
|
+
i += 1;
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if ((a === '--remote-relay' || a === '--relay-url') && argv[i + 1]) {
|
|
169
|
+
opts.remoteControlRelayUrl = argv[i + 1];
|
|
170
|
+
i += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (a.startsWith('--remote-relay=')) {
|
|
174
|
+
opts.remoteControlRelayUrl = a.slice('--remote-relay='.length);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (a.startsWith('--relay-url=')) {
|
|
178
|
+
opts.remoteControlRelayUrl = a.slice('--relay-url='.length);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
153
181
|
if (a === '--remote-lan') {
|
|
154
182
|
opts.remoteControlHost = '0.0.0.0';
|
|
155
183
|
continue;
|
|
@@ -197,6 +225,7 @@ function usage() {
|
|
|
197
225
|
console.log('Usage:');
|
|
198
226
|
console.log(` ${cliName} [options] [prompt]`);
|
|
199
227
|
console.log(` ${cliName} remote-control [--name <title>] [--remote-lan]`);
|
|
228
|
+
console.log(` ${cliName} remote-control --cloud [--name <title>]`);
|
|
200
229
|
console.log(` ${cliName} --api-key <apikey>`);
|
|
201
230
|
console.log(` ${cliName} "write a python function"`);
|
|
202
231
|
console.log('');
|
|
@@ -218,6 +247,10 @@ function usage() {
|
|
|
218
247
|
console.log(' Disable startup update check');
|
|
219
248
|
console.log(' --remote-control, --rc [name]');
|
|
220
249
|
console.log(' Enable browser remote control for this session');
|
|
250
|
+
console.log(' --remote-cloud, --cloud');
|
|
251
|
+
console.log(' Use bortex.site relay instead of opening a local port');
|
|
252
|
+
console.log(' --remote-relay <url>');
|
|
253
|
+
console.log(' Remote relay base URL (default: current --url)');
|
|
221
254
|
console.log(' --remote-lan Bind remote control to 0.0.0.0 for LAN/mobile access');
|
|
222
255
|
console.log(' --remote-host <host>, --remote-port <port>');
|
|
223
256
|
console.log(' Remote control bind address');
|
|
@@ -230,6 +263,7 @@ function usage() {
|
|
|
230
263
|
console.log('Environment:');
|
|
231
264
|
console.log(' BORTEX_URL Server URL');
|
|
232
265
|
console.log(' BORTEX_API_KEY API key');
|
|
266
|
+
console.log(' BORTEX_REMOTE_RELAY_URL Remote Control cloud relay URL');
|
|
233
267
|
}
|
|
234
268
|
|
|
235
269
|
function formatMs(ms) {
|
|
@@ -3930,6 +3964,7 @@ function printLocalHelp() {
|
|
|
3930
3964
|
console.log(' /process-status check running processes');
|
|
3931
3965
|
console.log(' /port-status check listening ports');
|
|
3932
3966
|
console.log(' /remote-control [name] [--lan|--host <host>|--port <port>]');
|
|
3967
|
+
console.log(' /remote-control --cloud [name]');
|
|
3933
3968
|
console.log(' /remote-control stop');
|
|
3934
3969
|
console.log(' /diff [unstaged|staged|all]');
|
|
3935
3970
|
console.log(' /stage <file>|--all');
|
|
@@ -4382,7 +4417,7 @@ async function startRemoteControlServer(opts, overrides = {}) {
|
|
|
4382
4417
|
const token = crypto.randomBytes(24).toString('hex');
|
|
4383
4418
|
const state = {
|
|
4384
4419
|
id: crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
4385
|
-
name:
|
|
4420
|
+
name: getRemoteControlDisplayName(opts, overrides),
|
|
4386
4421
|
host,
|
|
4387
4422
|
port,
|
|
4388
4423
|
token,
|
|
@@ -4496,11 +4531,231 @@ async function stopRemoteControlServer(opts) {
|
|
|
4496
4531
|
return true;
|
|
4497
4532
|
}
|
|
4498
4533
|
|
|
4534
|
+
function getRemoteControlDisplayName(opts, overrides = {}) {
|
|
4535
|
+
return String(overrides.name || opts.remoteControlName || path.basename(opts.cwd || process.cwd()) || os.hostname() || 'Bortex Code').trim();
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
function getRemoteControlStatus(opts) {
|
|
4539
|
+
if (opts.remoteControlCloudSession?.active) return 'cloud';
|
|
4540
|
+
if (opts.remoteControlSession?.server) return 'local';
|
|
4541
|
+
return 'off';
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
function getRemoteControlRelayBase(opts, overrides = {}) {
|
|
4545
|
+
return String(overrides.relayUrl || opts.remoteControlRelayUrl || opts.url || 'https://bortex.site').replace(/\/+$/, '');
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
function remoteControlSleep(ms) {
|
|
4549
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
function withRemoteControlQuery(rawUrl, baseUrl, params = {}) {
|
|
4553
|
+
const urlObj = new URL(rawUrl, baseUrl);
|
|
4554
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
4555
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
4556
|
+
urlObj.searchParams.set(key, String(value));
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
return urlObj.toString();
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
async function fetchRemoteControlJson(url, options = {}, timeoutMs = 30000) {
|
|
4563
|
+
const controller = new AbortController();
|
|
4564
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
4565
|
+
if (typeof timeoutId.unref === 'function') timeoutId.unref();
|
|
4566
|
+
try {
|
|
4567
|
+
const res = await fetch(url, {
|
|
4568
|
+
...options,
|
|
4569
|
+
headers: {
|
|
4570
|
+
Accept: 'application/json',
|
|
4571
|
+
...(options.headers || {})
|
|
4572
|
+
},
|
|
4573
|
+
signal: controller.signal
|
|
4574
|
+
});
|
|
4575
|
+
const text = await res.text();
|
|
4576
|
+
let data = null;
|
|
4577
|
+
if (text) {
|
|
4578
|
+
try { data = JSON.parse(text); } catch (_err) { data = null; }
|
|
4579
|
+
}
|
|
4580
|
+
if (!res.ok) {
|
|
4581
|
+
const detail = data?.error || data?.message || text || res.statusText || 'request failed';
|
|
4582
|
+
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
4583
|
+
}
|
|
4584
|
+
if (data?.ok === false) {
|
|
4585
|
+
throw new Error(data.error || data.message || 'request failed');
|
|
4586
|
+
}
|
|
4587
|
+
return data || {};
|
|
4588
|
+
} finally {
|
|
4589
|
+
clearTimeout(timeoutId);
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
async function postCloudRemoteHeartbeat(state) {
|
|
4594
|
+
const heartbeatUrl = withRemoteControlQuery(
|
|
4595
|
+
state.heartbeatUrl,
|
|
4596
|
+
state.baseUrl,
|
|
4597
|
+
{ agentToken: state.agentToken }
|
|
4598
|
+
);
|
|
4599
|
+
await fetchRemoteControlJson(heartbeatUrl, {
|
|
4600
|
+
method: 'POST',
|
|
4601
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4602
|
+
body: JSON.stringify({ cwd: state.cwd, version: CLI_VERSION })
|
|
4603
|
+
}, 12000);
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
async function postCloudRemoteResult(state, commandId, result) {
|
|
4607
|
+
const resultUrl = withRemoteControlQuery(
|
|
4608
|
+
state.resultUrl,
|
|
4609
|
+
state.baseUrl,
|
|
4610
|
+
{ agentToken: state.agentToken }
|
|
4611
|
+
);
|
|
4612
|
+
await fetchRemoteControlJson(resultUrl, {
|
|
4613
|
+
method: 'POST',
|
|
4614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4615
|
+
body: JSON.stringify({
|
|
4616
|
+
commandId,
|
|
4617
|
+
ok: !!result.ok,
|
|
4618
|
+
output: String(result.output || '')
|
|
4619
|
+
})
|
|
4620
|
+
}, 35000);
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
async function pollCloudRemoteControlLoop(opts, state) {
|
|
4624
|
+
let lastHeartbeatAt = 0;
|
|
4625
|
+
while (state.active) {
|
|
4626
|
+
let delayMs = 1200;
|
|
4627
|
+
try {
|
|
4628
|
+
const pollUrl = withRemoteControlQuery(
|
|
4629
|
+
state.pollUrl,
|
|
4630
|
+
state.baseUrl,
|
|
4631
|
+
{ agentToken: state.agentToken, after: state.lastCommandId || 0 }
|
|
4632
|
+
);
|
|
4633
|
+
const data = await fetchRemoteControlJson(pollUrl, { method: 'GET' }, 35000);
|
|
4634
|
+
const commands = Array.isArray(data.commands) ? data.commands : [];
|
|
4635
|
+
state.lastSeenAt = Date.now();
|
|
4636
|
+
state.errorCount = 0;
|
|
4637
|
+
|
|
4638
|
+
for (const command of commands) {
|
|
4639
|
+
if (!state.active) break;
|
|
4640
|
+
const commandId = Number(command?.id || 0);
|
|
4641
|
+
if (commandId > (state.lastCommandId || 0)) state.lastCommandId = commandId;
|
|
4642
|
+
const text = String(command?.text || command?.prompt || '').trim();
|
|
4643
|
+
if (!text) continue;
|
|
4644
|
+
state.busy = true;
|
|
4645
|
+
let result;
|
|
4646
|
+
try {
|
|
4647
|
+
result = await runRemoteControlPrompt(opts, text);
|
|
4648
|
+
} catch (err) {
|
|
4649
|
+
result = { ok: false, output: err.stack || err.message || String(err) };
|
|
4650
|
+
} finally {
|
|
4651
|
+
state.busy = false;
|
|
4652
|
+
}
|
|
4653
|
+
await postCloudRemoteResult(state, commandId, result);
|
|
4654
|
+
}
|
|
4655
|
+
|
|
4656
|
+
if (!commands.length && Date.now() - lastHeartbeatAt > 10000) {
|
|
4657
|
+
lastHeartbeatAt = Date.now();
|
|
4658
|
+
await postCloudRemoteHeartbeat(state);
|
|
4659
|
+
}
|
|
4660
|
+
if (commands.length) delayMs = 250;
|
|
4661
|
+
} catch (err) {
|
|
4662
|
+
if (state.active) {
|
|
4663
|
+
state.errorCount = (state.errorCount || 0) + 1;
|
|
4664
|
+
const now = Date.now();
|
|
4665
|
+
if (!state.lastErrorAt || now - state.lastErrorAt > 15000) {
|
|
4666
|
+
console.error(`Cloud Remote Control error: ${err.message || String(err)}`);
|
|
4667
|
+
state.lastErrorAt = now;
|
|
4668
|
+
}
|
|
4669
|
+
delayMs = Math.min(5000, 1000 + state.errorCount * 500);
|
|
4670
|
+
}
|
|
4671
|
+
} finally {
|
|
4672
|
+
state.busy = false;
|
|
4673
|
+
}
|
|
4674
|
+
await remoteControlSleep(delayMs);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
async function startCloudRemoteControlSession(opts, overrides = {}) {
|
|
4679
|
+
if (opts.remoteControlCloudSession?.active) {
|
|
4680
|
+
console.log(`Cloud Remote Control already active: ${opts.remoteControlCloudSession.url}`);
|
|
4681
|
+
return opts.remoteControlCloudSession;
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4684
|
+
const baseUrl = getRemoteControlRelayBase(opts, overrides);
|
|
4685
|
+
const name = getRemoteControlDisplayName(opts, overrides);
|
|
4686
|
+
const payload = {
|
|
4687
|
+
name,
|
|
4688
|
+
cwd: opts.cwd,
|
|
4689
|
+
version: CLI_VERSION,
|
|
4690
|
+
machine: os.hostname(),
|
|
4691
|
+
platform: process.platform,
|
|
4692
|
+
node: process.version,
|
|
4693
|
+
mode: opts.agent ? 'agent' : 'chat'
|
|
4694
|
+
};
|
|
4695
|
+
const data = await fetchRemoteControlJson(`${baseUrl}/api/bortex-code/remote/register`, {
|
|
4696
|
+
method: 'POST',
|
|
4697
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4698
|
+
body: JSON.stringify(payload)
|
|
4699
|
+
}, 30000);
|
|
4700
|
+
|
|
4701
|
+
const sessionId = String(data.sessionId || '').trim();
|
|
4702
|
+
const agentToken = String(data.agentToken || '').trim();
|
|
4703
|
+
if (!sessionId || !agentToken) {
|
|
4704
|
+
throw new Error('Remote relay registration did not return a session token.');
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
const state = {
|
|
4708
|
+
active: true,
|
|
4709
|
+
cloud: true,
|
|
4710
|
+
baseUrl,
|
|
4711
|
+
sessionId,
|
|
4712
|
+
agentToken,
|
|
4713
|
+
clientToken: data.clientToken || '',
|
|
4714
|
+
name,
|
|
4715
|
+
cwd: opts.cwd,
|
|
4716
|
+
url: data.url || `${baseUrl}/bortex-code/remote?session=${encodeURIComponent(sessionId)}`,
|
|
4717
|
+
pollUrl: data.pollUrl || `${baseUrl}/api/bortex-code/remote/session/${encodeURIComponent(sessionId)}/poll`,
|
|
4718
|
+
resultUrl: data.resultUrl || `${baseUrl}/api/bortex-code/remote/session/${encodeURIComponent(sessionId)}/result`,
|
|
4719
|
+
heartbeatUrl: data.heartbeatUrl || `${baseUrl}/api/bortex-code/remote/session/${encodeURIComponent(sessionId)}/heartbeat`,
|
|
4720
|
+
lastCommandId: 0,
|
|
4721
|
+
startedAt: Date.now(),
|
|
4722
|
+
busy: false,
|
|
4723
|
+
errorCount: 0
|
|
4724
|
+
};
|
|
4725
|
+
state.stop = async () => {
|
|
4726
|
+
state.active = false;
|
|
4727
|
+
};
|
|
4728
|
+
opts.remoteControlCloudSession = state;
|
|
4729
|
+
state.pollPromise = pollCloudRemoteControlLoop(opts, state).catch((err) => {
|
|
4730
|
+
if (state.active) console.error(`Cloud Remote Control stopped unexpectedly: ${err.message || String(err)}`);
|
|
4731
|
+
});
|
|
4732
|
+
|
|
4733
|
+
console.log(`Cloud Remote Control active: ${state.name}`);
|
|
4734
|
+
console.log(`URL: ${state.url}`);
|
|
4735
|
+
console.log('No inbound port required. Keep this process running.');
|
|
4736
|
+
console.log('Security: keep this tokenized URL private. Anyone with the URL can control this Bortex Code session.');
|
|
4737
|
+
return state;
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
async function stopCloudRemoteControlSession(opts) {
|
|
4741
|
+
const state = opts.remoteControlCloudSession;
|
|
4742
|
+
if (!state?.active) {
|
|
4743
|
+
console.log('Cloud Remote Control is not active.');
|
|
4744
|
+
return false;
|
|
4745
|
+
}
|
|
4746
|
+
await state.stop();
|
|
4747
|
+
opts.remoteControlCloudSession = null;
|
|
4748
|
+
console.log('Cloud Remote Control stopped.');
|
|
4749
|
+
return true;
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4499
4752
|
function parseRemoteControlSlashArgs(rest = [], opts = {}) {
|
|
4500
4753
|
const out = {
|
|
4501
4754
|
name: '',
|
|
4502
4755
|
host: opts.remoteControlHost || '127.0.0.1',
|
|
4503
|
-
port: Number(opts.remoteControlPort || 0) || 0
|
|
4756
|
+
port: Number(opts.remoteControlPort || 0) || 0,
|
|
4757
|
+
relayUrl: opts.remoteControlRelayUrl || '',
|
|
4758
|
+
cloud: false
|
|
4504
4759
|
};
|
|
4505
4760
|
const nameParts = [];
|
|
4506
4761
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -4513,6 +4768,23 @@ function parseRemoteControlSlashArgs(rest = [], opts = {}) {
|
|
|
4513
4768
|
out.host = '0.0.0.0';
|
|
4514
4769
|
continue;
|
|
4515
4770
|
}
|
|
4771
|
+
if (a === 'cloud' || a === '--cloud' || a === '--remote-cloud' || a === '--relay') {
|
|
4772
|
+
out.cloud = true;
|
|
4773
|
+
continue;
|
|
4774
|
+
}
|
|
4775
|
+
if ((a === '--relay-url' || a === '--remote-relay') && rest[i + 1]) {
|
|
4776
|
+
out.relayUrl = String(rest[i + 1]);
|
|
4777
|
+
i += 1;
|
|
4778
|
+
continue;
|
|
4779
|
+
}
|
|
4780
|
+
if (a.startsWith('--relay-url=')) {
|
|
4781
|
+
out.relayUrl = a.slice('--relay-url='.length);
|
|
4782
|
+
continue;
|
|
4783
|
+
}
|
|
4784
|
+
if (a.startsWith('--remote-relay=')) {
|
|
4785
|
+
out.relayUrl = a.slice('--remote-relay='.length);
|
|
4786
|
+
continue;
|
|
4787
|
+
}
|
|
4516
4788
|
if ((a === '--host' || a === '--remote-host') && rest[i + 1]) {
|
|
4517
4789
|
out.host = String(rest[i + 1]);
|
|
4518
4790
|
i += 1;
|
|
@@ -5835,13 +6107,18 @@ async function handleLocalCommand(opts, line) {
|
|
|
5835
6107
|
const done = todoItems.filter((t) => t.done).length;
|
|
5836
6108
|
const runPending = Array.isArray(opts.runState?.steps) ? opts.runState.steps.filter((s) => s.status === 'pending').length : 0;
|
|
5837
6109
|
console.log(formatModeLine(opts));
|
|
5838
|
-
console.log(`Todo: ${done}/${todoItems.length} | Plan: ${opts.plan?.goal ? 'active' : 'none'} | Run: ${opts.runState?.goal ? `active (${runPending} pending)` : 'none'} | Remote: ${opts
|
|
6110
|
+
console.log(`Todo: ${done}/${todoItems.length} | Plan: ${opts.plan?.goal ? 'active' : 'none'} | Run: ${opts.runState?.goal ? `active (${runPending} pending)` : 'none'} | Remote: ${getRemoteControlStatus(opts)}`);
|
|
5839
6111
|
return { handled: true };
|
|
5840
6112
|
}
|
|
5841
6113
|
if (lc === '/remote-control' || lc === '/remote' || lc === '/rc') {
|
|
5842
6114
|
const parsed = parseRemoteControlSlashArgs(rest, opts);
|
|
5843
|
-
if (parsed.stop || opts.remoteControlSession?.server) {
|
|
5844
|
-
await stopRemoteControlServer(opts);
|
|
6115
|
+
if (parsed.stop || opts.remoteControlSession?.server || opts.remoteControlCloudSession?.active) {
|
|
6116
|
+
if (opts.remoteControlSession?.server) await stopRemoteControlServer(opts);
|
|
6117
|
+
if (opts.remoteControlCloudSession?.active) await stopCloudRemoteControlSession(opts);
|
|
6118
|
+
return { handled: true };
|
|
6119
|
+
}
|
|
6120
|
+
if (parsed.cloud) {
|
|
6121
|
+
await startCloudRemoteControlSession(opts, parsed);
|
|
5845
6122
|
return { handled: true };
|
|
5846
6123
|
}
|
|
5847
6124
|
await startRemoteControlServer(opts, parsed);
|
|
@@ -7010,6 +7287,7 @@ const SLASH_COMMANDS = [
|
|
|
7010
7287
|
['/help', 'Show command help'],
|
|
7011
7288
|
['/remote-control [name]', 'Control this local session from a browser'],
|
|
7012
7289
|
['/remote-control --lan', 'Expose Remote Control on the local network'],
|
|
7290
|
+
['/remote-control --cloud', 'Control this session through bortex.site relay'],
|
|
7013
7291
|
['/rc', 'Toggle Remote Control'],
|
|
7014
7292
|
['/llm-config show', 'Show cached LLM configuration'],
|
|
7015
7293
|
['/llm-config sync', 'Sync LLM configuration from Bortex'],
|
|
@@ -7186,11 +7464,18 @@ async function runRepl(opts) {
|
|
|
7186
7464
|
};
|
|
7187
7465
|
opts._askInput = async (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
7188
7466
|
if (opts.remoteControl) {
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7467
|
+
if (opts.remoteControlCloud) {
|
|
7468
|
+
await startCloudRemoteControlSession(opts, {
|
|
7469
|
+
name: opts.remoteControlName,
|
|
7470
|
+
relayUrl: opts.remoteControlRelayUrl
|
|
7471
|
+
});
|
|
7472
|
+
} else {
|
|
7473
|
+
await startRemoteControlServer(opts, {
|
|
7474
|
+
name: opts.remoteControlName,
|
|
7475
|
+
host: opts.remoteControlHost,
|
|
7476
|
+
port: opts.remoteControlPort
|
|
7477
|
+
});
|
|
7478
|
+
}
|
|
7194
7479
|
}
|
|
7195
7480
|
|
|
7196
7481
|
const question = () => new Promise((resolve) => {
|
|
@@ -7216,7 +7501,7 @@ async function runRepl(opts) {
|
|
|
7216
7501
|
const done = todoItems.filter((t) => t.done).length;
|
|
7217
7502
|
console.log(formatModeLine(opts));
|
|
7218
7503
|
const runPending = Array.isArray(opts.runState?.steps) ? opts.runState.steps.filter((s) => s.status === 'pending').length : 0;
|
|
7219
|
-
console.log(`Todo: ${done}/${todoItems.length} | Plan: ${opts.plan?.goal ? 'active' : 'none'} | Run: ${opts.runState?.goal ? `active (${runPending} pending)` : 'none'}`);
|
|
7504
|
+
console.log(`Todo: ${done}/${todoItems.length} | Plan: ${opts.plan?.goal ? 'active' : 'none'} | Run: ${opts.runState?.goal ? `active (${runPending} pending)` : 'none'} | Remote: ${getRemoteControlStatus(opts)}`);
|
|
7220
7505
|
continue;
|
|
7221
7506
|
}
|
|
7222
7507
|
if (line === '/help') {
|
|
@@ -7266,6 +7551,9 @@ async function runRepl(opts) {
|
|
|
7266
7551
|
if (opts.remoteControlSession?.server) {
|
|
7267
7552
|
await stopRemoteControlServer(opts);
|
|
7268
7553
|
}
|
|
7554
|
+
if (opts.remoteControlCloudSession?.active) {
|
|
7555
|
+
await stopCloudRemoteControlSession(opts);
|
|
7556
|
+
}
|
|
7269
7557
|
try { saveCliWorkspaceState(opts); } catch (_err) { }
|
|
7270
7558
|
delete opts._readMultiline;
|
|
7271
7559
|
delete opts._askInput;
|
|
@@ -7317,18 +7605,30 @@ async function main() {
|
|
|
7317
7605
|
}
|
|
7318
7606
|
|
|
7319
7607
|
if (opts.remoteControlServerMode) {
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7608
|
+
if (opts.remoteControlCloud) {
|
|
7609
|
+
await startCloudRemoteControlSession(opts, {
|
|
7610
|
+
name: opts.remoteControlName,
|
|
7611
|
+
relayUrl: opts.remoteControlRelayUrl
|
|
7612
|
+
});
|
|
7613
|
+
} else {
|
|
7614
|
+
await startRemoteControlServer(opts, {
|
|
7615
|
+
name: opts.remoteControlName,
|
|
7616
|
+
host: opts.remoteControlHost,
|
|
7617
|
+
port: opts.remoteControlPort
|
|
7618
|
+
});
|
|
7619
|
+
}
|
|
7325
7620
|
console.log('Server mode: keep this process running. Press Ctrl+C to stop Remote Control.');
|
|
7326
7621
|
await new Promise((resolve) => {
|
|
7327
7622
|
let stopping = false;
|
|
7328
7623
|
const stop = async () => {
|
|
7329
7624
|
if (stopping) return;
|
|
7330
7625
|
stopping = true;
|
|
7331
|
-
|
|
7626
|
+
if (opts.remoteControlSession?.server) {
|
|
7627
|
+
try { await stopRemoteControlServer(opts); } catch (_err) {}
|
|
7628
|
+
}
|
|
7629
|
+
if (opts.remoteControlCloudSession?.active) {
|
|
7630
|
+
try { await stopCloudRemoteControlSession(opts); } catch (_err) {}
|
|
7631
|
+
}
|
|
7332
7632
|
resolve();
|
|
7333
7633
|
};
|
|
7334
7634
|
process.once('SIGINT', stop);
|