@miketromba/ploof 0.1.2 → 0.1.3
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/README.md +4 -4
- package/SPEC.md +3 -3
- package/dist/ploof.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ ploof login openai --api-key <your-api-key>
|
|
|
58
58
|
ploof image generate \
|
|
59
59
|
--prompt "Studio product photo of a matte black water bottle" \
|
|
60
60
|
--out assets/hero.png \
|
|
61
|
-
--model gpt-image-
|
|
61
|
+
--model gpt-image-2 \
|
|
62
62
|
--size 1024x1024
|
|
63
63
|
|
|
64
64
|
# Edit an image with context
|
|
@@ -114,7 +114,7 @@ ploof image generate \
|
|
|
114
114
|
--profile default \
|
|
115
115
|
--prompt "Editorial portrait, dramatic side light" \
|
|
116
116
|
--out assets/portrait.png \
|
|
117
|
-
--model gpt-image-
|
|
117
|
+
--model gpt-image-2 \
|
|
118
118
|
--size 1024x1024 \
|
|
119
119
|
--quality high \
|
|
120
120
|
--format png
|
|
@@ -161,7 +161,7 @@ tasks:
|
|
|
161
161
|
provider: openai
|
|
162
162
|
prompt: "Studio product photo"
|
|
163
163
|
params:
|
|
164
|
-
model: gpt-image-
|
|
164
|
+
model: gpt-image-2
|
|
165
165
|
size: 1024x1024
|
|
166
166
|
quality: high
|
|
167
167
|
output: assets/base.png
|
|
@@ -265,7 +265,7 @@ PLOOF_OPENAI_API_KEY=sk-... bun test tests/e2e
|
|
|
265
265
|
Optional live-test overrides:
|
|
266
266
|
|
|
267
267
|
```bash
|
|
268
|
-
PLOOF_OPENAI_LIVE_MODEL=gpt-image-
|
|
268
|
+
PLOOF_OPENAI_LIVE_MODEL=gpt-image-2
|
|
269
269
|
PLOOF_OPENAI_LIVE_SIZE=1024x1024
|
|
270
270
|
```
|
|
271
271
|
|
package/SPEC.md
CHANGED
|
@@ -182,7 +182,7 @@ ploof image generate \
|
|
|
182
182
|
--profile default \
|
|
183
183
|
--prompt "Studio product photo" \
|
|
184
184
|
--out assets/hero.png \
|
|
185
|
-
--model gpt-image-
|
|
185
|
+
--model gpt-image-2 \
|
|
186
186
|
--size 1024x1024 \
|
|
187
187
|
--quality high \
|
|
188
188
|
--format png
|
|
@@ -226,7 +226,7 @@ tasks:
|
|
|
226
226
|
provider: openai
|
|
227
227
|
prompt: "Studio product photo"
|
|
228
228
|
params:
|
|
229
|
-
model: gpt-image-
|
|
229
|
+
model: gpt-image-2
|
|
230
230
|
size: 1024x1024
|
|
231
231
|
quality: high
|
|
232
232
|
output: assets/base.png
|
|
@@ -325,7 +325,7 @@ Asset-producing commands should write the asset to disk and print structured met
|
|
|
325
325
|
"provider": "openai",
|
|
326
326
|
"outputs": ["assets/hero.png"],
|
|
327
327
|
"metadata": {
|
|
328
|
-
"model": "gpt-image-
|
|
328
|
+
"model": "gpt-image-2"
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
331
|
```
|
package/dist/ploof.js
CHANGED
|
@@ -259,7 +259,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
259
259
|
`,z)}while(J!==-1);return X+=$.slice(z),X}var{stdout:GV,stderr:NV}=zV,i3=Symbol("GENERATOR"),dU=Symbol("STYLER"),H0=Symbol("IS_EMPTY"),_V=["ansi","ansi","ansi256","ansi16m"],nU=Object.create(null),MT=($,U={})=>{if(U.level&&!(Number.isInteger(U.level)&&U.level>=0&&U.level<=3))throw Error("The `level` option should be an integer from 0 to 3");let w=GV?GV.level:0;$.level=U.level===void 0?w:U.level};var vT=($)=>{let U=(...w)=>w.join(" ");return MT(U,$),Object.setPrototypeOf(U,K0.prototype),U};function K0($){return vT($)}Object.setPrototypeOf(K0.prototype,Function.prototype);for(let[$,U]of Object.entries(t$))nU[$]={get(){let w=$J(this,m3(U.open,U.close,this[dU]),this[H0]);return Object.defineProperty(this,$,{value:w}),w}};nU.visible={get(){let $=$J(this,this[dU],!0);return Object.defineProperty(this,"visible",{value:$}),$}};var h3=($,U,w,...J)=>{if($==="rgb"){if(U==="ansi16m")return t$[w].ansi16m(...J);if(U==="ansi256")return t$[w].ansi256(t$.rgbToAnsi256(...J));return t$[w].ansi(t$.rgbToAnsi(...J))}if($==="hex")return h3("rgb",U,w,...t$.hexToRgb(...J));return t$[w][$](...J)},ST=["rgb","hex","ansi256"];for(let $ of ST){nU[$]={get(){let{level:w}=this;return function(...J){let z=m3(h3($,_V[w],"color",...J),t$.color.close,this[dU]);return $J(this,z,this[H0])}}};let U="bg"+$[0].toUpperCase()+$.slice(1);nU[U]={get(){let{level:w}=this;return function(...J){let z=m3(h3($,_V[w],"bgColor",...J),t$.bgColor.close,this[dU]);return $J(this,z,this[H0])}}}}var bT=Object.defineProperties(()=>{},{...nU,level:{enumerable:!0,get(){return this[i3].level},set($){this[i3].level=$}}}),m3=($,U,w)=>{let J,z;if(w===void 0)J=$,z=U;else J=w.openAll+$,z=U+w.closeAll;return{open:$,close:U,openAll:J,closeAll:z,parent:w}},$J=($,U,w)=>{let J=(...z)=>ET(J,z.length===1?""+z[0]:z.join(" "));return Object.setPrototypeOf(J,bT),J[i3]=$,J[dU]=U,J[H0]=w,J},ET=($,U)=>{if($.level<=0||!U)return $[H0]?"":U;let w=$[dU];if(w===void 0)return U;let{openAll:J,closeAll:z}=w;if(U.includes("\x1B"))while(w!==void 0)U=JV(U,w.close,w.open),w=w.parent;let X=U.indexOf(`
|
|
260
260
|
`);if(X!==-1)U=XV(U,z,J,X);return J+U+z};Object.defineProperties(K0.prototype,nU);var AT=K0(),vr=K0({level:NV?NV.level:0});var UJ=AT;class y$ extends Error{code;constructor($,U=1){super($);this.code=U;this.name="CliError"}}function YV($,U=!1){let w=$ instanceof Error?$.message:String($);if(U)return`Error: ${w}`;return`${UJ.red("Error:")} ${w}`}function QV($){return`${$.join(`
|
|
261
261
|
`).trim()}
|
|
262
|
-
`}var RT=QV(["# Generate assets with the ploof CLI","","Ploof is an AI asset generation CLI. Use it when a task needs generated or edited images, batch asset creation, provider authentication, image context inputs, or reusable generation manifests.","","Package name: `@miketromba/ploof`. Command name: `ploof`. Supported today: OpenAI image generation and image editing.","","## Core workflow","","1. Run `ploof whoami openai`. If unauthenticated, ask the user to run `ploof login openai` or provide `PLOOF_OPENAI_API_KEY` in their shell.","2. For one asset, use `ploof image generate` or `ploof image edit` with an explicit `--out` path.","3. For multiple assets, dependencies, or parallel work, write a YAML manifest and run `ploof run <manifest.yaml>`.","4. Use `--output json` for commands another tool or agent must parse. Use `--output jsonl` for batch streams.","5. After a command completes, verify every path in `outputs` exists before telling the user generation succeeded.","","## Authentication","","```bash","ploof login openai --api-key <key>","ploof whoami openai","ploof profiles openai","```","","There is no `ploof auth` namespace. The authentication commands are top-level: `login`, `whoami`, `profiles`, and `logout`.","","Environment variables override stored credentials:","","```bash","PLOOF_OPENAI_API_KEY=...","OPENAI_API_KEY=...","```","","Use `--profile <name>` on `login`, `whoami`, `image generate`, `image edit`, and manifest tasks when the user has multiple OpenAI credentials. Never print or store secrets in generated project files.","","## Image generation","","```bash","ploof image generate \\"," --provider openai \\",' --prompt "Studio product photo" \\'," --out assets/hero.png \\"," --model gpt-image-
|
|
262
|
+
`}var RT=QV(["# Generate assets with the ploof CLI","","Ploof is an AI asset generation CLI. Use it when a task needs generated or edited images, batch asset creation, provider authentication, image context inputs, or reusable generation manifests.","","Package name: `@miketromba/ploof`. Command name: `ploof`. Supported today: OpenAI image generation and image editing.","","## Core workflow","","1. Run `ploof whoami openai`. If unauthenticated, ask the user to run `ploof login openai` or provide `PLOOF_OPENAI_API_KEY` in their shell.","2. For one asset, use `ploof image generate` or `ploof image edit` with an explicit `--out` path.","3. For multiple assets, dependencies, or parallel work, write a YAML manifest and run `ploof run <manifest.yaml>`.","4. Use `--output json` for commands another tool or agent must parse. Use `--output jsonl` for batch streams.","5. After a command completes, verify every path in `outputs` exists before telling the user generation succeeded.","","## Authentication","","```bash","ploof login openai --api-key <key>","ploof whoami openai","ploof profiles openai","```","","There is no `ploof auth` namespace. The authentication commands are top-level: `login`, `whoami`, `profiles`, and `logout`.","","Environment variables override stored credentials:","","```bash","PLOOF_OPENAI_API_KEY=...","OPENAI_API_KEY=...","```","","Use `--profile <name>` on `login`, `whoami`, `image generate`, `image edit`, and manifest tasks when the user has multiple OpenAI credentials. Never print or store secrets in generated project files.","","## Image generation","","```bash","ploof image generate \\"," --provider openai \\",' --prompt "Studio product photo" \\'," --out assets/hero.png \\"," --model gpt-image-2 \\"," --size 1024x1024 \\"," --quality high \\"," --format png \\"," --output json","```","","Important: `--format png` controls the generated asset format. `--output json` controls CLI output formatting.","","First-class OpenAI image flags:","","- `--model <model>`: for example `gpt-image-2`.","- `--size <size>`: provider size such as `1024x1024`.","- `--quality <quality>`: provider quality such as `low`, `medium`, or `high` when supported.","- `--format <format>` or `--output-format <format>`: provider image format, usually `png`, `jpeg`, or `webp` when supported.","- `--background <value>`, `--moderation <value>`, `--n <count>`, `--output-compression <number>`, `--partial-images <number>`, `--response-format <format>`, `--style <style>`, `--user <id>`, `--stream`.","- `--param key=value`: pass one provider-specific parameter. Nested keys work, such as `--param extra.foo=bar`.","- `--json '{...}'`: merge a provider-specific JSON object into the request. Explicit first-class flags override overlapping JSON keys.","","Use `--quality low` for cheap smoke tests. Use the user's requested quality and size for final assets.","","## Image editing and context images","","```bash","ploof image edit \\"," --provider openai \\"," --image input.png \\"," --image reference.png \\"," --mask mask.png \\",' --prompt "Replace the background" \\'," --out assets/edited.png \\"," --model gpt-image-2 \\"," --format png \\"," --output json","```","","Pass every source image the provider should see with repeated `--image`. Use `--mask` only when the provider/model supports masked edits. Edit also supports `--input-fidelity <value>`.","","Input assets can be local paths, `http://` or `https://` URLs, or `-` for stdin. Ploof infers common image MIME types from file extensions and preserves generated files locally.","","## Outputs and verification","","`--out <path>` can be a file path or directory. When generating multiple images with `--n`, a file path is expanded with `-1`, `-2`, etc. If `--out` is omitted, Ploof writes in the current directory using default names like `image.png` or `edited-image.png`.","","Default sidecar metadata is enabled. For each output file, Ploof writes `<output>.json` with the operation, prompt, params, outputs, provider metadata, and timestamp.","","Parseable result shape:","","```json","{",' "kind": "image.generate",',' "provider": "openai",',' "profile": "default",',' "outputs": ["assets/hero.png"],',' "metadata": { "model": "gpt-image-2" }',"}","```","","Useful global output flags:","","- `--output json|jsonl|compact|table`: choose CLI output format.","- `--fields outputs,metadata.usage.total_tokens`: select fields for parseable outputs.","- `--detail`, `--quiet`, `--no-color`, `--verbose`.","","## Batch manifests","","```yaml","version: 1","parallel: 4","tasks:"," - id: base"," kind: image.generate"," provider: openai"," profile: default",' prompt: "Studio product photo"'," output: assets/base.png"," params:"," model: gpt-image-2"," size: 1024x1024"," quality: high"," output_format: png",""," - id: edit"," kind: image.edit"," provider: openai"," needs: [base]"," inputs:"," images:"," - task: base"," - source: ./reference.png"," mask:"," source: ./mask.png",' prompt: "Add a premium background"'," output: assets/final.png"," params:"," model: gpt-image-2"," size: 1024x1024","```","","Run it with:","","```bash","ploof run assets.yaml --parallel 4 --output json","```","","Manifest notes:","","- `version: 1`, `parallel`, and `tasks` are supported.","- Task kinds are `image.generate` and `image.edit`.","- Task fields: `id`, `kind`, `provider`, `profile`, `needs`, `prompt`, `output`, `params`, `sidecar`, `inputs`.","- Relative paths are resolved from the manifest file location.","- `inputs.images` accepts strings, `{ source }`, or `{ task }`. `{ task: base }` uses the first output from that completed task.","- Run `ploof run assets.yaml --dry-run --output json` before expensive batches.","","## Configuration","","```bash","ploof config list","ploof config set output json","ploof config set defaultParallel 4","ploof config set sidecar true","ploof config reset","```","","Config is stored at `~/.ploof/config.json`. Credentials are stored separately at `~/.ploof/credentials.json`.","","## Agent guidance","","- Prefer exact user-provided prompts, dimensions, source images, and output paths. Ask only when a missing choice would change the intended asset.","- Keep prompts and params explicit in commands or manifests.","- Use deterministic output paths, usually under the user's requested asset directory or a temp directory for tests.","- Use manifests for parallel generation, dependency chains, or more than one related asset.","- Keep `--parallel` modest unless the user requests volume; generation costs money and provider rate limits may apply.","- Report generated file paths and any relevant metadata such as model and token usage.","- Do not claim assets were generated unless the command completed successfully and output files exist.","- If a command fails, report the command goal, the error, and the next concrete fix. Do not fabricate assets.","- Run `ploof <command> --help` when a detail is unclear; prefer the installed CLI help over memory."]);function WV(){return QV(["Usage: ploof learn [options]","","Print AI-agent instructions for using the installed ploof CLI.","","Options:"," --help, -h Show this help","","Examples:"," ploof learn"])}function VV($=[]){if($.includes("--help")||$.includes("-h"))return WV();let U=$.find((w)=>w.trim()!=="");if(U)throw Error(`Unknown learn argument: ${U}
|
|
263
263
|
|
|
264
264
|
${WV()}`);return RT}import{readFile as RE}from"node:fs/promises";import{dirname as kE,extname as fE,resolve as Eq}from"node:path";var lb=hY(),rb=k0(),db=FY(),sY=f0(),nb=P0(),e4=p(),pb=n4(),tb=N$(),ab=t4(),ob=a4(),td=iJ(),eb=aY(),sb=oY(),$E=eY(),lJ=Zq(),Tq=L0();var UE=lb.Composer,wE=rb.Document,zE=db.Schema,JE=sY.YAMLError,XE=sY.YAMLParseError,GE=sY.YAMLWarning,NE=nb.Alias,_E=e4.isAlias,YE=e4.isCollection,WE=e4.isDocument,QE=e4.isMap,VE=e4.isNode,BE=e4.isPair,qE=e4.isScalar,HE=e4.isSeq,KE=pb.Pair,LE=tb.Scalar,IE=ab.YAMLMap,PE=ob.YAMLSeq;var ZE=eb.Lexer,TE=sb.LineCounter,gE=$E.Parser,$W=lJ.parse,DE=lJ.parseAllDocuments,OE=lJ.parseDocument,FE=lJ.stringify,ME=Tq.visit,vE=Tq.visitAsync;class UW{id="openai";capabilities=["image.generate","image.edit"];async runImageGenerate($,U){let w=gq(U),J={...$.params,prompt:$.prompt},z=await Dq(w).generate(J),X=await Oq({response:z,output:$.output,format:Mq($.params),defaultName:$.id??"image"}),G={id:$.id,kind:"image.generate",provider:this.id,profile:U.credential.profile,outputs:X,metadata:Fq(z,$.params)};if($.sidecar??U.sidecar??!0)await iX(G,$,"image.generate");return G}async runImageEdit($,U){let w=gq(U),J=$.inputs.filter((V)=>V.role==="image"),z=$.inputs.find((V)=>V.role==="mask");if(J.length===0)throw Error("At least one --image input is required for image edits.");let X=await Promise.all(J.map(yX)),G=z?await yX(z):void 0,N=X.length===1?X[0]:X,_={...$.params,prompt:$.prompt,image:N,...G?{mask:G}:{}},Y=await Dq(w).edit(_),W=await Oq({response:Y,output:$.output,format:Mq($.params),defaultName:$.id??"edited-image"}),Q={id:$.id,kind:"image.edit",provider:this.id,profile:U.credential.profile,outputs:W,metadata:Fq(Y,$.params)};if($.sidecar??U.sidecar??!0)await iX(Q,$,"image.edit");return Q}}function gq($){let U=$.credential;if(!U.apiKey)throw Error("No OpenAI API key found. Run 'ploof login openai --api-key <key>' or set PLOOF_OPENAI_API_KEY.");return new i({apiKey:U.apiKey,organization:U.organization,project:U.project,baseURL:U.baseURL})}function Dq($){return $.images}async function Oq($){if(EE($.response))return SE($);let U=vq($.response),w=U.length,J=[];for(let z=0;z<U.length;z++){let X=U[z];if(X.b64_json)J.push(await Pz({data:X.b64_json,output:$.output,index:z,total:w,format:$.format,defaultName:$.defaultName}));else if(X.url)J.push(await dQ({url:X.url,output:$.output,index:z,total:w,format:$.format,defaultName:$.defaultName}))}if(J.length===0)throw Error("OpenAI response did not include image data or URLs.");return J}async function SE($){let U=[],w=0;for await(let J of $.response)for(let z of bE(J))U.push(await Pz({data:z,output:$.output,index:w,total:2,format:$.format,defaultName:$.defaultName})),w+=1;return U}function Fq($,U){let w=$&&typeof $==="object"?$:{};return{model:U?.model,created:w.created,usage:w.usage,revisedPrompts:vq($).map((J)=>J.revised_prompt).filter(Boolean)}}function vq($){if(!$||typeof $!=="object")return[];let U=$.data;return Array.isArray(U)?U:[]}function bE($){if(!$||typeof $!=="object")return[];let U=[],w=(J)=>{if(!J||typeof J!=="object")return;for(let[z,X]of Object.entries(J))if(typeof X==="string"&&(z==="b64_json"||z==="partial_image_b64"||z==="image_b64"))U.push(X);else if(typeof X==="object")w(X)};return w($),U}function Mq($){return $?.output_format??$?.format}function EE($){return Boolean($&&typeof $==="object"&&Symbol.asyncIterator in $)}var AE=[new UW];function rJ($){let U=AE.find((w)=>w.id===$);if(!U)throw Error(`Unknown provider: ${$}`);return U}var Sq=y.union([y.string(),y.object({source:y.string().optional(),task:y.string().optional(),mime:y.string().optional(),name:y.string().optional()})]),jE=y.object({id:y.string(),kind:y.enum(["image.generate","image.edit"]),provider:y.string().default("openai"),profile:y.string().optional(),needs:y.array(y.string()).default([]),prompt:y.string(),params:y.record(y.string(),y.unknown()).default({}),output:y.string().optional(),sidecar:y.boolean().optional(),inputs:y.object({images:y.array(Sq).optional(),mask:Sq.optional()}).optional()}),CE=y.object({version:y.union([y.literal(1),y.string()]).default(1),parallel:y.number().int().positive().optional(),tasks:y.array(jE).min(1)});async function uE($){let U=await RE($,"utf-8"),J=fE($).toLowerCase()===".json"?JSON.parse(U):$W(U),z=CE.parse(J);return xE(z),z}async function Aq($,U={}){let w=await uE($),J=U.parallel??w.parallel??4,z=kE(Eq($));if(U.dryRun)return w.tasks.map((Q)=>({id:Q.id,kind:Q.kind,provider:Q.provider,profile:Q.profile,outputs:Q.output?[dJ(z,Q.output)]:[],metadata:{dryRun:!0,needs:Q.needs}}));let X=U.auth??new g4,G=new Map,N=new Map,_=new Map(w.tasks.map((Q)=>[Q.id,Q])),Y=[],W;while(_.size>0||N.size>0){if(W)throw W;for(let[Q,V]of[..._]){if(N.size>=J)break;if(!V.needs.every((K)=>G.has(K)))continue;_.delete(Q);let B=yE(V,{auth:X,baseDir:z,completed:G,verbose:U.verbose,sidecar:U.config?.get("sidecar")??!0}).then((K)=>{G.set(Q,K),Y.push(K)}).catch((K)=>{W=K}).finally(()=>{N.delete(Q)});N.set(Q,B)}if(N.size===0&&_.size>0)throw Error("Manifest has unresolved dependencies.");if(N.size>0)await Promise.race(N.values())}if(W)throw W;return Y}function xE($){let U=new Set;for(let w of $.tasks){if(U.has(w.id))throw Error(`Duplicate task id: ${w.id}`);U.add(w.id)}for(let w of $.tasks)for(let J of w.needs)if(!U.has(J))throw Error(`Task ${w.id} depends on unknown task ${J}.`)}async function yE($,U){let w=rJ($.provider),J=U.auth.getCredential($.provider,$.profile);if(!J?.apiKey)throw Error(`No credentials found for ${$.provider}. Run 'ploof login ${$.provider}'.`);let z={id:$.id,kind:$.kind,provider:$.provider,profile:$.profile,prompt:$.prompt,params:$.params,output:$.output?dJ(U.baseDir,$.output):void 0,sidecar:$.sidecar??U.sidecar};if($.kind==="image.generate")return w.runImageGenerate({...z,kind:"image.generate"},{credential:J,verbose:U.verbose,sidecar:z.sidecar});let X=iE($,U);return w.runImageEdit({...z,kind:"image.edit",inputs:X},{credential:J,verbose:U.verbose,sidecar:z.sidecar})}function iE($,U){let w=[];for(let J of $.inputs?.images??[])w.push({role:"image",source:bq(J,U)});if($.inputs?.mask)w.push({role:"mask",source:bq($.inputs.mask,U)});return w}function bq($,U){if(typeof $==="string")return dJ(U.baseDir,$);if($.task){let J=U.completed.get($.task)?.outputs[0];if(!J)throw Error(`Task output not available: ${$.task}`);return J}if($.source)return dJ(U.baseDir,$.source);throw Error("Input reference must include source or task.")}function dJ($,U){if(U.startsWith("/")||U.startsWith("http://")||U.startsWith("https://")||U==="-")return U;return Eq($,U)}function Rq($,U,w,J){let z=$??w??U;if(z&&z!=="auto"){if(rE(z))return z;throw Error(`Invalid output format: ${z}`)}return J?"table":"compact"}function kq($,U){let w=Array.isArray($)?$:[$],J=U.fields?.length?w.map((z)=>cE(z,U.fields)):w;switch(U.format){case"json":return JSON.stringify(Array.isArray($)?J:J[0],null,2);case"jsonl":return J.map((z)=>JSON.stringify(z)).join(`
|
|
265
265
|
`);case"table":return mE(w,U);case"compact":return w.map(hE).join(`
|