@puredesktop/create-app 2.1.9 → 2.1.10
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 +70 -338
- package/package.json +3 -2
- package/template/README.md +3 -3
- package/template/docs/README.md +9 -8
- package/template/docs/howtos/persisting-app-state.md +242 -242
- package/template/docs/howtos/using-document-editor.md +440 -0
- package/template/docs/plugin-manifest.md +1 -1
- package/template/docs/ui-and-components.md +84 -79
- package/template/gitignore.tpl +6 -1
- package/template/package.json +1 -1
- package/template/plugin.json +1 -1
- package/template/scripts/validate-puredesktop-app.mjs +46 -46
- package/template/src/App.tsx +8 -8
- package/template/src/components/AppShell.tsx +11 -11
- package/template/src/components/StarterWorkspace.tsx +154 -154
- package/template/src/constants.ts +2 -2
- package/template/src/lib/starterWorkspace.ts +29 -29
- package/template/vite.config.js +13 -13
|
@@ -4,12 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Published as @puredesktop/create-app -> npm create @puredesktop/app@latest
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { dirname, join, resolve } from 'node:path'
|
|
9
|
-
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { scaffoldAppFromFiles } from '../lib/scaffold.mjs'
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
9
|
+
class UsageError extends Error {}
|
|
13
10
|
|
|
14
11
|
function usage() {
|
|
15
12
|
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
@@ -18,13 +15,16 @@ Options:
|
|
|
18
15
|
--name <title> Display name (default: derived from dir)
|
|
19
16
|
--slug <slug> App slug for settings/bridge (default: dir name)
|
|
20
17
|
--port <number> Dev server port in plugin.json (default: 5300)
|
|
21
|
-
--
|
|
22
|
-
|
|
23
|
-
--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
--target-path <path>
|
|
19
|
+
Scaffold into this path instead of [dir]
|
|
20
|
+
--allow-existing Allow target path to already exist
|
|
21
|
+
--manifest-json-file <path>
|
|
22
|
+
Read structured plugin.json manifest draft from a file
|
|
23
|
+
--agents-md-file <path>
|
|
24
|
+
Read structured agents.md content from a file
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
npm create @puredesktop/app@latest my-tool
|
|
28
28
|
npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
|
|
29
29
|
`)
|
|
30
30
|
}
|
|
@@ -32,12 +32,14 @@ Examples:
|
|
|
32
32
|
function parseArgs(argv) {
|
|
33
33
|
const positional = []
|
|
34
34
|
const options = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
agentsMdFile: '',
|
|
36
|
+
allowExisting: false,
|
|
37
|
+
manifestJsonFile: '',
|
|
38
|
+
name: '',
|
|
39
|
+
port: 5300,
|
|
40
|
+
slug: '',
|
|
41
|
+
targetPath: '',
|
|
42
|
+
}
|
|
41
43
|
|
|
42
44
|
for (let i = 0; i < argv.length; i += 1) {
|
|
43
45
|
const arg = argv[i]
|
|
@@ -57,333 +59,43 @@ function parseArgs(argv) {
|
|
|
57
59
|
options.port = Number(argv[++i])
|
|
58
60
|
continue
|
|
59
61
|
}
|
|
60
|
-
if (arg === '--
|
|
61
|
-
options.
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
if (arg === '--
|
|
65
|
-
options.
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
62
|
+
if (arg === '--target-path') {
|
|
63
|
+
options.targetPath = argv[++i] ?? ''
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
if (arg === '--allow-existing') {
|
|
67
|
+
options.allowExisting = true
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (arg === '--manifest-json-file') {
|
|
71
|
+
options.manifestJsonFile = argv[++i] ?? ''
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
if (arg === '--agents-md-file') {
|
|
75
|
+
options.agentsMdFile = argv[++i] ?? ''
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
68
78
|
if (arg.startsWith('-')) {
|
|
69
|
-
|
|
70
|
-
usage()
|
|
71
|
-
process.exit(1)
|
|
79
|
+
throw new UsageError(`Unknown option: ${arg}`)
|
|
72
80
|
}
|
|
73
81
|
positional.push(arg)
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
process.exit(1)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return { targetDir: positional[0] ?? 'puredesktop-app', options }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function slugify(value) {
|
|
85
|
-
return value
|
|
86
|
-
.trim()
|
|
87
|
-
.toLowerCase()
|
|
88
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
89
|
-
.replace(/^-+|-+$/g, '')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function titleCaseFromSlug(slug) {
|
|
93
|
-
return slug
|
|
94
|
-
.split('-')
|
|
95
|
-
.filter(Boolean)
|
|
96
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
97
|
-
.join(' ')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function readStructuredManifestFile(path, port) {
|
|
101
|
-
const text = readUtf8File(path, '--manifest-json-file')
|
|
102
|
-
return readStructuredManifestText(text, port, '--manifest-json-file')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function readStructuredManifestText(text, port, label) {
|
|
106
|
-
let manifest
|
|
107
|
-
try {
|
|
108
|
-
manifest = JSON.parse(text)
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error(`${label} must contain JSON: ${errorMessage(error)}`)
|
|
111
|
-
process.exit(1)
|
|
112
|
-
}
|
|
113
|
-
return normalizeStructuredManifest(manifest, port, label)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function normalizeStructuredManifest(manifest, port, label) {
|
|
117
|
-
if (!isPlainObject(manifest)) {
|
|
118
|
-
console.error(`${label} must contain a manifest object`)
|
|
119
|
-
process.exit(1)
|
|
120
|
-
}
|
|
121
|
-
if (manifest.schemaVersion !== 1) {
|
|
122
|
-
console.error('manifest.schemaVersion must be 1')
|
|
123
|
-
process.exit(1)
|
|
124
|
-
}
|
|
125
|
-
const id = requiredString(manifest.id, 'manifest.id')
|
|
126
|
-
const name = requiredString(manifest.name, 'manifest.name')
|
|
127
|
-
if (!isPlainObject(manifest.app)) {
|
|
128
|
-
console.error('manifest.app is required')
|
|
129
|
-
process.exit(1)
|
|
130
|
-
}
|
|
131
|
-
const appSlug = requiredString(manifest.app.slug, 'manifest.app.slug')
|
|
132
|
-
const appName = requiredString(manifest.app.name, 'manifest.app.name')
|
|
133
|
-
if (slugify(appSlug) !== appSlug) {
|
|
134
|
-
console.error('manifest.app.slug must use lowercase letters, numbers, and hyphens')
|
|
135
|
-
process.exit(1)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return stripUndefinedProperties({
|
|
139
|
-
...manifest,
|
|
140
|
-
id,
|
|
141
|
-
name,
|
|
142
|
-
entrypoint: {
|
|
143
|
-
kind: 'dev-url',
|
|
144
|
-
url: `http://localhost:${port}`,
|
|
145
|
-
},
|
|
146
|
-
app: stripUndefinedProperties({
|
|
147
|
-
...manifest.app,
|
|
148
|
-
slug: appSlug,
|
|
149
|
-
name: appName,
|
|
150
|
-
entrypoint: undefined,
|
|
151
|
-
}),
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function readStructuredAgentsMdFile(path) {
|
|
156
|
-
const text = readUtf8File(path, '--agents-md-file')
|
|
157
|
-
return readStructuredAgentsMdText(text, '--agents-md-file')
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function readStructuredAgentsMdText(text, label) {
|
|
161
|
-
if (!text.trim()) {
|
|
162
|
-
console.error(`${label} must contain non-empty agents.md content`)
|
|
163
|
-
process.exit(1)
|
|
164
|
-
}
|
|
165
|
-
return text.endsWith('\n') ? text : `${text}\n`
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function readUtf8File(path, label) {
|
|
169
|
-
if (typeof path !== 'string' || !path.trim()) {
|
|
170
|
-
console.error(`${label} requires a file path`)
|
|
171
|
-
process.exit(1)
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
return readFileSync(resolve(path), 'utf8')
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.error(`${label} could not be read: ${errorMessage(error)}`)
|
|
177
|
-
process.exit(1)
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function requiredString(value, label) {
|
|
182
|
-
const text = typeof value === 'string' ? value.trim() : ''
|
|
183
|
-
if (!text) {
|
|
184
|
-
console.error(`${label} is required`)
|
|
185
|
-
process.exit(1)
|
|
186
|
-
}
|
|
187
|
-
return text
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function isPlainObject(value) {
|
|
191
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function stripUndefinedProperties(value) {
|
|
195
|
-
return Object.fromEntries(
|
|
196
|
-
Object.entries(value).filter(([, entry]) => entry !== undefined),
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function errorMessage(error) {
|
|
201
|
-
return error instanceof Error ? error.message : String(error)
|
|
202
|
-
}
|
|
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)
|
|
84
|
+
if (options.targetPath && positional.length > 0) {
|
|
85
|
+
throw new UsageError('Pass either [dir] or --target-path, not both')
|
|
295
86
|
}
|
|
296
87
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
)
|
|
301
|
-
const withHook = withImport.replace(
|
|
302
|
-
callPattern,
|
|
303
|
-
(_match, line) => `${line} useAppAgentTools(ready)\n`,
|
|
304
|
-
)
|
|
305
|
-
writeFileSync(appPath, withHook)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function copyTemplate(srcDir, destDir, vars) {
|
|
309
|
-
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
310
|
-
const srcPath = join(srcDir, entry.name)
|
|
311
|
-
const destName = entry.name.replace(/\.tpl$/u, '')
|
|
312
|
-
const destPath = join(destDir, destName)
|
|
313
|
-
if (entry.isDirectory()) {
|
|
314
|
-
mkdirSync(destPath, { recursive: true })
|
|
315
|
-
copyTemplate(srcPath, destPath, vars)
|
|
316
|
-
continue
|
|
317
|
-
}
|
|
318
|
-
const raw = readFileSync(srcPath, 'utf8')
|
|
319
|
-
const rendered = raw.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? '')
|
|
320
|
-
writeFileSync(destPath, rendered)
|
|
88
|
+
return {
|
|
89
|
+
options,
|
|
90
|
+
targetDir: options.targetPath || positional[0] || 'puredesktop-app',
|
|
321
91
|
}
|
|
322
92
|
}
|
|
323
93
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
? readStructuredManifestFile(options.manifestJsonFile, options.port)
|
|
327
|
-
: null
|
|
328
|
-
const structuredAgentsMd = options.agentsMdFile
|
|
329
|
-
? readStructuredAgentsMdFile(options.agentsMdFile)
|
|
330
|
-
: null
|
|
331
|
-
const dirName = targetDir
|
|
332
|
-
const structuredSlug = structuredManifest?.app.slug ?? ''
|
|
333
|
-
const structuredTitle = structuredManifest?.app.name ?? ''
|
|
334
|
-
if (structuredManifest && options.slug && options.slug !== structuredSlug) {
|
|
335
|
-
console.error('--slug must match manifest.app.slug when a structured manifest is used')
|
|
336
|
-
process.exit(1)
|
|
337
|
-
}
|
|
338
|
-
if (structuredManifest && options.name && options.name !== structuredTitle) {
|
|
339
|
-
console.error('--name must match manifest.app.name when a structured manifest is used')
|
|
340
|
-
process.exit(1)
|
|
341
|
-
}
|
|
342
|
-
const slug = structuredSlug || options.slug || slugify(dirName) || 'my-app'
|
|
343
|
-
const title = structuredTitle || options.name || titleCaseFromSlug(slug) || 'My App'
|
|
344
|
-
const pluginId = structuredManifest?.id ?? slug
|
|
345
|
-
const outDir = resolve(process.cwd(), dirName)
|
|
346
|
-
|
|
347
|
-
if (existsSync(outDir)) {
|
|
348
|
-
console.error(`Target already exists: ${outDir}`)
|
|
349
|
-
process.exit(1)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
mkdirSync(outDir, { recursive: true })
|
|
353
|
-
|
|
354
|
-
const vars = {
|
|
355
|
-
APP_TITLE: title,
|
|
356
|
-
APP_SLUG: slug,
|
|
357
|
-
APP_NAME: slug,
|
|
358
|
-
PLUGIN_ID: pluginId,
|
|
359
|
-
APP_PORT: String(options.port),
|
|
360
|
-
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
copyTemplate(TEMPLATE_DIR, outDir, vars)
|
|
364
|
-
|
|
365
|
-
if (structuredManifest) {
|
|
366
|
-
writeFileSync(
|
|
367
|
-
join(outDir, 'plugin.json'),
|
|
368
|
-
`${JSON.stringify(structuredManifest, null, 2)}\n`,
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (structuredAgentsMd) {
|
|
373
|
-
writeFileSync(join(outDir, 'agents.md'), structuredAgentsMd)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const generatedManifest = readGeneratedManifest(outDir)
|
|
377
|
-
writeAgentToolScaffold(
|
|
378
|
-
outDir,
|
|
379
|
-
generatedManifest?.app?.slug ?? slug,
|
|
380
|
-
readAgentToolNames(generatedManifest),
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
console.log(`
|
|
94
|
+
function printSuccess({ outDir, port, targetDir, title }) {
|
|
95
|
+
console.log(`
|
|
384
96
|
Created ${title} at ${outDir}
|
|
385
97
|
|
|
386
|
-
cd ${
|
|
98
|
+
cd ${targetDir}
|
|
387
99
|
npm install
|
|
388
100
|
npm run dev
|
|
389
101
|
|
|
@@ -393,9 +105,29 @@ Before shipping:
|
|
|
393
105
|
npm run puredesktop:check
|
|
394
106
|
|
|
395
107
|
Register in PureDesktop (File -> Register App...) with:
|
|
396
|
-
entrypoint: http://localhost:${
|
|
397
|
-
permissions: network, settings, filesystem, agents
|
|
108
|
+
entrypoint: http://localhost:${port}
|
|
109
|
+
permissions: network, settings, filesystem, agents
|
|
110
|
+
|
|
111
|
+
Build product surfaces in src/components, workflow hooks in src/hooks, and domain logic in src/lib.
|
|
112
|
+
Keep src/App.tsx and src/components/AppShell.tsx thin.
|
|
113
|
+
`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const { options, targetDir } = parseArgs(process.argv.slice(2))
|
|
118
|
+
const result = scaffoldAppFromFiles({
|
|
119
|
+
agentsMdFile: options.agentsMdFile,
|
|
120
|
+
allowExisting: options.allowExisting,
|
|
121
|
+
manifestJsonFile: options.manifestJsonFile,
|
|
122
|
+
name: options.name,
|
|
123
|
+
port: options.port,
|
|
124
|
+
slug: options.slug,
|
|
125
|
+
targetDir,
|
|
126
|
+
})
|
|
398
127
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
128
|
+
printSuccess(result)
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
131
|
+
if (error instanceof UsageError) usage()
|
|
132
|
+
process.exit(1)
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puredesktop/create-app",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
4
4
|
"description": "Scaffold a PureDesktop plugin app (Vite + bridge SDK)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
|
+
"lib",
|
|
12
13
|
"template"
|
|
13
14
|
],
|
|
14
15
|
"publishConfig": {
|
|
15
|
-
"access": "
|
|
16
|
+
"access": "public",
|
|
16
17
|
"registry": "https://registry.npmjs.org"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
package/template/README.md
CHANGED
|
@@ -36,9 +36,9 @@ and declared app agent tools.
|
|
|
36
36
|
- `src/App.tsx`: bridge and boot gates wrapped in `AppFrame`.
|
|
37
37
|
- `src/bridge/platformBridge.ts`: local app bridge exports.
|
|
38
38
|
- `src/hooks/useAppBoot.ts`: boot data loading after bridge readiness.
|
|
39
|
-
- `src/components/AppShell.tsx`: thin app-owned composition root.
|
|
40
|
-
- `src/components/StarterWorkspace.tsx`: temporary starter screen to replace
|
|
41
|
-
when the first product surface exists.
|
|
39
|
+
- `src/components/AppShell.tsx`: thin app-owned composition root.
|
|
40
|
+
- `src/components/StarterWorkspace.tsx`: temporary starter screen to replace
|
|
41
|
+
when the first product surface exists.
|
|
42
42
|
- `docs/`: builder-facing app, bridge, agent, and tool-handler docs.
|
|
43
43
|
|
|
44
44
|
## Builder Docs
|
package/template/docs/README.md
CHANGED
|
@@ -13,10 +13,11 @@ Read these in order when building or repairing an app:
|
|
|
13
13
|
2. [Plugin manifest](./plugin-manifest.md)
|
|
14
14
|
3. [Bridge helpers](./bridge/README.md)
|
|
15
15
|
4. [App agent](./agent.md)
|
|
16
|
-
5. [Tool handlers](./tool-handlers.md)
|
|
16
|
+
5. [Tool handlers](./tool-handlers.md)
|
|
17
17
|
6. [Adding an app agent tool](./howtos/adding-agent-tool.md)
|
|
18
|
-
7. [UI and components](./ui-and-components.md)
|
|
19
|
-
8. [
|
|
18
|
+
7. [UI and components](./ui-and-components.md)
|
|
19
|
+
8. [Using the document editor](./howtos/using-document-editor.md)
|
|
20
|
+
9. [Theme CSS variables](./theme-vars.md)
|
|
20
21
|
|
|
21
22
|
Key files in this scaffold:
|
|
22
23
|
|
|
@@ -26,11 +27,11 @@ Key files in this scaffold:
|
|
|
26
27
|
- `src/App.tsx`: bridge readiness, boot loading, and `AppFrame` wrapper.
|
|
27
28
|
- `src/bridge/platformBridge.ts`: local app bridge surface.
|
|
28
29
|
- `src/hooks/useAppBoot.ts`: app boot data loading after the bridge is ready.
|
|
29
|
-
- `src/components/AppShell.tsx`: thin app-owned composition root.
|
|
30
|
-
- `src/components/StarterWorkspace.tsx`: temporary starter screen to replace
|
|
31
|
-
when the first product surface exists.
|
|
32
|
-
- `src/lib/starterWorkspace.ts`: starter data/helper example showing where
|
|
33
|
-
reusable non-rendering logic belongs.
|
|
30
|
+
- `src/components/AppShell.tsx`: thin app-owned composition root.
|
|
31
|
+
- `src/components/StarterWorkspace.tsx`: temporary starter screen to replace
|
|
32
|
+
when the first product surface exists.
|
|
33
|
+
- `src/lib/starterWorkspace.ts`: starter data/helper example showing where
|
|
34
|
+
reusable non-rendering logic belongs.
|
|
34
35
|
- `scripts/validate-puredesktop-app.mjs`: registration and tool scaffold check.
|
|
35
36
|
|
|
36
37
|
Use `npm run puredesktop:check` after `npm run build` before registration.
|