office-core 0.1.4 → 0.2.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/.runtime-dist/scripts/build-windows-release.js +54 -0
- package/.runtime-dist/scripts/home-agent-host.js +9 -18
- package/.runtime-dist/scripts/host-config-command.js +33 -0
- package/.runtime-dist/scripts/host-doctor.js +18 -13
- package/.runtime-dist/scripts/host-login.js +13 -11
- package/.runtime-dist/scripts/host-logs.js +22 -0
- package/.runtime-dist/scripts/host-menu.js +1 -1
- package/.runtime-dist/scripts/host-open.js +7 -10
- package/.runtime-dist/scripts/host-support-bundle.js +43 -0
- package/.runtime-dist/scripts/install-host.js +88 -111
- package/.runtime-dist/scripts/lib/cli-support.js +199 -0
- package/.runtime-dist/scripts/lib/host-config.js +91 -57
- package/.runtime-dist/scripts/lib/local-runner.js +16 -3
- package/.runtime-dist/scripts/office-cli.js +154 -223
- package/README.md +57 -94
- package/bin/double-penetration-host.mjs +1 -82
- package/bin/office-core.mjs +95 -0
- package/package.json +11 -7
- package/public/index.html +373 -1
- package/public/install-host.ps1 +7 -5
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
void main().catch((error) => {
|
|
6
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
});
|
|
9
|
+
async function main() {
|
|
10
|
+
const rootDir = process.cwd();
|
|
11
|
+
const distDir = path.join(rootDir, 'dist', 'windows-portable');
|
|
12
|
+
await rm(distDir, { recursive: true, force: true });
|
|
13
|
+
await mkdir(distDir, { recursive: true });
|
|
14
|
+
const packResult = spawnSync(process.platform === 'win32' ? 'cmd.exe' : 'npm', process.platform === 'win32' ? ['/d', '/s', '/c', 'npm pack --json'] : ['pack', '--json'], {
|
|
15
|
+
cwd: rootDir,
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
});
|
|
19
|
+
if (packResult.status !== 0) {
|
|
20
|
+
throw new Error(`npm pack failed\n${packResult.stdout}\n${packResult.stderr}`.trim());
|
|
21
|
+
}
|
|
22
|
+
const pack = JSON.parse(String(packResult.stdout).trim())?.[0];
|
|
23
|
+
if (!pack?.filename) {
|
|
24
|
+
throw new Error('npm pack did not return a filename');
|
|
25
|
+
}
|
|
26
|
+
const tarballPath = path.join(rootDir, pack.filename);
|
|
27
|
+
const portableTarball = path.join(distDir, 'office-core.tgz');
|
|
28
|
+
await copyFile(tarballPath, portableTarball);
|
|
29
|
+
await rm(tarballPath, { force: true });
|
|
30
|
+
const launcherBat = [
|
|
31
|
+
'@echo off',
|
|
32
|
+
'setlocal',
|
|
33
|
+
'cd /d "%~dp0"',
|
|
34
|
+
'npm install -g .\\office-core.tgz',
|
|
35
|
+
'if errorlevel 1 pause & exit /b 1',
|
|
36
|
+
'office-core setup',
|
|
37
|
+
'endlocal',
|
|
38
|
+
'',
|
|
39
|
+
].join('\r\n');
|
|
40
|
+
const dashboardBat = [
|
|
41
|
+
'@echo off',
|
|
42
|
+
'setlocal',
|
|
43
|
+
'office-core open',
|
|
44
|
+
'if errorlevel 1 pause',
|
|
45
|
+
'endlocal',
|
|
46
|
+
'',
|
|
47
|
+
].join('\r\n');
|
|
48
|
+
await writeFile(path.join(distDir, 'Install Office Core.bat'), launcherBat, 'utf8');
|
|
49
|
+
await writeFile(path.join(distDir, 'Open Office Dashboard.bat'), dashboardBat, 'utf8');
|
|
50
|
+
await copyFile(path.join(rootDir, 'public', 'install-host.ps1'), path.join(distDir, 'install-host.ps1'));
|
|
51
|
+
const readme = await readFile(path.join(rootDir, 'RUN_AND_BUILD.md'), 'utf8').catch(() => 'See project root documentation.');
|
|
52
|
+
await writeFile(path.join(distDir, 'README.txt'), readme, 'utf8');
|
|
53
|
+
console.log(`Windows portable release prepared at ${distDir}`);
|
|
54
|
+
}
|
|
@@ -5,7 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { probeRunnerAvailability, resolveRunnerCommand, } from "./lib/local-runner.js";
|
|
8
|
-
import { loadHostConfig, loadPersistedHostSessions, upsertHostConfig, savePersistedHostSessions, } from "./lib/host-config.js";
|
|
8
|
+
import { appendHostLog, loadHostConfig, loadPersistedHostSessions, upsertHostConfig, savePersistedHostSessions, } from "./lib/host-config.js";
|
|
9
|
+
import { parseCliArgs } from "./lib/cli-support.js";
|
|
9
10
|
export const sessions = new Map();
|
|
10
11
|
let tickInFlight = false;
|
|
11
12
|
export const hooks = {
|
|
@@ -187,18 +188,19 @@ async function processRoomMessages(runtime) {
|
|
|
187
188
|
hooks.onRoomMessage?.(message, runtime);
|
|
188
189
|
if (message.author_type === "system") {
|
|
189
190
|
runtime.roomCursorSeq = Math.max(runtime.roomCursorSeq, message.seq);
|
|
190
|
-
await persistRoomCursor(runtime);
|
|
191
191
|
continue;
|
|
192
192
|
}
|
|
193
193
|
const runnableSessions = Array.from(sessions.values()).filter((session) => session.status === "running");
|
|
194
194
|
const targetSessions = runnableSessions.filter((session) => shouldSessionReceiveMessage(runtime, session, message, settings));
|
|
195
195
|
if (targetSessions.length === 0) {
|
|
196
196
|
console.log(`[room] seq=${message.seq} no sessions matched (runnable=${runnableSessions.length}), skipping`);
|
|
197
|
+
void appendHostLog(`[room] seq=${message.seq} no matching sessions`);
|
|
197
198
|
runtime.roomCursorSeq = Math.max(runtime.roomCursorSeq, message.seq);
|
|
198
199
|
await persistRoomCursor(runtime);
|
|
199
200
|
continue;
|
|
200
201
|
}
|
|
201
202
|
console.log(`[room] seq=${message.seq} queueing for ${targetSessions.map((s) => s.agent_id).join(", ")}`);
|
|
203
|
+
void appendHostLog(`[room] seq=${message.seq} queued for ${targetSessions.map((s) => s.agent_id).join(", ")}`);
|
|
202
204
|
for (const session of targetSessions) {
|
|
203
205
|
await refreshSessionSummary(runtime, session, project).catch(() => undefined);
|
|
204
206
|
await enqueueRoomPrompt(runtime, session, message, messages, settings);
|
|
@@ -241,6 +243,7 @@ async function handleCommand(runtime, command) {
|
|
|
241
243
|
});
|
|
242
244
|
return;
|
|
243
245
|
}
|
|
246
|
+
void appendHostLog(`[command] ${command.command_type} ${command.agent_id}`);
|
|
244
247
|
const context = await buildSpawnContext(runtime, command);
|
|
245
248
|
const session = await spawnInteractiveSession(runtime, command, context);
|
|
246
249
|
sessions.set(session.session_id, session);
|
|
@@ -474,6 +477,7 @@ async function sendSpawnedAgentStatus(runtime, command, bootstrap, task, summary
|
|
|
474
477
|
await postJson(runtime, "/api/agents/events", { commands: [envelope] }, bootstrap.session_token);
|
|
475
478
|
}
|
|
476
479
|
export async function spawnInteractiveSession(runtime, command, context) {
|
|
480
|
+
void appendHostLog(`[spawn] preparing ${command.agent_id} runner=${command.runner ?? "unknown"}`);
|
|
477
481
|
if (!command.runner) {
|
|
478
482
|
throw new Error("Missing runner for spawn command");
|
|
479
483
|
}
|
|
@@ -1293,22 +1297,9 @@ export async function postRoomMessageUpsert(runtime, body) {
|
|
|
1293
1297
|
}
|
|
1294
1298
|
}
|
|
1295
1299
|
function parseArgs(argv) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if (!item.startsWith("--")) {
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
const key = item.slice(2);
|
|
1303
|
-
const value = argv[i + 1];
|
|
1304
|
-
if (!value || value.startsWith("--")) {
|
|
1305
|
-
result[key] = "true";
|
|
1306
|
-
continue;
|
|
1307
|
-
}
|
|
1308
|
-
result[key] = value;
|
|
1309
|
-
i += 1;
|
|
1310
|
-
}
|
|
1311
|
-
return result;
|
|
1300
|
+
return parseCliArgs(argv, {
|
|
1301
|
+
value: ["baseUrl", "project", "hostId", "displayName", "workdir", "token", "roomCursorSeq", "pollMs"],
|
|
1302
|
+
});
|
|
1312
1303
|
}
|
|
1313
1304
|
export function sanitizeId(value) {
|
|
1314
1305
|
return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { getHostConfigDir, getHostConfigPath, getHostSessionsPath, loadHostConfig } from './lib/host-config.js';
|
|
4
|
+
void main().catch((error) => {
|
|
5
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
});
|
|
8
|
+
async function main() {
|
|
9
|
+
const action = (process.argv[2] ?? 'show').toLowerCase();
|
|
10
|
+
if (action === 'path') {
|
|
11
|
+
console.log(getHostConfigPath());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (action === 'reset') {
|
|
15
|
+
await rm(getHostConfigPath(), { force: true });
|
|
16
|
+
await rm(getHostSessionsPath(), { force: true });
|
|
17
|
+
console.log(`Cleared Office Core host config from ${getHostConfigDir()}`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (action !== 'show') {
|
|
21
|
+
throw new Error('Usage: office-core config [show|path|reset]');
|
|
22
|
+
}
|
|
23
|
+
const config = await loadHostConfig();
|
|
24
|
+
if (!config) {
|
|
25
|
+
console.log('No host config found. Run office-core setup first.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
console.log(JSON.stringify({
|
|
29
|
+
...config,
|
|
30
|
+
token: config.token ? `${config.token.slice(0, 18)}...` : '',
|
|
31
|
+
config_path: getHostConfigPath(),
|
|
32
|
+
}, null, 2));
|
|
33
|
+
}
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { getDesktopLauncherPath, getHostConfigPath, getHostLogPath, getStartupLauncherPath, loadHostConfig, } from './lib/host-config.js';
|
|
4
|
+
import { probeRunnerAvailability } from './lib/local-runner.js';
|
|
4
5
|
void main().catch((error) => {
|
|
5
|
-
console.error(error);
|
|
6
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
7
|
process.exitCode = 1;
|
|
7
8
|
});
|
|
8
9
|
async function main() {
|
|
9
10
|
const config = await loadHostConfig();
|
|
10
|
-
const baseUrl = config?.base_url ??
|
|
11
|
-
const codex = probeRunnerAvailability(
|
|
12
|
-
const claude = probeRunnerAvailability(
|
|
11
|
+
const baseUrl = config?.base_url ?? 'http://127.0.0.1:8787';
|
|
12
|
+
const codex = probeRunnerAvailability('codex', process.env.CODEX_CMD);
|
|
13
|
+
const claude = probeRunnerAvailability('claude', process.env.CLAUDE_CMD);
|
|
13
14
|
const health = await fetch(`${baseUrl}/healthz`)
|
|
14
|
-
.then(async (response) => (response.ok ?
|
|
15
|
-
.catch(() =>
|
|
16
|
-
console.log(
|
|
17
|
-
console.log(`Config: ${config ?
|
|
15
|
+
.then(async (response) => (response.ok ? 'ok' : `${response.status} ${await response.text()}`))
|
|
16
|
+
.catch(() => 'unreachable');
|
|
17
|
+
console.log('office core doctor');
|
|
18
|
+
console.log(`Config: ${config ? 'present' : 'missing'}`);
|
|
18
19
|
console.log(`Config path: ${getHostConfigPath()}`);
|
|
19
20
|
console.log(`Worker health: ${health}`);
|
|
20
|
-
console.log(`Codex: ${codex.available ? `ok (${codex.command})` :
|
|
21
|
-
console.log(`Claude: ${claude.available ? `ok (${claude.command})` :
|
|
21
|
+
console.log(`Codex: ${codex.available ? `ok (${codex.command})` : 'missing'}`);
|
|
22
|
+
console.log(`Claude: ${claude.available ? `ok (${claude.command})` : 'missing'}`);
|
|
23
|
+
console.log(`Desktop launcher: ${existsSync(getDesktopLauncherPath()) ? 'present' : 'missing'} (${getDesktopLauncherPath()})`);
|
|
24
|
+
console.log(`Startup launcher: ${existsSync(getStartupLauncherPath()) ? 'present' : 'missing'} (${getStartupLauncherPath()})`);
|
|
25
|
+
console.log(`Host log: ${existsSync(getHostLogPath()) ? 'present' : 'missing'} (${getHostLogPath()})`);
|
|
22
26
|
if (config) {
|
|
23
27
|
console.log(`Host id: ${config.host_id}`);
|
|
24
28
|
console.log(`Project: ${config.project_id}`);
|
|
25
29
|
console.log(`Workdir: ${config.workdir}`);
|
|
26
30
|
console.log(`Base URL: ${config.base_url}`);
|
|
31
|
+
console.log(`Auto-start: ${config.auto_start}`);
|
|
27
32
|
}
|
|
28
33
|
}
|
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import process from
|
|
3
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { assertRunnerAvailable } from './lib/local-runner.js';
|
|
4
4
|
void main().catch((error) => {
|
|
5
|
-
console.error(error);
|
|
5
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
6
|
process.exitCode = 1;
|
|
7
7
|
});
|
|
8
8
|
async function main() {
|
|
9
9
|
const provider = normalizeProvider(process.argv[2]);
|
|
10
|
-
const command =
|
|
11
|
-
const args = provider ===
|
|
10
|
+
const command = assertRunnerAvailable(provider);
|
|
11
|
+
const args = provider === 'codex' ? ['login'] : ['auth'];
|
|
12
12
|
await new Promise((resolve, reject) => {
|
|
13
13
|
const child = spawn(command, args, {
|
|
14
|
-
stdio:
|
|
14
|
+
stdio: 'inherit',
|
|
15
15
|
windowsHide: false,
|
|
16
16
|
});
|
|
17
|
-
child.on(
|
|
17
|
+
child.on('close', (code) => {
|
|
18
18
|
if ((code ?? 0) !== 0) {
|
|
19
19
|
reject(new Error(`${provider} login exited with code ${code}`));
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
resolve();
|
|
23
23
|
});
|
|
24
|
-
child.on(
|
|
24
|
+
child.on('error', (error) => {
|
|
25
|
+
reject(new Error(`Unable to launch ${provider}: ${error instanceof Error ? error.message : String(error)}`));
|
|
26
|
+
});
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
function normalizeProvider(value) {
|
|
28
|
-
if (value ===
|
|
30
|
+
if (value === 'codex' || value === 'claude') {
|
|
29
31
|
return value;
|
|
30
32
|
}
|
|
31
|
-
throw new Error(
|
|
33
|
+
throw new Error('Usage: office-core login <codex|claude>');
|
|
32
34
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { getHostLogPath, getHostLogsDir } from './lib/host-config.js';
|
|
3
|
+
import { parseCliArgs, readRecentLines } from './lib/cli-support.js';
|
|
4
|
+
void main().catch((error) => {
|
|
5
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
});
|
|
8
|
+
async function main() {
|
|
9
|
+
const args = parseCliArgs(process.argv.slice(2), { value: ['tail'] });
|
|
10
|
+
const tail = Number(args.tail ?? 120);
|
|
11
|
+
const lines = await readRecentLines(getHostLogPath(), Number.isFinite(tail) ? tail : 120);
|
|
12
|
+
console.log(`Log directory: ${getHostLogsDir()}`);
|
|
13
|
+
console.log(`Log file: ${getHostLogPath()}`);
|
|
14
|
+
console.log('');
|
|
15
|
+
if (!lines.length) {
|
|
16
|
+
console.log('No host logs yet. Start the host once and try again.');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
console.log(line);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -128,7 +128,7 @@ async function startConfiguredHost(config) {
|
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
131
|
-
spawn(process.execPath, [path.join(root, "bin", "
|
|
131
|
+
spawn(process.execPath, [path.join(root, "bin", "office-core.mjs"), "start"], {
|
|
132
132
|
cwd: root,
|
|
133
133
|
detached: true,
|
|
134
134
|
stdio: "ignore",
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { appendHostLog, loadHostConfig } from './lib/host-config.js';
|
|
3
|
+
import { openExternal } from './lib/cli-support.js';
|
|
4
4
|
void main().catch((error) => {
|
|
5
|
-
console.error(error);
|
|
5
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
6
|
process.exitCode = 1;
|
|
7
7
|
});
|
|
8
8
|
async function main() {
|
|
9
9
|
const config = await loadHostConfig();
|
|
10
10
|
if (!config) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error('No host config found. Run office-core setup first.');
|
|
12
12
|
}
|
|
13
13
|
const url = `${config.base_url}/?projectId=${encodeURIComponent(config.project_id)}`;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
stdio: "ignore",
|
|
17
|
-
windowsHide: true,
|
|
18
|
-
}).unref();
|
|
14
|
+
await openExternal(url);
|
|
15
|
+
await appendHostLog(`[ui] opened dashboard ${url}`);
|
|
19
16
|
console.log(`Opened ${url}`);
|
|
20
17
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { getHostConfigPath, getHostDiagnosticsDir, getHostLogPath, getHostSessionsPath, getSupportBundlesDir, loadHostConfig, } from './lib/host-config.js';
|
|
5
|
+
import { createSupportBundle } from './lib/cli-support.js';
|
|
6
|
+
import { probeRunnerAvailability } from './lib/local-runner.js';
|
|
7
|
+
void main().catch((error) => {
|
|
8
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
});
|
|
11
|
+
async function main() {
|
|
12
|
+
const config = await loadHostConfig();
|
|
13
|
+
const bundle = await createSupportBundle({
|
|
14
|
+
outputRoot: getSupportBundlesDir(),
|
|
15
|
+
bundleNamePrefix: 'office-core-support',
|
|
16
|
+
includePaths: [
|
|
17
|
+
{ source: getHostConfigPath(), target: 'config/host-config.json' },
|
|
18
|
+
{ source: getHostSessionsPath(), target: 'config/host-sessions.json' },
|
|
19
|
+
{ source: getHostLogPath(), target: 'logs/office-host.log' },
|
|
20
|
+
{ source: getHostDiagnosticsDir(), target: 'diagnostics' },
|
|
21
|
+
],
|
|
22
|
+
metadata: {
|
|
23
|
+
generated_at: new Date().toISOString(),
|
|
24
|
+
platform: `${process.platform}/${process.arch}`,
|
|
25
|
+
hostname: os.hostname(),
|
|
26
|
+
node_version: process.version,
|
|
27
|
+
cwd: process.cwd(),
|
|
28
|
+
host_config: config ? {
|
|
29
|
+
host_id: config.host_id,
|
|
30
|
+
project_id: config.project_id,
|
|
31
|
+
base_url: config.base_url,
|
|
32
|
+
workdir: config.workdir,
|
|
33
|
+
auto_start: config.auto_start,
|
|
34
|
+
} : null,
|
|
35
|
+
runners: {
|
|
36
|
+
codex: probeRunnerAvailability('codex', process.env.CODEX_CMD),
|
|
37
|
+
claude: probeRunnerAvailability('claude', process.env.CLAUDE_CMD),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
console.log(`Support bundle written: ${path.resolve(bundle.outputPath)}`);
|
|
42
|
+
console.log(`Bundle format: ${bundle.mode}`);
|
|
43
|
+
}
|
|
@@ -1,154 +1,131 @@
|
|
|
1
|
-
import { mkdir, writeFile } from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import process from
|
|
5
|
-
import { fileURLToPath } from
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { createInterface } from 'node:readline/promises';
|
|
7
|
+
import { appendHostLog, getDesktopLauncherPath, getHostConfigPath, getStartupLauncherPath, upsertHostConfig, } from './lib/host-config.js';
|
|
8
|
+
import { buildDashboardLauncher, buildDesktopLauncher, buildStartupLauncher, parseBooleanLike, parseCliArgs, parseNumberLike, } from './lib/cli-support.js';
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
10
|
void main().catch((error) => {
|
|
10
11
|
console.error(error instanceof Error ? error.message : String(error));
|
|
11
12
|
process.exitCode = 1;
|
|
12
13
|
});
|
|
13
14
|
async function main() {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const args = parseCliArgs(process.argv.slice(2), {
|
|
16
|
+
boolean: ['autoStart', 'wizard'],
|
|
17
|
+
value: ['baseUrl', 'project', 'hostId', 'displayName', 'workdir', 'enrollSecret', 'token', 'pollMs'],
|
|
18
|
+
aliases: {
|
|
19
|
+
baseUrl: 'baseUrl',
|
|
20
|
+
project: 'project',
|
|
21
|
+
hostId: 'hostId',
|
|
22
|
+
displayName: 'displayName',
|
|
23
|
+
workdir: 'workdir',
|
|
24
|
+
enrollSecret: 'enrollSecret',
|
|
25
|
+
token: 'token',
|
|
26
|
+
pollMs: 'pollMs',
|
|
27
|
+
autoStart: 'autoStart',
|
|
28
|
+
wizard: 'wizard',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && (!Object.keys(args).length || args.wizard === 'true'));
|
|
32
|
+
const wizard = interactive ? await promptForMissingValues(args) : args;
|
|
33
|
+
const baseUrl = String(wizard.baseUrl ?? 'http://127.0.0.1:8787').trim().replace(/\/$/, '');
|
|
34
|
+
const projectId = String(wizard.project ?? 'prj_local').trim();
|
|
35
|
+
const requestedHostId = wizard.hostId ? String(wizard.hostId).trim() : undefined;
|
|
36
|
+
const displayName = String(wizard.displayName ?? `${os.hostname()} host`).trim();
|
|
18
37
|
const machineName = os.hostname();
|
|
19
|
-
const enrollSecret =
|
|
38
|
+
const enrollSecret = wizard.enrollSecret ??
|
|
20
39
|
process.env.OFFICE_HOST_ENROLL_SECRET ??
|
|
21
40
|
process.env.DOUBLE_PENETRATION_ENROLL_SECRET ??
|
|
22
41
|
process.env.LOCAL_HOST_ENROLL_SECRET ??
|
|
23
|
-
|
|
24
|
-
const
|
|
42
|
+
'office-host-enroll-secret';
|
|
43
|
+
const workdir = path.resolve(String(wizard.workdir ?? process.cwd()));
|
|
44
|
+
const pollMs = parseNumberLike(wizard.pollMs, 5000, 'poll interval');
|
|
45
|
+
const autoStart = parseBooleanLike(wizard.autoStart, true);
|
|
46
|
+
await mkdir(workdir, { recursive: true });
|
|
47
|
+
const registration = wizard.token
|
|
25
48
|
? {
|
|
26
49
|
ok: true,
|
|
27
50
|
host_id: requestedHostId ?? `host_${machineName.toLowerCase()}`,
|
|
28
|
-
host_token:
|
|
51
|
+
host_token: String(wizard.token),
|
|
29
52
|
project_id: projectId,
|
|
30
53
|
}
|
|
31
54
|
: await registerHost(baseUrl, projectId, {
|
|
32
55
|
requested_host_id: requestedHostId,
|
|
33
56
|
display_name: displayName,
|
|
34
57
|
machine_name: machineName,
|
|
35
|
-
}, enrollSecret);
|
|
58
|
+
}, String(enrollSecret));
|
|
36
59
|
const config = await upsertHostConfig({
|
|
37
60
|
host_id: registration.host_id,
|
|
38
61
|
base_url: baseUrl,
|
|
39
62
|
project_id: registration.project_id,
|
|
40
|
-
workdir
|
|
63
|
+
workdir,
|
|
41
64
|
display_name: displayName,
|
|
42
65
|
token: registration.host_token,
|
|
43
66
|
room_cursor_seq: Number(registration.room_cursor_seq ?? 0),
|
|
44
|
-
poll_ms:
|
|
45
|
-
auto_start:
|
|
67
|
+
poll_ms: pollMs,
|
|
68
|
+
auto_start: autoStart,
|
|
46
69
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
70
|
+
await installDesktopLaunchers(root, autoStart);
|
|
71
|
+
await appendHostLog(`[install] configured host=${config.host_id} project=${config.project_id} base=${config.base_url} startup=${config.auto_start}`);
|
|
50
72
|
console.log(`Host config written: ${getHostConfigPath()}`);
|
|
51
73
|
console.log(`Host id: ${config.host_id}`);
|
|
52
74
|
console.log(`Project: ${config.project_id}`);
|
|
53
75
|
console.log(`Workdir: ${config.workdir}`);
|
|
54
76
|
console.log(`Base URL: ${config.base_url}`);
|
|
55
|
-
console.log(`Auto-start: ${config.auto_start ?
|
|
77
|
+
console.log(`Auto-start: ${config.auto_start ? 'enabled' : 'disabled'}`);
|
|
78
|
+
console.log(`Desktop launcher: ${getDesktopLauncherPath()}`);
|
|
79
|
+
if (config.auto_start) {
|
|
80
|
+
console.log(`Startup launcher: ${getStartupLauncherPath()}`);
|
|
81
|
+
}
|
|
56
82
|
}
|
|
57
|
-
async function
|
|
58
|
-
|
|
83
|
+
async function promptForMissingValues(args) {
|
|
84
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
59
85
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
const ask = async (label, fallback) => {
|
|
87
|
+
const answer = (await rl.question(`${label} [${fallback}]: `)).trim();
|
|
88
|
+
return answer || fallback;
|
|
89
|
+
};
|
|
90
|
+
const next = { ...args };
|
|
91
|
+
next.baseUrl = await ask('Worker base URL', args.baseUrl ?? 'http://127.0.0.1:8787');
|
|
92
|
+
next.project = await ask('Project id', args.project ?? 'prj_local');
|
|
93
|
+
next.workdir = await ask('Project workdir', args.workdir ?? process.cwd());
|
|
94
|
+
next.displayName = await ask('Display name', args.displayName ?? `${os.hostname()} host`);
|
|
95
|
+
if (!args.token) {
|
|
96
|
+
next.enrollSecret = await ask('Enrollment secret', args.enrollSecret ?? process.env.OFFICE_HOST_ENROLL_SECRET ?? 'office-host-enroll-secret');
|
|
97
|
+
}
|
|
98
|
+
next.pollMs = await ask('Poll interval (ms)', args.pollMs ?? '5000');
|
|
99
|
+
next.autoStart = await ask('Install startup launcher? (y/n)', args.autoStart ?? 'y');
|
|
100
|
+
return next;
|
|
68
101
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
throw new Error(`Host registration failed: could not reach ${baseUrl} (${message})`);
|
|
102
|
+
finally {
|
|
103
|
+
rl.close();
|
|
72
104
|
}
|
|
105
|
+
}
|
|
106
|
+
async function registerHost(baseUrl, projectId, body, enrollSecret) {
|
|
107
|
+
const response = await fetch(`${baseUrl}/api/projects/${projectId}/local-host/register`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'content-type': 'application/json',
|
|
111
|
+
'x-enroll-secret': enrollSecret,
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(body),
|
|
114
|
+
});
|
|
73
115
|
if (!response.ok) {
|
|
74
116
|
throw new Error(`Host registration failed: ${response.status} ${await response.text()}`);
|
|
75
117
|
}
|
|
76
118
|
return response.json();
|
|
77
119
|
}
|
|
78
|
-
async function
|
|
120
|
+
async function installDesktopLaunchers(rootDir, autoStart) {
|
|
121
|
+
const desktopLauncherPath = getDesktopLauncherPath();
|
|
79
122
|
const startupPath = getStartupLauncherPath();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
" if errorlevel 1 exit /b 1",
|
|
88
|
-
")",
|
|
89
|
-
`start "office core" cmd /c "cd /d ""${escapeBatch(rootDir)}"" && npm run host-configured"`,
|
|
90
|
-
"endlocal",
|
|
91
|
-
"",
|
|
92
|
-
].join("\r\n");
|
|
93
|
-
await writeFile(startupPath, launcher, "utf8");
|
|
94
|
-
}
|
|
95
|
-
function parseArgs(argv) {
|
|
96
|
-
const result = {};
|
|
97
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
98
|
-
const item = argv[i];
|
|
99
|
-
if (!item.startsWith("--")) {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
const key = item.slice(2);
|
|
103
|
-
const value = argv[i + 1];
|
|
104
|
-
if (!value || value.startsWith("--")) {
|
|
105
|
-
result[key] = "true";
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
result[key] = value;
|
|
109
|
-
i += 1;
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
function escapeBatch(value) {
|
|
114
|
-
return value.replace(/"/g, '""');
|
|
115
|
-
}
|
|
116
|
-
function normalizeBaseUrlArg(value) {
|
|
117
|
-
if (value === "true") {
|
|
118
|
-
throw new Error("Missing value for --baseUrl");
|
|
119
|
-
}
|
|
120
|
-
const raw = (value ?? "http://127.0.0.1:8787").trim().replace(/\/$/, "");
|
|
121
|
-
try {
|
|
122
|
-
const url = new URL(raw);
|
|
123
|
-
if (!/^https?:$/.test(url.protocol)) {
|
|
124
|
-
throw new Error("protocol");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
throw new Error(`Invalid --baseUrl: ${raw}`);
|
|
129
|
-
}
|
|
130
|
-
return raw;
|
|
131
|
-
}
|
|
132
|
-
function normalizeStringArg(value, flagName, fallback) {
|
|
133
|
-
if (value === "true") {
|
|
134
|
-
throw new Error(`Missing value for ${flagName}`);
|
|
135
|
-
}
|
|
136
|
-
const normalized = String(value ?? fallback).trim();
|
|
137
|
-
if (!normalized) {
|
|
138
|
-
throw new Error(`Missing value for ${flagName}`);
|
|
139
|
-
}
|
|
140
|
-
return normalized;
|
|
141
|
-
}
|
|
142
|
-
function normalizeOptionalPathArg(value, flagName) {
|
|
143
|
-
if (value === undefined) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
if (value === "true") {
|
|
147
|
-
throw new Error(`Missing value for ${flagName}`);
|
|
148
|
-
}
|
|
149
|
-
const normalized = value.trim();
|
|
150
|
-
if (!normalized) {
|
|
151
|
-
throw new Error(`Missing value for ${flagName}`);
|
|
123
|
+
const dashboardPath = path.join(path.dirname(desktopLauncherPath), 'Office Core Dashboard.bat');
|
|
124
|
+
await mkdir(path.dirname(desktopLauncherPath), { recursive: true });
|
|
125
|
+
await writeFile(desktopLauncherPath, buildDesktopLauncher(rootDir), 'utf8');
|
|
126
|
+
await writeFile(dashboardPath, buildDashboardLauncher(rootDir), 'utf8');
|
|
127
|
+
if (autoStart) {
|
|
128
|
+
await mkdir(path.dirname(startupPath), { recursive: true });
|
|
129
|
+
await writeFile(startupPath, buildStartupLauncher(rootDir), 'utf8');
|
|
152
130
|
}
|
|
153
|
-
return normalized;
|
|
154
131
|
}
|