@feynmanzhang/open-party 0.1.1 → 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/claude-code/open-party-0.1.1/.claude-plugin/plugin.json +5 -0
- package/dist/claude-code/open-party-0.1.1/.mcp.json +9 -0
- package/dist/cli/index.js +565 -89
- package/dist/cli/index.js.map +1 -1
- package/dist/party-server.js +30 -2
- package/dist/party-server.js.map +1 -1
- package/package.json +2 -2
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
|
|
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((
|
|
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
|
|
3414
|
-
return new Promise((
|
|
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
|
|
3490
|
+
await sleep2(DISCOVERY_INTERVAL * 1e3);
|
|
3491
3491
|
}
|
|
3492
3492
|
}
|
|
3493
3493
|
async discoveryCycle() {
|
|
@@ -4639,6 +4639,19 @@ body::after{
|
|
|
4639
4639
|
fastTimer = setInterval(fastRefresh, 3000);
|
|
4640
4640
|
fullTimer = setInterval(fullRefresh, 5000);
|
|
4641
4641
|
|
|
4642
|
+
// Clipboard click delegation for .cmd elements
|
|
4643
|
+
document.addEventListener('click', function(e) {
|
|
4644
|
+
var cmd = e.target.closest('.cmd');
|
|
4645
|
+
if (!cmd) return;
|
|
4646
|
+
var text = cmd.getAttribute('data-clipboard');
|
|
4647
|
+
if (text) {
|
|
4648
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
4649
|
+
var hint = cmd.querySelector('.copy-hint');
|
|
4650
|
+
if (hint) hint.textContent = 'Copied!';
|
|
4651
|
+
});
|
|
4652
|
+
}
|
|
4653
|
+
});
|
|
4654
|
+
|
|
4642
4655
|
// Update uptime display every second
|
|
4643
4656
|
setInterval(function() {
|
|
4644
4657
|
if (overview && overview.server) {
|
|
@@ -4766,7 +4779,7 @@ body::after{
|
|
|
4766
4779
|
html += '<div style="color:var(--muted);margin-bottom:6px">Install for ' + info.os + ':</div>';
|
|
4767
4780
|
info.commands.forEach(function(cmd) {
|
|
4768
4781
|
const display = info.needs_sudo ? 'sudo ' + cmd : cmd;
|
|
4769
|
-
html += '<div class="cmd"
|
|
4782
|
+
html += '<div class="cmd" data-clipboard="' + display.replace(/"/g, '"') + '">';
|
|
4770
4783
|
html += '<code>' + display + '</code><span class="copy-hint">Click to copy</span></div>';
|
|
4771
4784
|
});
|
|
4772
4785
|
if (info.download_url) {
|
|
@@ -4927,9 +4940,20 @@ var init_dashboard = __esm({
|
|
|
4927
4940
|
|
|
4928
4941
|
// src/server/index.ts
|
|
4929
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";
|
|
4930
4946
|
async function periodicCleanup() {
|
|
4931
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
|
+
}
|
|
4932
4953
|
async function main() {
|
|
4954
|
+
const pidPath = pidFilePath2();
|
|
4955
|
+
mkdirSync3(dirname3(pidPath), { recursive: true });
|
|
4956
|
+
writeFileSync3(pidPath, String(process.pid));
|
|
4933
4957
|
console.log(`Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
|
|
4934
4958
|
process.on("SIGHUP", () => {
|
|
4935
4959
|
});
|
|
@@ -4938,6 +4962,10 @@ async function main() {
|
|
|
4938
4962
|
const cleanupPromise = periodicCleanup();
|
|
4939
4963
|
const shutdown = () => {
|
|
4940
4964
|
console.log("\nShutting down Party Server...");
|
|
4965
|
+
try {
|
|
4966
|
+
unlinkSync2(pidPath);
|
|
4967
|
+
} catch {
|
|
4968
|
+
}
|
|
4941
4969
|
server.close();
|
|
4942
4970
|
process.exit(0);
|
|
4943
4971
|
};
|
|
@@ -4998,7 +5026,7 @@ async function installTailscale(platform) {
|
|
|
4998
5026
|
const args2 = entry.needsSudo ? [entry.cmd, ...entry.args] : entry.args;
|
|
4999
5027
|
console.log(`Running: ${cmd} ${args2.join(" ")}
|
|
5000
5028
|
`);
|
|
5001
|
-
return new Promise((
|
|
5029
|
+
return new Promise((resolve3) => {
|
|
5002
5030
|
const child = spawn(cmd, args2, {
|
|
5003
5031
|
stdio: "inherit",
|
|
5004
5032
|
windowsHide: true
|
|
@@ -5008,15 +5036,15 @@ async function installTailscale(platform) {
|
|
|
5008
5036
|
if (exited) return;
|
|
5009
5037
|
exited = true;
|
|
5010
5038
|
if (code === 0) {
|
|
5011
|
-
|
|
5039
|
+
resolve3({ success: true, output: "Installation completed." });
|
|
5012
5040
|
} else {
|
|
5013
|
-
|
|
5041
|
+
resolve3({ success: false, output: `Installation exited with code ${code}` });
|
|
5014
5042
|
}
|
|
5015
5043
|
});
|
|
5016
5044
|
child.on("error", (err) => {
|
|
5017
5045
|
if (exited) return;
|
|
5018
5046
|
exited = true;
|
|
5019
|
-
|
|
5047
|
+
resolve3({ success: false, output: err.message });
|
|
5020
5048
|
});
|
|
5021
5049
|
});
|
|
5022
5050
|
}
|
|
@@ -5068,93 +5096,229 @@ function detectAgents() {
|
|
|
5068
5096
|
}
|
|
5069
5097
|
|
|
5070
5098
|
// src/cli/agent-installer.ts
|
|
5071
|
-
import { existsSync as existsSync3, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5099
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync, mkdirSync, cpSync, rmSync, readdirSync } from "fs";
|
|
5072
5100
|
import { join as join3, dirname, resolve } from "path";
|
|
5073
5101
|
import { homedir as homedir2 } from "os";
|
|
5074
|
-
function
|
|
5102
|
+
function ensureDir(filePath) {
|
|
5103
|
+
const dir = dirname(filePath);
|
|
5104
|
+
if (!existsSync3(dir)) {
|
|
5105
|
+
mkdirSync(dir, { recursive: true });
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
function readJsonFile(filePath, fallback) {
|
|
5109
|
+
if (!existsSync3(filePath)) return fallback;
|
|
5110
|
+
try {
|
|
5111
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
5112
|
+
} catch {
|
|
5113
|
+
return fallback;
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
function writeJsonFile(filePath, data) {
|
|
5117
|
+
ensureDir(filePath);
|
|
5118
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
5119
|
+
}
|
|
5120
|
+
function findPluginDistDir() {
|
|
5121
|
+
const distDir = resolve(import.meta.dirname ?? ".", "..", "claude-code");
|
|
5122
|
+
if (!existsSync3(distDir)) return null;
|
|
5123
|
+
try {
|
|
5124
|
+
const entries = readdirSync(distDir);
|
|
5125
|
+
const dirs = entries.filter((e) => e.startsWith("open-party-"));
|
|
5126
|
+
if (dirs.length === 0) return null;
|
|
5127
|
+
const pluginDir = join3(distDir, dirs[dirs.length - 1]);
|
|
5128
|
+
if (existsSync3(join3(pluginDir, ".claude-plugin", "plugin.json"))) {
|
|
5129
|
+
return pluginDir;
|
|
5130
|
+
}
|
|
5131
|
+
} catch {
|
|
5132
|
+
}
|
|
5133
|
+
return null;
|
|
5134
|
+
}
|
|
5135
|
+
function findDistJsDir() {
|
|
5075
5136
|
const possiblePaths = [
|
|
5076
|
-
// When installed globally: <pkg-root>/dist/cli/index.js → <pkg-root>/dist/
|
|
5077
|
-
resolve(import.meta.dirname ?? ".", ".."
|
|
5078
|
-
// When running from source: <project>/dist/cli/index.js → <project>/dist/
|
|
5079
|
-
resolve(import.meta.dirname ?? ".", "..", "mcp-server.js")
|
|
5137
|
+
// When installed globally via npm: <pkg-root>/dist/cli/index.js → <pkg-root>/dist/
|
|
5138
|
+
resolve(import.meta.dirname ?? ".", "..")
|
|
5080
5139
|
];
|
|
5081
5140
|
for (const p of possiblePaths) {
|
|
5082
|
-
if (existsSync3(p))
|
|
5141
|
+
if (existsSync3(join3(p, "mcp-server.js")) && existsSync3(join3(p, "hook-handler.js"))) {
|
|
5142
|
+
return p;
|
|
5143
|
+
}
|
|
5083
5144
|
}
|
|
5084
5145
|
return null;
|
|
5085
5146
|
}
|
|
5086
|
-
function
|
|
5087
|
-
const
|
|
5088
|
-
if (
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
return JSON.parse(content);
|
|
5095
|
-
} catch {
|
|
5096
|
-
}
|
|
5147
|
+
function getPluginVersion() {
|
|
5148
|
+
const pluginDir = findPluginDistDir();
|
|
5149
|
+
if (pluginDir) {
|
|
5150
|
+
const manifest2 = readJsonFile(
|
|
5151
|
+
join3(pluginDir, ".claude-plugin", "plugin.json"),
|
|
5152
|
+
{}
|
|
5153
|
+
);
|
|
5154
|
+
if (manifest2.version) return manifest2.version;
|
|
5097
5155
|
}
|
|
5098
|
-
|
|
5156
|
+
const sourceManifest = resolve(
|
|
5157
|
+
import.meta.dirname ?? ".",
|
|
5158
|
+
"..",
|
|
5159
|
+
"..",
|
|
5160
|
+
"src",
|
|
5161
|
+
"client",
|
|
5162
|
+
"claude-code",
|
|
5163
|
+
".claude-plugin",
|
|
5164
|
+
"plugin.json"
|
|
5165
|
+
);
|
|
5166
|
+
const manifest = readJsonFile(sourceManifest, {});
|
|
5167
|
+
if (manifest.version) return manifest.version;
|
|
5168
|
+
return "0.1.0";
|
|
5099
5169
|
}
|
|
5100
|
-
function
|
|
5101
|
-
|
|
5170
|
+
function getMarketplaceDir() {
|
|
5171
|
+
return join3(homedir2(), ".claude", "plugins", "marketplaces", "open-party");
|
|
5102
5172
|
}
|
|
5103
|
-
function
|
|
5104
|
-
const
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
}
|
|
5173
|
+
function registerMarketplace(version, pluginDir) {
|
|
5174
|
+
const marketplaceDir = getMarketplaceDir();
|
|
5175
|
+
const pluginSourceDir = join3(marketplaceDir, "plugin");
|
|
5176
|
+
const marketplacePluginDir = join3(marketplaceDir, ".claude-plugin");
|
|
5177
|
+
if (!existsSync3(marketplacePluginDir)) {
|
|
5178
|
+
mkdirSync(marketplacePluginDir, { recursive: true });
|
|
5110
5179
|
}
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5180
|
+
const marketplaceManifest = {
|
|
5181
|
+
name: "open-party",
|
|
5182
|
+
owner: { name: "Feynman Zhang" },
|
|
5183
|
+
metadata: {
|
|
5184
|
+
description: "Decentralized Agent communication network for Claude Code",
|
|
5185
|
+
homepage: "https://github.com/FeynmanZhang/open-party"
|
|
5186
|
+
},
|
|
5187
|
+
plugins: [
|
|
5188
|
+
{
|
|
5189
|
+
name: "open-party",
|
|
5190
|
+
version,
|
|
5191
|
+
source: "./plugin",
|
|
5192
|
+
description: "Decentralized Agent communication network for Claude Code"
|
|
5193
|
+
}
|
|
5194
|
+
]
|
|
5195
|
+
};
|
|
5196
|
+
writeJsonFile(join3(marketplacePluginDir, "marketplace.json"), marketplaceManifest);
|
|
5197
|
+
if (existsSync3(pluginSourceDir)) {
|
|
5198
|
+
rmSync(pluginSourceDir, { recursive: true });
|
|
5199
|
+
}
|
|
5200
|
+
mkdirSync(pluginSourceDir, { recursive: true });
|
|
5201
|
+
cpSync(pluginDir, pluginSourceDir, { recursive: true });
|
|
5202
|
+
const knownMarketplacesPath = join3(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
5203
|
+
const knownMarketplaces = readJsonFile(knownMarketplacesPath, {});
|
|
5204
|
+
knownMarketplaces["open-party"] = {
|
|
5205
|
+
source: {
|
|
5206
|
+
source: "github",
|
|
5207
|
+
repo: "FeynmanZhang/open-party"
|
|
5208
|
+
},
|
|
5209
|
+
installLocation: marketplaceDir,
|
|
5210
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5120
5211
|
};
|
|
5212
|
+
writeJsonFile(knownMarketplacesPath, knownMarketplaces);
|
|
5121
5213
|
}
|
|
5122
5214
|
function installClaudeCode() {
|
|
5123
|
-
const
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5215
|
+
const pluginDir = findPluginDistDir();
|
|
5216
|
+
if (!pluginDir) {
|
|
5217
|
+
return {
|
|
5218
|
+
success: false,
|
|
5219
|
+
error: 'Plugin package not found. Run "npm run build:plugin" first, or use "claude --plugin-dir" to install manually.'
|
|
5220
|
+
};
|
|
5127
5221
|
}
|
|
5128
|
-
const
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5222
|
+
const version = getPluginVersion();
|
|
5223
|
+
const installDir = join3(homedir2(), ".claude", "plugins", "cache", "open-party", "open-party", version);
|
|
5224
|
+
if (existsSync3(installDir)) {
|
|
5225
|
+
rmSync(installDir, { recursive: true });
|
|
5226
|
+
}
|
|
5227
|
+
mkdirSync(installDir, { recursive: true });
|
|
5228
|
+
cpSync(pluginDir, installDir, { recursive: true });
|
|
5229
|
+
const mcpServerPath = join3(installDir, "dist", "mcp-server.js");
|
|
5230
|
+
if (!existsSync3(mcpServerPath)) {
|
|
5231
|
+
const distJsDir = findDistJsDir();
|
|
5232
|
+
if (distJsDir) {
|
|
5233
|
+
const targetDist = join3(installDir, "dist");
|
|
5234
|
+
if (!existsSync3(targetDist)) mkdirSync(targetDist, { recursive: true });
|
|
5235
|
+
for (const file of ["mcp-server.js", "hook-handler.js", "party-server.js"]) {
|
|
5236
|
+
const src = join3(distJsDir, file);
|
|
5237
|
+
if (existsSync3(src)) {
|
|
5238
|
+
cpSync(src, join3(targetDist, file));
|
|
5239
|
+
}
|
|
5240
|
+
}
|
|
5241
|
+
for (const file of ["mcp-server.js.map", "hook-handler.js.map", "party-server.js.map"]) {
|
|
5242
|
+
const src = join3(distJsDir, file);
|
|
5243
|
+
if (existsSync3(src)) {
|
|
5244
|
+
cpSync(src, join3(targetDist, file));
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
const orphanedPath = join3(installDir, "dist", ".orphaned_at");
|
|
5250
|
+
if (existsSync3(orphanedPath)) {
|
|
5251
|
+
rmSync(orphanedPath);
|
|
5252
|
+
}
|
|
5253
|
+
registerMarketplace(version, pluginDir);
|
|
5254
|
+
const pluginsJsonPath = join3(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
5255
|
+
const pluginsData = readJsonFile(
|
|
5256
|
+
pluginsJsonPath,
|
|
5257
|
+
{ version: 2, plugins: {} }
|
|
5258
|
+
);
|
|
5259
|
+
if (!pluginsData.plugins) pluginsData.plugins = {};
|
|
5260
|
+
if (pluginsData.plugins["open-party@local"]) {
|
|
5261
|
+
delete pluginsData.plugins["open-party@local"];
|
|
5262
|
+
}
|
|
5263
|
+
pluginsData.plugins["open-party@open-party"] = [
|
|
5264
|
+
{
|
|
5265
|
+
scope: "user",
|
|
5266
|
+
installPath: installDir,
|
|
5267
|
+
version,
|
|
5268
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5269
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5270
|
+
}
|
|
5271
|
+
];
|
|
5272
|
+
writeJsonFile(pluginsJsonPath, pluginsData);
|
|
5273
|
+
const settingsPath = join3(homedir2(), ".claude", "settings.json");
|
|
5274
|
+
const settings = readJsonFile(settingsPath, {});
|
|
5275
|
+
if (settings.mcpServers?.["open-party"]) {
|
|
5276
|
+
delete settings.mcpServers["open-party"];
|
|
5277
|
+
if (Object.keys(settings.mcpServers).length === 0) {
|
|
5278
|
+
delete settings.mcpServers;
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
if (!settings.enabledPlugins) {
|
|
5282
|
+
settings.enabledPlugins = {};
|
|
5283
|
+
}
|
|
5284
|
+
if (settings.enabledPlugins["open-party@local"] !== void 0) {
|
|
5285
|
+
delete settings.enabledPlugins["open-party@local"];
|
|
5286
|
+
}
|
|
5287
|
+
settings.enabledPlugins["open-party@open-party"] = true;
|
|
5288
|
+
writeJsonFile(settingsPath, settings);
|
|
5289
|
+
return {
|
|
5290
|
+
success: true,
|
|
5291
|
+
configPath: settingsPath
|
|
5292
|
+
};
|
|
5133
5293
|
}
|
|
5134
5294
|
function installCursor() {
|
|
5135
5295
|
const configPath = join3(homedir2(), ".cursor", "mcp.json");
|
|
5136
|
-
const
|
|
5137
|
-
|
|
5138
|
-
return { success: false, error: "Could not locate Open Party plugin files" };
|
|
5139
|
-
}
|
|
5140
|
-
const config = ensureConfigFile(configPath);
|
|
5296
|
+
const { command: command2, args: args2 } = getPluginCommand();
|
|
5297
|
+
const config = readJsonFile(configPath, {});
|
|
5141
5298
|
if (!config.mcpServers) config.mcpServers = {};
|
|
5142
|
-
config.mcpServers["open-party"] =
|
|
5143
|
-
|
|
5299
|
+
config.mcpServers["open-party"] = { type: "stdio", command: command2, args: args2 };
|
|
5300
|
+
writeJsonFile(configPath, config);
|
|
5144
5301
|
return { success: true, configPath };
|
|
5145
5302
|
}
|
|
5146
5303
|
function installGeminiCli() {
|
|
5147
5304
|
const configPath = join3(homedir2(), ".gemini", "settings.json");
|
|
5148
|
-
const
|
|
5149
|
-
|
|
5150
|
-
return { success: false, error: "Could not locate Open Party plugin files" };
|
|
5151
|
-
}
|
|
5152
|
-
const config = ensureConfigFile(configPath);
|
|
5305
|
+
const { command: command2, args: args2 } = getPluginCommand();
|
|
5306
|
+
const config = readJsonFile(configPath, {});
|
|
5153
5307
|
if (!config.mcpServers) config.mcpServers = {};
|
|
5154
|
-
config.mcpServers["open-party"] =
|
|
5155
|
-
|
|
5308
|
+
config.mcpServers["open-party"] = { type: "stdio", command: command2, args: args2 };
|
|
5309
|
+
writeJsonFile(configPath, config);
|
|
5156
5310
|
return { success: true, configPath };
|
|
5157
5311
|
}
|
|
5312
|
+
function getPluginCommand() {
|
|
5313
|
+
const distDir = findDistJsDir();
|
|
5314
|
+
if (distDir) {
|
|
5315
|
+
const serverPath = join3(distDir, "mcp-server.js");
|
|
5316
|
+
if (existsSync3(serverPath)) {
|
|
5317
|
+
return { command: "node", args: [serverPath] };
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
return { command: "npx", args: ["@feynmanzhang/open-party", "mcp"] };
|
|
5321
|
+
}
|
|
5158
5322
|
async function installPluginToAgent(agentType) {
|
|
5159
5323
|
switch (agentType) {
|
|
5160
5324
|
case "claude-code":
|
|
@@ -5171,7 +5335,7 @@ async function installPluginToAgent(agentType) {
|
|
|
5171
5335
|
// src/cli/setup.ts
|
|
5172
5336
|
var rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5173
5337
|
function prompt(question) {
|
|
5174
|
-
return new Promise((
|
|
5338
|
+
return new Promise((resolve3) => rl.question(question, (answer) => resolve3(answer.trim())));
|
|
5175
5339
|
}
|
|
5176
5340
|
function cyan(text) {
|
|
5177
5341
|
return `\x1B[36m${text}\x1B[0m`;
|
|
@@ -5274,10 +5438,10 @@ async function handleInteractiveLogin(binary) {
|
|
|
5274
5438
|
console.log(`
|
|
5275
5439
|
${cyan("Running interactive login...")}`);
|
|
5276
5440
|
console.log("A browser window should open. Authenticate in the browser, then return here.\n");
|
|
5277
|
-
const { spawn:
|
|
5278
|
-
const child =
|
|
5279
|
-
const exitCode = await new Promise((
|
|
5280
|
-
child.on("close",
|
|
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);
|
|
5281
5445
|
});
|
|
5282
5446
|
resetTailscaleBinaryCache();
|
|
5283
5447
|
const status = getTailscaleInstallationStatus();
|
|
@@ -5293,7 +5457,7 @@ ${yellow("\u26A0\uFE0F Login may not have completed. Status: " + status.state)}
|
|
|
5293
5457
|
}
|
|
5294
5458
|
async function handleAuthKeyLogin(binary) {
|
|
5295
5459
|
console.log("");
|
|
5296
|
-
console.log("
|
|
5460
|
+
console.log("Ask the network creator to generate an Auth Key at:");
|
|
5297
5461
|
console.log(`${cyan(" https://login.tailscale.com/admin/settings/keys")}
|
|
5298
5462
|
`);
|
|
5299
5463
|
const authKey = await prompt("Enter Auth Key: ");
|
|
@@ -5352,6 +5516,12 @@ Installing Open Party plugin for ${agent.name}...`);
|
|
|
5352
5516
|
const result = await installPluginToAgent(agent.type);
|
|
5353
5517
|
if (result.success) {
|
|
5354
5518
|
console.log(`${green("\u2705")} Plugin installed for ${agent.name}${result.configPath ? ` (${result.configPath})` : ""}`);
|
|
5519
|
+
if (result.warning) {
|
|
5520
|
+
console.log(` ${yellow("\u26A0\uFE0F")} ${result.warning}`);
|
|
5521
|
+
}
|
|
5522
|
+
if (agent.type === "claude-code") {
|
|
5523
|
+
console.log(` ${bold("Please restart Claude Code")} for changes to take effect.`);
|
|
5524
|
+
}
|
|
5355
5525
|
} else {
|
|
5356
5526
|
console.log(`${red("\u274C")} Failed to install for ${agent.name}: ${result.error}`);
|
|
5357
5527
|
}
|
|
@@ -5377,12 +5547,12 @@ async function setupCommand() {
|
|
|
5377
5547
|
await stepAgentPlugin();
|
|
5378
5548
|
console.log(`
|
|
5379
5549
|
${bold(cyan("\u{1F680} Starting Party Server..."))}`);
|
|
5380
|
-
const { spawn:
|
|
5381
|
-
const { resolve:
|
|
5382
|
-
const { fileURLToPath } = await import("url");
|
|
5383
|
-
const
|
|
5384
|
-
const serverScript =
|
|
5385
|
-
const serverProc =
|
|
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], {
|
|
5386
5556
|
detached: true,
|
|
5387
5557
|
stdio: "ignore",
|
|
5388
5558
|
windowsHide: true
|
|
@@ -5396,28 +5566,334 @@ ${bold(green("\u{1F389} Setup complete!"))}`);
|
|
|
5396
5566
|
rl.close();
|
|
5397
5567
|
}
|
|
5398
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
|
+
|
|
5399
5723
|
// src/cli/start-server.ts
|
|
5400
|
-
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
|
+
});
|
|
5401
5738
|
await Promise.resolve().then(() => (init_server(), server_exports));
|
|
5402
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
|
+
}
|
|
5403
5847
|
|
|
5404
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
|
+
}
|
|
5405
5871
|
var args = process.argv.slice(2);
|
|
5406
5872
|
var command = args[0] ?? "start";
|
|
5873
|
+
var commandArgs = args.slice(1);
|
|
5407
5874
|
async function main2() {
|
|
5408
5875
|
switch (command) {
|
|
5409
5876
|
case "setup":
|
|
5410
5877
|
await setupCommand();
|
|
5411
5878
|
break;
|
|
5412
5879
|
case "start":
|
|
5413
|
-
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();
|
|
5414
5892
|
break;
|
|
5415
5893
|
default:
|
|
5416
|
-
console.log(`
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
console.log(" setup Interactive setup wizard (Tailscale + agent plugins)");
|
|
5420
|
-
console.log(" start Start the Party Server (default)");
|
|
5894
|
+
console.log(`Unknown command: ${command}
|
|
5895
|
+
`);
|
|
5896
|
+
showHelp();
|
|
5421
5897
|
process.exit(1);
|
|
5422
5898
|
}
|
|
5423
5899
|
}
|