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 +44 -6
- package/bin/bananahub.js +16 -7
- package/lib/commands/add.js +2 -1
- package/lib/commands/info.js +43 -2
- package/lib/commands/init.js +74 -9
- package/lib/commands/list.js +28 -1
- package/lib/commands/models.js +129 -0
- package/lib/commands/search.js +82 -6
- package/lib/constants.js +2 -6
- package/lib/frontmatter.js +63 -17
- package/lib/github.js +2 -1
- package/lib/hub.js +2 -1
- package/lib/paths.js +3 -16
- package/lib/registry.js +8 -2
- package/lib/template-schema.js +276 -0
- package/lib/validate.js +116 -6
- package/lib/version.js +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# bananahub
|
|
2
2
|
|
|
3
|
-
Template manager for [BananaHub Skill](https://github.com/bananahub-ai/
|
|
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-
|
|
31
|
-
bananahub add bananahub-ai/
|
|
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 =
|
|
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')}
|
|
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('
|
|
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-
|
|
35
|
-
bananahub add bananahub-ai/
|
|
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);
|
package/lib/commands/add.js
CHANGED
|
@@ -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,
|
|
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'];
|
package/lib/commands/info.js
CHANGED
|
@@ -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('
|
|
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}
|
|
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
|
}
|
package/lib/commands/init.js
CHANGED
|
@@ -106,7 +106,19 @@ Reusable prompt block or instruction fragment for this workflow
|
|
|
106
106
|
return `## Prompt Template
|
|
107
107
|
|
|
108
108
|
\`\`\`
|
|
109
|
-
|
|
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
|
-
-
|
|
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\`
|
|
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
|
|
146
|
-
|
|
147
|
-
| \`samples/sample-3-pro-01.png\` | \`gemini-3-pro-image-preview\` |
|
|
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
|
-
|
|
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
|
|
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();
|
package/lib/commands/list.js
CHANGED
|
@@ -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
|
+
}
|