bananahub 0.1.0 → 0.1.1
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 +6 -6
- package/bin/bananahub.js +4 -4
- package/lib/commands/add.js +2 -2
- package/lib/commands/info.js +6 -4
- package/lib/commands/init.js +1 -1
- package/lib/commands/remove.js +11 -7
- package/lib/commands/update.js +4 -2
- package/lib/constants.js +8 -3
- package/lib/github.js +1 -1
- package/lib/paths.js +62 -0
- package/lib/registry.js +39 -31
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# bananahub
|
|
2
2
|
|
|
3
|
-
Template manager for [
|
|
3
|
+
Template manager for [BananaHub Skill](https://github.com/bananahub-ai/banana-hub-skill) — the agent-native Gemini image workflow.
|
|
4
4
|
|
|
5
|
-
Install, manage, and share prompt or workflow modules for the
|
|
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
7
|
## Installation
|
|
8
8
|
|
|
@@ -27,8 +27,8 @@ npx bananahub <command>
|
|
|
27
27
|
Install template(s) from a GitHub repository, a specific template directory, or a known template collection.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
bananahub add user/
|
|
31
|
-
bananahub add
|
|
30
|
+
bananahub add user/bananahub-cyberpunk
|
|
31
|
+
bananahub add bananahub-ai/banana-hub-skill/cute-sticker
|
|
32
32
|
bananahub add user/multi-template-repo --template portrait
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -107,7 +107,7 @@ bananahub init --type workflow
|
|
|
107
107
|
|
|
108
108
|
### `validate [path]`
|
|
109
109
|
|
|
110
|
-
Validate a template directory against the
|
|
110
|
+
Validate a template directory against the BananaHub template spec.
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
113
|
bananahub validate ./my-template
|
|
@@ -131,7 +131,7 @@ bananahub registry rebuild
|
|
|
131
131
|
|
|
132
132
|
## Template Format
|
|
133
133
|
|
|
134
|
-
A valid
|
|
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:
|
|
135
135
|
|
|
136
136
|
- a single-template repository with `template.md` at repo root
|
|
137
137
|
- a multi-template repository with `bananahub.json` plus per-template subdirectories
|
package/bin/bananahub.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { bold, dim, cyan, yellow } from '../lib/color.js';
|
|
4
4
|
|
|
5
|
-
const VERSION = '0.1.
|
|
5
|
+
const VERSION = '0.1.1';
|
|
6
6
|
|
|
7
7
|
const HELP = `
|
|
8
|
-
${bold('bananahub')} ${dim(`v${VERSION}`)} — Template manager for
|
|
8
|
+
${bold('bananahub')} ${dim(`v${VERSION}`)} — Template manager for BananaHub Skill
|
|
9
9
|
|
|
10
10
|
${bold('USAGE')}
|
|
11
11
|
bananahub <command> [options]
|
|
@@ -31,8 +31,8 @@ ${bold('OPTIONS')}
|
|
|
31
31
|
--version, -v Show version
|
|
32
32
|
|
|
33
33
|
${bold('EXAMPLES')}
|
|
34
|
-
bananahub add user/
|
|
35
|
-
bananahub add
|
|
34
|
+
bananahub add user/bananahub-cyberpunk
|
|
35
|
+
bananahub add bananahub-ai/banana-hub-skill/cute-sticker
|
|
36
36
|
bananahub add user/multi-template-repo --template portrait
|
|
37
37
|
bananahub search logo --curated
|
|
38
38
|
bananahub trending --period 7d
|
package/lib/commands/add.js
CHANGED
|
@@ -8,7 +8,7 @@ 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 } from '../constants.js';
|
|
11
|
+
import { TEMPLATES_DIR, CLI_VERSION, HUB_API, SKILL_COMMAND } from '../constants.js';
|
|
12
12
|
import { bold, green, red, yellow, cyan, dim } from '../color.js';
|
|
13
13
|
|
|
14
14
|
const KNOWN_TEMPLATE_ROOTS = ['references/templates', 'templates'];
|
|
@@ -107,7 +107,7 @@ export async function addCommand(args) {
|
|
|
107
107
|
if (result.meta.tags?.length) {
|
|
108
108
|
console.log(dim(` Tags: ${result.meta.tags.join(', ')}`));
|
|
109
109
|
}
|
|
110
|
-
console.log(cyan(`\n Use:
|
|
110
|
+
console.log(cyan(`\n Use: ${SKILL_COMMAND} use ${id}\n`));
|
|
111
111
|
|
|
112
112
|
trackInstall(branchInfo.fullName, id, template.relativePath, installTarget).catch(() => {});
|
|
113
113
|
installed += 1;
|
package/lib/commands/info.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { SOURCE_FILE, SKILL_COMMAND } from '../constants.js';
|
|
4
4
|
import { parseFrontmatter } from '../frontmatter.js';
|
|
5
5
|
import { bold, dim, cyan, green } from '../color.js';
|
|
6
6
|
import { red } from '../color.js';
|
|
7
|
+
import { resolveInstalledTemplateDir } from '../paths.js';
|
|
7
8
|
|
|
8
9
|
export async function infoCommand(args) {
|
|
9
10
|
const id = args[0];
|
|
@@ -13,8 +14,9 @@ export async function infoCommand(args) {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
let content;
|
|
17
|
+
const templateDir = await resolveInstalledTemplateDir(id);
|
|
16
18
|
try {
|
|
17
|
-
content = await readFile(join(
|
|
19
|
+
content = await readFile(join(templateDir, 'template.md'), 'utf8');
|
|
18
20
|
} catch {
|
|
19
21
|
console.error(red(`Template "${id}" is not installed.`));
|
|
20
22
|
process.exit(1);
|
|
@@ -28,7 +30,7 @@ export async function infoCommand(args) {
|
|
|
28
30
|
|
|
29
31
|
let source = null;
|
|
30
32
|
try {
|
|
31
|
-
const raw = await readFile(join(
|
|
33
|
+
const raw = await readFile(join(templateDir, SOURCE_FILE), 'utf8');
|
|
32
34
|
source = JSON.parse(raw);
|
|
33
35
|
} catch { /* ok */ }
|
|
34
36
|
|
|
@@ -70,5 +72,5 @@ export async function infoCommand(args) {
|
|
|
70
72
|
if (source.sha) console.log(dim(` SHA: ${source.sha.slice(0, 8)}`));
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
console.log(green(`\n Use:
|
|
75
|
+
console.log(green(`\n Use: ${SKILL_COMMAND} use ${id}\n`));
|
|
74
76
|
}
|
package/lib/commands/init.js
CHANGED
|
@@ -124,7 +124,7 @@ Your prompt here with {{variable|default value}} slots
|
|
|
124
124
|
function buildReadme(titleEn, id, profile, type) {
|
|
125
125
|
return `# ${titleEn}
|
|
126
126
|
|
|
127
|
-
A
|
|
127
|
+
A BananaHub ${type} template for ${profile} workflows.
|
|
128
128
|
|
|
129
129
|
## Install
|
|
130
130
|
|
package/lib/commands/remove.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { rm, access } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { TEMPLATES_DIR } from '../constants.js';
|
|
4
2
|
import { rebuildRegistry } from '../registry.js';
|
|
5
3
|
import { bold, green, red } from '../color.js';
|
|
4
|
+
import { resolveInstalledTemplateDirs } from '../paths.js';
|
|
6
5
|
|
|
7
6
|
export async function removeCommand(args) {
|
|
8
7
|
const id = args[0];
|
|
@@ -11,15 +10,20 @@ export async function removeCommand(args) {
|
|
|
11
10
|
process.exit(1);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
await access(dir);
|
|
17
|
-
} catch {
|
|
13
|
+
const dirs = await resolveInstalledTemplateDirs(id);
|
|
14
|
+
if (dirs.length === 0) {
|
|
18
15
|
console.error(red(`Template "${id}" is not installed.`));
|
|
19
16
|
process.exit(1);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
for (const dir of dirs) {
|
|
20
|
+
try {
|
|
21
|
+
await access(dir);
|
|
22
|
+
await rm(dir, { recursive: true, force: true });
|
|
23
|
+
} catch {
|
|
24
|
+
// ignore races and partial legacy cleanup
|
|
25
|
+
}
|
|
26
|
+
}
|
|
23
27
|
await rebuildRegistry();
|
|
24
28
|
console.log(green(` Removed: ${bold(id)}`));
|
|
25
29
|
}
|
package/lib/commands/update.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { SOURCE_FILE } from '../constants.js';
|
|
4
4
|
import { loadRegistry } from '../registry.js';
|
|
5
5
|
import { addCommand } from './add.js';
|
|
6
6
|
import { bold, dim, yellow, green, red } from '../color.js';
|
|
7
|
+
import { resolveInstalledTemplateDir } from '../paths.js';
|
|
7
8
|
|
|
8
9
|
export async function updateCommand(args) {
|
|
9
10
|
const targetId = args[0];
|
|
@@ -26,8 +27,9 @@ export async function updateCommand(args) {
|
|
|
26
27
|
|
|
27
28
|
for (const template of toUpdate) {
|
|
28
29
|
let source;
|
|
30
|
+
const templateDir = await resolveInstalledTemplateDir(template.id);
|
|
29
31
|
try {
|
|
30
|
-
const raw = await readFile(join(
|
|
32
|
+
const raw = await readFile(join(templateDir, SOURCE_FILE), 'utf8');
|
|
31
33
|
source = JSON.parse(raw);
|
|
32
34
|
} catch {
|
|
33
35
|
console.log(yellow(` Skipping ${bold(template.id)}: no source info (locally created?)`));
|
package/lib/constants.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const CLI_VERSION = '0.1.
|
|
5
|
-
export const
|
|
4
|
+
export const CLI_VERSION = '0.1.1';
|
|
5
|
+
export const CONFIG_HOME = join(homedir(), '.config', 'bananahub');
|
|
6
|
+
export const LEGACY_CONFIG_HOME = join(homedir(), '.config', 'nanobanana');
|
|
7
|
+
export const TEMPLATES_DIR = join(CONFIG_HOME, 'templates');
|
|
8
|
+
export const LEGACY_TEMPLATES_DIR = join(LEGACY_CONFIG_HOME, 'templates');
|
|
6
9
|
export const REGISTRY_FILE = '.registry.json';
|
|
7
10
|
export const SOURCE_FILE = '.source.json';
|
|
8
11
|
export const GITHUB_API = 'https://api.github.com';
|
|
9
12
|
export const HUB_API = 'https://bananahub-api.zhan9kun.workers.dev/api';
|
|
10
|
-
export const HUB_SITE = 'https://
|
|
13
|
+
export const HUB_SITE = 'https://bananahub-ai.github.io';
|
|
11
14
|
export const HUB_CATALOG_URL = `${HUB_SITE}/catalog.json`;
|
|
15
|
+
export const SKILL_COMMAND = '/bananahub';
|
|
16
|
+
export const LEGACY_SKILL_COMMAND = '/nanobanana';
|
|
12
17
|
|
|
13
18
|
export const VALID_PROFILES = [
|
|
14
19
|
'photo', 'illustration', 'diagram', 'text-heavy',
|
package/lib/github.js
CHANGED
|
@@ -70,7 +70,7 @@ export async function getLatestSha(repo, ref = 'HEAD') {
|
|
|
70
70
|
function ghHeaders() {
|
|
71
71
|
const headers = {
|
|
72
72
|
'Accept': 'application/vnd.github.v3+json',
|
|
73
|
-
'User-Agent': 'bananahub-cli/0.1.
|
|
73
|
+
'User-Agent': 'bananahub-cli/0.1.1'
|
|
74
74
|
};
|
|
75
75
|
if (process.env.GITHUB_TOKEN) {
|
|
76
76
|
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { access, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { TEMPLATES_DIR, LEGACY_TEMPLATES_DIR } from './constants.js';
|
|
4
|
+
|
|
5
|
+
async function dirExists(path) {
|
|
6
|
+
try {
|
|
7
|
+
await access(path);
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function ensurePrimaryTemplatesDir() {
|
|
15
|
+
await mkdir(TEMPLATES_DIR, { recursive: true });
|
|
16
|
+
return TEMPLATES_DIR;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getTemplateRoots() {
|
|
20
|
+
const roots = [];
|
|
21
|
+
const primaryExists = await dirExists(TEMPLATES_DIR);
|
|
22
|
+
const legacyExists = await dirExists(LEGACY_TEMPLATES_DIR);
|
|
23
|
+
|
|
24
|
+
if (primaryExists) {
|
|
25
|
+
roots.push(TEMPLATES_DIR);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (legacyExists) {
|
|
29
|
+
roots.push(LEGACY_TEMPLATES_DIR);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (roots.length === 0) {
|
|
33
|
+
await ensurePrimaryTemplatesDir();
|
|
34
|
+
roots.push(TEMPLATES_DIR);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return roots;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function resolveInstalledTemplateDir(id) {
|
|
41
|
+
for (const root of await getTemplateRoots()) {
|
|
42
|
+
const candidate = join(root, id);
|
|
43
|
+
if (await dirExists(candidate)) {
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return join(TEMPLATES_DIR, id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function resolveInstalledTemplateDirs(id) {
|
|
52
|
+
const dirs = [];
|
|
53
|
+
|
|
54
|
+
for (const root of await getTemplateRoots()) {
|
|
55
|
+
const candidate = join(root, id);
|
|
56
|
+
if (await dirExists(candidate)) {
|
|
57
|
+
dirs.push(candidate);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return dirs;
|
|
62
|
+
}
|
package/lib/registry.js
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
-
import { readdir, readFile, writeFile
|
|
1
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { TEMPLATES_DIR, REGISTRY_FILE } from './constants.js';
|
|
4
4
|
import { parseFrontmatter } from './frontmatter.js';
|
|
5
|
+
import { ensurePrimaryTemplatesDir, getTemplateRoots } from './paths.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Rebuild .registry.json by scanning all installed template directories.
|
|
8
9
|
*/
|
|
9
10
|
export async function rebuildRegistry() {
|
|
10
|
-
await
|
|
11
|
-
const
|
|
11
|
+
await ensurePrimaryTemplatesDir();
|
|
12
|
+
const roots = await getTemplateRoots();
|
|
12
13
|
const templates = [];
|
|
14
|
+
const seenIds = new Set();
|
|
13
15
|
|
|
14
|
-
for (const
|
|
15
|
-
|
|
16
|
-
const tmplPath = join(TEMPLATES_DIR, entry.name, 'template.md');
|
|
17
|
-
try {
|
|
18
|
-
const content = await readFile(tmplPath, 'utf8');
|
|
19
|
-
const fm = parseFrontmatter(content);
|
|
20
|
-
if (!fm) continue;
|
|
16
|
+
for (const root of roots) {
|
|
17
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || seenIds.has(entry.name)) continue;
|
|
21
|
+
|
|
22
|
+
const tmplPath = join(root, entry.name, 'template.md');
|
|
23
23
|
try {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const content = await readFile(tmplPath, 'utf8');
|
|
25
|
+
const fm = parseFrontmatter(content);
|
|
26
|
+
if (!fm) continue;
|
|
27
|
+
|
|
28
|
+
let source = null;
|
|
29
|
+
try {
|
|
30
|
+
const srcJson = await readFile(join(root, entry.name, '.source.json'), 'utf8');
|
|
31
|
+
source = JSON.parse(srcJson);
|
|
32
|
+
} catch { /* no source info */ }
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
templates.push({
|
|
35
|
+
id: fm.id || entry.name,
|
|
36
|
+
type: fm.type || 'prompt',
|
|
37
|
+
title: fm.title || '',
|
|
38
|
+
title_en: fm.title_en || '',
|
|
39
|
+
author: fm.author || '',
|
|
40
|
+
profile: fm.profile || 'general',
|
|
41
|
+
tags: fm.tags || [],
|
|
42
|
+
difficulty: fm.difficulty || 'beginner',
|
|
43
|
+
aspect: fm.aspect || '',
|
|
44
|
+
models: Array.isArray(fm.models) ? fm.models.map((m) => m.name || m) : [],
|
|
45
|
+
source: source?.repo || '',
|
|
46
|
+
version: fm.version || '0.0.0',
|
|
47
|
+
installed_at: source?.installed_at || ''
|
|
48
|
+
});
|
|
49
|
+
seenIds.add(entry.name);
|
|
50
|
+
} catch {
|
|
51
|
+
// skip unreadable templates
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bananahub",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Template manager for
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Template manager for BananaHub Skill — installable Gemini workflow modules",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"bananahub": "
|
|
7
|
+
"bananahub": "bin/bananahub.js"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18.0.0"
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/
|
|
29
|
+
"url": "git+https://github.com/bananahub-ai/bananahub.git"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/
|
|
31
|
+
"homepage": "https://github.com/bananahub-ai/bananahub#readme",
|
|
32
32
|
"bugs": {
|
|
33
|
-
"url": "https://github.com/
|
|
33
|
+
"url": "https://github.com/bananahub-ai/bananahub/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"tar": "^7.0.0"
|