@puredesktop/create-app 1.0.0-beta.1 → 1.0.0-beta.2
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 +164 -159
- package/package.json +1 -1
- package/template/README.md +299 -16
- package/template/agents.md +11 -0
- package/template/gitignore.tpl +5 -5
- package/template/index.html +12 -12
- package/template/package.json +22 -21
- package/template/plugin.json +3 -2
- package/template/scripts/validate-puredesktop-app.mjs +277 -0
- package/template/src/App.tsx +49 -49
- package/template/src/bridge/platformBridge.ts +31 -31
- package/template/src/components/AppShell.tsx +31 -31
- package/template/src/constants.ts +1 -1
- package/template/src/hooks/useAppBoot.ts +45 -45
- package/template/src/main.tsx +9 -9
- package/template/src/types.ts +5 -5
- package/template/tsconfig.json +15 -15
- package/template/vite-env.d.ts +1 -1
- package/template/vite.config.js +28 -28
|
@@ -1,164 +1,169 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Scaffold an external PureScience Desktop plugin app.
|
|
4
|
-
*
|
|
5
|
-
* Published as @puredesktop/create-app → npm create @puredesktop/app@latest
|
|
6
|
-
*/
|
|
7
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
8
|
-
import { dirname, join, resolve } from 'node:path'
|
|
9
|
-
import { fileURLToPath } from 'node:url'
|
|
10
|
-
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
13
|
-
const DEFAULT_UI = '@puredesktop/puredesktop-ui-bridge'
|
|
14
|
-
const DEFAULT_UI_VERSION = '1.0.0-beta.
|
|
15
|
-
|
|
16
|
-
function usage() {
|
|
17
|
-
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
18
|
-
|
|
19
|
-
Options:
|
|
20
|
-
--name <title> Display name (default: derived from dir)
|
|
21
|
-
--slug <slug> App slug for settings/bridge (default: dir name)
|
|
22
|
-
--port <number> Dev server port in plugin.json (default: 5300)
|
|
23
|
-
--ui <spec> UI package spec (default: ${DEFAULT_UI}@${DEFAULT_UI_VERSION})
|
|
24
|
-
|
|
25
|
-
Examples:
|
|
26
|
-
npm create @puredesktop/app@latest my-tool
|
|
27
|
-
npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
|
|
28
|
-
`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function uiDependencySpec(spec) {
|
|
32
|
-
if (spec.startsWith('file:')) {
|
|
33
|
-
return {
|
|
34
|
-
name: '@puredesktop/puredesktop-ui-bridge',
|
|
35
|
-
version: spec,
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const at = spec.lastIndexOf('@')
|
|
39
|
-
if (at > 1) {
|
|
40
|
-
return { name: spec.slice(0, at), version: spec.slice(at + 1) }
|
|
41
|
-
}
|
|
42
|
-
return { name: spec, version: DEFAULT_UI_VERSION }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseArgs(argv) {
|
|
46
|
-
const positional = []
|
|
47
|
-
const options = {
|
|
48
|
-
name: '',
|
|
49
|
-
slug: '',
|
|
50
|
-
port: 5300,
|
|
51
|
-
ui: `${DEFAULT_UI}@${DEFAULT_UI_VERSION}`,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
55
|
-
const arg = argv[i]
|
|
56
|
-
if (arg === '--help' || arg === '-h') {
|
|
57
|
-
usage()
|
|
58
|
-
process.exit(0)
|
|
59
|
-
}
|
|
60
|
-
if (arg === '--name') {
|
|
61
|
-
options.name = argv[++i] ?? ''
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
if (arg === '--slug') {
|
|
65
|
-
options.slug = argv[++i] ?? ''
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
if (arg === '--port') {
|
|
69
|
-
options.port = Number(argv[++i])
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
if (arg === '--ui') {
|
|
73
|
-
options.ui = argv[++i] ?? options.ui
|
|
74
|
-
continue
|
|
75
|
-
}
|
|
76
|
-
if (arg.startsWith('-')) {
|
|
77
|
-
console.error(`Unknown option: ${arg}`)
|
|
78
|
-
usage()
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
positional.push(arg)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
85
|
-
console.error('--port must be a positive integer')
|
|
86
|
-
process.exit(1)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { targetDir: positional[0] ?? 'puredesktop-app', options }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function slugify(value) {
|
|
93
|
-
return value
|
|
94
|
-
.trim()
|
|
95
|
-
.toLowerCase()
|
|
96
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
97
|
-
.replace(/^-+|-+$/g, '')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function titleCaseFromSlug(slug) {
|
|
101
|
-
return slug
|
|
102
|
-
.split('-')
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
105
|
-
.join(' ')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function copyTemplate(srcDir, destDir, vars) {
|
|
109
|
-
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
110
|
-
const srcPath = join(srcDir, entry.name)
|
|
111
|
-
const destName = entry.name.replace(/\.tpl$/u, '')
|
|
112
|
-
const destPath = join(destDir, destName)
|
|
113
|
-
if (entry.isDirectory()) {
|
|
114
|
-
mkdirSync(destPath, { recursive: true })
|
|
115
|
-
copyTemplate(srcPath, destPath, vars)
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
const raw = readFileSync(srcPath, 'utf8')
|
|
119
|
-
const rendered = raw.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? '')
|
|
120
|
-
writeFileSync(destPath, rendered)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const { targetDir, options } = parseArgs(process.argv.slice(2))
|
|
125
|
-
const uiDep = uiDependencySpec(options.ui)
|
|
126
|
-
const dirName = targetDir
|
|
127
|
-
const slug = options.slug || slugify(dirName) || 'my-app'
|
|
128
|
-
const title = options.name || titleCaseFromSlug(slug) || 'My App'
|
|
129
|
-
const pluginId = slug
|
|
130
|
-
const outDir = resolve(process.cwd(), dirName)
|
|
131
|
-
|
|
132
|
-
if (existsSync(outDir)) {
|
|
133
|
-
console.error(`Target already exists: ${outDir}`)
|
|
134
|
-
process.exit(1)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
mkdirSync(outDir, { recursive: true })
|
|
138
|
-
|
|
139
|
-
const vars = {
|
|
140
|
-
APP_TITLE: title,
|
|
141
|
-
APP_SLUG: slug,
|
|
142
|
-
APP_NAME: slug,
|
|
143
|
-
PLUGIN_ID: pluginId,
|
|
144
|
-
APP_PORT: String(options.port),
|
|
145
|
-
UI_PACKAGE_NAME: uiDep.name,
|
|
146
|
-
UI_PACKAGE_VERSION: uiDep.version,
|
|
147
|
-
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
copyTemplate(TEMPLATE_DIR, outDir, vars)
|
|
151
|
-
|
|
152
|
-
console.log(`
|
|
153
|
-
Created ${title} at ${outDir}
|
|
154
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold an external PureScience Desktop plugin app.
|
|
4
|
+
*
|
|
5
|
+
* Published as @puredesktop/create-app → npm create @puredesktop/app@latest
|
|
6
|
+
*/
|
|
7
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
8
|
+
import { dirname, join, resolve } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
13
|
+
const DEFAULT_UI = '@puredesktop/puredesktop-ui-bridge'
|
|
14
|
+
const DEFAULT_UI_VERSION = '1.0.0-beta.2'
|
|
15
|
+
|
|
16
|
+
function usage() {
|
|
17
|
+
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--name <title> Display name (default: derived from dir)
|
|
21
|
+
--slug <slug> App slug for settings/bridge (default: dir name)
|
|
22
|
+
--port <number> Dev server port in plugin.json (default: 5300)
|
|
23
|
+
--ui <spec> UI package spec (default: ${DEFAULT_UI}@${DEFAULT_UI_VERSION})
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
npm create @puredesktop/app@latest my-tool
|
|
27
|
+
npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
|
|
28
|
+
`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uiDependencySpec(spec) {
|
|
32
|
+
if (spec.startsWith('file:')) {
|
|
33
|
+
return {
|
|
34
|
+
name: '@puredesktop/puredesktop-ui-bridge',
|
|
35
|
+
version: spec,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const at = spec.lastIndexOf('@')
|
|
39
|
+
if (at > 1) {
|
|
40
|
+
return { name: spec.slice(0, at), version: spec.slice(at + 1) }
|
|
41
|
+
}
|
|
42
|
+
return { name: spec, version: DEFAULT_UI_VERSION }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const positional = []
|
|
47
|
+
const options = {
|
|
48
|
+
name: '',
|
|
49
|
+
slug: '',
|
|
50
|
+
port: 5300,
|
|
51
|
+
ui: `${DEFAULT_UI}@${DEFAULT_UI_VERSION}`,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
55
|
+
const arg = argv[i]
|
|
56
|
+
if (arg === '--help' || arg === '-h') {
|
|
57
|
+
usage()
|
|
58
|
+
process.exit(0)
|
|
59
|
+
}
|
|
60
|
+
if (arg === '--name') {
|
|
61
|
+
options.name = argv[++i] ?? ''
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
if (arg === '--slug') {
|
|
65
|
+
options.slug = argv[++i] ?? ''
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
if (arg === '--port') {
|
|
69
|
+
options.port = Number(argv[++i])
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--ui') {
|
|
73
|
+
options.ui = argv[++i] ?? options.ui
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (arg.startsWith('-')) {
|
|
77
|
+
console.error(`Unknown option: ${arg}`)
|
|
78
|
+
usage()
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
positional.push(arg)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
85
|
+
console.error('--port must be a positive integer')
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { targetDir: positional[0] ?? 'puredesktop-app', options }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function slugify(value) {
|
|
93
|
+
return value
|
|
94
|
+
.trim()
|
|
95
|
+
.toLowerCase()
|
|
96
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
97
|
+
.replace(/^-+|-+$/g, '')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function titleCaseFromSlug(slug) {
|
|
101
|
+
return slug
|
|
102
|
+
.split('-')
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
105
|
+
.join(' ')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function copyTemplate(srcDir, destDir, vars) {
|
|
109
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
110
|
+
const srcPath = join(srcDir, entry.name)
|
|
111
|
+
const destName = entry.name.replace(/\.tpl$/u, '')
|
|
112
|
+
const destPath = join(destDir, destName)
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
mkdirSync(destPath, { recursive: true })
|
|
115
|
+
copyTemplate(srcPath, destPath, vars)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
const raw = readFileSync(srcPath, 'utf8')
|
|
119
|
+
const rendered = raw.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? '')
|
|
120
|
+
writeFileSync(destPath, rendered)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { targetDir, options } = parseArgs(process.argv.slice(2))
|
|
125
|
+
const uiDep = uiDependencySpec(options.ui)
|
|
126
|
+
const dirName = targetDir
|
|
127
|
+
const slug = options.slug || slugify(dirName) || 'my-app'
|
|
128
|
+
const title = options.name || titleCaseFromSlug(slug) || 'My App'
|
|
129
|
+
const pluginId = slug
|
|
130
|
+
const outDir = resolve(process.cwd(), dirName)
|
|
131
|
+
|
|
132
|
+
if (existsSync(outDir)) {
|
|
133
|
+
console.error(`Target already exists: ${outDir}`)
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mkdirSync(outDir, { recursive: true })
|
|
138
|
+
|
|
139
|
+
const vars = {
|
|
140
|
+
APP_TITLE: title,
|
|
141
|
+
APP_SLUG: slug,
|
|
142
|
+
APP_NAME: slug,
|
|
143
|
+
PLUGIN_ID: pluginId,
|
|
144
|
+
APP_PORT: String(options.port),
|
|
145
|
+
UI_PACKAGE_NAME: uiDep.name,
|
|
146
|
+
UI_PACKAGE_VERSION: uiDep.version,
|
|
147
|
+
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
copyTemplate(TEMPLATE_DIR, outDir, vars)
|
|
151
|
+
|
|
152
|
+
console.log(`
|
|
153
|
+
Created ${title} at ${outDir}
|
|
154
|
+
|
|
155
155
|
cd ${dirName}
|
|
156
156
|
npm install
|
|
157
157
|
npm run dev
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
Before shipping:
|
|
160
|
+
npm run typecheck
|
|
161
|
+
npm run build
|
|
162
|
+
npm run puredesktop:check
|
|
163
|
+
|
|
164
|
+
Register in PureScience Desktop (File → Register App…) with:
|
|
165
|
+
entrypoint: http://localhost:${options.port}
|
|
166
|
+
permissions: network, settings, agents
|
|
167
|
+
|
|
168
|
+
Build your UI in src/components/AppShell.tsx.
|
|
169
|
+
`)
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -1,30 +1,313 @@
|
|
|
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**
|
|
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
|
-
|
|
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
17
|
## Build & deploy
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
+
npm run typecheck
|
|
20
21
|
npm run build
|
|
22
|
+
npm run puredesktop:check
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
Deploy the `dist/` folder to HTTPS, then register that URL in PureScience Desktop.
|
|
24
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
|
+
|
|
25
39
|
## Project layout
|
|
26
40
|
|
|
27
41
|
- `src/App.tsx` — bridge + boot gates, always wraps `AppFrame`
|
|
28
42
|
- `src/components/AppShell.tsx` — your UI starts here
|
|
29
43
|
- `src/bridge/platformBridge.ts` — only file that calls `bridge.call`
|
|
30
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
|
|
95
|
+
|
|
96
|
+
Keep this structure and update values rather than inventing new fields:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"schemaVersion": 1,
|
|
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
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Register matching runtime handlers after the bridge is ready:
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { usePlatformAgentTools } from '@puredesktop/puredesktop-ui-bridge/bridge/react/usePlatformAgentTools'
|
|
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'
|
|
178
|
+
|
|
179
|
+
function AppAgentTools({ ready }: { ready: boolean }) {
|
|
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
|
+
})
|
|
201
|
+
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Manifest tool names and handler names must match exactly. Read tools should
|
|
207
|
+
return compact JSON with stable ids. Write tools should validate exact targets,
|
|
208
|
+
reject ambiguous edits, and return a concise JSON result. Destructive or
|
|
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.
|
|
217
|
+
|
|
218
|
+
## Platform CSS variables
|
|
219
|
+
|
|
220
|
+
Theme variables are injected by PureDesktop and follow
|
|
221
|
+
`--platform-<domain>-<token>`. Use these before adding app-local variables:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
--platform-mode
|
|
225
|
+
|
|
226
|
+
--platform-colors-bg
|
|
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
|
|
267
|
+
|
|
268
|
+
--platform-typography-font-family
|
|
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
|
|
281
|
+
|
|
282
|
+
--platform-spacing-xs
|
|
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
|
+
```
|
|
310
|
+
|
|
311
|
+
Use the plural `--platform-colors-*` prefix for colors. Do not create
|
|
312
|
+
`--platform-color-*` variables. App-specific variables should use an app prefix,
|
|
313
|
+
for example `--my-app-accent`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# {{APP_TITLE}} Agent
|
|
2
|
+
|
|
3
|
+
You are the app-scoped assistant for {{APP_TITLE}}.
|
|
4
|
+
|
|
5
|
+
Help the user understand, review, and act on the work inside this app. Stay
|
|
6
|
+
grounded in the app UI and data. Prefer concise answers, concrete next actions,
|
|
7
|
+
and safe tool use.
|
|
8
|
+
|
|
9
|
+
When tools are available, use read-only tools before write tools. Ask for
|
|
10
|
+
confirmation before destructive or external-effect changes unless the tool
|
|
11
|
+
policy already requires approval.
|
package/template/gitignore.tpl
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
.gitignore
|
|
2
|
-
node_modules
|
|
3
|
-
dist
|
|
4
|
-
.env
|
|
5
|
-
.npmrc
|
|
1
|
+
.gitignore
|
|
2
|
+
node_modules
|
|
3
|
+
dist
|
|
4
|
+
.env
|
|
5
|
+
.npmrc
|