@feynmanzhang/open-party 0.1.2 → 0.1.3-beta.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/dist/cli/index.js CHANGED
@@ -201,7 +201,7 @@ function joinTailnet(authKey, timeout = 3e4) {
201
201
  const binary = getTailscaleBinary();
202
202
  try {
203
203
  const output = execWithSudoFallback(
204
- [binary, "up", "--authkey", authKey, "--accept-routes"],
204
+ [binary, "up", "--authkey", authKey],
205
205
  timeout
206
206
  );
207
207
  return { success: true, output: output.trim() };
@@ -3171,7 +3171,7 @@ var init_dist2 = __esm({
3171
3171
  });
3172
3172
  if (!chunk) {
3173
3173
  if (i === 1) {
3174
- await new Promise((resolve2) => setTimeout(resolve2));
3174
+ await new Promise((resolve3) => setTimeout(resolve3));
3175
3175
  maxReadCount = 3;
3176
3176
  continue;
3177
3177
  }
@@ -3410,8 +3410,8 @@ function classifyFetchError(error) {
3410
3410
  if (error instanceof DOMException && error.name === "AbortError") return null;
3411
3411
  return null;
3412
3412
  }
3413
- function sleep(ms) {
3414
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3413
+ function sleep2(ms) {
3414
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3415
3415
  }
3416
3416
  var UNKNOWN, PARTY_SERVER, DEGRADED, SUSPECT, DOWN, NOT_SERVER, MAYBE, MAYBE_MAX_RETRIES, BACKOFF_BASE, BACKOFF_CAP, FAILURE_SUSPECT, FAILURE_DOWN, PeerDiscovery;
3417
3417
  var init_peer_discovery = __esm({
@@ -3487,7 +3487,7 @@ var init_peer_discovery = __esm({
3487
3487
  } catch (e) {
3488
3488
  console.error("[Discovery] Cycle failed:", e);
3489
3489
  }
3490
- await sleep(DISCOVERY_INTERVAL * 1e3);
3490
+ await sleep2(DISCOVERY_INTERVAL * 1e3);
3491
3491
  }
3492
3492
  }
3493
3493
  async discoveryCycle() {
@@ -4940,9 +4940,20 @@ var init_dashboard = __esm({
4940
4940
 
4941
4941
  // src/server/index.ts
4942
4942
  var server_exports = {};
4943
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
4944
+ import { join as join5, dirname as dirname3 } from "path";
4945
+ import { homedir as homedir4 } from "os";
4943
4946
  async function periodicCleanup() {
4944
4947
  }
4948
+ function pidFilePath2() {
4949
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
4950
+ if (pluginData) return join5(pluginData, "server.pid");
4951
+ return join5(homedir4(), ".open-party", "server.pid");
4952
+ }
4945
4953
  async function main() {
4954
+ const pidPath = pidFilePath2();
4955
+ mkdirSync3(dirname3(pidPath), { recursive: true });
4956
+ writeFileSync3(pidPath, String(process.pid));
4946
4957
  console.log(`Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
4947
4958
  process.on("SIGHUP", () => {
4948
4959
  });
@@ -4951,6 +4962,10 @@ async function main() {
4951
4962
  const cleanupPromise = periodicCleanup();
4952
4963
  const shutdown = () => {
4953
4964
  console.log("\nShutting down Party Server...");
4965
+ try {
4966
+ unlinkSync2(pidPath);
4967
+ } catch {
4968
+ }
4954
4969
  server.close();
4955
4970
  process.exit(0);
4956
4971
  };
@@ -5011,7 +5026,7 @@ async function installTailscale(platform) {
5011
5026
  const args2 = entry.needsSudo ? [entry.cmd, ...entry.args] : entry.args;
5012
5027
  console.log(`Running: ${cmd} ${args2.join(" ")}
5013
5028
  `);
5014
- return new Promise((resolve2) => {
5029
+ return new Promise((resolve3) => {
5015
5030
  const child = spawn(cmd, args2, {
5016
5031
  stdio: "inherit",
5017
5032
  windowsHide: true
@@ -5021,15 +5036,15 @@ async function installTailscale(platform) {
5021
5036
  if (exited) return;
5022
5037
  exited = true;
5023
5038
  if (code === 0) {
5024
- resolve2({ success: true, output: "Installation completed." });
5039
+ resolve3({ success: true, output: "Installation completed." });
5025
5040
  } else {
5026
- resolve2({ success: false, output: `Installation exited with code ${code}` });
5041
+ resolve3({ success: false, output: `Installation exited with code ${code}` });
5027
5042
  }
5028
5043
  });
5029
5044
  child.on("error", (err) => {
5030
5045
  if (exited) return;
5031
5046
  exited = true;
5032
- resolve2({ success: false, output: err.message });
5047
+ resolve3({ success: false, output: err.message });
5033
5048
  });
5034
5049
  });
5035
5050
  }
@@ -5320,7 +5335,7 @@ async function installPluginToAgent(agentType) {
5320
5335
  // src/cli/setup.ts
5321
5336
  var rl = createInterface({ input: process.stdin, output: process.stdout });
5322
5337
  function prompt(question) {
5323
- return new Promise((resolve2) => rl.question(question, (answer) => resolve2(answer.trim())));
5338
+ return new Promise((resolve3) => rl.question(question, (answer) => resolve3(answer.trim())));
5324
5339
  }
5325
5340
  function cyan(text) {
5326
5341
  return `\x1B[36m${text}\x1B[0m`;
@@ -5423,10 +5438,10 @@ async function handleInteractiveLogin(binary) {
5423
5438
  console.log(`
5424
5439
  ${cyan("Running interactive login...")}`);
5425
5440
  console.log("A browser window should open. Authenticate in the browser, then return here.\n");
5426
- const { spawn: spawn2 } = await import("child_process");
5427
- const child = spawn2(binary, ["login"], { stdio: "inherit" });
5428
- const exitCode = await new Promise((resolve2) => {
5429
- child.on("close", resolve2);
5441
+ const { spawn: spawn3 } = await import("child_process");
5442
+ const child = spawn3(binary, ["login"], { stdio: "inherit" });
5443
+ const exitCode = await new Promise((resolve3) => {
5444
+ child.on("close", resolve3);
5430
5445
  });
5431
5446
  resetTailscaleBinaryCache();
5432
5447
  const status = getTailscaleInstallationStatus();
@@ -5442,7 +5457,7 @@ ${yellow("\u26A0\uFE0F Login may not have completed. Status: " + status.state)}
5442
5457
  }
5443
5458
  async function handleAuthKeyLogin(binary) {
5444
5459
  console.log("");
5445
- console.log("You can generate an Auth Key at:");
5460
+ console.log("Ask the network creator to generate an Auth Key at:");
5446
5461
  console.log(`${cyan(" https://login.tailscale.com/admin/settings/keys")}
5447
5462
  `);
5448
5463
  const authKey = await prompt("Enter Auth Key: ");
@@ -5532,12 +5547,12 @@ async function setupCommand() {
5532
5547
  await stepAgentPlugin();
5533
5548
  console.log(`
5534
5549
  ${bold(cyan("\u{1F680} Starting Party Server..."))}`);
5535
- const { spawn: spawn2 } = await import("child_process");
5536
- const { resolve: resolve2, dirname: dirname2 } = await import("path");
5537
- const { fileURLToPath } = await import("url");
5538
- const __dirname = dirname2(fileURLToPath(import.meta.url));
5539
- const serverScript = resolve2(__dirname, "..", "party-server.js");
5540
- const serverProc = spawn2(process.execPath, [serverScript], {
5550
+ const { spawn: spawn3 } = await import("child_process");
5551
+ const { resolve: resolve3, dirname: dirname4 } = await import("path");
5552
+ const { fileURLToPath: fileURLToPath2 } = await import("url");
5553
+ const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
5554
+ const serverScript = resolve3(__dirname2, "..", "party-server.js");
5555
+ const serverProc = spawn3(process.execPath, [serverScript], {
5541
5556
  detached: true,
5542
5557
  stdio: "ignore",
5543
5558
  windowsHide: true
@@ -5551,28 +5566,334 @@ ${bold(green("\u{1F389} Setup complete!"))}`);
5551
5566
  rl.close();
5552
5567
  }
5553
5568
 
5569
+ // src/cli/server-utils.ts
5570
+ import { spawn as spawn2, execSync as execSync3 } from "child_process";
5571
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2, openSync } from "fs";
5572
+ import { join as join4, dirname as dirname2, resolve as resolve2 } from "path";
5573
+ import { homedir as homedir3 } from "os";
5574
+ import { fileURLToPath } from "url";
5575
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
5576
+ function pidFilePath() {
5577
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
5578
+ if (pluginData) return join4(pluginData, "server.pid");
5579
+ return join4(homedir3(), ".open-party", "server.pid");
5580
+ }
5581
+ function logFilePath() {
5582
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
5583
+ if (pluginData) return join4(pluginData, "server.log");
5584
+ return join4(homedir3(), ".open-party", "server.log");
5585
+ }
5586
+ function serverScriptPath() {
5587
+ return resolve2(__dirname, "..", "party-server.js");
5588
+ }
5589
+ function readPid() {
5590
+ const path = pidFilePath();
5591
+ if (!existsSync4(path)) return null;
5592
+ try {
5593
+ return parseInt(readFileSync2(path, "utf-8").trim(), 10);
5594
+ } catch {
5595
+ return null;
5596
+ }
5597
+ }
5598
+ function writePid(pid) {
5599
+ const path = pidFilePath();
5600
+ const dir = dirname2(path);
5601
+ if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
5602
+ writeFileSync2(path, String(pid));
5603
+ }
5604
+ function removePidFile() {
5605
+ try {
5606
+ unlinkSync(pidFilePath());
5607
+ } catch {
5608
+ }
5609
+ }
5610
+ function isProcessRunning(pid) {
5611
+ if (process.platform === "win32") {
5612
+ try {
5613
+ const output = execSync3(`tasklist /FI "PID eq ${pid}" /NH`, {
5614
+ encoding: "utf-8",
5615
+ windowsHide: true,
5616
+ stdio: ["pipe", "pipe", "pipe"]
5617
+ });
5618
+ return output.includes(String(pid));
5619
+ } catch {
5620
+ return false;
5621
+ }
5622
+ }
5623
+ try {
5624
+ process.kill(pid, 0);
5625
+ return true;
5626
+ } catch {
5627
+ return false;
5628
+ }
5629
+ }
5630
+ function resolvePort() {
5631
+ return parseInt(process.env.PARTY_PORT || "8000", 10);
5632
+ }
5633
+ async function fetchJson(url, timeoutMs = 2e3) {
5634
+ try {
5635
+ const controller = new AbortController();
5636
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
5637
+ const resp = await fetch(url, { signal: controller.signal });
5638
+ clearTimeout(timer);
5639
+ if (!resp.ok) return null;
5640
+ return await resp.json();
5641
+ } catch {
5642
+ return null;
5643
+ }
5644
+ }
5645
+ async function isServerHealthy(port) {
5646
+ const p = port ?? resolvePort();
5647
+ const data = await fetchJson(`http://127.0.0.1:${p}/proxy/health`);
5648
+ return data !== null && data.status === "ok";
5649
+ }
5650
+ async function getServerHealth(port) {
5651
+ const p = port ?? resolvePort();
5652
+ return fetchJson(`http://127.0.0.1:${p}/proxy/health`);
5653
+ }
5654
+ async function getServerOverview(port) {
5655
+ const p = port ?? resolvePort();
5656
+ return fetchJson(`http://127.0.0.1:${p}/dashboard/api/overview`, 3e3);
5657
+ }
5658
+ async function spawnServerInBackground(port) {
5659
+ const script = serverScriptPath();
5660
+ if (!existsSync4(script)) {
5661
+ console.error(`Server script not found: ${script}`);
5662
+ return { pid: 0, ok: false };
5663
+ }
5664
+ const logPath = logFilePath();
5665
+ mkdirSync2(dirname2(logPath), { recursive: true });
5666
+ const logFd = openSync(logPath, "a");
5667
+ const env = { ...process.env, PARTY_PORT: String(port) };
5668
+ const proc = spawn2(process.execPath, [script], {
5669
+ stdio: ["ignore", logFd, logFd],
5670
+ detached: true,
5671
+ windowsHide: true,
5672
+ env
5673
+ });
5674
+ proc.unref();
5675
+ const pid = proc.pid;
5676
+ writePid(pid);
5677
+ proc.on("error", (err) => {
5678
+ console.error(`Failed to start server: ${err.message}`);
5679
+ });
5680
+ return { pid, ok: true };
5681
+ }
5682
+ async function waitForServerReady(port, timeoutMs = 1e4) {
5683
+ const deadline = Date.now() + timeoutMs;
5684
+ while (Date.now() < deadline) {
5685
+ if (await isServerHealthy(port)) return true;
5686
+ const pid = readPid();
5687
+ if (pid !== null && !isProcessRunning(pid)) {
5688
+ return false;
5689
+ }
5690
+ await sleep(500);
5691
+ }
5692
+ return false;
5693
+ }
5694
+ function killServer(pid) {
5695
+ try {
5696
+ if (process.platform === "win32") {
5697
+ execSync3(`taskkill /F /T /PID ${pid}`, { stdio: "ignore", windowsHide: true });
5698
+ } else {
5699
+ process.kill(pid, "SIGTERM");
5700
+ }
5701
+ } catch {
5702
+ }
5703
+ }
5704
+ function parseStartArgs(args2) {
5705
+ let daemon = false;
5706
+ let port = null;
5707
+ for (let i = 0; i < args2.length; i++) {
5708
+ if (args2[i] === "-d" || args2[i] === "--daemon") {
5709
+ daemon = true;
5710
+ } else if (args2[i] === "-p" || args2[i] === "--port") {
5711
+ const val = args2[++i];
5712
+ if (val) port = parseInt(val, 10);
5713
+ } else if (args2[i].startsWith("--port=")) {
5714
+ port = parseInt(args2[i].split("=")[1], 10);
5715
+ }
5716
+ }
5717
+ return { daemon, port };
5718
+ }
5719
+ function sleep(ms) {
5720
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5721
+ }
5722
+
5554
5723
  // src/cli/start-server.ts
5555
- async function startServer() {
5724
+ async function startServer(args2 = []) {
5725
+ const opts = parseStartArgs(args2);
5726
+ const port = opts.port ?? resolvePort();
5727
+ if (opts.daemon) {
5728
+ await startDaemon(port);
5729
+ } else {
5730
+ await startForeground();
5731
+ }
5732
+ }
5733
+ async function startForeground() {
5734
+ writePid(process.pid);
5735
+ process.on("exit", () => {
5736
+ removePidFile();
5737
+ });
5556
5738
  await Promise.resolve().then(() => (init_server(), server_exports));
5557
5739
  }
5740
+ async function startDaemon(port) {
5741
+ if (await isServerHealthy(port)) {
5742
+ const pid2 = readPid();
5743
+ console.log(`Party Server is already running (PID ${pid2 ?? "unknown"}, port ${port}).`);
5744
+ process.exit(0);
5745
+ }
5746
+ const existingPid = readPid();
5747
+ if (existingPid !== null && !isProcessRunning(existingPid)) {
5748
+ removePidFile();
5749
+ }
5750
+ const { pid, ok } = await spawnServerInBackground(port);
5751
+ if (!ok) {
5752
+ process.exit(1);
5753
+ }
5754
+ console.log(`Starting Party Server in background (PID ${pid})...`);
5755
+ const ready = await waitForServerReady(port);
5756
+ if (ready) {
5757
+ console.log(`Party Server is running on port ${port}.`);
5758
+ console.log(` Dashboard: http://127.0.0.1:${port}/dashboard`);
5759
+ console.log(` Logs: ${logFilePath()}`);
5760
+ console.log(` Use 'open-party stop' to stop the server.`);
5761
+ } else {
5762
+ console.error("Party Server failed to start within timeout.");
5763
+ console.error(`Check logs: ${logFilePath()}`);
5764
+ process.exit(1);
5765
+ }
5766
+ }
5767
+
5768
+ // src/cli/stop-server.ts
5769
+ async function stopServer() {
5770
+ const pid = readPid();
5771
+ if (pid === null) {
5772
+ const port2 = resolvePort();
5773
+ const healthy = await isServerHealthy(port2);
5774
+ if (healthy) {
5775
+ console.log(`No PID file found, but a server is responding on port ${port2}.`);
5776
+ console.log("It may have been started manually. Kill it by port or process name.");
5777
+ } else {
5778
+ console.log("Party Server is not running (no PID file found).");
5779
+ }
5780
+ return;
5781
+ }
5782
+ if (!isProcessRunning(pid)) {
5783
+ console.log(`Stale PID file found (PID ${pid} is not running). Cleaning up.`);
5784
+ removePidFile();
5785
+ return;
5786
+ }
5787
+ console.log(`Stopping Party Server (PID ${pid})...`);
5788
+ killServer(pid);
5789
+ removePidFile();
5790
+ const port = resolvePort();
5791
+ const stillUp = await isServerHealthy(port);
5792
+ if (stillUp) {
5793
+ console.warn(`Process ${pid} was killed, but port ${port} is still responding.`);
5794
+ console.warn("Another process may be using this port.");
5795
+ } else {
5796
+ console.log("Party Server stopped.");
5797
+ }
5798
+ }
5799
+
5800
+ // src/cli/status.ts
5801
+ async function statusCommand() {
5802
+ const port = resolvePort();
5803
+ const pid = readPid();
5804
+ let processAlive = false;
5805
+ if (pid !== null) {
5806
+ processAlive = isProcessRunning(pid);
5807
+ }
5808
+ const healthy = await isServerHealthy(port);
5809
+ if (healthy) {
5810
+ const health = await getServerHealth(port);
5811
+ const overview = await getServerOverview(port);
5812
+ console.log("Party Server is running.");
5813
+ console.log(` PID: ${pid ?? "unknown (no PID file)"}`);
5814
+ console.log(` Port: ${port}`);
5815
+ console.log(` Tailscale IP: ${health?.tailscale_ip ?? "N/A"}`);
5816
+ console.log(` Hostname: ${health?.hostname ?? "N/A"}`);
5817
+ if (overview) {
5818
+ const server = overview.server;
5819
+ const agents = overview.agents;
5820
+ if (server?.uptime_seconds != null) {
5821
+ const uptime = server.uptime_seconds;
5822
+ const mins = Math.floor(uptime / 60);
5823
+ const secs = Math.floor(uptime % 60);
5824
+ console.log(` Uptime: ${mins}m ${secs}s`);
5825
+ }
5826
+ console.log(` Local agents: ${agents?.local_count ?? "N/A"}`);
5827
+ console.log(` Remote agents: ${agents?.remote_count ?? "N/A"}`);
5828
+ } else {
5829
+ console.log(` Local agents: ${health?.agent_count ?? "N/A"}`);
5830
+ }
5831
+ console.log(` Dashboard: http://127.0.0.1:${port}/dashboard`);
5832
+ } else if (processAlive && pid !== null) {
5833
+ console.log("Party Server process exists but is not responding on health endpoint.");
5834
+ console.log(` PID: ${pid}`);
5835
+ console.log(" The server may be starting up or has crashed.");
5836
+ console.log(` Logs: ~/.open-party/server.log`);
5837
+ } else if (pid !== null) {
5838
+ console.log("Party Server is NOT running (stale PID file).");
5839
+ console.log(` PID file references PID ${pid}, which is not a live process.`);
5840
+ console.log(" Use: open-party start to start the server.");
5841
+ } else {
5842
+ console.log("Party Server is NOT running.");
5843
+ console.log(" No PID file found.");
5844
+ console.log(" Use: open-party start to start the server.");
5845
+ }
5846
+ }
5558
5847
 
5559
5848
  // src/cli/index.ts
5849
+ function showHelp() {
5850
+ console.log(`Usage: open-party <command> [options]
5851
+
5852
+ Commands:
5853
+ start Start the Party Server (default when no command given)
5854
+ stop Stop the Party Server
5855
+ status Show server status
5856
+ setup Interactive setup wizard (Tailscale + agent plugins)
5857
+ help Show this help message
5858
+
5859
+ Options for 'start':
5860
+ -d, --daemon Run in background (daemon mode)
5861
+ -p, --port <port> Override port (default: 8000, env: PARTY_PORT)
5862
+
5863
+ Examples:
5864
+ open-party Start server in foreground
5865
+ open-party start Start server in foreground
5866
+ open-party start -d Start server in background
5867
+ open-party start -d -p 9000 Start server in background on port 9000
5868
+ open-party stop Stop the server
5869
+ open-party status Check if the server is running`);
5870
+ }
5560
5871
  var args = process.argv.slice(2);
5561
5872
  var command = args[0] ?? "start";
5873
+ var commandArgs = args.slice(1);
5562
5874
  async function main2() {
5563
5875
  switch (command) {
5564
5876
  case "setup":
5565
5877
  await setupCommand();
5566
5878
  break;
5567
5879
  case "start":
5568
- await startServer();
5880
+ await startServer(commandArgs);
5881
+ break;
5882
+ case "stop":
5883
+ await stopServer();
5884
+ break;
5885
+ case "status":
5886
+ await statusCommand();
5887
+ break;
5888
+ case "help":
5889
+ case "--help":
5890
+ case "-h":
5891
+ showHelp();
5569
5892
  break;
5570
5893
  default:
5571
- console.log(`Usage: npx open-party [setup|start]`);
5572
- console.log("");
5573
- console.log("Commands:");
5574
- console.log(" setup Interactive setup wizard (Tailscale + agent plugins)");
5575
- console.log(" start Start the Party Server (default)");
5894
+ console.log(`Unknown command: ${command}
5895
+ `);
5896
+ showHelp();
5576
5897
  process.exit(1);
5577
5898
  }
5578
5899
  }