@euraika-labs/pan-ui 0.2.3 → 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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +92 -92
- package/.next/standalone/.next/app-path-routes-manifest.json +25 -25
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +17 -17
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/sessions/[id]/fork/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/sessions/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/sessions/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/stream/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/extensions/[id]/capabilities/[capabilityId]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/extensions/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/extensions/[id]/test/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/extensions/mcp/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/extensions/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/memory/agent/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/memory/context-inspector/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/memory/session-search/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/memory/user/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/config/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/profiles/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/approvals/[toolCallId]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/approvals/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/artifacts/download/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/artifacts/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/export/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/health/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/mcp-probes/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/runs/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/runs/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/status/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/telemetry/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/runtime/timeline/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/disable/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/enable/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/files/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/install/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/load/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/catalog/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/categories/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/hub/install/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/hub/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/telemetry/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/uploads/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/extensions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/extensions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +1 -1
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/memory/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profiles/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/approvals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/artifacts/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/health/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/mcp-diagnostics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/runs/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/runs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/telemetry/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/skills/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +25 -25
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/pages-manifest.json +1 -1
- package/.next/standalone/package.json +1 -1
- package/bin/pan-ui.mjs +410 -11
- package/package.json +1 -1
- /package/.next/standalone/.next/static/{9GQlyWupf2ogNOh-EUSPL → Pqhqpl3ZPqKI0qfvpIPeN}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{9GQlyWupf2ogNOh-EUSPL → Pqhqpl3ZPqKI0qfvpIPeN}/_ssgManifest.js +0 -0
- /package/.next/static/{9GQlyWupf2ogNOh-EUSPL → Pqhqpl3ZPqKI0qfvpIPeN}/_buildManifest.js +0 -0
- /package/.next/static/{9GQlyWupf2ogNOh-EUSPL → Pqhqpl3ZPqKI0qfvpIPeN}/_ssgManifest.js +0 -0
package/bin/pan-ui.mjs
CHANGED
|
@@ -4,17 +4,23 @@
|
|
|
4
4
|
* pan-ui — CLI launcher with interactive setup wizard.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx pan-ui
|
|
8
|
-
* npx pan-ui
|
|
9
|
-
* npx pan-ui
|
|
7
|
+
* npx pan-ui # start foreground (runs setup on first launch)
|
|
8
|
+
* npx pan-ui --daemon # start in background (daemonize)
|
|
9
|
+
* npx pan-ui stop # stop background daemon
|
|
10
|
+
* npx pan-ui status # check if daemon is running
|
|
11
|
+
* npx pan-ui logs # tail daemon log output
|
|
12
|
+
* npx pan-ui setup # re-run setup wizard
|
|
13
|
+
* npx pan-ui service install # install systemd user service
|
|
14
|
+
* npx pan-ui service remove # remove systemd user service
|
|
15
|
+
* npx pan-ui --port 8080 # custom port
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import { createInterface } from 'node:readline';
|
|
13
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync, unlinkSync, openSync } from 'node:fs';
|
|
14
20
|
import { resolve, join, dirname } from 'node:path';
|
|
15
21
|
import { execSync, spawn, execFileSync } from 'node:child_process';
|
|
16
22
|
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { homedir } from 'node:os';
|
|
23
|
+
import { homedir, platform } from 'node:os';
|
|
18
24
|
import crypto from 'node:crypto';
|
|
19
25
|
|
|
20
26
|
// ─── Paths ──────────────────────────────────────────────────────────────────
|
|
@@ -23,6 +29,9 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
23
29
|
const __dirname = dirname(__filename);
|
|
24
30
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
25
31
|
const ENV_PATH = join(PKG_ROOT, '.env.local');
|
|
32
|
+
const RUN_DIR = join(homedir(), '.pan-ui');
|
|
33
|
+
const PID_FILE = join(RUN_DIR, 'pan-ui.pid');
|
|
34
|
+
const LOG_FILE = join(RUN_DIR, 'pan-ui.log');
|
|
26
35
|
|
|
27
36
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
28
37
|
|
|
@@ -396,6 +405,324 @@ function startServer(env, portOverride) {
|
|
|
396
405
|
}
|
|
397
406
|
}
|
|
398
407
|
|
|
408
|
+
// ─── Daemon management ───────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
function ensureRunDir() {
|
|
411
|
+
if (!existsSync(RUN_DIR)) mkdirSync(RUN_DIR, { recursive: true });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function readPid() {
|
|
415
|
+
if (!existsSync(PID_FILE)) return null;
|
|
416
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
417
|
+
if (isNaN(pid)) return null;
|
|
418
|
+
return pid;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function isProcessRunning(pid) {
|
|
422
|
+
try {
|
|
423
|
+
process.kill(pid, 0); // signal 0 = check existence
|
|
424
|
+
return true;
|
|
425
|
+
} catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getDaemonStatus() {
|
|
431
|
+
const pid = readPid();
|
|
432
|
+
if (!pid) return { running: false, pid: null };
|
|
433
|
+
if (isProcessRunning(pid)) return { running: true, pid };
|
|
434
|
+
// Stale PID file
|
|
435
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
436
|
+
return { running: false, pid: null };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function startDaemon(env, portOverride) {
|
|
440
|
+
const { running, pid: existingPid } = getDaemonStatus();
|
|
441
|
+
if (running) {
|
|
442
|
+
console.log(` ${yellow('!')} Pan UI is already running (PID ${existingPid})`);
|
|
443
|
+
console.log(` ${dim('Stop with:')} ${cyan('npx pan-ui stop')}`);
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
ensureRunDir();
|
|
448
|
+
|
|
449
|
+
const port = portOverride || env.PORT || '3199';
|
|
450
|
+
const serverEnv = {
|
|
451
|
+
...process.env,
|
|
452
|
+
...env,
|
|
453
|
+
PORT: port,
|
|
454
|
+
NODE_ENV: 'production',
|
|
455
|
+
HOSTNAME: '0.0.0.0',
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const standaloneServer = findStandaloneServer();
|
|
459
|
+
let cmd, cmdArgs, cwd;
|
|
460
|
+
|
|
461
|
+
if (standaloneServer) {
|
|
462
|
+
patchStandalonePaths();
|
|
463
|
+
cmd = process.execPath; // node
|
|
464
|
+
cmdArgs = [standaloneServer];
|
|
465
|
+
cwd = join(PKG_ROOT, '.next', 'standalone');
|
|
466
|
+
} else {
|
|
467
|
+
const hasProductionBuild = existsSync(join(PKG_ROOT, '.next', 'BUILD_ID'));
|
|
468
|
+
const nextCmd = hasProductionBuild ? 'start' : 'dev';
|
|
469
|
+
cmd = 'npx';
|
|
470
|
+
cmdArgs = ['next', nextCmd, '-p', port];
|
|
471
|
+
cwd = PKG_ROOT;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Open log file for stdout/stderr
|
|
475
|
+
const logFd = openSync(LOG_FILE, 'a');
|
|
476
|
+
|
|
477
|
+
const child = spawn(cmd, cmdArgs, {
|
|
478
|
+
cwd,
|
|
479
|
+
env: serverEnv,
|
|
480
|
+
detached: true,
|
|
481
|
+
stdio: ['ignore', logFd, logFd],
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
child.unref();
|
|
485
|
+
|
|
486
|
+
writeFileSync(PID_FILE, String(child.pid), 'utf-8');
|
|
487
|
+
|
|
488
|
+
console.log('');
|
|
489
|
+
console.log(bold(' ╔══════════════════════════════════════════╗'));
|
|
490
|
+
console.log(bold(' ║') + green(' ⚡ Pan UI started (daemon) ') + bold('║'));
|
|
491
|
+
console.log(bold(' ╚══════════════════════════════════════════╝'));
|
|
492
|
+
console.log('');
|
|
493
|
+
console.log(` ${dim('PID:')} ${cyan(String(child.pid))}`);
|
|
494
|
+
console.log(` ${dim('Local:')} ${cyan(`http://localhost:${port}`)}`);
|
|
495
|
+
console.log(` ${dim('Log:')} ${cyan(LOG_FILE)}`);
|
|
496
|
+
console.log('');
|
|
497
|
+
console.log(` ${dim('Stop with:')} ${cyan('npx pan-ui stop')}`);
|
|
498
|
+
console.log(` ${dim('View logs:')} ${cyan('npx pan-ui logs')}`);
|
|
499
|
+
console.log(` ${dim('Check status:')} ${cyan('npx pan-ui status')}`);
|
|
500
|
+
console.log('');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function stopDaemon() {
|
|
504
|
+
const { running, pid } = getDaemonStatus();
|
|
505
|
+
if (!running) {
|
|
506
|
+
console.log(` ${dim('Pan UI is not running.')}`);
|
|
507
|
+
process.exit(0);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
process.kill(pid, 'SIGTERM');
|
|
512
|
+
console.log(` ${green('✓')} Pan UI stopped (PID ${pid})`);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.log(` ${red('✗')} Failed to stop PID ${pid}: ${err.message}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function showStatus() {
|
|
521
|
+
const { running, pid } = getDaemonStatus();
|
|
522
|
+
const env = loadEnv(ENV_PATH);
|
|
523
|
+
const port = env.PORT || '3199';
|
|
524
|
+
|
|
525
|
+
if (running) {
|
|
526
|
+
console.log('');
|
|
527
|
+
console.log(` ${green('●')} Pan UI is ${green('running')}`);
|
|
528
|
+
console.log(` ${dim('PID:')} ${cyan(String(pid))}`);
|
|
529
|
+
console.log(` ${dim('Local:')} ${cyan(`http://localhost:${port}`)}`);
|
|
530
|
+
console.log(` ${dim('Log:')} ${cyan(LOG_FILE)}`);
|
|
531
|
+
console.log('');
|
|
532
|
+
} else {
|
|
533
|
+
console.log('');
|
|
534
|
+
console.log(` ${red('●')} Pan UI is ${red('stopped')}`);
|
|
535
|
+
console.log(` ${dim('Start with:')} ${cyan('npx pan-ui --daemon')}`);
|
|
536
|
+
console.log('');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function showLogs() {
|
|
541
|
+
if (!existsSync(LOG_FILE)) {
|
|
542
|
+
console.log(` ${dim('No log file found. Start the daemon first.')}`);
|
|
543
|
+
process.exit(0);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Tail the last 50 lines, then follow
|
|
547
|
+
const child = spawn('tail', ['-n', '50', '-f', LOG_FILE], {
|
|
548
|
+
stdio: 'inherit',
|
|
549
|
+
});
|
|
550
|
+
process.on('SIGINT', () => { child.kill('SIGINT'); process.exit(0); });
|
|
551
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ─── Systemd service management ─────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
function getServiceDir() {
|
|
557
|
+
return join(homedir(), '.config', 'systemd', 'user');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const SERVICE_NAME = 'pan-ui';
|
|
561
|
+
|
|
562
|
+
function getServicePath() {
|
|
563
|
+
return join(getServiceDir(), `${SERVICE_NAME}.service`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function generateServiceUnit(env) {
|
|
567
|
+
const port = env.PORT || '3199';
|
|
568
|
+
const nodePath = process.execPath;
|
|
569
|
+
const binPath = resolve(__dirname, 'pan-ui.mjs');
|
|
570
|
+
|
|
571
|
+
// Build environment lines (skip PORT — we add it explicitly below)
|
|
572
|
+
const envLines = [];
|
|
573
|
+
for (const [key, value] of Object.entries(env)) {
|
|
574
|
+
if (key && value && key !== 'PORT') envLines.push(`Environment=${key}=${value}`);
|
|
575
|
+
}
|
|
576
|
+
envLines.push(`Environment=NODE_ENV=production`);
|
|
577
|
+
envLines.push(`Environment=PORT=${port}`);
|
|
578
|
+
envLines.push(`Environment=HOSTNAME=0.0.0.0`);
|
|
579
|
+
|
|
580
|
+
return `[Unit]
|
|
581
|
+
Description=Pan UI — WebUI for Hermes Agent
|
|
582
|
+
After=network.target
|
|
583
|
+
|
|
584
|
+
[Service]
|
|
585
|
+
Type=simple
|
|
586
|
+
ExecStart=${nodePath} ${binPath}
|
|
587
|
+
WorkingDirectory=${PKG_ROOT}
|
|
588
|
+
Restart=on-failure
|
|
589
|
+
RestartSec=5
|
|
590
|
+
${envLines.join('\n')}
|
|
591
|
+
|
|
592
|
+
# Logging
|
|
593
|
+
StandardOutput=journal
|
|
594
|
+
StandardError=journal
|
|
595
|
+
SyslogIdentifier=pan-ui
|
|
596
|
+
|
|
597
|
+
[Install]
|
|
598
|
+
WantedBy=default.target
|
|
599
|
+
`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function serviceInstall() {
|
|
603
|
+
if (platform() !== 'linux') {
|
|
604
|
+
console.log(` ${red('✗')} Systemd services are only supported on Linux.`);
|
|
605
|
+
console.log(` ${dim('Use')} ${cyan('npx pan-ui --daemon')} ${dim('instead.')}`);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Ensure setup has been run
|
|
610
|
+
const env = loadEnv(ENV_PATH);
|
|
611
|
+
if (Object.keys(env).length === 0) {
|
|
612
|
+
console.log(` ${yellow('!')} No configuration found. Running setup first...`);
|
|
613
|
+
console.log('');
|
|
614
|
+
const newEnv = await setup(true);
|
|
615
|
+
rl.close();
|
|
616
|
+
return doServiceInstall(newEnv);
|
|
617
|
+
}
|
|
618
|
+
rl.close();
|
|
619
|
+
return doServiceInstall(env);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function doServiceInstall(env) {
|
|
623
|
+
const serviceDir = getServiceDir();
|
|
624
|
+
const servicePath = getServicePath();
|
|
625
|
+
|
|
626
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
627
|
+
|
|
628
|
+
const unit = generateServiceUnit(env);
|
|
629
|
+
writeFileSync(servicePath, unit, 'utf-8');
|
|
630
|
+
|
|
631
|
+
console.log('');
|
|
632
|
+
console.log(` ${green('✓')} Service file written to ${cyan(servicePath)}`);
|
|
633
|
+
|
|
634
|
+
// Reload and enable
|
|
635
|
+
try {
|
|
636
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
637
|
+
console.log(` ${green('✓')} Systemd daemon reloaded`);
|
|
638
|
+
} catch {
|
|
639
|
+
console.log(` ${yellow('!')} Could not reload systemd daemon (are you in a user session?)`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
execSync(`systemctl --user enable ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
644
|
+
console.log(` ${green('✓')} Service enabled (starts on login)`);
|
|
645
|
+
} catch {
|
|
646
|
+
console.log(` ${yellow('!')} Could not enable service`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
execSync(`systemctl --user start ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
651
|
+
console.log(` ${green('✓')} Service started`);
|
|
652
|
+
} catch {
|
|
653
|
+
console.log(` ${yellow('!')} Could not start service — try: systemctl --user start ${SERVICE_NAME}`);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Enable lingering so service runs even when not logged in
|
|
657
|
+
try {
|
|
658
|
+
execSync(`loginctl enable-linger ${process.env.USER || ''}`, { stdio: 'ignore' });
|
|
659
|
+
console.log(` ${green('✓')} Lingering enabled (survives logout)`);
|
|
660
|
+
} catch {
|
|
661
|
+
console.log(` ${dim('Tip: run')} ${cyan(`loginctl enable-linger`)} ${dim('so the service survives logout')}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const port = env.PORT || '3199';
|
|
665
|
+
console.log('');
|
|
666
|
+
console.log(` ${dim('Local:')} ${cyan(`http://localhost:${port}`)}`);
|
|
667
|
+
console.log('');
|
|
668
|
+
console.log(` ${dim('Manage with:')}`);
|
|
669
|
+
console.log(` ${cyan(`systemctl --user status ${SERVICE_NAME}`)}`);
|
|
670
|
+
console.log(` ${cyan(`systemctl --user restart ${SERVICE_NAME}`)}`);
|
|
671
|
+
console.log(` ${cyan(`journalctl --user -u ${SERVICE_NAME} -f`)}`);
|
|
672
|
+
console.log(` ${cyan(`npx pan-ui service remove`)}`);
|
|
673
|
+
console.log('');
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function serviceRemove() {
|
|
677
|
+
if (platform() !== 'linux') {
|
|
678
|
+
console.log(` ${red('✗')} Systemd services are only supported on Linux.`);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const servicePath = getServicePath();
|
|
683
|
+
|
|
684
|
+
// Stop and disable
|
|
685
|
+
try {
|
|
686
|
+
execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
687
|
+
console.log(` ${green('✓')} Service stopped`);
|
|
688
|
+
} catch {}
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
execSync(`systemctl --user disable ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
692
|
+
console.log(` ${green('✓')} Service disabled`);
|
|
693
|
+
} catch {}
|
|
694
|
+
|
|
695
|
+
if (existsSync(servicePath)) {
|
|
696
|
+
unlinkSync(servicePath);
|
|
697
|
+
console.log(` ${green('✓')} Removed ${servicePath}`);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
702
|
+
console.log(` ${green('✓')} Systemd daemon reloaded`);
|
|
703
|
+
} catch {}
|
|
704
|
+
|
|
705
|
+
console.log('');
|
|
706
|
+
console.log(` ${dim('Pan UI service has been fully removed.')}`);
|
|
707
|
+
console.log('');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function serviceStatus() {
|
|
711
|
+
if (platform() !== 'linux') {
|
|
712
|
+
console.log(` ${red('✗')} Systemd services are only supported on Linux.`);
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
try {
|
|
717
|
+
const output = execSync(`systemctl --user status ${SERVICE_NAME} 2>&1`, { encoding: 'utf-8' });
|
|
718
|
+
console.log(output);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
// systemctl returns non-zero for inactive services
|
|
721
|
+
if (err.stdout) console.log(err.stdout);
|
|
722
|
+
else console.log(` ${dim('Service not installed. Run:')} ${cyan('npx pan-ui service install')}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
399
726
|
// ─── CLI entry point ────────────────────────────────────────────────────────
|
|
400
727
|
|
|
401
728
|
async function main() {
|
|
@@ -413,8 +740,65 @@ async function main() {
|
|
|
413
740
|
portOverride = args[shortPortIdx + 1];
|
|
414
741
|
}
|
|
415
742
|
|
|
743
|
+
// Parse --daemon / -d flag
|
|
744
|
+
const isDaemon = args.includes('--daemon') || args.includes('-d');
|
|
745
|
+
|
|
746
|
+
// ── Stop ──────────────────────────────────────────────────────────────
|
|
747
|
+
|
|
748
|
+
if (command === 'stop') {
|
|
749
|
+
rl.close();
|
|
750
|
+
stopDaemon();
|
|
751
|
+
process.exit(0);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// ── Status ────────────────────────────────────────────────────────────
|
|
755
|
+
|
|
756
|
+
if (command === 'status') {
|
|
757
|
+
rl.close();
|
|
758
|
+
showStatus();
|
|
759
|
+
process.exit(0);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// ── Logs ──────────────────────────────────────────────────────────────
|
|
763
|
+
|
|
764
|
+
if (command === 'logs') {
|
|
765
|
+
rl.close();
|
|
766
|
+
showLogs();
|
|
767
|
+
return; // showLogs takes over the process
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ── Service management ────────────────────────────────────────────────
|
|
771
|
+
|
|
772
|
+
if (command === 'service') {
|
|
773
|
+
const subcommand = args[1];
|
|
774
|
+
if (subcommand === 'install') {
|
|
775
|
+
await serviceInstall();
|
|
776
|
+
process.exit(0);
|
|
777
|
+
}
|
|
778
|
+
if (subcommand === 'remove' || subcommand === 'uninstall') {
|
|
779
|
+
rl.close();
|
|
780
|
+
serviceRemove();
|
|
781
|
+
process.exit(0);
|
|
782
|
+
}
|
|
783
|
+
if (subcommand === 'status') {
|
|
784
|
+
rl.close();
|
|
785
|
+
serviceStatus();
|
|
786
|
+
process.exit(0);
|
|
787
|
+
}
|
|
788
|
+
// Unknown subcommand
|
|
789
|
+
rl.close();
|
|
790
|
+
console.log('');
|
|
791
|
+
console.log(` ${dim('Usage:')}`);
|
|
792
|
+
console.log(` ${cyan('npx pan-ui service install')} Install systemd user service`);
|
|
793
|
+
console.log(` ${cyan('npx pan-ui service remove')} Remove systemd user service`);
|
|
794
|
+
console.log(` ${cyan('npx pan-ui service status')} Show service status`);
|
|
795
|
+
console.log('');
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ── Setup ─────────────────────────────────────────────────────────────
|
|
800
|
+
|
|
416
801
|
if (command === 'setup') {
|
|
417
|
-
// Explicit setup
|
|
418
802
|
const env = await setup(true);
|
|
419
803
|
rl.close();
|
|
420
804
|
console.log(` ${dim('Start with:')} ${cyan('npx pan-ui')}`);
|
|
@@ -422,15 +806,23 @@ async function main() {
|
|
|
422
806
|
process.exit(0);
|
|
423
807
|
}
|
|
424
808
|
|
|
809
|
+
// ── Help ──────────────────────────────────────────────────────────────
|
|
810
|
+
|
|
425
811
|
if (command === '--help' || command === '-h') {
|
|
426
812
|
console.log('');
|
|
427
813
|
console.log(bold(' pan-ui') + dim(' — Beautiful WebUI for Hermes Agent'));
|
|
428
814
|
console.log('');
|
|
429
815
|
console.log(' Usage:');
|
|
430
|
-
console.log(` ${cyan('npx pan-ui')}
|
|
431
|
-
console.log(` ${cyan('npx pan-ui
|
|
432
|
-
console.log(` ${cyan('npx pan-ui
|
|
433
|
-
console.log(` ${cyan('npx pan-ui
|
|
816
|
+
console.log(` ${cyan('npx pan-ui')} Start the workspace (foreground)`);
|
|
817
|
+
console.log(` ${cyan('npx pan-ui --daemon')} Start in background`);
|
|
818
|
+
console.log(` ${cyan('npx pan-ui stop')} Stop background daemon`);
|
|
819
|
+
console.log(` ${cyan('npx pan-ui status')} Check if running`);
|
|
820
|
+
console.log(` ${cyan('npx pan-ui logs')} Tail daemon logs`);
|
|
821
|
+
console.log(` ${cyan('npx pan-ui setup')} Run setup wizard`);
|
|
822
|
+
console.log(` ${cyan('npx pan-ui service install')} Install as systemd user service`);
|
|
823
|
+
console.log(` ${cyan('npx pan-ui service remove')} Remove systemd service`);
|
|
824
|
+
console.log(` ${cyan('npx pan-ui --port 8080')} Custom port`);
|
|
825
|
+
console.log(` ${cyan('npx pan-ui --help')} Show this help`);
|
|
434
826
|
console.log('');
|
|
435
827
|
console.log(' Environment variables:');
|
|
436
828
|
console.log(` ${dim('HERMES_HOME')} Path to Hermes home (default: ~/.hermes)`);
|
|
@@ -445,9 +837,16 @@ async function main() {
|
|
|
445
837
|
process.exit(0);
|
|
446
838
|
}
|
|
447
839
|
|
|
448
|
-
// Default:
|
|
840
|
+
// ── Default: setup if first time, then start ──────────────────────────
|
|
841
|
+
|
|
449
842
|
const env = await setup(false);
|
|
450
843
|
rl.close();
|
|
844
|
+
|
|
845
|
+
if (isDaemon) {
|
|
846
|
+
await startDaemon(env, portOverride);
|
|
847
|
+
process.exit(0);
|
|
848
|
+
}
|
|
849
|
+
|
|
451
850
|
startServer(env, portOverride);
|
|
452
851
|
}
|
|
453
852
|
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|