@jhytabest/plashboard 0.1.11 → 1.0.0

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/src/publisher.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
- import { spawn } from 'node:child_process';
3
2
  import { dirname, join } from 'node:path';
3
+ import { runAndReadStdout, type CommandRunner } from './command-runner.js';
4
4
  import type { DisplayProfile, PlashboardConfig } from './types.js';
5
5
 
6
6
  interface PublishOptions {
@@ -9,49 +9,11 @@ interface PublishOptions {
9
9
  displayProfile: DisplayProfile;
10
10
  }
11
11
 
12
- function spawnPython(
13
- pythonBin: string,
14
- scriptPath: string,
15
- args: string[],
16
- profile: DisplayProfile,
17
- tolerance: number
18
- ): Promise<string> {
19
- return new Promise((resolve, reject) => {
20
- const child = spawn(pythonBin, [scriptPath, ...args], {
21
- env: {
22
- ...process.env,
23
- PLASH_TARGET_VIEWPORT_HEIGHT: String(profile.height_px),
24
- PLASH_LAYOUT_SAFETY_MARGIN: String(profile.layout_safety_margin_px),
25
- PLASH_LAYOUT_OVERFLOW_TOLERANCE: String(tolerance),
26
- PLASH_FRAME_GUTTER_TOP: String(profile.safe_top_px),
27
- PLASH_FRAME_GUTTER_BOTTOM: String(profile.safe_bottom_px)
28
- }
29
- });
30
-
31
- let stdout = '';
32
- let stderr = '';
33
-
34
- child.stdout.on('data', (chunk) => {
35
- stdout += String(chunk);
36
- });
37
- child.stderr.on('data', (chunk) => {
38
- stderr += String(chunk);
39
- });
40
-
41
- child.on('error', (error) => reject(error));
42
-
43
- child.on('close', (code) => {
44
- if (code !== 0) {
45
- reject(new Error(stderr.trim() || stdout.trim() || `writer script failed with code ${code}`));
46
- return;
47
- }
48
- resolve(stdout.trim());
49
- });
50
- });
51
- }
52
-
53
12
  export class DashboardValidatorPublisher {
54
- constructor(private readonly config: PlashboardConfig) {}
13
+ constructor(
14
+ private readonly config: PlashboardConfig,
15
+ private readonly commandRunner: CommandRunner | null
16
+ ) {}
55
17
 
56
18
  async validateOnly(payload: Record<string, unknown>, displayProfile: DisplayProfile): Promise<void> {
57
19
  await this.run(payload, {
@@ -75,22 +37,28 @@ export class DashboardValidatorPublisher {
75
37
  try {
76
38
  await writeFile(inputPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
77
39
 
78
- const args = ['--input', inputPath];
40
+ const argv = [this.config.python_bin, this.config.writer_script_path, '--input', inputPath];
79
41
  if (options.validateOnly) {
80
- args.push('--validate-only');
42
+ argv.push('--validate-only');
81
43
  } else {
82
- args.push('--output', options.outputPath);
44
+ argv.push('--output', options.outputPath);
83
45
  }
84
46
 
85
- const output = await spawnPython(
86
- this.config.python_bin,
87
- this.config.writer_script_path,
88
- args,
89
- options.displayProfile,
90
- this.config.layout_overflow_tolerance_px
47
+ return await runAndReadStdout(
48
+ this.commandRunner,
49
+ argv,
50
+ {
51
+ timeoutMs: Math.max(15, this.config.session_timeout_seconds) * 1000,
52
+ env: {
53
+ PLASH_TARGET_VIEWPORT_HEIGHT: String(options.displayProfile.height_px),
54
+ PLASH_LAYOUT_SAFETY_MARGIN: String(options.displayProfile.layout_safety_margin_px),
55
+ PLASH_LAYOUT_OVERFLOW_TOLERANCE: String(this.config.layout_overflow_tolerance_px),
56
+ PLASH_FRAME_GUTTER_TOP: String(options.displayProfile.safe_top_px),
57
+ PLASH_FRAME_GUTTER_BOTTOM: String(options.displayProfile.safe_bottom_px)
58
+ }
59
+ },
60
+ 'dashboard writer'
91
61
  );
92
-
93
- return output;
94
62
  } finally {
95
63
  await rm(tempDir, { recursive: true, force: true });
96
64
  }
@@ -1,9 +1,10 @@
1
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
1
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import { PlashboardRuntime } from './runtime.js';
6
6
  import type { DashboardTemplate, PlashboardConfig } from './types.js';
7
+ import type { CommandRunner } from './command-runner.js';
7
8
 
8
9
  function baseDashboard() {
9
10
  return {
@@ -63,6 +64,7 @@ async function setupRuntime(overrides: Partial<PlashboardConfig> = {}) {
63
64
  session_timeout_seconds: 30,
64
65
  auto_seed_template: false,
65
66
  fill_provider: 'mock',
67
+ allow_command_fill: false,
66
68
  fill_command: undefined,
67
69
  python_bin: 'python3',
68
70
  writer_script_path: join(process.cwd(), 'scripts', 'dashboard_write.py'),
@@ -80,11 +82,57 @@ async function setupRuntime(overrides: Partial<PlashboardConfig> = {}) {
80
82
  ...overrides
81
83
  };
82
84
 
83
- const runtime = new PlashboardRuntime(config);
85
+ const runtime = new PlashboardRuntime(config, undefined, {
86
+ commandRunner: createTestCommandRunner()
87
+ });
84
88
  await runtime.init();
85
89
  return { runtime, root, config };
86
90
  }
87
91
 
92
+ function createTestCommandRunner(): CommandRunner {
93
+ return async (argv: string[]) => {
94
+ if (argv[0] === 'openclaw' && argv[1] === 'agent') {
95
+ return {
96
+ stdout: '{"values":{"summary":"updated summary from openclaw"}}',
97
+ stderr: '',
98
+ code: 0
99
+ };
100
+ }
101
+
102
+ const inputIndex = argv.indexOf('--input');
103
+ if (argv[0] === 'python3' && inputIndex >= 0) {
104
+ const inputPath = argv[inputIndex + 1];
105
+ const outputIndex = argv.indexOf('--output');
106
+ const validateOnly = argv.includes('--validate-only');
107
+ const inputText = await readFile(inputPath, 'utf8');
108
+ const payload = JSON.parse(inputText) as Record<string, unknown>;
109
+
110
+ const normalized: Record<string, unknown> = {
111
+ ...payload,
112
+ version: typeof payload.version === 'string' ? payload.version : '3.0',
113
+ generated_at: new Date().toISOString()
114
+ };
115
+
116
+ if (!validateOnly && outputIndex >= 0) {
117
+ const outputPath = argv[outputIndex + 1];
118
+ await writeFile(outputPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
119
+ }
120
+
121
+ return {
122
+ stdout: 'ok',
123
+ stderr: '',
124
+ code: 0
125
+ };
126
+ }
127
+
128
+ return {
129
+ stdout: '',
130
+ stderr: `unsupported test command: ${argv.join(' ')}`,
131
+ code: 1
132
+ };
133
+ };
134
+ }
135
+
88
136
  describe('PlashboardRuntime', () => {
89
137
  it('creates template and runs pipeline with publish', async () => {
90
138
  const { runtime, root, config } = await setupRuntime();
@@ -175,6 +223,8 @@ describe('PlashboardRuntime', () => {
175
223
  const status = await runtime.status();
176
224
  expect(status.ok).toBe(true);
177
225
  expect(status.data?.active_template_id).toBe('starter');
226
+ expect(status.data?.capabilities.runtime_command_runner_available).toBe(true);
227
+ expect(status.data?.capabilities.command_fill_allowed).toBe(false);
178
228
  } finally {
179
229
  await rm(root, { recursive: true, force: true });
180
230
  }
package/src/runtime.ts CHANGED
@@ -18,6 +18,7 @@ import { collectCurrentValues, mergeTemplateValues, validateFieldPointers } from
18
18
  import { validateFillShape, validateTemplateShape } from './schema-validation.js';
19
19
  import { DashboardValidatorPublisher } from './publisher.js';
20
20
  import { createFillRunner, type FillRunnerDeps } from './fill-runner.js';
21
+ import type { CommandRunner } from './command-runner.js';
21
22
 
22
23
  interface Logger {
23
24
  info(message: string, ...args: unknown[]): void;
@@ -76,6 +77,7 @@ export class PlashboardRuntime {
76
77
  private readonly runStore: RunStore;
77
78
  private readonly publisher: DashboardValidatorPublisher;
78
79
  private readonly fillRunner: FillRunner;
80
+ private readonly runtimeCommandRunnerAvailable: boolean;
79
81
 
80
82
  private schedulerTimer: NodeJS.Timeout | null = null;
81
83
  private tickInProgress = false;
@@ -91,7 +93,9 @@ export class PlashboardRuntime {
91
93
  this.stateStore = new StateStore(this.paths);
92
94
  this.templateStore = new TemplateStore(this.paths);
93
95
  this.runStore = new RunStore(this.paths);
94
- this.publisher = new DashboardValidatorPublisher(config);
96
+ const commandRunner: CommandRunner | null = fillRunnerDeps.commandRunner ?? null;
97
+ this.runtimeCommandRunnerAvailable = Boolean(commandRunner);
98
+ this.publisher = new DashboardValidatorPublisher(config, commandRunner);
95
99
  this.fillRunner = createFillRunner(config, fillRunnerDeps);
96
100
  }
97
101
 
@@ -462,6 +466,10 @@ export class PlashboardRuntime {
462
466
  template_count: templates.length,
463
467
  enabled_template_count: templates.filter((entry) => entry.enabled).length,
464
468
  running_template_ids: [...this.runningTemplates],
469
+ capabilities: {
470
+ runtime_command_runner_available: this.runtimeCommandRunnerAvailable,
471
+ command_fill_allowed: this.config.allow_command_fill
472
+ },
465
473
  state
466
474
  }
467
475
  };
package/src/types.ts CHANGED
@@ -25,6 +25,7 @@ export interface PlashboardConfig {
25
25
  session_timeout_seconds: number;
26
26
  auto_seed_template: boolean;
27
27
  fill_provider: 'command' | 'mock' | 'openclaw';
28
+ allow_command_fill: boolean;
28
29
  fill_command?: string;
29
30
  openclaw_fill_agent_id?: string;
30
31
  python_bin: string;
@@ -137,5 +138,9 @@ export interface RuntimeStatus {
137
138
  template_count: number;
138
139
  enabled_template_count: number;
139
140
  running_template_ids: string[];
141
+ capabilities: {
142
+ runtime_command_runner_available: boolean;
143
+ command_fill_allowed: boolean;
144
+ };
140
145
  state: PlashboardState;
141
146
  }