@matthias-hausberger/beige 0.2.1-beta1 → 1.0.0-beta3

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 (120) hide show
  1. package/config.schema.json +23 -83
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +185 -87
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/loader.d.ts.map +1 -1
  6. package/dist/config/loader.js +9 -49
  7. package/dist/config/loader.js.map +1 -1
  8. package/dist/config/schema.d.ts +18 -61
  9. package/dist/config/schema.d.ts.map +1 -1
  10. package/dist/config/schema.js +46 -71
  11. package/dist/config/schema.js.map +1 -1
  12. package/dist/gateway/agent-manager.d.ts +76 -5
  13. package/dist/gateway/agent-manager.d.ts.map +1 -1
  14. package/dist/gateway/agent-manager.js +400 -46
  15. package/dist/gateway/agent-manager.js.map +1 -1
  16. package/dist/gateway/api.d.ts +2 -2
  17. package/dist/gateway/api.d.ts.map +1 -1
  18. package/dist/gateway/api.js +24 -2
  19. package/dist/gateway/api.js.map +1 -1
  20. package/dist/gateway/error-logger.d.ts +52 -0
  21. package/dist/gateway/error-logger.d.ts.map +1 -0
  22. package/dist/gateway/error-logger.js +213 -0
  23. package/dist/gateway/error-logger.js.map +1 -0
  24. package/dist/gateway/gateway.d.ts +4 -5
  25. package/dist/gateway/gateway.d.ts.map +1 -1
  26. package/dist/gateway/gateway.js +74 -44
  27. package/dist/gateway/gateway.js.map +1 -1
  28. package/dist/gateway/llm-errors.d.ts +54 -0
  29. package/dist/gateway/llm-errors.d.ts.map +1 -0
  30. package/dist/gateway/llm-errors.js +208 -0
  31. package/dist/gateway/llm-errors.js.map +1 -0
  32. package/dist/gateway/policy.d.ts +2 -1
  33. package/dist/gateway/policy.d.ts.map +1 -1
  34. package/dist/gateway/policy.js +12 -3
  35. package/dist/gateway/policy.js.map +1 -1
  36. package/dist/gateway/sessions.d.ts +7 -5
  37. package/dist/gateway/sessions.d.ts.map +1 -1
  38. package/dist/gateway/sessions.js +13 -7
  39. package/dist/gateway/sessions.js.map +1 -1
  40. package/dist/gateway/system-prompt.template.md +7 -5
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +5 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/install.d.ts +2 -3
  46. package/dist/install.d.ts.map +1 -1
  47. package/dist/install.js +14 -65
  48. package/dist/install.js.map +1 -1
  49. package/dist/plugins/context.d.ts +31 -0
  50. package/dist/plugins/context.d.ts.map +1 -0
  51. package/dist/plugins/context.js +222 -0
  52. package/dist/plugins/context.js.map +1 -0
  53. package/dist/plugins/index.d.ts +10 -0
  54. package/dist/plugins/index.d.ts.map +1 -0
  55. package/dist/plugins/index.js +7 -0
  56. package/dist/plugins/index.js.map +1 -0
  57. package/dist/plugins/installer.d.ts +80 -0
  58. package/dist/plugins/installer.d.ts.map +1 -0
  59. package/dist/{tools → plugins}/installer.js +231 -278
  60. package/dist/plugins/installer.js.map +1 -0
  61. package/dist/plugins/loader.d.ts +41 -0
  62. package/dist/plugins/loader.d.ts.map +1 -0
  63. package/dist/plugins/loader.js +136 -0
  64. package/dist/plugins/loader.js.map +1 -0
  65. package/dist/plugins/registry.d.ts +40 -0
  66. package/dist/plugins/registry.d.ts.map +1 -0
  67. package/dist/plugins/registry.js +171 -0
  68. package/dist/plugins/registry.js.map +1 -0
  69. package/dist/plugins/types.d.ts +455 -0
  70. package/dist/plugins/types.d.ts.map +1 -0
  71. package/dist/plugins/types.js +15 -0
  72. package/dist/plugins/types.js.map +1 -0
  73. package/dist/sandbox/manager.d.ts +5 -26
  74. package/dist/sandbox/manager.d.ts.map +1 -1
  75. package/dist/sandbox/manager.js +32 -53
  76. package/dist/sandbox/manager.js.map +1 -1
  77. package/dist/socket/protocol.d.ts +2 -0
  78. package/dist/socket/protocol.d.ts.map +1 -1
  79. package/dist/socket/protocol.js.map +1 -1
  80. package/dist/socket/server.d.ts +3 -1
  81. package/dist/socket/server.d.ts.map +1 -1
  82. package/dist/socket/server.js +5 -1
  83. package/dist/socket/server.js.map +1 -1
  84. package/dist/test/fixtures.d.ts +9 -7
  85. package/dist/test/fixtures.d.ts.map +1 -1
  86. package/dist/test/fixtures.js +17 -29
  87. package/dist/test/fixtures.js.map +1 -1
  88. package/dist/tools/runner.d.ts +26 -27
  89. package/dist/tools/runner.d.ts.map +1 -1
  90. package/dist/tools/runner.js +114 -11
  91. package/dist/tools/runner.js.map +1 -1
  92. package/dist/types/session.d.ts +17 -0
  93. package/dist/types/session.d.ts.map +1 -1
  94. package/dist/types/session.js +10 -14
  95. package/dist/types/session.js.map +1 -1
  96. package/package.json +2 -4
  97. package/sandbox/Dockerfile +6 -0
  98. package/sandbox/tool-client.ts +19 -0
  99. package/dist/channels/registry.d.ts +0 -14
  100. package/dist/channels/registry.d.ts.map +0 -1
  101. package/dist/channels/registry.js +0 -14
  102. package/dist/channels/registry.js.map +0 -1
  103. package/dist/channels/telegram.d.ts +0 -92
  104. package/dist/channels/telegram.d.ts.map +0 -1
  105. package/dist/channels/telegram.js +0 -469
  106. package/dist/channels/telegram.js.map +0 -1
  107. package/dist/tools/installer.d.ts +0 -93
  108. package/dist/tools/installer.d.ts.map +0 -1
  109. package/dist/tools/installer.js.map +0 -1
  110. package/dist/tools/registry.d.ts +0 -20
  111. package/dist/tools/registry.d.ts.map +0 -1
  112. package/dist/tools/registry.js +0 -124
  113. package/dist/tools/registry.js.map +0 -1
  114. package/tools/README.md +0 -1
  115. package/tools/kv/README.md +0 -150
  116. package/tools/kv/index.ts +0 -154
  117. package/tools/kv/tool.json +0 -23
  118. package/tools/message/README.md +0 -53
  119. package/tools/message/index.ts +0 -183
  120. package/tools/message/tool.json +0 -11
@@ -5,7 +5,6 @@
5
5
  "type": "object",
6
6
  "required": [
7
7
  "llm",
8
- "tools",
9
8
  "agents"
10
9
  ],
11
10
  "properties": {
@@ -59,37 +58,28 @@
59
58
  }
60
59
  }
61
60
  },
62
- "tools": {
63
- "description": "Tool registry. Each key becomes the tool name used in agent configs and /tools/bin/.",
61
+ "plugins": {
62
+ "description": "Plugin registry. Each key becomes the plugin name. Plugins can provide tools, channels, hooks, and skills.",
64
63
  "type": "object",
65
64
  "patternProperties": {
66
65
  "^(.*)$": {
67
- "title": "ToolConfig",
66
+ "title": "PluginConfig",
68
67
  "type": "object",
69
68
  "properties": {
70
69
  "path": {
71
- "description": "Path to the tool package directory. Resolved relative to the config file unless absolute. Auto-resolved for tools installed via 'beige tools install'.",
70
+ "description": "Path to the plugin package directory. Resolved relative to the config file unless absolute.",
72
71
  "type": "string"
73
72
  },
74
- "target": {
75
- "description": "\"gateway\" runs the handler on the host process. \"sandbox\" is planned. Auto-resolved for tools installed via 'beige tools install'.",
76
- "anyOf": [
77
- {
78
- "const": "gateway",
79
- "type": "string"
80
- },
81
- {
82
- "const": "sandbox",
83
- "type": "string"
84
- }
85
- ]
86
- },
87
73
  "config": {
88
- "description": "Arbitrary config object passed to createHandler(config) at startup",
74
+ "description": "Arbitrary config object passed to createPlugin(config, ctx) at startup",
89
75
  "type": "object",
90
76
  "patternProperties": {
91
77
  "^(.*)$": {}
92
78
  }
79
+ },
80
+ "_source": {
81
+ "description": "Install source (e.g. 'npm:@scope/package'). Written by 'beige plugins install', used by 'beige plugins update'. Do not edit manually.",
82
+ "type": "string"
93
83
  }
94
84
  }
95
85
  }
@@ -166,6 +156,11 @@
166
156
  "type": "string"
167
157
  }
168
158
  ]
159
+ },
160
+ "compactionThreshold": {
161
+ "description": "Context token count at which auto-compaction triggers for this model. Must be less than the model's context window. When set, compaction kicks in earlier than the default (contextWindow − 16384 tokens). Example: set to 100000 on a 200k-window model to compact at 100k tokens.",
162
+ "minimum": 1,
163
+ "type": "number"
169
164
  }
170
165
  }
171
166
  },
@@ -212,19 +207,24 @@
212
207
  "type": "string"
213
208
  }
214
209
  ]
210
+ },
211
+ "compactionThreshold": {
212
+ "description": "Context token count at which auto-compaction triggers for this model. Must be less than the model's context window. When set, compaction kicks in earlier than the default (contextWindow − 16384 tokens). Example: set to 100000 on a 200k-window model to compact at 100k tokens.",
213
+ "minimum": 1,
214
+ "type": "number"
215
215
  }
216
216
  }
217
217
  }
218
218
  },
219
219
  "tools": {
220
- "description": "Tool names from the tools registry that this agent can invoke",
220
+ "description": "Tool names registered by plugins that this agent can invoke",
221
221
  "type": "array",
222
222
  "items": {
223
223
  "type": "string"
224
224
  }
225
225
  },
226
226
  "skills": {
227
- "description": "Skill names from the skills registry mounted into this agent's sandbox",
227
+ "description": "Skill names (from skills registry or plugin-registered) mounted into this agent's sandbox",
228
228
  "type": "array",
229
229
  "items": {
230
230
  "type": "string"
@@ -262,8 +262,8 @@
262
262
  }
263
263
  }
264
264
  },
265
- "toolConfigs": {
266
- "description": "Per-agent tool config overrides. Keys must reference tools in this agent's tools array. Values are deep-merged with the top-level tool config from config.tools.<name>.config.",
265
+ "pluginConfigs": {
266
+ "description": "Per-agent plugin config overrides. Keys must reference plugins in the plugins registry. Values are deep-merged with the top-level plugin config from config.plugins.<name>.config.",
267
267
  "type": "object",
268
268
  "patternProperties": {
269
269
  "^(.*)$": {
@@ -295,66 +295,6 @@
295
295
  "type": "string"
296
296
  }
297
297
  }
298
- },
299
- "channels": {
300
- "title": "ChannelsConfig",
301
- "type": "object",
302
- "properties": {
303
- "telegram": {
304
- "title": "TelegramChannelConfig",
305
- "type": "object",
306
- "required": [
307
- "enabled",
308
- "token",
309
- "allowedUsers",
310
- "agentMapping"
311
- ],
312
- "properties": {
313
- "enabled": {
314
- "description": "Set to true to activate the Telegram bot",
315
- "type": "boolean"
316
- },
317
- "token": {
318
- "description": "Telegram bot token from @BotFather. Use \"${TELEGRAM_BOT_TOKEN}\".",
319
- "type": "string"
320
- },
321
- "allowedUsers": {
322
- "description": "Telegram user IDs permitted to interact with the bot. All others are silently ignored.",
323
- "type": "array",
324
- "items": {
325
- "type": "number"
326
- }
327
- },
328
- "agentMapping": {
329
- "description": "Maps Telegram chats to agents",
330
- "type": "object",
331
- "required": [
332
- "default"
333
- ],
334
- "properties": {
335
- "default": {
336
- "description": "Agent name that handles all incoming messages",
337
- "type": "string"
338
- }
339
- }
340
- },
341
- "defaults": {
342
- "title": "ChannelDefaultSettings",
343
- "type": "object",
344
- "properties": {
345
- "verbose": {
346
- "description": "Send a notification for every tool call the agent makes (e.g. 🔧 exec: ls). Default: false",
347
- "type": "boolean"
348
- },
349
- "streaming": {
350
- "description": "Stream responses to the user in real-time. Default: true",
351
- "type": "boolean"
352
- }
353
- }
354
- }
355
- }
356
- }
357
- }
358
298
  }
359
299
  }
360
300
  }
package/dist/cli.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * beige gateway logs Show gateway logs
13
13
  * beige gateway logs -f Follow gateway logs (tail -f)
14
14
  * beige tui [agent] Connect to a running gateway via TUI
15
- * beige tools <command> Manage tools (install, list, update, remove)
15
+ * beige plugins <command> Manage plugins (install, list, update, remove)
16
16
  * beige --config <path> Use a specific config file
17
17
  *
18
18
  * Shell 1: beige ← starts gateway daemon
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * beige gateway logs Show gateway logs
13
13
  * beige gateway logs -f Follow gateway logs (tail -f)
14
14
  * beige tui [agent] Connect to a running gateway via TUI
15
- * beige tools <command> Manage tools (install, list, update, remove)
15
+ * beige plugins <command> Manage plugins (install, list, update, remove)
16
16
  * beige --config <path> Use a specific config file
17
17
  *
18
18
  * Shell 1: beige ← starts gateway daemon
@@ -24,7 +24,7 @@ import { spawn } from "child_process";
24
24
  import { watch } from "fs";
25
25
  import { runSetup } from "./install.js";
26
26
  import { beigeDir } from "./paths.js";
27
- import { installTools, removeTool, updateTool, updateAllTools, listInstalledTools, readMetaFile, } from "./tools/installer.js";
27
+ import { installPlugins, removePlugin, updatePlugin, updateAllPlugins, listPluginsFromConfig, } from "./plugins/installer.js";
28
28
  // ── Timestamp helpers ────────────────────────────────────────────────
29
29
  /** Returns a local-time HH:MM:SS prefix, e.g. "14:03:07". */
30
30
  function timestampPrefix() {
@@ -34,6 +34,8 @@ function timestampPrefix() {
34
34
  const ss = String(d.getSeconds()).padStart(2, "0");
35
35
  return `${hh}:${mm}:${ss}`;
36
36
  }
37
+ // Save original stderr.write before wrapping (for immediate flush on shutdown)
38
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
37
39
  /**
38
40
  * Wraps process.stdout.write / process.stderr.write so that every line
39
41
  * written by the gateway process is prefixed with an HH:MM:SS timestamp.
@@ -300,13 +302,40 @@ async function cmdGatewayStart(configPath, foreground, timeoutMs) {
300
302
  const config = loadConfig(configPath);
301
303
  const { Gateway } = await import("./gateway/gateway.js");
302
304
  const gateway = new Gateway(config, configPath);
303
- const shutdown = async () => {
304
- console.log("\n[BEIGE] Shutting down...");
305
- await gateway.stop();
306
- process.exit(0);
305
+ let isShuttingDown = false;
306
+ const shutdown = async (signal) => {
307
+ // Prevent duplicate shutdowns
308
+ if (isShuttingDown)
309
+ return;
310
+ isShuttingDown = true;
311
+ // Use original stderr.write to bypass timestamp buffering for immediate feedback
312
+ originalStderrWrite(`\n[BEIGE] Received ${signal} - Shutting down...\n`);
313
+ // Force exit after 10 seconds if shutdown hangs
314
+ const timeout = setTimeout(() => {
315
+ originalStderrWrite("[BEIGE] Shutdown timed out, forcing exit...\n");
316
+ process.exit(1);
317
+ }, 10000);
318
+ try {
319
+ await gateway.stop();
320
+ clearTimeout(timeout);
321
+ originalStderrWrite("[BEIGE] Shutdown complete\n");
322
+ process.exit(0);
323
+ }
324
+ catch (err) {
325
+ clearTimeout(timeout);
326
+ originalStderrWrite(`[BEIGE] Shutdown error: ${err.message}\n`);
327
+ process.exit(1);
328
+ }
307
329
  };
308
- process.on("SIGINT", shutdown);
309
- process.on("SIGTERM", shutdown);
330
+ // Log that signal handlers are being registered (helps debug signal issues)
331
+ originalStderrWrite("[BEIGE] Registering signal handlers...\n");
332
+ // Register handlers with explicit listener count check
333
+ process.on("SIGINT", () => shutdown("SIGINT"));
334
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
335
+ // Verify handlers are registered
336
+ const sigintListeners = process.listenerCount("SIGINT");
337
+ const sigtermListeners = process.listenerCount("SIGTERM");
338
+ originalStderrWrite(`[BEIGE] Signal handlers registered (SIGINT: ${sigintListeners}, SIGTERM: ${sigtermListeners})\n`);
310
339
  // SIGHUP → graceful in-place restart (reload config, recreate sandboxes)
311
340
  process.on("SIGHUP", () => {
312
341
  console.log("[BEIGE] Received SIGHUP — restarting gateway...");
@@ -341,27 +370,84 @@ function cmdGatewayStop() {
341
370
  process.exit(1);
342
371
  }
343
372
  }
344
- function cmdGatewayRestart() {
373
+ async function cmdGatewayRestart(configPath, timeoutMs) {
345
374
  const pid = readPid();
346
- if (pid === null) {
347
- console.log("[BEIGE] No PID file found — gateway is not running");
348
- console.log("[BEIGE] Run 'beige gateway start' to start it");
349
- process.exit(1);
350
- }
351
- if (!isRunning(pid)) {
352
- console.log(`[BEIGE] Gateway (PID ${pid}) is not running`);
353
- console.log("[BEIGE] Run 'beige gateway start' to start it");
354
- process.exit(1);
375
+ // If not running, just start it
376
+ if (pid === null || !isRunning(pid)) {
377
+ console.log("[BEIGE] Gateway is not running starting it...");
378
+ await cmdGatewayStart(configPath, false, timeoutMs);
379
+ return;
355
380
  }
381
+ // Gateway is running — perform graceful restart
382
+ console.log(`[BEIGE] Restarting gateway (PID ${pid})...`);
383
+ const { loadConfig } = await import("./config/loader.js");
384
+ const config = loadConfig(configPath);
385
+ const port = config.gateway?.port ?? 7433;
386
+ const host = config.gateway?.host ?? "127.0.0.1";
387
+ const healthUrl = `http://${host}:${port}/api/health`;
388
+ // Send SIGHUP to trigger restart
356
389
  try {
357
390
  process.kill(pid, "SIGHUP");
358
- console.log(`[BEIGE] Sent SIGHUP to gateway (PID ${pid}) — graceful restart initiated`);
359
- console.log(`[BEIGE] Follow progress with: beige gateway logs -f`);
360
391
  }
361
392
  catch (err) {
362
393
  console.error(`[BEIGE] Failed to signal gateway (PID ${pid}):`, err);
363
394
  process.exit(1);
364
395
  }
396
+ // Wait for restart to complete
397
+ const success = await waitForGatewayRestart(pid, healthUrl, timeoutMs);
398
+ if (success) {
399
+ console.log(`[BEIGE] Gateway restarted successfully`);
400
+ }
401
+ else {
402
+ console.error(`[BEIGE] Gateway restart timed out after ${timeoutMs / 1000}s`);
403
+ console.error(`[BEIGE] Check logs: ${getLogFile()}`);
404
+ process.exit(1);
405
+ }
406
+ }
407
+ /**
408
+ * Wait for gateway restart to complete.
409
+ *
410
+ * The restart cycle is: healthy → unhealthy (teardown) → healthy (startup)
411
+ * We need to see it go from healthy to unhealthy and back to healthy.
412
+ */
413
+ async function waitForGatewayRestart(childPid, healthUrl, timeoutMs) {
414
+ const startTime = Date.now();
415
+ const pollIntervalMs = 500;
416
+ let seenHealthy = false;
417
+ let seenUnhealthy = false;
418
+ const checkHealth = async () => {
419
+ try {
420
+ const res = await fetch(healthUrl, { method: "GET", signal: AbortSignal.timeout(2000) });
421
+ return res.ok;
422
+ }
423
+ catch {
424
+ return false;
425
+ }
426
+ };
427
+ while (Date.now() - startTime < timeoutMs) {
428
+ // Check if child process exited unexpectedly
429
+ if (!isRunning(childPid)) {
430
+ console.log("[BEIGE] Gateway process exited during restart");
431
+ return false;
432
+ }
433
+ const healthy = await checkHealth();
434
+ if (healthy) {
435
+ if (seenUnhealthy) {
436
+ // We saw it go down and come back up - restart complete!
437
+ return true;
438
+ }
439
+ seenHealthy = true;
440
+ }
441
+ else {
442
+ if (seenHealthy) {
443
+ // We saw it go down after being up - restart in progress
444
+ seenUnhealthy = true;
445
+ console.log("[BEIGE] Gateway tearing down...");
446
+ }
447
+ }
448
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
449
+ }
450
+ return false;
365
451
  }
366
452
  function cmdGatewayStatus() {
367
453
  const pid = readPid();
@@ -419,7 +505,7 @@ Usage:
419
505
  beige setup First-time setup (copies tools, writes default config)
420
506
  beige gateway <command> Manage the gateway daemon
421
507
  beige tui [agent] Connect TUI to running gateway
422
- beige tools <command> Manage tools (install, list, update, remove)
508
+ beige plugins <command> Manage plugins (install, list, update, remove)
423
509
 
424
510
  Options:
425
511
  -c, --config <path> Config file (default: ~/.beige/config.json5)
@@ -428,26 +514,26 @@ Options:
428
514
  -h, --help Show this help
429
515
 
430
516
  Run 'beige gateway' for gateway-specific commands.
431
- Run 'beige tools' for tool management commands.
517
+ Run 'beige plugins' for plugin management commands.
432
518
  `);
433
519
  }
434
- function printToolsHelp() {
520
+ function printPluginsHelp() {
435
521
  console.log(`
436
- Beige — Tool commands
522
+ Beige — Plugin commands
437
523
 
438
524
  Usage:
439
- beige tools install <source> Install tools from a source
440
- beige tools list List installed tools
441
- beige tools remove <name> Remove an installed tool
442
- beige tools update [name] Update a tool (or all tools)
525
+ beige plugins install <source> Install plugins from a source
526
+ beige plugins list List installed plugins
527
+ beige plugins remove <name> Remove an installed plugin
528
+ beige plugins update [name] Update a plugin (or all plugins)
443
529
 
444
530
  Install sources:
445
531
  npm:@scope/package NPM package (latest)
446
532
  npm:@scope/package@1.2.3 NPM package (specific version)
447
- github:owner/repo GitHub repository (all tools)
448
- github:owner/repo/path/to/tool Single tool from a GitHub subfolder
533
+ github:owner/repo GitHub repository (all plugins)
534
+ github:owner/repo/path/to/plugin Single plugin from a GitHub subfolder
449
535
  github:owner/repo#tag GitHub repository at a tag/branch
450
- ./path/to/tool Local directory
536
+ ./path/to/plugin Local directory
451
537
 
452
538
  Options:
453
539
  --force, -f Force install even with conflicts
@@ -462,14 +548,14 @@ Usage:
462
548
  beige gateway start Start the gateway daemon
463
549
  beige gateway start --foreground Start the gateway in the foreground
464
550
  beige gateway stop Stop the gateway daemon
465
- beige gateway restart Gracefully restart the gateway (drain, reload config, recreate sandboxes)
551
+ beige gateway restart Gracefully restart the gateway (or start if not running)
466
552
  beige gateway status Show gateway daemon status
467
553
  beige gateway logs Show gateway logs
468
554
  beige gateway logs -f Follow gateway logs
469
555
 
470
556
  Options:
471
557
  -c, --config <path> Config file (default: ~/.beige/config.json5)
472
- --timeout <seconds> Startup wait timeout in seconds (default: 60, 0 = indefinite)
558
+ --timeout <seconds> Startup/restart wait timeout in seconds (default: 60, 0 = indefinite)
473
559
  -h, --help Show this help
474
560
  `);
475
561
  }
@@ -527,8 +613,21 @@ function parseArgs() {
527
613
  }
528
614
  if (sub === "stop")
529
615
  return { kind: "gateway-stop" };
530
- if (sub === "restart")
531
- return { kind: "gateway-restart" };
616
+ if (sub === "restart") {
617
+ let timeoutMs = 60000;
618
+ const timeoutIdx = rest.findIndex((a) => a.startsWith("--timeout"));
619
+ if (timeoutIdx !== -1) {
620
+ const arg = rest[timeoutIdx];
621
+ const raw = arg.includes("=") ? arg.split("=")[1] : rest[timeoutIdx + 1];
622
+ const parsed = Number(raw);
623
+ if (isNaN(parsed) || parsed < 0) {
624
+ console.error(`[BEIGE] Invalid --timeout value: ${raw}. Must be a non-negative number (0 = indefinite).`);
625
+ process.exit(1);
626
+ }
627
+ timeoutMs = parsed * 1000;
628
+ }
629
+ return { kind: "gateway-restart", timeoutMs };
630
+ }
532
631
  if (sub === "status")
533
632
  return { kind: "gateway-status" };
534
633
  if (sub === "logs") {
@@ -546,37 +645,37 @@ function parseArgs() {
546
645
  const force = args.includes("--force") || args.includes("-f");
547
646
  return { kind: "setup", force };
548
647
  }
549
- if (cmd === "tools") {
648
+ if (cmd === "plugins") {
550
649
  if (!sub || sub === "--help" || sub === "-h") {
551
- printToolsHelp();
650
+ printPluginsHelp();
552
651
  process.exit(0);
553
652
  }
554
653
  if (sub === "install") {
555
654
  const source = rest[0];
556
655
  if (!source || source === "--help" || source === "-h") {
557
- printToolsHelp();
656
+ printPluginsHelp();
558
657
  process.exit(source === "--help" || source === "-h" ? 0 : 1);
559
658
  }
560
659
  const force = rest.includes("--force") || rest.includes("-f");
561
660
  return { kind: "install", source, force };
562
661
  }
563
662
  if (sub === "list") {
564
- return { kind: "tools-list" };
663
+ return { kind: "plugins-list" };
565
664
  }
566
665
  if (sub === "remove") {
567
666
  const name = rest[0];
568
667
  if (!name) {
569
- console.error("[BEIGE] Missing tool name. Usage: beige tools remove <name>");
668
+ console.error("[BEIGE] Missing plugin name. Usage: beige plugins remove <name>");
570
669
  process.exit(1);
571
670
  }
572
- return { kind: "tools-remove", name };
671
+ return { kind: "plugins-remove", name };
573
672
  }
574
673
  if (sub === "update") {
575
- const name = rest[0]; // optional — if omitted, update all
576
- return { kind: "tools-update", name };
674
+ const name = rest[0];
675
+ return { kind: "plugins-update", name };
577
676
  }
578
- console.error(`[BEIGE] Unknown tools subcommand: ${sub}`);
579
- printToolsHelp();
677
+ console.error(`[BEIGE] Unknown plugins subcommand: ${sub}`);
678
+ printPluginsHelp();
580
679
  process.exit(1);
581
680
  }
582
681
  console.error(`[BEIGE] Unknown command: ${cmd}`);
@@ -602,7 +701,7 @@ else if (mode.kind === "gateway-stop") {
602
701
  cmdGatewayStop();
603
702
  }
604
703
  else if (mode.kind === "gateway-restart") {
605
- cmdGatewayRestart();
704
+ await cmdGatewayRestart(configPath, mode.timeoutMs);
606
705
  }
607
706
  else if (mode.kind === "gateway-status") {
608
707
  cmdGatewayStatus();
@@ -645,21 +744,21 @@ else if (mode.kind === "tui") {
645
744
  else if (mode.kind === "install") {
646
745
  await cmdInstall(mode.source, mode.force);
647
746
  }
648
- else if (mode.kind === "tools-list") {
649
- cmdToolsList();
747
+ else if (mode.kind === "plugins-list") {
748
+ cmdPluginsList();
650
749
  }
651
- else if (mode.kind === "tools-remove") {
652
- cmdToolsRemove(mode.name);
750
+ else if (mode.kind === "plugins-remove") {
751
+ cmdPluginsRemove(mode.name);
653
752
  }
654
- else if (mode.kind === "tools-update") {
655
- await cmdToolsUpdate(mode.name);
753
+ else if (mode.kind === "plugins-update") {
754
+ await cmdPluginsUpdate(mode.name);
656
755
  }
657
756
  async function cmdInstall(source, force) {
658
757
  console.log(`[BEIGE] Installing from: ${source}`);
659
- const result = await installTools(source, { force });
758
+ const result = await installPlugins(source, { force });
660
759
  if (!result.success) {
661
760
  if (result.conflicts && result.conflicts.length > 0) {
662
- console.error("[BEIGE] Tool name conflicts detected:");
761
+ console.error("[BEIGE] Plugin name conflicts detected:");
663
762
  for (const conflict of result.conflicts) {
664
763
  console.error(` - ${conflict}`);
665
764
  }
@@ -670,48 +769,48 @@ async function cmdInstall(source, force) {
670
769
  }
671
770
  process.exit(1);
672
771
  }
673
- if (result.tools && result.tools.length > 0) {
674
- console.log(`[BEIGE] Installed ${result.tools.length} tool(s):`);
675
- for (const tool of result.tools) {
676
- console.log(` - ${tool.name}: ${tool.manifest.description}`);
772
+ if (result.plugins && result.plugins.length > 0) {
773
+ console.log(`[BEIGE] Installed ${result.plugins.length} plugin(s) and added to config.json5:`);
774
+ for (const plugin of result.plugins) {
775
+ console.log(` - ${plugin.name}: ${plugin.manifest.description}`);
677
776
  }
678
- console.log(`\n[BEIGE] Add tools to your agent's 'tools' array to enable them.`);
777
+ console.log(`\n[BEIGE] Add plugin tools to your agent's 'tools' array and restart the gateway.`);
778
+ console.log(`[BEIGE] Review config.json5 to fill in any required config values (e.g. API keys).`);
679
779
  }
680
780
  }
681
- function cmdToolsList() {
682
- const tools = listInstalledTools();
683
- if (tools.length === 0) {
684
- console.log("[BEIGE] No tools installed.");
685
- console.log("\n[BEIGE] Install tools with: beige tools install <source>");
781
+ function cmdPluginsList() {
782
+ const plugins = listPluginsFromConfig();
783
+ if (plugins.length === 0) {
784
+ console.log("[BEIGE] No plugins in config.");
785
+ console.log("\n[BEIGE] Install plugins with: beige plugins install <source>");
686
786
  return;
687
787
  }
688
- console.log("[BEIGE] Installed tools:\n");
689
- for (const tool of tools) {
690
- const meta = readMetaFile(tool.name);
691
- const source = meta?.source ?? "unknown";
692
- console.log(` ${tool.name}`);
693
- console.log(` ${tool.manifest.description}`);
694
- console.log(` Source: ${source}`);
695
- if (meta?.package) {
696
- console.log(` Package: ${meta.package}`);
697
- }
788
+ console.log("[BEIGE] Plugins in config.json5:\n");
789
+ for (const plugin of plugins) {
790
+ console.log(` ${plugin.name}`);
791
+ if (plugin.path)
792
+ console.log(` Path: ${plugin.path}`);
793
+ if (plugin.source)
794
+ console.log(` Source: ${plugin.source}`);
795
+ if (plugin.hasConfig)
796
+ console.log(` Config: ✓ (has config values)`);
698
797
  console.log();
699
798
  }
700
799
  }
701
- function cmdToolsRemove(name) {
702
- const result = removeTool(name);
800
+ function cmdPluginsRemove(name) {
801
+ const result = removePlugin(name);
703
802
  if (!result.success) {
704
803
  console.error(`[BEIGE] ${result.error}`);
705
804
  process.exit(1);
706
805
  }
707
- console.log(`[BEIGE] Removed tool: ${name}`);
806
+ console.log(`[BEIGE] Removed plugin '${name}' from config.json5 and disk.`);
708
807
  }
709
- async function cmdToolsUpdate(name) {
808
+ async function cmdPluginsUpdate(name) {
710
809
  if (name) {
711
- console.log(`[BEIGE] Updating tool: ${name}`);
712
- const result = await updateTool(name);
810
+ console.log(`[BEIGE] Updating plugin: ${name}`);
811
+ const result = await updatePlugin(name);
713
812
  if (result.success) {
714
- console.log(`[BEIGE] Updated ${result.tools?.length ?? 0} tool(s) from same source.`);
813
+ console.log(`[BEIGE] Updated ${result.plugins?.length ?? 0} plugin(s) from same source.`);
715
814
  }
716
815
  else {
717
816
  console.error(`[BEIGE] Failed to update: ${result.error}`);
@@ -719,14 +818,13 @@ async function cmdToolsUpdate(name) {
719
818
  }
720
819
  return;
721
820
  }
722
- // Update all
723
- const tools = listInstalledTools();
724
- if (tools.length === 0) {
725
- console.log("[BEIGE] No tools installed.");
821
+ const plugins = listPluginsFromConfig();
822
+ if (plugins.length === 0) {
823
+ console.log("[BEIGE] No plugins in config.");
726
824
  return;
727
825
  }
728
- console.log(`[BEIGE] Updating all installed tools...\n`);
729
- const result = await updateAllTools();
826
+ console.log(`[BEIGE] Updating all plugins with _source...\n`);
827
+ const result = await updateAllPlugins();
730
828
  console.log(`\n[BEIGE] Update complete: ${result.updated.length} updated, ${result.failed.length} failed.`);
731
829
  for (const f of result.failed) {
732
830
  console.error(` Failed: ${f.source} — ${f.error}`);