11#!/usr/bin/env node
22import process from "node:process" ;
33import { createInterface } from "node:readline/promises" ;
4+ import { spawnSync } from "node:child_process" ;
45
56import pc from "picocolors" ;
67import { Command } from "commander" ;
78
9+ import { buildAgentPrompt } from "./lib/agent-prompt.js" ;
810import {
911 findLatestDmg ,
1012 installDependencies ,
@@ -36,7 +38,7 @@ const program = new Command();
3638program
3739 . name ( "appbun" )
3840 . description ( "Generate an Electrobun desktop wrapper from any web app URL." )
39- . version ( "0.5.1 " ) ;
41+ . version ( "0.5.2 " ) ;
4042
4143program
4244 . command ( "create" )
@@ -164,6 +166,76 @@ program
164166 }
165167 } ) ;
166168
169+ program
170+ . command ( "prompt" )
171+ . argument ( "<url>" , "web app URL to wrap" )
172+ . option ( "-n, --name <name>" , "app display name" )
173+ . option ( "-o, --out-dir <dir>" , "desktop wrapper output directory inside the repo" )
174+ . option ( "--title <title>" , "desktop window title" )
175+ . option ( "--description <description>" , "package description" )
176+ . option ( "--identifier <identifier>" , "bundle identifier, for example com.example.app" )
177+ . option ( "--theme-color <hex>" , "shell accent color, for example #2563eb" )
178+ . option ( "--width <number>" , "window width" , parseInteger , defaultOptions . width )
179+ . option ( "--height <number>" , "window height" , parseInteger , defaultOptions . height )
180+ . option ( "--copy" , "copy the generated prompt to the clipboard when supported" )
181+ . option ( "--quiet" , "reduce metadata logs" )
182+ . action ( async ( url : string , options : CreateCommandOptions & { copy ?: boolean } ) => {
183+ try {
184+ validatePackageManager ( defaultOptions . packageManager ) ;
185+ let usedFallbackMetadata = false ;
186+ const metadata = await fetchSiteMetadata ( url ) . catch ( ( error ) => {
187+ usedFallbackMetadata = true ;
188+ if ( ! options . quiet ) {
189+ const message = error instanceof Error ? error . message : String ( error ) ;
190+ console . log ( pc . bold ( pc . yellow ( "warning" ) ) , `metadata fetch failed, continuing with URL defaults` ) ;
191+ console . log ( ` reason: ${ message } ` ) ;
192+ }
193+ return createFallbackSiteMetadata ( url ) ;
194+ } ) ;
195+
196+ const resolvedOptions = {
197+ ...defaultOptions ,
198+ ...options ,
199+ packageManager : defaultOptions . packageManager ,
200+ install : false ,
201+ dmg : false ,
202+ yes : false ,
203+ showConfig : false ,
204+ } ;
205+ let config = resolveAppConfig ( url , resolvedOptions , metadata ) ;
206+ if ( ! options . outDir ) {
207+ config = {
208+ ...config ,
209+ outDir : `./desktop/${ config . slug } ` ,
210+ } ;
211+ }
212+ const prompt = buildAgentPrompt ( config , metadata ) ;
213+
214+ if ( ! options . quiet ) {
215+ console . log ( pc . bold ( pc . cyan ( "appbun" ) ) , "agent prompt" ) ;
216+ console . log ( ` title: ${ metadata . title ?? "(not found)" } ` ) ;
217+ console . log ( ` description: ${ metadata . description ?? "(not found)" } ` ) ;
218+ console . log ( ` theme color: ${ metadata . themeColor ?? "(not found)" } ` ) ;
219+ console . log ( ` icon candidates: ${ metadata . iconCandidates . length } ` ) ;
220+ console . log ( ` metadata mode: ${ usedFallbackMetadata ? "fallback" : "fetched" } ` ) ;
221+ }
222+
223+ if ( options . copy ) {
224+ copyToClipboard ( prompt ) ;
225+ if ( ! options . quiet ) {
226+ console . log ( pc . bold ( pc . green ( "copied" ) ) , "agent prompt copied to clipboard" ) ;
227+ console . log ( "" ) ;
228+ }
229+ }
230+
231+ console . log ( prompt ) ;
232+ } catch ( error ) {
233+ const message = error instanceof Error ? error . message : String ( error ) ;
234+ console . error ( pc . bold ( pc . red ( "error" ) ) , message ) ;
235+ process . exitCode = 1 ;
236+ }
237+ } ) ;
238+
167239program . addHelpText (
168240 "after" ,
169241 `
@@ -173,6 +245,7 @@ Examples:
173245 $ appbun create https://chat.openai.com --theme-color #10a37f --width 1600 --height 1000
174246 $ appbun https://chat.openai.com --name ChatGPT --dmg
175247 $ appbun https://github.com --name GitHub --out-dir ./github --yes
248+ $ appbun prompt https://myapp.com --name "My App" --copy
176249` ,
177250) ;
178251
@@ -276,3 +349,26 @@ async function askForConfirmation(message: string, defaultYes: boolean): Promise
276349function isInteractiveSession ( ) : boolean {
277350 return Boolean ( process . stdin . isTTY && process . stdout . isTTY ) ;
278351}
352+
353+ function copyToClipboard ( value : string ) : void {
354+ const command =
355+ process . platform === "darwin"
356+ ? { bin : "pbcopy" , args : [ ] }
357+ : process . platform === "win32"
358+ ? { bin : "clip" , args : [ ] }
359+ : { bin : "xclip" , args : [ "-selection" , "clipboard" ] } ;
360+
361+ const result = spawnSync ( command . bin , command . args , {
362+ input : value ,
363+ encoding : "utf8" ,
364+ stdio : [ "pipe" , "ignore" , "pipe" ] ,
365+ shell : process . platform === "win32" ,
366+ } ) ;
367+
368+ if ( result . status !== 0 ) {
369+ const error = result . stderr ?. toString ( ) . trim ( ) ;
370+ throw new Error (
371+ `Clipboard copy failed${ error ? `: ${ error } ` : "" } . Re-run without --copy if clipboard access is unavailable.` ,
372+ ) ;
373+ }
374+ }
0 commit comments