@jhytabest/plashboard 0.1.8 → 0.1.10

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
@@ -30,7 +30,7 @@ No manual config is required for first use. Defaults are safe:
30
30
  In chat, run:
31
31
 
32
32
  ```text
33
- /plashboard quickstart <what this dashboard should focus on>
33
+ /plashboard onboard <what this dashboard should focus on>
34
34
  ```
35
35
 
36
36
  ## Optional Config
@@ -74,8 +74,11 @@ Use `fill_provider: "command"` only if you need a custom external runner.
74
74
  ## Runtime Command
75
75
 
76
76
  ```text
77
+ /plashboard onboard <description> [local_url] [https_port] [repo_dir]
77
78
  /plashboard setup [openclaw [agent_id]|mock|command <fill_command>]
78
79
  /plashboard quickstart <description>
80
+ /plashboard doctor [local_url] [https_port] [repo_dir]
81
+ /plashboard web-guide [local_url] [repo_dir]
79
82
  /plashboard expose-guide [local_url] [https_port]
80
83
  /plashboard expose-check [local_url] [https_port]
81
84
  /plashboard init
@@ -91,7 +94,15 @@ Use `fill_provider: "command"` only if you need a custom external runner.
91
94
  Recommended first run:
92
95
 
93
96
  ```text
94
- /plashboard quickstart "Focus on service health, priorities, blockers, and next actions."
97
+ /plashboard onboard "Focus on service health, priorities, blockers, and next actions."
98
+ ```
99
+
100
+ If `onboard` returns web/exposure warnings:
101
+
102
+ ```text
103
+ /plashboard web-guide
104
+ /plashboard expose-guide
105
+ /plashboard doctor
95
106
  ```
96
107
 
97
108
  Tailscale helper flow:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "plashboard",
3
3
  "name": "Plashboard",
4
- "version": "0.1.8",
4
+ "version": "0.1.10",
5
5
  "description": "Template-driven dashboard runtime with scheduled OpenClaw fills and safe publish.",
6
6
  "entry": "./src/index.ts",
7
7
  "skills": ["./skills/plashboard-admin"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhytabest/plashboard",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "description": "Plashboard OpenClaw plugin runtime",
6
6
  "license": "MIT",
@@ -17,9 +17,12 @@ Use this skill for plashboard runtime administration.
17
17
 
18
18
  ## Required Tooling
19
19
  Always use plugin tools:
20
+ - `plashboard_onboard`
20
21
  - `plashboard_setup`
21
22
  - `plashboard_exposure_guide`
22
23
  - `plashboard_exposure_check`
24
+ - `plashboard_web_guide`
25
+ - `plashboard_doctor`
23
26
  - `plashboard_init`
24
27
  - `plashboard_quickstart`
25
28
  - `plashboard_template_create`
@@ -40,8 +43,11 @@ Always use plugin tools:
40
43
  - Never ask the model to generate full dashboard structure when filling values.
41
44
 
42
45
  ## Command Shortcuts
46
+ - `/plashboard onboard <description> [local_url] [https_port] [repo_dir]`
43
47
  - `/plashboard quickstart <description>`
44
48
  - `/plashboard setup [openclaw [agent_id]|mock|command <fill_command>]`
49
+ - `/plashboard doctor [local_url] [https_port] [repo_dir]`
50
+ - `/plashboard web-guide [local_url] [repo_dir]`
45
51
  - `/plashboard expose-guide [local_url] [https_port]`
46
52
  - `/plashboard expose-check [local_url] [https_port]`
47
53
  - `/plashboard init`
package/src/plugin.ts CHANGED
@@ -117,6 +117,19 @@ type ExposureParams = {
117
117
  dashboard_output_path?: string;
118
118
  };
119
119
 
120
+ type WebGuideParams = {
121
+ local_url?: string;
122
+ repo_dir?: string;
123
+ };
124
+
125
+ type DoctorParams = ExposureParams & {
126
+ repo_dir?: string;
127
+ };
128
+
129
+ type OnboardParams = DoctorParams & QuickstartParams & {
130
+ force_quickstart?: boolean;
131
+ };
132
+
120
133
  type CommandExecResult = {
121
134
  ok: boolean;
122
135
  stdout: string;
@@ -230,6 +243,40 @@ async function buildExposureGuide(resolvedConfig: ReturnType<typeof resolveConfi
230
243
  } satisfies ToolResponse<Record<string, unknown>>;
231
244
  }
232
245
 
246
+ function deriveRepoDir(raw?: string): string {
247
+ const value = (raw || '').trim();
248
+ return value || '/opt/plashboard';
249
+ }
250
+
251
+ async function buildWebGuide(resolvedConfig: ReturnType<typeof resolveConfig>, params: WebGuideParams = {}) {
252
+ const localUrl = normalizeLocalUrl(params.local_url);
253
+ const repoDir = deriveRepoDir(params.repo_dir);
254
+ const dashboardPath = resolvedConfig.dashboard_output_path;
255
+
256
+ return {
257
+ ok: true,
258
+ errors: [],
259
+ data: {
260
+ local_url: localUrl,
261
+ repo_dir: repoDir,
262
+ dashboard_output_path: dashboardPath,
263
+ commands: [
264
+ `git clone https://github.com/jhytabest/plashboard.git ${repoDir} || true`,
265
+ `git -C ${repoDir} pull --ff-only`,
266
+ `docker compose -f ${repoDir}/docker-compose.yml up -d`,
267
+ `docker ps --format "{{.Names}} {{.Ports}}" | grep -E "plash-web|18888"`,
268
+ `curl -I ${localUrl}`,
269
+ `curl -I ${localUrl}/healthz`
270
+ ],
271
+ notes: [
272
+ 'Plashboard writes dashboard JSON; a local web server must serve the UI and /data/dashboard.json.',
273
+ 'The bundled docker-compose stack exposes nginx at 127.0.0.1:18888 by default.',
274
+ 'If you host UI differently, update local_url in expose-check/expose-guide.'
275
+ ]
276
+ }
277
+ } satisfies ToolResponse<Record<string, unknown>>;
278
+ }
279
+
233
280
  async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>, params: ExposureParams = {}) {
234
281
  const localUrl = normalizeLocalUrl(params.local_url);
235
282
  const httpsPort = normalizePort(asNumber(params.tailscale_https_port), 8444);
@@ -469,6 +516,146 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
469
516
  } satisfies ToolResponse<Record<string, unknown>>;
470
517
  }
471
518
 
519
+ async function runQuickstart(
520
+ runtime: PlashboardRuntime,
521
+ resolvedConfig: ReturnType<typeof resolveConfig>,
522
+ params: QuickstartParams = {}
523
+ ): Promise<ToolResponse<Record<string, unknown>>> {
524
+ const quickstart = await runtime.quickstart(params);
525
+ const exposure = await runExposureCheck(resolvedConfig, {});
526
+ const guide = await buildExposureGuide(resolvedConfig, {});
527
+ const webGuide = await buildWebGuide(resolvedConfig, {});
528
+
529
+ const warnings: string[] = [];
530
+ if (!exposure.ok) {
531
+ warnings.push(...exposure.errors);
532
+ }
533
+
534
+ return {
535
+ ok: quickstart.ok,
536
+ errors: quickstart.errors,
537
+ data: {
538
+ ...(quickstart.data || {}),
539
+ postcheck: {
540
+ local_url: exposure.data?.local_url,
541
+ local_url_ok: exposure.data?.local_url_ok,
542
+ tailscale_port_configured: exposure.data?.tailscale_port_configured,
543
+ dashboard_exists: exposure.data?.dashboard_exists
544
+ },
545
+ warnings,
546
+ next_steps: warnings.length
547
+ ? [
548
+ 'run /plashboard web-guide and execute its commands',
549
+ 'run /plashboard expose-guide and apply tailscale mapping',
550
+ 'run /plashboard doctor'
551
+ ]
552
+ : [
553
+ 'dashboard generation is working',
554
+ 'run /plashboard doctor for full readiness check'
555
+ ],
556
+ exposure_guide: guide.data,
557
+ web_guide: webGuide.data
558
+ }
559
+ };
560
+ }
561
+
562
+ async function runDoctor(
563
+ runtime: PlashboardRuntime,
564
+ resolvedConfig: ReturnType<typeof resolveConfig>,
565
+ params: DoctorParams = {}
566
+ ): Promise<ToolResponse<Record<string, unknown>>> {
567
+ const status = await runtime.status();
568
+ const exposure = await runExposureCheck(resolvedConfig, params);
569
+ const exposureGuide = await buildExposureGuide(resolvedConfig, params);
570
+ const webGuide = await buildWebGuide(resolvedConfig, params);
571
+
572
+ const issues: string[] = [];
573
+ const statusData = status.data;
574
+ const templateCount = Number(statusData?.template_count ?? 0);
575
+ const activeTemplateId = statusData?.active_template_id || null;
576
+
577
+ if (!status.ok) issues.push(...status.errors);
578
+ if (templateCount === 0) issues.push('no templates exist; run /plashboard quickstart "<description>"');
579
+ if (!activeTemplateId) issues.push('no active template; activate one with /plashboard activate <template-id>');
580
+ if (exposure.data?.dashboard_exists !== true) {
581
+ issues.push(`dashboard output missing at ${resolvedConfig.dashboard_output_path}`);
582
+ }
583
+ if (exposure.data?.local_url_ok !== true) {
584
+ issues.push(`local dashboard server is not reachable at ${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}`);
585
+ }
586
+ if (exposure.data?.tailscale_status_ok !== true) {
587
+ issues.push('tailscale serve status failed');
588
+ } else if (exposure.data?.tailscale_port_configured !== true) {
589
+ issues.push(`tailscale serve mapping missing for port ${String(exposure.data?.tailscale_https_port || 8444)}`);
590
+ }
591
+
592
+ const ready = issues.length === 0;
593
+ return {
594
+ ok: ready,
595
+ errors: issues,
596
+ data: {
597
+ ready,
598
+ status: statusData,
599
+ exposure: exposure.data,
600
+ exposure_guide: exposureGuide.data,
601
+ web_guide: webGuide.data,
602
+ next_steps: ready
603
+ ? ['dashboard runtime + web exposure look healthy']
604
+ : [
605
+ 'run /plashboard quickstart "<description>" if no templates exist',
606
+ 'run /plashboard web-guide and start local UI server',
607
+ 'run /plashboard expose-guide and apply tailscale mapping',
608
+ 're-run /plashboard doctor'
609
+ ]
610
+ }
611
+ };
612
+ }
613
+
614
+ async function runOnboard(
615
+ runtime: PlashboardRuntime,
616
+ resolvedConfig: ReturnType<typeof resolveConfig>,
617
+ params: OnboardParams = {}
618
+ ): Promise<ToolResponse<Record<string, unknown>>> {
619
+ const initResult = await runtime.init();
620
+ if (!initResult.ok) return initResult;
621
+
622
+ const beforeStatus = await runtime.status();
623
+ const beforeTemplateCount = Number(beforeStatus.data?.template_count ?? 0);
624
+ const shouldQuickstart = params.force_quickstart === true || beforeTemplateCount === 0;
625
+
626
+ let quickstartResult: ToolResponse<Record<string, unknown>> | null = null;
627
+ if (shouldQuickstart) {
628
+ quickstartResult = await runQuickstart(runtime, resolvedConfig, {
629
+ description: params.description,
630
+ template_id: params.template_id,
631
+ template_name: params.template_name,
632
+ every_minutes: params.every_minutes,
633
+ activate: params.activate,
634
+ run_now: params.run_now
635
+ });
636
+ }
637
+
638
+ const doctorResult = await runDoctor(runtime, resolvedConfig, {
639
+ local_url: params.local_url,
640
+ tailscale_https_port: params.tailscale_https_port,
641
+ dashboard_output_path: params.dashboard_output_path,
642
+ repo_dir: params.repo_dir
643
+ });
644
+
645
+ return {
646
+ ok: doctorResult.ok,
647
+ errors: doctorResult.errors,
648
+ data: {
649
+ workflow: 'onboard',
650
+ init: initResult.data,
651
+ quickstart_ran: shouldQuickstart,
652
+ quickstart: quickstartResult?.data,
653
+ doctor: doctorResult.data,
654
+ next_steps: doctorResult.data?.next_steps ?? []
655
+ }
656
+ };
657
+ }
658
+
472
659
  export function registerPlashboardPlugin(api: UnknownApi): void {
473
660
  const config = resolveConfig(api);
474
661
  const runtimeCommand = api.runtime?.system?.runCommandWithTimeout;
@@ -513,6 +700,31 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
513
700
  }
514
701
  });
515
702
 
703
+ api.registerTool?.({
704
+ name: 'plashboard_onboard',
705
+ description: 'Run complete onboarding flow: init, first template (if needed), and readiness doctor.',
706
+ optional: true,
707
+ parameters: {
708
+ type: 'object',
709
+ properties: {
710
+ description: { type: 'string' },
711
+ template_id: { type: 'string' },
712
+ template_name: { type: 'string' },
713
+ every_minutes: { type: 'number' },
714
+ activate: { type: 'boolean' },
715
+ run_now: { type: 'boolean' },
716
+ local_url: { type: 'string' },
717
+ tailscale_https_port: { type: 'number' },
718
+ dashboard_output_path: { type: 'string' },
719
+ repo_dir: { type: 'string' },
720
+ force_quickstart: { type: 'boolean' }
721
+ },
722
+ additionalProperties: false
723
+ },
724
+ execute: async (_toolCallId: unknown, params: OnboardParams = {}) =>
725
+ toToolResult(await runOnboard(runtime, config, params))
726
+ });
727
+
516
728
  api.registerTool?.({
517
729
  name: 'plashboard_exposure_guide',
518
730
  description: 'Return copy-paste commands to expose dashboard UI over existing Tailscale.',
@@ -547,6 +759,40 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
547
759
  toToolResult(await runExposureCheck(config, params))
548
760
  });
549
761
 
762
+ api.registerTool?.({
763
+ name: 'plashboard_web_guide',
764
+ description: 'Return exact commands to start the local plashboard web UI server.',
765
+ optional: true,
766
+ parameters: {
767
+ type: 'object',
768
+ properties: {
769
+ local_url: { type: 'string' },
770
+ repo_dir: { type: 'string' }
771
+ },
772
+ additionalProperties: false
773
+ },
774
+ execute: async (_toolCallId: unknown, params: WebGuideParams = {}) =>
775
+ toToolResult(await buildWebGuide(config, params))
776
+ });
777
+
778
+ api.registerTool?.({
779
+ name: 'plashboard_doctor',
780
+ description: 'Run full plashboard readiness checks (templates, local UI, and tailscale mapping).',
781
+ optional: true,
782
+ parameters: {
783
+ type: 'object',
784
+ properties: {
785
+ local_url: { type: 'string' },
786
+ tailscale_https_port: { type: 'number' },
787
+ dashboard_output_path: { type: 'string' },
788
+ repo_dir: { type: 'string' }
789
+ },
790
+ additionalProperties: false
791
+ },
792
+ execute: async (_toolCallId: unknown, params: DoctorParams = {}) =>
793
+ toToolResult(await runDoctor(runtime, config, params))
794
+ });
795
+
550
796
  api.registerTool?.({
551
797
  name: 'plashboard_setup',
552
798
  description: 'Bootstrap or update plashboard plugin configuration in openclaw.json.',
@@ -603,7 +849,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
603
849
  additionalProperties: false
604
850
  },
605
851
  execute: async (_toolCallId: unknown, params: QuickstartParams = {}) =>
606
- toToolResult(await runtime.quickstart(params))
852
+ toToolResult(await runQuickstart(runtime, config, params))
607
853
  });
608
854
 
609
855
  api.registerTool?.({
@@ -809,6 +1055,43 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
809
1055
  })
810
1056
  );
811
1057
  }
1058
+ if (cmd === 'web-guide') {
1059
+ const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
1060
+ const repoDir = rest.find((token) => token.startsWith('/'));
1061
+ return toCommandResult(
1062
+ await buildWebGuide(config, {
1063
+ local_url: localUrl,
1064
+ repo_dir: repoDir
1065
+ })
1066
+ );
1067
+ }
1068
+ if (cmd === 'doctor') {
1069
+ const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
1070
+ const portToken = rest.find((token) => /^[0-9]+$/.test(token));
1071
+ const repoDir = rest.find((token) => token.startsWith('/'));
1072
+ return toCommandResult(
1073
+ await runDoctor(runtime, config, {
1074
+ local_url: localUrl,
1075
+ tailscale_https_port: portToken ? Number(portToken) : undefined,
1076
+ repo_dir: repoDir
1077
+ })
1078
+ );
1079
+ }
1080
+ if (cmd === 'onboard') {
1081
+ const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
1082
+ const portToken = rest.find((token) => /^[0-9]+$/.test(token));
1083
+ const repoDir = rest.find((token) => token.startsWith('/'));
1084
+ const descriptionTokens = rest.filter((token) => token !== localUrl && token !== portToken && token !== repoDir);
1085
+ const description = descriptionTokens.join(' ').trim() || undefined;
1086
+ return toCommandResult(
1087
+ await runOnboard(runtime, config, {
1088
+ description,
1089
+ local_url: localUrl,
1090
+ tailscale_https_port: portToken ? Number(portToken) : undefined,
1091
+ repo_dir: repoDir
1092
+ })
1093
+ );
1094
+ }
812
1095
  if (cmd === 'setup') {
813
1096
  const mode = asString(rest[0]).toLowerCase();
814
1097
  const fillProvider = mode === 'command' || mode === 'mock' || mode === 'openclaw' ? mode : undefined;
@@ -824,7 +1107,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
824
1107
  }
825
1108
  if (cmd === 'quickstart') {
826
1109
  const description = rest.join(' ').trim() || undefined;
827
- return toCommandResult(await runtime.quickstart({ description }));
1110
+ return toCommandResult(await runQuickstart(runtime, config, { description }));
828
1111
  }
829
1112
  if (cmd === 'init') return toCommandResult(await runtime.init());
830
1113
  if (cmd === 'status') return toCommandResult(await runtime.status());
@@ -850,7 +1133,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
850
1133
  return toCommandResult({
851
1134
  ok: false,
852
1135
  errors: [
853
- 'unknown command. supported: setup [openclaw [agent_id]|mock|command <fill_command>], quickstart <description>, expose-guide [local_url] [https_port], expose-check [local_url] [https_port], init, status, list, activate <id>, delete <id>, copy <src> <new-id> [new-name] [activate], run <id>, set-display <width> <height> <top> <bottom>'
1136
+ 'unknown command. supported: onboard <description> [local_url] [https_port] [repo_dir], setup [openclaw [agent_id]|mock|command <fill_command>], quickstart <description>, doctor [local_url] [https_port] [repo_dir], web-guide [local_url] [repo_dir], expose-guide [local_url] [https_port], expose-check [local_url] [https_port], init, status, list, activate <id>, delete <id>, copy <src> <new-id> [new-name] [activate], run <id>, set-display <width> <height> <top> <bottom>'
854
1137
  ]
855
1138
  });
856
1139
  }