akari-video 0.1.67 → 0.1.69
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 +11 -0
- package/bin/akari.mjs +5 -1
- package/package.json +1 -1
- package/src/kits.mjs +238 -0
- package/src/messages.mjs +1 -0
- package/src/repo-assets.mjs +7 -1
- package/src/store-command.mjs +138 -1
- package/src/storyboard-command.mjs +24 -0
- package/src/world-command.mjs +17 -0
- package/vendor/.akari-capability-sources.json +3 -0
- package/vendor/docs/contract-2026-09-13-extension-kit-v0.md +79 -0
- package/vendor/docs/contract-2026-09-13-world-map-v0.md +54 -0
- package/vendor/packages/akari-launcher/README.md +11 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/akari-tools/package.json +3 -2
- package/vendor/packages/asset-resolver/src/state.mjs +32 -1
- package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +36 -0
- package/vendor/packages/edit-lint/src/edit-lint.mjs +2 -0
- package/vendor/packages/edit-lint/src/world-scene-declaration.mjs +64 -0
- package/vendor/packages/edit-store/lib/generation-meta-node.js +2 -11
- package/vendor/packages/edit-store/lib/generation-meta.d.ts +17 -0
- package/vendor/packages/edit-store/lib/generation-meta.js +44 -0
- package/vendor/packages/generate/README.md +1 -1
- package/vendor/packages/overlay-runtime/README.md +18 -0
- package/vendor/packages/overlay-runtime/package.json +2 -0
- package/vendor/packages/project-scaffold/src/index.mjs +33 -3
- package/vendor/packages/project-scaffold/test/kit-skill-adapter.test.mjs +38 -0
- package/vendor/skills/overlay-authoring/SKILL.md +1 -0
- package/vendor/skills/overlay-authoring/world.md +42 -0
- package/vendor/skills/render-cut/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,17 @@ sha256 検証・fail-closed は resolver 側の責務のまま。`src/assets-com
|
|
|
56
56
|
`akari clean [project-dir] [--dry-run] [--yes] [--json]`(使い捨ての中間ファイル、保持する
|
|
57
57
|
正本、判断が必要なものを容量付きで一覧する。既定は一覧のみで、削除可能なものだけを承認後に削除)。
|
|
58
58
|
|
|
59
|
+
## 拡張キット
|
|
60
|
+
|
|
61
|
+
`manifest.json` を持つ配布物を `akari store install <productId>` で導入すると、CLI / runtime の要件を検査し、素材とスキルを `~/.akari` 配下へ symlink で合成する。導入状況は `akari store status`、解除は `akari store uninstall <productId>` で確認・操作できる。Claude Code では初回だけ次を実行する。
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
claude plugin marketplace add ~/.akari/kits
|
|
65
|
+
claude plugin install akari-kits@akari-kits
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`claude` が PATH に無い場合は、Claude Code のプラグイン設定で `~/.akari/kits` を marketplace として追加する。
|
|
69
|
+
|
|
59
70
|
`akari` に渡した引数はそのまま `opencode` に転送する(例: `akari --continue` は
|
|
60
71
|
`opencode --continue` を起動する)。
|
|
61
72
|
|
package/bin/akari.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import { runMigrateCommand } from '../src/migrate-command.mjs';
|
|
|
14
14
|
import { runCleanCommand } from '../src/clean-command.mjs';
|
|
15
15
|
import { runDoctorCommand } from '../src/doctor-command.mjs';
|
|
16
16
|
import { runGenerateCommand } from '../src/generate-command.mjs';
|
|
17
|
+
import { runStoryboardCommand } from '../src/storyboard-command.mjs';
|
|
18
|
+
import { runWorldCommand } from '../src/world-command.mjs';
|
|
17
19
|
import { resolveRuntimePaths } from '../src/runtime-diagnostics.mjs';
|
|
18
20
|
import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
|
|
19
21
|
import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
|
|
@@ -36,7 +38,7 @@ async function printVersion() {
|
|
|
36
38
|
// `--help` は claude/opencode へそのまま転送されてしまっていた — AKARI Video 自身の
|
|
37
39
|
// コマンド一覧が一度も出ない行き止まりだったため新設した)。
|
|
38
40
|
async function printCliHelp() {
|
|
39
|
-
for (const line of describeCliHelp()) {
|
|
41
|
+
for (const line of [...describeCliHelp(), ' world ワールド地図を検査・生成・プレビュー']) {
|
|
40
42
|
console.log(line);
|
|
41
43
|
}
|
|
42
44
|
return { exitCode: 0 };
|
|
@@ -82,6 +84,8 @@ const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
|
|
|
82
84
|
: argv[0] === 'migrate' ? runMigrateCommand(argv.slice(1))
|
|
83
85
|
: argv[0] === 'clean' ? runCleanCommand(argv.slice(1))
|
|
84
86
|
: argv[0] === 'generate' ? runGenerateCommand(argv.slice(1))
|
|
87
|
+
: argv[0] === 'storyboard' ? runStoryboardCommand(argv.slice(1))
|
|
88
|
+
: argv[0] === 'world' ? runWorldCommand(argv.slice(1))
|
|
85
89
|
: run(argv);
|
|
86
90
|
|
|
87
91
|
const result = await invoke.catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/kits.mjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import {
|
|
3
|
+
existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync,
|
|
4
|
+
renameSync, rmSync, symlinkSync, writeFileSync
|
|
5
|
+
} from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
9
|
+
|
|
10
|
+
const KITS_SCHEMA = 'akari-installed-kits/v0';
|
|
11
|
+
const PLUGIN_DESCRIPTION = 'AKARI Video 拡張キットのスキルをまとめて提供するローカルプラグイン。';
|
|
12
|
+
|
|
13
|
+
function parseVersion(value) {
|
|
14
|
+
const match = String(value).match(/^(\d+)\.(\d+)\.(\d+)$/u);
|
|
15
|
+
return match ? match.slice(1).map(Number) : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function compareVersions(left, right) {
|
|
19
|
+
for (let index = 0; index < 3; index += 1) {
|
|
20
|
+
if (left[index] !== right[index]) return left[index] - right[index];
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function satisfies(version, range) {
|
|
26
|
+
const actual = parseVersion(version);
|
|
27
|
+
const match = String(range).match(/^(\^|~|>=)?(\d+\.\d+\.\d+)$/u);
|
|
28
|
+
if (!actual || !match) return false;
|
|
29
|
+
const required = parseVersion(match[2]);
|
|
30
|
+
if (compareVersions(actual, required) < 0) return false;
|
|
31
|
+
if (match[1] === '^') {
|
|
32
|
+
return actual[0] === required[0] && (required[0] !== 0 || actual[1] === required[1]);
|
|
33
|
+
}
|
|
34
|
+
if (match[1] === '~') return actual[0] === required[0] && actual[1] === required[1];
|
|
35
|
+
if (match[1] === '>=') return true;
|
|
36
|
+
return compareVersions(actual, required) === 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readKitManifest(kitDir) {
|
|
40
|
+
const manifestPath = path.join(kitDir, 'manifest.json');
|
|
41
|
+
return existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, 'utf8')) : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function checkRequires(manifest, { cliVersion, runtimeIds = [], entitledProductIds = [] }) {
|
|
45
|
+
const blockers = [];
|
|
46
|
+
const warnings = [];
|
|
47
|
+
const requires = manifest?.requires ?? {};
|
|
48
|
+
if (!satisfies(cliVersion, requires.cli)) {
|
|
49
|
+
blockers.push(`CLI ${requires.cli} が必要です(現在 ${cliVersion})。AKARI Video を更新してください。`);
|
|
50
|
+
}
|
|
51
|
+
const availableRuntimes = new Set(runtimeIds);
|
|
52
|
+
for (const runtimeId of requires.runtimes ?? []) {
|
|
53
|
+
if (!availableRuntimes.has(runtimeId)) {
|
|
54
|
+
blockers.push(`runtime ${runtimeId} がありません。このアプリ版ではキットを利用できません。`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const entitled = new Set(entitledProductIds);
|
|
58
|
+
for (const productId of requires.products ?? []) {
|
|
59
|
+
if (!entitled.has(productId)) {
|
|
60
|
+
warnings.push(`依存商品 ${productId} の購入が確認できません。\`akari store install ${productId}\` で導入してください。`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { ok: blockers.length === 0, blockers, warnings };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function safeKitPath(kitDir, ...parts) {
|
|
67
|
+
const root = path.resolve(kitDir);
|
|
68
|
+
const candidate = path.resolve(root, ...parts);
|
|
69
|
+
if (candidate !== root && !candidate.startsWith(`${root}${path.sep}`)) {
|
|
70
|
+
throw new Error(`キット外のパスは参照できません: ${parts.join('/')}`);
|
|
71
|
+
}
|
|
72
|
+
return candidate;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function replaceSymlink(source, destination, {
|
|
76
|
+
platform = process.platform,
|
|
77
|
+
relativeTarget,
|
|
78
|
+
symlinkSyncImpl = symlinkSync
|
|
79
|
+
} = {}) {
|
|
80
|
+
if (existsSync(destination) || (() => { try { lstatSync(destination); return true; } catch { return false; } })()) {
|
|
81
|
+
const stat = lstatSync(destination);
|
|
82
|
+
if (!stat.isSymbolicLink()) return { status: 'occupied' };
|
|
83
|
+
rmSync(destination);
|
|
84
|
+
}
|
|
85
|
+
mkdirSync(path.dirname(destination), { recursive: true });
|
|
86
|
+
const target = relativeTarget ?? path.relative(path.dirname(destination), source);
|
|
87
|
+
try {
|
|
88
|
+
symlinkSyncImpl(target, destination, 'dir');
|
|
89
|
+
return { status: 'linked', target };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (platform === 'win32' && error?.code === 'EPERM') return { status: 'permission-denied' };
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function linkKitAssets(kitDir, manifest, home, options = {}) {
|
|
97
|
+
const warnings = [];
|
|
98
|
+
const linked = [];
|
|
99
|
+
const assets = options.assets?.schemasSourceDir !== undefined
|
|
100
|
+
? options.assets
|
|
101
|
+
: resolveLauncherAssets(options.assets);
|
|
102
|
+
const validator = options.validateAssetPath
|
|
103
|
+
?? (assets.schemasSourceDir ? path.join(assets.schemasSourceDir, 'bin', 'validate-asset.mjs') : null);
|
|
104
|
+
for (const asset of manifest.assets ?? []) {
|
|
105
|
+
const source = safeKitPath(kitDir, 'assets', asset.category, asset.id);
|
|
106
|
+
if (validator && existsSync(validator)) {
|
|
107
|
+
const result = (options.spawnSyncImpl ?? spawnSync)(process.execPath, [validator, source], { stdio: 'pipe' });
|
|
108
|
+
if (result.status !== 0) {
|
|
109
|
+
warnings.push(`素材 ${asset.category}/${asset.id} は検査に失敗したためリンクしませんでした。`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
warnings.push(`素材 ${asset.category}/${asset.id} の検査ツールが見つからないため検査をスキップしました。`);
|
|
114
|
+
}
|
|
115
|
+
const destination = path.join(home, 'assets', asset.category, asset.id);
|
|
116
|
+
const result = replaceSymlink(source, destination, {
|
|
117
|
+
...options,
|
|
118
|
+
relativeTarget: path.join('..', '..', 'assets', 'store', manifest.id, 'assets', asset.category, asset.id)
|
|
119
|
+
});
|
|
120
|
+
if (result.status === 'occupied') {
|
|
121
|
+
warnings.push(`既存の実ディレクトリを保持しました: ${destination}`);
|
|
122
|
+
} else if (result.status === 'permission-denied') {
|
|
123
|
+
warnings.push(`symlink を作成できませんでした(Windows の権限を確認してください): ${destination}`);
|
|
124
|
+
} else {
|
|
125
|
+
linked.push({ category: asset.category, id: asset.id });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { linked, warnings };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function linkKitSkills(kitDir, manifest, home, options = {}) {
|
|
132
|
+
const warnings = [];
|
|
133
|
+
const blockers = [];
|
|
134
|
+
const linked = [];
|
|
135
|
+
const planned = [];
|
|
136
|
+
for (const skill of manifest.skills ?? []) {
|
|
137
|
+
const source = safeKitPath(kitDir, skill.dir);
|
|
138
|
+
const destination = path.join(home, 'kits', 'plugin', 'skills', skill.name);
|
|
139
|
+
try {
|
|
140
|
+
const stat = lstatSync(destination);
|
|
141
|
+
if (!stat.isSymbolicLink()) {
|
|
142
|
+
blockers.push(`スキル名 ${skill.name} は既存の実ディレクトリと重複しています。`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const current = path.resolve(path.dirname(destination), readlinkSync(destination));
|
|
146
|
+
if (current !== source) {
|
|
147
|
+
blockers.push(`スキル名 ${skill.name} は別のキットと重複しています。`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
152
|
+
}
|
|
153
|
+
planned.push({ skill, source, destination });
|
|
154
|
+
}
|
|
155
|
+
if (blockers.length > 0) return { linked, blockers, warnings };
|
|
156
|
+
for (const { skill, source, destination } of planned) {
|
|
157
|
+
const result = replaceSymlink(source, destination, options);
|
|
158
|
+
if (result.status === 'permission-denied') {
|
|
159
|
+
warnings.push(`symlink を作成できませんでした(Windows の権限を確認してください): ${destination}`);
|
|
160
|
+
} else {
|
|
161
|
+
linked.push(skill.name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { linked, blockers, warnings };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function readKitsLedger(home) {
|
|
168
|
+
const ledgerPath = path.join(home, 'kits', 'installed.json');
|
|
169
|
+
if (!existsSync(ledgerPath)) return { schema: KITS_SCHEMA, kits: [] };
|
|
170
|
+
const ledger = JSON.parse(readFileSync(ledgerPath, 'utf8'));
|
|
171
|
+
if (ledger?.schema !== KITS_SCHEMA || !Array.isArray(ledger.kits)) {
|
|
172
|
+
throw new Error(`拡張キット台帳の形式が想定と違います: ${ledgerPath}`);
|
|
173
|
+
}
|
|
174
|
+
return ledger;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function writeKitsLedger(home, entry) {
|
|
178
|
+
const ledger = readKitsLedger(home);
|
|
179
|
+
ledger.kits = [...ledger.kits.filter((kit) => kit.id !== entry.id), entry];
|
|
180
|
+
const ledgerPath = path.join(home, 'kits', 'installed.json');
|
|
181
|
+
mkdirSync(path.dirname(ledgerPath), { recursive: true });
|
|
182
|
+
const temporary = `${ledgerPath}.tmp-${process.pid}`;
|
|
183
|
+
writeFileSync(temporary, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
|
|
184
|
+
renameSync(temporary, ledgerPath);
|
|
185
|
+
return ledger;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function removeKit(home, productId) {
|
|
189
|
+
const ledger = readKitsLedger(home);
|
|
190
|
+
const entry = ledger.kits.find((kit) => kit.id === productId);
|
|
191
|
+
if (!entry) return false;
|
|
192
|
+
for (const name of entry.skills ?? []) removeOwnedSymlink(path.join(home, 'kits', 'plugin', 'skills', name), entry.kitDir);
|
|
193
|
+
for (const asset of entry.assets ?? []) {
|
|
194
|
+
removeOwnedSymlink(path.join(home, 'assets', asset.category, asset.id), entry.kitDir);
|
|
195
|
+
}
|
|
196
|
+
ledger.kits = ledger.kits.filter((kit) => kit.id !== productId);
|
|
197
|
+
const ledgerPath = path.join(home, 'kits', 'installed.json');
|
|
198
|
+
const temporary = `${ledgerPath}.tmp-${process.pid}`;
|
|
199
|
+
writeFileSync(temporary, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
|
|
200
|
+
renameSync(temporary, ledgerPath);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function removeOwnedSymlink(linkPath, kitDir) {
|
|
205
|
+
try {
|
|
206
|
+
if (!lstatSync(linkPath).isSymbolicLink()) return;
|
|
207
|
+
const target = path.resolve(path.dirname(linkPath), readlinkSync(linkPath));
|
|
208
|
+
if (target === path.resolve(kitDir) || target.startsWith(`${path.resolve(kitDir)}${path.sep}`)) rmSync(linkPath);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function ensureKitsMarketplace(home) {
|
|
215
|
+
const marketplacePath = path.join(home, 'kits', '.claude-plugin', 'marketplace.json');
|
|
216
|
+
const pluginPath = path.join(home, 'kits', 'plugin', '.claude-plugin', 'plugin.json');
|
|
217
|
+
const marketplace = {
|
|
218
|
+
name: 'akari-kits',
|
|
219
|
+
owner: { name: 'AKARI Video' },
|
|
220
|
+
plugins: [{ name: 'akari-kits', source: './plugin', description: PLUGIN_DESCRIPTION }]
|
|
221
|
+
};
|
|
222
|
+
const plugin = { name: 'akari-kits', description: PLUGIN_DESCRIPTION };
|
|
223
|
+
for (const [filePath, value] of [[marketplacePath, marketplace], [pluginPath, plugin]]) {
|
|
224
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
225
|
+
const content = `${JSON.stringify(value, null, 2)}\n`;
|
|
226
|
+
if (!existsSync(filePath) || readFileSync(filePath, 'utf8') !== content) writeFileSync(filePath, content);
|
|
227
|
+
}
|
|
228
|
+
return { marketplacePath, pluginPath };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function enableHint() {
|
|
232
|
+
return [
|
|
233
|
+
'Claude Code で拡張キットを有効化してください:',
|
|
234
|
+
' claude plugin marketplace add ~/.akari/kits',
|
|
235
|
+
' claude plugin install akari-kits@akari-kits',
|
|
236
|
+
'claude が PATH に無い場合は、Claude Code のプラグイン設定で ~/.akari/kits を marketplace として追加してください。'
|
|
237
|
+
].join('\n');
|
|
238
|
+
}
|
package/src/messages.mjs
CHANGED
|
@@ -331,6 +331,7 @@ export function describeCliHelp() {
|
|
|
331
331
|
' status 接続状態を確認する',
|
|
332
332
|
' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
|
|
333
333
|
' generate 台本のビートから静止画クリップを生成',
|
|
334
|
+
' storyboard タイムラインから印刷用の絵コンテを作成',
|
|
334
335
|
' akari clean [dir] 使い捨ての中間ファイルを一覧・削除(既定は一覧のみ)',
|
|
335
336
|
'',
|
|
336
337
|
'開発者向け:',
|
package/src/repo-assets.mjs
CHANGED
|
@@ -40,6 +40,7 @@ export const CAPTURE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin
|
|
|
40
40
|
const RENDER_WHEN_IDLE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'render-when-idle.sh');
|
|
41
41
|
const EYE_BAR_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'eye-bar.mjs');
|
|
42
42
|
export const GENERATE_CLI_RELATIVE = path.join('packages', 'generate', 'src', 'cli', 'index.mjs');
|
|
43
|
+
export const STORYBOARD_CLI_RELATIVE = path.join('packages', 'decision-cards', 'render-storyboard-print.mjs');
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold 実装・creator-root
|
|
@@ -62,6 +63,7 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
62
63
|
const decisionLogScript = path.join(repoRoot, DECISION_LOG_SCRIPT_RELATIVE);
|
|
63
64
|
const wordBookScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'word-book.mjs');
|
|
64
65
|
const generateScript = path.join(repoRoot, GENERATE_CLI_RELATIVE);
|
|
66
|
+
const storyboardScript = path.join(repoRoot, STORYBOARD_CLI_RELATIVE);
|
|
65
67
|
|
|
66
68
|
return {
|
|
67
69
|
repoRoot,
|
|
@@ -82,7 +84,8 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
82
84
|
mediaScript: existsSync(mediaScript) ? mediaScript : null,
|
|
83
85
|
...(existsSync(decisionLogScript) ? { decisionLogScript } : {}),
|
|
84
86
|
...(existsSync(wordBookScript) ? { wordBookScript } : {}),
|
|
85
|
-
...(existsSync(generateScript) ? { generateScript } : {})
|
|
87
|
+
...(existsSync(generateScript) ? { generateScript } : {}),
|
|
88
|
+
...(existsSync(storyboardScript) ? { storyboardScript } : {})
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -121,6 +124,9 @@ export function resolveLauncherAssets({
|
|
|
121
124
|
...(candidate.generateScript ?? vendor.generateScript
|
|
122
125
|
? { generateScript: candidate.generateScript ?? vendor.generateScript }
|
|
123
126
|
: {}),
|
|
127
|
+
...(candidate.storyboardScript ?? vendor.storyboardScript
|
|
128
|
+
? { storyboardScript: candidate.storyboardScript ?? vendor.storyboardScript }
|
|
129
|
+
: {}),
|
|
124
130
|
...(candidate.decisionLogScript ?? vendor.decisionLogScript ? { decisionLogScript: candidate.decisionLogScript ?? vendor.decisionLogScript } : {}),
|
|
125
131
|
...(candidate.wordBookScript ?? vendor.wordBookScript
|
|
126
132
|
? { wordBookScript: candidate.wordBookScript ?? vendor.wordBookScript }
|
package/src/store-command.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_STORE_BASE_URL,
|
|
10
11
|
defaultOpenBrowser,
|
|
@@ -17,6 +18,19 @@ import {
|
|
|
17
18
|
startDeviceConnection,
|
|
18
19
|
validateAndSaveCredentials
|
|
19
20
|
} from './store-device-connect.mjs';
|
|
21
|
+
import { readOwnVersion } from './update-check.mjs';
|
|
22
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
23
|
+
import {
|
|
24
|
+
checkRequires,
|
|
25
|
+
enableHint,
|
|
26
|
+
ensureKitsMarketplace,
|
|
27
|
+
linkKitAssets,
|
|
28
|
+
linkKitSkills,
|
|
29
|
+
readKitManifest,
|
|
30
|
+
readKitsLedger,
|
|
31
|
+
removeKit,
|
|
32
|
+
writeKitsLedger
|
|
33
|
+
} from './kits.mjs';
|
|
20
34
|
|
|
21
35
|
export { readCredentials, resolveCredentialsPath } from './store-device-connect.mjs';
|
|
22
36
|
|
|
@@ -288,6 +302,27 @@ export async function runStoreCommand(args, options = {}) {
|
|
|
288
302
|
}
|
|
289
303
|
log(`接続中: ${data.email}(${creds.url})`);
|
|
290
304
|
formatStoreEntitlements(data, log);
|
|
305
|
+
const kits = readKitsLedger(resolveAkariHome(env)).kits;
|
|
306
|
+
if (kits.length > 0) {
|
|
307
|
+
log('拡張キット:');
|
|
308
|
+
for (const kit of kits) {
|
|
309
|
+
log(` ${kit.id} v${kit.version} / スキル: ${(kit.skills ?? []).join(', ') || 'なし'} / 素材: ${(kit.assets ?? []).length} 件`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return { exitCode: 0 };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (sub === 'uninstall') {
|
|
316
|
+
const productId = args[1];
|
|
317
|
+
if (!isSafePathSegment(productId) || productId.startsWith('--')) {
|
|
318
|
+
log('使い方: akari store uninstall <productId>');
|
|
319
|
+
return { exitCode: 1 };
|
|
320
|
+
}
|
|
321
|
+
if (!removeKit(resolveAkariHome(env), productId)) {
|
|
322
|
+
log(`導入済みの拡張キットが見つかりません: ${productId}`);
|
|
323
|
+
return { exitCode: 1 };
|
|
324
|
+
}
|
|
325
|
+
log(`拡張キットを無効化しました: ${productId}(展開済みファイルは残しています)`);
|
|
291
326
|
return { exitCode: 0 };
|
|
292
327
|
}
|
|
293
328
|
|
|
@@ -415,6 +450,107 @@ export async function runStoreCommand(args, options = {}) {
|
|
|
415
450
|
const readme = findFile(destDir, 'README.md');
|
|
416
451
|
log(`展開しました: ${destDir}`);
|
|
417
452
|
if (readme) log(`導入手順: ${readme}`);
|
|
453
|
+
// 深い階層の無関係な manifest.json をキットと誤認すると、従来成功していた素材商品の
|
|
454
|
+
// install を壊す。キットの規定位置は展開ルート、または zip が単一トップディレクトリを
|
|
455
|
+
// 持つ場合のその直下だけとし、JSON として読めても kind !== kit なら完全に素通りする。
|
|
456
|
+
let manifestPath = path.join(destDir, 'manifest.json');
|
|
457
|
+
if (!existsSync(manifestPath)) {
|
|
458
|
+
const rootEntries = readdirSync(destDir, { withFileTypes: true });
|
|
459
|
+
manifestPath = rootEntries.length === 1 && rootEntries[0].isDirectory()
|
|
460
|
+
? path.join(destDir, rootEntries[0].name, 'manifest.json')
|
|
461
|
+
: null;
|
|
462
|
+
if (manifestPath && !existsSync(manifestPath)) manifestPath = null;
|
|
463
|
+
}
|
|
464
|
+
let manifest = null;
|
|
465
|
+
if (manifestPath) {
|
|
466
|
+
try {
|
|
467
|
+
manifest = readKitManifest(path.dirname(manifestPath));
|
|
468
|
+
} catch {
|
|
469
|
+
log('キットの検査に失敗しました。展開済みファイルを確認してください。');
|
|
470
|
+
return { exitCode: 1 };
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (manifest?.kind === 'kit') {
|
|
474
|
+
const kitDir = path.dirname(manifestPath);
|
|
475
|
+
const home = resolveAkariHome(env);
|
|
476
|
+
const launcherAssets = options.assets ?? resolveLauncherAssets();
|
|
477
|
+
const validator = launcherAssets.schemasSourceDir
|
|
478
|
+
? path.join(launcherAssets.schemasSourceDir, 'bin', 'validate-kit-manifest.mjs')
|
|
479
|
+
: null;
|
|
480
|
+
if (validator && existsSync(validator)) {
|
|
481
|
+
const validateArgs = [validator, kitDir];
|
|
482
|
+
if (launcherAssets.skillsSourceDir) validateArgs.push('--public-skills', launcherAssets.skillsSourceDir);
|
|
483
|
+
const validation = (options.spawnSync ?? spawnSync)(process.execPath, validateArgs, { stdio: 'pipe' });
|
|
484
|
+
if (validation.status !== 0) {
|
|
485
|
+
log('キットの検査に失敗しました。展開済みファイルを確認してください。');
|
|
486
|
+
return { exitCode: 1 };
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
// npm 配布物には schemas の検査 bin が無い場合がある。runtime と同じく、
|
|
490
|
+
// 器の欠落で購入済みコンテンツを利用不能にしないため warning へ degrade する。
|
|
491
|
+
log('キットの検査ツールが見つからないため検査をスキップしました');
|
|
492
|
+
}
|
|
493
|
+
const requirementWarnings = [];
|
|
494
|
+
let runtimeIds;
|
|
495
|
+
const runtimePath = path.join(launcherAssets.repoRoot, 'packages', 'overlay-runtime', 'runtimes.mjs');
|
|
496
|
+
if (existsSync(runtimePath)) {
|
|
497
|
+
try {
|
|
498
|
+
const runtimeModule = await import(pathToFileURL(runtimePath).href);
|
|
499
|
+
runtimeIds = runtimeModule.runtimes.map((runtime) => runtime.id);
|
|
500
|
+
} catch {
|
|
501
|
+
runtimeIds = null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (!runtimeIds) {
|
|
505
|
+
// overlay-runtime は launcher の npm tarball に同梱されない。照合不能は
|
|
506
|
+
// manifest 不備ではないため warning に落とし、要求 id を既知扱いして続行する。
|
|
507
|
+
requirementWarnings.push('runtime registry が見つからないため runtime id の照合をスキップしました。');
|
|
508
|
+
runtimeIds = manifest.requires?.runtimes ?? [];
|
|
509
|
+
}
|
|
510
|
+
let entitledProductIds = [];
|
|
511
|
+
const credentials = readCredentials(env);
|
|
512
|
+
if (credentials) {
|
|
513
|
+
const entitlementResult = await fetchStoreEntitlements(fetchImpl, credentials.url, credentials.token);
|
|
514
|
+
entitledProductIds = (entitlementResult.data?.entitlements ?? [])
|
|
515
|
+
.map((entry) => entry.product_id ?? entry.id)
|
|
516
|
+
.filter(Boolean);
|
|
517
|
+
}
|
|
518
|
+
const requires = checkRequires(manifest, {
|
|
519
|
+
cliVersion: options.cliVersion ?? readOwnVersion(),
|
|
520
|
+
runtimeIds,
|
|
521
|
+
entitledProductIds
|
|
522
|
+
});
|
|
523
|
+
if (manifest.id !== productId) requires.blockers.push(`manifest id が商品 id と一致しません: ${manifest.id} != ${productId}`);
|
|
524
|
+
requires.ok = requires.blockers.length === 0;
|
|
525
|
+
for (const warning of [...requirementWarnings, ...requires.warnings]) log(`警告: ${warning}`);
|
|
526
|
+
if (!requires.ok) {
|
|
527
|
+
for (const blocker of requires.blockers) log(`導入できません: ${blocker}`);
|
|
528
|
+
return { exitCode: 1 };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const hadInstalledKit = readKitsLedger(home).kits.length > 0;
|
|
532
|
+
const assetLinks = linkKitAssets(kitDir, manifest, home, {
|
|
533
|
+
assets: options.assets,
|
|
534
|
+
spawnSyncImpl: options.spawnSync,
|
|
535
|
+
platform: options.platform
|
|
536
|
+
});
|
|
537
|
+
const skillLinks = linkKitSkills(kitDir, manifest, home, { platform: options.platform });
|
|
538
|
+
for (const warning of [...assetLinks.warnings, ...skillLinks.warnings]) log(`警告: ${warning}`);
|
|
539
|
+
if (skillLinks.blockers.length > 0) {
|
|
540
|
+
for (const blocker of skillLinks.blockers) log(`導入できません: ${blocker}`);
|
|
541
|
+
return { exitCode: 1 };
|
|
542
|
+
}
|
|
543
|
+
writeKitsLedger(home, {
|
|
544
|
+
id: manifest.id,
|
|
545
|
+
version: manifest.version,
|
|
546
|
+
installedAt: new Date().toISOString(),
|
|
547
|
+
kitDir,
|
|
548
|
+
skills: skillLinks.linked,
|
|
549
|
+
assets: assetLinks.linked
|
|
550
|
+
});
|
|
551
|
+
ensureKitsMarketplace(home);
|
|
552
|
+
if (!hadInstalledKit) log(enableHint());
|
|
553
|
+
}
|
|
418
554
|
const packPath = findFile(destDir, 'PACK.json');
|
|
419
555
|
if (packPath) {
|
|
420
556
|
const items = registerInstalledPack(env, productId, packPath);
|
|
@@ -436,10 +572,11 @@ export async function runStoreCommand(args, options = {}) {
|
|
|
436
572
|
return { exitCode: 0 };
|
|
437
573
|
}
|
|
438
574
|
|
|
439
|
-
log('使い方: akari store <connect|status|install|download|disconnect>');
|
|
575
|
+
log('使い方: akari store <connect|status|install|uninstall|download|disconnect>');
|
|
440
576
|
log(' connect ブラウザで承認して接続(既定。--token akst_... で手動 / --no-open でブラウザを開かない / --url <base>)');
|
|
441
577
|
log(' status 接続状態と購入済み一覧');
|
|
442
578
|
log(' install <productId> [--from <zip>] 購入済み商品の導入(--from は手元 zip / PACK.json 素材を installed 索引へ登録)');
|
|
579
|
+
log(' uninstall <productId> 拡張キットの symlink と台帳登録を解除(展開済みファイルは保持)');
|
|
443
580
|
log(' download <productId> [--dest <dir>] 購入済み配布物の取得のみ');
|
|
444
581
|
log(' disconnect 接続解除');
|
|
445
582
|
return { exitCode: sub ? 1 : 0 };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from "./repo-assets.mjs";
|
|
4
|
+
|
|
5
|
+
const USAGE = "使い方: akari storyboard <projectDir> [--no-capture] [--captures <dir>] [--out <dir>]";
|
|
6
|
+
|
|
7
|
+
export async function runStoryboardCommand(args, options = {}) {
|
|
8
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
9
|
+
if (args.includes("--help")) {
|
|
10
|
+
log(USAGE);
|
|
11
|
+
return { exitCode: 0 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
15
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
16
|
+
const spawn = options.spawn ?? spawnSync;
|
|
17
|
+
if (!assets.storyboardScript) {
|
|
18
|
+
logError("akari storyboard の実行スクリプトが見つかりません。完全な AKARI Video を再導入してください:");
|
|
19
|
+
logError(" npm install -g akari-video");
|
|
20
|
+
return { exitCode: 2 };
|
|
21
|
+
}
|
|
22
|
+
const result = spawn(process.execPath, [assets.storyboardScript, ...args], { stdio: "inherit" });
|
|
23
|
+
return { exitCode: typeof result?.status === "number" ? result.status : 1 };
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
6
|
+
|
|
7
|
+
export async function runWorldCommand(args, options = {}) {
|
|
8
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
9
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
10
|
+
const script = assets.repoRoot ? path.join(assets.repoRoot, 'packages', 'akari-tools', 'bin', 'world.mjs') : null;
|
|
11
|
+
if (!script || !existsSync(script)) {
|
|
12
|
+
logError('内部コマンド world の実行スクリプトが見つかりません。AKARI Video の完全な checkout または配布物を確認してください。');
|
|
13
|
+
return { exitCode: 1 };
|
|
14
|
+
}
|
|
15
|
+
const result = (options.spawn ?? spawnSync)(process.execPath, [script, ...args], { stdio: 'inherit' });
|
|
16
|
+
return { exitCode: typeof result?.status === 'number' ? result.status : 1 };
|
|
17
|
+
}
|
|
@@ -67,7 +67,9 @@
|
|
|
67
67
|
"docs/contract-2026-09-06-cut-audio-split-v0.md",
|
|
68
68
|
"docs/contract-2026-09-06-vgpu-layer-v0.md",
|
|
69
69
|
"docs/contract-2026-09-12-review-session-viewer.md",
|
|
70
|
+
"docs/contract-2026-09-13-extension-kit-v0.md",
|
|
70
71
|
"docs/contract-2026-09-13-generation-v0.md",
|
|
72
|
+
"docs/contract-2026-09-13-world-map-v0.md",
|
|
71
73
|
"packages/akari-launcher/package.json",
|
|
72
74
|
"packages/akari-launcher/README.md",
|
|
73
75
|
"packages/akari-tools/package.json",
|
|
@@ -188,6 +190,7 @@
|
|
|
188
190
|
"skills/overlay-authoring/telop.md",
|
|
189
191
|
"skills/overlay-authoring/text-behind-person.md",
|
|
190
192
|
"skills/overlay-authoring/thumbnail.md",
|
|
193
|
+
"skills/overlay-authoring/world.md",
|
|
191
194
|
"skills/render-cut/SKILL.md",
|
|
192
195
|
"skills/research-plan/competitor.md",
|
|
193
196
|
"skills/research-plan/ideate.md",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 設計契約 — 拡張キット v0(技術契約)
|
|
2
|
+
|
|
3
|
+
## 1. 定義と構成
|
|
4
|
+
|
|
5
|
+
拡張キットは、公開されている AKARI Video の器へスキル・テンプレート・素材・説明書を追加するコンテキスト束である。既存の `akari store install <productId>` が次へ展開する。
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
~/.akari/assets/store/<productId>/
|
|
9
|
+
├── manifest.json
|
|
10
|
+
├── skills/<skill-name>/SKILL.md
|
|
11
|
+
├── templates/<name>.json
|
|
12
|
+
├── assets/<category>/<id>/{meta.json, …}
|
|
13
|
+
├── docs/*.md
|
|
14
|
+
├── README.md
|
|
15
|
+
├── LICENSE.md
|
|
16
|
+
└── checksums.txt
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`productId` は Store の商品 id、`version` は商品の整数版である。キット全体のライセンスは `LicenseRef-AKARI-Assets-v0` とし、各素材の `meta.json` も個別の `license` を持つ。
|
|
20
|
+
|
|
21
|
+
## 2. `manifest.json`
|
|
22
|
+
|
|
23
|
+
`schemaVersion: 1` の manifest は additive-only とする。例:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"schemaVersion": 1,
|
|
28
|
+
"id": "world-kit",
|
|
29
|
+
"kind": "kit",
|
|
30
|
+
"name": "ワールドキット",
|
|
31
|
+
"version": 1,
|
|
32
|
+
"requires": {
|
|
33
|
+
"cli": ">=0.1.70",
|
|
34
|
+
"runtimes": ["world", "three"],
|
|
35
|
+
"products": ["akari-pop-motion-set"]
|
|
36
|
+
},
|
|
37
|
+
"skills": [{ "dir": "skills/design-world", "name": "design-world" }],
|
|
38
|
+
"templates": [{ "path": "templates/paper-to-browser.json", "for": "world-map", "label": "紙の地図 → ブラウザの中" }],
|
|
39
|
+
"assets": [{ "category": "overlay", "id": "world-far-bands" }],
|
|
40
|
+
"docs": [{ "path": "docs/world-hen.md", "label": "ワールド編(抜粋)" }],
|
|
41
|
+
"license": "LicenseRef-AKARI-Assets-v0",
|
|
42
|
+
"provenance": { "author": "AKARI Labs", "source": "example:world-kit" }
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
必須フィールドは `schemaVersion`、`id`、`kind`、`name`、`version`、`requires.cli`、`license`。`kind` は v0 では `kit` のみ。`validate-kit-manifest.mjs` はスキル実体、素材メタデータ、テンプレート用途語彙を含めて検査する。
|
|
47
|
+
|
|
48
|
+
## 3. 展開
|
|
49
|
+
|
|
50
|
+
`akari store install <productId>` は従来の entitlement 確認、zip 取得、checksum 照合、展開を終えたあと、`manifest.json` がある商品だけ次を行う。
|
|
51
|
+
|
|
52
|
+
1. manifest と `requires` を検査する。CLI または runtime の不足は fail-closed、依存商品の不足は警告と導入案内にする。
|
|
53
|
+
2. `assets[]` を `~/.akari/assets/<category>/<id>` へ相対 symlink で公開する。各素材はリンク前に `validate-asset.mjs` で検査する。
|
|
54
|
+
3. `skills[]` を `~/.akari/kits/plugin/skills/<name>` へ相対 symlink で公開する。
|
|
55
|
+
4. `~/.akari/kits/installed.json` に id、version、導入日時、展開先、スキル、素材を記録する。
|
|
56
|
+
5. `templates[]` は移動せず、CLI が各展開先の manifest を列挙して読む。
|
|
57
|
+
|
|
58
|
+
manifest が無い商品は従来の素材商品として扱う。検査器や runtime registry が npm 配布物に同梱されておらず照合できない場合は、その検査だけを警告付きでスキップする。`akari store uninstall <productId>` は台帳に記録した symlink と台帳エントリを外し、再導入用の展開ディレクトリは残す。
|
|
59
|
+
|
|
60
|
+
## 4. スキルの発見
|
|
61
|
+
|
|
62
|
+
Claude Code 向けには `~/.akari/kits` を directory marketplace として生成する。`plugin/skills/` は全キットの合成ディレクトリで、名前空間は純正の `akari:` と分離した `akari-kits:` になる。初回だけ次を実行する。
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
claude plugin marketplace add ~/.akari/kits
|
|
66
|
+
claude plugin install akari-kits@akari-kits
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`claude` が PATH に無い場合は、Claude Code のプラグイン設定で `~/.akari/kits` を marketplace として追加する。SessionStart hook は導入済みキットがあり、`enabledPlugins` に `akari-kits@akari-kits` が無い場合だけこの案内を 1 行表示し、設定を変更しない。
|
|
70
|
+
|
|
71
|
+
Codex、Cursor、opencode では、プロジェクトの `.agents/.codex/.cursor/.opencode/skills` へキットスキルも合成する。同名があれば純正スキルを優先する。plugin が利用できない環境でも `~/.akari/kits/plugin/skills/<name>/SKILL.md` を直接読める。
|
|
72
|
+
|
|
73
|
+
## 5. アプリ(ホームの拡張キットカード)
|
|
74
|
+
|
|
75
|
+
ホームの AKARI Store カードの隣に拡張キットカードを 1 枚出し、未接続では出さず、導入済みは id・version・スキル名・素材数の一覧と未有効化時の有効化案内、購入済み・未導入は `akari store install <id>` の案内、未購入は教材の引換ページの案内、という 3 状態とする(アプリはコマンドを実行せず、コピーと外部ブラウザ起動だけを行う)。
|
|
76
|
+
|
|
77
|
+
## 6. 更新と版
|
|
78
|
+
|
|
79
|
+
キットの版は Store の整数版とする。`akari store status` が導入済みの id、version、スキル名、素材数を表示し、同じ `akari store install` で新版へ置換する。
|