@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.
- package/README.md +7 -2
- package/dist/commands/canvas/node/edit-resource.d.ts +15 -0
- package/dist/commands/canvas/node/edit-resource.js +45 -0
- package/dist/commands/skill/install.d.ts +2 -0
- package/dist/commands/skill/install.js +12 -3
- package/dist/commands/skill/path.d.ts +4 -0
- package/dist/commands/skill/path.js +13 -2
- package/dist/commands/skill/status.d.ts +2 -0
- package/dist/commands/skill/status.js +13 -3
- package/dist/domain/vvicat-api.d.ts +1 -0
- package/dist/domain/vvicat-api.js +5 -0
- package/dist/skill/command-flags.d.ts +6 -2
- package/dist/skill/command-flags.js +9 -4
- package/dist/skill/skill-manager.d.ts +105 -1
- package/dist/skill/skill-manager.js +73 -33
- package/package.json +1 -1
- package/scripts/postinstall.mjs +16 -4
- package/skills/short-drama/LICENSE +21 -0
- package/skills/short-drama/README.md +508 -0
- package/skills/short-drama/SKILL.md +525 -0
- package/skills/short-drama/references/compliance-checklist.md +295 -0
- package/skills/short-drama/references/genre-guide.md +195 -0
- package/skills/short-drama/references/hook-design.md +310 -0
- package/skills/short-drama/references/opening-rules.md +205 -0
- package/skills/short-drama/references/paywall-design.md +220 -0
- package/skills/short-drama/references/rhythm-curve.md +263 -0
- package/skills/short-drama/references/satisfaction-matrix.md +319 -0
- package/skills/short-drama/references/villain-design.md +319 -0
- package/skills/using-vvicat-ai-short-studio-cli/SKILL.md +17 -7
- 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`
|
|
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
|
|
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 {
|
|
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() {
|
|
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() {
|
|
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 {
|
|
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() {
|
|
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
|
|
8
|
+
export declare function resolveSkillsRoot(flags: {
|
|
6
9
|
target: string;
|
|
7
|
-
|
|
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 {
|
|
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
|
|
8
|
-
return flags.
|
|
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
|
|
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
|
|
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(
|
|
16
|
+
return join(defaultSkillsRoot(target), BUNDLED_SKILL_NAMES[0]);
|
|
14
17
|
}
|
|
15
|
-
export
|
|
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(
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 = `${
|
|
42
|
-
const backup = `${
|
|
43
|
-
await mkdir(dirname(
|
|
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(
|
|
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(
|
|
46
|
+
const hadTarget = await exists(target);
|
|
48
47
|
try {
|
|
49
48
|
if (hadTarget)
|
|
50
|
-
await rename(
|
|
51
|
-
await rename(temporary,
|
|
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(
|
|
58
|
-
await rename(backup,
|
|
56
|
+
if (hadTarget && await exists(backup) && !await exists(target))
|
|
57
|
+
await rename(backup, target);
|
|
59
58
|
throw error;
|
|
60
59
|
}
|
|
61
|
-
return
|
|
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
|
|
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
|
|
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
package/scripts/postinstall.mjs
CHANGED
|
@@ -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,
|
|
15
|
-
const result =
|
|
16
|
-
|
|
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.
|