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.
@@ -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
- const result = {};
1297
- for (let i = 0; i < argv.length; i += 1) {
1298
- const item = argv[i];
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 process from "node:process";
2
- import { loadHostConfig, getHostConfigPath } from "./lib/host-config.js";
3
- import { probeRunnerAvailability } from "./lib/local-runner.js";
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 ?? "http://127.0.0.1:8787";
11
- const codex = probeRunnerAvailability("codex", process.env.CODEX_CMD);
12
- const claude = probeRunnerAvailability("claude", process.env.CLAUDE_CMD);
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 ? "ok" : `${response.status} ${await response.text()}`))
15
- .catch(() => "unreachable");
16
- console.log("office host doctor");
17
- console.log(`Config: ${config ? "present" : "missing"}`);
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})` : "missing"}`);
21
- console.log(`Claude: ${claude.available ? `ok (${claude.command})` : "missing"}`);
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 "node:child_process";
2
- import process from "node:process";
3
- import { resolveRunnerCommand } from "./lib/local-runner.js";
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 = resolveRunnerCommand(provider);
11
- const args = provider === "codex" ? ["login"] : ["auth"];
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: "inherit",
14
+ stdio: 'inherit',
15
15
  windowsHide: false,
16
16
  });
17
- child.on("close", (code) => {
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("error", reject);
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 === "codex" || value === "claude") {
30
+ if (value === 'codex' || value === 'claude') {
29
31
  return value;
30
32
  }
31
- throw new Error("Usage: office-core login <codex|claude>");
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", "double-penetration-host.mjs"), "start"], {
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 { spawn } from "node:child_process";
2
- import process from "node:process";
3
- import { loadHostConfig } from "./lib/host-config.js";
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("No host config found. Run office-core install first.");
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
- spawn("cmd.exe", ["/c", "start", "", url], {
15
- detached: true,
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 "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 { getHostConfigPath, getStartupLauncherPath, upsertHostConfig, } from "./lib/host-config.js";
7
- const args = parseArgs(process.argv.slice(2));
8
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
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 baseUrl = normalizeBaseUrlArg(args.baseUrl);
15
- const projectId = normalizeStringArg(args.project, "--project", "prj_local");
16
- const requestedHostId = args.hostId;
17
- const displayName = normalizeStringArg(args.displayName, "--displayName", `${os.hostname()} host`);
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 = args.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
- "office-host-enroll-secret";
24
- const registration = args.token
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: args.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: normalizeOptionalPathArg(args.workdir, "--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: args.pollMs ? Number(args.pollMs) : undefined,
45
- auto_start: args.autoStart ? args.autoStart !== "false" : undefined,
67
+ poll_ms: pollMs,
68
+ auto_start: autoStart,
46
69
  });
47
- if (config.auto_start) {
48
- await installStartupLauncher(root);
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 ? "enabled" : "disabled"}`);
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 registerHost(baseUrl, projectId, body, enrollSecret) {
58
- let response;
83
+ async function promptForMissingValues(args) {
84
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
59
85
  try {
60
- response = await fetch(`${baseUrl}/api/projects/${projectId}/local-host/register`, {
61
- method: "POST",
62
- headers: {
63
- "content-type": "application/json",
64
- "x-enroll-secret": enrollSecret,
65
- },
66
- body: JSON.stringify(body),
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
- catch (error) {
70
- const message = error instanceof Error ? error.message : String(error);
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 installStartupLauncher(rootDir) {
120
+ async function installDesktopLaunchers(rootDir, autoStart) {
121
+ const desktopLauncherPath = getDesktopLauncherPath();
79
122
  const startupPath = getStartupLauncherPath();
80
- await mkdir(path.dirname(startupPath), { recursive: true });
81
- const launcher = [
82
- "@echo off",
83
- "setlocal",
84
- `cd /d "${escapeBatch(rootDir)}"`,
85
- 'if not exist "node_modules" (',
86
- " call npm install",
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
  }