@puredesktop/create-app 2.1.5 → 2.1.7

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.
@@ -1,43 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Scaffold an external PureDesktop plugin app.
3
+ * Scaffold an external PureDesktop plugin app.
4
4
  *
5
- * Published as @puredesktop/create-app -> npm create @puredesktop/app@latest
5
+ * Published as @puredesktop/create-app -> npm create @puredesktop/app@latest
6
6
  */
7
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
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
- function usage() {
15
- console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
16
-
17
- Options:
18
- --name <title> Display name (default: derived from dir)
19
- --slug <slug> App slug for settings/bridge (default: dir name)
20
- --port <number> Dev server port in plugin.json (default: 5300)
21
- --manifest-json-base64 <base64>
22
- Structured plugin.json manifest draft for app-builder use
23
- --agents-md-base64 <base64>
24
- Structured agents.md content for app-builder use
25
-
26
- Examples:
27
- npm create @puredesktop/app@latest my-tool
28
- npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
29
- `)
30
- }
31
-
32
- function parseArgs(argv) {
33
- const positional = []
34
- const options = {
35
- name: '',
36
- slug: '',
37
- port: 5300,
38
- manifestJsonBase64: '',
39
- agentsMdBase64: '',
40
- }
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]
16
+
17
+ Options:
18
+ --name <title> Display name (default: derived from dir)
19
+ --slug <slug> App slug for settings/bridge (default: dir name)
20
+ --port <number> Dev server port in plugin.json (default: 5300)
21
+ --manifest-json-base64 <base64>
22
+ Structured plugin.json manifest draft for app-builder use
23
+ --agents-md-base64 <base64>
24
+ Structured agents.md content for app-builder use
25
+
26
+ Examples:
27
+ npm create @puredesktop/app@latest my-tool
28
+ npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
29
+ `)
30
+ }
31
+
32
+ function parseArgs(argv) {
33
+ const positional = []
34
+ const options = {
35
+ name: '',
36
+ slug: '',
37
+ port: 5300,
38
+ manifestJsonBase64: '',
39
+ agentsMdBase64: '',
40
+ }
41
41
 
42
42
  for (let i = 0; i < argv.length; i += 1) {
43
43
  const arg = argv[i]
@@ -53,18 +53,18 @@ function parseArgs(argv) {
53
53
  options.slug = argv[++i] ?? ''
54
54
  continue
55
55
  }
56
- if (arg === '--port') {
57
- options.port = Number(argv[++i])
58
- continue
59
- }
60
- if (arg === '--manifest-json-base64') {
61
- options.manifestJsonBase64 = argv[++i] ?? ''
62
- continue
63
- }
64
- if (arg === '--agents-md-base64') {
65
- options.agentsMdBase64 = argv[++i] ?? ''
66
- continue
67
- }
56
+ if (arg === '--port') {
57
+ options.port = Number(argv[++i])
58
+ continue
59
+ }
60
+ if (arg === '--manifest-json-base64') {
61
+ options.manifestJsonBase64 = argv[++i] ?? ''
62
+ continue
63
+ }
64
+ if (arg === '--agents-md-base64') {
65
+ options.agentsMdBase64 = argv[++i] ?? ''
66
+ continue
67
+ }
68
68
  if (arg.startsWith('-')) {
69
69
  console.error(`Unknown option: ${arg}`)
70
70
  usage()
@@ -89,223 +89,223 @@ function slugify(value) {
89
89
  .replace(/^-+|-+$/g, '')
90
90
  }
91
91
 
92
- function titleCaseFromSlug(slug) {
92
+ function titleCaseFromSlug(slug) {
93
93
  return slug
94
94
  .split('-')
95
95
  .filter(Boolean)
96
96
  .map(part => part.charAt(0).toUpperCase() + part.slice(1))
97
97
  .join(' ')
98
- }
99
-
100
- function decodeBase64Utf8(value, label) {
101
- if (!isBase64(value)) {
102
- console.error(`${label} must be valid base64`)
103
- process.exit(1)
104
- }
105
- try {
106
- return Buffer.from(value, 'base64').toString('utf8')
107
- } catch (error) {
108
- console.error(`${label} must be valid base64: ${errorMessage(error)}`)
109
- process.exit(1)
110
- }
111
- }
112
-
113
- function readStructuredManifest(value, port) {
114
- const text = decodeBase64Utf8(value, '--manifest-json-base64')
115
- let manifest
116
- try {
117
- manifest = JSON.parse(text)
118
- } catch (error) {
119
- console.error(`--manifest-json-base64 must decode to JSON: ${errorMessage(error)}`)
120
- process.exit(1)
121
- }
122
- return normalizeStructuredManifest(manifest, port)
123
- }
124
-
125
- function normalizeStructuredManifest(manifest, port) {
126
- if (!isPlainObject(manifest)) {
127
- console.error('--manifest-json-base64 must decode to a manifest object')
128
- process.exit(1)
129
- }
130
- if (manifest.schemaVersion !== 1) {
131
- console.error('manifest.schemaVersion must be 1')
132
- process.exit(1)
133
- }
134
- const id = requiredString(manifest.id, 'manifest.id')
135
- const name = requiredString(manifest.name, 'manifest.name')
136
- if (!isPlainObject(manifest.app)) {
137
- console.error('manifest.app is required')
138
- process.exit(1)
139
- }
140
- const appSlug = requiredString(manifest.app.slug, 'manifest.app.slug')
141
- const appName = requiredString(manifest.app.name, 'manifest.app.name')
142
- if (slugify(appSlug) !== appSlug) {
143
- console.error('manifest.app.slug must use lowercase letters, numbers, and hyphens')
144
- process.exit(1)
145
- }
146
-
147
- return stripUndefinedProperties({
148
- ...manifest,
149
- id,
150
- name,
151
- entrypoint: {
152
- kind: 'dev-url',
153
- url: `http://localhost:${port}`,
154
- },
155
- app: stripUndefinedProperties({
156
- ...manifest.app,
157
- slug: appSlug,
158
- name: appName,
159
- entrypoint: undefined,
160
- }),
161
- })
162
- }
163
-
164
- function readStructuredAgentsMd(value) {
165
- const text = decodeBase64Utf8(value, '--agents-md-base64')
166
- if (!text.trim()) {
167
- console.error('--agents-md-base64 must decode to non-empty agents.md content')
168
- process.exit(1)
169
- }
170
- return text.endsWith('\n') ? text : `${text}\n`
171
- }
172
-
173
- function requiredString(value, label) {
174
- const text = typeof value === 'string' ? value.trim() : ''
175
- if (!text) {
176
- console.error(`${label} is required`)
177
- process.exit(1)
178
- }
179
- return text
180
- }
181
-
182
- function isPlainObject(value) {
183
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
184
- }
185
-
186
- function isBase64(value) {
187
- if (typeof value !== 'string' || !value.trim()) return false
188
- const normalized = value.trim()
189
- if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(normalized)) return false
190
- if (normalized.length % 4 !== 0) return false
191
- return Buffer.from(normalized, 'base64').toString('base64') === normalized
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)
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
-
308
- function copyTemplate(srcDir, destDir, vars) {
98
+ }
99
+
100
+ function decodeBase64Utf8(value, label) {
101
+ if (!isBase64(value)) {
102
+ console.error(`${label} must be valid base64`)
103
+ process.exit(1)
104
+ }
105
+ try {
106
+ return Buffer.from(value, 'base64').toString('utf8')
107
+ } catch (error) {
108
+ console.error(`${label} must be valid base64: ${errorMessage(error)}`)
109
+ process.exit(1)
110
+ }
111
+ }
112
+
113
+ function readStructuredManifest(value, port) {
114
+ const text = decodeBase64Utf8(value, '--manifest-json-base64')
115
+ let manifest
116
+ try {
117
+ manifest = JSON.parse(text)
118
+ } catch (error) {
119
+ console.error(`--manifest-json-base64 must decode to JSON: ${errorMessage(error)}`)
120
+ process.exit(1)
121
+ }
122
+ return normalizeStructuredManifest(manifest, port)
123
+ }
124
+
125
+ function normalizeStructuredManifest(manifest, port) {
126
+ if (!isPlainObject(manifest)) {
127
+ console.error('--manifest-json-base64 must decode to a manifest object')
128
+ process.exit(1)
129
+ }
130
+ if (manifest.schemaVersion !== 1) {
131
+ console.error('manifest.schemaVersion must be 1')
132
+ process.exit(1)
133
+ }
134
+ const id = requiredString(manifest.id, 'manifest.id')
135
+ const name = requiredString(manifest.name, 'manifest.name')
136
+ if (!isPlainObject(manifest.app)) {
137
+ console.error('manifest.app is required')
138
+ process.exit(1)
139
+ }
140
+ const appSlug = requiredString(manifest.app.slug, 'manifest.app.slug')
141
+ const appName = requiredString(manifest.app.name, 'manifest.app.name')
142
+ if (slugify(appSlug) !== appSlug) {
143
+ console.error('manifest.app.slug must use lowercase letters, numbers, and hyphens')
144
+ process.exit(1)
145
+ }
146
+
147
+ return stripUndefinedProperties({
148
+ ...manifest,
149
+ id,
150
+ name,
151
+ entrypoint: {
152
+ kind: 'dev-url',
153
+ url: `http://localhost:${port}`,
154
+ },
155
+ app: stripUndefinedProperties({
156
+ ...manifest.app,
157
+ slug: appSlug,
158
+ name: appName,
159
+ entrypoint: undefined,
160
+ }),
161
+ })
162
+ }
163
+
164
+ function readStructuredAgentsMd(value) {
165
+ const text = decodeBase64Utf8(value, '--agents-md-base64')
166
+ if (!text.trim()) {
167
+ console.error('--agents-md-base64 must decode to non-empty agents.md content')
168
+ process.exit(1)
169
+ }
170
+ return text.endsWith('\n') ? text : `${text}\n`
171
+ }
172
+
173
+ function requiredString(value, label) {
174
+ const text = typeof value === 'string' ? value.trim() : ''
175
+ if (!text) {
176
+ console.error(`${label} is required`)
177
+ process.exit(1)
178
+ }
179
+ return text
180
+ }
181
+
182
+ function isPlainObject(value) {
183
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
184
+ }
185
+
186
+ function isBase64(value) {
187
+ if (typeof value !== 'string' || !value.trim()) return false
188
+ const normalized = value.trim()
189
+ if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(normalized)) return false
190
+ if (normalized.length % 4 !== 0) return false
191
+ return Buffer.from(normalized, 'base64').toString('base64') === normalized
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)
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
+
308
+ function copyTemplate(srcDir, destDir, vars) {
309
309
  for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
310
310
  const srcPath = join(srcDir, entry.name)
311
311
  const destName = entry.name.replace(/\.tpl$/u, '')
@@ -319,30 +319,30 @@ function copyTemplate(srcDir, destDir, vars) {
319
319
  const rendered = raw.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? '')
320
320
  writeFileSync(destPath, rendered)
321
321
  }
322
- }
323
-
324
- const { targetDir, options } = parseArgs(process.argv.slice(2))
325
- const structuredManifest = options.manifestJsonBase64
326
- ? readStructuredManifest(options.manifestJsonBase64, options.port)
327
- : null
328
- const structuredAgentsMd = options.agentsMdBase64
329
- ? readStructuredAgentsMd(options.agentsMdBase64)
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 --manifest-json-base64 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 --manifest-json-base64 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)
322
+ }
323
+
324
+ const { targetDir, options } = parseArgs(process.argv.slice(2))
325
+ const structuredManifest = options.manifestJsonBase64
326
+ ? readStructuredManifest(options.manifestJsonBase64, options.port)
327
+ : null
328
+ const structuredAgentsMd = options.agentsMdBase64
329
+ ? readStructuredAgentsMd(options.agentsMdBase64)
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 --manifest-json-base64 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 --manifest-json-base64 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
346
 
347
347
  if (existsSync(outDir)) {
348
348
  console.error(`Target already exists: ${outDir}`)
@@ -354,47 +354,47 @@ mkdirSync(outDir, { recursive: true })
354
354
  const vars = {
355
355
  APP_TITLE: title,
356
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(`
384
- Created ${title} at ${outDir}
385
-
386
- cd ${dirName}
387
- npm install
388
- npm run dev
389
-
390
- Before shipping:
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(`
384
+ Created ${title} at ${outDir}
385
+
386
+ cd ${dirName}
387
+ npm install
388
+ npm run dev
389
+
390
+ Before shipping:
391
391
  npm run typecheck
392
- npm run build
393
- npm run puredesktop:check
392
+ npm run build
393
+ npm run puredesktop:check
394
394
 
395
- Register in PureDesktop (File -> Register App...) with:
395
+ Register in PureDesktop (File -> Register App...) with:
396
396
  entrypoint: http://localhost:${options.port}
397
- permissions: network, settings, agents
397
+ permissions: network, settings, agents
398
398
 
399
399
  Build your UI in src/components/AppShell.tsx.
400
400
  `)