@hachej/boring-ui-plugin-cli 0.1.31 → 0.1.33
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/LICENSE +21 -0
- package/dist/bin.js +5 -2
- package/dist/chunk-DH4PSVGY.js +1096 -0
- package/dist/index.d.ts +103 -2
- package/dist/index.js +17 -3
- package/package.json +8 -8
- package/templates/front-canonical.tsx +135 -15
- package/templates/plugin/README.md +56 -0
- package/templates/plugin/package.json +67 -0
- package/templates/plugin/src/front/__tests__/samplePlugin.test.ts +53 -0
- package/templates/plugin/src/front/index.ts +45 -0
- package/templates/plugin/src/front/panels.tsx +16 -0
- package/templates/plugin/src/front/surfaceResolver.ts +19 -0
- package/templates/plugin/src/server/index.ts +29 -0
- package/templates/plugin/src/shared/constants.ts +2 -0
- package/templates/plugin/src/shared/index.ts +2 -0
- package/templates/plugin/src/shared/types.ts +3 -0
- package/templates/plugin/src/test-setup.ts +43 -0
- package/templates/plugin/tsconfig.json +23 -0
- package/templates/plugin/tsup.config.ts +21 -0
- package/templates/plugin/vitest.config.ts +20 -0
- package/templates/server-canonical.ts +42 -0
- package/dist/chunk-MNNNQ7Y7.js +0 -565
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,105 @@
|
|
|
1
|
+
interface CreatePluginOptions {
|
|
2
|
+
name: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
}
|
|
6
|
+
interface CreatePluginResult {
|
|
7
|
+
id: string;
|
|
8
|
+
pluginDir: string;
|
|
9
|
+
packageName: string;
|
|
10
|
+
filesCreated: string[];
|
|
11
|
+
}
|
|
12
|
+
declare function createPlugin(options: CreatePluginOptions): CreatePluginResult;
|
|
13
|
+
|
|
14
|
+
interface ScaffoldPluginOptions {
|
|
15
|
+
/** Plugin id — must be kebab-case, used as folder name + npm package name. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Workspace root the .pi/extensions/<name>/ folder is created under. */
|
|
18
|
+
workspaceRoot: string;
|
|
19
|
+
/** Optional override for the canonical templates dir (test escape hatch). */
|
|
20
|
+
templatesDir?: string;
|
|
21
|
+
}
|
|
22
|
+
interface ScaffoldPluginResult {
|
|
23
|
+
pluginDir: string;
|
|
24
|
+
filesCreated: string[];
|
|
25
|
+
}
|
|
26
|
+
declare function scaffoldPlugin(opts: ScaffoldPluginOptions): ScaffoldPluginResult;
|
|
27
|
+
|
|
28
|
+
interface VerifyPluginOptions {
|
|
29
|
+
/** Workspace root containing `.pi/extensions/`. */
|
|
30
|
+
workspaceRoot: string;
|
|
31
|
+
/** Optional plugin name to verify only one; otherwise verifies all. */
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
interface PluginVerifyOutcome {
|
|
35
|
+
id: string;
|
|
36
|
+
dir: string;
|
|
37
|
+
ok: boolean;
|
|
38
|
+
errors: string[];
|
|
39
|
+
/**
|
|
40
|
+
* Non-fatal informational messages — e.g. "this plugin declares
|
|
41
|
+
* boring.server, which is boot-time/static composition only and is
|
|
42
|
+
* not hot-registered by /reload". Does NOT flip `ok` to false;
|
|
43
|
+
* boring-ui-plugin verify is about manifest validity, not activation.
|
|
44
|
+
*/
|
|
45
|
+
warnings: string[];
|
|
46
|
+
}
|
|
47
|
+
interface VerifyPluginResult {
|
|
48
|
+
outcomes: PluginVerifyOutcome[];
|
|
49
|
+
ok: boolean;
|
|
50
|
+
/** Absolute path of the `.pi/extensions/` dir that was scanned. */
|
|
51
|
+
extensionsDir: string;
|
|
52
|
+
/** True when `.pi/extensions/` doesn't exist at all under the workspace. */
|
|
53
|
+
extensionsDirMissing: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verify boring-ui plugins on disk WITHOUT a running workspace server.
|
|
57
|
+
* Runs the same manifest validator the asset manager runs, plus file-
|
|
58
|
+
* existence checks for `boring.front` / `boring.server` / `pi.extensions`
|
|
59
|
+
* paths. Designed to catch the common authoring mistakes (invalid
|
|
60
|
+
* `boring.server` value, missing front file, malformed manifest, etc.)
|
|
61
|
+
* in the same agent turn that wrote the files.
|
|
62
|
+
*
|
|
63
|
+
* Does NOT execute plugin code (no jiti, no Vite). Syntax errors in
|
|
64
|
+
* front/Pi modules only surface when the workspace's real /reload runs;
|
|
65
|
+
* declared `boring.server` files require static composition plus a
|
|
66
|
+
* process restart to execute.
|
|
67
|
+
*/
|
|
68
|
+
declare function verifyPlugin(opts: VerifyPluginOptions): VerifyPluginResult;
|
|
69
|
+
declare function formatVerifyResult(result: VerifyPluginResult): string;
|
|
70
|
+
declare function findHintForError(message: string): string | undefined;
|
|
71
|
+
|
|
72
|
+
type PaneSelfTestState = "ready" | "loading" | "error" | "missing" | "timeout" | "no-browser-connected";
|
|
73
|
+
interface SelfTestEvent {
|
|
74
|
+
code: string;
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
interface SelfTestResult {
|
|
78
|
+
ok: boolean;
|
|
79
|
+
workspaceId?: string;
|
|
80
|
+
pluginId: string;
|
|
81
|
+
revision?: number;
|
|
82
|
+
reloadErrors: SelfTestEvent[];
|
|
83
|
+
pane: {
|
|
84
|
+
found: boolean;
|
|
85
|
+
state: PaneSelfTestState;
|
|
86
|
+
panelId: string;
|
|
87
|
+
panelInstanceId: string;
|
|
88
|
+
error?: SelfTestEvent;
|
|
89
|
+
lastReportedAt?: string;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
interface RunPluginSelfTestOptions {
|
|
93
|
+
pluginId: string;
|
|
94
|
+
url?: string;
|
|
95
|
+
workspaceId?: string;
|
|
96
|
+
panelId?: string;
|
|
97
|
+
timeoutMs?: number;
|
|
98
|
+
}
|
|
99
|
+
declare function runPluginSelfTest(options: RunPluginSelfTestOptions): Promise<SelfTestResult>;
|
|
100
|
+
declare function formatSelfTestResult(result: SelfTestResult): string;
|
|
101
|
+
|
|
1
102
|
declare function pluginCommandUsage(): string;
|
|
2
|
-
declare function runBoringUiPluginCli(argv?: string[]): void
|
|
103
|
+
declare function runBoringUiPluginCli(argv?: string[]): Promise<void>;
|
|
3
104
|
|
|
4
|
-
export { pluginCommandUsage, runBoringUiPluginCli };
|
|
105
|
+
export { type CreatePluginOptions, type CreatePluginResult, type PaneSelfTestState, type PluginVerifyOutcome, type RunPluginSelfTestOptions, type ScaffoldPluginOptions, type ScaffoldPluginResult, type SelfTestEvent, type SelfTestResult, type VerifyPluginResult, createPlugin, findHintForError, formatSelfTestResult, formatVerifyResult, pluginCommandUsage, runBoringUiPluginCli, runPluginSelfTest, scaffoldPlugin, verifyPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createPlugin,
|
|
3
|
+
findHintForError,
|
|
4
|
+
formatSelfTestResult,
|
|
5
|
+
formatVerifyResult,
|
|
2
6
|
pluginCommandUsage,
|
|
3
|
-
runBoringUiPluginCli
|
|
4
|
-
|
|
7
|
+
runBoringUiPluginCli,
|
|
8
|
+
runPluginSelfTest,
|
|
9
|
+
scaffoldPlugin,
|
|
10
|
+
verifyPlugin
|
|
11
|
+
} from "./chunk-DH4PSVGY.js";
|
|
5
12
|
export {
|
|
13
|
+
createPlugin,
|
|
14
|
+
findHintForError,
|
|
15
|
+
formatSelfTestResult,
|
|
16
|
+
formatVerifyResult,
|
|
6
17
|
pluginCommandUsage,
|
|
7
|
-
runBoringUiPluginCli
|
|
18
|
+
runBoringUiPluginCli,
|
|
19
|
+
runPluginSelfTest,
|
|
20
|
+
scaffoldPlugin,
|
|
21
|
+
verifyPlugin
|
|
8
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-ui-plugin-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Slim boring-ui plugin authoring CLI for workspace runtimes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,11 +18,6 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsup",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"test": "pnpm run build && vitest run"
|
|
25
|
-
},
|
|
26
21
|
"devDependencies": {
|
|
27
22
|
"@types/node": "^22.15.3",
|
|
28
23
|
"tsup": "^8.4.0",
|
|
@@ -33,5 +28,10 @@
|
|
|
33
28
|
"type": "git",
|
|
34
29
|
"url": "git+https://github.com/hachej/boring-ui.git"
|
|
35
30
|
},
|
|
36
|
-
"homepage": "https://github.com/hachej/boring-ui"
|
|
37
|
-
|
|
31
|
+
"homepage": "https://github.com/hachej/boring-ui",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "pnpm run build && vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,34 +1,150 @@
|
|
|
1
|
-
// CANONICAL front/index.tsx for a boring-ui plugin.
|
|
1
|
+
// CANONICAL front/index.tsx for a boring-ui runtime plugin.
|
|
2
2
|
// Copy this shape — replace <kebab-name> and <Label>.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Structure rule:
|
|
5
|
+
// - Commands open full-size center panes for detailed work.
|
|
6
|
+
// - Left tabs are compact, persistent sidebar/navigator panes.
|
|
7
|
+
// - If a plugin has both, do NOT render the same component in both places.
|
|
8
|
+
// Register a small LeftPane and a separate MainPane.
|
|
9
9
|
|
|
10
10
|
import React from "react"
|
|
11
|
-
import { definePlugin } from "@hachej/boring-workspace/plugin"
|
|
11
|
+
import { definePlugin, type PaneProps } from "@hachej/boring-workspace/plugin"
|
|
12
|
+
import {
|
|
13
|
+
Badge,
|
|
14
|
+
Button,
|
|
15
|
+
Card,
|
|
16
|
+
CardContent,
|
|
17
|
+
CardDescription,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
EmptyState,
|
|
21
|
+
Toolbar,
|
|
22
|
+
ToolbarGroup,
|
|
23
|
+
} from "@hachej/boring-ui-kit"
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
const MAIN_PANEL_ID = "<kebab-name>.panel"
|
|
26
|
+
const LEFT_PANEL_ID = "<kebab-name>.left"
|
|
27
|
+
|
|
28
|
+
function MainPane() {
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground">
|
|
31
|
+
<Toolbar className="border-b border-border px-3 py-2">
|
|
32
|
+
<ToolbarGroup>
|
|
33
|
+
<Badge variant="secondary">Runtime plugin</Badge>
|
|
34
|
+
<Badge variant="outline"><Label></Badge>
|
|
35
|
+
</ToolbarGroup>
|
|
36
|
+
<ToolbarGroup className="ml-auto">
|
|
37
|
+
<Button size="sm" variant="secondary">Refresh</Button>
|
|
38
|
+
</ToolbarGroup>
|
|
39
|
+
</Toolbar>
|
|
40
|
+
|
|
41
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-4">
|
|
42
|
+
<div className="grid min-w-0 gap-4 lg:grid-cols-[minmax(0,1fr)_280px]">
|
|
43
|
+
<Card className="min-w-0">
|
|
44
|
+
<CardHeader>
|
|
45
|
+
<CardTitle><Label></CardTitle>
|
|
46
|
+
<CardDescription>
|
|
47
|
+
This is the full center pane. Put detailed views, tables, charts,
|
|
48
|
+
editors, previews, and multi-step workflows here.
|
|
49
|
+
</CardDescription>
|
|
50
|
+
</CardHeader>
|
|
51
|
+
<CardContent>
|
|
52
|
+
<EmptyState
|
|
53
|
+
title="Nothing to show yet"
|
|
54
|
+
description="Connect data, register a surface resolver, or add actions for this plugin."
|
|
55
|
+
actions={<Button size="sm">Primary action</Button>}
|
|
56
|
+
/>
|
|
57
|
+
</CardContent>
|
|
58
|
+
</Card>
|
|
59
|
+
|
|
60
|
+
<Card className="min-w-0">
|
|
61
|
+
<CardHeader>
|
|
62
|
+
<CardTitle className="text-sm">Details</CardTitle>
|
|
63
|
+
<CardDescription>Use side cards for metadata or secondary controls.</CardDescription>
|
|
64
|
+
</CardHeader>
|
|
65
|
+
<CardContent>
|
|
66
|
+
<div className="space-y-2 text-sm text-muted-foreground">
|
|
67
|
+
<div className="flex items-center justify-between gap-3">
|
|
68
|
+
<span>Status</span>
|
|
69
|
+
<Badge variant="outline">Ready</Badge>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="flex items-center justify-between gap-3">
|
|
72
|
+
<span>Items</span>
|
|
73
|
+
<span>0</span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function LeftPane({ containerApi }: PaneProps) {
|
|
85
|
+
const openMainPane = () => {
|
|
86
|
+
containerApi.addPanel({
|
|
87
|
+
id: `${MAIN_PANEL_ID}.from-left`,
|
|
88
|
+
component: MAIN_PANEL_ID,
|
|
89
|
+
title: "<Label>",
|
|
90
|
+
params: { source: "left-tab" },
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground">
|
|
96
|
+
<div className="border-b border-border/60 px-3 py-2">
|
|
97
|
+
<div className="text-sm font-medium"><Label></div>
|
|
98
|
+
<div className="text-xs text-muted-foreground">Compact sidebar navigator</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
|
|
102
|
+
<div className="space-y-3">
|
|
103
|
+
<Card className="min-w-0">
|
|
104
|
+
<CardHeader className="space-y-1 p-3">
|
|
105
|
+
<CardTitle className="text-sm">Overview</CardTitle>
|
|
106
|
+
<CardDescription className="text-xs">
|
|
107
|
+
Keep left tabs small: summary, filters, navigation, and actions.
|
|
108
|
+
</CardDescription>
|
|
109
|
+
</CardHeader>
|
|
110
|
+
<CardContent className="p-3 pt-0">
|
|
111
|
+
<Button size="sm" className="w-full" onClick={openMainPane}>
|
|
112
|
+
Open main pane
|
|
113
|
+
</Button>
|
|
114
|
+
</CardContent>
|
|
115
|
+
</Card>
|
|
116
|
+
|
|
117
|
+
<div className="space-y-1 text-xs text-muted-foreground">
|
|
118
|
+
<div className="rounded-md border border-border bg-card px-2 py-1.5">Recent item</div>
|
|
119
|
+
<div className="rounded-md border border-border bg-card px-2 py-1.5">Saved filter</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
15
125
|
}
|
|
16
126
|
|
|
17
127
|
export default definePlugin({
|
|
18
128
|
id: "<kebab-name>", // contribution namespace; matching package name is recommended
|
|
19
129
|
label: "<Label>",
|
|
20
130
|
panels: [
|
|
21
|
-
{ id:
|
|
131
|
+
{ id: MAIN_PANEL_ID, label: "<Label>", component: MainPane },
|
|
132
|
+
// Optional left-tab component. Register separately from the main panel so
|
|
133
|
+
// the sidebar does not duplicate a full workbench view.
|
|
134
|
+
{ id: LEFT_PANEL_ID, label: "<Label>", component: LeftPane },
|
|
22
135
|
],
|
|
23
136
|
commands: [
|
|
24
|
-
{ id: "<kebab-name>.open", title: "Open <Label>", panelId:
|
|
137
|
+
{ id: "<kebab-name>.open", title: "Open <Label>", panelId: MAIN_PANEL_ID },
|
|
25
138
|
],
|
|
26
|
-
// Do not add leftTabs
|
|
27
|
-
//
|
|
28
|
-
//
|
|
139
|
+
// Do not add leftTabs just because a plugin has a panel. A left tab is a
|
|
140
|
+
// permanent sidebar category. Keep it compact, and use its buttons/rows to
|
|
141
|
+
// open the full center panel via containerApi.addPanel(...).
|
|
142
|
+
//
|
|
143
|
+
// If this plugin should have persistent sidebar navigation, uncomment this:
|
|
29
144
|
// leftTabs: [
|
|
30
|
-
// { id: "<kebab-name>.tab", title: "<Label>", panelId:
|
|
145
|
+
// { id: "<kebab-name>.tab", title: "<Label>", panelId: LEFT_PANEL_ID },
|
|
31
146
|
// ],
|
|
147
|
+
//
|
|
32
148
|
// File visualizer example: import WORKSPACE_OPEN_PATH_SURFACE_KIND from
|
|
33
149
|
// "@hachej/boring-workspace/plugin", import useApiBaseUrl/useWorkspaceRequestId
|
|
34
150
|
// from "@hachej/boring-workspace", set kind: WORKSPACE_OPEN_PATH_SURFACE_KIND,
|
|
@@ -43,6 +159,10 @@ export default definePlugin({
|
|
|
43
159
|
// setup: (api) => { if (env.beta) api.registerPanel(betaPanel) },
|
|
44
160
|
})
|
|
45
161
|
|
|
162
|
+
// Responsive pane rule: panels and left tabs live inside resizable dock regions.
|
|
163
|
+
// Avoid fixed large widths; prefer w-full/min-w-0 layouts and responsive chart
|
|
164
|
+
// wrappers such as Recharts ResponsiveContainer.
|
|
165
|
+
//
|
|
46
166
|
// All available `definePlugin` config fields:
|
|
47
167
|
// id (required, string)
|
|
48
168
|
// label (optional, string)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Plugin template
|
|
2
|
+
|
|
3
|
+
Reference shape for **app/internal publishable npm-package plugins**.
|
|
4
|
+
The plugin CLI copies this directory when you run:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
boring-ui-plugin create <your-name> --path plugins
|
|
8
|
+
cd plugins/<your-name>
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm typecheck && pnpm test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Building a generated/runtime user plugin instead** (hot-reloadable,
|
|
14
|
+
> no build step, drops into a workspace's `.pi/extensions/<name>/`)?
|
|
15
|
+
> Don't copy this template and don't use `npx` from inside the agent
|
|
16
|
+
> runtime — run the workspace-local `boring-ui-plugin scaffold <name>`.
|
|
17
|
+
|
|
18
|
+
## Shape
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
plugins/<name>/
|
|
22
|
+
package.json private: true, workspace:* deps, nested exports map
|
|
23
|
+
tsconfig.json paths aliases into packages/workspace/src for fast iteration
|
|
24
|
+
tsup.config.ts nested entries (front/index, server/index, shared/index)
|
|
25
|
+
vitest.config.ts jsdom + @vitejs/plugin-react + globals: true
|
|
26
|
+
setupFiles: ./src/test-setup.ts
|
|
27
|
+
src/
|
|
28
|
+
front/
|
|
29
|
+
index.ts definePlugin({ ... }) entry and re-exports
|
|
30
|
+
panels.tsx
|
|
31
|
+
surfaceResolver.ts
|
|
32
|
+
__tests__/xxxPlugin.test.ts
|
|
33
|
+
server/
|
|
34
|
+
index.ts createXxxServerPlugin() and default server factory
|
|
35
|
+
shared/
|
|
36
|
+
constants.ts ids, surface kinds
|
|
37
|
+
types.ts param/option types
|
|
38
|
+
index.ts re-export
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What lives where
|
|
42
|
+
|
|
43
|
+
- **front/** — anything that runs in the browser shell: panels, command
|
|
44
|
+
contributions, surface resolvers, providers, bindings.
|
|
45
|
+
- **server/** — anything that runs in the agent backend: agent tools,
|
|
46
|
+
system prompt fragments, server hooks.
|
|
47
|
+
- **shared/** — constants and types used by both sides. Keep it tiny.
|
|
48
|
+
- **`src/test-setup.ts`** — jest-dom matchers, ResizeObserver + Range
|
|
49
|
+
polyfills, testing-library cleanup. Each plugin owns its own copy;
|
|
50
|
+
keep them in sync with `packages/plugin-cli/templates/plugin/src/test-setup.ts`
|
|
51
|
+
if the canonical setup changes.
|
|
52
|
+
|
|
53
|
+
## Invariants
|
|
54
|
+
|
|
55
|
+
`packages/workspace/scripts/check-plugin-invariants.mjs` lints this
|
|
56
|
+
template (and the live plugins) for the plugin contract. Keep it valid.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hachej/boring-plugin-template",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"description": "Reference app/internal boring-ui package-plugin example using declarative definePlugin({ ... }) front contributions plus boot-time server integration. Generated .pi/extensions plugins should use boring-ui-plugin scaffold instead.",
|
|
7
|
+
"boring": {
|
|
8
|
+
"label": "Sample",
|
|
9
|
+
"front": "dist/front/index.js",
|
|
10
|
+
"server": "dist/server/index.js"
|
|
11
|
+
},
|
|
12
|
+
"pi": {
|
|
13
|
+
"systemPrompt": "Sample plugin template \u2014 replace this prompt with a short hint telling the agent when to use your plugin's panels/tools."
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/front/index.d.ts",
|
|
21
|
+
"import": "./dist/front/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./front": {
|
|
24
|
+
"types": "./dist/front/index.d.ts",
|
|
25
|
+
"import": "./dist/front/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./server": {
|
|
28
|
+
"types": "./dist/server/index.d.ts",
|
|
29
|
+
"import": "./dist/server/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./shared": {
|
|
32
|
+
"types": "./dist/shared/index.d.ts",
|
|
33
|
+
"import": "./dist/shared/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"typecheck": "pnpm --filter @hachej/boring-ui-kit build && pnpm --filter @hachej/boring-agent build && pnpm --filter @hachej/boring-workspace build && tsc --noEmit",
|
|
41
|
+
"test": "pnpm --filter @hachej/boring-ui-kit build && vitest run --passWithNoTests",
|
|
42
|
+
"lint": "pnpm run typecheck",
|
|
43
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@hachej/boring-workspace": "workspace:*",
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@hachej/boring-ui-kit": "workspace:*"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@hachej/boring-workspace": "workspace:*",
|
|
55
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
56
|
+
"@testing-library/react": "^16.3.2",
|
|
57
|
+
"@types/react": "^19.0.0",
|
|
58
|
+
"@types/react-dom": "^19.0.0",
|
|
59
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
60
|
+
"jsdom": "^29.0.2",
|
|
61
|
+
"react": "^19.0.0",
|
|
62
|
+
"react-dom": "^19.0.0",
|
|
63
|
+
"tsup": "^8.0.0",
|
|
64
|
+
"typescript": "^5.4.0",
|
|
65
|
+
"vitest": "^3.1.3"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
import samplePlugin, { SAMPLE_PANEL_ID, SamplePanel } from "../index"
|
|
3
|
+
import { SAMPLE_PLUGIN_ID, SAMPLE_SURFACE_KIND } from "../../shared/constants"
|
|
4
|
+
|
|
5
|
+
describe("samplePlugin (BoringFrontFactory)", () => {
|
|
6
|
+
it("registers a panel, a panel command, and a surface resolver", async () => {
|
|
7
|
+
const registerPanel = vi.fn()
|
|
8
|
+
const registerPanelCommand = vi.fn()
|
|
9
|
+
const registerSurfaceResolver = vi.fn()
|
|
10
|
+
const api = {
|
|
11
|
+
registerProvider: vi.fn(),
|
|
12
|
+
registerBinding: vi.fn(),
|
|
13
|
+
registerCatalog: vi.fn(),
|
|
14
|
+
registerPanel,
|
|
15
|
+
registerPanelCommand,
|
|
16
|
+
registerLeftTab: vi.fn(),
|
|
17
|
+
registerSurfaceResolver,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await samplePlugin(api)
|
|
21
|
+
|
|
22
|
+
expect(registerPanel).toHaveBeenCalledExactlyOnceWith(
|
|
23
|
+
expect.objectContaining({
|
|
24
|
+
id: SAMPLE_PANEL_ID,
|
|
25
|
+
label: "Sample",
|
|
26
|
+
placement: "center",
|
|
27
|
+
component: SamplePanel,
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
expect(registerPanelCommand).toHaveBeenCalledExactlyOnceWith(
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
id: "sample.open",
|
|
33
|
+
title: "Open Sample",
|
|
34
|
+
panelId: SAMPLE_PANEL_ID,
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
expect(registerSurfaceResolver).toHaveBeenCalledExactlyOnceWith(
|
|
38
|
+
expect.objectContaining({
|
|
39
|
+
id: "sample",
|
|
40
|
+
kind: SAMPLE_SURFACE_KIND,
|
|
41
|
+
}),
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("is the default export (required for hot-reload dynamic import)", () => {
|
|
46
|
+
expect(typeof samplePlugin).toBe("function")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("carries pluginId + pluginLabel metadata (definePlugin contract)", () => {
|
|
50
|
+
expect(samplePlugin.pluginId).toBe(SAMPLE_PLUGIN_ID)
|
|
51
|
+
expect(samplePlugin.pluginLabel).toBe("Sample")
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { definePlugin, type BoringFrontFactoryWithId } from "@hachej/boring-workspace/plugin"
|
|
2
|
+
import { SAMPLE_PLUGIN_ID } from "../shared/constants"
|
|
3
|
+
import { SAMPLE_PANEL_ID, SamplePanel } from "./panels"
|
|
4
|
+
import { sampleSurfaceResolver } from "./surfaceResolver"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default-exported `BoringFrontFactoryWithId`. The workspace shell
|
|
8
|
+
* accepts this directly in `WorkspaceProvider.plugins`; on bootstrap,
|
|
9
|
+
* it dispatches each declarative field to the corresponding
|
|
10
|
+
* `api.register*` method.
|
|
11
|
+
*
|
|
12
|
+
* `definePlugin(config)` takes a single declarative config object.
|
|
13
|
+
* For imperative composition (calling an external factory), use the
|
|
14
|
+
* `setup: (api) => void` escape hatch — see SKILL.md.
|
|
15
|
+
*
|
|
16
|
+
* Plugins in `.pi/extensions/<name>/` also get hot-reloaded — the
|
|
17
|
+
* workspace dynamically re-imports this module and re-runs the
|
|
18
|
+
* factory.
|
|
19
|
+
*/
|
|
20
|
+
const samplePlugin: BoringFrontFactoryWithId = definePlugin({
|
|
21
|
+
id: SAMPLE_PLUGIN_ID,
|
|
22
|
+
label: "Sample",
|
|
23
|
+
panels: [
|
|
24
|
+
{
|
|
25
|
+
id: SAMPLE_PANEL_ID,
|
|
26
|
+
label: "Sample",
|
|
27
|
+
component: SamplePanel,
|
|
28
|
+
placement: "center",
|
|
29
|
+
source: "app",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
commands: [
|
|
33
|
+
{
|
|
34
|
+
id: "sample.open",
|
|
35
|
+
title: "Open Sample",
|
|
36
|
+
panelId: SAMPLE_PANEL_ID,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
surfaceResolvers: [sampleSurfaceResolver],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export default samplePlugin
|
|
43
|
+
|
|
44
|
+
export { SamplePanel, SAMPLE_PANEL_ID } from "./panels"
|
|
45
|
+
export { sampleSurfaceResolver } from "./surfaceResolver"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PaneProps } from "@hachej/boring-workspace"
|
|
2
|
+
import type { SampleParams } from "../shared/types"
|
|
3
|
+
|
|
4
|
+
export const SAMPLE_PANEL_ID = "sample-panel"
|
|
5
|
+
|
|
6
|
+
export function SamplePanel({ params }: PaneProps<SampleParams>) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground">
|
|
9
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-4">
|
|
10
|
+
<div className="rounded-lg border border-border bg-card p-4 text-sm">
|
|
11
|
+
{params.id}
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BoringFrontSurfaceResolverRegistration } from "@hachej/boring-workspace/plugin"
|
|
2
|
+
import { SAMPLE_SURFACE_KIND } from "../shared/constants"
|
|
3
|
+
import { SAMPLE_PANEL_ID } from "./panels"
|
|
4
|
+
|
|
5
|
+
export const sampleSurfaceResolver: BoringFrontSurfaceResolverRegistration = {
|
|
6
|
+
id: "sample",
|
|
7
|
+
kind: SAMPLE_SURFACE_KIND,
|
|
8
|
+
source: "app",
|
|
9
|
+
resolve(request) {
|
|
10
|
+
if (request.kind !== SAMPLE_SURFACE_KIND) return undefined
|
|
11
|
+
return {
|
|
12
|
+
id: `sample:${request.target}`,
|
|
13
|
+
component: SAMPLE_PANEL_ID,
|
|
14
|
+
title: request.target,
|
|
15
|
+
params: { id: request.target },
|
|
16
|
+
score: 0,
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentTool } from "@hachej/boring-workspace"
|
|
2
|
+
import {
|
|
3
|
+
defineServerPlugin,
|
|
4
|
+
type WorkspaceServerPlugin,
|
|
5
|
+
} from "@hachej/boring-workspace/server"
|
|
6
|
+
import { SAMPLE_PLUGIN_ID } from "../shared/constants"
|
|
7
|
+
|
|
8
|
+
export function createSampleServerPlugin(): WorkspaceServerPlugin {
|
|
9
|
+
return defineServerPlugin({
|
|
10
|
+
id: SAMPLE_PLUGIN_ID,
|
|
11
|
+
label: "Sample",
|
|
12
|
+
agentTools: [] satisfies AgentTool[],
|
|
13
|
+
systemPrompt: "## Sample Plugin",
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default export — adapter for the standard `defaultPluginPackages`
|
|
19
|
+
* load process. The workspace's `pluginEntryResolver` calls a
|
|
20
|
+
* dir-source plugin's default-exported factory with `(options, ctx)`
|
|
21
|
+
* where `ctx = { workspaceRoot, bridge }`. The named
|
|
22
|
+
* `createSampleServerPlugin()` factory stays for direct callers.
|
|
23
|
+
*/
|
|
24
|
+
export default function defaultSampleServerPlugin(
|
|
25
|
+
_options?: unknown,
|
|
26
|
+
_ctx?: { workspaceRoot: string; bridge: unknown },
|
|
27
|
+
): WorkspaceServerPlugin {
|
|
28
|
+
return createSampleServerPlugin()
|
|
29
|
+
}
|