zigrix 0.1.0-alpha.3 → 0.1.0-alpha.5

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/README.md CHANGED
@@ -36,6 +36,36 @@ zigrix onboard
36
36
  zigrix doctor
37
37
  ```
38
38
 
39
+ ### Prerequisites: Node.js version
40
+
41
+ Zigrix requires Node.js **22 or later**. Verify before installing:
42
+
43
+ ```bash
44
+ node --version # must be v22.x or higher
45
+ ```
46
+
47
+ If you use **nvm**, make sure the correct version is active:
48
+
49
+ ```bash
50
+ nvm use 22 # or: nvm use --lts
51
+ node --version # confirm v22+
52
+ ```
53
+
54
+ > **Important (nvm users):** `npm link` binds `zigrix` to whichever Node version is active at install time.
55
+ > If you switch node versions later, re-run the install to rebind:
56
+ > ```bash
57
+ > npm run build && npm link
58
+ > ```
59
+
60
+ ### Version mismatch troubleshooting
61
+
62
+ If `zigrix --version` shows an unexpected version after updating, rebuild and relink:
63
+
64
+ ```bash
65
+ npm run build && npm link
66
+ zigrix --version # should now match package.json
67
+ ```
68
+
39
69
  `zigrix onboard` will:
40
70
  1. Create `~/.zigrix/` with default config
41
71
  2. Detect OpenClaw and import agents from `openclaw.json`
@@ -0,0 +1,35 @@
1
+ /** Default port for zigrix dashboard */
2
+ export declare const DASHBOARD_DEFAULT_PORT = 3838;
3
+ /**
4
+ * Resolve the dashboard directory from a given dist index.js path.
5
+ * Convention: dist/index.js → ../../dashboard (i.e. <package-root>/dashboard)
6
+ *
7
+ * The path "../../dashboard" is interpreted relative to the FILE dist/index.js:
8
+ * - First ".." removes the filename component → <pkg>/dist/
9
+ * - Second ".." removes the dist directory → <pkg>/ (package root)
10
+ * - "dashboard" → <pkg>/dashboard
11
+ *
12
+ * Using path.resolve(distIndexPath, '..', '..', 'dashboard') achieves exactly
13
+ * this because path.resolve treats each ".." as stripping the last segment of
14
+ * the current accumulated path (including the filename on the first step).
15
+ */
16
+ export declare function resolveDashboardDir(distIndexPath: string): string;
17
+ /**
18
+ * Check whether a TCP port is already in use.
19
+ * Returns true if the port is busy (another process is listening).
20
+ */
21
+ export declare function isPortBusy(port: number): Promise<boolean>;
22
+ export interface RunDashboardOptions {
23
+ port?: number;
24
+ }
25
+ /**
26
+ * Main entry point for `zigrix dashboard`.
27
+ *
28
+ * Steps:
29
+ * 1. Resolve dashboard directory relative to this module's dist path.
30
+ * 2. Check port availability; fail clearly on conflict.
31
+ * 3. Auto-install if node_modules is missing.
32
+ * 4. Auto-build if .next directory is missing.
33
+ * 5. Spawn `next start -p <port>` in foreground and wait.
34
+ */
35
+ export declare function runDashboard(options?: RunDashboardOptions): Promise<void>;
@@ -0,0 +1,113 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as net from 'node:net';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ /** Default port for zigrix dashboard */
7
+ export const DASHBOARD_DEFAULT_PORT = 3838;
8
+ /**
9
+ * Resolve the dashboard directory from a given dist index.js path.
10
+ * Convention: dist/index.js → ../../dashboard (i.e. <package-root>/dashboard)
11
+ *
12
+ * The path "../../dashboard" is interpreted relative to the FILE dist/index.js:
13
+ * - First ".." removes the filename component → <pkg>/dist/
14
+ * - Second ".." removes the dist directory → <pkg>/ (package root)
15
+ * - "dashboard" → <pkg>/dashboard
16
+ *
17
+ * Using path.resolve(distIndexPath, '..', '..', 'dashboard') achieves exactly
18
+ * this because path.resolve treats each ".." as stripping the last segment of
19
+ * the current accumulated path (including the filename on the first step).
20
+ */
21
+ export function resolveDashboardDir(distIndexPath) {
22
+ return path.resolve(distIndexPath, '..', '..', 'dashboard');
23
+ }
24
+ /**
25
+ * Check whether a TCP port is already in use.
26
+ * Returns true if the port is busy (another process is listening).
27
+ */
28
+ export function isPortBusy(port) {
29
+ return new Promise((resolve) => {
30
+ const server = net.createServer();
31
+ server.once('error', (err) => {
32
+ if (err.code === 'EADDRINUSE') {
33
+ resolve(true);
34
+ }
35
+ else {
36
+ // Unexpected error — treat as busy to be safe
37
+ resolve(true);
38
+ }
39
+ });
40
+ server.once('listening', () => {
41
+ server.close(() => resolve(false));
42
+ });
43
+ server.listen(port, '127.0.0.1');
44
+ });
45
+ }
46
+ /**
47
+ * Main entry point for `zigrix dashboard`.
48
+ *
49
+ * Steps:
50
+ * 1. Resolve dashboard directory relative to this module's dist path.
51
+ * 2. Check port availability; fail clearly on conflict.
52
+ * 3. Auto-install if node_modules is missing.
53
+ * 4. Auto-build if .next directory is missing.
54
+ * 5. Spawn `next start -p <port>` in foreground and wait.
55
+ */
56
+ export async function runDashboard(options = {}) {
57
+ const port = options.port ?? DASHBOARD_DEFAULT_PORT;
58
+ // Resolve dashboard directory.
59
+ // When running from compiled dist/index.js: import.meta.url resolves to
60
+ // file:///…/dist/index.js, so dirname is …/dist.
61
+ // ../../dashboard therefore points to <package-root>/dashboard.
62
+ const selfPath = fileURLToPath(import.meta.url);
63
+ const dashboardDir = resolveDashboardDir(selfPath);
64
+ if (!fs.existsSync(dashboardDir)) {
65
+ throw new Error(`Dashboard directory not found: ${dashboardDir}`);
66
+ }
67
+ // --- Port conflict check ---
68
+ const busy = await isPortBusy(port);
69
+ if (busy) {
70
+ throw new Error(`Port ${port} is already in use. ` +
71
+ `Stop the conflicting process or specify a different port with --port.`);
72
+ }
73
+ // --- Auto-install dependencies ---
74
+ const nodeModulesDir = path.join(dashboardDir, 'node_modules');
75
+ if (!fs.existsSync(nodeModulesDir)) {
76
+ console.log('📦 dashboard/node_modules not found — running npm install…');
77
+ execSync('npm install', { cwd: dashboardDir, stdio: 'inherit' });
78
+ }
79
+ // --- Auto-build ---
80
+ const nextBuildDir = path.join(dashboardDir, '.next');
81
+ if (!fs.existsSync(nextBuildDir)) {
82
+ console.log('🔨 dashboard/.next not found — running npm run build…');
83
+ execSync('npm run build', { cwd: dashboardDir, stdio: 'inherit' });
84
+ }
85
+ // --- Launch Next.js in foreground ---
86
+ console.log(`🚀 Starting zigrix dashboard on http://localhost:${port}`);
87
+ const nextBin = path.join(dashboardDir, 'node_modules', '.bin', 'next');
88
+ const child = spawn(nextBin, ['start', '-p', String(port)], {
89
+ cwd: dashboardDir,
90
+ stdio: 'inherit',
91
+ env: { ...process.env },
92
+ });
93
+ await new Promise((resolve, reject) => {
94
+ child.on('exit', (code, signal) => {
95
+ if (code === 0 || signal === 'SIGINT' || signal === 'SIGTERM') {
96
+ resolve();
97
+ }
98
+ else {
99
+ reject(new Error(`next start exited with code ${code ?? signal}`));
100
+ }
101
+ });
102
+ child.on('error', (err) => {
103
+ reject(new Error(`Failed to start next: ${err.message}`));
104
+ });
105
+ // Forward SIGINT/SIGTERM to child for clean shutdown
106
+ const forward = (sig) => {
107
+ if (!child.killed)
108
+ child.kill(sig);
109
+ };
110
+ process.once('SIGINT', () => forward('SIGINT'));
111
+ process.once('SIGTERM', () => forward('SIGTERM'));
112
+ });
113
+ }
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { loadRunRecord } from './runner/store.js';
21
21
  import { ensureBaseState, resolvePaths } from './state/paths.js';
22
22
  import { applyStalePolicy, createTask, findStaleTasks, listTaskEvents, listTasks, loadTask, rebuildIndex, recordTaskProgress, updateTaskStatus, } from './state/tasks.js';
23
23
  import { verifyState } from './state/verify.js';
24
+ import { runDashboard, DASHBOARD_DEFAULT_PORT } from './dashboard.js';
24
25
  const STATUS_MAP = {
25
26
  start: 'IN_PROGRESS',
26
27
  report: 'REPORTED',
@@ -57,11 +58,12 @@ function loadRuntime(options) {
57
58
  const loaded = loadConfig({ baseDir: options.baseDir, configPath: options.config });
58
59
  return { ...loaded, paths: resolvePaths(loaded.config) };
59
60
  }
61
+ const { version: pkgVersion } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
60
62
  const program = new Command();
61
63
  program
62
64
  .name('zigrix')
63
65
  .description('Zigrix — multi-project parallel task orchestration CLI')
64
- .version('0.1.0-alpha.0');
66
+ .version(pkgVersion);
65
67
  const config = program.command('config').description('Inspect Zigrix config');
66
68
  const agent = program.command('agent').description('Manage Zigrix agent registry and orchestration membership');
67
69
  const rule = program.command('rule').description('Inspect and validate rule assets');
@@ -785,6 +787,20 @@ program
785
787
  const loaded = loadConfig({ baseDir: options.baseDir, configPath: options.config });
786
788
  printValue(loadRunRecord(loaded.config, runIdOrPath), true);
787
789
  });
790
+ // ─── dashboard ──────────────────────────────────────────────────────────────
791
+ program
792
+ .command('dashboard')
793
+ .description('Start the zigrix web dashboard (Next.js) in the foreground')
794
+ .option('--port <n>', `TCP port to listen on (default: ${DASHBOARD_DEFAULT_PORT})`, String(DASHBOARD_DEFAULT_PORT))
795
+ .action(async (options) => {
796
+ const port = parseInt(options.port, 10);
797
+ if (isNaN(port) || port < 1 || port > 65535) {
798
+ console.error(`Invalid port: ${options.port}`);
799
+ process.exitCode = 1;
800
+ return;
801
+ }
802
+ await runDashboard({ port });
803
+ });
788
804
  program.parseAsync(process.argv).catch((error) => {
789
805
  console.error(error instanceof Error ? error.message : String(error));
790
806
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigrix",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "type": "module",
5
5
  "description": "Multi-project parallel task orchestration CLI for agent-assisted development",
6
6
  "license": "Apache-2.0",