museav-cli 2.4.0 → 2.5.0

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/AGENTS.md CHANGED
@@ -64,8 +64,14 @@ museav upload ./face.png
64
64
  # compress: resize/re-encode via sharp. Output <name>-min.<fmt>, never overwrites input.
65
65
  # remove-bg: ISNet via onnxruntime-node → alpha PNG (~170MB model auto-downloaded
66
66
  # to ~/.museav-models on first use; %USERPROFILE%\.museav-models on Windows).
67
+ # upscale: Real-ESRGAN via Vulkan GPU → 2M image becomes 10M+ (~65MB engine+model
68
+ # auto-downloaded once to ~/.museav-bin + ~/.museav-models).
69
+ # remove-watermark: pixel-heuristic auto-locate (zero model deps) + LaMa inpainting
70
+ # → clean PNG (~200MB model on-demand, freed after run).
67
71
  museav compress ./photo.jpg --max-edge 800 --format webp --quality 70
68
72
  museav remove-bg ./shoe.png # stdout: path to <name>-nobg.png
73
+ museav upscale ./photo.jpg --scale 4 # stdout: path to <name>-4x.png
74
+ museav remove-watermark ./poster.jpg # stdout: path to <name>-clean.png
69
75
 
70
76
  # List your own (or, if using a tenant apiKey, your tenant's) recent jobs as JSON
71
77
  museav jobs --limit 10 --status failed
@@ -81,7 +87,9 @@ museav products
81
87
  museav assets
82
88
 
83
89
  # Check who you're logged in as and whether the account is affiliated with a tenant
84
- # (personal login only apiKey callers get an error, they're already acting as the tenant)
90
+ # Works for both personal login and apiKey (platform account or tenant). For apiKey:
91
+ # platform-account → 账户: nickname + 邮箱 + 累计出图 + credits
92
+ # tenant → 租户: tenant_id + name + logo
85
93
  museav whoami
86
94
  ```
87
95
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0 (2026-08-17)
4
+
5
+ ### 修复
6
+ - **`whoami` 不再拒绝 apiKey 模式**:之前会直接抛错「只支持个人 login 身份」。实际上 `/api/me` 对两种 apiKey 都返回真实数据——
7
+ - 平台账户 apiKey(`STUDIO_API_KEY` 指向 `accounts.sk-*`)→ 账户身份:邮箱 + nickname + 累计出图次数 + credits
8
+ - 租户 apiKey(指向 `api_tenants.tenant_key`)→ 业务身份:tenant_id + name + nickname + logo
9
+ 修复后三种身份都能 `museav whoami` 查清楚当前是谁、关联到哪个租户。
10
+
11
+ ### 文档
12
+ - AGENTS.md `whoami` 说明:去掉「apiKey 不可用」的旧断言。
13
+
3
14
  ## 2.4.0 · 2026-08-17
4
15
 
5
16
  **本地图像工具箱补全(免登录、零成本、macOS/Windows 通用)。** 均为轻量级工具,无大模型常驻内存。
package/README.md CHANGED
@@ -85,6 +85,27 @@
85
85
 
86
86
  两条路径互不依赖,选跟你身份匹配的那条,不用两个都配。下面「快速开始」走的是平台用户(login)这条路;如果你是租户,直接跳到下面「B 端 / CI 场景」那一节。
87
87
 
88
+ ## 能力速览(小白先看这个)
89
+
90
+ `museav` 能干什么,一句话一组:
91
+
92
+ | 分类 | 能力 | 命令 |
93
+ |---|---|---|
94
+ | **AI 生成**(中台) | 文字出图 / 垫图出图 / 技能出图 / 模板出图 | `gen`(含 `--ref` 垫图、`--transparent` 透明底) |
95
+ | | 文生视频 / 图生视频 | `gen --video` |
96
+ | | 从已有图片反推提示词 | `reverse` |
97
+ | | 一张图做成可复用模板 | `image-to-template` |
98
+ | | 建/查图片模板、视频模板 | `templates` / `video-templates` |
99
+ | **项目管理**(中台) | 工作区(项目)+ 每个项目自己的素材库 | `projects`、`projects assets` |
100
+ | | 素材直链垫图、作品归档进项目 | `gen --project`、`jobs --project` |
101
+ | **本地图像处理**(免登录/免费/离线) | 压缩图片 | `compress` |
102
+ | | 抠图去背景(输出透明 PNG) | `remove-bg` |
103
+ | | **放大清晰度**(2M → 10M+ 级) | `upscale` |
104
+ | | 去水印 | `remove-watermark` |
105
+ | **素材与统计** | 上传素材 / 查任务 / 查余额 / 查模型 / 查身份 | `upload` / `jobs` / `balance` / `models` / `whoami` |
106
+
107
+ > 本地工具(`compress` / `remove-bg` / `upscale` / `remove-watermark`)**不用登录、不花一分钱**,装了就能用;其余命令需要一个凭证(个人 `login` 或租户 apiKey)。
108
+
88
109
  ---
89
110
 
90
111
  ## 快速开始
@@ -193,6 +214,10 @@ curl -o poster.png "$URL"
193
214
  # 先查有哪些模板(自己租户建的 + 平台共享的)
194
215
  museav templates
195
216
  museav templates --category 电商白底图 # 按分类过滤
217
+ museav templates --mine # 只看我这个人建的
218
+ museav templates --tenant # 只看本租户专属的
219
+ museav templates --platform # 只看平台共享的
220
+ museav templates --type image # 按类型过滤(image / article)
196
221
 
197
222
  # 没有占位符的模板,直接用
198
223
  museav gen --template <模板id>
@@ -476,6 +501,8 @@ console.log(r.sculpt.light) // 光影分析
476
501
  | `upload <file>` | 上传素材(图片/音频/视频) | 公网直链 |
477
502
  | `compress <file>` | 本地压缩图片(免登录,`--max-edge`/`--quality`/`--format`) | 产物路径 |
478
503
  | `remove-bg <file>` | 本地抠图去背景(免登录,输出带 alpha 的 PNG) | 产物路径 |
504
+ | `upscale <file>` | 本地超分放大,2M 图变 10M+(免登录,Real-ESRGAN GPU,`--scale 2/3/4`) | 产物路径 |
505
+ | `remove-watermark <file>` | 本地去水印(免登录,自动定位 + LaMa 修复,`--mask` 手工兜底) | 产物路径 |
479
506
  | `projects` | 工作区(项目)列表:一账户多项目,各带自己的素材库 | 项目 id 列表 |
480
507
  | `projects assets --project` | 项目素材库:ls / add / rm(垫图母版,人像库/产品库各管各的) | `id<TAB>url` 行 |
481
508
  | `models` | 可用模型 | 模型名列表 |
package/dist/client.d.ts CHANGED
@@ -71,6 +71,10 @@ export interface TemplateOption {
71
71
  sample_cover_image?: string | null;
72
72
  /** 归属:自己租户建的 vs 平台共享的(tenant_id 为空) */
73
73
  tenant_id: string | null;
74
+ /** 中台下发的归属标记:mine=本租户建的 / platform=平台共享 / personal=我这个人建的 */
75
+ source?: 'mine' | 'platform' | 'personal';
76
+ /** 创建人(平台管理员个人建的模板会带邮箱;租户建的为 null) */
77
+ created_by?: string | null;
74
78
  generation_configs: Array<{
75
79
  model: string;
76
80
  prompt_template: string;
@@ -319,8 +323,9 @@ export declare class StudioClient {
319
323
  /** 可用技能清单:私有 + 所属租户专属模板 + 公共库,服务端已按调用者权限过滤 */
320
324
  skills(): Promise<SkillOption[]>;
321
325
  /** 可用图片/文字模板清单:自己租户建的 + 平台共享的,服务端已按调用者权限过滤。
322
- * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。 */
323
- templates(type?: 'image' | 'article'): Promise<TemplateOption[]>;
326
+ * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。
327
+ * source=mine|platform|personal|all(默认 all;mine=本租户,platform=平台共享,personal=我这个人建的)。 */
328
+ templates(type?: 'image' | 'article', source?: 'mine' | 'platform' | 'personal' | 'all'): Promise<TemplateOption[]>;
324
329
  /** 视频模板清单(POST /api/videos 用 template_id)。结构同图片模板的 generation_configs 形态 */
325
330
  videoTemplates(): Promise<TemplateOption[]>;
326
331
  /** 新建图片模板。归属(是否关联租户)由服务端根据鉴权身份决定,见 CreateTemplateInput 注释 */
package/dist/client.js CHANGED
@@ -90,9 +90,15 @@ export class StudioClient {
90
90
  return Array.isArray(r) ? r : [];
91
91
  }
92
92
  /** 可用图片/文字模板清单:自己租户建的 + 平台共享的,服务端已按调用者权限过滤。
93
- * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。 */
94
- async templates(type) {
95
- const qs = type ? `?type=${type}` : '';
93
+ * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。
94
+ * source=mine|platform|personal|all(默认 all;mine=本租户,platform=平台共享,personal=我这个人建的)。 */
95
+ async templates(type, source) {
96
+ const params = new URLSearchParams();
97
+ if (type)
98
+ params.set('type', type);
99
+ if (source && source !== 'all')
100
+ params.set('source', source);
101
+ const qs = params.toString() ? `?${params}` : '';
96
102
  const r = await this.request(`templates${qs}`);
97
103
  return Array.isArray(r) ? r : [];
98
104
  }
@@ -20,9 +20,12 @@ export async function projects(client) {
20
20
  process.stderr.write('还没有工作区(museav projects create --name 新建)\n');
21
21
  return;
22
22
  }
23
+ // 每个项目的素材数(模板不挂项目,这里统计的是项目素材库,不是模板)
24
+ const assetCounts = await Promise.all(list.map(async (w) => ({ id: w.id, n: (await client.workspaceAssets(w.id).catch(() => [])).length })));
25
+ const assetOf = Object.fromEntries(assetCounts.map((x) => [x.id, x.n]));
23
26
  process.stderr.write(`工作区(${list.length} 个):\n`);
24
27
  for (const w of list) {
25
- process.stderr.write(` ${w.id} ${w.name.padEnd(16)} 出图 ${w.gen_done ?? 0}/${w.gen_total ?? 0}${w.brand ? ` brand:${w.brand}` : ''}\n`);
28
+ process.stderr.write(` ${w.id} ${w.name.padEnd(16)} 出图 ${w.gen_done ?? 0}/${w.gen_total ?? 0} 素材 ${assetOf[w.id] ?? 0}${w.brand ? ` brand:${w.brand}` : ''}\n`);
26
29
  }
27
30
  process.stderr.write(`\n素材库: museav projects assets --project <id|名>\n出图归档: museav gen --project <id|名> ...\n`);
28
31
  // stdout 只出 id,便于脚本解析
@@ -1,9 +1,14 @@
1
- /** museav templates —— 查可用图片/文字模板(自己租户建的 + 平台共享的)
2
- * --type image|article 可过滤(中台 templates 表同时装两种,不传则都列并标注类型) */
1
+ /** museav templates —— 查可用图片/文字模板。
2
+ * --type image|article 按类型过滤
3
+ * --mine 只看本租户建的;--platform 只看平台共享的;都不传则全部列出
4
+ * --category 按分类过滤 */
3
5
  import type { StudioClient } from '../client.js';
4
6
  export declare function templates(client: StudioClient, opts?: {
5
7
  category?: string;
6
8
  type?: string;
9
+ mine?: boolean;
10
+ tenant?: boolean;
11
+ platform?: boolean;
7
12
  }): Promise<void>;
8
13
  interface CreateTemplateOpts {
9
14
  name: string;
@@ -1,5 +1,22 @@
1
1
  export async function templates(client, opts = {}) {
2
- let list = await client.templates((opts.type === 'image' || opts.type === 'article') ? opts.type : undefined);
2
+ const type = opts.type === 'image' || opts.type === 'article' ? opts.type : undefined;
3
+ // 三个归属维度都走服务端 source 参数(正式 API,不再客户端猜):
4
+ // --mine → source=personal(created_by = 当前账户邮箱,我这个人建的)
5
+ // --tenant → source=mine(本租户专属)
6
+ // --platform → source=platform(平台共享)
7
+ let list;
8
+ if (opts.mine) {
9
+ list = await client.templates(type, 'personal');
10
+ }
11
+ else if (opts.tenant) {
12
+ list = await client.templates(type, 'mine');
13
+ }
14
+ else if (opts.platform) {
15
+ list = await client.templates(type, 'platform');
16
+ }
17
+ else {
18
+ list = await client.templates(type);
19
+ }
3
20
  if (opts.category) {
4
21
  const kw = opts.category.toLowerCase();
5
22
  list = list.filter((t) => (t.category || '').toLowerCase().includes(kw));
@@ -8,7 +25,13 @@ export async function templates(client, opts = {}) {
8
25
  process.stderr.write(opts.category ? `没有匹配「${opts.category}」的模板\n` : '没有可用模板\n');
9
26
  return;
10
27
  }
11
- const tag = (t) => (t.tenant_id ? '' : '[平台]');
28
+ const tag = (t) => {
29
+ if (t.source === 'personal')
30
+ return '[个人]';
31
+ if (t.tenant_id)
32
+ return '[租户]';
33
+ return '[平台]';
34
+ };
12
35
  const typeTag = (t) => (t.template_type === 'article' ? '[文字]' : t.template_type === 'image' ? '[图片]' : '');
13
36
  process.stderr.write(`可用模板(${list.length} 个):\n`);
14
37
  for (const t of list) {
@@ -19,7 +42,7 @@ export async function templates(client, opts = {}) {
19
42
  process.stderr.write(` ${t.id.padEnd(38)} ${(t.zh_name || '').padEnd(16)} ${(t.category || '').padEnd(10)} ${(t.ratio || '').padEnd(6)} ${typeTag(t).padEnd(8)} ${fieldHint.padEnd(20)} ${tag(t)}\n`);
20
43
  }
21
44
  process.stderr.write(`\n出图: museav gen --template <模板id> [--fields '{"key":"值"}']\n`);
22
- process.stderr.write(`按类型过滤: museav templates --type image|article\n`);
45
+ process.stderr.write(`筛选: --mine(我建的) --tenant(本租户) --platform(平台共享) --type image|article --category <分类>\n`);
23
46
  // stdout 只出 id,便于脚本与 agent 解析
24
47
  console.log(list.map((t) => t.id).join('\n'));
25
48
  }
@@ -1,3 +1,9 @@
1
- /** museav whoami —— 查当前登录账户 + 租户归属 */
1
+ /** museav whoami —— 查当前身份 + 关联信息。
2
+ *
3
+ * apiKey 模式下也支持(2026-08-17 修复):服务端 /api/me 对两种 apiKey 都返回真实数据——
4
+ * - 平台账户 apiKey(STUDIO_API_KEY 指向 accounts.sk-*) → 走个人身份,含 credits / gen 统计
5
+ * - 租户 apiKey(指向 api_tenants.tenant_key) → 走业务身份,只下发 tenant 信息
6
+ * 个人 login(museav login)走完整 me,输出不变。
7
+ */
2
8
  import type { StudioClient } from '../client.js';
3
9
  export declare function whoami(client: StudioClient): Promise<void>;
@@ -1,14 +1,19 @@
1
- import { loadConfig } from '../config.js';
2
1
  export async function whoami(client) {
3
- // apiKey 是系统接入方/服务身份,不是个人账户,/api/me 不适用——直接提示,别等服务端 401
4
- const cfg = loadConfig();
5
- if (cfg.apiKey && !cfg.token) {
6
- throw new Error('whoami 只支持个人 login 身份:museav login\n' +
7
- '(apiKey 代表接入的业务系统,不是个人账户,可用 museav jobs / balance 查看业务数据)');
2
+ const me = (await client.me());
3
+ if (me.identity === 'tenant') {
4
+ const t = me.tenant;
5
+ process.stderr.write(`身份: 租户 API Key\n`);
6
+ process.stderr.write(`租户: ${t.nickname || t.name}(${t.name})\n`);
7
+ process.stderr.write(`tenant_id: ${t.id}\n`);
8
+ if (t.logo)
9
+ process.stderr.write(`logo: ${t.logo}\n`);
10
+ }
11
+ else {
12
+ process.stderr.write(`账户: ${me.nickname || me.email}(${me.email})\n`);
13
+ process.stderr.write(me.brand ? `业务系统: ${me.brand.name}\n` : `身份: 平台账户 API Key\n`);
14
+ process.stderr.write(`出图: 累计 ${me.gen_total ?? 0} 次,成功 ${me.gen_done ?? 0} 次\n`);
15
+ if (me.credits != null)
16
+ process.stderr.write(`credits: ${me.credits}\n`);
8
17
  }
9
- const me = await client.me();
10
- process.stderr.write(`账户: ${me.nickname || me.email}(${me.email})\n`);
11
- process.stderr.write(me.brand ? `业务系统: ${me.brand.name}\n` : '业务系统: 未接入(个人用户)\n');
12
- process.stderr.write(`出图: 累计 ${me.gen_total} 次,成功 ${me.gen_done} 次\n`);
13
18
  console.log(JSON.stringify(me));
14
19
  }
package/dist/index.js CHANGED
@@ -262,6 +262,9 @@ const templatesCmd = program
262
262
  .description('查可用图片/文字模板:自己租户建的 + 平台共享的(跟技能是两套不同的机制,见 gen --template)')
263
263
  .option('--category <name>', '按分类过滤,如 电商白底图 / 演唱会')
264
264
  .option('--type <type>', '按类型过滤:image(图片) / article(文字),不传则两类都列并标注')
265
+ .option('--mine', '只看我这个人建的模板(created_by 是我,排除系统种子和租户专属)')
266
+ .option('--tenant', '只看本租户建的模板')
267
+ .option('--platform', '只看平台共享的模板')
265
268
  .action(withClient((client, opts) => templates(client, opts)));
266
269
  const videoTemplatesCmd = program
267
270
  .command('video-templates')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "museav-cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "MUSE AV 出图中台官方 CLI —— 命令行调中台 API 出图、出视频、读图逆向、图生模板",
5
5
  "type": "module",
6
6
  "bin": {
package/src/client.ts CHANGED
@@ -123,6 +123,10 @@ export interface TemplateOption {
123
123
  sample_cover_image?: string | null
124
124
  /** 归属:自己租户建的 vs 平台共享的(tenant_id 为空) */
125
125
  tenant_id: string | null
126
+ /** 中台下发的归属标记:mine=本租户建的 / platform=平台共享 / personal=我这个人建的 */
127
+ source?: 'mine' | 'platform' | 'personal'
128
+ /** 创建人(平台管理员个人建的模板会带邮箱;租户建的为 null) */
129
+ created_by?: string | null
126
130
  generation_configs: Array<{
127
131
  model: string
128
132
  prompt_template: string
@@ -386,9 +390,13 @@ export class StudioClient {
386
390
  }
387
391
 
388
392
  /** 可用图片/文字模板清单:自己租户建的 + 平台共享的,服务端已按调用者权限过滤。
389
- * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。 */
390
- async templates(type?: 'image' | 'article'): Promise<TemplateOption[]> {
391
- const qs = type ? `?type=${type}` : ''
393
+ * type=image|article 二选一(不传则图片+文字都返回,跟中台默认一致)。
394
+ * source=mine|platform|personal|all(默认 all;mine=本租户,platform=平台共享,personal=我这个人建的)。 */
395
+ async templates(type?: 'image' | 'article', source?: 'mine' | 'platform' | 'personal' | 'all'): Promise<TemplateOption[]> {
396
+ const params = new URLSearchParams()
397
+ if (type) params.set('type', type)
398
+ if (source && source !== 'all') params.set('source', source)
399
+ const qs = params.toString() ? `?${params}` : ''
392
400
  const r = await this.request(`templates${qs}`)
393
401
  return Array.isArray(r) ? r : []
394
402
  }
@@ -22,10 +22,15 @@ export async function projects(client: StudioClient): Promise<void> {
22
22
  process.stderr.write('还没有工作区(museav projects create --name 新建)\n')
23
23
  return
24
24
  }
25
+ // 每个项目的素材数(模板不挂项目,这里统计的是项目素材库,不是模板)
26
+ const assetCounts = await Promise.all(
27
+ list.map(async (w) => ({ id: w.id, n: (await client.workspaceAssets(w.id).catch(() => [])).length })),
28
+ )
29
+ const assetOf = Object.fromEntries(assetCounts.map((x) => [x.id, x.n]))
25
30
  process.stderr.write(`工作区(${list.length} 个):\n`)
26
31
  for (const w of list) {
27
32
  process.stderr.write(
28
- ` ${w.id} ${w.name.padEnd(16)} 出图 ${w.gen_done ?? 0}/${w.gen_total ?? 0}${w.brand ? ` brand:${w.brand}` : ''}\n`,
33
+ ` ${w.id} ${w.name.padEnd(16)} 出图 ${w.gen_done ?? 0}/${w.gen_total ?? 0} 素材 ${assetOf[w.id] ?? 0}${w.brand ? ` brand:${w.brand}` : ''}\n`,
29
34
  )
30
35
  }
31
36
  process.stderr.write(`\n素材库: museav projects assets --project <id|名>\n出图归档: museav gen --project <id|名> ...\n`)
@@ -1,9 +1,25 @@
1
- /** museav templates —— 查可用图片/文字模板(自己租户建的 + 平台共享的)
2
- * --type image|article 可过滤(中台 templates 表同时装两种,不传则都列并标注类型) */
1
+ /** museav templates —— 查可用图片/文字模板。
2
+ * --type image|article 按类型过滤
3
+ * --mine 只看本租户建的;--platform 只看平台共享的;都不传则全部列出
4
+ * --category 按分类过滤 */
3
5
  import type { StudioClient } from '../client.js'
4
6
 
5
- export async function templates(client: StudioClient, opts: { category?: string; type?: string } = {}): Promise<void> {
6
- let list = await client.templates((opts.type === 'image' || opts.type === 'article') ? opts.type : undefined)
7
+ export async function templates(client: StudioClient, opts: { category?: string; type?: string; mine?: boolean; tenant?: boolean; platform?: boolean } = {}): Promise<void> {
8
+ const type = opts.type === 'image' || opts.type === 'article' ? opts.type : undefined
9
+ // 三个归属维度都走服务端 source 参数(正式 API,不再客户端猜):
10
+ // --mine → source=personal(created_by = 当前账户邮箱,我这个人建的)
11
+ // --tenant → source=mine(本租户专属)
12
+ // --platform → source=platform(平台共享)
13
+ let list: Awaited<ReturnType<StudioClient['templates']>>
14
+ if (opts.mine) {
15
+ list = await client.templates(type, 'personal')
16
+ } else if (opts.tenant) {
17
+ list = await client.templates(type, 'mine')
18
+ } else if (opts.platform) {
19
+ list = await client.templates(type, 'platform')
20
+ } else {
21
+ list = await client.templates(type)
22
+ }
7
23
  if (opts.category) {
8
24
  const kw = opts.category.toLowerCase()
9
25
  list = list.filter((t) => (t.category || '').toLowerCase().includes(kw))
@@ -13,7 +29,11 @@ export async function templates(client: StudioClient, opts: { category?: string;
13
29
  return
14
30
  }
15
31
 
16
- const tag = (t: (typeof list)[number]) => (t.tenant_id ? '' : '[平台]')
32
+ const tag = (t: (typeof list)[number]) => {
33
+ if (t.source === 'personal') return '[个人]'
34
+ if (t.tenant_id) return '[租户]'
35
+ return '[平台]'
36
+ }
17
37
  const typeTag = (t: (typeof list)[number]) => (t.template_type === 'article' ? '[文字]' : t.template_type === 'image' ? '[图片]' : '')
18
38
 
19
39
  process.stderr.write(`可用模板(${list.length} 个):\n`)
@@ -27,7 +47,7 @@ export async function templates(client: StudioClient, opts: { category?: string;
27
47
  )
28
48
  }
29
49
  process.stderr.write(`\n出图: museav gen --template <模板id> [--fields '{"key":"值"}']\n`)
30
- process.stderr.write(`按类型过滤: museav templates --type image|article\n`)
50
+ process.stderr.write(`筛选: --mine(我建的) --tenant(本租户) --platform(平台共享) --type image|article --category <分类>\n`)
31
51
  // stdout 只出 id,便于脚本与 agent 解析
32
52
  console.log(list.map((t) => t.id).join('\n'))
33
53
  }
@@ -1,19 +1,46 @@
1
- /** museav whoami —— 查当前登录账户 + 租户归属 */
1
+ /** museav whoami —— 查当前身份 + 关联信息。
2
+ *
3
+ * apiKey 模式下也支持(2026-08-17 修复):服务端 /api/me 对两种 apiKey 都返回真实数据——
4
+ * - 平台账户 apiKey(STUDIO_API_KEY 指向 accounts.sk-*) → 走个人身份,含 credits / gen 统计
5
+ * - 租户 apiKey(指向 api_tenants.tenant_key) → 走业务身份,只下发 tenant 信息
6
+ * 个人 login(museav login)走完整 me,输出不变。
7
+ */
2
8
  import type { StudioClient } from '../client.js'
3
- import { loadConfig } from '../config.js'
9
+
10
+ /** /api/me 实际返回的形状:可能是账户身份也可能是租户身份(apiKey 模式) */
11
+ type MeResponse =
12
+ | {
13
+ identity?: 'account'
14
+ id: string
15
+ email: string
16
+ nickname?: string | null
17
+ bio?: string | null
18
+ role: string
19
+ credits?: number | null
20
+ gen_quota?: number | null
21
+ gen_total?: number
22
+ gen_done?: number
23
+ brand?: { name: string } | null
24
+ }
25
+ | {
26
+ identity: 'tenant'
27
+ tenant: { id: string; name: string; nickname?: string | null; logo?: string | null }
28
+ }
4
29
 
5
30
  export async function whoami(client: StudioClient): Promise<void> {
6
- // apiKey 是系统接入方/服务身份,不是个人账户,/api/me 不适用——直接提示,别等服务端 401
7
- const cfg = loadConfig()
8
- if (cfg.apiKey && !cfg.token) {
9
- throw new Error(
10
- 'whoami 只支持个人 login 身份:museav login\n' +
11
- '(apiKey 代表接入的业务系统,不是个人账户,可用 museav jobs / balance 查看业务数据)',
12
- )
31
+ const me = (await client.me()) as MeResponse
32
+
33
+ if (me.identity === 'tenant') {
34
+ const t = me.tenant
35
+ process.stderr.write(`身份: 租户 API Key\n`)
36
+ process.stderr.write(`租户: ${t.nickname || t.name}(${t.name})\n`)
37
+ process.stderr.write(`tenant_id: ${t.id}\n`)
38
+ if (t.logo) process.stderr.write(`logo: ${t.logo}\n`)
39
+ } else {
40
+ process.stderr.write(`账户: ${me.nickname || me.email}(${me.email})\n`)
41
+ process.stderr.write(me.brand ? `业务系统: ${me.brand.name}\n` : `身份: 平台账户 API Key\n`)
42
+ process.stderr.write(`出图: 累计 ${me.gen_total ?? 0} 次,成功 ${me.gen_done ?? 0} 次\n`)
43
+ if (me.credits != null) process.stderr.write(`credits: ${me.credits}\n`)
13
44
  }
14
- const me = await client.me()
15
- process.stderr.write(`账户: ${me.nickname || me.email}(${me.email})\n`)
16
- process.stderr.write(me.brand ? `业务系统: ${me.brand.name}\n` : '业务系统: 未接入(个人用户)\n')
17
- process.stderr.write(`出图: 累计 ${me.gen_total} 次,成功 ${me.gen_done} 次\n`)
18
45
  console.log(JSON.stringify(me))
19
46
  }
package/src/index.ts CHANGED
@@ -281,6 +281,9 @@ const templatesCmd = program
281
281
  .description('查可用图片/文字模板:自己租户建的 + 平台共享的(跟技能是两套不同的机制,见 gen --template)')
282
282
  .option('--category <name>', '按分类过滤,如 电商白底图 / 演唱会')
283
283
  .option('--type <type>', '按类型过滤:image(图片) / article(文字),不传则两类都列并标注')
284
+ .option('--mine', '只看我这个人建的模板(created_by 是我,排除系统种子和租户专属)')
285
+ .option('--tenant', '只看本租户建的模板')
286
+ .option('--platform', '只看平台共享的模板')
284
287
  .action(withClient((client: StudioClient, opts: any) => templates(client, opts)))
285
288
 
286
289
  const videoTemplatesCmd = program