@omnixal/openclaw-nats-plugin 0.2.2 → 0.2.4

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.
@@ -1,9 +1,42 @@
1
1
  import { mkdirSync, cpSync, writeFileSync, existsSync } from 'node:fs';
2
2
  import { execFileSync } from 'node:child_process';
3
3
  import { join, dirname } from 'node:path';
4
- import { PLUGIN_DIR, DOCKER_DIR, STATE_FILE, type PluginState } from './paths';
4
+ import { PLUGIN_DIR, DOCKER_DIR, DASHBOARD_DIR, STATE_FILE, type PluginState } from './paths';
5
5
  import { generateApiKey, writeEnvVariables } from './env-writer';
6
6
 
7
+ /**
8
+ * Try to copy dashboard dist into a running OpenClaw container's volume.
9
+ * This handles the case where setup runs on the host but OpenClaw is in Docker.
10
+ */
11
+ function copyDashboardToContainer(dashboardDir: string): void {
12
+ if (!existsSync(join(dashboardDir, 'index.html'))) return;
13
+
14
+ const candidates = ['openclaw-gateway', 'openclaw-cli'];
15
+ for (const container of candidates) {
16
+ try {
17
+ const status = execFileSync(
18
+ 'docker', ['inspect', '--format={{.State.Running}}', container],
19
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
20
+ ).trim();
21
+ if (status !== 'true') continue;
22
+
23
+ const home = execFileSync(
24
+ 'docker', ['exec', container, 'sh', '-c', 'echo $HOME'],
25
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
26
+ ).trim();
27
+
28
+ const remotePath = `${home}/.openclaw/nats-plugin/dashboard`;
29
+ execFileSync('docker', ['exec', container, 'mkdir', '-p', remotePath]);
30
+ execFileSync('docker', ['cp', `${dashboardDir}/.`, `${container}:${remotePath}/`]);
31
+ console.log(`Dashboard copied into container '${container}' at ${remotePath}`);
32
+ return;
33
+ } catch {
34
+ // Container not found or not running, try next
35
+ }
36
+ }
37
+ console.log('Note: No running OpenClaw container found. Dashboard will be served after setup runs inside the container.');
38
+ }
39
+
7
40
  export async function dockerSetup(): Promise<void> {
8
41
  console.log('Setting up NATS plugin (Docker mode)...\n');
9
42
 
@@ -40,7 +73,10 @@ export async function dockerSetup(): Promise<void> {
40
73
  NATS_SERVERS: 'nats://127.0.0.1:4222',
41
74
  });
42
75
 
43
- // 5. Save state
76
+ // 5. Copy dashboard dist into OpenClaw container (host→container bridge)
77
+ copyDashboardToContainer(DASHBOARD_DIR);
78
+
79
+ // 6. Save state
44
80
  const state: PluginState = {
45
81
  runtime: 'docker',
46
82
  installedAt: new Date().toISOString(),
package/cli/paths.ts CHANGED
@@ -9,6 +9,7 @@ export const DATA_DIR = join(PLUGIN_DIR, 'data');
9
9
  export const JETSTREAM_DIR = join(DATA_DIR, 'jetstream');
10
10
  export const NATS_SERVER_BIN = join(BIN_DIR, 'nats-server');
11
11
  export const NATS_CONF = join(PLUGIN_DIR, 'nats-server.conf');
12
+ export const DASHBOARD_DIR = join(PLUGIN_DIR, 'dashboard');
12
13
  export const DOCKER_DIR = join(PLUGIN_DIR, 'docker');
13
14
  export const OPENCLAW_ENV = join(OPENCLAW_DIR, '.env');
14
15
  export const STATE_FILE = join(PLUGIN_DIR, 'state.json');
package/cli/setup.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, cpSync, mkdirSync } from 'node:fs';
2
2
  import { execFileSync } from 'node:child_process';
3
3
  import { join, dirname } from 'node:path';
4
- import { STATE_FILE, type PluginState } from './paths';
4
+ import { STATE_FILE, DASHBOARD_DIR, type PluginState } from './paths';
5
5
  import { detectRuntime, type Runtime } from './detect-runtime';
6
6
  import { bunSetup } from './bun-setup';
7
7
  import { dockerSetup } from './docker-setup';
@@ -10,9 +10,10 @@ const PLUGIN_ROOT = join(dirname(new URL(import.meta.url).pathname), '..');
10
10
 
11
11
  export function buildDashboard(): void {
12
12
  const dashboardDir = join(PLUGIN_ROOT, 'dashboard');
13
- const distIndex = join(dashboardDir, 'dist', 'index.html');
13
+ const stableDist = DASHBOARD_DIR; // ~/.openclaw/nats-plugin/dashboard/
14
14
 
15
- if (existsSync(distIndex)) {
15
+ // Already installed to stable location
16
+ if (existsSync(join(stableDist, 'index.html'))) {
16
17
  console.log('Dashboard already built, skipping.');
17
18
  return;
18
19
  }
@@ -23,8 +24,23 @@ export function buildDashboard(): void {
23
24
  }
24
25
 
25
26
  console.log('Building dashboard...');
26
- execFileSync('bun', ['install', '--frozen-lockfile'], { cwd: dashboardDir, stdio: 'inherit' });
27
- execFileSync('bun', ['run', 'build'], { cwd: dashboardDir, stdio: 'inherit' });
27
+ const hasBun = (() => { try { execFileSync('bun', ['--version'], { stdio: 'pipe' }); return true; } catch { return false; } })();
28
+ if (hasBun) {
29
+ execFileSync('bun', ['install', '--frozen-lockfile'], { cwd: dashboardDir, stdio: 'inherit' });
30
+ execFileSync('bun', ['run', 'build'], { cwd: dashboardDir, stdio: 'inherit' });
31
+ } else {
32
+ execFileSync('npm', ['install'], { cwd: dashboardDir, stdio: 'inherit' });
33
+ execFileSync('npx', ['vite', 'build'], { cwd: dashboardDir, stdio: 'inherit' });
34
+ }
35
+
36
+ // Copy built dist to stable location so the plugin can find it at runtime
37
+ const builtDist = join(dashboardDir, 'dist');
38
+ if (existsSync(builtDist)) {
39
+ mkdirSync(stableDist, { recursive: true });
40
+ cpSync(builtDist, stableDist, { recursive: true });
41
+ console.log(`Dashboard installed to ${stableDist}`);
42
+ }
43
+
28
44
  console.log('Dashboard built successfully.\n');
29
45
  }
30
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnixal/openclaw-nats-plugin",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "NATS JetStream event-driven plugin for OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,12 +1,18 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
3
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
4
6
 
5
7
  const ROUTE_PREFIX = '/nats-dashboard';
6
- const DIST_DIR = path.resolve(__dirname, '../../dashboard/dist');
7
8
  const SIDECAR_URL = process.env.NATS_SIDECAR_URL || 'http://127.0.0.1:3104';
8
9
  const API_KEY = process.env.NATS_PLUGIN_API_KEY || 'dev-nats-plugin-key';
9
10
 
11
+ // Stable location (copied during setup) takes priority over in-package dist
12
+ const STABLE_DIST = path.join(homedir(), '.openclaw', 'nats-plugin', 'dashboard');
13
+ const PACKAGE_DIST = path.resolve(__dirname, '../../dashboard/dist');
14
+ const DIST_DIR = existsSync(path.join(STABLE_DIST, 'index.html')) ? STABLE_DIST : PACKAGE_DIST;
15
+
10
16
  const MIME_TYPES: Record<string, string> = {
11
17
  '.html': 'text/html; charset=utf-8',
12
18
  '.js': 'application/javascript; charset=utf-8',
@@ -124,7 +130,7 @@ async function serveStatic(subPath: string, res: ServerResponse): Promise<boolea
124
130
  res.end(indexHtml);
125
131
  } catch {
126
132
  res.statusCode = 404;
127
- res.end('Dashboard not built. Run: cd openclaw-nats-plugin/dashboard && bun run build');
133
+ res.end('Dashboard not built. Run: npx @omnixal/openclaw-nats-plugin setup');
128
134
  }
129
135
  }
130
136
  return true;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Controller, Get, Post, Patch, Delete,
3
3
  Body, Param, BaseController,
4
- UseMiddleware, Subscribe,
4
+ UseMiddleware, Subscribe, OnQueueReady,
5
5
  type Message,
6
6
  type OneBunResponse,
7
7
  } from '@onebun/core';
@@ -16,6 +16,11 @@ export class SchedulerController extends BaseController {
16
16
  super();
17
17
  }
18
18
 
19
+ @OnQueueReady()
20
+ async onQueueReady(): Promise<void> {
21
+ await this.scheduler.restoreJobs();
22
+ }
23
+
19
24
  @Post()
20
25
  async createJob(@Body(createCronBodySchema) body: CreateCronBody): Promise<OneBunResponse> {
21
26
  if (!body.subject.startsWith('agent.events.')) {
@@ -1,4 +1,4 @@
1
- import { Service, BaseService, QueueService, OnModuleInit } from '@onebun/core';
1
+ import { Service, BaseService, QueueService } from '@onebun/core';
2
2
  import { SchedulerRepository } from './scheduler.repository';
3
3
  import { PublisherService } from '../publisher/publisher.service';
4
4
  import { ulid } from 'ulid';
@@ -12,7 +12,7 @@ interface AddJobInput {
12
12
  }
13
13
 
14
14
  @Service()
15
- export class SchedulerService extends BaseService implements OnModuleInit {
15
+ export class SchedulerService extends BaseService {
16
16
  constructor(
17
17
  private repo: SchedulerRepository,
18
18
  private queueService: QueueService,
@@ -25,7 +25,7 @@ export class SchedulerService extends BaseService implements OnModuleInit {
25
25
  return this.queueService.getScheduler();
26
26
  }
27
27
 
28
- async onModuleInit(): Promise<void> {
28
+ async restoreJobs(): Promise<void> {
29
29
  const jobs = await this.repo.findAllEnabled();
30
30
  for (const job of jobs) {
31
31
  this.scheduler.addCronJob(