@puredesktop/create-app 2.1.0 → 2.1.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/bin/create-puredesktop-app.mjs +134 -48
- package/package.json +2 -2
- package/template/README.md +39 -296
- package/template/docs/README.md +31 -0
- package/template/docs/agent.md +58 -0
- package/template/docs/app-lifecycle.md +60 -0
- package/template/docs/bridge/README.md +79 -0
- package/template/docs/bridge/dialog.md +56 -0
- package/template/docs/bridge/fs.md +95 -0
- package/template/docs/bridge/network.md +38 -0
- package/template/docs/bridge/settings.md +63 -0
- package/template/docs/bridge/storage.md +48 -0
- package/template/docs/plugin-manifest.md +108 -0
- package/template/docs/theme-vars.md +138 -0
- package/template/docs/tool-handlers.md +124 -0
- package/template/docs/ui-and-components.md +59 -0
- package/template/package.json +4 -4
- package/template/plugin.json +1 -1
- package/template/scripts/validate-puredesktop-app.mjs +66 -1
- package/template/src/App.tsx +2 -2
- package/template/src/bridge/platformBridge.ts +62 -15
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Scaffold an external
|
|
3
|
+
* Scaffold an external PureDesktop plugin app.
|
|
4
4
|
*
|
|
5
|
-
* Published as @puredesktop/create-app
|
|
5
|
+
* Published as @puredesktop/create-app -> npm create @puredesktop/app@latest
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
8
8
|
import { dirname, join, resolve } from 'node:path'
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
10
10
|
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function usage() {
|
|
17
|
-
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
18
16
|
|
|
19
17
|
Options:
|
|
20
18
|
--name <title> Display name (default: derived from dir)
|
|
21
19
|
--slug <slug> App slug for settings/bridge (default: dir name)
|
|
22
20
|
--port <number> Dev server port in plugin.json (default: 5300)
|
|
23
|
-
--ui <spec> UI package spec (default: ${DEFAULT_UI}@${DEFAULT_UI_VERSION})
|
|
24
21
|
--manifest-json-base64 <base64>
|
|
25
22
|
Structured plugin.json manifest draft for app-builder use
|
|
26
23
|
--agents-md-base64 <base64>
|
|
@@ -29,30 +26,15 @@ Options:
|
|
|
29
26
|
Examples:
|
|
30
27
|
npm create @puredesktop/app@latest my-tool
|
|
31
28
|
npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
|
|
32
|
-
`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
name: '@puredesktop/puredesktop-ui-bridge',
|
|
39
|
-
version: spec,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const at = spec.lastIndexOf('@')
|
|
43
|
-
if (at > 1) {
|
|
44
|
-
return { name: spec.slice(0, at), version: spec.slice(at + 1) }
|
|
45
|
-
}
|
|
46
|
-
return { name: spec, version: DEFAULT_UI_VERSION }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parseArgs(argv) {
|
|
50
|
-
const positional = []
|
|
51
|
-
const options = {
|
|
29
|
+
`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const positional = []
|
|
34
|
+
const options = {
|
|
52
35
|
name: '',
|
|
53
36
|
slug: '',
|
|
54
37
|
port: 5300,
|
|
55
|
-
ui: `${DEFAULT_UI}@${DEFAULT_UI_VERSION}`,
|
|
56
38
|
manifestJsonBase64: '',
|
|
57
39
|
agentsMdBase64: '',
|
|
58
40
|
}
|
|
@@ -71,12 +53,8 @@ function parseArgs(argv) {
|
|
|
71
53
|
options.slug = argv[++i] ?? ''
|
|
72
54
|
continue
|
|
73
55
|
}
|
|
74
|
-
if (arg === '--port') {
|
|
75
|
-
options.port = Number(argv[++i])
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
if (arg === '--ui') {
|
|
79
|
-
options.ui = argv[++i] ?? options.ui
|
|
56
|
+
if (arg === '--port') {
|
|
57
|
+
options.port = Number(argv[++i])
|
|
80
58
|
continue
|
|
81
59
|
}
|
|
82
60
|
if (arg === '--manifest-json-base64') {
|
|
@@ -223,6 +201,110 @@ function errorMessage(error) {
|
|
|
223
201
|
return error instanceof Error ? error.message : String(error)
|
|
224
202
|
}
|
|
225
203
|
|
|
204
|
+
function readGeneratedManifest(outDir) {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(readFileSync(join(outDir, 'plugin.json'), 'utf8'))
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`Generated plugin.json could not be read: ${errorMessage(error)}`)
|
|
209
|
+
process.exit(1)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readAgentToolNames(manifest) {
|
|
214
|
+
const tools = manifest?.app?.agents?.tools
|
|
215
|
+
if (!Array.isArray(tools)) return []
|
|
216
|
+
return tools
|
|
217
|
+
.map(tool => (typeof tool?.name === 'string' ? tool.name.trim() : ''))
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function jsonString(value) {
|
|
222
|
+
return JSON.stringify(value)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function writeAgentToolScaffold(outDir, appSlug, toolNames) {
|
|
226
|
+
if (toolNames.length === 0) return
|
|
227
|
+
|
|
228
|
+
const agentsDir = join(outDir, 'src', 'agents')
|
|
229
|
+
const handlersDir = join(agentsDir, 'handlers')
|
|
230
|
+
const hooksDir = join(outDir, 'src', 'hooks')
|
|
231
|
+
mkdirSync(handlersDir, { recursive: true })
|
|
232
|
+
mkdirSync(hooksDir, { recursive: true })
|
|
233
|
+
|
|
234
|
+
writeFileSync(
|
|
235
|
+
join(agentsDir, 'catalog.ts'),
|
|
236
|
+
`export const APP_AGENT_TOOL_NAMES = ${JSON.stringify(toolNames, null, 2)} as const
|
|
237
|
+
|
|
238
|
+
export const APP_AGENT_LOG_LABEL = ${jsonString(appSlug)}
|
|
239
|
+
`,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const handlerEntries = toolNames
|
|
243
|
+
.map(
|
|
244
|
+
name =>
|
|
245
|
+
` ${jsonString(name)}: async () => notImplemented(${jsonString(name)}),`,
|
|
246
|
+
)
|
|
247
|
+
.join('\n')
|
|
248
|
+
|
|
249
|
+
writeFileSync(
|
|
250
|
+
join(handlersDir, 'index.ts'),
|
|
251
|
+
`import { agentToolErrorContent } from '@puredesktop/puredesktop-ui-bridge/bridge/agentToolHelpers'
|
|
252
|
+
import type { AgentToolHandler } from '@puredesktop/puredesktop-ui-bridge/bridge/react/usePlatformAgentTools'
|
|
253
|
+
|
|
254
|
+
function notImplemented(toolName: string) {
|
|
255
|
+
return agentToolErrorContent(
|
|
256
|
+
\`Tool "\${toolName}" is declared in plugin.json but its runtime handler has not been implemented yet.\`,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const appAgentHandlers = {
|
|
261
|
+
${handlerEntries}
|
|
262
|
+
} satisfies Record<string, AgentToolHandler>
|
|
263
|
+
`,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
writeFileSync(
|
|
267
|
+
join(hooksDir, 'useAppAgentTools.ts'),
|
|
268
|
+
`import { usePlatformAgentTools } from '@puredesktop/puredesktop-ui-bridge/bridge/react/usePlatformAgentTools'
|
|
269
|
+
import { APP_AGENT_LOG_LABEL, APP_AGENT_TOOL_NAMES } from '../agents/catalog'
|
|
270
|
+
import { appAgentHandlers } from '../agents/handlers'
|
|
271
|
+
|
|
272
|
+
export function useAppAgentTools(ready: boolean): void {
|
|
273
|
+
usePlatformAgentTools({
|
|
274
|
+
ready,
|
|
275
|
+
tools: APP_AGENT_TOOL_NAMES,
|
|
276
|
+
logLabel: APP_AGENT_LOG_LABEL,
|
|
277
|
+
handlers: appAgentHandlers,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
`,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
wireAgentToolHook(outDir)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function wireAgentToolHook(outDir) {
|
|
287
|
+
const appPath = join(outDir, 'src', 'App.tsx')
|
|
288
|
+
const source = readFileSync(appPath, 'utf8')
|
|
289
|
+
const importPattern = /import \{ useAppBoot \} from '\.\/hooks\/useAppBoot'\r?\n/
|
|
290
|
+
const callPattern = /( const \{ error: bridgeError, ready \} = usePlatformBridge\(\)\r?\n)/
|
|
291
|
+
|
|
292
|
+
if (!importPattern.test(source) || !callPattern.test(source)) {
|
|
293
|
+
console.error('Generated src/App.tsx does not match the agent tool scaffold insertion points.')
|
|
294
|
+
process.exit(1)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const withImport = source.replace(
|
|
298
|
+
importPattern,
|
|
299
|
+
match => `${match}import { useAppAgentTools } from './hooks/useAppAgentTools'\n`,
|
|
300
|
+
)
|
|
301
|
+
const withHook = withImport.replace(
|
|
302
|
+
callPattern,
|
|
303
|
+
(_match, line) => `${line} useAppAgentTools(ready)\n`,
|
|
304
|
+
)
|
|
305
|
+
writeFileSync(appPath, withHook)
|
|
306
|
+
}
|
|
307
|
+
|
|
226
308
|
function copyTemplate(srcDir, destDir, vars) {
|
|
227
309
|
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
228
310
|
const srcPath = join(srcDir, entry.name)
|
|
@@ -246,7 +328,6 @@ const structuredManifest = options.manifestJsonBase64
|
|
|
246
328
|
const structuredAgentsMd = options.agentsMdBase64
|
|
247
329
|
? readStructuredAgentsMd(options.agentsMdBase64)
|
|
248
330
|
: null
|
|
249
|
-
const uiDep = uiDependencySpec(options.ui)
|
|
250
331
|
const dirName = targetDir
|
|
251
332
|
const structuredSlug = structuredManifest?.app.slug ?? ''
|
|
252
333
|
const structuredTitle = structuredManifest?.app.name ?? ''
|
|
@@ -273,13 +354,11 @@ mkdirSync(outDir, { recursive: true })
|
|
|
273
354
|
const vars = {
|
|
274
355
|
APP_TITLE: title,
|
|
275
356
|
APP_SLUG: slug,
|
|
276
|
-
APP_NAME: slug,
|
|
277
|
-
PLUGIN_ID: pluginId,
|
|
278
|
-
APP_PORT: String(options.port),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
282
|
-
}
|
|
357
|
+
APP_NAME: slug,
|
|
358
|
+
PLUGIN_ID: pluginId,
|
|
359
|
+
APP_PORT: String(options.port),
|
|
360
|
+
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
361
|
+
}
|
|
283
362
|
|
|
284
363
|
copyTemplate(TEMPLATE_DIR, outDir, vars)
|
|
285
364
|
|
|
@@ -294,8 +373,15 @@ if (structuredAgentsMd) {
|
|
|
294
373
|
writeFileSync(join(outDir, 'agents.md'), structuredAgentsMd)
|
|
295
374
|
}
|
|
296
375
|
|
|
376
|
+
const generatedManifest = readGeneratedManifest(outDir)
|
|
377
|
+
writeAgentToolScaffold(
|
|
378
|
+
outDir,
|
|
379
|
+
generatedManifest?.app?.slug ?? slug,
|
|
380
|
+
readAgentToolNames(generatedManifest),
|
|
381
|
+
)
|
|
382
|
+
|
|
297
383
|
console.log(`
|
|
298
|
-
Created ${title} at ${outDir}
|
|
384
|
+
Created ${title} at ${outDir}
|
|
299
385
|
|
|
300
386
|
cd ${dirName}
|
|
301
387
|
npm install
|
|
@@ -306,7 +392,7 @@ Before shipping:
|
|
|
306
392
|
npm run build
|
|
307
393
|
npm run puredesktop:check
|
|
308
394
|
|
|
309
|
-
Register in
|
|
395
|
+
Register in PureDesktop (File -> Register App...) with:
|
|
310
396
|
entrypoint: http://localhost:${options.port}
|
|
311
397
|
permissions: network, settings, agents
|
|
312
398
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puredesktop/create-app",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Scaffold a
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Scaffold a PureDesktop plugin app (Vite + bridge SDK)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-app": "./bin/create-puredesktop-app.mjs",
|
package/template/README.md
CHANGED
|
@@ -1,313 +1,56 @@
|
|
|
1
|
-
# {{APP_TITLE}}
|
|
2
|
-
|
|
3
|
-
Vite + React plugin app for **PureScience Desktop**, scaffolded with `@puredesktop/create-app`.
|
|
4
|
-
|
|
5
|
-
## Dev in the shell
|
|
6
|
-
|
|
7
|
-
1. `npm install`
|
|
8
|
-
2. `npm run dev` — serves at `http://localhost:{{APP_PORT}}` (must match `plugin.json`)
|
|
9
|
-
3. In PureScience Desktop: **File → Register App…**
|
|
10
|
-
- Entrypoint URL: `http://localhost:{{APP_PORT}}`
|
|
11
|
-
- Permissions: **Network**, **Settings**, **Agents**
|
|
12
|
-
|
|
13
|
-
## Dev in the browser (optional)
|
|
14
|
-
|
|
15
|
-
Open the Vite URL directly. The app runs in standalone dev mode without the shell bridge.
|
|
16
|
-
|
|
17
|
-
## Build & deploy
|
|
1
|
+
# {{APP_TITLE}}
|
|
18
2
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
npm run build
|
|
22
|
-
npm run puredesktop:check
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Deploy the `dist/` folder to HTTPS, then register that URL in PureScience Desktop.
|
|
26
|
-
|
|
27
|
-
Run `npm run puredesktop:check` after every production build. It checks the
|
|
28
|
-
same package facts PureScience Desktop needs before registration:
|
|
29
|
-
|
|
30
|
-
- `plugin.json` is valid and has the required app identity fields.
|
|
31
|
-
- non-local URL entrypoints include the `network` permission.
|
|
32
|
-
- `dist/` exists.
|
|
33
|
-
- `agents.md` is matched by the `agents` permission.
|
|
34
|
-
- `app.usePureDesktopAiPanel` is `true`.
|
|
35
|
-
- `package.json` has the required scripts and dependencies.
|
|
36
|
-
- declared `app.agents.tools` have names, descriptions, schemas, source
|
|
37
|
-
handlers, and built output.
|
|
38
|
-
|
|
39
|
-
## Project layout
|
|
40
|
-
|
|
41
|
-
- `src/App.tsx` — bridge + boot gates, always wraps `AppFrame`
|
|
42
|
-
- `src/components/AppShell.tsx` — your UI starts here
|
|
43
|
-
- `src/bridge/platformBridge.ts` — only file that calls `bridge.call`
|
|
44
|
-
- `plugin.json` — catalog metadata and dev entrypoint port
|
|
45
|
-
- `agents.md` — app-scoped assistant prompt used by PureDesktop agent sessions
|
|
46
|
-
|
|
47
|
-
## PureDesktop app rules
|
|
48
|
-
|
|
49
|
-
Every generated app must keep these files aligned:
|
|
50
|
-
|
|
51
|
-
- `plugin.json` `app.slug` must match `src/constants.ts`.
|
|
52
|
-
- `plugin.json` `entrypoint.url` must match the Vite dev port.
|
|
53
|
-
- `plugin.json` `app.usePureDesktopAiPanel` must stay `true` for now. The
|
|
54
|
-
shell PureDesktop AI panel owns the agent UI.
|
|
55
|
-
- `agents.md` must exist at the package root for app-scoped sessions.
|
|
56
|
-
- `plugin.json` `permissions` must include every bridge capability the app uses:
|
|
57
|
-
`settings` for app settings, `filesystem` for files/storage/dialog/catalog
|
|
58
|
-
file opens, `network` for hosted URLs and network/OAuth/vision calls,
|
|
59
|
-
`assets` for asset library calls, `render` for print/render calls, and
|
|
60
|
-
`agents` for agent sessions, tools, credentials, context, or screen capture.
|
|
61
|
-
- Keep all direct `bridge.call(...)` usage inside `src/bridge/platformBridge.ts`
|
|
62
|
-
or a local bridge helper owned by that folder.
|
|
63
|
-
- Keep `AppFrame` around every return path in `src/App.tsx`, including loading
|
|
64
|
-
and error states.
|
|
65
|
-
|
|
66
|
-
## Required package.json shape
|
|
67
|
-
|
|
68
|
-
Keep these scripts:
|
|
69
|
-
|
|
70
|
-
```json
|
|
71
|
-
{
|
|
72
|
-
"scripts": {
|
|
73
|
-
"dev": "vite",
|
|
74
|
-
"build": "vite build",
|
|
75
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
76
|
-
"puredesktop:check": "node scripts/validate-puredesktop-app.mjs"
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Keep these runtime dependencies:
|
|
82
|
-
|
|
83
|
-
- `@puredesktop/puredesktop-ui-bridge`
|
|
84
|
-
- `react`
|
|
85
|
-
- `react-dom`
|
|
86
|
-
- `styled-components`
|
|
87
|
-
|
|
88
|
-
Keep these dev dependencies:
|
|
89
|
-
|
|
90
|
-
- `@vitejs/plugin-react-swc`
|
|
91
|
-
- `typescript`
|
|
92
|
-
- `vite`
|
|
93
|
-
|
|
94
|
-
## Required plugin.json shape
|
|
3
|
+
Vite + React plugin app for **PureDesktop**, scaffolded with
|
|
4
|
+
`@puredesktop/create-app`.
|
|
95
5
|
|
|
96
|
-
|
|
6
|
+
## Start
|
|
97
7
|
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"id": "{{PLUGIN_ID}}",
|
|
102
|
-
"name": "{{APP_TITLE}}",
|
|
103
|
-
"permissions": ["network", "settings", "agents"],
|
|
104
|
-
"entrypoint": {
|
|
105
|
-
"kind": "dev-url",
|
|
106
|
-
"url": "http://localhost:{{APP_PORT}}"
|
|
107
|
-
},
|
|
108
|
-
"app": {
|
|
109
|
-
"id": "plugin.{{APP_SLUG}}",
|
|
110
|
-
"slug": "{{APP_SLUG}}",
|
|
111
|
-
"name": "{{APP_TITLE}}",
|
|
112
|
-
"navigationLabel": "{{APP_TITLE}}",
|
|
113
|
-
"productName": "pure.{{APP_SLUG}}",
|
|
114
|
-
"kind": "{{APP_SLUG}}",
|
|
115
|
-
"description": "{{APP_TITLE}} - built with PureScience Desktop.",
|
|
116
|
-
"usePureDesktopAiPanel": true
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
When the app exposes tools to its app agent, add tool schemas under
|
|
122
|
-
`app.agents.tools` and keep the top-level `agents` permission:
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"app": {
|
|
127
|
-
"agents": {
|
|
128
|
-
"tools": [
|
|
129
|
-
{
|
|
130
|
-
"name": "listItems",
|
|
131
|
-
"description": "List the current items with ids, titles, status, and next action.",
|
|
132
|
-
"inputSchema": {
|
|
133
|
-
"type": "object",
|
|
134
|
-
"properties": {
|
|
135
|
-
"status": {
|
|
136
|
-
"type": "string",
|
|
137
|
-
"description": "Optional status filter."
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
"additionalProperties": false
|
|
141
|
-
},
|
|
142
|
-
"requiresApproval": false
|
|
143
|
-
}
|
|
144
|
-
]
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Required app agent
|
|
151
|
-
|
|
152
|
-
Every app has an app agent:
|
|
153
|
-
|
|
154
|
-
1. Keep `agents` in top-level `plugin.json` permissions.
|
|
155
|
-
2. Keep root-level `agents.md` next to `plugin.json`.
|
|
156
|
-
3. Keep `app.usePureDesktopAiPanel` set to `true`.
|
|
157
|
-
4. Run `npm run typecheck`, `npm run build`, and `npm run puredesktop:check`.
|
|
158
|
-
|
|
159
|
-
Add tool handlers when the manifest declares `app.agents.tools`.
|
|
160
|
-
|
|
161
|
-
Create a stable tool-name catalog:
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
// src/agents/catalog.ts
|
|
165
|
-
export const APP_AGENT_TOOL_NAMES = ['listItems'] as const
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
npm run dev
|
|
166
11
|
```
|
|
167
12
|
|
|
168
|
-
|
|
13
|
+
The dev server runs at `http://localhost:{{APP_PORT}}`, matching
|
|
14
|
+
`plugin.json`.
|
|
169
15
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
import {
|
|
173
|
-
agentToolErrorContent,
|
|
174
|
-
formatAgentToolJson,
|
|
175
|
-
readAgentToolStringArg,
|
|
176
|
-
} from '@puredesktop/puredesktop-ui-bridge/bridge/agentToolHelpers'
|
|
177
|
-
import { APP_AGENT_TOOL_NAMES } from './agents/catalog'
|
|
16
|
+
In PureDesktop, register the app from **File -> Register App...** with the same
|
|
17
|
+
entrypoint URL.
|
|
178
18
|
|
|
179
|
-
|
|
180
|
-
usePlatformAgentTools({
|
|
181
|
-
ready,
|
|
182
|
-
tools: APP_AGENT_TOOL_NAMES,
|
|
183
|
-
handlers: {
|
|
184
|
-
async listItems(ctx) {
|
|
185
|
-
const status = readAgentToolStringArg(ctx.arguments, 'status')
|
|
186
|
-
try {
|
|
187
|
-
return {
|
|
188
|
-
content: formatAgentToolJson({
|
|
189
|
-
items: [],
|
|
190
|
-
status: status || null,
|
|
191
|
-
}),
|
|
192
|
-
}
|
|
193
|
-
} catch (error) {
|
|
194
|
-
return agentToolErrorContent(
|
|
195
|
-
error instanceof Error ? error.message : String(error),
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
})
|
|
19
|
+
## Build
|
|
201
20
|
|
|
202
|
-
|
|
203
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npm run typecheck
|
|
23
|
+
npm run build
|
|
24
|
+
npm run puredesktop:check
|
|
204
25
|
```
|
|
205
26
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
external-effect tools should use `requiresApproval: true`.
|
|
210
|
-
|
|
211
|
-
## Design baseline
|
|
212
|
-
|
|
213
|
-
Build quiet, dense, legible work software. Make the main work object visible
|
|
214
|
-
quickly and directly actionable. Use platform components and `--platform-*`
|
|
215
|
-
CSS variables before adding local styling. Keep one source of truth for each
|
|
216
|
-
object and avoid persistent chrome that competes with the work.
|
|
27
|
+
`puredesktop:check` verifies the package facts the shell needs before
|
|
28
|
+
registration: manifest shape, entrypoint, permissions, `agents.md`, build output,
|
|
29
|
+
and declared app agent tools.
|
|
217
30
|
|
|
218
|
-
##
|
|
31
|
+
## Project Layout
|
|
219
32
|
|
|
220
|
-
|
|
221
|
-
|
|
33
|
+
- `plugin.json`: app identity, permissions, entrypoint, routing, and agent tool
|
|
34
|
+
declarations.
|
|
35
|
+
- `agents.md`: app-scoped assistant prompt.
|
|
36
|
+
- `src/App.tsx`: bridge and boot gates wrapped in `AppFrame`.
|
|
37
|
+
- `src/bridge/platformBridge.ts`: local app bridge exports.
|
|
38
|
+
- `src/hooks/useAppBoot.ts`: boot data loading after bridge readiness.
|
|
39
|
+
- `src/components/AppShell.tsx`: first app-owned UI surface.
|
|
40
|
+
- `docs/`: builder-facing app, bridge, agent, and tool-handler docs.
|
|
222
41
|
|
|
223
|
-
|
|
224
|
-
--platform-mode
|
|
42
|
+
## Builder Docs
|
|
225
43
|
|
|
226
|
-
|
|
227
|
-
--platform-colors-surface
|
|
228
|
-
--platform-colors-surface-hover
|
|
229
|
-
--platform-colors-surface-active
|
|
230
|
-
--platform-colors-elevated
|
|
231
|
-
--platform-colors-text
|
|
232
|
-
--platform-colors-text-secondary
|
|
233
|
-
--platform-colors-text-disabled
|
|
234
|
-
--platform-colors-text-inverse
|
|
235
|
-
--platform-colors-accent
|
|
236
|
-
--platform-colors-accent-hover
|
|
237
|
-
--platform-colors-accent-muted
|
|
238
|
-
--platform-colors-border
|
|
239
|
-
--platform-colors-border-strong
|
|
240
|
-
--platform-colors-divider
|
|
241
|
-
--platform-colors-danger
|
|
242
|
-
--platform-colors-text-danger
|
|
243
|
-
--platform-colors-warning
|
|
244
|
-
--platform-colors-success
|
|
245
|
-
--platform-colors-info
|
|
246
|
-
--platform-colors-semantic-blue
|
|
247
|
-
--platform-colors-semantic-blue-text
|
|
248
|
-
--platform-colors-semantic-blue-muted
|
|
249
|
-
--platform-colors-semantic-blue-border
|
|
250
|
-
--platform-colors-semantic-orange
|
|
251
|
-
--platform-colors-semantic-orange-text
|
|
252
|
-
--platform-colors-semantic-orange-muted
|
|
253
|
-
--platform-colors-semantic-orange-border
|
|
254
|
-
--platform-colors-semantic-green
|
|
255
|
-
--platform-colors-semantic-green-text
|
|
256
|
-
--platform-colors-semantic-green-muted
|
|
257
|
-
--platform-colors-semantic-green-border
|
|
258
|
-
--platform-colors-semantic-red
|
|
259
|
-
--platform-colors-semantic-red-text
|
|
260
|
-
--platform-colors-semantic-red-muted
|
|
261
|
-
--platform-colors-semantic-red-border
|
|
262
|
-
--platform-colors-focus
|
|
263
|
-
--platform-colors-selection
|
|
264
|
-
--platform-colors-app-viewport
|
|
265
|
-
--platform-colors-chrome-titlebar
|
|
266
|
-
--platform-colors-chrome-tab-strip
|
|
44
|
+
Read [docs/README.md](./docs/README.md) before changing app structure.
|
|
267
45
|
|
|
268
|
-
|
|
269
|
-
--platform-typography-font-family-content
|
|
270
|
-
--platform-typography-font-family-mono
|
|
271
|
-
--platform-typography-font-size-base
|
|
272
|
-
--platform-typography-font-size-xs
|
|
273
|
-
--platform-typography-font-size-sm
|
|
274
|
-
--platform-typography-font-size-lg
|
|
275
|
-
--platform-typography-font-size-xl
|
|
276
|
-
--platform-typography-line-height-base
|
|
277
|
-
--platform-typography-line-height-tight
|
|
278
|
-
--platform-typography-font-weight-normal
|
|
279
|
-
--platform-typography-font-weight-medium
|
|
280
|
-
--platform-typography-font-weight-bold
|
|
46
|
+
Bridge helper docs live in [docs/bridge/README.md](./docs/bridge/README.md).
|
|
281
47
|
|
|
282
|
-
|
|
283
|
-
--platform-spacing-sm
|
|
284
|
-
--platform-spacing-md
|
|
285
|
-
--platform-spacing-lg
|
|
286
|
-
--platform-spacing-xl
|
|
287
|
-
--platform-spacing-xxl
|
|
288
|
-
|
|
289
|
-
--platform-radius-sm
|
|
290
|
-
--platform-radius-md
|
|
291
|
-
--platform-radius-lg
|
|
292
|
-
--platform-radius-full
|
|
293
|
-
|
|
294
|
-
--platform-shadow-sm
|
|
295
|
-
--platform-shadow-md
|
|
296
|
-
--platform-shadow-lg
|
|
297
|
-
|
|
298
|
-
--platform-transition-fast
|
|
299
|
-
--platform-transition-base
|
|
300
|
-
--platform-transition-slow
|
|
301
|
-
|
|
302
|
-
--platform-z-index-zi-app-base
|
|
303
|
-
--platform-z-index-zi-app-sidebar
|
|
304
|
-
--platform-z-index-zi-app-chrome
|
|
305
|
-
--platform-z-index-zi-app-viewport-toolbar
|
|
306
|
-
--platform-z-index-zi-app-menus
|
|
307
|
-
--platform-z-index-zi-app-toast
|
|
308
|
-
--platform-z-index-zi-app-modal
|
|
309
|
-
```
|
|
48
|
+
## Core Rules
|
|
310
49
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
for
|
|
50
|
+
- `plugin.json` `app.slug` matches `src/constants.ts` `APP_SLUG`.
|
|
51
|
+
- `plugin.json` `entrypoint.url` matches the Vite dev port.
|
|
52
|
+
- `app.usePureDesktopAiPanel` stays `true` for shell-owned agent UI.
|
|
53
|
+
- `agents.md` stays at the package root for app-scoped sessions.
|
|
54
|
+
- Bridge calls stay inside `src/bridge/platformBridge.ts` or files owned by
|
|
55
|
+
`src/bridge/`.
|
|
56
|
+
- `AppFrame` wraps every `src/App.tsx` return path.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# PureDesktop App Builder Docs
|
|
2
|
+
|
|
3
|
+
This folder is the builder-facing contract for generated PureDesktop apps.
|
|
4
|
+
|
|
5
|
+
A generated app is a Vite + React application rendered by PureDesktop inside a
|
|
6
|
+
workspace iframe. The app owns its UI, state, domain logic, and optional tool
|
|
7
|
+
handlers. The shell owns discovery, tabs, bridge permissions, filesystem and OS
|
|
8
|
+
services, agent sessions, and app registration.
|
|
9
|
+
|
|
10
|
+
Read these in order when building or repairing an app:
|
|
11
|
+
|
|
12
|
+
1. [App lifecycle](./app-lifecycle.md)
|
|
13
|
+
2. [Plugin manifest](./plugin-manifest.md)
|
|
14
|
+
3. [Bridge helpers](./bridge/README.md)
|
|
15
|
+
4. [App agent](./agent.md)
|
|
16
|
+
5. [Tool handlers](./tool-handlers.md)
|
|
17
|
+
6. [UI and components](./ui-and-components.md)
|
|
18
|
+
7. [Theme CSS variables](./theme-vars.md)
|
|
19
|
+
|
|
20
|
+
Key files in this scaffold:
|
|
21
|
+
|
|
22
|
+
- `plugin.json`: app identity, entrypoint, permissions, file routing, and agent
|
|
23
|
+
tool declarations.
|
|
24
|
+
- `agents.md`: app-scoped assistant instructions used by shell agent sessions.
|
|
25
|
+
- `src/App.tsx`: bridge readiness, boot loading, and `AppFrame` wrapper.
|
|
26
|
+
- `src/bridge/platformBridge.ts`: local app bridge surface.
|
|
27
|
+
- `src/hooks/useAppBoot.ts`: app boot data loading after the bridge is ready.
|
|
28
|
+
- `src/components/AppShell.tsx`: first app-owned UI surface.
|
|
29
|
+
- `scripts/validate-puredesktop-app.mjs`: registration and tool scaffold check.
|
|
30
|
+
|
|
31
|
+
Use `npm run puredesktop:check` after `npm run build` before registration.
|