bananahub 0.1.1 → 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 CHANGED
@@ -1,9 +1,17 @@
1
1
  # bananahub
2
2
 
3
- Template manager for [BananaHub Skill](https://github.com/bananahub-ai/banana-hub-skill) — the agent-native Gemini image workflow.
3
+ Template manager for [BananaHub Skill](https://github.com/bananahub-ai/bananahub-skill) — the agent-native multi-provider image workflow.
4
4
 
5
5
  Install, manage, and share prompt or workflow modules for the BananaHub Skill workflow. BananaHub keeps the runtime lean and lets reusable prompt structures and guided SOPs travel as installable units.
6
6
 
7
+ ## Model Strategy
8
+
9
+ BananaHub now treats provider/model choice as a first-class interaction:
10
+
11
+ - Prefer `gpt-image-2` for new high-quality generation, launch visuals, README covers, information graphics, and premium first-pass outputs.
12
+ - Prefer Gemini / Nano Banana (`gemini-3-pro-image-preview` first) for edit-heavy, multi-reference, consistency-heavy, and previously validated template workflows.
13
+ - Templates can declare provider/model support explicitly, so `bananahub` can surface the recommended model instead of treating all image models as interchangeable.
14
+
7
15
  ## Installation
8
16
 
9
17
  ```bash
@@ -27,8 +35,8 @@ npx bananahub <command>
27
35
  Install template(s) from a GitHub repository, a specific template directory, or a known template collection.
28
36
 
29
37
  ```bash
30
- bananahub add user/bananahub-cyberpunk
31
- bananahub add bananahub-ai/banana-hub-skill/cute-sticker
38
+ bananahub add user/bananahub-infographics
39
+ bananahub add bananahub-ai/templates/cute-sticker
32
40
  bananahub add user/multi-template-repo --template portrait
33
41
  ```
34
42
 
@@ -44,12 +52,13 @@ Uninstall an installed template.
44
52
  bananahub remove cyberpunk
45
53
  ```
46
54
 
47
- ### `list`
55
+ ### `list [--by-model]`
48
56
 
49
57
  List all installed templates.
50
58
 
51
59
  ```bash
52
60
  bananahub list
61
+ bananahub list --by-model
53
62
  ```
54
63
 
55
64
  ### `update [template-id]`
@@ -69,6 +78,18 @@ Show details about an installed template (metadata, version, source).
69
78
  bananahub info cyberpunk
70
79
  ```
71
80
 
81
+ `info` now shows provider/model support and suggests an explicit override when a template has a stronger recommended model, for example `gpt-image-2`.
82
+
83
+ ### `models [--remote]`
84
+
85
+ Show BananaHub's model support map from the local registry or the remote hub catalog.
86
+
87
+ ```bash
88
+ bananahub models
89
+ bananahub models --remote
90
+ bananahub models --provider openai
91
+ ```
92
+
72
93
  ### `search <keyword>`
73
94
 
74
95
  Search the hub catalog for prompt or workflow templates.
@@ -76,12 +97,17 @@ Search the hub catalog for prompt or workflow templates.
76
97
  ```bash
77
98
  bananahub search portrait
78
99
  bananahub search logo --curated
100
+ bananahub search logo --model gpt-image-2
101
+ bananahub search diagram --provider openai --capability generation
79
102
  ```
80
103
 
81
104
  Options:
82
105
  - `--limit <n>` — Limit the number of results (default: 8, max: 20)
83
106
  - `--curated` — Search only curated templates
84
107
  - `--discovered` — Search only discovered templates
108
+ - `--provider <id>` — Filter by provider, for example `openai` or `google-ai-studio`
109
+ - `--model <id>` — Filter by canonical model id or alias, for example `gpt-image-2` or `nano-banana-pro`
110
+ - `--capability <name>` — Filter by capability such as `generation`, `edit`, or `mask_edit`
85
111
 
86
112
  ### `trending`
87
113
 
@@ -131,12 +157,24 @@ bananahub registry rebuild
131
157
 
132
158
  ## Template Format
133
159
 
134
- A valid BananaHub template directory must contain a `template.md` file with YAML frontmatter at its root. Templates may be `type: prompt` or `type: workflow`, and may live as:
160
+ A valid BananaHub template directory must contain a `template.md` file with YAML frontmatter at its root. Published templates should declare a frontmatter `license` such as `CC-BY-4.0` and include a repo `LICENSE` file. Templates may be `type: prompt` or `type: workflow`, and may live as:
135
161
 
136
162
  - a single-template repository with `template.md` at repo root
137
163
  - a multi-template repository with `bananahub.json` plus per-template subdirectories
138
164
  - a known collection layout such as `references/templates/<template-id>/template.md`
139
165
 
166
+ ## Contributing
167
+
168
+ Code, docs, and tests contributed to this repo are accepted under the repo's MIT license.
169
+
170
+ Sign off each commit with the Developer Certificate of Origin:
171
+
172
+ ```bash
173
+ git commit -s -m "feat: your change"
174
+ ```
175
+
176
+ Template repos created with `bananahub init` default to `CC-BY-4.0` for template content. If you publish community templates, keep the template frontmatter `license` field and the repo `LICENSE` file in sync.
177
+
140
178
  ## License
141
179
 
142
- MIT
180
+ Code in this repo is licensed under MIT.
package/bin/bananahub.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { bold, dim, cyan, yellow } from '../lib/color.js';
4
+ import { CLI_VERSION } from '../lib/version.js';
4
5
 
5
- const VERSION = '0.1.1';
6
+ const VERSION = CLI_VERSION;
6
7
 
7
8
  const HELP = `
8
9
  ${bold('bananahub')} ${dim(`v${VERSION}`)} — Template manager for BananaHub Skill
@@ -15,10 +16,11 @@ ${bold('COMMANDS')}
15
16
  --template <name> Pick one template from a multi-template directory
16
17
  --all Install all templates from a collection
17
18
  ${cyan('remove')} <template-id> Uninstall a template
18
- ${cyan('list')} List installed templates
19
+ ${cyan('list')} [--by-model] List installed templates
19
20
  ${cyan('update')} [template-id] Update one or all installed templates
20
21
  ${cyan('info')} <template-id> Show template details
21
- ${cyan('search')} <keyword> [--limit N] [--curated|--discovered]
22
+ ${cyan('models')} [--remote] [--provider P] Show provider/model support map
23
+ ${cyan('search')} <keyword> [--limit N] [--provider P] [--model M]
22
24
  Search hub for templates
23
25
  ${cyan('trending')} [--period 24h|7d] [--limit N]
24
26
  Show trending templates
@@ -31,12 +33,14 @@ ${bold('OPTIONS')}
31
33
  --version, -v Show version
32
34
 
33
35
  ${bold('EXAMPLES')}
34
- bananahub add user/bananahub-cyberpunk
35
- bananahub add bananahub-ai/banana-hub-skill/cute-sticker
36
+ bananahub add user/bananahub-infographics
37
+ bananahub add bananahub-ai/templates/cute-sticker
36
38
  bananahub add user/multi-template-repo --template portrait
37
39
  bananahub search logo --curated
40
+ bananahub search logo --model gpt-image-2
41
+ bananahub models --remote
38
42
  bananahub trending --period 7d
39
- bananahub list
43
+ bananahub list --by-model
40
44
  bananahub validate ./my-template
41
45
  bananahub init
42
46
  bananahub init --type workflow
@@ -70,7 +74,7 @@ async function main() {
70
74
  }
71
75
  case 'list': {
72
76
  const { listCommand } = await import('../lib/commands/list.js');
73
- await listCommand();
77
+ await listCommand(cmdArgs);
74
78
  break;
75
79
  }
76
80
  case 'update': {
@@ -83,6 +87,11 @@ async function main() {
83
87
  await infoCommand(cmdArgs);
84
88
  break;
85
89
  }
90
+ case 'models': {
91
+ const { modelsCommand } = await import('../lib/commands/models.js');
92
+ await modelsCommand(cmdArgs);
93
+ break;
94
+ }
86
95
  case 'search': {
87
96
  const { searchCommand } = await import('../lib/commands/search.js');
88
97
  await searchCommand(cmdArgs);
@@ -8,7 +8,8 @@ import { extract } from 'tar';
8
8
  import { downloadTarball, getDefaultBranchInfo, getLatestSha } from '../github.js';
9
9
  import { validateTemplate } from '../validate.js';
10
10
  import { rebuildRegistry } from '../registry.js';
11
- import { TEMPLATES_DIR, CLI_VERSION, HUB_API, SKILL_COMMAND } from '../constants.js';
11
+ import { TEMPLATES_DIR, HUB_API, SKILL_COMMAND } from '../constants.js';
12
+ import { CLI_VERSION } from '../version.js';
12
13
  import { bold, green, red, yellow, cyan, dim } from '../color.js';
13
14
 
14
15
  const KNOWN_TEMPLATE_ROOTS = ['references/templates', 'templates'];
@@ -5,6 +5,7 @@ import { parseFrontmatter } from '../frontmatter.js';
5
5
  import { bold, dim, cyan, green } from '../color.js';
6
6
  import { red } from '../color.js';
7
7
  import { resolveInstalledTemplateDir } from '../paths.js';
8
+ import { MODEL_DISPLAY, normalizeProviders } from '../template-schema.js';
8
9
 
9
10
  export async function infoCommand(args) {
10
11
  const id = args[0];
@@ -43,6 +44,7 @@ export async function infoCommand(args) {
43
44
  ['Type', fm.type || 'prompt'],
44
45
  ['Version', fm.version || '-'],
45
46
  ['Author', fm.author || '-'],
47
+ ['License', fm.license || '-'],
46
48
  ['Profile', fm.profile || '-'],
47
49
  ['Aspect', fm.aspect || '-'],
48
50
  ['Difficulty', fm.difficulty || '-'],
@@ -57,7 +59,7 @@ export async function infoCommand(args) {
57
59
  }
58
60
 
59
61
  if (fm.models?.length) {
60
- console.log(` ${cyan('Models'.padEnd(12))}`);
62
+ console.log(` ${cyan('Legacy'.padEnd(12))}`);
61
63
  for (const m of fm.models) {
62
64
  const name = m.name || m;
63
65
  const quality = m.quality ? ` (${m.quality})` : '';
@@ -65,6 +67,28 @@ export async function infoCommand(args) {
65
67
  }
66
68
  }
67
69
 
70
+ const providers = normalizeProviders(fm);
71
+ if (providers.length) {
72
+ console.log(` ${cyan('Providers'.padEnd(12))}`);
73
+ for (const provider of providers) {
74
+ const family = provider.family ? dim(` [${provider.family}]`) : '';
75
+ console.log(` - ${provider.id}${family}`);
76
+ for (const model of provider.models) {
77
+ const quality = model.quality ? dim(` (${model.quality})`) : '';
78
+ const variant = model.prompt_variant ? dim(` via ${model.prompt_variant}`) : '';
79
+ const recommendation = MODEL_DISPLAY[model.id]?.tier === 'recommended' ? dim(' recommended') : '';
80
+ console.log(` - ${model.id}${quality}${variant}${recommendation}`);
81
+ }
82
+ }
83
+ }
84
+
85
+ if (fm.capabilities && typeof fm.capabilities === 'object') {
86
+ const capabilities = Object.entries(fm.capabilities)
87
+ .map(([key, value]) => `${key}=${value}`)
88
+ .join(', ');
89
+ console.log(` ${cyan('Capabilities'.padEnd(12))} ${capabilities}`);
90
+ }
91
+
68
92
  if (source) {
69
93
  console.log();
70
94
  console.log(dim(` Source: ${source.repo}`));
@@ -72,5 +96,22 @@ export async function infoCommand(args) {
72
96
  if (source.sha) console.log(dim(` SHA: ${source.sha.slice(0, 8)}`));
73
97
  }
74
98
 
75
- console.log(green(`\n Use: ${SKILL_COMMAND} use ${id}\n`));
99
+ console.log(green(`\n Use: ${SKILL_COMMAND} use ${id}`));
100
+ const recommended = findRecommendedProviderModel(providers);
101
+ if (recommended) {
102
+ console.log(dim(` Provider override: ${SKILL_COMMAND} use ${id} --provider ${recommended.provider} --model ${recommended.model}`));
103
+ }
104
+ console.log();
105
+ }
106
+
107
+ function findRecommendedProviderModel(providers) {
108
+ for (const provider of providers) {
109
+ const model = provider.models.find((entry) => entry.id === 'gpt-image-2');
110
+ if (model) return { provider: provider.id, model: model.id };
111
+ }
112
+ for (const provider of providers) {
113
+ const model = provider.models.find((entry) => entry.quality === 'best') || provider.models[0];
114
+ if (model) return { provider: provider.id, model: model.id };
115
+ }
116
+ return null;
76
117
  }
@@ -106,7 +106,19 @@ Reusable prompt block or instruction fragment for this workflow
106
106
  return `## Prompt Template
107
107
 
108
108
  \`\`\`
109
- Your prompt here with {{variable|default value}} slots
109
+ Provider-neutral base prompt with {{variable|default value}} slots. Keep this conservative and avoid model-specific hacks.
110
+ \`\`\`
111
+
112
+ ## Prompt Template: gemini
113
+
114
+ \`\`\`
115
+ Gemini/Nano Banana tuned prompt with {{variable|default value}} slots. Use descriptive scene language, preserve exact quoted labels when needed, and avoid generic quality tags.
116
+ \`\`\`
117
+
118
+ ## Prompt Template: gpt-image
119
+
120
+ \`\`\`
121
+ GPT Image tuned prompt with {{variable|default value}} slots. State exact constraints, what to avoid, and any text/label limits explicitly.
110
122
  \`\`\`
111
123
 
112
124
  ## Variables
@@ -117,7 +129,8 @@ Your prompt here with {{variable|default value}} slots
117
129
 
118
130
  ## Tips
119
131
 
120
- - Add tips for using this template
132
+ - Keep provider-specific tactics inside the matching prompt variant
133
+ - Do not reuse a Gemini-tuned prompt for GPT Image unless it has been tested and documented
121
134
  `;
122
135
  }
123
136
 
@@ -134,19 +147,40 @@ npx bananahub add your-username/${id}
134
147
 
135
148
  ## Verified Models
136
149
 
137
- - \`gemini-3-pro-image-preview\` validate the primary flow with a real sample before publishing
150
+ - \`gemini-3-pro-image-preview\` via \`gemini\` prompt variant validate with a real sample before publishing
138
151
 
139
152
  ## Supported Models
140
153
 
141
- - \`gemini-3.1-flash-image-preview\` — expected to work, not yet manually verified
154
+ - \`gemini-3.1-flash-image-preview\` via \`gemini\` prompt variant — expected to work, not yet manually verified
155
+ - \`gpt-image-2\` via \`gpt-image\` prompt variant — only list as verified after generating a real sample
142
156
 
143
157
  ## Sample Outputs
144
158
 
145
- | File | Model | Prompt / Variant |
146
- |---|---|---|
147
- | \`samples/sample-3-pro-01.png\` | \`gemini-3-pro-image-preview\` | Replace with your real sample or representative workflow output |
159
+ | File | Provider | Model | Prompt Variant |
160
+ |---|---|---|---|
161
+ | \`samples/sample-3-pro-01.png\` | \`google-ai-studio\` | \`gemini-3-pro-image-preview\` | \`gemini\` |
162
+
163
+ Update this README after you generate real samples. Each sample should name the provider, exact model, and prompt variant used.
164
+
165
+ ## License
166
+
167
+ This template content is published under CC BY 4.0. Keep the frontmatter \`license\` value and the repo \`LICENSE\` file aligned. Only publish samples and copy that you can redistribute.
168
+ `;
169
+ }
170
+
171
+ function buildTemplateLicenseFile() {
172
+ return `Creative Commons Attribution 4.0 International
173
+
174
+ SPDX-License-Identifier: CC-BY-4.0
175
+
176
+ This template content is licensed under the Creative Commons Attribution 4.0 International License.
177
+
178
+ You may share and adapt this material for any purpose, including commercial use,
179
+ as long as you give appropriate credit, provide a link to the license, and indicate
180
+ if changes were made.
148
181
 
149
- Update this README after you generate real samples. Each sample filename should include the generating model shorthand, for example \`sample-3-pro-01.png\` or \`sample-3.1-flash-01.png\`.
182
+ License text:
183
+ https://creativecommons.org/licenses/by/4.0/
150
184
  `;
151
185
  }
152
186
 
@@ -166,7 +200,9 @@ export async function initCommand(args) {
166
200
 
167
201
  const sampleFrontmatter = type === 'prompt' ? `samples:
168
202
  - file: samples/sample-3-pro-01.png
203
+ provider: google-ai-studio
169
204
  model: gemini-3-pro-image-preview
205
+ prompt_variant: gemini
170
206
  prompt: "The exact prompt used to generate this sample"
171
207
  aspect: "16:9"` : `samples: []`;
172
208
 
@@ -176,6 +212,7 @@ id: ${id}
176
212
  title: ${title}
177
213
  title_en: ${titleEn}
178
214
  author: your-github-username
215
+ license: CC-BY-4.0
179
216
  version: 1.0.0
180
217
  profile: ${profile}
181
218
  tags: []
@@ -186,6 +223,32 @@ models:
186
223
  - name: gemini-3.1-flash-image-preview
187
224
  tested: false
188
225
  quality: good
226
+ providers:
227
+ - id: google-ai-studio
228
+ family: gemini-image
229
+ models:
230
+ - id: gemini-3-pro-image-preview
231
+ aliases: [nano-banana-pro]
232
+ quality: best
233
+ prompt_variant: gemini
234
+ - id: gemini-3.1-flash-image-preview
235
+ aliases: [nano-banana-2]
236
+ quality: good
237
+ prompt_variant: gemini
238
+ - id: openai
239
+ family: gpt-image
240
+ models:
241
+ - id: gpt-image-2
242
+ quality: untested
243
+ prompt_variant: gpt-image
244
+ capabilities:
245
+ generation: true
246
+ edit: false
247
+ mask_edit: false
248
+ prompt_variants:
249
+ default: base
250
+ gemini: prompt-gemini
251
+ gpt-image: prompt-gpt-image
189
252
  aspect: "16:9"
190
253
  difficulty: ${difficulty}
191
254
  ${sampleFrontmatter}
@@ -199,15 +262,17 @@ ${buildTemplateBody(type)}
199
262
  await writeFile(join(outDir, 'template.md'), templateMd);
200
263
  await writeFile(join(outDir, 'samples', '.gitkeep'), '');
201
264
  await writeFile(join(outDir, 'README.md'), buildReadme(titleEn, id, profile, type));
265
+ await writeFile(join(outDir, 'LICENSE'), buildTemplateLicenseFile());
202
266
 
203
267
  console.log(green(`\n Created: ${bold(id)}/`));
204
268
  console.log(dim(` ${id}/template.md`));
205
269
  console.log(dim(` ${id}/samples/.gitkeep`));
206
270
  console.log(dim(` ${id}/README.md`));
271
+ console.log(dim(` ${id}/LICENSE`));
207
272
  console.log(cyan('\n Next steps:'));
208
273
  console.log(dim(` 1. Edit template.md — add your ${type === 'workflow' ? 'workflow sections and prompt blocks' : 'prompt and variables'}`));
209
274
  console.log(dim(' 2. Add sample images to samples/ and include the model in each filename'));
210
- console.log(dim(' 3. Update README.md with verified/supported models and sample mappings'));
275
+ console.log(dim(' 3. Update README.md with verified/supported models, sample mappings, and any license notes'));
211
276
  console.log(dim(' 4. Create a GitHub repo and push'));
212
277
  console.log(dim(' 5. Others install: npx bananahub add <user>/' + id));
213
278
  console.log();
@@ -1,7 +1,8 @@
1
1
  import { loadRegistry } from '../registry.js';
2
2
  import { bold, dim, cyan, yellow } from '../color.js';
3
+ import { collectModelSupport } from '../template-schema.js';
3
4
 
4
- export async function listCommand() {
5
+ export async function listCommand(args = []) {
5
6
  const registry = await loadRegistry();
6
7
  const templates = registry.templates || [];
7
8
 
@@ -11,6 +12,11 @@ export async function listCommand() {
11
12
  return;
12
13
  }
13
14
 
15
+ if (args.includes('--by-model') || args.includes('--by-provider')) {
16
+ printByModel(templates);
17
+ return;
18
+ }
19
+
14
20
  // Group by profile
15
21
  const groups = {};
16
22
  for (const t of templates) {
@@ -32,6 +38,27 @@ export async function listCommand() {
32
38
  if (t.tags?.length) {
33
39
  console.log(dim(` Tags: ${t.tags.join(', ')}`));
34
40
  }
41
+ if (t.providers?.length || t.models?.length) {
42
+ const providers = t.providers?.length ? t.providers.join(', ') : 'providers n/a';
43
+ const models = t.models?.length ? t.models.slice(0, 4).join(', ') : 'models n/a';
44
+ console.log(dim(` Support: ${providers} | ${models}`));
45
+ }
46
+ }
47
+ console.log();
48
+ }
49
+ }
50
+
51
+ function printByModel(templates) {
52
+ const models = collectModelSupport(templates);
53
+ console.log(bold(`\n Installed Templates by Model (${templates.length})\n`));
54
+
55
+ for (const model of models.sort((left, right) => right.template_count - left.template_count || left.id.localeCompare(right.id))) {
56
+ console.log(cyan(` ${model.id}`) + dim(` ${model.provider || 'provider n/a'}${model.family ? `/${model.family}` : ''}`));
57
+ for (const templateId of model.templates.slice(0, 12)) {
58
+ console.log(` - ${templateId}`);
59
+ }
60
+ if (model.templates.length > 12) {
61
+ console.log(dim(` ... ${model.templates.length - 12} more`));
35
62
  }
36
63
  console.log();
37
64
  }
@@ -0,0 +1,129 @@
1
+ import { bold, cyan, dim, green, red, yellow } from '../color.js';
2
+ import { fetchHubCatalog } from '../hub.js';
3
+ import { loadRegistry } from '../registry.js';
4
+ import {
5
+ MODEL_DISPLAY,
6
+ collectModelSupport,
7
+ templateSupportsProviderModel
8
+ } from '../template-schema.js';
9
+
10
+ export async function modelsCommand(args = []) {
11
+ const options = parseModelsArgs(args);
12
+ let templates;
13
+ let sourceLabel;
14
+
15
+ try {
16
+ if (options.remote) {
17
+ const catalog = await fetchHubCatalog();
18
+ templates = catalog.templates || [];
19
+ sourceLabel = 'hub catalog';
20
+ } else {
21
+ const registry = await loadRegistry();
22
+ templates = registry.templates || [];
23
+ sourceLabel = 'local registry';
24
+ }
25
+ } catch (error) {
26
+ console.error(red(`Error: ${error.message}`));
27
+ process.exit(1);
28
+ }
29
+
30
+ const filteredTemplates = templates.filter((template) => templateSupportsProviderModel(template, options));
31
+ const models = collectModelSupport(filteredTemplates)
32
+ .filter((model) => !options.provider || model.provider === options.provider)
33
+ .sort(compareModels);
34
+
35
+ console.log(bold(`\n BananaHub Model Map (${sourceLabel})\n`));
36
+ console.log(dim(' Use this to choose a provider/model before installing or activating templates.'));
37
+ console.log(dim(' Recommendation: use gpt-image-2 for new high-quality generation when OpenAI is available; use Gemini/Nano Banana for proven template/edit workflows.\n'));
38
+
39
+ if (models.length === 0) {
40
+ console.log(yellow(' No models matched the current filters.\n'));
41
+ return;
42
+ }
43
+
44
+ for (const model of models) {
45
+ printModel(model, options);
46
+ }
47
+
48
+ console.log(green(' Examples:'));
49
+ console.log(dim(' bananahub search logo --model gpt-image-2'));
50
+ console.log(dim(' bananahub search diagram --provider openai --capability generation'));
51
+ console.log(dim(' bananahub list --by-model\n'));
52
+ }
53
+
54
+ function parseModelsArgs(args) {
55
+ const options = {
56
+ remote: false,
57
+ provider: '',
58
+ model: '',
59
+ capability: ''
60
+ };
61
+
62
+ for (let index = 0; index < args.length; index++) {
63
+ const arg = args[index];
64
+ if (arg === '--remote' || arg === '--hub') {
65
+ options.remote = true;
66
+ continue;
67
+ }
68
+ if (arg === '--provider') {
69
+ options.provider = args[index + 1] || '';
70
+ index++;
71
+ continue;
72
+ }
73
+ if (arg === '--model') {
74
+ options.model = args[index + 1] || '';
75
+ index++;
76
+ continue;
77
+ }
78
+ if (arg === '--capability') {
79
+ options.capability = args[index + 1] || '';
80
+ index++;
81
+ continue;
82
+ }
83
+ }
84
+
85
+ return options;
86
+ }
87
+
88
+ function compareModels(left, right) {
89
+ const rankDiff = modelRank(left.id) - modelRank(right.id);
90
+ if (rankDiff !== 0) return rankDiff;
91
+ const countDiff = right.template_count - left.template_count;
92
+ if (countDiff !== 0) return countDiff;
93
+ return left.id.localeCompare(right.id);
94
+ }
95
+
96
+ function modelRank(modelId) {
97
+ if (modelId === 'gpt-image-2') return 0;
98
+ if (modelId === 'gemini-3-pro-image-preview') return 1;
99
+ if (modelId === 'gemini-3.1-flash-image-preview') return 2;
100
+ if (modelId.startsWith('gpt-image')) return 3;
101
+ if (modelId.startsWith('gemini')) return 4;
102
+ return 9;
103
+ }
104
+
105
+ function printModel(model, options) {
106
+ const display = MODEL_DISPLAY[model.id] || {};
107
+ const label = display.label || model.id;
108
+ const tier = display.tier ? dim(` ${display.tier}`) : '';
109
+ const provider = model.provider ? dim(` ${model.provider}`) : '';
110
+ const family = model.family ? dim(`/${model.family}`) : '';
111
+ const templates = model.templates.slice(0, 5).join(', ');
112
+
113
+ console.log(` ${bold(model.id)}${tier}`);
114
+ console.log(` ${cyan(label)}${provider}${family}`);
115
+ if (display.note) {
116
+ console.log(dim(` ${display.note}`));
117
+ }
118
+ if (model.aliases.length) {
119
+ console.log(dim(` Aliases: ${model.aliases.join(', ')}`));
120
+ }
121
+ if (model.capabilities.length) {
122
+ console.log(dim(` Capabilities: ${model.capabilities.join(', ')}`));
123
+ }
124
+ console.log(dim(` Templates: ${model.template_count}${templates ? ` (${templates}${model.templates.length > 5 ? ', ...' : ''})` : ''}`));
125
+ if (options.model && model.id !== options.model) {
126
+ console.log(dim(` Matched by alias/filter: ${options.model}`));
127
+ }
128
+ console.log();
129
+ }