@jhytabest/plashboard 0.1.11 → 1.0.1
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 +57 -1
- package/openclaw.plugin.json +3 -1
- package/package.json +3 -2
- package/skills/plashboard-admin/SKILL.md +2 -0
- package/src/command-runner.ts +120 -0
- package/src/config.ts +6 -0
- package/src/fill-runner.test.ts +122 -2
- package/src/fill-runner.ts +57 -111
- package/src/plugin.test.ts +186 -0
- package/src/plugin.ts +397 -120
- package/src/publisher.ts +22 -54
- package/src/runtime.test.ts +53 -2
- package/src/runtime.ts +9 -1
- package/src/types.ts +8 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { chmod, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { registerPlashboardPlugin } from './plugin.js';
|
|
6
|
+
|
|
7
|
+
type ToolDef = {
|
|
8
|
+
name: string;
|
|
9
|
+
execute?: (toolCallId: unknown, params?: Record<string, unknown>) => Promise<{
|
|
10
|
+
content: Array<{ type: string; text: string }>;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function parseToolJson(result: { content: Array<{ type: string; text: string }> }) {
|
|
15
|
+
const text = result.content[0]?.text || '{}';
|
|
16
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('registerPlashboardPlugin', () => {
|
|
20
|
+
it('doctor reports readiness flags when runtime command runner is unavailable', async () => {
|
|
21
|
+
const root = await mkdtemp(join(tmpdir(), 'plashboard-plugin-test-'));
|
|
22
|
+
try {
|
|
23
|
+
const tools = new Map<string, ToolDef>();
|
|
24
|
+
|
|
25
|
+
registerPlashboardPlugin({
|
|
26
|
+
pluginConfig: {
|
|
27
|
+
config: {
|
|
28
|
+
data_dir: root,
|
|
29
|
+
dashboard_output_path: join(root, 'dashboard.json'),
|
|
30
|
+
fill_provider: 'openclaw',
|
|
31
|
+
allow_command_fill: false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
registerTool: (definition: unknown) => {
|
|
35
|
+
const tool = definition as ToolDef;
|
|
36
|
+
tools.set(tool.name, tool);
|
|
37
|
+
},
|
|
38
|
+
registerCommand: () => {},
|
|
39
|
+
registerService: () => {},
|
|
40
|
+
runtime: {
|
|
41
|
+
config: {
|
|
42
|
+
loadConfig: () => ({}),
|
|
43
|
+
writeConfigFile: async () => {}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const doctor = tools.get('plashboard_doctor');
|
|
49
|
+
expect(doctor?.execute).toBeTypeOf('function');
|
|
50
|
+
|
|
51
|
+
const result = await doctor!.execute!('tool-1', {
|
|
52
|
+
local_url: 'http://127.0.0.1:9'
|
|
53
|
+
});
|
|
54
|
+
const payload = parseToolJson(result);
|
|
55
|
+
const data = (payload.data || {}) as Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
expect(payload.ok).toBe(false);
|
|
58
|
+
expect(data.fill_provider_ready).toBe(false);
|
|
59
|
+
expect(data.writer_runner_ready).toBe(false);
|
|
60
|
+
} finally {
|
|
61
|
+
await rm(root, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('setup rejects command provider unless allow_command_fill is true', async () => {
|
|
66
|
+
const root = await mkdtemp(join(tmpdir(), 'plashboard-plugin-test-'));
|
|
67
|
+
try {
|
|
68
|
+
const tools = new Map<string, ToolDef>();
|
|
69
|
+
let writtenConfig: unknown;
|
|
70
|
+
|
|
71
|
+
registerPlashboardPlugin({
|
|
72
|
+
pluginConfig: {
|
|
73
|
+
config: {
|
|
74
|
+
data_dir: root,
|
|
75
|
+
dashboard_output_path: join(root, 'dashboard.json'),
|
|
76
|
+
fill_provider: 'openclaw',
|
|
77
|
+
allow_command_fill: false
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
registerTool: (definition: unknown) => {
|
|
81
|
+
const tool = definition as ToolDef;
|
|
82
|
+
tools.set(tool.name, tool);
|
|
83
|
+
},
|
|
84
|
+
registerCommand: () => {},
|
|
85
|
+
registerService: () => {},
|
|
86
|
+
runtime: {
|
|
87
|
+
config: {
|
|
88
|
+
loadConfig: () => ({}),
|
|
89
|
+
writeConfigFile: async (nextConfig: unknown) => {
|
|
90
|
+
writtenConfig = nextConfig;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
system: {
|
|
94
|
+
runCommandWithTimeout: async (argv: string[]) => {
|
|
95
|
+
if (argv[0] === 'python3' && argv[1] === '--version') {
|
|
96
|
+
return {
|
|
97
|
+
stdout: 'Python 3.12.0',
|
|
98
|
+
stderr: '',
|
|
99
|
+
code: 0,
|
|
100
|
+
termination: 'exit'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
stdout: '',
|
|
105
|
+
stderr: `unsupported command: ${argv.join(' ')}`,
|
|
106
|
+
code: 1,
|
|
107
|
+
termination: 'exit'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const setup = tools.get('plashboard_setup');
|
|
115
|
+
expect(setup?.execute).toBeTypeOf('function');
|
|
116
|
+
|
|
117
|
+
const rejected = parseToolJson(await setup!.execute!('tool-2', {
|
|
118
|
+
fill_provider: 'command',
|
|
119
|
+
fill_command: 'echo hello'
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
expect(rejected.ok).toBe(false);
|
|
123
|
+
expect((rejected.errors as string[]).join(' ')).toMatch(/allow_command_fill=true/i);
|
|
124
|
+
expect(writtenConfig).toBeUndefined();
|
|
125
|
+
|
|
126
|
+
const accepted = parseToolJson(await setup!.execute!('tool-3', {
|
|
127
|
+
fill_provider: 'command',
|
|
128
|
+
allow_command_fill: true,
|
|
129
|
+
fill_command: 'echo hello'
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
expect(accepted.ok).toBe(true);
|
|
133
|
+
expect((accepted.data as Record<string, unknown>).allow_command_fill).toBe(true);
|
|
134
|
+
expect(writtenConfig).toBeTruthy();
|
|
135
|
+
} finally {
|
|
136
|
+
await rm(root, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('permissions fix normalizes directory and dashboard file modes', async () => {
|
|
141
|
+
const root = await mkdtemp(join(tmpdir(), 'plashboard-plugin-test-'));
|
|
142
|
+
const dashboardPath = join(root, 'dashboard.json');
|
|
143
|
+
try {
|
|
144
|
+
const tools = new Map<string, ToolDef>();
|
|
145
|
+
await writeFile(dashboardPath, '{"ok":true}\n', 'utf8');
|
|
146
|
+
await chmod(root, 0o700);
|
|
147
|
+
await chmod(dashboardPath, 0o600);
|
|
148
|
+
|
|
149
|
+
registerPlashboardPlugin({
|
|
150
|
+
pluginConfig: {
|
|
151
|
+
config: {
|
|
152
|
+
data_dir: root,
|
|
153
|
+
dashboard_output_path: dashboardPath,
|
|
154
|
+
fill_provider: 'openclaw',
|
|
155
|
+
allow_command_fill: false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
registerTool: (definition: unknown) => {
|
|
159
|
+
const tool = definition as ToolDef;
|
|
160
|
+
tools.set(tool.name, tool);
|
|
161
|
+
},
|
|
162
|
+
registerCommand: () => {},
|
|
163
|
+
registerService: () => {},
|
|
164
|
+
runtime: {
|
|
165
|
+
config: {
|
|
166
|
+
loadConfig: () => ({}),
|
|
167
|
+
writeConfigFile: async () => {}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const fix = tools.get('plashboard_permissions_fix');
|
|
173
|
+
expect(fix?.execute).toBeTypeOf('function');
|
|
174
|
+
|
|
175
|
+
const result = parseToolJson(await fix!.execute!('tool-4', {}));
|
|
176
|
+
expect(result.ok).toBe(true);
|
|
177
|
+
|
|
178
|
+
const dirMode = (await stat(root)).mode & 0o777;
|
|
179
|
+
const fileMode = (await stat(dashboardPath)).mode & 0o777;
|
|
180
|
+
expect(dirMode).toBe(0o755);
|
|
181
|
+
expect(fileMode).toBe(0o644);
|
|
182
|
+
} finally {
|
|
183
|
+
await rm(root, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|