genlab 0.1.3 → 0.1.4

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,6 +1,6 @@
1
- # genlab
1
+ # GenLab
2
2
 
3
- 给本地 agent(Claude Code、Codex 等)用的 AI 媒体生成命令行工具。通过 [kie.ai](https://kie.ai) 生成图片、视频、音频(音乐、音效、语音),产物与完整生成记录保存在本机的库里。
3
+ 给本地 agent(Claude Code、Codex 等)用的 AI 媒体生成命令行工具。通过 [Kie.ai](https://kie.ai) 生成图片、视频、音频(音乐、音效、语音),产物与完整生成记录保存在本机的库里。
4
4
 
5
5
  ## 安装与配置
6
6
 
@@ -17,7 +17,7 @@ genlab init --key <KIE_API_KEY> --library ~/GenLab # 或直接给出
17
17
 
18
18
  agent 使用前先运行 `genlab guide`:它输出完整用法(概念、流程、参数写法、命令参考),内容总与当前版本一致。
19
19
 
20
- 可选安装 skill,让 agent 需要生成媒体时能发现 genlab。`genlab init` 时会询问,也可单独管理:
20
+ 可选安装 GenLab skill,`genlab init` 时会询问,也可单独管理:
21
21
 
22
22
  ```bash
23
23
  genlab skill # 安装
@@ -67,7 +67,7 @@ genlab create intro/shot -m wan/3-0-video -p prompt="骑士转身拔剑" -p firs
67
67
 
68
68
  ## 模型
69
69
 
70
- 模型清单与参数取自 kie 的模型目录,本地缓存一天,`genlab models --refresh` 强制刷新。支持 kie 上的图片、视频、音频生成模型,不支持对话模型。
70
+ 模型清单与参数取自 Kie.ai 的模型目录,本地缓存一天,`genlab models --refresh` 强制刷新。支持 Kie.ai 上的图片、视频、音频生成模型,不支持对话模型。
71
71
 
72
72
  ## 开发
73
73
 
package/dist/cli.mjs CHANGED
@@ -19,7 +19,7 @@ import { parseEnv } from "node:util";
19
19
  import * as p from "@clack/prompts";
20
20
  import spawn$1 from "nano-spawn";
21
21
  // package.json
22
- var version = "0.1.3";
22
+ var version = "0.1.4";
23
23
  // src/errors.ts
24
24
  /** 面向使用者的预期错误:只输出 message,不带堆栈。 */
25
25
  var CliError = class extends Error {
@@ -68,7 +68,7 @@ async function createKieTask(task) {
68
68
  method: "POST",
69
69
  body: JSON.stringify(task)
70
70
  });
71
- if (!body.data?.taskId) throw new CliError("kie.ai 未返回 taskId");
71
+ if (!body.data?.taskId) throw new CliError("Kie.ai 未返回 taskId");
72
72
  return body.data.taskId;
73
73
  }
74
74
  async function listKieModels() {
@@ -85,7 +85,7 @@ async function getKieCredits(apiKey) {
85
85
  /** 任务的原始记录(完整保存进素材记录,解析见 parseKieRecord)。 */
86
86
  async function getKieRecord(taskId) {
87
87
  const body = await kieFetch(`${API_BASE_URL}/api/v1/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`);
88
- if (!body.data) throw new CliError("kie.ai 未返回任务记录");
88
+ if (!body.data) throw new CliError("Kie.ai 未返回任务记录");
89
89
  return body.data;
90
90
  }
91
91
  /**
@@ -100,7 +100,7 @@ async function uploadKieFile(filePath) {
100
100
  method: "POST",
101
101
  body: form
102
102
  }, TRANSFER_TIMEOUT_MS);
103
- if (!body.data?.downloadUrl) throw new CliError("kie.ai 上传未返回地址");
103
+ if (!body.data?.downloadUrl) throw new CliError("Kie.ai 上传未返回地址");
104
104
  return body.data.downloadUrl;
105
105
  }
106
106
  /** 下载被拒绝(4xx,超时与限流除外):链接已失效或无权访问,再试也拿不到。 */
@@ -145,9 +145,9 @@ async function kieFetch(url, init, timeoutMs = REQUEST_TIMEOUT_MS, apiKey = requ
145
145
  try {
146
146
  body = await response.json();
147
147
  } catch {
148
- throw new CliError(`kie.ai 请求失败:HTTP ${response.status}`);
148
+ throw new CliError(`Kie.ai 请求失败:HTTP ${response.status}`);
149
149
  }
150
- if (!response.ok || body.code !== 200) throw new (body.code === 429 ? KieRateLimitError : CliError)(`kie.ai 请求失败(${body.code ?? response.status}):${body.msg || response.statusText}`);
150
+ if (!response.ok || body.code !== 200) throw new (body.code === 429 ? KieRateLimitError : CliError)(`Kie.ai 请求失败(${body.code ?? response.status}):${body.msg || response.statusText}`);
151
151
  return body;
152
152
  }
153
153
  // src/catalog/cache.ts
@@ -285,7 +285,7 @@ const TASK_PATH = "/api/v1/jobs/createTask";
285
285
  async function getInputSchema(model, refresh = false) {
286
286
  const doc = await cached(`schemas/${encodeURIComponent(model)}.json`, async () => {
287
287
  const openapi = await getKieModelSchema(model);
288
- if (!openapi) throw new CliError(`kie 尚未提供模型 ${model} 的参数定义`);
288
+ if (!openapi) throw new CliError(`Kie.ai 尚未提供模型 ${model} 的参数定义`);
289
289
  return openapi;
290
290
  }, refresh);
291
291
  const operation = (await dereference(structuredClone(doc), { resolve: { external: false } })).paths?.[TASK_PATH]?.post;
@@ -939,7 +939,7 @@ async function placeOrders(model, input, source, count, deps) {
939
939
  }
940
940
  // src/kie/record.ts
941
941
  /**
942
- * 把 kie.ai 的五态任务记录归约成三态。结果在 resultJson 里:图片、视频、语音为
942
+ * 把 Kie.ai 的五态任务记录归约成三态。结果在 resultJson 里:图片、视频、语音为
943
943
  * `{ resultUrls: [...] }`;Suno(音乐、音效)为 `{ data: [{ audio_url, duration }, ...] }`。
944
944
  */
945
945
  function parseKieRecord(record) {
@@ -1215,7 +1215,7 @@ async function downloadProduct(dir, record, taskId, index, item, deps) {
1215
1215
  */
1216
1216
  const LINK_DIR = ".genlab";
1217
1217
  const LINK_FILE = "project.json";
1218
- const GITIGNORE = "# genlab 自动创建\n*\n";
1218
+ const GITIGNORE = "# GenLab 自动创建\n*\n";
1219
1219
  async function readIfExists(file) {
1220
1220
  try {
1221
1221
  return await readFile(file, "utf8");
@@ -1588,7 +1588,7 @@ async function listCommand(folder, args) {
1588
1588
  };
1589
1589
  }
1590
1590
  // src/commands/skill.ts
1591
- /** genlab skill:经 skills(npx skills)全局安装或卸载本机 agent 的 genlab skill。 */
1591
+ /** genlab skill:经 skills(npx skills)全局安装或卸载本机 agent 的 GenLab skill。 */
1592
1592
  /** 固定 skills 的版本,安装行为不随它的新版本变化。 */
1593
1593
  const SKILLS_CLI = "skills@1.7.6";
1594
1594
  const SKILL_SOURCE = "WBBB0730/genlab";
@@ -1668,35 +1668,49 @@ function answered(value) {
1668
1668
  function expandHome(dir) {
1669
1669
  return dir === "~" || dir.startsWith("~/") ? path.join(homedir(), dir.slice(1)) : dir;
1670
1670
  }
1671
+ /** 验证 Key 后建好库目录、写入配置,返回余额。 */
1672
+ async function writeConfig(file, key, library) {
1673
+ if (!key) throw new CliError("缺少 --key");
1674
+ if (!path.isAbsolute(expandHome(library))) throw new CliError(`库目录必须是绝对路径:${library}`);
1675
+ const credits = await getKieCredits(key);
1676
+ await mkdir(expandHome(library), { recursive: true });
1677
+ await mkdir(path.dirname(file), { recursive: true });
1678
+ await writeFileAtomic(file, `KIE_API_KEY=${JSON.stringify(key)}\nGENLAB_LIBRARY=${JSON.stringify(library)}\n`, { mode: 384 });
1679
+ return credits;
1680
+ }
1671
1681
  /**
1672
1682
  * 未通过选项给出的值:终端里交互询问(直接回车沿用已有配置),非终端时沿用已有配置,
1673
1683
  * 都没有就报错。未指定 --skill / --no-skill 时终端里询问是否安装 skill,非终端不安装;
1674
- * 安装失败只警告,不影响已写入的配置。
1684
+ * 安装失败只警告,不影响已写入的配置。有询问时以 intro 开始,配置写入成败作为结尾。
1675
1685
  */
1676
1686
  async function initCommand(args) {
1677
1687
  const file = configFile();
1678
1688
  const existing = await readConfig(file);
1679
1689
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
1690
+ const asking = interactive && (!args.key || !args.library || args.skill === void 0);
1680
1691
  let key = args.key ?? existing.KIE_API_KEY;
1681
1692
  let library = args.library ?? existing.GENLAB_LIBRARY;
1682
1693
  let skill = args.skill;
1694
+ if (asking) p.intro("genlab init");
1683
1695
  if (interactive && !args.key) key = answered(await p.password({
1684
- message: existing.KIE_API_KEY ? "kie.ai API Key(回车沿用已有)" : "kie.ai API Key(https://kie.ai/api-key)",
1696
+ message: existing.KIE_API_KEY ? "请输入 Kie.ai API Key(留空沿用已保存的 Key):" : "请输入 Kie.ai API Key(获取地址:https://kie.ai/api-key):",
1685
1697
  validate: (value) => value || existing.KIE_API_KEY ? void 0 : "不能为空"
1686
1698
  })) || existing.KIE_API_KEY;
1687
1699
  if (interactive && !args.library) library = answered(await p.text({
1688
- message: "库目录",
1700
+ message: "请输入库目录(存放所有项目与素材):",
1689
1701
  initialValue: existing.GENLAB_LIBRARY ?? DEFAULT_LIBRARY,
1690
1702
  validate: (value) => value ? void 0 : "不能为空"
1691
1703
  }));
1692
- if (interactive && skill === void 0) skill = answered(await p.confirm({ message: "安装 skill,让 agent 需要生成媒体时自动想到 genlab?" }));
1693
- if (!key) throw new CliError("缺少 --key");
1704
+ if (interactive && skill === void 0) skill = answered(await p.confirm({ message: "是否为本机 agent 安装 GenLab skill?" }));
1694
1705
  library ||= DEFAULT_LIBRARY;
1695
- if (!path.isAbsolute(expandHome(library))) throw new CliError(`库目录必须是绝对路径:${library}`);
1696
- const credits = await getKieCredits(key);
1697
- await mkdir(expandHome(library), { recursive: true });
1698
- await mkdir(path.dirname(file), { recursive: true });
1699
- await writeFileAtomic(file, `KIE_API_KEY=${JSON.stringify(key)}\nGENLAB_LIBRARY=${JSON.stringify(library)}\n`, { mode: 384 });
1706
+ let credits;
1707
+ try {
1708
+ credits = await writeConfig(file, key, library);
1709
+ } catch (error) {
1710
+ if (asking) p.cancel("配置未写入");
1711
+ throw error;
1712
+ }
1713
+ if (asking) p.outro("配置已写入");
1700
1714
  let installed = false;
1701
1715
  if (skill) try {
1702
1716
  await installSkill(interactive);
@@ -1777,7 +1791,7 @@ function withBackgroundSync(run) {
1777
1791
  }
1778
1792
  });
1779
1793
  }
1780
- const program = new Command("genlab").description("用 kie.ai 的模型生成图片、视频、音频,产物与生成记录保存在本机的库里。完整用法:genlab guide").version(version, "-V, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand("help [command]", "显示命令的帮助").showHelpAfterError();
1794
+ const program = new Command("genlab").description("用 Kie.ai 的模型生成图片、视频、音频,产物与生成记录保存在本机的库里。完整用法:genlab guide").version(version, "-V, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand("help [command]", "显示命令的帮助").showHelpAfterError();
1781
1795
  function generationOptions(command, modelDescription) {
1782
1796
  return command.option("-m, --model <id>", modelDescription).option("-p, --param <key=value>", "模型参数,可重复;key:=JSON 传 JSON 值,@<路径> 上传本机文件", collect).option("-n, --count <n>", "并列任务数(1–10,默认 1)", positiveInteger);
1783
1797
  }
@@ -1788,12 +1802,12 @@ function waitOptions(command) {
1788
1802
  return command.option("--no-wait", "提交后立即返回,不等待结果").option("--timeout <seconds>", "最多等待的秒数", positiveInteger, DEFAULT_TIMEOUT_SECONDS);
1789
1803
  }
1790
1804
  program.command("guide").description("输出完整用法:概念、流程、参数写法、规则与命令参考").action(() => guideCommand(program, GUIDE_FILE));
1791
- program.command("init").description("写入配置(kie.ai API Key 与库目录),可顺带安装 skill").option("--key <key>", "kie.ai API Key").option("--library <dir>", "库目录(绝对路径,可用 ~ 开头)").option("--skill", "安装 skill,不再询问").option("--no-skill", "不安装 skill").action(action(initCommand));
1792
- const skill = program.command("skill").description("安装、更新或卸载 genlab skill,让 agent 能发现 genlab");
1805
+ program.command("init").description("写入配置(Kie.ai API Key 与库目录),可顺带安装 skill").option("--key <key>", "Kie.ai API Key").option("--library <dir>", "库目录(绝对路径,可用 ~ 开头)").option("--skill", "安装 skill,不再询问").option("--no-skill", "不安装 skill").action(action(initCommand));
1806
+ const skill = program.command("skill").description("安装、更新或卸载 GenLab skill");
1793
1807
  skill.command("add", { isDefault: true }).description("安装 skill(默认)").action(action(skillAddCommand));
1794
1808
  skill.command("update").description("更新 skill,沿用安装时选的 agent").action(action(skillUpdateCommand));
1795
1809
  skill.command("remove").description("卸载 skill").action(action(skillRemoveCommand));
1796
- program.command("models").description("列出 kie 模型;指定模型时输出价格与参数表").argument("[id]", "模型标识").option("--kind <kind>", "按类型筛选:image / video / audio").option("--search <text>", "按模型标识、供应商、名称筛选").option("--refresh", "忽略缓存,重新获取").action(action(modelsCommand));
1810
+ program.command("models").description("列出 Kie.ai 模型;指定模型时输出价格与参数表").argument("[id]", "模型标识").option("--kind <kind>", "按类型筛选:image / video / audio").option("--search <text>", "按模型标识、供应商、名称筛选").option("--refresh", "忽略缓存,重新获取").action(action(modelsCommand));
1797
1811
  const project = program.command("project").description("查看当前目录关联的项目与库里的全部项目").action(action(projectCommand));
1798
1812
  project.command("create").description("新建项目").argument("<name>", "项目名").action(action(projectCreateCommand));
1799
1813
  project.command("link").description("把当前目录关联到项目;不属于任何代码项目时用 default").argument("<name>", "项目名").action(action(projectLinkCommand));
package/dist/guide.md CHANGED
@@ -1,12 +1,12 @@
1
- # genlab 使用说明
1
+ # GenLab 使用说明
2
2
 
3
- 用 kie.ai 的模型生成图片、视频、音频,产物与完整生成记录保存在本机的库里。
3
+ 用 Kie.ai 的模型生成图片、视频、音频,产物与完整生成记录保存在本机的库里。
4
4
 
5
5
  下文的 `genlab` 均可写作 `npx -y genlab@latest`(推荐,每次都用最新版本)。
6
6
 
7
7
  ## 准备
8
8
 
9
- - 报错「缺少配置」时,请用户在终端运行 `genlab init`,填写 kie.ai API Key 与库目录。
9
+ - 报错「缺少配置」时,请用户在终端运行 `genlab init`,填写 Kie.ai API Key 与库目录。
10
10
  - 库目录在代码项目之外。在沙箱里运行时,需要允许读写库目录与 `~/.cache/genlab`(模型目录缓存),并允许访问网络。
11
11
 
12
12
  ## 概念
@@ -29,7 +29,7 @@
29
29
  3. `genlab models --kind <类型> [--search <关键词>]` 找模型,`genlab models <模型>` 查看价格与参数表。参数名以参数表为准,不凭记忆填。文生与图生常是两个模型(`…-text-to-image` / `…-image-to-image`),按是否带输入图选择。
30
30
  4. 用户没指定模型时,为新素材的第一次生成挑 2–3 个候选(模型、价格、特点)请用户选;同一素材之后的生成默认沿用该模型(需要带输入图时换成同系列的图生模型),不再询问。
31
31
  5. `create` 新建素材,`generate` 在已有素材里生成,`edit` 做 AI 编辑,`retry` 重试失败的版本。命令默认最多等 90 秒;输出 `"pending": true` 时用 `genlab wait` 继续等。同时为多个素材发起生成时加 `--no-wait`,再用 `genlab wait` 一起等。
32
- 6. 生成完向用户汇报使用的模型(版本的 `model`)、消耗的积分(各版本 `credits` 之和)与折合美元(kie 基础价 1 积分 = $0.005)。
32
+ 6. 生成完向用户汇报使用的模型(版本的 `model`)、消耗的积分(各版本 `credits` 之和)与折合美元(Kie.ai 基础价 1 积分 = $0.005)。
33
33
  7. 每个版本的 `file` 是产物的绝对路径;一个任务可能产出多个版本(如一次出两首的音乐模型)。用户选定后用 `genlab use` 设为当前版本;需要放进代码项目时自行复制该文件。
34
34
 
35
35
  ## 模型参数(-p)
@@ -43,7 +43,7 @@
43
43
 
44
44
  ## 规则
45
45
 
46
- - 生成消耗 kie 积分:先看价格,任务数、分辨率、时长按需设置。
46
+ - 生成消耗 Kie.ai 积分:先看价格,任务数、分辨率、时长按需设置。
47
47
  - 不手动修改库里的 `asset.json`;改名、移动、删除整个素材由用户在文件管理器里操作。
48
48
  - 项目、文件夹、素材的名字用英文。
49
49
  - 结果为 JSON;错误写 stderr 的 `{"error": ...}`,警告为 `{"warning": ...}`。
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "genlab",
3
3
  "type": "module",
4
- "version": "0.1.3",
5
- "description": "给本地 agent 用的 AI 媒体生成命令行工具(kie.ai),产物与完整生成记录保存在本机",
4
+ "version": "0.1.4",
5
+ "description": "给本地 agent 用的 AI 媒体生成命令行工具(Kie.ai),产物与完整生成记录保存在本机",
6
6
  "author": "WBBB",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/WBBB0730/genlab#readme",