@aswless_854771076/ai_short_studio_cli 0.1.32 → 0.1.34

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.
Files changed (30) hide show
  1. package/README.md +7 -2
  2. package/dist/commands/canvas/node/edit-resource.d.ts +15 -0
  3. package/dist/commands/canvas/node/edit-resource.js +45 -0
  4. package/dist/commands/skill/install.d.ts +2 -0
  5. package/dist/commands/skill/install.js +12 -3
  6. package/dist/commands/skill/path.d.ts +4 -0
  7. package/dist/commands/skill/path.js +13 -2
  8. package/dist/commands/skill/status.d.ts +2 -0
  9. package/dist/commands/skill/status.js +13 -3
  10. package/dist/domain/vvicat-api.d.ts +1 -0
  11. package/dist/domain/vvicat-api.js +5 -0
  12. package/dist/skill/command-flags.d.ts +6 -2
  13. package/dist/skill/command-flags.js +9 -4
  14. package/dist/skill/skill-manager.d.ts +105 -1
  15. package/dist/skill/skill-manager.js +73 -33
  16. package/package.json +1 -1
  17. package/scripts/postinstall.mjs +16 -4
  18. package/skills/short-drama/LICENSE +21 -0
  19. package/skills/short-drama/README.md +508 -0
  20. package/skills/short-drama/SKILL.md +525 -0
  21. package/skills/short-drama/references/compliance-checklist.md +295 -0
  22. package/skills/short-drama/references/genre-guide.md +195 -0
  23. package/skills/short-drama/references/hook-design.md +310 -0
  24. package/skills/short-drama/references/opening-rules.md +205 -0
  25. package/skills/short-drama/references/paywall-design.md +220 -0
  26. package/skills/short-drama/references/rhythm-curve.md +263 -0
  27. package/skills/short-drama/references/satisfaction-matrix.md +319 -0
  28. package/skills/short-drama/references/villain-design.md +319 -0
  29. package/skills/using-vvicat-ai-short-studio-cli/SKILL.md +17 -7
  30. package/skills/using-vvicat-ai-short-studio-cli/references/commands.md +9 -6
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
@@ -78,10 +80,12 @@ ai-short-studio canvas settings set --project "$PROJECT_ID" --file settings.json
78
80
  }
79
81
  ```
80
82
 
81
- `null` 或空字符串会清除项目覆盖并恢复继承。设置 `artStyle` 会把当前关联的 `visualBible` 保存为项目快照,画风库后续变化不会反向更新项目;同一请求显式传入 `visualBible` 时以显式值为准。所有者和管理员可写,协作者只读;409 冲突后重新 `settings get` 并携带新 `expectedVersion` 重试。`canvas apply` 仍用于节点、连线和视口等低层画布图补丁;它显式请求完整画布响应,正常保存不预读全图,409 后才读取最新状态并合并重试。Pexels 继续使用 `project provider get|set|test`。
83
+ `null` 或空字符串会清除项目覆盖并恢复继承。设置 `artStyle` 会把当前关联的 `visualBible` 保存为项目快照,画风库后续变化不会反向更新项目;同一请求显式传入 `visualBible` 时以显式值为准。所有者和管理员可写,协作者只读;409 冲突后重新 `settings get` 并携带新 `expectedVersion` 重试。`canvas apply` 仍用于节点、连线、删除和视口等低层画布图补丁;同阶段有多个可合并图变更时默认一次批量提交并统一回读,批量入口不可用或合法批量请求仍因粒度失败时才回退单条命令。Pexels 继续使用 `project provider get|set|test`。
82
84
 
83
85
  使用 `canvas node edit-shot <nodeId> --project <projectId> --file shot.json` 编辑镜头。命令会追加并选中不可变的人工 DOCUMENT 版本,不允许修改 `shotKey`、`shotIndex` 或来源身份,旧版本继续可选。
84
86
 
87
+ 使用 `canvas node edit-resource <nodeId> --project <projectId> --file resource.json` 编辑剧本、人物、人物视觉、场景、子场景或道具结构化资源。命令只替换完整父文档中的目标实体,追加并选中人工版本;名称、资源键、ID 和所属关系不可修改,兄弟资源节点保持原选版。资源版本写入不属于 `canvas apply` 图补丁,409 后需重新读取画布与当前选版。
88
+
85
89
  CLI 通过 `/api/v1/bootstrap` 完成版本、连通性与 Supabase 公共配置预检,并直接使用现有 `/api/projects`、`/api/canvas/node-types`、项目画布与资产路由。
86
90
 
87
91
  `canvas node types [kind] --json` 同时兼容节点目录 schema v1/v2,并原样返回动态节点的 `source`、`definitionId` 和 `definitionVersionId`。后台发布后重新执行该命令即可读取新目录;归档节点不能新建,但已有实例仍按画布中的固定版本 schema 更新和执行。CLI 创建节点只提交 `kind` 与配置,版本由服务端固定,不能由客户端伪造。
@@ -112,9 +116,10 @@ CLI 通过 `/api/v1/bootstrap` 完成版本、连通性与 Supabase 公共配置
112
116
  ai-short-studio skill install --target codex
113
117
  ai-short-studio skill status --target codex
114
118
  ai-short-studio skill update --target codex
119
+ ai-short-studio skill update --target codex --skill short-drama --json
115
120
  ```
116
121
 
117
- 手动恢复或安装到其他 Agent 时,可使用 `--target agents` `--directory <path>`。CLI 会记录版本和 SHA-256;已安装 Skill 被用户修改后,更新默认停止,只有显式 `--force` 才覆盖。自动安装可通过 `VVICAT_SKIP_SKILL_INSTALL=1` 跳过。
122
+ 手动恢复或安装到其他 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` 跳过。
118
123
 
119
124
  ## 发布
120
125
 
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../../base-command.js';
2
+ export default class CanvasNodeEditResource extends BaseCommand {
3
+ static args: {
4
+ nodeId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static flags: {
7
+ project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ locale: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,45 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base-command.js';
3
+ import { commandApi, projectFlag, readJsonInput } from '../../../command-helpers.js';
4
+ export default class CanvasNodeEditResource extends BaseCommand {
5
+ static args = { nodeId: Args.string({ required: true }) };
6
+ static flags = {
7
+ ...BaseCommand.baseFlags,
8
+ project: projectFlag,
9
+ file: Flags.string({ description: '结构化资源 JSON 文件;省略时读取 stdin' }),
10
+ };
11
+ async run() {
12
+ const { args, flags } = await this.parse(CanvasNodeEditResource);
13
+ const { api } = await commandApi(flags);
14
+ const canvas = await api.canvas(flags.project);
15
+ const node = canvas.nodes.find((candidate) => candidate.id === args.nodeId);
16
+ const rawArtifact = node?.data.artifact;
17
+ const artifact = rawArtifact && typeof rawArtifact === 'object' && !Array.isArray(rawArtifact)
18
+ ? rawArtifact
19
+ : null;
20
+ if (!node || !artifact
21
+ || artifact.type !== 'GENERATED_RESOURCE'
22
+ || typeof artifact.operation !== 'string' || !artifact.operation
23
+ || typeof artifact.resourceKey !== 'string' || !artifact.resourceKey
24
+ || typeof artifact.resourceType !== 'string' || !artifact.resourceType) {
25
+ throw new Error(`结构化资源节点不可编辑:${args.nodeId}`);
26
+ }
27
+ let basedOnVersionId = typeof node.data.selectedOutputVersionId === 'string'
28
+ ? node.data.selectedOutputVersionId
29
+ : '';
30
+ if (!basedOnVersionId && typeof node.refId === 'string') {
31
+ const payload = await api.asset(flags.project, node.refId);
32
+ const asset = payload.asset && typeof payload.asset === 'object' && !Array.isArray(payload.asset)
33
+ ? payload.asset
34
+ : {};
35
+ basedOnVersionId = typeof asset.selectedVersionId === 'string' ? asset.selectedVersionId : '';
36
+ }
37
+ if (!basedOnVersionId)
38
+ throw new Error('结构化资源当前版本不存在');
39
+ this.print(await api.updateStructuredResource(flags.project, args.nodeId, {
40
+ expectedCanvasVersion: canvas.version,
41
+ basedOnVersionId,
42
+ resource: await readJsonInput(flags.file),
43
+ }));
44
+ }
45
+ }
@@ -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
  }
@@ -63,6 +63,7 @@ export declare class VvicatApi {
63
63
  }): Promise<JsonObject>;
64
64
  patchCanvas(projectId: string, input: JsonObject): Promise<JsonObject>;
65
65
  updateStoryboardShot(projectId: string, nodeId: string, input: JsonObject): Promise<JsonObject>;
66
+ updateStructuredResource(projectId: string, nodeId: string, input: JsonObject): Promise<JsonObject>;
66
67
  nodeTypes(): Promise<{
67
68
  schemaVersion: 1 | 2;
68
69
  catalogRevision?: string;
@@ -121,6 +121,11 @@ export class VvicatApi {
121
121
  method: 'PATCH', body: input,
122
122
  });
123
123
  }
124
+ updateStructuredResource(projectId, nodeId, input) {
125
+ return this.client.request(`/api/projects/${projectId}/canvas/nodes/${nodeId}/structured-resource`, {
126
+ method: 'PATCH', body: input,
127
+ });
128
+ }
124
129
  async nodeTypes() {
125
130
  const payload = await this.client.request('/api/canvas/node-types');
126
131
  if (payload.data.schemaVersion !== 1 && payload.data.schemaVersion !== 2) {
@@ -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.32",
3
+ "version": "0.1.34",
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.