openpalm 0.3.3 → 0.4.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/openpalm.js +123 -45
- package/package.json +1 -1
package/dist/openpalm.js
CHANGED
|
@@ -7553,6 +7553,39 @@ env:
|
|
|
7553
7553
|
required: false
|
|
7554
7554
|
`;
|
|
7555
7555
|
|
|
7556
|
+
// packages/lib/assets/channels/mcp.yaml
|
|
7557
|
+
var mcp_default = `name: MCP
|
|
7558
|
+
description: Expose your assistant as an MCP tool server for AI clients and IDE integrations
|
|
7559
|
+
containerPort: 8187
|
|
7560
|
+
rewritePath: /mcp
|
|
7561
|
+
sharedSecretEnv: CHANNEL_MCP_SECRET
|
|
7562
|
+
env:
|
|
7563
|
+
- name: MCP_BEARER_TOKEN
|
|
7564
|
+
description: Optional bearer token for authenticating MCP clients
|
|
7565
|
+
required: false
|
|
7566
|
+
- name: CHANNEL_MCP_SECRET
|
|
7567
|
+
description: HMAC signing key for gateway communication. Auto-generated if left blank.
|
|
7568
|
+
required: false
|
|
7569
|
+
`;
|
|
7570
|
+
|
|
7571
|
+
// packages/lib/assets/channels/a2a.yaml
|
|
7572
|
+
var a2a_default = `name: A2A
|
|
7573
|
+
description: Expose your assistant as an A2A agent for agent-to-agent communication
|
|
7574
|
+
containerPort: 8188
|
|
7575
|
+
rewritePath: /a2a
|
|
7576
|
+
sharedSecretEnv: CHANNEL_A2A_SECRET
|
|
7577
|
+
env:
|
|
7578
|
+
- name: A2A_BEARER_TOKEN
|
|
7579
|
+
description: Optional bearer token for authenticating A2A clients
|
|
7580
|
+
required: false
|
|
7581
|
+
- name: A2A_PUBLIC_URL
|
|
7582
|
+
description: Public URL advertised in the Agent Card
|
|
7583
|
+
required: false
|
|
7584
|
+
- name: CHANNEL_A2A_SECRET
|
|
7585
|
+
description: HMAC signing key for gateway communication. Auto-generated if left blank.
|
|
7586
|
+
required: false
|
|
7587
|
+
`;
|
|
7588
|
+
|
|
7556
7589
|
// packages/lib/assets/channels/index.ts
|
|
7557
7590
|
function parseBuiltInChannel(raw, source) {
|
|
7558
7591
|
const parsed = parseYamlDocument(raw);
|
|
@@ -7584,7 +7617,9 @@ var BUILTIN_CHANNELS = Object.fromEntries([
|
|
|
7584
7617
|
[chat_default, "chat.yaml"],
|
|
7585
7618
|
[discord_default, "discord.yaml"],
|
|
7586
7619
|
[voice_default, "voice.yaml"],
|
|
7587
|
-
[telegram_default, "telegram.yaml"]
|
|
7620
|
+
[telegram_default, "telegram.yaml"],
|
|
7621
|
+
[mcp_default, "mcp.yaml"],
|
|
7622
|
+
[a2a_default, "a2a.yaml"]
|
|
7588
7623
|
].map(([raw, source]) => {
|
|
7589
7624
|
const def = parseBuiltInChannel(raw, source);
|
|
7590
7625
|
return [def.name.toLowerCase(), def];
|
|
@@ -7618,9 +7653,9 @@ async function checkDiskSpace() {
|
|
|
7618
7653
|
} catch {}
|
|
7619
7654
|
return null;
|
|
7620
7655
|
}
|
|
7621
|
-
async function
|
|
7656
|
+
async function checkPort(port = 80) {
|
|
7622
7657
|
try {
|
|
7623
|
-
const lsof = Bun.spawn(["lsof",
|
|
7658
|
+
const lsof = Bun.spawn(["lsof", `-iTCP:${port}`, "-sTCP:LISTEN", "-P", "-n"], {
|
|
7624
7659
|
stdout: "pipe",
|
|
7625
7660
|
stderr: "ignore"
|
|
7626
7661
|
});
|
|
@@ -7631,8 +7666,8 @@ async function checkPort80() {
|
|
|
7631
7666
|
`).slice(0, 3).join(`
|
|
7632
7667
|
`);
|
|
7633
7668
|
return {
|
|
7634
|
-
message:
|
|
7635
|
-
detail: `OpenPalm needs port
|
|
7669
|
+
message: `Port ${port} is already in use by another process.`,
|
|
7670
|
+
detail: `OpenPalm needs port ${port} for its web interface.
|
|
7636
7671
|
${lines}`
|
|
7637
7672
|
};
|
|
7638
7673
|
}
|
|
@@ -7644,10 +7679,10 @@ ${lines}`
|
|
|
7644
7679
|
});
|
|
7645
7680
|
await ss.exited;
|
|
7646
7681
|
const output = await new Response(ss.stdout).text();
|
|
7647
|
-
if (output.includes(
|
|
7682
|
+
if (output.includes(`:${port} `)) {
|
|
7648
7683
|
return {
|
|
7649
|
-
message:
|
|
7650
|
-
detail:
|
|
7684
|
+
message: `Port ${port} is already in use by another process.`,
|
|
7685
|
+
detail: `OpenPalm needs port ${port} for its web interface.`
|
|
7651
7686
|
};
|
|
7652
7687
|
}
|
|
7653
7688
|
} catch {}
|
|
@@ -7677,10 +7712,10 @@ async function checkDaemonRunning(bin, platform) {
|
|
|
7677
7712
|
}
|
|
7678
7713
|
return null;
|
|
7679
7714
|
}
|
|
7680
|
-
async function runPreflightChecks(bin, platform) {
|
|
7715
|
+
async function runPreflightChecks(bin, platform, port = 80) {
|
|
7681
7716
|
const results = await Promise.all([
|
|
7682
7717
|
checkDiskSpace(),
|
|
7683
|
-
|
|
7718
|
+
checkPort(port),
|
|
7684
7719
|
checkDaemonRunning(bin, platform)
|
|
7685
7720
|
]);
|
|
7686
7721
|
return results.filter((w) => w !== null);
|
|
@@ -7822,6 +7857,23 @@ async function confirm(prompt) {
|
|
|
7822
7857
|
}
|
|
7823
7858
|
|
|
7824
7859
|
// packages/cli/src/commands/install.ts
|
|
7860
|
+
function reportIssueUrl(context) {
|
|
7861
|
+
const title = encodeURIComponent(`Install failure: ${context.error.slice(0, 80)}`);
|
|
7862
|
+
const body = encodeURIComponent(`## Environment
|
|
7863
|
+
` + `- OS: ${context.os}
|
|
7864
|
+
` + `- Arch: ${context.arch}
|
|
7865
|
+
` + `- Runtime: ${context.runtime}
|
|
7866
|
+
|
|
7867
|
+
` + `## Error
|
|
7868
|
+
\`\`\`
|
|
7869
|
+
${context.error}
|
|
7870
|
+
\`\`\`
|
|
7871
|
+
|
|
7872
|
+
` + `## Steps to Reproduce
|
|
7873
|
+
1. Ran \`openpalm install\`
|
|
7874
|
+
`);
|
|
7875
|
+
return `https://github.com/itlackey/openpalm/issues/new?title=${title}&body=${body}`;
|
|
7876
|
+
}
|
|
7825
7877
|
async function install(options) {
|
|
7826
7878
|
log(bold(`
|
|
7827
7879
|
OpenPalm Installation
|
|
@@ -7835,10 +7887,14 @@ OpenPalm Installation
|
|
|
7835
7887
|
const platform = options.runtime ?? await detectRuntime(os);
|
|
7836
7888
|
if (!platform) {
|
|
7837
7889
|
error(noRuntimeGuidance(os));
|
|
7890
|
+
info("");
|
|
7891
|
+
info(" If this keeps happening, report the issue:");
|
|
7892
|
+
info(` ${cyan(reportIssueUrl({ os, arch, runtime: "none", error: "No container runtime found" }))}`);
|
|
7838
7893
|
process.exit(1);
|
|
7839
7894
|
}
|
|
7840
7895
|
const { bin, subcommand } = resolveComposeBin(platform);
|
|
7841
|
-
const
|
|
7896
|
+
const ingressPort = options.port ?? 80;
|
|
7897
|
+
const preflightWarnings = await runPreflightChecks(bin, platform, ingressPort);
|
|
7842
7898
|
for (const w of preflightWarnings) {
|
|
7843
7899
|
warn(w.message);
|
|
7844
7900
|
if (w.detail) {
|
|
@@ -7853,6 +7909,13 @@ OpenPalm Installation
|
|
|
7853
7909
|
if (daemonWarning) {
|
|
7854
7910
|
process.exit(1);
|
|
7855
7911
|
}
|
|
7912
|
+
const portWarning = preflightWarnings.find((w) => w.message.includes("already in use"));
|
|
7913
|
+
if (portWarning) {
|
|
7914
|
+
if (!options.port) {
|
|
7915
|
+
error("Port 80 is required but already in use. Use --port to specify an alternative.");
|
|
7916
|
+
}
|
|
7917
|
+
process.exit(1);
|
|
7918
|
+
}
|
|
7856
7919
|
const isValid = await validateRuntime(bin, subcommand);
|
|
7857
7920
|
if (!isValid) {
|
|
7858
7921
|
error(noComposeGuidance(platform));
|
|
@@ -7939,12 +8002,9 @@ XDG paths:`));
|
|
|
7939
8002
|
}
|
|
7940
8003
|
if (generatedAdminToken) {
|
|
7941
8004
|
log("");
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
log("");
|
|
7946
|
-
info(" You will need this password to log in to the admin dashboard.");
|
|
7947
|
-
info(` It is also saved in: ${dim(stateEnvFile)}`);
|
|
8005
|
+
info(" A temporary admin token has been generated.");
|
|
8006
|
+
info(" You will choose your own password in the setup wizard.");
|
|
8007
|
+
info(` Temporary token saved in: ${dim(stateEnvFile)}`);
|
|
7948
8008
|
log("");
|
|
7949
8009
|
}
|
|
7950
8010
|
const socketPath = resolveSocketPath(platform, os);
|
|
@@ -7966,7 +8026,8 @@ XDG paths:`));
|
|
|
7966
8026
|
["OPENPALM_WORK_HOME", normPath(resolveWorkHome())],
|
|
7967
8027
|
["OPENPALM_UID", String(process.getuid?.() ?? 1000)],
|
|
7968
8028
|
["OPENPALM_GID", String(process.getgid?.() ?? 1000)],
|
|
7969
|
-
["OPENPALM_ENABLED_CHANNELS", ""]
|
|
8029
|
+
["OPENPALM_ENABLED_CHANNELS", ""],
|
|
8030
|
+
["OPENPALM_INGRESS_PORT", String(ingressPort)]
|
|
7970
8031
|
]);
|
|
7971
8032
|
await Bun.write(cwdEnvPath, Bun.file(stateEnvFile));
|
|
7972
8033
|
const spin4 = spinner("Seeding configuration files...");
|
|
@@ -7992,7 +8053,7 @@ openpalm uninstall
|
|
|
7992
8053
|
http: {
|
|
7993
8054
|
servers: {
|
|
7994
8055
|
main: {
|
|
7995
|
-
listen: [
|
|
8056
|
+
listen: [`:${ingressPort}`],
|
|
7996
8057
|
routes: [
|
|
7997
8058
|
{
|
|
7998
8059
|
match: [{ path: ["/api*"] }],
|
|
@@ -8024,11 +8085,6 @@ openpalm uninstall
|
|
|
8024
8085
|
`;
|
|
8025
8086
|
const caddyJsonPath = join4(xdg.state, "caddy.json");
|
|
8026
8087
|
await writeFile(caddyJsonPath, minimalCaddyJson, "utf8");
|
|
8027
|
-
const fallbackCaddyJsonPath = join4(xdg.state, "caddy-fallback.json");
|
|
8028
|
-
const fallbackCaddyJsonExists = await Bun.file(fallbackCaddyJsonPath).exists();
|
|
8029
|
-
if (!fallbackCaddyJsonExists) {
|
|
8030
|
-
await writeFile(fallbackCaddyJsonPath, minimalCaddyJson, "utf8");
|
|
8031
|
-
}
|
|
8032
8088
|
log(bold(`
|
|
8033
8089
|
Downloading OpenPalm services (this may take a few minutes on first install)...
|
|
8034
8090
|
`));
|
|
@@ -8037,7 +8093,7 @@ Downloading OpenPalm services (this may take a few minutes on first install)...
|
|
|
8037
8093
|
image: caddy:2-alpine
|
|
8038
8094
|
restart: unless-stopped
|
|
8039
8095
|
ports:
|
|
8040
|
-
- "\${OPENPALM_INGRESS_BIND_ADDRESS:-127.0.0.1}
|
|
8096
|
+
- "\${OPENPALM_INGRESS_BIND_ADDRESS:-127.0.0.1}:\${OPENPALM_INGRESS_PORT:-80}:80"
|
|
8041
8097
|
- "\${OPENPALM_INGRESS_BIND_ADDRESS:-127.0.0.1}:443:443"
|
|
8042
8098
|
volumes:
|
|
8043
8099
|
- \${OPENPALM_STATE_HOME}/caddy.json:/etc/caddy/caddy.json:ro
|
|
@@ -8049,6 +8105,8 @@ Downloading OpenPalm services (this may take a few minutes on first install)...
|
|
|
8049
8105
|
admin:
|
|
8050
8106
|
image: \${OPENPALM_IMAGE_NAMESPACE:-openpalm}/admin:\${OPENPALM_IMAGE_TAG:-latest}
|
|
8051
8107
|
restart: unless-stopped
|
|
8108
|
+
ports:
|
|
8109
|
+
- "127.0.0.1:8100:8100"
|
|
8052
8110
|
env_file:
|
|
8053
8111
|
- \${OPENPALM_STATE_HOME}/system.env
|
|
8054
8112
|
environment:
|
|
@@ -8076,15 +8134,9 @@ Downloading OpenPalm services (this may take a few minutes on first install)...
|
|
|
8076
8134
|
start_period: 10s
|
|
8077
8135
|
|
|
8078
8136
|
networks:
|
|
8079
|
-
channel_net:
|
|
8080
8137
|
assistant_net:
|
|
8081
8138
|
`;
|
|
8082
8139
|
await writeFile(stateComposeFile, minimalCompose, "utf8");
|
|
8083
|
-
const fallbackComposePath = join4(xdg.state, "docker-compose-fallback.yml");
|
|
8084
|
-
const fallbackComposeExists = await Bun.file(fallbackComposePath).exists();
|
|
8085
|
-
if (!fallbackComposeExists) {
|
|
8086
|
-
await writeFile(fallbackComposePath, minimalCompose, "utf8");
|
|
8087
|
-
}
|
|
8088
8140
|
const composeConfig = {
|
|
8089
8141
|
bin,
|
|
8090
8142
|
subcommand,
|
|
@@ -8106,14 +8158,19 @@ networks:
|
|
|
8106
8158
|
info(" Or manually pull and then start:");
|
|
8107
8159
|
info(` ${cyan(`${bin} ${subcommand} --env-file ${stateEnvFile} -f ${stateComposeFile} pull`)}`);
|
|
8108
8160
|
info(` ${cyan(`${bin} ${subcommand} --env-file ${stateEnvFile} -f ${stateComposeFile} up -d`)}`);
|
|
8161
|
+
info("");
|
|
8162
|
+
info(" If this keeps happening, report the issue:");
|
|
8163
|
+
info(` ${cyan(reportIssueUrl({ os, arch, runtime: platform, error: String(pullErr) }))}`);
|
|
8109
8164
|
log("");
|
|
8110
8165
|
process.exit(1);
|
|
8111
8166
|
}
|
|
8112
8167
|
const spin7 = spinner("Starting core services...");
|
|
8113
8168
|
await composeUp(composeConfig, coreServices, { detach: true });
|
|
8114
8169
|
spin7.stop(green("Core services started"));
|
|
8115
|
-
|
|
8170
|
+
let adminUrl = ingressPort === 80 ? "http://localhost" : `http://localhost:${ingressPort}`;
|
|
8171
|
+
const adminDirectUrl = "http://localhost:8100";
|
|
8116
8172
|
const healthUrl = `${adminUrl}/setup/status`;
|
|
8173
|
+
const healthDirectUrl = `${adminDirectUrl}/setup/status`;
|
|
8117
8174
|
const spin8 = spinner("Waiting for admin interface...");
|
|
8118
8175
|
let healthy = false;
|
|
8119
8176
|
let delay = 1000;
|
|
@@ -8126,7 +8183,16 @@ networks:
|
|
|
8126
8183
|
healthy = true;
|
|
8127
8184
|
break;
|
|
8128
8185
|
}
|
|
8129
|
-
} catch {
|
|
8186
|
+
} catch {
|
|
8187
|
+
try {
|
|
8188
|
+
const directResponse = await fetch(healthDirectUrl, { signal: AbortSignal.timeout(3000) });
|
|
8189
|
+
if (directResponse.ok) {
|
|
8190
|
+
healthy = true;
|
|
8191
|
+
adminUrl = adminDirectUrl;
|
|
8192
|
+
break;
|
|
8193
|
+
}
|
|
8194
|
+
} catch {}
|
|
8195
|
+
}
|
|
8130
8196
|
await Bun.sleep(delay);
|
|
8131
8197
|
delay = Math.min(delay * 1.5, maxDelay);
|
|
8132
8198
|
}
|
|
@@ -8151,17 +8217,15 @@ networks:
|
|
|
8151
8217
|
log(bold(green(" OpenPalm setup wizard is ready!")));
|
|
8152
8218
|
log("");
|
|
8153
8219
|
info(` Setup wizard: ${cyan(adminUrl)}`);
|
|
8220
|
+
info(` Direct admin: ${cyan("http://localhost:8100")} (if port 80 is blocked)`);
|
|
8154
8221
|
log("");
|
|
8155
|
-
if (generatedAdminToken) {
|
|
8156
|
-
info(` Admin password: ${yellow(generatedAdminToken)}`);
|
|
8157
|
-
log("");
|
|
8158
|
-
}
|
|
8159
8222
|
log(bold(" What happens next:"));
|
|
8160
8223
|
info(" 1. The setup wizard opens in your browser");
|
|
8161
|
-
info(" 2.
|
|
8162
|
-
info(" 3.
|
|
8163
|
-
info(" 4.
|
|
8164
|
-
info(" 5.
|
|
8224
|
+
info(" 2. Create your admin password and profile");
|
|
8225
|
+
info(" 3. Enter your AI provider API key (e.g. from console.anthropic.com)");
|
|
8226
|
+
info(" 4. The wizard will download and start remaining services automatically");
|
|
8227
|
+
info(" 5. Pick which channels to enable (chat, Discord, etc.)");
|
|
8228
|
+
info(" 6. Done! Start chatting with your assistant");
|
|
8165
8229
|
log("");
|
|
8166
8230
|
if (!options.noOpen) {
|
|
8167
8231
|
info(" Opening setup wizard in your browser...");
|
|
@@ -8186,6 +8250,12 @@ networks:
|
|
|
8186
8250
|
info(" - Make sure port 80 is not used by another service");
|
|
8187
8251
|
info(" - Restart Docker/Podman and try again");
|
|
8188
8252
|
info(" - Check that you have internet access (images need to download)");
|
|
8253
|
+
log("");
|
|
8254
|
+
info(" 5. If the browser doesn't open, try the direct admin URL:");
|
|
8255
|
+
info(` ${cyan("http://localhost:8100")}`);
|
|
8256
|
+
log("");
|
|
8257
|
+
info(" 6. Still stuck? Report the issue:");
|
|
8258
|
+
info(` ${cyan(reportIssueUrl({ os, arch, runtime: platform, error: "Health check timeout after 3 minutes" }))}`);
|
|
8189
8259
|
}
|
|
8190
8260
|
log("");
|
|
8191
8261
|
log(bold(" Useful commands:"));
|
|
@@ -8622,7 +8692,7 @@ function createChannel(args) {
|
|
|
8622
8692
|
}
|
|
8623
8693
|
if (name.length > 32)
|
|
8624
8694
|
throw new Error("Error: channel name must be 32 characters or fewer");
|
|
8625
|
-
const reserved = new Set(["chat", "discord", "voice", "telegram", "webhook"]);
|
|
8695
|
+
const reserved = new Set(["chat", "discord", "voice", "telegram", "webhook", "api", "mcp", "a2a"]);
|
|
8626
8696
|
if (reserved.has(name)) {
|
|
8627
8697
|
throw new Error(`Error: "${name}" is an existing built-in channel. Choose a different name.`);
|
|
8628
8698
|
}
|
|
@@ -9131,7 +9201,7 @@ async function automation(subcommand, args) {
|
|
|
9131
9201
|
// packages/cli/package.json
|
|
9132
9202
|
var package_default = {
|
|
9133
9203
|
name: "openpalm",
|
|
9134
|
-
version: "0.
|
|
9204
|
+
version: "0.4.0",
|
|
9135
9205
|
description: "CLI tool for installing and managing an OpenPalm stack",
|
|
9136
9206
|
type: "module",
|
|
9137
9207
|
license: "MIT",
|
|
@@ -9203,6 +9273,7 @@ function printHelp() {
|
|
|
9203
9273
|
log(" --no-open Don't auto-open browser");
|
|
9204
9274
|
log(" --ref <branch|tag> Git ref for asset download");
|
|
9205
9275
|
log(" --force Overwrite existing installation");
|
|
9276
|
+
log(" --port <number> Use alternative port (default: 80)");
|
|
9206
9277
|
log("");
|
|
9207
9278
|
log(bold("Uninstall options:"));
|
|
9208
9279
|
log(" --runtime <docker|podman|orbstack> Force container runtime");
|
|
@@ -9243,7 +9314,7 @@ function getPositionalArgs(args) {
|
|
|
9243
9314
|
while (i < args.length) {
|
|
9244
9315
|
if (args[i].startsWith("--")) {
|
|
9245
9316
|
const flagName = args[i].slice(2);
|
|
9246
|
-
if (["runtime", "ref", "plugin"].includes(flagName)) {
|
|
9317
|
+
if (["runtime", "ref", "plugin", "port"].includes(flagName)) {
|
|
9247
9318
|
i += 2;
|
|
9248
9319
|
} else {
|
|
9249
9320
|
i += 1;
|
|
@@ -9274,11 +9345,18 @@ async function main() {
|
|
|
9274
9345
|
error(`Invalid runtime "${runtimeArg}". Must be one of: ${VALID_RUNTIMES.join(", ")}`);
|
|
9275
9346
|
process.exit(1);
|
|
9276
9347
|
}
|
|
9348
|
+
const portArg = parseArg(args, "port");
|
|
9349
|
+
const port = portArg ? Number(portArg) : undefined;
|
|
9350
|
+
if (portArg && (!port || port < 1 || port > 65535)) {
|
|
9351
|
+
error(`Invalid port "${portArg}". Must be a number between 1 and 65535.`);
|
|
9352
|
+
process.exit(1);
|
|
9353
|
+
}
|
|
9277
9354
|
const options = {
|
|
9278
9355
|
runtime: runtimeArg,
|
|
9279
9356
|
noOpen: hasFlag(args, "no-open"),
|
|
9280
9357
|
ref: parseArg(args, "ref"),
|
|
9281
|
-
force: hasFlag(args, "force")
|
|
9358
|
+
force: hasFlag(args, "force"),
|
|
9359
|
+
port
|
|
9282
9360
|
};
|
|
9283
9361
|
await install(options);
|
|
9284
9362
|
break;
|