@aswless_854771076/ai_short_studio_cli 0.1.33 → 0.1.35

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
@@ -13,6 +13,8 @@ ai-short-studio canvas node types --json
13
13
 
14
14
  npm 包内置 `using-vvicat-ai-short-studio-cli` Agent Skill。安装或升级 CLI 时,`postinstall` 会自动把该 Skill 安装或更新到 Codex;用户修改过或不由 CLI 管理的副本不会被覆盖。设置 `VVICAT_SKIP_SKILL_INSTALL=1` 可关闭自动安装。
15
15
 
16
+ npm 安装或升级现在会同时安装 `using-vvicat-ai-short-studio-cli` 和 `short-drama`。`skill install/status/update` 默认管理全部内置 Skill,也可用 `--skill short-drama` 只管理短剧 Skill;某个 Skill 被用户修改时会独立跳过,不影响另一个 Skill。创作剧本使用 `short-drama`,操作 VVICAT 使用主 CLI Skill。
17
+
16
18
  邮箱注册和登录从 stdin 读取密码:
17
19
 
18
20
  ```bash
@@ -92,6 +94,8 @@ CLI 通过 `/api/v1/bootstrap` 完成版本、连通性与 Supabase 公共配置
92
94
 
93
95
  `storyboard-image` schema 现在暴露节点级 `gridSize`。CLI 继续按实时 schema 通用写入,配套 Skill 会分析每个镜头的动作、运镜、多人调度和连续性难度,向用户确认后逐镜设置;未单独设置或连接旧服务时回退到项目格数,系统默认为 6 格。
94
96
 
97
+ `character-visual-design` schema 暴露 `candidateCount`,范围为 1~3,默认值为 1。CLI 按实时 schema 校验和写入;多个视觉描述仍分别进入独立三视图和图片资产链。
98
+
95
99
  `canvas settings set` 支持项目级 `imageResolution`、`imageQuality`、`videoResolution`,未覆盖的图片、图片编辑和视频节点会继承这些默认值。候选值来自 `settings get` 对应字段的实时 `options`;节点仅在需要偏离项目默认时,按当前模型 `capabilities` 写入 `resolution`、`quality` 覆盖。
96
100
 
97
101
  项目输出语言通过 `ai-short-studio canvas settings set --project <PROJECT_ID> --field outputLanguage --value en` 设置,也可用 `null` 恢复自动。语言变化只把相关下游节点标为 stale,不删除历史资产或自动生成。交付时只下载每镜 selected 原始 MP4,按 `S001.mp4` 顺序命名,并用 manifest 记录镜号、节点、资产、版本和文件名;字幕单独交付,不烧录进原始视频。
@@ -114,9 +118,10 @@ CLI 通过 `/api/v1/bootstrap` 完成版本、连通性与 Supabase 公共配置
114
118
  ai-short-studio skill install --target codex
115
119
  ai-short-studio skill status --target codex
116
120
  ai-short-studio skill update --target codex
121
+ ai-short-studio skill update --target codex --skill short-drama --json
117
122
  ```
118
123
 
119
- 手动恢复或安装到其他 Agent 时,可使用 `--target agents` `--directory <path>`。CLI 会记录版本和 SHA-256;已安装 Skill 被用户修改后,更新默认停止,只有显式 `--force` 才覆盖。自动安装可通过 `VVICAT_SKIP_SKILL_INSTALL=1` 跳过。
124
+ 手动恢复或安装到其他 Agent 时,可使用 `--target agents`;自定义全部内置 Skills 的父目录使用 `--root <path>`。旧版 `--directory <path>` 保留为兼容入口,只把主 CLI Skill 精确安装到该目录。自动安装对应使用 `VVICAT_SKILLS_ROOT`;旧变量 `VVICAT_SKILL_DIRECTORY` 仍保持主 Skill 精确目录语义。CLI 会记录版本和 SHA-256;已安装 Skill 被用户修改后,更新默认停止,只有显式 `--force` 才覆盖。自动安装可通过 `VVICAT_SKIP_SKILL_INSTALL=1` 跳过。
120
125
 
121
126
  ## 发布
122
127
 
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import { BaseCommand } from '../../../base-command.js';
3
3
  import { commandApi } from '../../../command-helpers.js';
4
4
  export default class CanvasNodeTypes extends BaseCommand {
5
- static description = '读取服务端实时画布节点类型与 schema';
5
+ static description = '读取服务端实时画布节点类型、默认配置与 schema';
6
6
  static args = { kind: Args.string() };
7
7
  static flags = BaseCommand.baseFlags;
8
8
  async run() { const { args, flags } = await this.parse(CanvasNodeTypes); const { api } = await commandApi(flags); const catalog = await api.nodeTypes(); this.print(args.kind ? catalog.nodeTypes.find((item) => item.kind === args.kind) ?? null : catalog); }
@@ -4,6 +4,8 @@ export default class SkillInstall extends BaseCommand {
4
4
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
5
5
  target: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
6
  directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ root: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ skill: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
9
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
10
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
11
  locale: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,8 +1,17 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base-command.js';
3
- import { installSkill } from '../../skill/skill-manager.js';
4
- import { resolveSkillTarget, skillFlags } from '../../skill/command-flags.js';
3
+ import { installSkill, installSkills, withLegacySkillFields } from '../../skill/skill-manager.js';
4
+ import { resolveSkillsRoot, selectedSkillNames, skillFlags } from '../../skill/command-flags.js';
5
5
  export default class SkillInstall extends BaseCommand {
6
6
  static flags = { ...BaseCommand.baseFlags, ...skillFlags, force: Flags.boolean({ description: '覆盖非托管或已修改的 Skill' }) };
7
- async run() { const { flags } = await this.parse(SkillInstall); this.print(await installSkill({ target: resolveSkillTarget(flags), force: flags.force })); }
7
+ async run() {
8
+ const { flags } = await this.parse(SkillInstall);
9
+ if (flags.directory) {
10
+ this.print(await installSkill({ target: flags.directory, force: flags.force }));
11
+ return;
12
+ }
13
+ this.print(withLegacySkillFields(await installSkills({
14
+ root: resolveSkillsRoot(flags), skills: selectedSkillNames(flags.skill), force: flags.force,
15
+ })));
16
+ }
8
17
  }
@@ -1,6 +1,10 @@
1
1
  import { BaseCommand } from '../../base-command.js';
2
2
  export default class SkillPath extends BaseCommand {
3
3
  static flags: {
4
+ target: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
5
+ directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ root: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ skill: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
4
8
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
10
  locale: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,6 +1,17 @@
1
1
  import { BaseCommand } from '../../base-command.js';
2
+ import { join } from 'node:path';
2
3
  import { bundledSkillPath } from '../../skill/skill-manager.js';
4
+ import { resolveSkillsRoot, selectedSkillNames, skillFlags } from '../../skill/command-flags.js';
3
5
  export default class SkillPath extends BaseCommand {
4
- static flags = { ...BaseCommand.baseFlags };
5
- async run() { await this.parse(SkillPath); this.print({ path: bundledSkillPath() }); }
6
+ static flags = { ...BaseCommand.baseFlags, ...skillFlags };
7
+ async run() {
8
+ const { flags } = await this.parse(SkillPath);
9
+ if (flags.directory) {
10
+ this.print({ path: bundledSkillPath(), target: flags.directory });
11
+ return;
12
+ }
13
+ const root = resolveSkillsRoot(flags);
14
+ const skills = selectedSkillNames(flags.skill).map((name) => ({ name, path: join(root, name), bundledPath: bundledSkillPath(name) }));
15
+ this.print({ path: skills[0]?.bundledPath ?? bundledSkillPath(), root, skills });
16
+ }
6
17
  }
@@ -3,6 +3,8 @@ export default class SkillStatus extends BaseCommand {
3
3
  static flags: {
4
4
  target: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
5
5
  directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ root: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ skill: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
8
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  locale: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,7 +1,17 @@
1
1
  import { BaseCommand } from '../../base-command.js';
2
- import { skillStatus } from '../../skill/skill-manager.js';
3
- import { resolveSkillTarget, skillFlags } from '../../skill/command-flags.js';
2
+ import { skillStatus, skillStatuses, withLegacySkillFields } from '../../skill/skill-manager.js';
3
+ import { resolveSkillsRoot, selectedSkillNames, skillFlags } from '../../skill/command-flags.js';
4
4
  export default class SkillStatus extends BaseCommand {
5
5
  static flags = { ...BaseCommand.baseFlags, ...skillFlags };
6
- async run() { const { flags } = await this.parse(SkillStatus); this.print(await skillStatus(resolveSkillTarget(flags))); }
6
+ async run() {
7
+ const { flags } = await this.parse(SkillStatus);
8
+ if (flags.directory) {
9
+ this.print(await skillStatus(flags.directory));
10
+ return;
11
+ }
12
+ const result = await skillStatuses(resolveSkillsRoot(flags));
13
+ const selected = new Set(selectedSkillNames(flags.skill));
14
+ const skills = result.skills.filter((item) => selected.has(item.name));
15
+ this.print(withLegacySkillFields({ ...result, current: skills.every((item) => item.current), skills }));
16
+ }
7
17
  }
@@ -1,8 +1,12 @@
1
+ import { type BundledSkillName } from './skill-manager.js';
1
2
  export declare const skillFlags: {
2
3
  target: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
3
4
  directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
+ root: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ skill: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
4
7
  };
5
- export declare function resolveSkillTarget(flags: {
8
+ export declare function resolveSkillsRoot(flags: {
6
9
  target: string;
7
- directory?: string;
10
+ root?: string;
8
11
  }): string;
12
+ export declare function selectedSkillNames(skill: string): readonly BundledSkillName[];
@@ -1,9 +1,14 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { defaultSkillTarget } from './skill-manager.js';
2
+ import { BUNDLED_SKILL_NAMES, defaultSkillsRoot, normalizeSkillsRoot, } from './skill-manager.js';
3
3
  export const skillFlags = {
4
4
  target: Flags.string({ options: ['codex', 'agents'], default: 'codex', description: 'Agent 类型' }),
5
- directory: Flags.string({ description: '自定义安装目录' }),
5
+ directory: Flags.string({ description: '兼容旧版:主 Skill 的精确安装目录', exclusive: ['root'] }),
6
+ root: Flags.string({ description: '全部内置 Skills 的根目录', exclusive: ['directory'] }),
7
+ skill: Flags.string({ options: ['all', ...BUNDLED_SKILL_NAMES], default: 'all', description: '要管理的内置 Skill' }),
6
8
  };
7
- export function resolveSkillTarget(flags) {
8
- return flags.directory || defaultSkillTarget(flags.target);
9
+ export function resolveSkillsRoot(flags) {
10
+ return normalizeSkillsRoot(flags.root || defaultSkillsRoot(flags.target));
11
+ }
12
+ export function selectedSkillNames(skill) {
13
+ return skill === 'all' ? BUNDLED_SKILL_NAMES : [skill];
9
14
  }
@@ -1,7 +1,12 @@
1
+ export declare const BUNDLED_SKILL_NAMES: readonly ["using-vvicat-ai-short-studio-cli", "short-drama"];
2
+ export type BundledSkillName = typeof BUNDLED_SKILL_NAMES[number];
1
3
  export type SkillTarget = 'codex' | 'agents';
2
- export declare function bundledSkillPath(): string;
4
+ export declare function bundledSkillPath(name?: BundledSkillName): string;
5
+ export declare function defaultSkillsRoot(target: SkillTarget): string;
3
6
  export declare function defaultSkillTarget(target: SkillTarget): string;
7
+ export declare function normalizeSkillsRoot(path: string): string;
4
8
  export declare function skillStatus(target: string): Promise<{
9
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
5
10
  target: string;
6
11
  installed: boolean;
7
12
  managed: boolean;
@@ -10,10 +15,99 @@ export declare function skillStatus(target: string): Promise<{
10
15
  installedVersion: string | null;
11
16
  modified: boolean;
12
17
  }>;
18
+ export declare function installSkills(input: {
19
+ root: string;
20
+ skills?: readonly BundledSkillName[];
21
+ force?: boolean;
22
+ }): Promise<{
23
+ root: string;
24
+ current: boolean;
25
+ skills: ({
26
+ action: "unchanged";
27
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
28
+ target: string;
29
+ installed: boolean;
30
+ managed: boolean;
31
+ current: boolean;
32
+ packageVersion: string;
33
+ installedVersion: string | null;
34
+ modified: boolean;
35
+ } | {
36
+ action: "skipped";
37
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
38
+ target: string;
39
+ installed: boolean;
40
+ managed: boolean;
41
+ current: boolean;
42
+ packageVersion: string;
43
+ installedVersion: string | null;
44
+ modified: boolean;
45
+ } | {
46
+ action: "updated" | "installed";
47
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
48
+ target: string;
49
+ installed: boolean;
50
+ managed: boolean;
51
+ current: boolean;
52
+ packageVersion: string;
53
+ installedVersion: string | null;
54
+ modified: boolean;
55
+ })[];
56
+ }>;
57
+ export declare function autoInstallSkills(root: string): Promise<{
58
+ root: string;
59
+ current: boolean;
60
+ skills: ({
61
+ action: "unchanged";
62
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
63
+ target: string;
64
+ installed: boolean;
65
+ managed: boolean;
66
+ current: boolean;
67
+ packageVersion: string;
68
+ installedVersion: string | null;
69
+ modified: boolean;
70
+ } | {
71
+ action: "skipped";
72
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
73
+ target: string;
74
+ installed: boolean;
75
+ managed: boolean;
76
+ current: boolean;
77
+ packageVersion: string;
78
+ installedVersion: string | null;
79
+ modified: boolean;
80
+ } | {
81
+ action: "updated" | "installed";
82
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
83
+ target: string;
84
+ installed: boolean;
85
+ managed: boolean;
86
+ current: boolean;
87
+ packageVersion: string;
88
+ installedVersion: string | null;
89
+ modified: boolean;
90
+ })[];
91
+ }>;
92
+ export declare function skillStatuses(rootInput: string): Promise<{
93
+ root: string;
94
+ current: boolean;
95
+ skills: {
96
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
97
+ target: string;
98
+ installed: boolean;
99
+ managed: boolean;
100
+ current: boolean;
101
+ packageVersion: string;
102
+ installedVersion: string | null;
103
+ modified: boolean;
104
+ }[];
105
+ }>;
13
106
  export declare function installSkill(input: {
14
107
  target: string;
15
108
  force?: boolean;
16
109
  }): Promise<{
110
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
17
111
  target: string;
18
112
  installed: boolean;
19
113
  managed: boolean;
@@ -23,6 +117,7 @@ export declare function installSkill(input: {
23
117
  modified: boolean;
24
118
  }>;
25
119
  export declare function autoInstallSkill(target: string): Promise<{
120
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
26
121
  target: string;
27
122
  installed: boolean;
28
123
  managed: boolean;
@@ -32,6 +127,7 @@ export declare function autoInstallSkill(target: string): Promise<{
32
127
  modified: boolean;
33
128
  action: "unchanged";
34
129
  } | {
130
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
35
131
  target: string;
36
132
  installed: boolean;
37
133
  managed: boolean;
@@ -41,6 +137,7 @@ export declare function autoInstallSkill(target: string): Promise<{
41
137
  modified: boolean;
42
138
  action: "skipped";
43
139
  } | {
140
+ name: "using-vvicat-ai-short-studio-cli" | "short-drama";
44
141
  target: string;
45
142
  installed: boolean;
46
143
  managed: boolean;
@@ -50,3 +147,10 @@ export declare function autoInstallSkill(target: string): Promise<{
50
147
  modified: boolean;
51
148
  action: "updated" | "installed";
52
149
  }>;
150
+ export declare function withLegacySkillFields<T extends {
151
+ root: string;
152
+ current: boolean;
153
+ skills: Array<Record<string, unknown> & {
154
+ current: boolean;
155
+ }>;
156
+ }>(result: T): Record<string, unknown>;
@@ -1,72 +1,112 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
- import { dirname, join, relative } from 'node:path';
4
+ import { basename, dirname, join, relative } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- const BUNDLED_SKILL = fileURLToPath(new URL('../../skills/using-vvicat-ai-short-studio-cli', import.meta.url));
6
+ export const BUNDLED_SKILL_NAMES = ['using-vvicat-ai-short-studio-cli', 'short-drama'];
7
7
  const PACKAGE_JSON = fileURLToPath(new URL('../../package.json', import.meta.url));
8
8
  const MANIFEST = '.vvicat-skill.json';
9
- export function bundledSkillPath() {
10
- return BUNDLED_SKILL;
9
+ export function bundledSkillPath(name = BUNDLED_SKILL_NAMES[0]) {
10
+ return fileURLToPath(new URL(`../../skills/${name}`, import.meta.url));
11
+ }
12
+ export function defaultSkillsRoot(target) {
13
+ return join(homedir(), target === 'codex' ? '.codex' : '.agents', 'skills');
11
14
  }
12
15
  export function defaultSkillTarget(target) {
13
- return join(homedir(), target === 'codex' ? '.codex' : '.agents', 'skills', 'using-vvicat-ai-short-studio-cli');
16
+ return join(defaultSkillsRoot(target), BUNDLED_SKILL_NAMES[0]);
14
17
  }
15
- export async function skillStatus(target) {
18
+ export function normalizeSkillsRoot(path) {
19
+ return BUNDLED_SKILL_NAMES.includes(basename(path)) ? dirname(path) : path;
20
+ }
21
+ async function statusFor(name, target) {
16
22
  const packageVersion = await readPackageVersion();
17
- const bundledChecksum = await directoryChecksum(BUNDLED_SKILL);
23
+ const bundledChecksum = await directoryChecksum(bundledSkillPath(name));
18
24
  const installed = await exists(target);
19
25
  const manifest = installed ? await readManifest(target) : null;
20
26
  const installedChecksum = installed ? await directoryChecksum(target) : null;
21
27
  return {
22
- target,
23
- installed,
24
- managed: Boolean(manifest),
28
+ name, target, installed, managed: Boolean(manifest),
25
29
  current: Boolean(manifest && manifest.packageVersion === packageVersion && installedChecksum === bundledChecksum),
26
- packageVersion,
27
- installedVersion: manifest?.packageVersion ?? null,
30
+ packageVersion, installedVersion: manifest?.packageVersion ?? null,
28
31
  modified: Boolean(manifest && installedChecksum !== manifest.checksum),
29
32
  };
30
33
  }
31
- export async function installSkill(input) {
32
- const current = await skillStatus(input.target);
33
- if (current.installed && !input.force) {
34
- if (!current.managed)
35
- throw new Error('目标目录不是 CLI 管理的 Skill;使用 --force 才能替换');
36
- if (current.modified)
37
- throw new Error('已安装 Skill 已被修改;使用 --force 才能替换');
38
- }
39
- const checksum = await directoryChecksum(BUNDLED_SKILL);
34
+ export async function skillStatus(target) {
35
+ return statusFor(BUNDLED_SKILL_NAMES[0], target);
36
+ }
37
+ async function installOne(name, target) {
38
+ const checksum = await directoryChecksum(bundledSkillPath(name));
40
39
  const packageVersion = await readPackageVersion();
41
- const temporary = `${input.target}.tmp-${process.pid}`;
42
- const backup = `${input.target}.backup-${process.pid}`;
43
- await mkdir(dirname(input.target), { recursive: true });
40
+ const temporary = `${target}.tmp-${process.pid}`;
41
+ const backup = `${target}.backup-${process.pid}`;
42
+ await mkdir(dirname(target), { recursive: true });
44
43
  await rm(temporary, { recursive: true, force: true });
45
- await cp(BUNDLED_SKILL, temporary, { recursive: true });
44
+ await cp(bundledSkillPath(name), temporary, { recursive: true });
46
45
  await writeFile(join(temporary, MANIFEST), `${JSON.stringify({ packageVersion, checksum }, null, 2)}\n`, 'utf8');
47
- const hadTarget = await exists(input.target);
46
+ const hadTarget = await exists(target);
48
47
  try {
49
48
  if (hadTarget)
50
- await rename(input.target, backup);
51
- await rename(temporary, input.target);
49
+ await rename(target, backup);
50
+ await rename(temporary, target);
52
51
  if (hadTarget)
53
52
  await rm(backup, { recursive: true, force: true });
54
53
  }
55
54
  catch (error) {
56
55
  await rm(temporary, { recursive: true, force: true });
57
- if (hadTarget && await exists(backup) && !await exists(input.target))
58
- await rename(backup, input.target);
56
+ if (hadTarget && await exists(backup) && !await exists(target))
57
+ await rename(backup, target);
59
58
  throw error;
60
59
  }
61
- return skillStatus(input.target);
60
+ return statusFor(name, target);
61
+ }
62
+ export async function installSkills(input) {
63
+ const root = normalizeSkillsRoot(input.root);
64
+ const results = [];
65
+ for (const name of input.skills ?? BUNDLED_SKILL_NAMES) {
66
+ const target = join(root, name);
67
+ const current = await statusFor(name, target);
68
+ if (current.current) {
69
+ results.push({ ...current, action: 'unchanged' });
70
+ continue;
71
+ }
72
+ if (current.installed && !input.force && (!current.managed || current.modified)) {
73
+ results.push({ ...current, action: 'skipped' });
74
+ continue;
75
+ }
76
+ results.push({ ...await installOne(name, target), action: current.installed ? 'updated' : 'installed' });
77
+ }
78
+ return { root, current: results.every((item) => item.current), skills: results };
79
+ }
80
+ export function autoInstallSkills(root) {
81
+ return installSkills({ root });
82
+ }
83
+ export async function skillStatuses(rootInput) {
84
+ const root = normalizeSkillsRoot(rootInput);
85
+ const skills = await Promise.all(BUNDLED_SKILL_NAMES.map((name) => statusFor(name, join(root, name))));
86
+ return { root, current: skills.every((item) => item.current), skills };
87
+ }
88
+ export async function installSkill(input) {
89
+ const name = BUNDLED_SKILL_NAMES[0];
90
+ const current = await statusFor(name, input.target);
91
+ if (current.installed && !input.force) {
92
+ if (!current.managed)
93
+ throw new Error('目标目录不是 CLI 管理的 Skill;使用 --force 才能替换');
94
+ if (current.modified)
95
+ throw new Error('已安装 Skill 已被修改;使用 --force 才能替换');
96
+ }
97
+ return installOne(name, input.target);
62
98
  }
63
99
  export async function autoInstallSkill(target) {
64
- const current = await skillStatus(target);
100
+ const name = BUNDLED_SKILL_NAMES[0];
101
+ const current = await statusFor(name, target);
65
102
  if (current.current)
66
103
  return { action: 'unchanged', ...current };
67
104
  if (current.installed && (!current.managed || current.modified))
68
105
  return { action: 'skipped', ...current };
69
- return { action: current.installed ? 'updated' : 'installed', ...await installSkill({ target }) };
106
+ return { action: current.installed ? 'updated' : 'installed', ...await installOne(name, target) };
107
+ }
108
+ export function withLegacySkillFields(result) {
109
+ return { ...result.skills[0], root: result.root, allCurrent: result.current, skills: result.skills };
70
110
  }
71
111
  async function directoryChecksum(root) {
72
112
  const files = await filesBelow(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aswless_854771076/ai_short_studio_cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "VVICAT 无限画布项目命令行工具",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -1,4 +1,4 @@
1
- import { resolve } from 'node:path'
1
+ import { dirname, resolve } from 'node:path'
2
2
  import { fileURLToPath } from 'node:url'
3
3
 
4
4
  const packageRoot = fileURLToPath(new URL('../', import.meta.url))
@@ -11,9 +11,21 @@ export function shouldAutoInstallSkill(environment = process.env) {
11
11
  export async function main(environment = process.env) {
12
12
  if (!shouldAutoInstallSkill(environment)) return
13
13
  try {
14
- const { autoInstallSkill, defaultSkillTarget } = await import('../dist/skill/skill-manager.js')
15
- const result = await autoInstallSkill(environment.VVICAT_SKILL_DIRECTORY || defaultSkillTarget('codex'))
16
- process.stdout.write(`VVICAT Skill ${result.action}: ${result.target}\n`)
14
+ const { autoInstallSkill, autoInstallSkills, defaultSkillsRoot, installSkills } = await import('../dist/skill/skill-manager.js')
15
+ const result = environment.VVICAT_SKILL_DIRECTORY
16
+ ? {
17
+ skills: [
18
+ await autoInstallSkill(environment.VVICAT_SKILL_DIRECTORY),
19
+ ...(await installSkills({
20
+ root: dirname(environment.VVICAT_SKILL_DIRECTORY),
21
+ skills: ['short-drama'],
22
+ })).skills,
23
+ ],
24
+ }
25
+ : await autoInstallSkills(environment.VVICAT_SKILLS_ROOT || defaultSkillsRoot('codex'))
26
+ for (const skill of result.skills) {
27
+ process.stdout.write(`VVICAT Skill ${skill.name} ${skill.action}: ${skill.target}\n`)
28
+ }
17
29
  } catch (error) {
18
30
  process.stderr.write(`VVICAT CLI 已安装,Skill 自动安装已跳过:${error instanceof Error ? error.message : String(error)}\n`)
19
31
  }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 0xsline
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.