openpalm 0.3.4 → 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.
Files changed (2) hide show
  1. package/dist/openpalm.js +123 -45
  2. 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 checkPort80() {
7656
+ async function checkPort(port = 80) {
7622
7657
  try {
7623
- const lsof = Bun.spawn(["lsof", "-iTCP:80", "-sTCP:LISTEN", "-P", "-n"], {
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: "Port 80 is already in use by another process.",
7635
- detail: `OpenPalm needs port 80 for its web interface.
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(":80 ")) {
7682
+ if (output.includes(`:${port} `)) {
7648
7683
  return {
7649
- message: "Port 80 is already in use by another process.",
7650
- detail: "OpenPalm needs port 80 for its web interface."
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
- checkPort80(),
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 preflightWarnings = await runPreflightChecks(bin, platform);
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
- log(bold(green(" YOUR ADMIN PASSWORD (save this!)")));
7943
- log("");
7944
- log(` ${yellow(generatedAdminToken)}`);
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: [":80"],
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}:80:80"
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
- const adminUrl = "http://localhost";
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. Enter your AI provider API key (e.g. from console.anthropic.com)");
8162
- info(" 3. The wizard will download and start remaining services automatically");
8163
- info(" 4. Pick which channels to enable (chat, Discord, etc.)");
8164
- info(" 5. Done! Start chatting with your assistant");
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.3.4",
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openpalm",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for installing and managing an OpenPalm stack",
5
5
  "type": "module",
6
6
  "license": "MIT",