ae-agent-skills 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CSXS/manifest.xml +2 -2
- package/README.ja.md +45 -13
- package/README.md +48 -16
- package/bin/ae-agent-setup-lib.mjs +453 -0
- package/bin/ae-agent-setup.mjs +1 -362
- package/client/index.html +1 -1
- package/client/lib/runtime.js +25 -0
- package/docs/development.ja.md +91 -1
- package/docs/development.md +9 -1
- package/package.json +7 -1
- package/scripts/signing/build-zxp-interactive.sh +30 -0
- package/templates/claude/commands/aftereffects-cli.md +7 -0
- package/templates/claude/commands/aftereffects-declarative.md +7 -0
- package/templates/skills/aftereffects-cli.SKILL.md +3 -3
- package/templates/skills/aftereffects-declarative.SKILL.md +2 -2
package/CSXS/manifest.xml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.yumehiko.aeagentskill" ExtensionBundleVersion="0.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.yumehiko.aeagentskill" ExtensionBundleVersion="0.3.0" ExtensionBundleName="ae-agent-skill" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.yumehiko.aeagentskill.panel" Version="0.
|
|
4
|
+
<Extension Id="com.yumehiko.aeagentskill.panel" Version="0.3.0" />
|
|
5
5
|
</ExtensionList>
|
|
6
6
|
<ExecutionEnvironment>
|
|
7
7
|
<HostList>
|
package/README.ja.md
CHANGED
|
@@ -1,35 +1,67 @@
|
|
|
1
1
|
# ae-agent-skills
|
|
2
2
|
|
|
3
|
-
`ae-agent-
|
|
3
|
+
`ae-agent-skills`は、コーディングエージェント(Codex / Gemini / Claude Code)からAdobe After Effectsを操作できるようにするためのリポジトリです。
|
|
4
|
+
`ae-agent-skill`(After Effects CEP パネル)、`ae-cli`、エージェント向け skill を導入し、コンポジション作成や既存シーン編集をエージェントに依頼できるようになります。
|
|
4
5
|
|
|
5
6
|
English README: [README.md](README.md)
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## クイックスタート
|
|
9
|
+
|
|
10
|
+
1. インストールする。
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
13
|
npx ae-agent-skills install
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
2. After Effects を再起動し、`ウィンドウ > 機能拡張 (ベータ) > ae-agent-skill` を開く。
|
|
17
|
+
3. Codex、Gemini、または Claude Code を起動し、After Effects操作を依頼する。
|
|
18
|
+
|
|
19
|
+
最初の依頼例(コンポジション作成):
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
$aftereffects-declarative を使って、1920x1080 / 30fps / 5秒のコンポジションを作成して。
|
|
23
|
+
背景はダークグレー、中央に「Hello AE Agent」のテキストを配置し、
|
|
24
|
+
0.5秒でフェードインするアニメーションを追加して。
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Claude Code では slash command として呼び出します。
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
/aftereffects-declarative を使って、1920x1080 / 30fps / 5秒のコンポジションを作成して。
|
|
31
|
+
背景はダークグレー、中央に「Hello AE Agent」のテキストを配置し、
|
|
32
|
+
0.5秒でフェードインするアニメーションを追加して。
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. エージェントの処理完了後、After Effects 上でコンポジションが作成されたことを確認する。
|
|
36
|
+
|
|
37
|
+
エージェントを明示してインストールする場合:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx ae-agent-skills install --agent codex
|
|
41
|
+
npx ae-agent-skills install --agent gemini
|
|
42
|
+
npx ae-agent-skills install --agent claude
|
|
43
|
+
npx ae-agent-skills install --agent all
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## インストール時に行われること
|
|
14
47
|
|
|
15
|
-
1. `--agent` 未指定なら、`codex` / `gemini` / `both` の選択を表示します。
|
|
48
|
+
1. `--agent` 未指定なら、`codex` / `gemini` / `claude` / `both` / `all` の選択を表示します。
|
|
16
49
|
2. 署名済み ZXP を `UPIA` または `ExManCmd` でインストールします。
|
|
17
50
|
3. `ae-cli` と agent skill をインストールします。
|
|
18
51
|
4. `~/ae-agent-skills/` を初期化し、`work/`・`done/`・`scene.schema.json`・`references/` を配置します。
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
`both` は後方互換のため Codex + Gemini、`all` は Codex + Gemini + Claude Code です。
|
|
54
|
+
Claude Code の個人 skill は `~/.claude/skills/<skill-name>/SKILL.md` にインストールされます。
|
|
55
|
+
Claude Code 用の slash command は `~/.claude/commands/<command>.md` にインストールされます。
|
|
21
56
|
|
|
22
|
-
|
|
57
|
+
## どの skill を使うか
|
|
23
58
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
3. コーディングエージェントで以下の skill を呼び出す。
|
|
27
|
-
- `$aftereffects-declarative`(通常の構成作成向け)
|
|
28
|
-
- `$aftereffects-cli`(個別編集・デバッグ向け)
|
|
59
|
+
- `$aftereffects-declarative`: 新規コンポジション作成や全体構成を作るとき(通常はこちら)
|
|
60
|
+
- `$aftereffects-cli`: 既存シーンの一部修正、プロパティ単位の調整、デバッグ
|
|
29
61
|
|
|
30
62
|
## ドキュメント
|
|
31
63
|
|
|
32
64
|
- CLI利用方法: [docs/cli.ja.md](docs/cli.ja.md)
|
|
33
65
|
- 開発者向け情報: [docs/development.ja.md](docs/development.ja.md)
|
|
34
|
-
- 宣言型 skill 本体: [
|
|
35
|
-
-
|
|
66
|
+
- 宣言型 skill 本体: [templates/skills/aftereffects-declarative.SKILL.md](templates/skills/aftereffects-declarative.SKILL.md)
|
|
67
|
+
- CLI skill 本体: [templates/skills/aftereffects-cli.SKILL.md](templates/skills/aftereffects-cli.SKILL.md)
|
package/README.md
CHANGED
|
@@ -1,35 +1,67 @@
|
|
|
1
1
|
# ae-agent-skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`ae-agent-skills` enables coding agents (Codex / Gemini / Claude Code) to operate Adobe After Effects.
|
|
4
|
+
It installs `ae-agent-skill` (After Effects CEP panel), `ae-cli`, and agent skills so you can ask your agent to create compositions or edit existing scenes.
|
|
4
5
|
|
|
5
6
|
Japanese README: [README.ja.md](README.ja.md)
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
1. Install:
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
13
|
npx ae-agent-skills install
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
2. Restart After Effects, then open `Window > Extensions (Beta) > ae-agent-skill`.
|
|
17
|
+
3. Launch Codex, Gemini, or Claude Code and ask it to perform an After Effects task.
|
|
18
|
+
|
|
19
|
+
First request example (create a composition):
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
Use $aftereffects-declarative to create a 1920x1080, 30fps, 5-second composition.
|
|
23
|
+
Set a dark gray background, place "Hello AE Agent" text at the center,
|
|
24
|
+
and add a 0.5-second fade-in animation.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
In Claude Code, invoke the skill as a slash command:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Use /aftereffects-declarative to create a 1920x1080, 30fps, 5-second composition.
|
|
31
|
+
Set a dark gray background, place "Hello AE Agent" text at the center,
|
|
32
|
+
and add a 0.5-second fade-in animation.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. After the agent finishes, confirm the composition is created in After Effects.
|
|
36
|
+
|
|
37
|
+
To choose an agent explicitly:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx ae-agent-skills install --agent codex
|
|
41
|
+
npx ae-agent-skills install --agent gemini
|
|
42
|
+
npx ae-agent-skills install --agent claude
|
|
43
|
+
npx ae-agent-skills install --agent all
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What the installer does
|
|
14
47
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
48
|
+
1. Prompts for target agent (`codex` / `gemini` / `claude` / `both` / `all`) when `--agent` is omitted.
|
|
49
|
+
2. Installs the signed ZXP extension via `UPIA` or `ExManCmd`.
|
|
50
|
+
3. Installs `ae-cli` and agent skills.
|
|
51
|
+
4. Initializes `~/ae-agent-skills/` with `work/`, `done/`, `scene.schema.json`, and `references/`.
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
`both` means Codex + Gemini for backward compatibility. `all` means Codex + Gemini + Claude Code.
|
|
54
|
+
Claude Code user skills are installed under `~/.claude/skills/<skill-name>/SKILL.md`.
|
|
55
|
+
Claude Code slash commands are installed under `~/.claude/commands/<command>.md`.
|
|
21
56
|
|
|
22
|
-
|
|
57
|
+
## Which skill to use
|
|
23
58
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
3. Run your coding agent and call one of these skills:
|
|
27
|
-
- `$aftereffects-declarative` for normal composition building.
|
|
28
|
-
- `$aftereffects-cli` for command-by-command edits/debug.
|
|
59
|
+
- `$aftereffects-declarative`: default for new composition building and overall scene structure
|
|
60
|
+
- `$aftereffects-cli`: best for surgical edits, property-level tweaks, and debugging
|
|
29
61
|
|
|
30
62
|
## Docs
|
|
31
63
|
|
|
32
64
|
- CLI usage: [docs/cli.md](docs/cli.md)
|
|
33
65
|
- Development: [docs/development.md](docs/development.md)
|
|
34
|
-
- Declarative skill source: [
|
|
35
|
-
-
|
|
66
|
+
- Declarative skill source: [templates/skills/aftereffects-declarative.SKILL.md](templates/skills/aftereffects-declarative.SKILL.md)
|
|
67
|
+
- CLI skill source: [templates/skills/aftereffects-cli.SKILL.md](templates/skills/aftereffects-cli.SKILL.md)
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import readline from 'node:readline';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_REPO = 'yumehiko/ae-agent-skills';
|
|
9
|
+
export const AGENT_WORKSPACE_NAME = 'ae-agent-skills';
|
|
10
|
+
|
|
11
|
+
export const SKILL_SOURCES = [
|
|
12
|
+
{ sourceName: 'aftereffects-cli.SKILL.md', destinationName: 'aftereffects-cli' },
|
|
13
|
+
{ sourceName: 'aftereffects-declarative.SKILL.md', destinationName: 'aftereffects-declarative' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const CLAUDE_COMMAND_SOURCES = [
|
|
17
|
+
{ sourceName: 'aftereffects-cli.md', destinationName: 'aftereffects-cli.md' },
|
|
18
|
+
{ sourceName: 'aftereffects-declarative.md', destinationName: 'aftereffects-declarative.md' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const WORKSPACE_RESOURCE_SOURCES = [
|
|
22
|
+
{ source: ['schemas', 'scene.schema.json'], destination: ['scene.schema.json'] },
|
|
23
|
+
{ source: ['examples', 'scene.example.json'], destination: ['references', 'scene.example.json'] },
|
|
24
|
+
{ source: ['docs', 'cli.ja.md'], destination: ['references', 'cli.ja.md'] },
|
|
25
|
+
{ source: ['docs', 'cli.md'], destination: ['references', 'cli.md'] },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const AGENT_REGISTRY = [
|
|
29
|
+
{
|
|
30
|
+
id: 'codex',
|
|
31
|
+
destRoot({ home, env }) {
|
|
32
|
+
const codexHome = env.CODEX_HOME || path.join(home, '.codex');
|
|
33
|
+
return path.join(codexHome, 'skills');
|
|
34
|
+
},
|
|
35
|
+
isDetected({ home, env, fsModule }) {
|
|
36
|
+
return Boolean(env.CODEX_HOME) || fsModule.existsSync(path.join(home, '.codex'));
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'gemini',
|
|
41
|
+
destRoot({ home }) {
|
|
42
|
+
return path.join(home, '.gemini', 'skills');
|
|
43
|
+
},
|
|
44
|
+
isDetected({ home, fsModule }) {
|
|
45
|
+
return fsModule.existsSync(path.join(home, '.gemini'));
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'claude',
|
|
50
|
+
destRoot({ home }) {
|
|
51
|
+
return path.join(home, '.claude', 'skills');
|
|
52
|
+
},
|
|
53
|
+
commandsRoot({ home }) {
|
|
54
|
+
return path.join(home, '.claude', 'commands');
|
|
55
|
+
},
|
|
56
|
+
isDetected({ home, fsModule }) {
|
|
57
|
+
return fsModule.existsSync(path.join(home, '.claude'));
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const AGENT_ALIASES = {
|
|
63
|
+
both: ['codex', 'gemini'],
|
|
64
|
+
all: AGENT_REGISTRY.map((agent) => agent.id),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function getRepoRoot() {
|
|
68
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function printHelp(output = console.log) {
|
|
72
|
+
output(`ae-agent-setup
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
ae-agent-setup install [options]
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
--agent <codex|gemini|claude|both|all>
|
|
79
|
+
Agent target. If omitted, interactive selection is shown.
|
|
80
|
+
"both" means codex+gemini for backward compatibility.
|
|
81
|
+
"all" means codex+gemini+claude.
|
|
82
|
+
--repo <owner/repo> GitHub repository for release and CLI install (default: ${DEFAULT_REPO}).
|
|
83
|
+
--zxp <path-or-url> Local ZXP path or direct download URL.
|
|
84
|
+
--skip-extension Skip ZXP extension installation.
|
|
85
|
+
--skip-cli Skip ae-cli installation.
|
|
86
|
+
--skip-skills Skip skill installation.
|
|
87
|
+
-h, --help Show this help.
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function parseArgs(argv) {
|
|
92
|
+
const opts = {
|
|
93
|
+
agent: '',
|
|
94
|
+
repo: DEFAULT_REPO,
|
|
95
|
+
zxp: '',
|
|
96
|
+
skipExtension: false,
|
|
97
|
+
skipCli: false,
|
|
98
|
+
skipSkills: false,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
102
|
+
const arg = argv[i];
|
|
103
|
+
if (arg === '--agent') {
|
|
104
|
+
opts.agent = argv[i + 1] || '';
|
|
105
|
+
i += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (arg === '--repo') {
|
|
109
|
+
opts.repo = argv[i + 1] || '';
|
|
110
|
+
i += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg === '--zxp') {
|
|
114
|
+
opts.zxp = argv[i + 1] || '';
|
|
115
|
+
i += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (arg === '--skip-extension') {
|
|
119
|
+
opts.skipExtension = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (arg === '--skip-cli') {
|
|
123
|
+
opts.skipCli = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (arg === '--skip-skills') {
|
|
127
|
+
opts.skipSkills = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (arg === '-h' || arg === '--help') {
|
|
131
|
+
opts.help = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return opts;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function expandAgentTarget(agentArg) {
|
|
141
|
+
const value = String(agentArg || '').toLowerCase();
|
|
142
|
+
const direct = AGENT_REGISTRY.find((agent) => agent.id === value);
|
|
143
|
+
if (direct) return [direct.id];
|
|
144
|
+
if (AGENT_ALIASES[value]) return [...AGENT_ALIASES[value]];
|
|
145
|
+
throw new Error(`Invalid --agent value: ${agentArg}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getDetectedAgents({ home = os.homedir(), env = process.env, fsModule = fs } = {}) {
|
|
149
|
+
return AGENT_REGISTRY
|
|
150
|
+
.filter((agent) => agent.isDetected({ home, env, fsModule }))
|
|
151
|
+
.map((agent) => agent.id);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function getSkillInstallTargets(
|
|
155
|
+
agentArg,
|
|
156
|
+
{ home = os.homedir(), env = process.env } = {},
|
|
157
|
+
) {
|
|
158
|
+
return expandAgentTarget(agentArg).map((agentId) => {
|
|
159
|
+
const agent = AGENT_REGISTRY.find((entry) => entry.id === agentId);
|
|
160
|
+
return {
|
|
161
|
+
kind: agent.id,
|
|
162
|
+
destRoot: agent.destRoot({ home, env }),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getClaudeCommandInstallTarget({ home = os.homedir() } = {}) {
|
|
168
|
+
const claude = AGENT_REGISTRY.find((entry) => entry.id === 'claude');
|
|
169
|
+
return claude.commandsRoot({ home });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ask(question) {
|
|
173
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
rl.question(question, (answer) => {
|
|
176
|
+
rl.close();
|
|
177
|
+
resolve(answer.trim());
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function resolveAgent(agentArg) {
|
|
183
|
+
if (agentArg) {
|
|
184
|
+
expandAgentTarget(agentArg);
|
|
185
|
+
return agentArg.toLowerCase();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const detected = getDetectedAgents();
|
|
189
|
+
const recommended = detected[0] || 'codex';
|
|
190
|
+
console.log('Select which coding agent should receive skills:');
|
|
191
|
+
console.log(` 1) codex${recommended === 'codex' ? ' (recommended)' : ''}`);
|
|
192
|
+
console.log(` 2) gemini${recommended === 'gemini' ? ' (recommended)' : ''}`);
|
|
193
|
+
console.log(` 3) claude${recommended === 'claude' ? ' (recommended)' : ''}`);
|
|
194
|
+
console.log(' 4) both (codex + gemini)');
|
|
195
|
+
console.log(' 5) all (codex + gemini + claude)');
|
|
196
|
+
|
|
197
|
+
if (!process.stdin.isTTY) {
|
|
198
|
+
console.log(`No interactive terminal detected. Defaulting to: ${recommended}`);
|
|
199
|
+
return recommended;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const answer = await ask('Enter 1, 2, 3, 4, or 5 [1]: ');
|
|
203
|
+
if (!answer || answer === '1') return 'codex';
|
|
204
|
+
if (answer === '2') return 'gemini';
|
|
205
|
+
if (answer === '3') return 'claude';
|
|
206
|
+
if (answer === '4') return 'both';
|
|
207
|
+
if (answer === '5') return 'all';
|
|
208
|
+
throw new Error(`Invalid selection: ${answer}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function run(cmd, args, options = {}) {
|
|
212
|
+
const pretty = `${cmd} ${args.join(' ')}`.trim();
|
|
213
|
+
console.log(`$ ${pretty}`);
|
|
214
|
+
const res = spawnSync(cmd, args, { stdio: 'inherit', ...options });
|
|
215
|
+
if (res.error) {
|
|
216
|
+
throw new Error(`Command failed: ${pretty}\n${res.error.message}`);
|
|
217
|
+
}
|
|
218
|
+
if (res.status !== 0) {
|
|
219
|
+
throw new Error(`Command failed with exit code ${res.status}: ${pretty}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function commandExists(cmd) {
|
|
224
|
+
const res = spawnSync('which', [cmd], { stdio: 'pipe', encoding: 'utf8' });
|
|
225
|
+
return res.status === 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function detectInstaller() {
|
|
229
|
+
if (commandExists('upia')) return { cmd: 'upia', args: ['--install'] };
|
|
230
|
+
if (commandExists('UPIA')) return { cmd: 'UPIA', args: ['--install'] };
|
|
231
|
+
|
|
232
|
+
const knownUpiaPaths = [
|
|
233
|
+
'/Library/Application Support/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.app/Contents/MacOS/UnifiedPluginInstallerAgent',
|
|
234
|
+
'/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/UPIA/UnifiedPluginInstallerAgent',
|
|
235
|
+
];
|
|
236
|
+
for (const knownUpiaPath of knownUpiaPaths) {
|
|
237
|
+
if (fs.existsSync(knownUpiaPath)) return { cmd: knownUpiaPath, args: ['--install'] };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (commandExists('ExManCmd')) return { cmd: 'ExManCmd', args: ['--install'] };
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function fetchJson(url) {
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
const req = fetch(url, {
|
|
247
|
+
headers: {
|
|
248
|
+
Accept: 'application/vnd.github+json',
|
|
249
|
+
'User-Agent': 'ae-agent-setup',
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
req.then(async (res) => {
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
reject(new Error(`Request failed: ${res.status} ${res.statusText}`));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const body = await res.json();
|
|
259
|
+
resolve(body);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
reject(err);
|
|
262
|
+
}
|
|
263
|
+
}).catch(reject);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function downloadToFile(url, destination) {
|
|
268
|
+
return fetch(url, {
|
|
269
|
+
headers: {
|
|
270
|
+
Accept: 'application/octet-stream',
|
|
271
|
+
'User-Agent': 'ae-agent-setup',
|
|
272
|
+
},
|
|
273
|
+
}).then(async (res) => {
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
276
|
+
}
|
|
277
|
+
const data = Buffer.from(await res.arrayBuffer());
|
|
278
|
+
fs.writeFileSync(destination, data);
|
|
279
|
+
return destination;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function resolveZxpPath(opts) {
|
|
284
|
+
if (opts.zxp) {
|
|
285
|
+
if (opts.zxp.startsWith('http://') || opts.zxp.startsWith('https://')) {
|
|
286
|
+
const output = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ae-agent-zxp-')), 'extension.zxp');
|
|
287
|
+
console.log(`Downloading ZXP from URL: ${opts.zxp}`);
|
|
288
|
+
await downloadToFile(opts.zxp, output);
|
|
289
|
+
return output;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const local = path.resolve(process.cwd(), opts.zxp);
|
|
293
|
+
if (!fs.existsSync(local)) {
|
|
294
|
+
throw new Error(`ZXP not found: ${local}`);
|
|
295
|
+
}
|
|
296
|
+
return local;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(`Fetching latest release from GitHub: ${opts.repo}`);
|
|
300
|
+
const release = await fetchJson(`https://api.github.com/repos/${opts.repo}/releases/latest`);
|
|
301
|
+
const asset = (release.assets || []).find((a) => typeof a.name === 'string' && a.name.endsWith('.zxp'));
|
|
302
|
+
if (!asset) {
|
|
303
|
+
throw new Error(`No .zxp asset found in latest release of ${opts.repo}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const output = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ae-agent-zxp-')), asset.name);
|
|
307
|
+
console.log(`Downloading asset: ${asset.name}`);
|
|
308
|
+
await downloadToFile(asset.browser_download_url, output);
|
|
309
|
+
return output;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function installSkills(
|
|
313
|
+
agent,
|
|
314
|
+
{ root = getRepoRoot(), home = os.homedir(), env = process.env } = {},
|
|
315
|
+
) {
|
|
316
|
+
const sourceRoot = path.join(root, 'templates', 'skills');
|
|
317
|
+
const targets = getSkillInstallTargets(agent, { home, env });
|
|
318
|
+
|
|
319
|
+
for (const target of targets) {
|
|
320
|
+
fs.mkdirSync(target.destRoot, { recursive: true });
|
|
321
|
+
for (const skill of SKILL_SOURCES) {
|
|
322
|
+
const source = path.join(sourceRoot, skill.sourceName);
|
|
323
|
+
const destination = path.join(target.destRoot, skill.destinationName);
|
|
324
|
+
if (!fs.existsSync(source)) {
|
|
325
|
+
throw new Error(`Skill source not found: ${source}`);
|
|
326
|
+
}
|
|
327
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
328
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
329
|
+
fs.copyFileSync(source, path.join(destination, 'SKILL.md'));
|
|
330
|
+
console.log(`Installed ${target.kind} skill: ${destination}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (expandAgentTarget(agent).includes('claude')) {
|
|
335
|
+
installClaudeCommands({ root, home });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function installClaudeCommands({ root = getRepoRoot(), home = os.homedir() } = {}) {
|
|
340
|
+
const sourceRoot = path.join(root, 'templates', 'claude', 'commands');
|
|
341
|
+
const destRoot = getClaudeCommandInstallTarget({ home });
|
|
342
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
343
|
+
|
|
344
|
+
for (const command of CLAUDE_COMMAND_SOURCES) {
|
|
345
|
+
const source = path.join(sourceRoot, command.sourceName);
|
|
346
|
+
const destination = path.join(destRoot, command.destinationName);
|
|
347
|
+
if (!fs.existsSync(source)) {
|
|
348
|
+
throw new Error(`Claude command source not found: ${source}`);
|
|
349
|
+
}
|
|
350
|
+
fs.copyFileSync(source, destination);
|
|
351
|
+
console.log(`Installed claude command: ${destination}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function setupAgentWorkspace({ root = getRepoRoot(), home = os.homedir() } = {}) {
|
|
356
|
+
const workspaceRoot = path.join(home, AGENT_WORKSPACE_NAME);
|
|
357
|
+
const workDir = path.join(workspaceRoot, 'work');
|
|
358
|
+
const doneDir = path.join(workspaceRoot, 'done');
|
|
359
|
+
const refsDir = path.join(workspaceRoot, 'references');
|
|
360
|
+
|
|
361
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
362
|
+
fs.mkdirSync(doneDir, { recursive: true });
|
|
363
|
+
fs.mkdirSync(refsDir, { recursive: true });
|
|
364
|
+
|
|
365
|
+
for (const resource of WORKSPACE_RESOURCE_SOURCES) {
|
|
366
|
+
const source = path.join(root, ...resource.source);
|
|
367
|
+
const destination = path.join(workspaceRoot, ...resource.destination);
|
|
368
|
+
if (!fs.existsSync(source)) {
|
|
369
|
+
throw new Error(`Workspace resource not found: ${source}`);
|
|
370
|
+
}
|
|
371
|
+
fs.copyFileSync(source, destination);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
console.log(`Prepared workspace: ${workspaceRoot}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function installCli(repo) {
|
|
378
|
+
if (!commandExists('python3')) {
|
|
379
|
+
throw new Error('python3 is required to install ae-cli.');
|
|
380
|
+
}
|
|
381
|
+
const spec = `git+https://github.com/${repo}.git`;
|
|
382
|
+
run('python3', ['-m', 'pip', 'install', '--user', '--upgrade', spec]);
|
|
383
|
+
|
|
384
|
+
const helpRes = spawnSync('ae-cli', ['--help'], { stdio: 'inherit' });
|
|
385
|
+
if (helpRes.status !== 0) {
|
|
386
|
+
console.log('ae-cli was installed, but not found on PATH.');
|
|
387
|
+
console.log('Run this once in your shell profile if needed:');
|
|
388
|
+
console.log(' export PATH="$HOME/Library/Python/3.*/bin:$PATH"');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export async function installCommand(opts) {
|
|
393
|
+
console.log('Starting ae-agent setup...');
|
|
394
|
+
setupAgentWorkspace();
|
|
395
|
+
|
|
396
|
+
const agent = await resolveAgent(opts.agent);
|
|
397
|
+
console.log(`Selected agent target: ${agent}`);
|
|
398
|
+
|
|
399
|
+
if (!opts.skipExtension) {
|
|
400
|
+
const installer = detectInstaller();
|
|
401
|
+
if (!installer) {
|
|
402
|
+
throw new Error('Neither UPIA nor ExManCmd was found. Install one of them and run again.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const zxpPath = await resolveZxpPath(opts);
|
|
406
|
+
console.log(`Installing extension via ${installer.cmd}...`);
|
|
407
|
+
run(installer.cmd, [...installer.args, zxpPath]);
|
|
408
|
+
} else {
|
|
409
|
+
console.log('Skipping extension installation (--skip-extension).');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!opts.skipCli) {
|
|
413
|
+
console.log('Installing ae-cli...');
|
|
414
|
+
installCli(opts.repo);
|
|
415
|
+
} else {
|
|
416
|
+
console.log('Skipping ae-cli installation (--skip-cli).');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!opts.skipSkills) {
|
|
420
|
+
console.log('Installing agent skills...');
|
|
421
|
+
installSkills(agent);
|
|
422
|
+
} else {
|
|
423
|
+
console.log('Skipping skill installation (--skip-skills).');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
console.log('Setup finished.');
|
|
427
|
+
console.log('Next actions:');
|
|
428
|
+
console.log(' 1) Fully restart After Effects.');
|
|
429
|
+
console.log(' 2) Open the panel: Window > Extensions (Beta) > ae-agent-skill.');
|
|
430
|
+
console.log(' 3) Run: ae-cli health');
|
|
431
|
+
console.log(` 4) Use workspace: ${path.join(os.homedir(), AGENT_WORKSPACE_NAME)}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
435
|
+
const [command, ...rest] = argv;
|
|
436
|
+
|
|
437
|
+
if (!command || command === '-h' || command === '--help') {
|
|
438
|
+
printHelp();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (command !== 'install') {
|
|
443
|
+
throw new Error(`Unknown command: ${command}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const opts = parseArgs(rest);
|
|
447
|
+
if (opts.help) {
|
|
448
|
+
printHelp();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
await installCommand(opts);
|
|
453
|
+
}
|
package/bin/ae-agent-setup.mjs
CHANGED
|
@@ -1,367 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import readline from 'node:readline';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
|
|
10
|
-
const DEFAULT_REPO = 'yumehiko/ae-agent-skills';
|
|
11
|
-
const AGENT_WORKSPACE_NAME = 'ae-agent-skills';
|
|
12
|
-
const SKILL_SOURCES = [
|
|
13
|
-
{ sourceName: 'aftereffects-cli.SKILL.md', destinationName: 'aftereffects-cli' },
|
|
14
|
-
{ sourceName: 'aftereffects-declarative.SKILL.md', destinationName: 'aftereffects-declarative' },
|
|
15
|
-
];
|
|
16
|
-
const WORKSPACE_RESOURCE_SOURCES = [
|
|
17
|
-
{ source: ['schemas', 'scene.schema.json'], destination: ['scene.schema.json'] },
|
|
18
|
-
{ source: ['examples', 'scene.example.json'], destination: ['references', 'scene.example.json'] },
|
|
19
|
-
{ source: ['docs', 'cli.ja.md'], destination: ['references', 'cli.ja.md'] },
|
|
20
|
-
{ source: ['docs', 'cli.md'], destination: ['references', 'cli.md'] },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
function printHelp() {
|
|
24
|
-
console.log(`ae-agent-setup
|
|
25
|
-
|
|
26
|
-
Usage:
|
|
27
|
-
ae-agent-setup install [options]
|
|
28
|
-
|
|
29
|
-
Options:
|
|
30
|
-
--agent <codex|gemini|both> Agent target. If omitted, interactive selection is shown.
|
|
31
|
-
--repo <owner/repo> GitHub repository for release and CLI install (default: ${DEFAULT_REPO}).
|
|
32
|
-
--zxp <path-or-url> Local ZXP path or direct download URL.
|
|
33
|
-
--skip-extension Skip ZXP extension installation.
|
|
34
|
-
--skip-cli Skip ae-cli installation.
|
|
35
|
-
--skip-skills Skip skill installation.
|
|
36
|
-
-h, --help Show this help.
|
|
37
|
-
`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function run(cmd, args, options = {}) {
|
|
41
|
-
const pretty = `${cmd} ${args.join(' ')}`.trim();
|
|
42
|
-
console.log(`$ ${pretty}`);
|
|
43
|
-
const res = spawnSync(cmd, args, { stdio: 'inherit', ...options });
|
|
44
|
-
if (res.error) {
|
|
45
|
-
throw new Error(`Command failed: ${pretty}\n${res.error.message}`);
|
|
46
|
-
}
|
|
47
|
-
if (res.status !== 0) {
|
|
48
|
-
throw new Error(`Command failed with exit code ${res.status}: ${pretty}`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function commandExists(cmd) {
|
|
53
|
-
const res = spawnSync('which', [cmd], { stdio: 'pipe', encoding: 'utf8' });
|
|
54
|
-
return res.status === 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function detectInstaller() {
|
|
58
|
-
if (commandExists('upia')) return { cmd: 'upia', args: ['--install'] };
|
|
59
|
-
if (commandExists('UPIA')) return { cmd: 'UPIA', args: ['--install'] };
|
|
60
|
-
|
|
61
|
-
const knownUpiaPaths = [
|
|
62
|
-
'/Library/Application Support/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.app/Contents/MacOS/UnifiedPluginInstallerAgent',
|
|
63
|
-
'/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/UPIA/UnifiedPluginInstallerAgent',
|
|
64
|
-
];
|
|
65
|
-
for (const knownUpiaPath of knownUpiaPaths) {
|
|
66
|
-
if (fs.existsSync(knownUpiaPath)) return { cmd: knownUpiaPath, args: ['--install'] };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (commandExists('ExManCmd')) return { cmd: 'ExManCmd', args: ['--install'] };
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function parseArgs(argv) {
|
|
74
|
-
const opts = {
|
|
75
|
-
agent: '',
|
|
76
|
-
repo: DEFAULT_REPO,
|
|
77
|
-
zxp: '',
|
|
78
|
-
skipExtension: false,
|
|
79
|
-
skipCli: false,
|
|
80
|
-
skipSkills: false,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
84
|
-
const arg = argv[i];
|
|
85
|
-
if (arg === '--agent') {
|
|
86
|
-
opts.agent = argv[i + 1] || '';
|
|
87
|
-
i += 1;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (arg === '--repo') {
|
|
91
|
-
opts.repo = argv[i + 1] || '';
|
|
92
|
-
i += 1;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (arg === '--zxp') {
|
|
96
|
-
opts.zxp = argv[i + 1] || '';
|
|
97
|
-
i += 1;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (arg === '--skip-extension') {
|
|
101
|
-
opts.skipExtension = true;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (arg === '--skip-cli') {
|
|
105
|
-
opts.skipCli = true;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (arg === '--skip-skills') {
|
|
109
|
-
opts.skipSkills = true;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (arg === '-h' || arg === '--help') {
|
|
113
|
-
opts.help = true;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
throw new Error(`Unknown argument: ${arg}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return opts;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function getDetectedAgents() {
|
|
123
|
-
const detected = [];
|
|
124
|
-
const home = os.homedir();
|
|
125
|
-
if (process.env.CODEX_HOME || fs.existsSync(path.join(home, '.codex'))) detected.push('codex');
|
|
126
|
-
if (fs.existsSync(path.join(home, '.gemini'))) detected.push('gemini');
|
|
127
|
-
return detected;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function ask(question) {
|
|
131
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
132
|
-
return new Promise((resolve) => {
|
|
133
|
-
rl.question(question, (answer) => {
|
|
134
|
-
rl.close();
|
|
135
|
-
resolve(answer.trim());
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function resolveAgent(agentArg) {
|
|
141
|
-
if (agentArg) {
|
|
142
|
-
const value = agentArg.toLowerCase();
|
|
143
|
-
if (!['codex', 'gemini', 'both'].includes(value)) {
|
|
144
|
-
throw new Error(`Invalid --agent value: ${agentArg}`);
|
|
145
|
-
}
|
|
146
|
-
return value;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const detected = getDetectedAgents();
|
|
150
|
-
const recommended = detected[0] || 'codex';
|
|
151
|
-
console.log('Select which coding agent should receive skills:');
|
|
152
|
-
console.log(` 1) codex${recommended === 'codex' ? ' (recommended)' : ''}`);
|
|
153
|
-
console.log(` 2) gemini${recommended === 'gemini' ? ' (recommended)' : ''}`);
|
|
154
|
-
console.log(' 3) both');
|
|
155
|
-
|
|
156
|
-
if (!process.stdin.isTTY) {
|
|
157
|
-
console.log(`No interactive terminal detected. Defaulting to: ${recommended}`);
|
|
158
|
-
return recommended;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const answer = await ask('Enter 1, 2, or 3 [1]: ');
|
|
162
|
-
if (!answer || answer === '1') return 'codex';
|
|
163
|
-
if (answer === '2') return 'gemini';
|
|
164
|
-
if (answer === '3') return 'both';
|
|
165
|
-
throw new Error(`Invalid selection: ${answer}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function fetchJson(url) {
|
|
169
|
-
return new Promise((resolve, reject) => {
|
|
170
|
-
const req = fetch(url, {
|
|
171
|
-
headers: {
|
|
172
|
-
Accept: 'application/vnd.github+json',
|
|
173
|
-
'User-Agent': 'ae-agent-setup',
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
req.then(async (res) => {
|
|
177
|
-
if (!res.ok) {
|
|
178
|
-
reject(new Error(`Request failed: ${res.status} ${res.statusText}`));
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
const body = await res.json();
|
|
183
|
-
resolve(body);
|
|
184
|
-
} catch (err) {
|
|
185
|
-
reject(err);
|
|
186
|
-
}
|
|
187
|
-
}).catch(reject);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function downloadToFile(url, destination) {
|
|
192
|
-
return fetch(url, {
|
|
193
|
-
headers: {
|
|
194
|
-
Accept: 'application/octet-stream',
|
|
195
|
-
'User-Agent': 'ae-agent-setup',
|
|
196
|
-
},
|
|
197
|
-
}).then(async (res) => {
|
|
198
|
-
if (!res.ok) {
|
|
199
|
-
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
200
|
-
}
|
|
201
|
-
const data = Buffer.from(await res.arrayBuffer());
|
|
202
|
-
fs.writeFileSync(destination, data);
|
|
203
|
-
return destination;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function resolveZxpPath(opts) {
|
|
208
|
-
if (opts.zxp) {
|
|
209
|
-
if (opts.zxp.startsWith('http://') || opts.zxp.startsWith('https://')) {
|
|
210
|
-
const output = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ae-agent-zxp-')), 'extension.zxp');
|
|
211
|
-
console.log(`Downloading ZXP from URL: ${opts.zxp}`);
|
|
212
|
-
await downloadToFile(opts.zxp, output);
|
|
213
|
-
return output;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const local = path.resolve(process.cwd(), opts.zxp);
|
|
217
|
-
if (!fs.existsSync(local)) {
|
|
218
|
-
throw new Error(`ZXP not found: ${local}`);
|
|
219
|
-
}
|
|
220
|
-
return local;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
console.log(`Fetching latest release from GitHub: ${opts.repo}`);
|
|
224
|
-
const release = await fetchJson(`https://api.github.com/repos/${opts.repo}/releases/latest`);
|
|
225
|
-
const asset = (release.assets || []).find((a) => typeof a.name === 'string' && a.name.endsWith('.zxp'));
|
|
226
|
-
if (!asset) {
|
|
227
|
-
throw new Error(`No .zxp asset found in latest release of ${opts.repo}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const output = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ae-agent-zxp-')), asset.name);
|
|
231
|
-
console.log(`Downloading asset: ${asset.name}`);
|
|
232
|
-
await downloadToFile(asset.browser_download_url, output);
|
|
233
|
-
return output;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function installSkills(agent) {
|
|
237
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
238
|
-
const sourceRoot = path.join(root, 'templates', 'skills');
|
|
239
|
-
|
|
240
|
-
const targets = [];
|
|
241
|
-
if (agent === 'codex' || agent === 'both') {
|
|
242
|
-
const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
243
|
-
targets.push({ kind: 'codex', destRoot: path.join(codexHome, 'skills') });
|
|
244
|
-
}
|
|
245
|
-
if (agent === 'gemini' || agent === 'both') {
|
|
246
|
-
targets.push({ kind: 'gemini', destRoot: path.join(os.homedir(), '.gemini', 'skills') });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
for (const target of targets) {
|
|
250
|
-
fs.mkdirSync(target.destRoot, { recursive: true });
|
|
251
|
-
for (const skill of SKILL_SOURCES) {
|
|
252
|
-
const source = path.join(sourceRoot, skill.sourceName);
|
|
253
|
-
const destination = path.join(target.destRoot, skill.destinationName);
|
|
254
|
-
if (!fs.existsSync(source)) {
|
|
255
|
-
throw new Error(`Skill source not found: ${source}`);
|
|
256
|
-
}
|
|
257
|
-
fs.rmSync(destination, { recursive: true, force: true });
|
|
258
|
-
fs.mkdirSync(destination, { recursive: true });
|
|
259
|
-
fs.copyFileSync(source, path.join(destination, 'SKILL.md'));
|
|
260
|
-
console.log(`Installed ${target.kind} skill: ${destination}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function setupAgentWorkspace() {
|
|
266
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
267
|
-
const workspaceRoot = path.join(os.homedir(), AGENT_WORKSPACE_NAME);
|
|
268
|
-
const workDir = path.join(workspaceRoot, 'work');
|
|
269
|
-
const doneDir = path.join(workspaceRoot, 'done');
|
|
270
|
-
const refsDir = path.join(workspaceRoot, 'references');
|
|
271
|
-
|
|
272
|
-
fs.mkdirSync(workDir, { recursive: true });
|
|
273
|
-
fs.mkdirSync(doneDir, { recursive: true });
|
|
274
|
-
fs.mkdirSync(refsDir, { recursive: true });
|
|
275
|
-
|
|
276
|
-
for (const resource of WORKSPACE_RESOURCE_SOURCES) {
|
|
277
|
-
const source = path.join(root, ...resource.source);
|
|
278
|
-
const destination = path.join(workspaceRoot, ...resource.destination);
|
|
279
|
-
if (!fs.existsSync(source)) {
|
|
280
|
-
throw new Error(`Workspace resource not found: ${source}`);
|
|
281
|
-
}
|
|
282
|
-
fs.copyFileSync(source, destination);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
console.log(`Prepared workspace: ${workspaceRoot}`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function installCli(repo) {
|
|
289
|
-
if (!commandExists('python3')) {
|
|
290
|
-
throw new Error('python3 is required to install ae-cli.');
|
|
291
|
-
}
|
|
292
|
-
const spec = `git+https://github.com/${repo}.git`;
|
|
293
|
-
run('python3', ['-m', 'pip', 'install', '--user', '--upgrade', spec]);
|
|
294
|
-
|
|
295
|
-
const helpRes = spawnSync('ae-cli', ['--help'], { stdio: 'inherit' });
|
|
296
|
-
if (helpRes.status !== 0) {
|
|
297
|
-
console.log('ae-cli was installed, but not found on PATH.');
|
|
298
|
-
console.log('Run this once in your shell profile if needed:');
|
|
299
|
-
console.log(' export PATH="$HOME/Library/Python/3.*/bin:$PATH"');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function installCommand(opts) {
|
|
304
|
-
console.log('Starting ae-agent setup...');
|
|
305
|
-
setupAgentWorkspace();
|
|
306
|
-
|
|
307
|
-
const agent = await resolveAgent(opts.agent);
|
|
308
|
-
console.log(`Selected agent target: ${agent}`);
|
|
309
|
-
|
|
310
|
-
if (!opts.skipExtension) {
|
|
311
|
-
const installer = detectInstaller();
|
|
312
|
-
if (!installer) {
|
|
313
|
-
throw new Error('Neither UPIA nor ExManCmd was found. Install one of them and run again.');
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const zxpPath = await resolveZxpPath(opts);
|
|
317
|
-
console.log(`Installing extension via ${installer.cmd}...`);
|
|
318
|
-
run(installer.cmd, [...installer.args, zxpPath]);
|
|
319
|
-
} else {
|
|
320
|
-
console.log('Skipping extension installation (--skip-extension).');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!opts.skipCli) {
|
|
324
|
-
console.log('Installing ae-cli...');
|
|
325
|
-
installCli(opts.repo);
|
|
326
|
-
} else {
|
|
327
|
-
console.log('Skipping ae-cli installation (--skip-cli).');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (!opts.skipSkills) {
|
|
331
|
-
console.log('Installing agent skills...');
|
|
332
|
-
installSkills(agent);
|
|
333
|
-
} else {
|
|
334
|
-
console.log('Skipping skill installation (--skip-skills).');
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
console.log('Setup finished.');
|
|
338
|
-
console.log('Next actions:');
|
|
339
|
-
console.log(' 1) Fully restart After Effects.');
|
|
340
|
-
console.log(' 2) Open the panel: Window > Extensions (Beta) > ae-agent-skill.');
|
|
341
|
-
console.log(' 3) Run: ae-cli health');
|
|
342
|
-
console.log(` 4) Use workspace: ${path.join(os.homedir(), AGENT_WORKSPACE_NAME)}`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function main() {
|
|
346
|
-
const [command, ...rest] = process.argv.slice(2);
|
|
347
|
-
|
|
348
|
-
if (!command || command === '-h' || command === '--help') {
|
|
349
|
-
printHelp();
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (command !== 'install') {
|
|
354
|
-
throw new Error(`Unknown command: ${command}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const opts = parseArgs(rest);
|
|
358
|
-
if (opts.help) {
|
|
359
|
-
printHelp();
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
await installCommand(opts);
|
|
364
|
-
}
|
|
3
|
+
import { main } from './ae-agent-setup-lib.mjs';
|
|
365
4
|
|
|
366
5
|
main().catch((err) => {
|
|
367
6
|
console.error(`ERROR: ${err.message}`);
|
package/client/index.html
CHANGED
package/client/lib/runtime.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
let http = null;
|
|
2
2
|
let path = null;
|
|
3
|
+
let fs = null;
|
|
3
4
|
let nodeReady = true;
|
|
4
5
|
let nodeInitError = null;
|
|
5
6
|
|
|
6
7
|
try {
|
|
7
8
|
http = require('http');
|
|
8
9
|
path = require('path');
|
|
10
|
+
fs = require('fs');
|
|
9
11
|
} catch (e) {
|
|
10
12
|
nodeReady = false;
|
|
11
13
|
nodeInitError = e;
|
|
@@ -16,6 +18,7 @@ const extensionRoot = csInterface.getSystemPath(SystemPath.EXTENSION);
|
|
|
16
18
|
const hostScriptPath = nodeReady
|
|
17
19
|
? escapeForExtendScript(path.join(extensionRoot, 'host', 'index.jsx'))
|
|
18
20
|
: null;
|
|
21
|
+
const extensionVersion = resolveExtensionVersion();
|
|
19
22
|
|
|
20
23
|
function escapeForExtendScript(str) {
|
|
21
24
|
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
@@ -33,3 +36,25 @@ function evalHostScript(scriptSource, callback) {
|
|
|
33
36
|
const fullScript = `$.evalFile("${hostScriptPath}");${scriptSource}`;
|
|
34
37
|
csInterface.evalScript(fullScript, callback);
|
|
35
38
|
}
|
|
39
|
+
|
|
40
|
+
function resolveExtensionVersion() {
|
|
41
|
+
if (!nodeReady || !fs || !path) return null;
|
|
42
|
+
try {
|
|
43
|
+
const manifestPath = path.join(extensionRoot, 'CSXS', 'manifest.xml');
|
|
44
|
+
const manifest = fs.readFileSync(manifestPath, 'utf8');
|
|
45
|
+
const match = manifest.match(/ExtensionBundleVersion="([^"]+)"/);
|
|
46
|
+
return match ? match[1] : null;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function applyPanelVersionLabel() {
|
|
53
|
+
const versionEl = document.getElementById('panel-version');
|
|
54
|
+
if (!versionEl) return;
|
|
55
|
+
if (extensionVersion) {
|
|
56
|
+
versionEl.textContent = `v${extensionVersion}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
applyPanelVersionLabel();
|
package/docs/development.ja.md
CHANGED
|
@@ -22,7 +22,97 @@ defaults write com.adobe.CSXS.11 PlayerDebugMode 1
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
python3 -m pip install -e ".[dev]"
|
|
25
|
-
PYTHONPATH=src pytest
|
|
25
|
+
PYTHONPATH=src python3 -m pytest
|
|
26
|
+
npm run test:node
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ZXPビルドと公開手順
|
|
30
|
+
|
|
31
|
+
`0.2.6` 以降は `package.json` の `version` を正として、`npm version` 実行時に `CSXS/manifest.xml` へ自動同期します。
|
|
32
|
+
Python パッケージ版は `pyproject.toml` も同じ値に揃えます。
|
|
33
|
+
|
|
34
|
+
### 1) バージョン更新
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm version 0.3.0 --no-git-tag-version
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2) 署名付き ZXP ビルド
|
|
41
|
+
|
|
42
|
+
前提:
|
|
43
|
+
- `ZXPSignCmd` が `PATH` 上にある、または `ZXPSIGNCMD_BIN` で実体パスを指定できる
|
|
44
|
+
- 署名証明書 (`.p12`) とパスワードを用意済み
|
|
45
|
+
|
|
46
|
+
1コマンド(パスワードはプロンプト入力):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run build:zxp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
または:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
./scripts/signing/build-zxp-interactive.sh
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
証明書パスを変える場合:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
SIGN_CERT_P12=/absolute/path/to/dev-cert.p12 npm run build:zxp
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
以下は従来どおり、環境変数で直接指定する方法:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
SIGN_CERT_P12=certs/dev-cert.p12 \
|
|
68
|
+
SIGN_CERT_PASSWORD='your-password' \
|
|
69
|
+
./scripts/signing/build-zxp.sh
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
必要なら `ZXPSIGNCMD_BIN` を指定:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ZXPSIGNCMD_BIN=/absolute/path/to/ZXPSignCmd \
|
|
76
|
+
SIGN_CERT_P12=certs/dev-cert.p12 \
|
|
77
|
+
SIGN_CERT_PASSWORD='your-password' \
|
|
78
|
+
./scripts/signing/build-zxp.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
出力先:
|
|
82
|
+
- `dist/ae-agent-skill-<version>.zxp`
|
|
83
|
+
|
|
84
|
+
### 3) コミット・タグ・push
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git add package.json pyproject.toml CSXS/manifest.xml
|
|
88
|
+
git commit -m "release: v0.3.0"
|
|
89
|
+
git tag v0.3.0
|
|
90
|
+
git push origin HEAD
|
|
91
|
+
git push origin v0.3.0
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4) npm 公開
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm publish
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 5) GitHub Release 作成(ZXP添付)
|
|
101
|
+
|
|
102
|
+
`npx ae-agent-skills install` は latest release の `.zxp` を参照するため、Release に ZXP を添付します。
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
gh release create v0.3.0 dist/ae-agent-skill-0.3.0.zxp \
|
|
106
|
+
--title v0.3.0 \
|
|
107
|
+
--notes "Release notes"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 6) 公開後確認
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm --cache /private/tmp/ae-agent-npm-cache pack --dry-run
|
|
114
|
+
npm view ae-agent-skills version dist-tags.latest --json
|
|
115
|
+
gh release view --repo yumehiko/ae-agent-skills --json tagName,assets
|
|
26
116
|
```
|
|
27
117
|
|
|
28
118
|
## プロジェクト構成
|
package/docs/development.md
CHANGED
|
@@ -22,7 +22,15 @@ Then fully quit and relaunch After Effects.
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
python3 -m pip install -e ".[dev]"
|
|
25
|
-
PYTHONPATH=src pytest
|
|
25
|
+
PYTHONPATH=src python3 -m pytest
|
|
26
|
+
npm run test:node
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`package.json`, `pyproject.toml`, and `CSXS/manifest.xml` versions should stay aligned for releases.
|
|
30
|
+
Before publishing, run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm --cache /private/tmp/ae-agent-npm-cache pack --dry-run
|
|
26
34
|
```
|
|
27
35
|
|
|
28
36
|
## Project structure
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ae-agent-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "One-command installer for the ae-agent-skill After Effects extension and CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ae-agent-skills": "bin/ae-agent-setup.mjs",
|
|
8
8
|
"ae-agent-setup": "bin/ae-agent-setup.mjs"
|
|
9
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build:zxp": "./scripts/signing/build-zxp-interactive.sh",
|
|
12
|
+
"sync:version": "node scripts/release/sync-version.mjs",
|
|
13
|
+
"test:node": "node --test tests/*.test.mjs",
|
|
14
|
+
"version": "npm run sync:version"
|
|
15
|
+
},
|
|
10
16
|
"files": [
|
|
11
17
|
"bin",
|
|
12
18
|
"templates",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Build signed ZXP by prompting for certificate password interactively.
|
|
5
|
+
# Optional env:
|
|
6
|
+
# SIGN_CERT_P12=certs/dev-cert.p12
|
|
7
|
+
# ZXPSIGNCMD_BIN=/absolute/path/to/ZXPSignCmd
|
|
8
|
+
# TIMESTAMP_URL=http://timestamp.digicert.com
|
|
9
|
+
|
|
10
|
+
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
11
|
+
CERT_P12="${SIGN_CERT_P12:-${ROOT_DIR}/certs/dev-cert.p12}"
|
|
12
|
+
|
|
13
|
+
if [[ ! -f "${CERT_P12}" ]]; then
|
|
14
|
+
echo "Certificate file not found: ${CERT_P12}" >&2
|
|
15
|
+
echo "Set SIGN_CERT_P12 to a valid .p12 file path." >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
read -r -s -p "SIGN_CERT_PASSWORD: " SIGN_CERT_PASSWORD
|
|
20
|
+
echo
|
|
21
|
+
|
|
22
|
+
if [[ -z "${SIGN_CERT_PASSWORD}" ]]; then
|
|
23
|
+
echo "Password is empty." >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
SIGN_CERT_P12="${CERT_P12}" SIGN_CERT_PASSWORD="${SIGN_CERT_PASSWORD}" \
|
|
28
|
+
"${ROOT_DIR}/scripts/signing/build-zxp.sh"
|
|
29
|
+
|
|
30
|
+
unset SIGN_CERT_PASSWORD
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Use the `aftereffects-declarative` skill for this request.
|
|
2
|
+
|
|
3
|
+
Follow the skill workflow: check `ae-cli health`, create or update scene JSON under `~/ae-agent-skills/work/`, validate with `ae-cli apply-scene --validate-only`, apply it, inspect the result, and keep the completed scene JSON under `~/ae-agent-skills/done/`.
|
|
4
|
+
|
|
5
|
+
User request:
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: aftereffects-cli
|
|
2
|
+
name: aftereffects-cli
|
|
3
3
|
description: Command-by-command After Effects editing via ae-cli. Best for surgical edits on existing human-made scenes (especially partial expression/property changes) and for debugging.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# aftereffects-cli
|
|
6
|
+
# aftereffects-cli
|
|
7
7
|
|
|
8
8
|
低レベル CLI 操作(命令型)のスキル。
|
|
9
9
|
新規構築の主軸は宣言型 `aftereffects-declarative` だが、**既存シーンへの外科的編集ではこのスキルを第一選択**にする。
|
|
@@ -81,7 +81,7 @@ description: Command-by-command After Effects editing via ae-cli. Best for surgi
|
|
|
81
81
|
ae-cli health
|
|
82
82
|
ae-cli create-comp --name "Skill_CLI_Minimal_Test" --width 1280 --height 720 --duration 3 --frame-rate 30
|
|
83
83
|
ae-cli set-active-comp --comp-name "Skill_CLI_Minimal_Test"
|
|
84
|
-
ae-cli add-layer --type text --name "Hello" --text "CLI skill test"
|
|
84
|
+
ae-cli add-layer --layer-type text --name "Hello" --text "CLI skill test"
|
|
85
85
|
ae-cli layers
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -20,7 +20,7 @@ After Effects を宣言型 JSON で構築する標準スキル。
|
|
|
20
20
|
- 新規 comp / 新規レイヤー群を組み上げる
|
|
21
21
|
- 同種変更を複数箇所へ展開する
|
|
22
22
|
- scene JSON を成果物として残し、再実行可能にしたい
|
|
23
|
-
- 命令型(`aftereffects-cli
|
|
23
|
+
- 命令型(`aftereffects-cli`)を使う:
|
|
24
24
|
- 人間が作った既存シーン(scene JSON 未管理)へ部分修正を入れる
|
|
25
25
|
- 複雑な既存レイヤーに expression をピンポイント適用する
|
|
26
26
|
- comp 全体の再宣言を避け、局所的に安全修正したい
|
|
@@ -152,4 +152,4 @@ ae-cli apply-scene --scene-file ~/ae-agent-skills/work/min.scene.json
|
|
|
152
152
|
- 旧レイヤーが残る:
|
|
153
153
|
- `apply-scene --mode replace-managed` または `--mode clear-all` を使う
|
|
154
154
|
- 既存シーンへ局所修正したい / 宣言型で表現しづらい:
|
|
155
|
-
- `aftereffects-cli
|
|
155
|
+
- `aftereffects-cli` へ切り替え
|