office-core 0.1.3 → 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 -17
- 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 +86 -63
- package/.runtime-dist/scripts/lib/cli-support.js +199 -0
- package/.runtime-dist/scripts/lib/host-config.js +93 -42
- package/.runtime-dist/scripts/lib/local-runner.js +16 -3
- package/.runtime-dist/scripts/office-cli.js +35 -4
- 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 = {
|
|
@@ -193,11 +194,13 @@ async function processRoomMessages(runtime) {
|
|
|
193
194
|
const targetSessions = runnableSessions.filter((session) => shouldSessionReceiveMessage(runtime, session, message, settings));
|
|
194
195
|
if (targetSessions.length === 0) {
|
|
195
196
|
console.log(`[room] seq=${message.seq} no sessions matched (runnable=${runnableSessions.length}), skipping`);
|
|
197
|
+
void appendHostLog(`[room] seq=${message.seq} no matching sessions`);
|
|
196
198
|
runtime.roomCursorSeq = Math.max(runtime.roomCursorSeq, message.seq);
|
|
197
199
|
await persistRoomCursor(runtime);
|
|
198
200
|
continue;
|
|
199
201
|
}
|
|
200
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(", ")}`);
|
|
201
204
|
for (const session of targetSessions) {
|
|
202
205
|
await refreshSessionSummary(runtime, session, project).catch(() => undefined);
|
|
203
206
|
await enqueueRoomPrompt(runtime, session, message, messages, settings);
|
|
@@ -240,6 +243,7 @@ async function handleCommand(runtime, command) {
|
|
|
240
243
|
});
|
|
241
244
|
return;
|
|
242
245
|
}
|
|
246
|
+
void appendHostLog(`[command] ${command.command_type} ${command.agent_id}`);
|
|
243
247
|
const context = await buildSpawnContext(runtime, command);
|
|
244
248
|
const session = await spawnInteractiveSession(runtime, command, context);
|
|
245
249
|
sessions.set(session.session_id, session);
|
|
@@ -473,6 +477,7 @@ async function sendSpawnedAgentStatus(runtime, command, bootstrap, task, summary
|
|
|
473
477
|
await postJson(runtime, "/api/agents/events", { commands: [envelope] }, bootstrap.session_token);
|
|
474
478
|
}
|
|
475
479
|
export async function spawnInteractiveSession(runtime, command, context) {
|
|
480
|
+
void appendHostLog(`[spawn] preparing ${command.agent_id} runner=${command.runner ?? "unknown"}`);
|
|
476
481
|
if (!command.runner) {
|
|
477
482
|
throw new Error("Missing runner for spawn command");
|
|
478
483
|
}
|
|
@@ -1292,22 +1297,9 @@ export async function postRoomMessageUpsert(runtime, body) {
|
|
|
1292
1297
|
}
|
|
1293
1298
|
}
|
|
1294
1299
|
function parseArgs(argv) {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
if (!item.startsWith("--")) {
|
|
1299
|
-
continue;
|
|
1300
|
-
}
|
|
1301
|
-
const key = item.slice(2);
|
|
1302
|
-
const value = argv[i + 1];
|
|
1303
|
-
if (!value || value.startsWith("--")) {
|
|
1304
|
-
result[key] = "true";
|
|
1305
|
-
continue;
|
|
1306
|
-
}
|
|
1307
|
-
result[key] = value;
|
|
1308
|
-
i += 1;
|
|
1309
|
-
}
|
|
1310
|
-
return result;
|
|
1300
|
+
return parseCliArgs(argv, {
|
|
1301
|
+
value: ["baseUrl", "project", "hostId", "displayName", "workdir", "token", "roomCursorSeq", "pollMs"],
|
|
1302
|
+
});
|
|
1311
1303
|
}
|
|
1312
1304
|
export function sanitizeId(value) {
|
|
1313
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,65 +1,114 @@
|
|
|
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
|
-
console.error(error);
|
|
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
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function promptForMissingValues(args) {
|
|
84
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
+
try {
|
|
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;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
rl.close();
|
|
104
|
+
}
|
|
56
105
|
}
|
|
57
106
|
async function registerHost(baseUrl, projectId, body, enrollSecret) {
|
|
58
107
|
const response = await fetch(`${baseUrl}/api/projects/${projectId}/local-host/register`, {
|
|
59
|
-
method:
|
|
108
|
+
method: 'POST',
|
|
60
109
|
headers: {
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
'content-type': 'application/json',
|
|
111
|
+
'x-enroll-secret': enrollSecret,
|
|
63
112
|
},
|
|
64
113
|
body: JSON.stringify(body),
|
|
65
114
|
});
|
|
@@ -68,41 +117,15 @@ async function registerHost(baseUrl, projectId, body, enrollSecret) {
|
|
|
68
117
|
}
|
|
69
118
|
return response.json();
|
|
70
119
|
}
|
|
71
|
-
async function
|
|
120
|
+
async function installDesktopLaunchers(rootDir, autoStart) {
|
|
121
|
+
const desktopLauncherPath = getDesktopLauncherPath();
|
|
72
122
|
const startupPath = getStartupLauncherPath();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
" if errorlevel 1 exit /b 1",
|
|
81
|
-
")",
|
|
82
|
-
`start "office core" cmd /c "cd /d ""${escapeBatch(rootDir)}"" && npm run host-configured"`,
|
|
83
|
-
"endlocal",
|
|
84
|
-
"",
|
|
85
|
-
].join("\r\n");
|
|
86
|
-
await writeFile(startupPath, launcher, "utf8");
|
|
87
|
-
}
|
|
88
|
-
function parseArgs(argv) {
|
|
89
|
-
const result = {};
|
|
90
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
91
|
-
const item = argv[i];
|
|
92
|
-
if (!item.startsWith("--")) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const key = item.slice(2);
|
|
96
|
-
const value = argv[i + 1];
|
|
97
|
-
if (!value || value.startsWith("--")) {
|
|
98
|
-
result[key] = "true";
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
result[key] = value;
|
|
102
|
-
i += 1;
|
|
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');
|
|
103
130
|
}
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
function escapeBatch(value) {
|
|
107
|
-
return value.replace(/"/g, '""');
|
|
108
131
|
}
|