akari-video 0.1.66 → 0.1.68
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 +7 -1
- package/package.json +1 -1
- package/src/doctor-command.mjs +5 -0
- package/src/generate-command.mjs +16 -0
- package/src/kits.mjs +238 -0
- package/src/messages.mjs +2 -0
- package/src/repo-assets.mjs +13 -1
- package/src/runtime-diagnostics.mjs +29 -0
- 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 +9 -0
- package/vendor/docs/contract-2026-07-20-plan-json-v0.md +12 -0
- package/vendor/docs/contract-2026-07-25-plan-comments-v0.md +10 -11
- package/vendor/docs/contract-2026-09-13-extension-kit-v0.md +75 -0
- package/vendor/docs/contract-2026-09-13-generation-v0.md +195 -0
- package/vendor/docs/contract-2026-09-13-world-map-v0.md +50 -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/creator-root/src/index.mjs +6 -0
- package/vendor/packages/decision-cards/README.md +12 -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 +8 -1
- package/vendor/packages/edit-store/lib/generation-meta.js +21 -7
- package/vendor/packages/generate/README.md +6 -0
- package/vendor/packages/generate/package.json +5 -1
- package/vendor/packages/overlay-runtime/README.md +20 -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/packages/schemas/bin/validate-connections.mjs +21 -1
- package/vendor/packages/schemas/bin/validate-kit-manifest.mjs +374 -0
- package/vendor/packages/schemas/bin/validate-plan-comments.mjs +2 -2
- package/vendor/packages/schemas/bin/validate-world-map.mjs +170 -0
- package/vendor/packages/schemas/connections.schema.json +25 -0
- package/vendor/packages/schemas/examples/connections-v0-defaults-absent-valid/connections.json +8 -0
- package/vendor/packages/schemas/examples/connections-v0-defaults-unknown-key-invalid/connections.json +13 -0
- package/vendor/packages/schemas/examples/connections-v0-defaults-valid/connections.json +14 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/manifest.json +9 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/manifest.json +8 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/manifest.json +12 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/manifest.json +12 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/manifest.json +12 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/skills/manifest-name/SKILL.md +6 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/LICENSE.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/README.md +1 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/manifest.json +12 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/skills/edit-lint/SKILL.md +6 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/LICENSE.md +3 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/README.md +3 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/docs/guide.md +3 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/manifest.json +27 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/skills/sample-kit-skill/SKILL.md +8 -0
- package/vendor/packages/schemas/examples/kit-manifest-v1-valid/templates/sample.json +3 -0
- package/vendor/packages/schemas/examples/world-map-v1-no-worlds-legacy/planning/world-map.json +12 -0
- package/vendor/packages/schemas/examples/world-map-v2-flat-legacy/planning/world-map.json +10 -0
- package/vendor/packages/schemas/examples/world-map-v2-spatial-legacy/planning/world-map.json +27 -0
- package/vendor/packages/schemas/examples/world-map-v3-flat-valid/planning/world-map.json +34 -0
- package/vendor/packages/schemas/examples/world-map-v3-invalid-c10-same-world-portal/planning/world-map.json +34 -0
- package/vendor/packages/schemas/examples/world-map-v3-invalid-c3-edge-timing/planning/world-map.json +34 -0
- package/vendor/packages/schemas/examples/world-map-v3-invalid-c5-carry/planning/world-map.json +34 -0
- package/vendor/packages/schemas/examples/world-map-v3-invalid-c6-push-in-spatial/planning/world-map.json +26 -0
- package/vendor/packages/schemas/examples/world-map-v3-invalid-c7-cut-cover-null/planning/world-map.json +34 -0
- package/vendor/packages/schemas/examples/world-map-v3-spatial-valid/planning/world-map.json +26 -0
- package/vendor/packages/schemas/kit-manifest.schema.json +108 -0
- package/vendor/packages/schemas/package.json +3 -1
- package/vendor/packages/schemas/plan-comments.schema.json +3 -3
- package/vendor/packages/schemas/test/kit-manifest.test.mjs +83 -0
- package/vendor/packages/schemas/test/validate-connections.test.mjs +21 -0
- package/vendor/packages/schemas/test/validate-plan-comments.test.mjs +22 -0
- package/vendor/packages/schemas/test/world-map-schema.test.mjs +116 -0
- package/vendor/packages/schemas/world-map.schema.json +166 -0
- package/vendor/skills/edit-plan/approvals-and-generation.md +2 -2
- package/vendor/skills/edit-plan/plan-json.md +5 -5
- package/vendor/skills/edit-plan/workflow.md +6 -5
- package/vendor/skills/generate-media/SKILL.md +43 -0
- package/vendor/skills/generate-media/chain.md +32 -0
- package/vendor/skills/generate-media/engines.md +23 -0
- package/vendor/skills/generate-media/still.md +79 -0
- package/vendor/skills/generate-media/video.md +54 -0
- package/vendor/skills/manage-connections/SKILL.md +20 -5
- package/vendor/skills/manage-connections/bin/doctor.mjs +71 -29
- package/vendor/skills/manage-connections/bin/resolve-connections.mjs +35 -3
- package/vendor/skills/manage-connections/test/doctor-key-source.test.mjs +74 -0
- package/vendor/skills/manage-connections/test/resolve-connections-defaults.test.mjs +78 -0
- package/vendor/skills/overlay-authoring/3d.md +4 -1
- 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 +12 -0
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
|
@@ -13,6 +13,9 @@ import { runAssetsCommand } from '../src/assets-command.mjs';
|
|
|
13
13
|
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
|
+
import { runGenerateCommand } from '../src/generate-command.mjs';
|
|
17
|
+
import { runStoryboardCommand } from '../src/storyboard-command.mjs';
|
|
18
|
+
import { runWorldCommand } from '../src/world-command.mjs';
|
|
16
19
|
import { resolveRuntimePaths } from '../src/runtime-diagnostics.mjs';
|
|
17
20
|
import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
|
|
18
21
|
import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
|
|
@@ -35,7 +38,7 @@ async function printVersion() {
|
|
|
35
38
|
// `--help` は claude/opencode へそのまま転送されてしまっていた — AKARI Video 自身の
|
|
36
39
|
// コマンド一覧が一度も出ない行き止まりだったため新設した)。
|
|
37
40
|
async function printCliHelp() {
|
|
38
|
-
for (const line of describeCliHelp()) {
|
|
41
|
+
for (const line of [...describeCliHelp(), ' world ワールド地図を検査・生成・プレビュー']) {
|
|
39
42
|
console.log(line);
|
|
40
43
|
}
|
|
41
44
|
return { exitCode: 0 };
|
|
@@ -80,6 +83,9 @@ const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
|
|
|
80
83
|
: argv[0] === 'assets' ? runAssetsCommand(argv.slice(1))
|
|
81
84
|
: argv[0] === 'migrate' ? runMigrateCommand(argv.slice(1))
|
|
82
85
|
: argv[0] === 'clean' ? runCleanCommand(argv.slice(1))
|
|
86
|
+
: argv[0] === 'generate' ? runGenerateCommand(argv.slice(1))
|
|
87
|
+
: argv[0] === 'storyboard' ? runStoryboardCommand(argv.slice(1))
|
|
88
|
+
: argv[0] === 'world' ? runWorldCommand(argv.slice(1))
|
|
83
89
|
: run(argv);
|
|
84
90
|
|
|
85
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.68",
|
|
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/doctor-command.mjs
CHANGED
|
@@ -36,6 +36,11 @@ export function formatDoctorReport(report) {
|
|
|
36
36
|
['ffmpeg', report.ffmpeg.origin, report.ffmpeg.path ?? '見つかりません'],
|
|
37
37
|
['ffprobe', report.ffprobe.origin, report.ffprobe.path ?? '見つかりません'],
|
|
38
38
|
['gpu_export', report.gpu_export?.available ? 'ok' : 'unavailable', report.gpu_export?.reason ?? '診断情報がありません'],
|
|
39
|
+
['fal_key', report.fal_key?.source ?? 'missing', report.fal_key?.source === 'env'
|
|
40
|
+
? '環境変数 FAL_KEY'
|
|
41
|
+
: report.fal_key?.source === 'credentials.env'
|
|
42
|
+
? report.fal_key.credentials_path
|
|
43
|
+
: '環境変数にも credentials.env にもありません'],
|
|
39
44
|
['path', report.path.on_path ? 'ok' : 'missing', report.path.cli_shim_dir],
|
|
40
45
|
];
|
|
41
46
|
const widths = [
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from "./repo-assets.mjs";
|
|
4
|
+
|
|
5
|
+
export async function runGenerateCommand(args, options = {}) {
|
|
6
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
7
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
8
|
+
const spawn = options.spawn ?? spawnSync;
|
|
9
|
+
if (!assets.generateScript) {
|
|
10
|
+
logError("akari generate の実行スクリプトが見つかりません。完全な AKARI Video を再導入してください:");
|
|
11
|
+
logError(" npm install -g akari-video");
|
|
12
|
+
return { exitCode: 1 };
|
|
13
|
+
}
|
|
14
|
+
const result = spawn(process.execPath, [assets.generateScript, ...args], { stdio: "inherit" });
|
|
15
|
+
return { exitCode: typeof result?.status === "number" ? result.status : 1 };
|
|
16
|
+
}
|
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
|
@@ -330,6 +330,8 @@ export function describeCliHelp() {
|
|
|
330
330
|
' update [--force] 更新を確認する(--force で install.sh 経路の本体を入れ直す)',
|
|
331
331
|
' status 接続状態を確認する',
|
|
332
332
|
' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
|
|
333
|
+
' generate 台本のビートから静止画クリップを生成',
|
|
334
|
+
' storyboard タイムラインから印刷用の絵コンテを作成',
|
|
333
335
|
' akari clean [dir] 使い捨ての中間ファイルを一覧・削除(既定は一覧のみ)',
|
|
334
336
|
'',
|
|
335
337
|
'開発者向け:',
|
package/src/repo-assets.mjs
CHANGED
|
@@ -39,6 +39,8 @@ const CAPTIONS_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'ca
|
|
|
39
39
|
export const CAPTURE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'capture.mjs');
|
|
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
|
+
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');
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold 実装・creator-root
|
|
@@ -60,6 +62,8 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
60
62
|
const mediaScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'media.mjs');
|
|
61
63
|
const decisionLogScript = path.join(repoRoot, DECISION_LOG_SCRIPT_RELATIVE);
|
|
62
64
|
const wordBookScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'word-book.mjs');
|
|
65
|
+
const generateScript = path.join(repoRoot, GENERATE_CLI_RELATIVE);
|
|
66
|
+
const storyboardScript = path.join(repoRoot, STORYBOARD_CLI_RELATIVE);
|
|
63
67
|
|
|
64
68
|
return {
|
|
65
69
|
repoRoot,
|
|
@@ -79,7 +83,9 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
79
83
|
eyeBarScript: existsSync(eyeBarScript) ? eyeBarScript : null,
|
|
80
84
|
mediaScript: existsSync(mediaScript) ? mediaScript : null,
|
|
81
85
|
...(existsSync(decisionLogScript) ? { decisionLogScript } : {}),
|
|
82
|
-
...(existsSync(wordBookScript) ? { wordBookScript } : {})
|
|
86
|
+
...(existsSync(wordBookScript) ? { wordBookScript } : {}),
|
|
87
|
+
...(existsSync(generateScript) ? { generateScript } : {}),
|
|
88
|
+
...(existsSync(storyboardScript) ? { storyboardScript } : {})
|
|
83
89
|
};
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -115,6 +121,12 @@ export function resolveLauncherAssets({
|
|
|
115
121
|
renderWhenIdleScript: candidate.renderWhenIdleScript ?? vendor.renderWhenIdleScript,
|
|
116
122
|
eyeBarScript: candidate.eyeBarScript ?? vendor.eyeBarScript,
|
|
117
123
|
mediaScript: candidate.mediaScript ?? vendor.mediaScript,
|
|
124
|
+
...(candidate.generateScript ?? vendor.generateScript
|
|
125
|
+
? { generateScript: candidate.generateScript ?? vendor.generateScript }
|
|
126
|
+
: {}),
|
|
127
|
+
...(candidate.storyboardScript ?? vendor.storyboardScript
|
|
128
|
+
? { storyboardScript: candidate.storyboardScript ?? vendor.storyboardScript }
|
|
129
|
+
: {}),
|
|
118
130
|
...(candidate.decisionLogScript ?? vendor.decisionLogScript ? { decisionLogScript: candidate.decisionLogScript ?? vendor.decisionLogScript } : {}),
|
|
119
131
|
...(candidate.wordBookScript ?? vendor.wordBookScript
|
|
120
132
|
? { wordBookScript: candidate.wordBookScript ?? vendor.wordBookScript }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
2
3
|
import path, { dirname, join, resolve } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
|
|
@@ -132,6 +133,7 @@ export async function resolveDoctorReport(options = {}) {
|
|
|
132
133
|
const runtime = resolveRuntimePaths(options);
|
|
133
134
|
const mediaBin = await resolveMediaTools({ ...options, env, platform, pathEnv, pathExt });
|
|
134
135
|
const gpuExport = await resolveGpuExportAvailability({ ...options, env, platform });
|
|
136
|
+
const falKey = resolveFalKeySource({ env, homeDirectory: options.homeDirectory });
|
|
135
137
|
const akariHome = resolveAkariHome(env);
|
|
136
138
|
const cliShimDir = join(akariHome, 'cli', 'bin');
|
|
137
139
|
const report = {
|
|
@@ -148,6 +150,7 @@ export async function resolveDoctorReport(options = {}) {
|
|
|
148
150
|
ffmpeg: mediaBin.ffmpeg,
|
|
149
151
|
ffprobe: mediaBin.ffprobe,
|
|
150
152
|
gpu_export: gpuExport,
|
|
153
|
+
fal_key: falKey,
|
|
151
154
|
path: {
|
|
152
155
|
cli_shim_dir: cliShimDir,
|
|
153
156
|
on_path: pathContains(pathEnv, cliShimDir, platform),
|
|
@@ -158,6 +161,29 @@ export async function resolveDoctorReport(options = {}) {
|
|
|
158
161
|
return report;
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
function resolveFalKeySource({ env, homeDirectory }) {
|
|
165
|
+
const credentialsPath = env.AKARI_CREDENTIALS_FILE
|
|
166
|
+
?? join(homeDirectory ?? homedir(), '.config', 'akari-video', 'credentials.env');
|
|
167
|
+
if (typeof env.FAL_KEY === 'string' && env.FAL_KEY.trim().length > 0) {
|
|
168
|
+
return { source: 'env', credentials_path: credentialsPath };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const source = readFileSync(credentialsPath, 'utf8');
|
|
172
|
+
for (const originalLine of source.split(/\r?\n/u)) {
|
|
173
|
+
const line = originalLine.trim();
|
|
174
|
+
if (!line || line.startsWith('#')) continue;
|
|
175
|
+
const separator = line.indexOf('=');
|
|
176
|
+
if (separator < 1 || line.slice(0, separator).trim() !== 'FAL_KEY') continue;
|
|
177
|
+
if (line.slice(separator + 1).trim().length > 0) {
|
|
178
|
+
return { source: 'credentials.env', credentials_path: credentialsPath };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// credentials.env が無い・読めない場合も doctor 自体は継続する。
|
|
183
|
+
}
|
|
184
|
+
return { source: 'missing', credentials_path: credentialsPath };
|
|
185
|
+
}
|
|
186
|
+
|
|
161
187
|
export async function resolveGpuExportAvailability(options = {}) {
|
|
162
188
|
const platform = options.platform ?? process.platform;
|
|
163
189
|
try {
|
|
@@ -265,6 +291,9 @@ function doctorNextSteps(report) {
|
|
|
265
291
|
if (!report.path.on_path && report.render_cut.origin !== 'monorepo') {
|
|
266
292
|
steps.push('`~/.akari/cli/bin` を PATH に追加してください。');
|
|
267
293
|
}
|
|
294
|
+
if (report.fal_key?.source === 'missing') {
|
|
295
|
+
steps.push('FAL_KEY がありません。環境変数 FAL_KEY か credentials.env に置くと fal の生成が使えます(無くても書き出しは動きます)。');
|
|
296
|
+
}
|
|
268
297
|
return steps;
|
|
269
298
|
}
|
|
270
299
|
|
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
|
+
}
|