openbot 0.2.5 → 0.2.6

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.js CHANGED
@@ -3,19 +3,34 @@ import { Command } from "commander";
3
3
  import * as readline from "node:readline/promises";
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as path from "node:path";
6
+ import { spawn } from "node:child_process";
6
7
  import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
7
8
  import { startServer } from "./server.js";
8
9
  import { getPluginMetadata } from "./registry/plugin-loader.js";
9
10
  import { checkGitHubRepo, checkNpmPackage, parsePluginInstallSource, parseAgentInstallSource, installPluginFromSource, installAgentFromSource, } from "./installers.js";
10
11
  const program = new Command();
12
+ const REQUIRED_NODE_VERSION = "20.12.0";
13
+ function checkNodeVersion() {
14
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
15
+ const [reqMajor, reqMinor, reqPatch] = REQUIRED_NODE_VERSION.split(".").map(Number);
16
+ const isOld = major < reqMajor ||
17
+ (major === reqMajor && minor < reqMinor) ||
18
+ (major === reqMajor && minor === reqMinor && patch < reqPatch);
19
+ if (isOld) {
20
+ console.warn(`\n⚠️ WARNING: You are using Node.js ${process.version}.`);
21
+ console.warn(` OpenBot works best with Node.js >=${REQUIRED_NODE_VERSION}.`);
22
+ console.warn(` You may encounter "ERR_REQUIRE_ESM" or other compatibility issues on older versions.\n`);
23
+ }
24
+ }
25
+ checkNodeVersion();
11
26
  program
12
27
  .name("openbot")
13
28
  .description("OpenBot CLI - Secure and easy configuration")
14
- .version("0.2.5");
15
- async function installPlugin(source, quiet = false) {
29
+ .version("0.2.6");
30
+ async function installPlugin(source, id, quiet = false) {
16
31
  try {
17
32
  const parsed = parsePluginInstallSource(source);
18
- const name = await installPluginFromSource(parsed, { quiet });
33
+ const name = await installPluginFromSource(parsed, { quiet, id });
19
34
  return name;
20
35
  }
21
36
  catch (err) {
@@ -26,10 +41,10 @@ async function installPlugin(source, quiet = false) {
26
41
  throw err;
27
42
  }
28
43
  }
29
- async function installAgent(source) {
44
+ async function installAgent(source, id) {
30
45
  try {
31
46
  const parsed = parseAgentInstallSource(source);
32
- const name = await installAgentFromSource(parsed);
47
+ const name = await installAgentFromSource(parsed, { id });
33
48
  return name;
34
49
  }
35
50
  catch (err) {
@@ -37,6 +52,9 @@ async function installAgent(source) {
37
52
  process.exit(1);
38
53
  }
39
54
  }
55
+ function shellEscape(arg) {
56
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
57
+ }
40
58
  program
41
59
  .command("configure")
42
60
  .description("Configure OpenBot model and settings")
@@ -85,6 +103,8 @@ program
85
103
  console.log("Alternatively, you can set the environment variable:");
86
104
  console.log(provider === "openai" ? " export OPENAI_API_KEY=your-key" : " export ANTHROPIC_API_KEY=your-key");
87
105
  console.log("------------------------------------------");
106
+ console.log("\n🚀 TIP: Use 'openbot up' to start the server and web UI together.");
107
+ console.log("------------------------------------------\n");
88
108
  rl.close();
89
109
  });
90
110
  program
@@ -96,6 +116,43 @@ program
96
116
  .action(async (options) => {
97
117
  await startServer(options);
98
118
  });
119
+ program
120
+ .command("up")
121
+ .description("Start OpenBot server and web dashboard together")
122
+ .option("-p, --port <number>", "Port to listen on")
123
+ .option("--openai-api-key <key>", "OpenAI API Key")
124
+ .option("--anthropic-api-key <key>", "Anthropic API Key")
125
+ .action(async (options) => {
126
+ const serverArgs = ["openbot", "server"];
127
+ if (options.port)
128
+ serverArgs.push("--port", String(options.port));
129
+ if (options.openaiApiKey)
130
+ serverArgs.push("--openai-api-key", options.openaiApiKey);
131
+ if (options.anthropicApiKey)
132
+ serverArgs.push("--anthropic-api-key", options.anthropicApiKey);
133
+ const serverCommand = serverArgs.map(shellEscape).join(" ");
134
+ await new Promise((resolve, reject) => {
135
+ const child = spawn("npx", [
136
+ "-y",
137
+ "concurrently",
138
+ "--kill-others",
139
+ "--names",
140
+ "SERVER,WEBUI",
141
+ "--prefix",
142
+ "{name}",
143
+ "--prefix-colors",
144
+ "blue.bold,green.bold",
145
+ serverCommand,
146
+ "openbot-web",
147
+ ], { stdio: "inherit" });
148
+ child.on("error", reject);
149
+ child.on("exit", (code) => {
150
+ if (typeof code === "number")
151
+ process.exitCode = code;
152
+ resolve();
153
+ });
154
+ });
155
+ });
99
156
  program
100
157
  .command("add <name>")
101
158
  .description("Add an agent or plugin by name (auto-resolves to GitHub/NPM)")
@@ -103,7 +160,7 @@ program
103
160
  // 1. Try as Agent
104
161
  const agentRepo = `meetopenbot/agent-${name}`;
105
162
  if (checkGitHubRepo(agentRepo)) {
106
- await installAgent(agentRepo);
163
+ await installAgent(agentRepo, `agent-${name}`);
107
164
  return;
108
165
  }
109
166
  // 2. Try as Plugin
@@ -119,13 +176,13 @@ program
119
176
  // Check GitHub Plugin
120
177
  const pluginGhRepo = `meetopenbot/plugin-${name}`;
121
178
  if (checkGitHubRepo(pluginGhRepo)) {
122
- await installPlugin(pluginGhRepo);
179
+ await installPlugin(pluginGhRepo, `plugin-${name}`);
123
180
  return;
124
181
  }
125
182
  // Check NPM Plugin
126
183
  const pluginNpmPkg = `@melony/plugin-${name}`;
127
184
  if (checkNpmPackage(pluginNpmPkg)) {
128
- await installPlugin(pluginNpmPkg);
185
+ await installPlugin(pluginNpmPkg, `plugin-${name}`);
129
186
  return;
130
187
  }
131
188
  console.error(`❌ Could not find agent or plugin named "${name}" in official repositories.`);
@@ -71,7 +71,7 @@ export async function setupPluginRegistry(resolvedBaseDir, model, options) {
71
71
  // ── Custom agents and plugins ────────────────────────────────────
72
72
  const agentsDir = path.join(resolvedBaseDir, "agents");
73
73
  const pluginsDir = path.join(resolvedBaseDir, "plugins");
74
- await discoverPlugins(agentsDir, registry, model, options);
75
74
  await discoverPlugins(pluginsDir, registry, model, options);
75
+ await discoverPlugins(agentsDir, registry, model, options);
76
76
  return registry;
77
77
  }
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { tmpdir } from "node:os";
5
5
  import { resolvePath, DEFAULT_BASE_DIR, loadConfig } from "./config.js";
6
- import { getPluginMetadata, readAgentConfig, ensurePluginReady } from "./registry/plugin-loader.js";
6
+ import { readAgentConfig, ensurePluginReady } from "./registry/plugin-loader.js";
7
7
  const BUILT_IN_PLUGIN_NAMES = new Set(["shell", "file-system", "approval"]);
8
8
  function run(command, args, options) {
9
9
  execFileSync(command, args, {
@@ -15,9 +15,6 @@ function log(message, quiet) {
15
15
  if (!quiet)
16
16
  console.log(message);
17
17
  }
18
- function githubRepoToCloneUrl(repo) {
19
- return `https://github.com/${repo}.git`;
20
- }
21
18
  function getBaseDir() {
22
19
  const cfg = loadConfig();
23
20
  const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
@@ -28,7 +25,8 @@ async function directoryExists(targetPath) {
28
25
  }
29
26
  export function checkGitHubRepo(repo) {
30
27
  try {
31
- run("git", ["ls-remote", githubRepoToCloneUrl(repo)], { quiet: true });
28
+ const url = `https://github.com/${repo}.git`;
29
+ run("git", ["ls-remote", url], { quiet: true });
32
30
  return true;
33
31
  }
34
32
  catch {
@@ -44,7 +42,7 @@ export function checkNpmPackage(pkg) {
44
42
  return false;
45
43
  }
46
44
  }
47
- export function parsePluginInstallSource(source) {
45
+ export function parseSource(source) {
48
46
  const normalized = source.trim();
49
47
  const isGithub = (normalized.includes("/") || normalized.startsWith("github:"))
50
48
  && !normalized.startsWith("/")
@@ -64,26 +62,33 @@ export function parsePluginInstallSource(source) {
64
62
  }
65
63
  return { type: "local", value: path.resolve(normalized) };
66
64
  }
67
- export function parseAgentInstallSource(source) {
68
- const normalized = source.trim();
69
- if (normalized.startsWith("github:")) {
70
- return { type: "github", value: normalized.slice(7) };
71
- }
72
- return { type: "github", value: normalized };
73
- }
74
- export async function installPluginFromSource(source, options = {}) {
65
+ export async function installExtension(type, source, options = {}) {
75
66
  const quiet = !!options.quiet;
76
- const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
77
67
  const baseDir = getBaseDir();
78
- const pluginRoot = path.join(baseDir, "plugins");
79
- await fs.mkdir(pluginRoot, { recursive: true });
68
+ const targetRoot = path.join(baseDir, type === "agent" ? "agents" : "plugins");
69
+ await fs.mkdir(targetRoot, { recursive: true });
70
+ // 1. Determine the folder name (the "id") - ALWAYS based on source or explicit id
71
+ let id = options.id;
72
+ if (!id) {
73
+ if (source.type === "github") {
74
+ id = path.basename(source.value); // e.g. "agent-browser"
75
+ }
76
+ else if (source.type === "npm") {
77
+ id = source.value.split("/").pop(); // e.g. "@melony/plugin-test" -> "plugin-test"
78
+ }
79
+ else {
80
+ id = path.basename(source.value);
81
+ }
82
+ }
83
+ const targetDir = path.join(targetRoot, id);
84
+ const tempDir = path.join(tmpdir(), `openbot-install-${Date.now()}-${id}`);
80
85
  try {
86
+ log(`📦 Installing ${type} "${id}" from ${source.type}...`, quiet);
81
87
  if (source.type === "github") {
82
- log(`📦 Installing plugin from: ${githubRepoToCloneUrl(source.value)}`, quiet);
83
- run("git", ["clone", "--depth", "1", githubRepoToCloneUrl(source.value), tempDir], { quiet });
88
+ const url = `https://github.com/${source.value}.git`;
89
+ run("git", ["clone", "--depth", "1", url, tempDir], { quiet });
84
90
  }
85
91
  else if (source.type === "npm") {
86
- log(`📦 Installing plugin from: ${source.value}`, quiet);
87
92
  await fs.mkdir(tempDir, { recursive: true });
88
93
  run("npm", ["install", source.value, "--prefix", tempDir], { quiet });
89
94
  const pkgFolder = path.join(tempDir, "node_modules", source.value);
@@ -93,55 +98,34 @@ export async function installPluginFromSource(source, options = {}) {
93
98
  await fs.rename(moveTemp, tempDir);
94
99
  }
95
100
  else {
96
- log(`📦 Installing plugin from: ${source.value}`, quiet);
97
101
  await fs.mkdir(tempDir, { recursive: true });
98
102
  await fs.cp(source.value, tempDir, { recursive: true });
99
103
  }
100
- const { name } = await getPluginMetadata(tempDir);
101
- const targetDir = path.join(pluginRoot, name);
102
104
  if (await directoryExists(targetDir)) {
103
- log(`⚠️ Plugin "${name}" already exists. Overwriting...`, quiet);
105
+ log(`⚠️ Removing existing folder: ${targetDir}`, quiet);
104
106
  await fs.rm(targetDir, { recursive: true, force: true });
105
107
  }
106
108
  await fs.rename(tempDir, targetDir);
107
- log(`✅ Moved to: ${targetDir}`, quiet);
108
- log(`⚙️ Preparing plugin "${name}"...`, quiet);
109
+ log(`✅ Installed to: ${targetDir}`, quiet);
110
+ // Prepare dependencies and build
109
111
  await ensurePluginReady(targetDir);
110
- log(`\n🎉 Successfully installed plugin: ${name}`, quiet);
111
- return name;
112
- }
113
- catch (error) {
114
- await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
115
- throw error;
116
- }
117
- }
118
- export async function installAgentFromSource(source, options = {}) {
119
- const quiet = !!options.quiet;
120
- const tempDir = path.join(tmpdir(), `openbot-agent-install-${Date.now()}`);
121
- const baseDir = getBaseDir();
122
- const agentRoot = path.join(baseDir, "agents");
123
- await fs.mkdir(agentRoot, { recursive: true });
124
- try {
125
- log(`🤖 Installing agent from: ${githubRepoToCloneUrl(source.value)}`, quiet);
126
- run("git", ["clone", "--depth", "1", githubRepoToCloneUrl(source.value), tempDir], { quiet });
127
- const config = await readAgentConfig(tempDir);
128
- const name = config.name || path.basename(source.value).replace(/^agent-/, "");
129
- const targetDir = path.join(agentRoot, name);
130
- if (await directoryExists(targetDir)) {
131
- log(`⚠️ Agent "${name}" already exists. Overwriting...`, quiet);
132
- await fs.rm(targetDir, { recursive: true, force: true });
112
+ // If it's an agent, check for missing plugins
113
+ if (type === "agent") {
114
+ await installMissingPluginsFromAgent(targetDir, { quiet });
133
115
  }
134
- await fs.rename(tempDir, targetDir);
135
- log(`✅ Moved to: ${targetDir}`, quiet);
136
- await installMissingPluginsFromAgent(targetDir, { quiet });
137
- log(`\n🎉 Successfully installed agent: ${name}`, quiet);
138
- return name;
116
+ log(`🎉 Successfully installed ${type}: ${id}`, quiet);
117
+ return id;
139
118
  }
140
119
  catch (error) {
141
120
  await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
142
121
  throw error;
143
122
  }
144
123
  }
124
+ // Backward compatibility aliases
125
+ export const installPluginFromSource = (source, opts) => installExtension("plugin", source, opts);
126
+ export const installAgentFromSource = (source, opts) => installExtension("agent", source, opts);
127
+ export const parsePluginInstallSource = parseSource;
128
+ export const parseAgentInstallSource = parseSource;
145
129
  export async function installMissingPluginsFromAgent(agentFolder, options = {}) {
146
130
  const quiet = !!options.quiet;
147
131
  const config = await readAgentConfig(agentFolder);
@@ -150,21 +134,23 @@ export async function installMissingPluginsFromAgent(agentFolder, options = {})
150
134
  const pluginName = typeof pluginItem === "string" ? pluginItem : pluginItem.name;
151
135
  if (!pluginName || BUILT_IN_PLUGIN_NAMES.has(pluginName))
152
136
  continue;
137
+ // Check if it already exists as a folder in plugins/
153
138
  const pluginPath = path.join(baseDir, "plugins", pluginName);
154
- const existsLocally = await directoryExists(pluginPath);
155
- if (existsLocally)
139
+ const prefixedPluginPath = path.join(baseDir, "plugins", `plugin-${pluginName}`);
140
+ if (await (directoryExists(pluginPath)) || await (directoryExists(prefixedPluginPath))) {
156
141
  continue;
142
+ }
157
143
  log(`🔍 Agent needs plugin "${pluginName}". Searching...`, quiet);
158
144
  const ghRepo = `meetopenbot/plugin-${pluginName}`;
159
145
  if (checkGitHubRepo(ghRepo)) {
160
- await installPluginFromSource({ type: "github", value: ghRepo }, { quiet });
146
+ await installExtension("plugin", { type: "github", value: ghRepo }, { quiet });
161
147
  continue;
162
148
  }
163
149
  const npmPkg = `@melony/plugin-${pluginName}`;
164
150
  if (checkNpmPackage(npmPkg)) {
165
- await installPluginFromSource({ type: "npm", value: npmPkg }, { quiet });
151
+ await installExtension("plugin", { type: "npm", value: npmPkg }, { quiet });
166
152
  continue;
167
153
  }
168
- log(`⚠️ Could not find plugin "${pluginName}" for this agent. You may need to install it manually.`, quiet);
154
+ log(`⚠️ Could not find plugin "${pluginName}" for this agent.`, quiet);
169
155
  }
170
156
  }
@@ -67,7 +67,7 @@ export async function installMarketplaceAgent(agentId) {
67
67
  if (agent.source.type !== "github") {
68
68
  throw new Error(`Marketplace agent "${agentId}" has unsupported source type "${agent.source.type}"`);
69
69
  }
70
- const installedName = await installAgentFromSource({ type: "github", value: agent.source.value });
70
+ const installedName = await installAgentFromSource({ type: "github", value: agent.source.value }, { id: agent.id });
71
71
  return { installedName, agent };
72
72
  }
73
73
  export async function installMarketplacePlugin(pluginId) {
@@ -75,6 +75,6 @@ export async function installMarketplacePlugin(pluginId) {
75
75
  const plugin = registry.plugins.find((entry) => entry.id === pluginId);
76
76
  if (!plugin)
77
77
  throw new Error(`Plugin "${pluginId}" was not found in marketplace`);
78
- const installedName = await installPluginFromSource(plugin.source);
78
+ const installedName = await installPluginFromSource(plugin.source, { id: plugin.id });
79
79
  return { installedName, plugin };
80
80
  }
@@ -8,6 +8,13 @@ import { llmPlugin } from "../plugins/llm/index.js";
8
8
  import { createModel } from "../models.js";
9
9
  import { resolvePath, DEFAULT_AGENT_MD } from "../config.js";
10
10
  // ── Helpers ──────────────────────────────────────────────────────────
11
+ function toTitleCaseFromSlug(value) {
12
+ return value
13
+ .split(/[-_]+/)
14
+ .filter(Boolean)
15
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
16
+ .join(" ") || "Agent";
17
+ }
11
18
  async function fileExists(filePath) {
12
19
  return fs.access(filePath).then(() => true).catch(() => false);
13
20
  }
@@ -217,7 +224,11 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
217
224
  const entryData = module.plugin || module.default || module.entry;
218
225
  if (codeAgentDef && typeof codeAgentDef.factory === "function") {
219
226
  const meta = await getPluginMetadata(pluginDir);
220
- const name = codeAgentDef.name || meta.name || "Unnamed Agent";
227
+ const folderName = path.basename(pluginDir);
228
+ let name = codeAgentDef.name || meta.name;
229
+ if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
230
+ name = toTitleCaseFromSlug(folderName);
231
+ }
221
232
  const description = codeAgentDef.description || meta.description || "Code Agent";
222
233
  registry.register({
223
234
  name,
@@ -232,8 +243,13 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
232
243
  }
233
244
  else if (entryData && typeof entryData.factory === "function") {
234
245
  const meta = await getPluginMetadata(pluginDir);
246
+ const folderName = path.basename(pluginDir);
247
+ let name = entryData.name || meta.name;
248
+ if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
249
+ name = toTitleCaseFromSlug(folderName);
250
+ }
235
251
  const pluginEntry = {
236
- name: entryData.name || meta.name || "Unnamed Tool",
252
+ name,
237
253
  description: entryData.description || meta.description || "Tool plugin",
238
254
  type: "tool",
239
255
  plugin: entryData.factory,
@@ -266,7 +282,10 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
266
282
  if (definition && typeof definition.factory === "function") {
267
283
  const config = await readAgentConfig(agentDir);
268
284
  const meta = await getPluginMetadata(agentDir);
269
- const name = config.name || definition.name || meta.name || "Unnamed Agent";
285
+ let name = config.name || definition.name || meta.name;
286
+ if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
287
+ name = toTitleCaseFromSlug(folderName);
288
+ }
270
289
  const description = definition.description || config.description || "TS Agent";
271
290
  registry.register({
272
291
  name,
@@ -284,7 +303,10 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
284
303
  // Declarative Agent — AGENT.md only, auto-wrapped with llmPlugin.
285
304
  const config = await readAgentConfig(agentDir);
286
305
  const meta = await getPluginMetadata(agentDir);
287
- const resolvedName = config.name || meta.name || "Unnamed Agent";
306
+ let resolvedName = config.name || meta.name;
307
+ if (!resolvedName || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(resolvedName)) {
308
+ resolvedName = toTitleCaseFromSlug(folderName);
309
+ }
288
310
  const resolvedDescription = config.description || meta.description || "No description";
289
311
  const agentModel = config.model
290
312
  ? createModel({ ...options, model: config.model })
package/dist/server.js CHANGED
@@ -7,6 +7,7 @@ import { createOpenBot } from "./open-bot.js";
7
7
  import { loadConfig, saveConfig, isConfigured, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
8
8
  import { loadSession, saveSession, logEvent, loadEvents, listSessions } from "./session.js";
9
9
  import { listPlugins } from "./registry/index.js";
10
+ import { readAgentConfig } from "./registry/plugin-loader.js";
10
11
  import { exec } from "node:child_process";
11
12
  import os from "node:os";
12
13
  import path from "node:path";
@@ -779,12 +780,32 @@ export async function startServer(options = {}) {
779
780
  const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
780
781
  const resolvedBaseDir = resolvePath(baseDir);
781
782
  const defaultName = cfg.name || "OpenBot";
783
+ // 1. Resolve agent folder
784
+ let agentFolder = null;
785
+ if (name === defaultName || name === "default") {
786
+ agentFolder = resolvedBaseDir;
787
+ }
788
+ else {
789
+ agentFolder = await resolveAgentFolder(name, resolvedBaseDir);
790
+ }
791
+ // 2. Check for remote image in AGENT.md if folder exists
792
+ if (agentFolder) {
793
+ try {
794
+ const { image } = await readAgentConfig(agentFolder);
795
+ if (image && (image.startsWith("http://") || image.startsWith("https://"))) {
796
+ return res.redirect(image);
797
+ }
798
+ }
799
+ catch {
800
+ // ignore
801
+ }
802
+ }
782
803
  const extensions = [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif"];
783
804
  const fileNames = ["avatar", "icon", "image", "logo"];
784
805
  const searchDirs = [
785
806
  (name === defaultName || name === "default")
786
807
  ? path.join(resolvedBaseDir, "assets")
787
- : path.join(resolvedBaseDir, "agents", name, "assets"),
808
+ : (agentFolder ? path.join(agentFolder, "assets") : path.join(resolvedBaseDir, "agents", name, "assets")),
788
809
  path.join(process.cwd(), "server", "src", "agents", name, "assets"),
789
810
  path.join(process.cwd(), "server", "src", "assets", "agents", name),
790
811
  path.join(process.cwd(), "server", "src", "agents", "assets"),
@@ -793,7 +814,8 @@ export async function startServer(options = {}) {
793
814
  for (const dir of searchDirs) {
794
815
  for (const fileName of fileNames) {
795
816
  for (const ext of extensions) {
796
- const baseName = (dir.endsWith("assets") && !dir.includes(name)) ? name : fileName;
817
+ const isAgentSpecificDir = dir.includes(name) || (agentFolder && dir.includes(agentFolder));
818
+ const baseName = (dir.endsWith("assets") && !isAgentSpecificDir) ? name : fileName;
797
819
  const p = path.join(dir, `${baseName}${ext}`);
798
820
  try {
799
821
  await fs.access(p);
@@ -868,6 +890,7 @@ export async function startServer(options = {}) {
868
890
  console.log(`OpenBot server listening at http://localhost:${PORT}`);
869
891
  console.log(` - Chat endpoint: POST /api/chat`);
870
892
  console.log(` - REST endpoints: /api/config, /api/sessions, /api/agents`);
893
+ console.log(`\n🚀 TIP: Use 'openbot up' to run both the server and web dashboard together.`);
871
894
  if (options.openaiApiKey)
872
895
  console.log(" - Using OpenAI API Key from CLI");
873
896
  if (options.anthropicApiKey)
package/package.json CHANGED
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "private": false,
5
5
  "type": "module",
6
- "scripts": {
7
- "dev": "tsx watch src/cli.ts server",
8
- "build": "tsc",
9
- "start": "node dist/cli.js server"
6
+ "engines": {
7
+ "node": ">=20.12.0"
10
8
  },
11
9
  "bin": {
12
10
  "openbot": "./dist/cli.js"
@@ -33,5 +31,10 @@
33
31
  "@types/node": "^20.10.1",
34
32
  "tsx": "^4.21.0",
35
33
  "typescript": "^5.9.3"
34
+ },
35
+ "scripts": {
36
+ "dev": "tsx watch src/cli.ts server",
37
+ "build": "tsc",
38
+ "start": "node dist/cli.js server"
36
39
  }
37
- }
40
+ }