@jhytabest/plashboard 0.1.10 → 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/README.md +31 -1
- package/openclaw.plugin.json +2 -1
- package/package.json +3 -2
- package/skills/plashboard-admin/SKILL.md +15 -1
- package/src/command-runner.ts +120 -0
- package/src/config.ts +1 -0
- package/src/fill-runner.test.ts +18 -1
- package/src/fill-runner.ts +8 -103
- package/src/plugin.test.ts +186 -0
- package/src/plugin.ts +389 -120
- package/src/publisher.ts +22 -54
- package/src/runtime.test.ts +52 -2
- package/src/runtime.ts +9 -1
- package/src/types.ts +5 -0
package/src/plugin.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { constants as fsConstants } from 'node:fs';
|
|
3
|
-
import { access, stat } from 'node:fs/promises';
|
|
2
|
+
import { access, chmod, stat } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
4
|
import type { DisplayProfile, ToolResponse } from './types.js';
|
|
5
5
|
import { resolveConfig } from './config.js';
|
|
6
6
|
import { PlashboardRuntime } from './runtime.js';
|
|
7
|
+
import {
|
|
8
|
+
createRuntimeCommandRunner,
|
|
9
|
+
runCommand,
|
|
10
|
+
type CommandRunner,
|
|
11
|
+
type RuntimeCommandWithTimeout
|
|
12
|
+
} from './command-runner.js';
|
|
7
13
|
|
|
8
14
|
type UnknownApi = {
|
|
9
15
|
registerTool?: (definition: unknown) => void;
|
|
@@ -20,24 +26,7 @@ type UnknownApi = {
|
|
|
20
26
|
writeConfigFile?: (nextConfig: unknown) => Promise<void>;
|
|
21
27
|
};
|
|
22
28
|
system?: {
|
|
23
|
-
runCommandWithTimeout?:
|
|
24
|
-
argv: string[],
|
|
25
|
-
optionsOrTimeout: number | {
|
|
26
|
-
timeoutMs: number;
|
|
27
|
-
cwd?: string;
|
|
28
|
-
input?: string;
|
|
29
|
-
env?: NodeJS.ProcessEnv;
|
|
30
|
-
windowsVerbatimArguments?: boolean;
|
|
31
|
-
noOutputTimeoutMs?: number;
|
|
32
|
-
}
|
|
33
|
-
) => Promise<{
|
|
34
|
-
stdout: string;
|
|
35
|
-
stderr: string;
|
|
36
|
-
code: number | null;
|
|
37
|
-
signal?: NodeJS.Signals | null;
|
|
38
|
-
killed?: boolean;
|
|
39
|
-
termination?: string;
|
|
40
|
-
}>;
|
|
29
|
+
runCommandWithTimeout?: RuntimeCommandWithTimeout;
|
|
41
30
|
};
|
|
42
31
|
};
|
|
43
32
|
config?: unknown;
|
|
@@ -88,6 +77,7 @@ function asErrorMessage(error: unknown): string {
|
|
|
88
77
|
|
|
89
78
|
type SetupParams = {
|
|
90
79
|
fill_provider?: 'mock' | 'command' | 'openclaw';
|
|
80
|
+
allow_command_fill?: boolean;
|
|
91
81
|
fill_command?: string;
|
|
92
82
|
openclaw_fill_agent_id?: string;
|
|
93
83
|
auto_seed_template?: boolean;
|
|
@@ -126,16 +116,12 @@ type DoctorParams = ExposureParams & {
|
|
|
126
116
|
repo_dir?: string;
|
|
127
117
|
};
|
|
128
118
|
|
|
129
|
-
type
|
|
130
|
-
|
|
119
|
+
type PermissionsFixParams = {
|
|
120
|
+
dashboard_output_path?: string;
|
|
131
121
|
};
|
|
132
122
|
|
|
133
|
-
type
|
|
134
|
-
|
|
135
|
-
stdout: string;
|
|
136
|
-
stderr: string;
|
|
137
|
-
code: number | null;
|
|
138
|
-
error?: string;
|
|
123
|
+
type OnboardParams = DoctorParams & QuickstartParams & {
|
|
124
|
+
force_quickstart?: boolean;
|
|
139
125
|
};
|
|
140
126
|
|
|
141
127
|
function normalizeLocalUrl(raw: string | undefined): string {
|
|
@@ -155,61 +141,99 @@ function normalizePort(raw: number | undefined, fallback: number): number {
|
|
|
155
141
|
return Math.max(1, Math.min(65535, value));
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
144
|
+
function parseJsonLoose(input: string): unknown | undefined {
|
|
145
|
+
const trimmed = input.trim();
|
|
146
|
+
if (!trimmed) return undefined;
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(trimmed);
|
|
149
|
+
} catch {
|
|
150
|
+
// continue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const starts = ['{', '['];
|
|
154
|
+
for (const opener of starts) {
|
|
155
|
+
const start = trimmed.indexOf(opener);
|
|
156
|
+
if (start < 0) continue;
|
|
157
|
+
const closer = opener === '{' ? '}' : ']';
|
|
158
|
+
const end = trimmed.lastIndexOf(closer);
|
|
159
|
+
if (end <= start) continue;
|
|
160
|
+
const candidate = trimmed.slice(start, end + 1);
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(candidate);
|
|
163
|
+
} catch {
|
|
164
|
+
// continue
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
function octalMode(value: number | undefined): string | undefined {
|
|
171
|
+
if (!Number.isFinite(value)) return undefined;
|
|
172
|
+
return `0${(value! & 0o777).toString(8).padStart(3, '0')}`;
|
|
173
|
+
}
|
|
167
174
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
async function listOpenClawAgentIds(commandRunner: CommandRunner | null): Promise<{
|
|
176
|
+
ok: boolean;
|
|
177
|
+
ids: string[];
|
|
178
|
+
error?: string;
|
|
179
|
+
}> {
|
|
180
|
+
const result = await runCommand(
|
|
181
|
+
commandRunner,
|
|
182
|
+
['openclaw', 'agents', 'list', '--json'],
|
|
183
|
+
12_000,
|
|
184
|
+
'openclaw agents list'
|
|
185
|
+
);
|
|
186
|
+
if (!result.ok) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
ids: [],
|
|
190
|
+
error: result.error || result.stderr || `exit ${String(result.code)}`
|
|
172
191
|
};
|
|
192
|
+
}
|
|
173
193
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
stdout,
|
|
179
|
-
stderr,
|
|
180
|
-
code: null,
|
|
181
|
-
error: `timed out after ${Math.floor(timeoutMs / 1000)}s`
|
|
182
|
-
});
|
|
183
|
-
}, timeoutMs);
|
|
194
|
+
const parsed = parseJsonLoose(result.stdout);
|
|
195
|
+
if (!Array.isArray(parsed)) {
|
|
196
|
+
return { ok: false, ids: [], error: 'unable to parse openclaw agents list output' };
|
|
197
|
+
}
|
|
184
198
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
const ids = parsed
|
|
200
|
+
.map((entry) => asObject(entry))
|
|
201
|
+
.map((entry) => asString(entry.id))
|
|
202
|
+
.filter(Boolean);
|
|
203
|
+
return { ok: true, ids };
|
|
204
|
+
}
|
|
191
205
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
206
|
+
async function checkWriterPreflight(
|
|
207
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
208
|
+
commandRunner: CommandRunner | null
|
|
209
|
+
): Promise<{
|
|
210
|
+
ready: boolean;
|
|
211
|
+
errors: string[];
|
|
212
|
+
python_version?: string;
|
|
213
|
+
}> {
|
|
214
|
+
const errors: string[] = [];
|
|
202
215
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
try {
|
|
217
|
+
await access(resolvedConfig.writer_script_path, fsConstants.R_OK);
|
|
218
|
+
} catch {
|
|
219
|
+
errors.push(`writer script is not readable: ${resolvedConfig.writer_script_path}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const version = await runCommand(
|
|
223
|
+
commandRunner,
|
|
224
|
+
[resolvedConfig.python_bin, '--version'],
|
|
225
|
+
8_000,
|
|
226
|
+
'python runtime preflight'
|
|
227
|
+
);
|
|
228
|
+
if (!version.ok) {
|
|
229
|
+
errors.push(`python runtime check failed: ${version.error || version.stderr || `exit ${String(version.code)}`}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
ready: errors.length === 0,
|
|
234
|
+
errors,
|
|
235
|
+
python_version: version.ok ? (version.stdout || version.stderr) : undefined
|
|
236
|
+
};
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
async function buildExposureGuide(resolvedConfig: ReturnType<typeof resolveConfig>, params: ExposureParams = {}) {
|
|
@@ -232,7 +256,8 @@ async function buildExposureGuide(resolvedConfig: ReturnType<typeof resolveConfi
|
|
|
232
256
|
],
|
|
233
257
|
checks: [
|
|
234
258
|
`test -f ${dashboardPath}`,
|
|
235
|
-
`curl -I ${localUrl}
|
|
259
|
+
`curl -I ${localUrl}`,
|
|
260
|
+
`curl -I ${localUrl.replace(/\/$/, '')}/data/dashboard.json`
|
|
236
261
|
],
|
|
237
262
|
notes: [
|
|
238
263
|
'plashboard only writes dashboard JSON; your local UI/server must serve it.',
|
|
@@ -277,15 +302,21 @@ async function buildWebGuide(resolvedConfig: ReturnType<typeof resolveConfig>, p
|
|
|
277
302
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
278
303
|
}
|
|
279
304
|
|
|
280
|
-
async function runExposureCheck(
|
|
305
|
+
async function runExposureCheck(
|
|
306
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
307
|
+
commandRunner: CommandRunner | null,
|
|
308
|
+
params: ExposureParams = {}
|
|
309
|
+
) {
|
|
281
310
|
const localUrl = normalizeLocalUrl(params.local_url);
|
|
282
311
|
const httpsPort = normalizePort(asNumber(params.tailscale_https_port), 8444);
|
|
283
312
|
const dashboardPath = (params.dashboard_output_path || resolvedConfig.dashboard_output_path).trim();
|
|
313
|
+
const dataDirPath = dirname(dashboardPath);
|
|
284
314
|
const errors: string[] = [];
|
|
285
315
|
|
|
286
316
|
let dashboardExists = false;
|
|
287
317
|
let dashboardSizeBytes: number | undefined;
|
|
288
318
|
let dashboardMtimeIso: string | undefined;
|
|
319
|
+
let dataDirMode: number | undefined;
|
|
289
320
|
|
|
290
321
|
try {
|
|
291
322
|
await access(dashboardPath, fsConstants.R_OK);
|
|
@@ -296,10 +327,20 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
296
327
|
} catch {
|
|
297
328
|
errors.push(`dashboard file is not readable: ${dashboardPath}`);
|
|
298
329
|
}
|
|
330
|
+
try {
|
|
331
|
+
const dirInfo = await stat(dataDirPath);
|
|
332
|
+
dataDirMode = dirInfo.mode & 0o777;
|
|
333
|
+
} catch {
|
|
334
|
+
// ignore
|
|
335
|
+
}
|
|
299
336
|
|
|
300
337
|
let localUrlOk = false;
|
|
301
338
|
let localStatusCode: number | undefined;
|
|
302
339
|
let localError: string | undefined;
|
|
340
|
+
const localDashboardUrl = new URL('/data/dashboard.json', localUrl).toString();
|
|
341
|
+
let localDashboardOk = false;
|
|
342
|
+
let localDashboardStatusCode: number | undefined;
|
|
343
|
+
let localDashboardError: string | undefined;
|
|
303
344
|
|
|
304
345
|
try {
|
|
305
346
|
const controller = new AbortController();
|
|
@@ -319,17 +360,43 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
319
360
|
errors.push(`local dashboard URL is not reachable: ${localUrl} (${localError})`);
|
|
320
361
|
}
|
|
321
362
|
|
|
322
|
-
|
|
363
|
+
try {
|
|
364
|
+
const controller = new AbortController();
|
|
365
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
366
|
+
const response = await fetch(localDashboardUrl, {
|
|
367
|
+
method: 'GET',
|
|
368
|
+
signal: controller.signal
|
|
369
|
+
});
|
|
370
|
+
clearTimeout(timer);
|
|
371
|
+
localDashboardStatusCode = response.status;
|
|
372
|
+
localDashboardOk = response.status >= 200 && response.status < 300;
|
|
373
|
+
if (!localDashboardOk) {
|
|
374
|
+
errors.push(`dashboard JSON URL returned status ${response.status}: ${localDashboardUrl}`);
|
|
375
|
+
if (response.status === 403) {
|
|
376
|
+
errors.push(`dashboard JSON access denied; check directory permissions for ${dataDirPath}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
localDashboardError = asErrorMessage(error);
|
|
381
|
+
errors.push(`dashboard JSON URL is not reachable: ${localDashboardUrl} (${localDashboardError})`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const tailscale = await runCommand(commandRunner, ['tailscale', 'serve', 'status'], 8000, 'tailscale serve status');
|
|
323
385
|
const tailscaleOutput = `${tailscale.stdout}\n${tailscale.stderr}`.trim();
|
|
324
386
|
let tailscalePortConfigured = false;
|
|
387
|
+
let tailscaleTargetConfigured = false;
|
|
325
388
|
|
|
326
389
|
if (!tailscale.ok) {
|
|
327
390
|
errors.push(`tailscale serve status failed: ${tailscale.error || tailscale.stderr || `exit ${tailscale.code}`}`);
|
|
328
391
|
} else {
|
|
329
392
|
tailscalePortConfigured = tailscaleOutput.includes(`:${httpsPort}`);
|
|
393
|
+
tailscaleTargetConfigured = tailscaleOutput.includes(`proxy ${localUrl.replace(/\/$/, '')}`);
|
|
330
394
|
if (!tailscalePortConfigured) {
|
|
331
395
|
errors.push(`tailscale serve has no mapping for https port ${httpsPort}`);
|
|
332
396
|
}
|
|
397
|
+
if (!tailscaleTargetConfigured) {
|
|
398
|
+
errors.push(`tailscale serve mapping does not target ${localUrl}`);
|
|
399
|
+
}
|
|
333
400
|
}
|
|
334
401
|
|
|
335
402
|
return {
|
|
@@ -344,15 +411,28 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
344
411
|
local_url_ok: localUrlOk,
|
|
345
412
|
local_status_code: localStatusCode,
|
|
346
413
|
local_error: localError,
|
|
414
|
+
local_dashboard_url: localDashboardUrl,
|
|
415
|
+
local_dashboard_ok: localDashboardOk,
|
|
416
|
+
local_dashboard_status_code: localDashboardStatusCode,
|
|
417
|
+
local_dashboard_error: localDashboardError,
|
|
418
|
+
data_dir_path: dataDirPath,
|
|
419
|
+
data_dir_mode: dataDirMode,
|
|
420
|
+
data_dir_mode_octal: octalMode(dataDirMode),
|
|
347
421
|
tailscale_https_port: httpsPort,
|
|
348
422
|
tailscale_status_ok: tailscale.ok,
|
|
349
423
|
tailscale_port_configured: tailscalePortConfigured,
|
|
424
|
+
tailscale_target_configured: tailscaleTargetConfigured,
|
|
350
425
|
tailscale_status_excerpt: tailscaleOutput.slice(0, 1200)
|
|
351
426
|
}
|
|
352
427
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
353
428
|
}
|
|
354
429
|
|
|
355
|
-
async function runSetup(
|
|
430
|
+
async function runSetup(
|
|
431
|
+
api: UnknownApi,
|
|
432
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
433
|
+
commandRunner: CommandRunner | null,
|
|
434
|
+
params: SetupParams = {}
|
|
435
|
+
) {
|
|
356
436
|
const loadConfig = api.runtime?.config?.loadConfig;
|
|
357
437
|
const writeConfigFile = api.runtime?.config?.writeConfigFile;
|
|
358
438
|
|
|
@@ -421,6 +501,13 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
421
501
|
? currentPluginConfig.auto_seed_template
|
|
422
502
|
: resolvedConfig.auto_seed_template
|
|
423
503
|
);
|
|
504
|
+
const selectedAllowCommandFill = (
|
|
505
|
+
typeof params.allow_command_fill === 'boolean'
|
|
506
|
+
? params.allow_command_fill
|
|
507
|
+
: typeof currentPluginConfig.allow_command_fill === 'boolean'
|
|
508
|
+
? Boolean(currentPluginConfig.allow_command_fill)
|
|
509
|
+
: resolvedConfig.allow_command_fill
|
|
510
|
+
);
|
|
424
511
|
const selectedAgentId = (
|
|
425
512
|
params.openclaw_fill_agent_id
|
|
426
513
|
|| asString(currentPluginConfig.openclaw_fill_agent_id)
|
|
@@ -434,12 +521,50 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
434
521
|
errors: ['fill_provider=command requires fill_command']
|
|
435
522
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
436
523
|
}
|
|
524
|
+
if (selectedProvider === 'command' && !selectedAllowCommandFill) {
|
|
525
|
+
return {
|
|
526
|
+
ok: false,
|
|
527
|
+
errors: ['fill_provider=command requires allow_command_fill=true']
|
|
528
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
529
|
+
}
|
|
530
|
+
if (selectedProvider === 'command' && !commandRunner) {
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
errors: ['fill_provider=command requires runtime command runner support in this OpenClaw build']
|
|
534
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
535
|
+
}
|
|
437
536
|
if (selectedProvider === 'openclaw' && !selectedAgentId) {
|
|
438
537
|
return {
|
|
439
538
|
ok: false,
|
|
440
539
|
errors: ['fill_provider=openclaw requires openclaw_fill_agent_id']
|
|
441
540
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
442
541
|
}
|
|
542
|
+
if (selectedProvider === 'openclaw') {
|
|
543
|
+
const agents = await listOpenClawAgentIds(commandRunner);
|
|
544
|
+
if (!agents.ok) {
|
|
545
|
+
return {
|
|
546
|
+
ok: false,
|
|
547
|
+
errors: [`unable to validate openclaw_fill_agent_id: ${agents.error || 'unknown error'}`]
|
|
548
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
549
|
+
}
|
|
550
|
+
if (!agents.ids.includes(selectedAgentId)) {
|
|
551
|
+
return {
|
|
552
|
+
ok: false,
|
|
553
|
+
errors: [
|
|
554
|
+
`openclaw_fill_agent_id not found: ${selectedAgentId}`,
|
|
555
|
+
`available agent ids: ${agents.ids.join(', ') || '(none)'}`
|
|
556
|
+
]
|
|
557
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const preflight = await checkWriterPreflight(resolvedConfig, commandRunner);
|
|
562
|
+
if (!preflight.ready) {
|
|
563
|
+
return {
|
|
564
|
+
ok: false,
|
|
565
|
+
errors: preflight.errors
|
|
566
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
567
|
+
}
|
|
443
568
|
|
|
444
569
|
const nextPluginConfig: Record<string, unknown> = {
|
|
445
570
|
...currentPluginConfig,
|
|
@@ -461,6 +586,7 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
461
586
|
)
|
|
462
587
|
),
|
|
463
588
|
fill_provider: selectedProvider,
|
|
589
|
+
allow_command_fill: selectedAllowCommandFill,
|
|
464
590
|
auto_seed_template: selectedAutoSeed,
|
|
465
591
|
display_profile: displayProfile
|
|
466
592
|
};
|
|
@@ -501,6 +627,7 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
501
627
|
restart_required: true,
|
|
502
628
|
plugin_id: 'plashboard',
|
|
503
629
|
fill_provider: selectedProvider,
|
|
630
|
+
allow_command_fill: selectedAllowCommandFill,
|
|
504
631
|
fill_command: selectedProvider === 'command' ? selectedCommand : undefined,
|
|
505
632
|
openclaw_fill_agent_id: selectedProvider === 'openclaw' ? selectedAgentId : undefined,
|
|
506
633
|
auto_seed_template: selectedAutoSeed,
|
|
@@ -508,6 +635,7 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
508
635
|
scheduler_tick_seconds: nextPluginConfig.scheduler_tick_seconds,
|
|
509
636
|
session_timeout_seconds: nextPluginConfig.session_timeout_seconds,
|
|
510
637
|
display_profile: displayProfile,
|
|
638
|
+
python_version: preflight.python_version,
|
|
511
639
|
next_steps: [
|
|
512
640
|
'restart OpenClaw gateway',
|
|
513
641
|
'run /plashboard init'
|
|
@@ -516,13 +644,72 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
516
644
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
517
645
|
}
|
|
518
646
|
|
|
647
|
+
async function runPermissionsFix(
|
|
648
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
649
|
+
params: PermissionsFixParams = {}
|
|
650
|
+
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
651
|
+
const dashboardPath = (params.dashboard_output_path || resolvedConfig.dashboard_output_path).trim();
|
|
652
|
+
const dataDirPath = dirname(dashboardPath);
|
|
653
|
+
const errors: string[] = [];
|
|
654
|
+
|
|
655
|
+
let beforeDataDirMode: number | undefined;
|
|
656
|
+
let beforeDashboardMode: number | undefined;
|
|
657
|
+
let afterDataDirMode: number | undefined;
|
|
658
|
+
let afterDashboardMode: number | undefined;
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
beforeDataDirMode = (await stat(dataDirPath)).mode & 0o777;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
return {
|
|
664
|
+
ok: false,
|
|
665
|
+
errors: [`data directory is missing or unreadable: ${dataDirPath} (${asErrorMessage(error)})`]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
beforeDashboardMode = (await stat(dashboardPath)).mode & 0o777;
|
|
671
|
+
} catch {
|
|
672
|
+
// dashboard file may not exist yet
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
await chmod(dataDirPath, 0o755);
|
|
677
|
+
afterDataDirMode = (await stat(dataDirPath)).mode & 0o777;
|
|
678
|
+
} catch (error) {
|
|
679
|
+
errors.push(`failed to set directory mode for ${dataDirPath}: ${asErrorMessage(error)}`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
await access(dashboardPath, fsConstants.F_OK);
|
|
684
|
+
await chmod(dashboardPath, 0o644);
|
|
685
|
+
afterDashboardMode = (await stat(dashboardPath)).mode & 0o777;
|
|
686
|
+
} catch {
|
|
687
|
+
// dashboard file may not exist yet
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return {
|
|
691
|
+
ok: errors.length === 0,
|
|
692
|
+
errors,
|
|
693
|
+
data: {
|
|
694
|
+
dashboard_output_path: dashboardPath,
|
|
695
|
+
data_dir_path: dataDirPath,
|
|
696
|
+
before_data_dir_mode_octal: octalMode(beforeDataDirMode),
|
|
697
|
+
after_data_dir_mode_octal: octalMode(afterDataDirMode),
|
|
698
|
+
before_dashboard_mode_octal: octalMode(beforeDashboardMode),
|
|
699
|
+
after_dashboard_mode_octal: octalMode(afterDashboardMode),
|
|
700
|
+
note: 'This is an explicit compatibility fix for dashboard web servers that read through bind mounts.'
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
519
705
|
async function runQuickstart(
|
|
520
706
|
runtime: PlashboardRuntime,
|
|
521
707
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
708
|
+
commandRunner: CommandRunner | null,
|
|
522
709
|
params: QuickstartParams = {}
|
|
523
710
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
524
711
|
const quickstart = await runtime.quickstart(params);
|
|
525
|
-
const exposure = await runExposureCheck(resolvedConfig, {});
|
|
712
|
+
const exposure = await runExposureCheck(resolvedConfig, commandRunner, {});
|
|
526
713
|
const guide = await buildExposureGuide(resolvedConfig, {});
|
|
527
714
|
const webGuide = await buildWebGuide(resolvedConfig, {});
|
|
528
715
|
|
|
@@ -562,19 +749,62 @@ async function runQuickstart(
|
|
|
562
749
|
async function runDoctor(
|
|
563
750
|
runtime: PlashboardRuntime,
|
|
564
751
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
752
|
+
commandRunner: CommandRunner | null,
|
|
565
753
|
params: DoctorParams = {}
|
|
566
754
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
567
755
|
const status = await runtime.status();
|
|
568
|
-
const
|
|
756
|
+
const templateList = await runtime.templateList();
|
|
757
|
+
const exposure = await runExposureCheck(resolvedConfig, commandRunner, params);
|
|
569
758
|
const exposureGuide = await buildExposureGuide(resolvedConfig, params);
|
|
570
759
|
const webGuide = await buildWebGuide(resolvedConfig, params);
|
|
760
|
+
const writerPreflight = await checkWriterPreflight(resolvedConfig, commandRunner);
|
|
571
761
|
|
|
572
762
|
const issues: string[] = [];
|
|
763
|
+
const warnings: string[] = [];
|
|
573
764
|
const statusData = status.data;
|
|
574
765
|
const templateCount = Number(statusData?.template_count ?? 0);
|
|
575
766
|
const activeTemplateId = statusData?.active_template_id || null;
|
|
767
|
+
const runtimeCommandRunnerAvailable = Boolean(statusData?.capabilities?.runtime_command_runner_available);
|
|
768
|
+
const commandFillAllowed = Boolean(statusData?.capabilities?.command_fill_allowed);
|
|
769
|
+
|
|
770
|
+
let fillProviderReady = resolvedConfig.fill_provider === 'mock'
|
|
771
|
+
? true
|
|
772
|
+
: resolvedConfig.fill_provider === 'openclaw'
|
|
773
|
+
? runtimeCommandRunnerAvailable && Boolean((resolvedConfig.openclaw_fill_agent_id || '').trim())
|
|
774
|
+
: runtimeCommandRunnerAvailable && commandFillAllowed && Boolean((resolvedConfig.fill_command || '').trim());
|
|
775
|
+
|
|
776
|
+
let fillAgentIds: string[] = [];
|
|
777
|
+
if (resolvedConfig.fill_provider === 'openclaw') {
|
|
778
|
+
const agents = await listOpenClawAgentIds(commandRunner);
|
|
779
|
+
if (!agents.ok) {
|
|
780
|
+
fillProviderReady = false;
|
|
781
|
+
issues.push(`unable to validate openclaw_fill_agent_id: ${agents.error || 'unknown error'}`);
|
|
782
|
+
} else {
|
|
783
|
+
fillAgentIds = agents.ids;
|
|
784
|
+
if (!agents.ids.includes(resolvedConfig.openclaw_fill_agent_id || 'main')) {
|
|
785
|
+
fillProviderReady = false;
|
|
786
|
+
issues.push(`openclaw_fill_agent_id not found: ${resolvedConfig.openclaw_fill_agent_id || 'main'}`);
|
|
787
|
+
}
|
|
788
|
+
if ((resolvedConfig.openclaw_fill_agent_id || 'main').trim() === 'main') {
|
|
789
|
+
warnings.push('openclaw_fill_agent_id=main can cause session lock contention; prefer a dedicated fill agent.');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const writerRunnerReady = writerPreflight.ready;
|
|
576
795
|
|
|
577
796
|
if (!status.ok) issues.push(...status.errors);
|
|
797
|
+
if (!templateList.ok) issues.push(...templateList.errors);
|
|
798
|
+
if (!fillProviderReady) {
|
|
799
|
+
if (resolvedConfig.fill_provider === 'command' && !commandFillAllowed) {
|
|
800
|
+
issues.push('fill_provider=command is disabled; set allow_command_fill=true');
|
|
801
|
+
} else {
|
|
802
|
+
issues.push(`fill provider "${resolvedConfig.fill_provider}" is not ready`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (!writerRunnerReady) {
|
|
806
|
+
issues.push(...writerPreflight.errors);
|
|
807
|
+
}
|
|
578
808
|
if (templateCount === 0) issues.push('no templates exist; run /plashboard quickstart "<description>"');
|
|
579
809
|
if (!activeTemplateId) issues.push('no active template; activate one with /plashboard activate <template-id>');
|
|
580
810
|
if (exposure.data?.dashboard_exists !== true) {
|
|
@@ -583,10 +813,38 @@ async function runDoctor(
|
|
|
583
813
|
if (exposure.data?.local_url_ok !== true) {
|
|
584
814
|
issues.push(`local dashboard server is not reachable at ${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}`);
|
|
585
815
|
}
|
|
816
|
+
if (exposure.data?.local_dashboard_ok !== true) {
|
|
817
|
+
issues.push(`dashboard JSON endpoint is not reachable at ${String(exposure.data?.local_dashboard_url || `${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}/data/dashboard.json`)}`);
|
|
818
|
+
}
|
|
586
819
|
if (exposure.data?.tailscale_status_ok !== true) {
|
|
587
820
|
issues.push('tailscale serve status failed');
|
|
588
821
|
} else if (exposure.data?.tailscale_port_configured !== true) {
|
|
589
822
|
issues.push(`tailscale serve mapping missing for port ${String(exposure.data?.tailscale_https_port || 8444)}`);
|
|
823
|
+
} else if (exposure.data?.tailscale_target_configured !== true) {
|
|
824
|
+
issues.push(`tailscale serve mapping does not target ${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}`);
|
|
825
|
+
}
|
|
826
|
+
if (Number(exposure.data?.local_dashboard_status_code) === 403) {
|
|
827
|
+
warnings.push(`dashboard JSON returned 403; run /plashboard fix-permissions to apply compatible read modes.`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const templates = Array.isArray(templateList.data?.templates) ? templateList.data?.templates : [];
|
|
831
|
+
const activeTemplate = templates?.find((entry) => asString(entry.id) === activeTemplateId);
|
|
832
|
+
const everyMinutes = asNumber(asObject(activeTemplate?.schedule).every_minutes);
|
|
833
|
+
const mtimeIso = asString(exposure.data?.dashboard_mtime_utc);
|
|
834
|
+
if (everyMinutes && mtimeIso) {
|
|
835
|
+
const ageMs = Date.now() - Date.parse(mtimeIso);
|
|
836
|
+
const maxAgeMs = Math.max(everyMinutes * 2 * 60_000, 10 * 60_000);
|
|
837
|
+
if (Number.isFinite(ageMs) && ageMs > maxAgeMs) {
|
|
838
|
+
issues.push(`dashboard appears stale: last update ${mtimeIso} (age ${Math.floor(ageMs / 60_000)}m)`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (!runtimeCommandRunnerAvailable) {
|
|
843
|
+
warnings.push('runtime command runner unavailable; fill/publish checks may fail in this OpenClaw build.');
|
|
844
|
+
}
|
|
845
|
+
const dataDirMode = asNumber(exposure.data?.data_dir_mode);
|
|
846
|
+
if (typeof dataDirMode === 'number' && (dataDirMode & 0o005) === 0) {
|
|
847
|
+
warnings.push(`data directory mode ${String(exposure.data?.data_dir_mode_octal || '')} may block containerized web readers.`);
|
|
590
848
|
}
|
|
591
849
|
|
|
592
850
|
const ready = issues.length === 0;
|
|
@@ -595,6 +853,14 @@ async function runDoctor(
|
|
|
595
853
|
errors: issues,
|
|
596
854
|
data: {
|
|
597
855
|
ready,
|
|
856
|
+
fill_provider_ready: fillProviderReady,
|
|
857
|
+
writer_runner_ready: writerRunnerReady,
|
|
858
|
+
warnings,
|
|
859
|
+
writer_preflight: {
|
|
860
|
+
ready: writerPreflight.ready,
|
|
861
|
+
python_version: writerPreflight.python_version
|
|
862
|
+
},
|
|
863
|
+
fill_agent_ids: fillAgentIds,
|
|
598
864
|
status: statusData,
|
|
599
865
|
exposure: exposure.data,
|
|
600
866
|
exposure_guide: exposureGuide.data,
|
|
@@ -605,6 +871,7 @@ async function runDoctor(
|
|
|
605
871
|
'run /plashboard quickstart "<description>" if no templates exist',
|
|
606
872
|
'run /plashboard web-guide and start local UI server',
|
|
607
873
|
'run /plashboard expose-guide and apply tailscale mapping',
|
|
874
|
+
'run /plashboard fix-permissions if dashboard JSON returns 403',
|
|
608
875
|
're-run /plashboard doctor'
|
|
609
876
|
]
|
|
610
877
|
}
|
|
@@ -614,6 +881,7 @@ async function runDoctor(
|
|
|
614
881
|
async function runOnboard(
|
|
615
882
|
runtime: PlashboardRuntime,
|
|
616
883
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
884
|
+
commandRunner: CommandRunner | null,
|
|
617
885
|
params: OnboardParams = {}
|
|
618
886
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
619
887
|
const initResult = await runtime.init();
|
|
@@ -625,7 +893,7 @@ async function runOnboard(
|
|
|
625
893
|
|
|
626
894
|
let quickstartResult: ToolResponse<Record<string, unknown>> | null = null;
|
|
627
895
|
if (shouldQuickstart) {
|
|
628
|
-
quickstartResult = await runQuickstart(runtime, resolvedConfig, {
|
|
896
|
+
quickstartResult = await runQuickstart(runtime, resolvedConfig, commandRunner, {
|
|
629
897
|
description: params.description,
|
|
630
898
|
template_id: params.template_id,
|
|
631
899
|
template_name: params.template_name,
|
|
@@ -635,7 +903,7 @@ async function runOnboard(
|
|
|
635
903
|
});
|
|
636
904
|
}
|
|
637
905
|
|
|
638
|
-
const doctorResult = await runDoctor(runtime, resolvedConfig, {
|
|
906
|
+
const doctorResult = await runDoctor(runtime, resolvedConfig, commandRunner, {
|
|
639
907
|
local_url: params.local_url,
|
|
640
908
|
tailscale_https_port: params.tailscale_https_port,
|
|
641
909
|
dashboard_output_path: params.dashboard_output_path,
|
|
@@ -658,36 +926,13 @@ async function runOnboard(
|
|
|
658
926
|
|
|
659
927
|
export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
660
928
|
const config = resolveConfig(api);
|
|
661
|
-
const
|
|
662
|
-
const fillCommandRunner = runtimeCommand
|
|
663
|
-
? async (
|
|
664
|
-
argv: string[],
|
|
665
|
-
optionsOrTimeout: number | {
|
|
666
|
-
timeoutMs: number;
|
|
667
|
-
cwd?: string;
|
|
668
|
-
input?: string;
|
|
669
|
-
env?: NodeJS.ProcessEnv;
|
|
670
|
-
windowsVerbatimArguments?: boolean;
|
|
671
|
-
noOutputTimeoutMs?: number;
|
|
672
|
-
}
|
|
673
|
-
) => {
|
|
674
|
-
const result = await runtimeCommand(argv, optionsOrTimeout);
|
|
675
|
-
return {
|
|
676
|
-
stdout: result.stdout,
|
|
677
|
-
stderr: result.stderr,
|
|
678
|
-
code: result.code,
|
|
679
|
-
signal: result.signal,
|
|
680
|
-
killed: result.killed,
|
|
681
|
-
termination: result.termination
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
: undefined;
|
|
929
|
+
const commandRunner = createRuntimeCommandRunner(api.runtime?.system?.runCommandWithTimeout);
|
|
685
930
|
const runtime = new PlashboardRuntime(config, {
|
|
686
931
|
info: (...args) => api.logger?.info?.(...args),
|
|
687
932
|
warn: (...args) => api.logger?.warn?.(...args),
|
|
688
933
|
error: (...args) => api.logger?.error?.(...args)
|
|
689
934
|
}, {
|
|
690
|
-
commandRunner
|
|
935
|
+
commandRunner
|
|
691
936
|
});
|
|
692
937
|
|
|
693
938
|
api.registerService?.({
|
|
@@ -722,7 +967,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
722
967
|
additionalProperties: false
|
|
723
968
|
},
|
|
724
969
|
execute: async (_toolCallId: unknown, params: OnboardParams = {}) =>
|
|
725
|
-
toToolResult(await runOnboard(runtime, config, params))
|
|
970
|
+
toToolResult(await runOnboard(runtime, config, commandRunner, params))
|
|
726
971
|
});
|
|
727
972
|
|
|
728
973
|
api.registerTool?.({
|
|
@@ -756,7 +1001,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
756
1001
|
additionalProperties: false
|
|
757
1002
|
},
|
|
758
1003
|
execute: async (_toolCallId: unknown, params: ExposureParams = {}) =>
|
|
759
|
-
toToolResult(await runExposureCheck(config, params))
|
|
1004
|
+
toToolResult(await runExposureCheck(config, commandRunner, params))
|
|
760
1005
|
});
|
|
761
1006
|
|
|
762
1007
|
api.registerTool?.({
|
|
@@ -790,7 +1035,22 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
790
1035
|
additionalProperties: false
|
|
791
1036
|
},
|
|
792
1037
|
execute: async (_toolCallId: unknown, params: DoctorParams = {}) =>
|
|
793
|
-
toToolResult(await runDoctor(runtime, config, params))
|
|
1038
|
+
toToolResult(await runDoctor(runtime, config, commandRunner, params))
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
api.registerTool?.({
|
|
1042
|
+
name: 'plashboard_permissions_fix',
|
|
1043
|
+
description: 'Apply compatibility file modes for dashboard web readers (explicit action).',
|
|
1044
|
+
optional: true,
|
|
1045
|
+
parameters: {
|
|
1046
|
+
type: 'object',
|
|
1047
|
+
properties: {
|
|
1048
|
+
dashboard_output_path: { type: 'string' }
|
|
1049
|
+
},
|
|
1050
|
+
additionalProperties: false
|
|
1051
|
+
},
|
|
1052
|
+
execute: async (_toolCallId: unknown, params: PermissionsFixParams = {}) =>
|
|
1053
|
+
toToolResult(await runPermissionsFix(config, params))
|
|
794
1054
|
});
|
|
795
1055
|
|
|
796
1056
|
api.registerTool?.({
|
|
@@ -801,6 +1061,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
801
1061
|
type: 'object',
|
|
802
1062
|
properties: {
|
|
803
1063
|
fill_provider: { type: 'string', enum: ['mock', 'command', 'openclaw'] },
|
|
1064
|
+
allow_command_fill: { type: 'boolean' },
|
|
804
1065
|
fill_command: { type: 'string' },
|
|
805
1066
|
openclaw_fill_agent_id: { type: 'string' },
|
|
806
1067
|
auto_seed_template: { type: 'boolean' },
|
|
@@ -817,7 +1078,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
817
1078
|
additionalProperties: false
|
|
818
1079
|
},
|
|
819
1080
|
execute: async (_toolCallId: unknown, params: SetupParams = {}) =>
|
|
820
|
-
toToolResult(await runSetup(api, config, params))
|
|
1081
|
+
toToolResult(await runSetup(api, config, commandRunner, params))
|
|
821
1082
|
});
|
|
822
1083
|
|
|
823
1084
|
api.registerTool?.({
|
|
@@ -849,7 +1110,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
849
1110
|
additionalProperties: false
|
|
850
1111
|
},
|
|
851
1112
|
execute: async (_toolCallId: unknown, params: QuickstartParams = {}) =>
|
|
852
|
-
toToolResult(await runQuickstart(runtime, config, params))
|
|
1113
|
+
toToolResult(await runQuickstart(runtime, config, commandRunner, params))
|
|
853
1114
|
});
|
|
854
1115
|
|
|
855
1116
|
api.registerTool?.({
|
|
@@ -1049,7 +1310,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1049
1310
|
const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
|
|
1050
1311
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
1051
1312
|
return toCommandResult(
|
|
1052
|
-
await runExposureCheck(config, {
|
|
1313
|
+
await runExposureCheck(config, commandRunner, {
|
|
1053
1314
|
local_url: localUrl,
|
|
1054
1315
|
tailscale_https_port: portToken ? Number(portToken) : undefined
|
|
1055
1316
|
})
|
|
@@ -1070,13 +1331,20 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1070
1331
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
1071
1332
|
const repoDir = rest.find((token) => token.startsWith('/'));
|
|
1072
1333
|
return toCommandResult(
|
|
1073
|
-
await runDoctor(runtime, config, {
|
|
1334
|
+
await runDoctor(runtime, config, commandRunner, {
|
|
1074
1335
|
local_url: localUrl,
|
|
1075
1336
|
tailscale_https_port: portToken ? Number(portToken) : undefined,
|
|
1076
1337
|
repo_dir: repoDir
|
|
1077
1338
|
})
|
|
1078
1339
|
);
|
|
1079
1340
|
}
|
|
1341
|
+
if (cmd === 'fix-permissions') {
|
|
1342
|
+
return toCommandResult(
|
|
1343
|
+
await runPermissionsFix(config, {
|
|
1344
|
+
dashboard_output_path: rest[0]
|
|
1345
|
+
})
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1080
1348
|
if (cmd === 'onboard') {
|
|
1081
1349
|
const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
|
|
1082
1350
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
@@ -1084,7 +1352,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1084
1352
|
const descriptionTokens = rest.filter((token) => token !== localUrl && token !== portToken && token !== repoDir);
|
|
1085
1353
|
const description = descriptionTokens.join(' ').trim() || undefined;
|
|
1086
1354
|
return toCommandResult(
|
|
1087
|
-
await runOnboard(runtime, config, {
|
|
1355
|
+
await runOnboard(runtime, config, commandRunner, {
|
|
1088
1356
|
description,
|
|
1089
1357
|
local_url: localUrl,
|
|
1090
1358
|
tailscale_https_port: portToken ? Number(portToken) : undefined,
|
|
@@ -1098,8 +1366,9 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1098
1366
|
const fillCommand = fillProvider === 'command' ? rest.slice(1).join(' ').trim() || undefined : undefined;
|
|
1099
1367
|
const fillAgentId = fillProvider === 'openclaw' ? (rest[1] || '').trim() || undefined : undefined;
|
|
1100
1368
|
return toCommandResult(
|
|
1101
|
-
await runSetup(api, config, {
|
|
1369
|
+
await runSetup(api, config, commandRunner, {
|
|
1102
1370
|
fill_provider: fillProvider,
|
|
1371
|
+
allow_command_fill: fillProvider === 'command' ? true : undefined,
|
|
1103
1372
|
fill_command: fillCommand,
|
|
1104
1373
|
openclaw_fill_agent_id: fillAgentId
|
|
1105
1374
|
})
|
|
@@ -1107,7 +1376,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1107
1376
|
}
|
|
1108
1377
|
if (cmd === 'quickstart') {
|
|
1109
1378
|
const description = rest.join(' ').trim() || undefined;
|
|
1110
|
-
return toCommandResult(await runQuickstart(runtime, config, { description }));
|
|
1379
|
+
return toCommandResult(await runQuickstart(runtime, config, commandRunner, { description }));
|
|
1111
1380
|
}
|
|
1112
1381
|
if (cmd === 'init') return toCommandResult(await runtime.init());
|
|
1113
1382
|
if (cmd === 'status') return toCommandResult(await runtime.status());
|
|
@@ -1133,7 +1402,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1133
1402
|
return toCommandResult({
|
|
1134
1403
|
ok: false,
|
|
1135
1404
|
errors: [
|
|
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>'
|
|
1405
|
+
'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], fix-permissions [dashboard_output_path], 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>'
|
|
1137
1406
|
]
|
|
1138
1407
|
});
|
|
1139
1408
|
}
|