akari-video 1.1.0 → 1.2.0-beta.1
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/package.json +1 -1
- package/src/doctor-command.mjs +7 -2
- package/src/migrate-command.mjs +8 -3
- package/vendor/.akari-capability-sources.json +4 -0
- package/vendor/docs/contract-2026-07-13-asset-library.md +1 -1
- package/vendor/docs/contract-2026-07-25-project-structure-v0.md +3 -0
- package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +9 -4
- package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +4 -0
- package/vendor/docs/contract-2026-10-04-asset-tier-v1.md +8 -0
- package/vendor/packages/akari-ear/README.md +9 -0
- package/vendor/packages/akari-ear/package.json +11 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/akari-vibe/README.md +2 -2
- package/vendor/packages/asset-resolver/bin/akari-assets.mjs +10 -7
- package/vendor/packages/asset-resolver/src/bundle.mjs +1 -1
- package/vendor/packages/asset-resolver/src/catalog.mjs +21 -4
- package/vendor/packages/asset-resolver/src/env.mjs +15 -0
- package/vendor/packages/asset-resolver/src/fetch-file.mjs +42 -5
- package/vendor/packages/asset-resolver/src/list-filter.mjs +10 -0
- package/vendor/packages/asset-resolver/src/pro-asset.mjs +103 -0
- package/vendor/packages/asset-resolver/src/project-references.mjs +5 -0
- package/vendor/packages/asset-resolver/src/resolve.mjs +210 -32
- package/vendor/packages/asset-resolver/src/state.mjs +1 -1
- package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +26 -1
- package/vendor/packages/asset-resolver/test/fixtures/fragment-root-timing.json +35 -0
- package/vendor/packages/asset-resolver/test/fragment-project-copy.test.mjs +45 -0
- package/vendor/packages/asset-resolver/test/installed-assets.test.mjs +2 -2
- package/vendor/packages/asset-resolver/test/list-filter.test.mjs +54 -0
- package/vendor/packages/asset-resolver/test/project-references.test.mjs +6 -1
- package/vendor/packages/asset-resolver/test/resolve-fast.test.mjs +12 -0
- package/vendor/packages/asset-resolver/test/resolve-paid-zip.test.mjs +9 -0
- package/vendor/packages/asset-resolver/test/resolve-pro-asset.test.mjs +445 -0
- package/vendor/packages/edit-lint/src/engine-capabilities.json +1 -0
- package/vendor/packages/edit-lint/src/lint/captions.mjs +12 -2
- package/vendor/packages/edit-lint/src/lint/edit-v2.mjs +13 -0
- package/vendor/packages/edit-lint/src/lint/overlays.mjs +4 -26
- package/vendor/packages/edit-store/lib/caption-clock.d.ts +3 -0
- package/vendor/packages/edit-store/lib/caption-clock.js +57 -6
- package/vendor/packages/edit-store/lib/caption-display.d.ts +9 -0
- package/vendor/packages/edit-store/lib/caption-display.js +181 -29
- package/vendor/packages/edit-store/lib/caption-store.d.ts +1 -1
- package/vendor/packages/edit-store/lib/caption-store.js +1 -1
- package/vendor/packages/edit-store/lib/caption-timeline.d.ts +32 -0
- package/vendor/packages/edit-store/lib/caption-timeline.js +87 -0
- package/vendor/packages/edit-store/lib/cut-ranges.d.ts +8 -0
- package/vendor/packages/edit-store/lib/cut-ranges.js +664 -8
- package/vendor/packages/edit-store/lib/edit-v2.d.ts +8 -0
- package/vendor/packages/edit-store/lib/edit-v2.js +20 -2
- package/vendor/packages/edit-store/lib/empty-edit-v2.d.ts +8 -0
- package/vendor/packages/edit-store/lib/empty-edit-v2.js +17 -0
- package/vendor/packages/edit-store/lib/generated/edit-v2-keys.d.ts +2 -2
- package/vendor/packages/edit-store/lib/generated/edit-v2-keys.js +2 -1
- package/vendor/packages/edit-store/lib/index.d.ts +1 -0
- package/vendor/packages/edit-store/lib/index.js +1 -0
- package/vendor/packages/edit-store/lib/internal-model.d.ts +2 -0
- package/vendor/packages/edit-store/lib/internal-model.js +4 -49
- package/vendor/packages/edit-store/lib/migrate/index.d.ts +2 -0
- package/vendor/packages/edit-store/lib/migrate/index.js +14 -1
- package/vendor/packages/edit-store/lib/webview-kernel.js +4915 -4758
- package/vendor/packages/frame-scene/README.md +62 -0
- package/vendor/packages/frame-scene/package.json +9 -0
- package/vendor/packages/overlay-runtime/src/fragment-source-write.mjs +16 -1
- package/vendor/packages/overlay-runtime/src/handle-geometry.js +50 -5
- package/vendor/packages/overlay-runtime/src/interaction.js +217 -151
- package/vendor/packages/project-scaffold/src/index.mjs +2 -6
- package/vendor/packages/render-cut/README.ja.md +2 -0
- package/vendor/packages/render-cut/README.md +2 -0
- package/vendor/packages/schemas/asset-meta.schema.json +25 -0
- package/vendor/packages/schemas/bin/validate-captions.mjs +13 -4
- package/vendor/packages/schemas/bin/validate-frame-scene.mjs +24 -0
- package/vendor/packages/schemas/captions.schema.json +2 -1
- package/vendor/packages/schemas/edit.schema.json +9 -0
- package/vendor/packages/schemas/engine-capabilities.json +1 -0
- package/vendor/packages/schemas/fixtures/asset-meta-frame-scene/model-invalid-unit.json +129 -0
- package/vendor/packages/schemas/fixtures/asset-meta-frame-scene/model-valid.json +129 -0
- package/vendor/packages/schemas/fixtures/asset-meta-frame-scene/world-invalid-kind.json +114 -0
- package/vendor/packages/schemas/fixtures/asset-meta-frame-scene/world-valid.json +114 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/anchor.unresolved/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/anchor.unresolved/scene.json +37 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/limit.objects/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/limit.objects/scene.json +2029 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.id.duplicate/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.id.duplicate/scene.json +37 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.rotation.exclusive/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.rotation.exclusive/scene.json +30 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.size.exclusive/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/object.size.exclusive/scene.json +30 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/on.unresolved/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/on.unresolved/scene.json +29 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/pose.joint.unknown/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/pose.joint.unknown/scene.json +35 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/pose.kind.invalid/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/pose.kind.invalid/scene.json +34 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/scene.empty/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/scene.empty/scene.json +16 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/unsupported_version/expected.json +6 -0
- package/vendor/packages/schemas/fixtures/frame-scene/invalid/unsupported_version/scene.json +28 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/at-limits/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/at-limits/scene.json +1 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/camera-a-b/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/camera-a-b/scene.json +36 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/empty-stage-scribble/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/empty-stage-scribble/scene.json +30 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/hdri-hidden/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/hdri-hidden/scene.json +33 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/minimal/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/minimal/scene.json +28 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/multi-annotation-anchor/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/multi-annotation-anchor/scene.json +70 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/multi-scene-set/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/multi-scene-set/scene.json +40 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/pose-targets/expected.json +4 -0
- package/vendor/packages/schemas/fixtures/frame-scene/valid/pose-targets/scene.json +40 -0
- package/vendor/packages/schemas/fixtures/generation-meta/frame-invalid-hash.json +84 -0
- package/vendor/packages/schemas/fixtures/generation-meta/frame-invalid-kind.json +84 -0
- package/vendor/packages/schemas/fixtures/generation-meta/frame-invalid-role.json +84 -0
- package/vendor/packages/schemas/fixtures/generation-meta/frame-valid.json +84 -0
- package/vendor/packages/schemas/frame-scene.schema.json +99 -0
- package/vendor/packages/schemas/generation-meta.schema.json +39 -0
- package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +15 -0
- package/vendor/packages/schemas/test/engine-capabilities.test.mjs +1 -1
- package/vendor/packages/schemas/test/frame-scene-fixtures.test.mjs +18 -0
- package/vendor/packages/schemas/test/meta-frame-model3d-compat.test.mjs +25 -0
- package/vendor/packages/schemas/test/validate-captions.test.mjs +29 -1
- package/vendor/skills/design-world/SKILL.md +1 -1
- package/vendor/skills/edit-plan/expression-selection.md +4 -2
- package/vendor/skills/overlay-authoring/SKILL.md +18 -1
- package/vendor/skills/overlay-authoring/table.md +17 -0
- package/vendor/skills/overlay-authoring/telop.md +2 -0
- package/vendor/skills/setup-audio-library/first-party.md +1 -1
- package/vendor/skills/setup-library/SKILL.md +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.1",
|
|
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
|
@@ -60,9 +60,14 @@ export function formatDoctorReport(report) {
|
|
|
60
60
|
'',
|
|
61
61
|
`判定: ${report.verdict}`,
|
|
62
62
|
];
|
|
63
|
-
|
|
63
|
+
const steps = [...report.next_steps];
|
|
64
|
+
if (report.cli.node?.runtime === 'electron' && report.cli.node.exec_path && report.render_cut.path) {
|
|
65
|
+
const quote = (value) => `"${value.replaceAll('"', '\\"')}"`;
|
|
66
|
+
steps.push(`Node が PATH に無い場合の書き出し: ELECTRON_RUN_AS_NODE=1 ${quote(report.cli.node.exec_path)} ${quote(report.render_cut.path)} <project-root>`);
|
|
67
|
+
}
|
|
68
|
+
if (steps.length > 0) {
|
|
64
69
|
lines.push('次の手順:');
|
|
65
|
-
lines.push(...
|
|
70
|
+
lines.push(...steps.map((step) => ` - ${step}`));
|
|
66
71
|
}
|
|
67
72
|
return lines.join('\n');
|
|
68
73
|
}
|
package/src/migrate-command.mjs
CHANGED
|
@@ -64,11 +64,14 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
64
64
|
}
|
|
65
65
|
if (parsed.json) {
|
|
66
66
|
log(JSON.stringify({
|
|
67
|
-
ok: true, dryRun: parsed.dryRun, version: proposal.version,
|
|
67
|
+
ok: true, dryRun: parsed.dryRun, version: proposal.emptyProject ? null : proposal.version,
|
|
68
|
+
...(proposal.emptyProject ? { emptyProject: true } : {}),
|
|
68
69
|
filePath: proposal.filePath, backupPath: proposal.backupPath, changes: proposal.changes
|
|
69
70
|
}));
|
|
70
71
|
} else {
|
|
71
|
-
log(
|
|
72
|
+
log(proposal.emptyProject
|
|
73
|
+
? `初期化対象: ${proposal.filePath} (空の edit.json -> version 2)`
|
|
74
|
+
: `変換対象: ${proposal.filePath} (version ${proposal.version}${proposal.version === 2 ? ' 正規化' : ' -> 2'})`);
|
|
72
75
|
for (const change of proposal.changes) log(`- ${change.path}: ${change.note}`);
|
|
73
76
|
log(`変換前の退避先: ${proposal.backupPath}`);
|
|
74
77
|
}
|
|
@@ -92,7 +95,9 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
92
95
|
}
|
|
93
96
|
await migrate.applyMigration(proposal);
|
|
94
97
|
if (!parsed.json) {
|
|
95
|
-
log(proposal.
|
|
98
|
+
log(proposal.emptyProject
|
|
99
|
+
? `空の edit.json を初期化しました。元ファイル: ${proposal.backupPath}`
|
|
100
|
+
: proposal.version === 2
|
|
96
101
|
? `version 2 を正規化しました。元ファイル: ${proposal.backupPath}`
|
|
97
102
|
: `version 2 へ変換しました。元ファイル: ${proposal.backupPath}`);
|
|
98
103
|
}
|
|
@@ -83,6 +83,8 @@
|
|
|
83
83
|
"docs/contract-2026-10-02-textstyle-v1-rich.md",
|
|
84
84
|
"docs/contract-2026-10-04-asset-tier-v1.md",
|
|
85
85
|
"docs/contract-2026-10-06-timeline-ripple-v0.md",
|
|
86
|
+
"packages/akari-ear/package.json",
|
|
87
|
+
"packages/akari-ear/README.md",
|
|
86
88
|
"packages/akari-launcher/package.json",
|
|
87
89
|
"packages/akari-launcher/README.md",
|
|
88
90
|
"packages/akari-tools/package.json",
|
|
@@ -109,6 +111,8 @@
|
|
|
109
111
|
"packages/frame-engine/package.json",
|
|
110
112
|
"packages/frame-engine/README.ja.md",
|
|
111
113
|
"packages/frame-engine/README.md",
|
|
114
|
+
"packages/frame-scene/package.json",
|
|
115
|
+
"packages/frame-scene/README.md",
|
|
112
116
|
"packages/generate/package.json",
|
|
113
117
|
"packages/generate/README.md",
|
|
114
118
|
"packages/gpu-export/package.json",
|
|
@@ -373,7 +373,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
|
|
|
373
373
|
|
|
374
374
|
| v1 category | 形の定義 | 判定 | 旧 category |
|
|
375
375
|
|---|---|---|---|
|
|
376
|
-
| `overlay` | 時間を持つ HTML 断片 | `fragment.html`
|
|
376
|
+
| `overlay` | 時間を持つ HTML 断片 | `fragment.html` を `overlays[]` から合成。断片ルートに時刻を置かず、素材本来の長さは `data-akari-natural-duration`(秒)に記録 | `telop` / `motion` |
|
|
377
377
|
| `still` | 時間を持たない HTML シート | `fragment.html`。決定的スクショで画像に焼く | `thumbnail` |
|
|
378
378
|
| `scene3d` | 3D モデル + 表示断片、またはベイクレシピ | `fragment.html` + glTF、または `scene.py` | `3d` |
|
|
379
379
|
| `audio` | 音声トラックに載るバイナリ | `edit.json` の `audio.bgm` / `sfx` が参照 | `audio`(変更なし) |
|
|
@@ -40,6 +40,7 @@ updated: 2026-09-06
|
|
|
40
40
|
|---|---|---|
|
|
41
41
|
| 人間の一次素材 | `assets/`(下位分類自由) | 人間・取り込み UI |
|
|
42
42
|
| 企画・プラン | `planning/` | スキル + 人間承認 |
|
|
43
|
+
| 仮枠のシーン JSON(AI が書く・人が微調整。仮枠の meta の `frame.content.scene` が指す) | `planning/frames/` | スキル・仮枠の編集 |
|
|
43
44
|
| エージェント中間物・ad-hoc 検証スクリプトと結果 | `.akari/work/` | スキル・エージェント |
|
|
44
45
|
| 検証証跡・レポート(キーフレーム視認 PNG・render-report 等) | `.akari/reports/` | スキル |
|
|
45
46
|
| 絵コンテ(タイムラインの印刷・初回だけ・AI は後から読まない) | `.akari/reports/storyboard/` | スキル |
|
|
@@ -47,6 +48,8 @@ updated: 2026-09-06
|
|
|
47
48
|
| 納品物 | `exports/` | `render-cut` |
|
|
48
49
|
| 契約サイドカー(既存) | `.akari/`(`sidecars/` `diffs/` `events/` 等) | 既存のまま |
|
|
49
50
|
|
|
51
|
+
`planning/frames/<stem>.scene.json` は仮枠(生成 v0 契約)の中身を記述する正本で、変更履歴に入る(`planning/**`)。仮枠の絵(png)と meta は従来どおり `assets/generated/`。
|
|
52
|
+
|
|
50
53
|
`.akari/` 直下にはこの表にない契約サイドカーファイルも存在する(`intake.json`・
|
|
51
54
|
`workflow.json`・`connections.json`・`lint.json`・`render.json` 等)。これらは各自の
|
|
52
55
|
契約文書が正本であり、本契約は「置き場所の層」を追加で確定させるだけで、個々のファイル形は
|
|
@@ -42,10 +42,15 @@ overlap; and reference/output aspect mismatch. A resolved production render stor
|
|
|
42
42
|
projection digest, and its immutable render receipt references the file and summary.
|
|
43
43
|
Captions with `time_domain: "output"` (placed text) are exempt from overlap and order checks; any number may share the same time (2026-09-22 decision). Source-domain captions retain their per-source checks.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
`karaoke
|
|
47
|
-
|
|
48
|
-
`display_policy`,
|
|
45
|
+
2026-10-06 amendment: `display_policy.word_style` is the global word display default (`none` when
|
|
46
|
+
omitted, or `karaoke`). An explicit `captions[].style: "karaoke"` or `"plain"` takes priority over
|
|
47
|
+
that default; `plain` restores ordinary display for one row under a global karaoke policy.
|
|
48
|
+
Under `display_policy`, `pop`, `reveal`, and `reveal-word` remain `STYLE_CONFLICT` errors, while any
|
|
49
|
+
unknown style is `INVALID_CAPTION`. Karaoke uses measured `words[]` projected into each resolved
|
|
50
|
+
fragment. Missing words or synthetic timing fall back to ordinary text. Without `display_policy`,
|
|
51
|
+
caption style handling remains on the unchanged legacy path.
|
|
52
|
+
If measured words omit only punctuation, symbols, or whitespace, those characters remain visible as
|
|
53
|
+
untimed unlit tokens while the measured words keep karaoke timing; other text mismatches fall back to ordinary text.
|
|
49
54
|
|
|
50
55
|
`reference-pixel` geometry is scaled only after exact aspect agreement. `webkit-outline` produces
|
|
51
56
|
real `-webkit-text-stroke` plus `paint-order:stroke fill` and disables shadow. Single-line consumers
|
|
@@ -334,3 +334,7 @@ await p.save(); // 正規直列化 → lint ゲート
|
|
|
334
334
|
- 観察・手術の CLI コマンド(作らない。§5.2-5)
|
|
335
335
|
- 種類ごとに違うグループ(sequence / scene 等)— 「段」と「グループ」の 2 つだけ
|
|
336
336
|
- 文字ごとのキーフレーム(姉妹契約 §8)
|
|
337
|
+
|
|
338
|
+
## 音声片のカット端
|
|
339
|
+
|
|
340
|
+
edit.json v2 の audio lane にある分離済み speech item は、カットで素材端が失われたとき、任意の `cut_edge: { "in": 秒, "out": 秒, "at": 整数フレーム }` を持てる。`in` と `out` はこの音声片自身の元の素材端であり、頭がまたぎで削られた場合だけ `in` が現在の `source.in` と異なり、尻がまたぎで削られた場合だけ `out` が現在の `source.out` と異なる。両端が現在の素材端と等しくなれば欄を消す。`at` は「頭を `cut_edge.in` まで戻したときの開始フレーム」で、現在の `at - round((source.in - cut_edge.in) / 1 フレームぶんの素材秒)` から求める派生値である。カットで音声片を割ったときはそれぞれの片の端だけを保持し、結合時は左片の `in` と右片の `out` を採る。`in` と `out` は 0 以上の有限数で `in < out`、`at` は整数。先行カットのリップルを反映した途中状態では `cut_edge.at` が負になることもある。再生範囲の `source.in/out` と現在位置の `at` はそのまま正本とする。`provenance` はナレーションの生成元専用で、この用途には使わない。欄のない既存 item の意味は変わらない。
|
|
@@ -42,3 +42,11 @@ JSON Schema は `remote: true` なら `source` 必須までを表し、akari-r2
|
|
|
42
42
|
`preview` URL は `https://akari.video/lab/media/telop-rich-pack/textstyle/<id>.png` の形とする。
|
|
43
43
|
配置後は既存の textstyle ライブラリ読み込みが `preset.json` を読み、
|
|
44
44
|
「テキスト > スタイル」へ並べる。未取得 textstyle の棚カード表示は別票で扱う。
|
|
45
|
+
|
|
46
|
+
## § Pro item の 1 件ずつの取得(2026-10-06)
|
|
47
|
+
|
|
48
|
+
resolver は取得済みの素材をキャッシュから先に返す。新規取得の Pro item は店の entitlements と catalog 行で資格を事前判定し、資格があれば `GET /api/store/v1/assets/<category>/<id>` で記述子を取得する。記述子が示す各ファイルを Bearer 認証で取得し、`sha256` と `bytes` を照合する。`meta.json` を必須として `validate-asset` に通し、全件成功後に `<ライブラリ>/<category>/<id>/` へ原子的に配置する。失敗時は一時ディレクトリを破棄する。
|
|
49
|
+
|
|
50
|
+
記述子の `schema` は `akari-pro-asset/v1`。`category`、`id`、正の整数 `version` と、空でない `files` 配列を持つ。各 `files[]` は相対パス `name`、小文字 64 桁の `sha256`、0 以上の整数 `bytes`、記述子 API と同じオリジンの絶対 HTTP(S) `url` を持つ。`name` は `/` 区切りで、空の区間、`.`、`..`、先頭の `/`、バックスラッシュ、NUL を含めない。`name` は一意で `meta.json` を必ず含む。
|
|
51
|
+
|
|
52
|
+
記述子またはファイル取得で 409 `stale_version` が返ったら、一時取得物を捨てて記述子を 1 回だけ取り直す。記述子 API が 404 `asset_not_found` を返した場合だけ、`product_id ?? id` の束 zip 取得へ進む。束 zip は予備経路として段階的に退役する。記述子 API の上書きは `AKARI_PRO_ASSET_API` を使い、`AKARI_STORE_API`、接続情報の URL、既定ホストより優先する。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# AKARI バイブの耳
|
|
2
|
+
|
|
3
|
+
耳はマイクを開き、音声を文字にして辞書を適用する共有部品です。判断サービスの資格や鍵に依存せず、マイクの持ち主を 1 つにします。
|
|
4
|
+
|
|
5
|
+
段階 A では、試し聞きとメモ、区画の「いま」が耳を使います。Jev は従来どおり自分のマイクを使い、同時利用は禁止します。Jev 動作中は耳を使用中として扱います。
|
|
6
|
+
|
|
7
|
+
段階 B では、Jev の聞き取りも耳へ統一します。発話と音量を係の `/say` と `/level` へ渡し、マイクの持ち主を耳に一本化します。
|
|
8
|
+
|
|
9
|
+
現時点は骨組みのみで、実装は今後行います。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.1",
|
|
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 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// akari-assets — 素材 resolver v0 の CLI(list / add / fetch / bundle / migrate / sync / browse)。
|
|
3
3
|
//
|
|
4
|
-
// akari-assets list [--category <c>] [--source <lab|site|own>] [--json]
|
|
4
|
+
// akari-assets list [--category <c>] [--source <lab|site|own>] [--tag <t> ...] [--query <s>] [--json]
|
|
5
5
|
// akari-assets add <path...> --plan [--json]
|
|
6
6
|
// akari-assets add --apply <plan.json> [--json]
|
|
7
7
|
// akari-assets fetch <ref> [--project <dir>] [--reference] [--force] (ref は category/id)
|
|
@@ -19,6 +19,7 @@ import { resolve as resolveAsset } from '../src/resolve.mjs';
|
|
|
19
19
|
import { DEFAULT_CATALOG_URL, DEFAULT_STORE_API } from '../src/service-urls.mjs';
|
|
20
20
|
import { composeState } from '../src/state.mjs';
|
|
21
21
|
import { checkLibrary, projectCredits } from '../src/library-check.mjs';
|
|
22
|
+
import { filterListItems } from '../src/list-filter.mjs';
|
|
22
23
|
|
|
23
24
|
function flagValue(args, name) {
|
|
24
25
|
const i = args.indexOf(name);
|
|
@@ -28,7 +29,7 @@ function flagValue(args, name) {
|
|
|
28
29
|
// Validate the entire command before any handler can read or mutate user data.
|
|
29
30
|
function validateArgs(sub, args) {
|
|
30
31
|
const specs = {
|
|
31
|
-
list: { values: ['--category', '--source'], flags: ['--json'], max: 0 },
|
|
32
|
+
list: { values: ['--category', '--source', '--tag', '--query'], flags: ['--json'], max: 0 },
|
|
32
33
|
add: { values: ['--apply'], flags: ['--plan', '--json'], max: Infinity },
|
|
33
34
|
fetch: { values: ['--project'], flags: ['--reference', '--force'], min: 1, max: 1 },
|
|
34
35
|
bundle: { values: ['--project'], flags: ['--dry-run'], max: 0 },
|
|
@@ -46,7 +47,7 @@ function validateArgs(sub, args) {
|
|
|
46
47
|
const arg = args[i];
|
|
47
48
|
if (!arg.startsWith('-')) { positional.push(arg); continue; }
|
|
48
49
|
if (!spec.values.includes(arg) && !spec.flags.includes(arg)) throw new Error(`不明なオプション: ${arg}`);
|
|
49
|
-
if (seen.has(arg)) throw new Error(`重複したオプション: ${arg}`);
|
|
50
|
+
if (seen.has(arg) && !(sub === 'list' && arg === '--tag')) throw new Error(`重複したオプション: ${arg}`);
|
|
50
51
|
seen.add(arg);
|
|
51
52
|
if (spec.values.includes(arg) && (!args[++i] || args[i].startsWith('-'))) throw new Error(`${arg} には値が必要です`);
|
|
52
53
|
}
|
|
@@ -76,10 +77,12 @@ async function cmdList(args, env) {
|
|
|
76
77
|
const category = flagValue(args, '--category');
|
|
77
78
|
const asJson = args.includes('--json');
|
|
78
79
|
const source = flagValue(args, '--source');
|
|
80
|
+
const tags = args.flatMap((arg, index) => arg === '--tag' ? [args[index + 1]] : []);
|
|
81
|
+
const query = flagValue(args, '--query');
|
|
79
82
|
if (args.includes('--source') && !['lab', 'site', 'own'].includes(source)) throw new Error('--source は lab / site / own で指定してください');
|
|
80
83
|
const { libraryRoots, items, warnings } = await composeState({ env });
|
|
81
84
|
for (const warning of warnings) console.error(`警告: ${warning}`);
|
|
82
|
-
const filtered = items
|
|
85
|
+
const filtered = filterListItems(items, { category, source, tags, query });
|
|
83
86
|
|
|
84
87
|
if (asJson) {
|
|
85
88
|
console.log(JSON.stringify(filtered, null, 2));
|
|
@@ -88,7 +91,7 @@ async function cmdList(args, env) {
|
|
|
88
91
|
|
|
89
92
|
console.log(`使える素材 ${filtered.length} 件(ライブラリ: ${libraryRoots.write})`);
|
|
90
93
|
for (const item of filtered) {
|
|
91
|
-
console.log(` ${badgeOf(item)} ${item.category}/${item.id}\t${item.sourceKind}\t[${item.category}]\t${item.title}`);
|
|
94
|
+
console.log(` ${badgeOf(item)} ${item.category}/${item.id}\t${item.sourceKind}\t[${item.category}]\t${item.tier === 'pro' ? 'pro' : 'free'}\t${item.title}`);
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -199,8 +202,8 @@ async function cmdCredits(args, env) {
|
|
|
199
202
|
function printUsage() {
|
|
200
203
|
console.log(`使い方: akari-assets <list|add|fetch|bundle|migrate|sync|browse|check|credits> [options]
|
|
201
204
|
|
|
202
|
-
list [--category <c>] [--source <lab|site|own>] [--json]
|
|
203
|
-
|
|
205
|
+
list [--category <c>] [--source <lab|site|own>] [--tag <t> ...] [--query <s>] [--json]
|
|
206
|
+
タグ完全一致(複数は AND)・題名/id/タグ部分一致で絞る素材一覧
|
|
204
207
|
add <path...> --plan [--json] ローカル素材の取り込み計画(書き込みなし)
|
|
205
208
|
add --apply <plan.json> [--json] 計画で選択した素材を複製して登録
|
|
206
209
|
fetch <ref> [--project <dir>] [--reference] [--force]
|
|
@@ -14,7 +14,7 @@ export async function bundleProjectReferences({
|
|
|
14
14
|
for (const reference of planned) {
|
|
15
15
|
try {
|
|
16
16
|
if (!isAssetCached(env, reference.category, reference.id)) {
|
|
17
|
-
const resolved = await resolveAsset(reference.id
|
|
17
|
+
const resolved = await resolveAsset(`${reference.category}/${reference.id}`, { env });
|
|
18
18
|
if (resolved.category !== reference.category) {
|
|
19
19
|
throw new Error(
|
|
20
20
|
`カタログのカテゴリが台帳と一致しません: ${reference.category}/${reference.id}(実際: ${resolved.category})`,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ~/.akari/catalog-cache.json へ自動キャッシュ(オフライン時のフォールバック)。
|
|
3
3
|
// ローカルパス指定(開発・テスト)はファイルをそのまま読む。
|
|
4
4
|
|
|
5
|
-
import { readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { readFile, mkdir, writeFile, stat } from 'node:fs/promises';
|
|
6
6
|
import { catalogCachePath, resolveAkariHome, resolveCatalogSource } from './env.mjs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { loadInstalledItems, mergeInstalledItems } from './installed.mjs';
|
|
@@ -16,9 +16,26 @@ function normalizeCatalog(catalog) {
|
|
|
16
16
|
return catalog;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// The local catalog is large; keep the parsed value until the file changes.
|
|
20
|
+
const parsedCatalogs = new Map();
|
|
21
|
+
async function readLocalCatalog(file, visible = false) {
|
|
22
|
+
const info = await stat(file);
|
|
23
|
+
const cached = parsedCatalogs.get(file);
|
|
24
|
+
if (cached && cached.mtimeMs === info.mtimeMs && cached.ctimeMs === info.ctimeMs
|
|
25
|
+
&& cached.size === info.size) {
|
|
26
|
+
if (visible && !cached.public) cached.public = publicCatalog(cached.catalog);
|
|
27
|
+
return visible ? cached.public : cached.catalog;
|
|
28
|
+
}
|
|
29
|
+
const catalog = normalizeCatalog(JSON.parse(await readFile(file, 'utf8')));
|
|
30
|
+
const entry = { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs, size: info.size, catalog,
|
|
31
|
+
...(visible ? { public: publicCatalog(catalog) } : {}) };
|
|
32
|
+
parsedCatalogs.set(file, entry);
|
|
33
|
+
return visible ? entry.public : catalog;
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
export async function readCatalogCache(env = process.env) {
|
|
20
37
|
try {
|
|
21
|
-
return
|
|
38
|
+
return await readLocalCatalog(catalogCachePath(env), true);
|
|
22
39
|
} catch {
|
|
23
40
|
return null;
|
|
24
41
|
}
|
|
@@ -53,6 +70,7 @@ export async function cacheCatalog(env = process.env, catalog) {
|
|
|
53
70
|
const home = resolveAkariHome(env);
|
|
54
71
|
await mkdir(home, { recursive: true });
|
|
55
72
|
await writeFile(catalogCachePath(env), `${JSON.stringify(publicCatalog(normalizeCatalog(catalog)), null, 2)}\n`);
|
|
73
|
+
parsedCatalogs.delete(catalogCachePath(env));
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
/**
|
|
@@ -70,8 +88,7 @@ export async function loadCatalog({ env = process.env, fetchImpl = fetch, includ
|
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
if (source.kind === 'file') {
|
|
73
|
-
|
|
74
|
-
catalog = normalizeCatalog(JSON.parse(raw));
|
|
91
|
+
catalog = await readLocalCatalog(source.value);
|
|
75
92
|
} else {
|
|
76
93
|
try {
|
|
77
94
|
const { response: res, controller } = await fetchTimed(source.value, { fetchImpl, timeouts, label: 'カタログ' });
|
|
@@ -90,3 +90,18 @@ export function resolveDownloadUrl(env = process.env, credentials, productId) {
|
|
|
90
90
|
}
|
|
91
91
|
return `${DEFAULT_STORE_API}/api/store/v1/download/${encodeURIComponent(productId)}`;
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
/** Pro item の記述子 API。専用上書き → 店 API → 接続情報 → 既定ホスト。 */
|
|
95
|
+
export function resolveProAssetUrl(env = process.env, credentials, category, id) {
|
|
96
|
+
const suffix = `${encodeURIComponent(category)}/${encodeURIComponent(id)}`;
|
|
97
|
+
if (env.AKARI_PRO_ASSET_API) {
|
|
98
|
+
return `${trimTrailingSlash(env.AKARI_PRO_ASSET_API)}/api/store/v1/assets/${suffix}`;
|
|
99
|
+
}
|
|
100
|
+
if (env.AKARI_STORE_API) {
|
|
101
|
+
return `${trimTrailingSlash(env.AKARI_STORE_API)}/api/store/v1/assets/${suffix}`;
|
|
102
|
+
}
|
|
103
|
+
if (credentials?.url) {
|
|
104
|
+
return `${trimTrailingSlash(credentials.url)}/v1/assets/${suffix}`;
|
|
105
|
+
}
|
|
106
|
+
return `${DEFAULT_STORE_API}/api/store/v1/assets/${suffix}`;
|
|
107
|
+
}
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
// (開発時は store リポのローカル出力を指す運用)。
|
|
7
7
|
|
|
8
8
|
import { createWriteStream } from 'node:fs';
|
|
9
|
-
import { copyFile, mkdir } from 'node:fs/promises';
|
|
9
|
+
import { copyFile, mkdir, rm } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { Readable } from 'node:stream';
|
|
12
12
|
import { pipeline } from 'node:stream/promises';
|
|
13
13
|
import { isRemoteLocation } from './env.mjs';
|
|
14
|
+
import { AssetResolverError } from './errors.mjs';
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_FETCH_TIMEOUTS = { responseMs: 15_000, idleMs: 30_000 };
|
|
16
17
|
|
|
@@ -98,14 +99,50 @@ export function resolvePreviewLocation(base, preview) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/** 解決済みロケーションを destPath へ実体化する(リモートは fetch、ローカルはファイルコピー) */
|
|
101
|
-
export async function materialize({ location, remote }, destPath, { fetchImpl = fetch, timeouts } = {}) {
|
|
102
|
+
export async function materialize({ location, remote }, destPath, { fetchImpl = fetch, timeouts, request, maxBytes } = {}) {
|
|
102
103
|
await mkdir(path.dirname(destPath), { recursive: true });
|
|
103
104
|
if (remote) {
|
|
104
|
-
const { response: res, controller } = await fetchTimed(location, { fetchImpl, timeouts, label: '素材ファイル' });
|
|
105
|
+
const { response: res, controller } = await fetchTimed(location, { fetchImpl, request, timeouts, label: '素材ファイル' });
|
|
105
106
|
if (!res.ok || !res.body) {
|
|
106
|
-
throw new Error(`ダウンロード失敗: ${location} → HTTP ${res.status}`);
|
|
107
|
+
if (!request) throw new Error(`ダウンロード失敗: ${location} → HTTP ${res.status}`);
|
|
108
|
+
let detail = '';
|
|
109
|
+
let storeCode;
|
|
110
|
+
try {
|
|
111
|
+
const data = await readTimedJson(res, controller, { timeouts, label: '素材ファイル' });
|
|
112
|
+
storeCode = typeof data?.error === 'string' ? data.error : undefined;
|
|
113
|
+
detail = [storeCode, typeof data?.message === 'string' ? data.message : ''].filter(Boolean).join(': ');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof Error && error.message.includes('時間切れ')) throw error;
|
|
116
|
+
// エラー本文が JSON でない場合も HTTP status を残す。
|
|
117
|
+
}
|
|
118
|
+
controller.abort();
|
|
119
|
+
const error = new Error(`ダウンロード失敗: ${location} → HTTP ${res.status}${detail ? ` (${detail})` : ''}`);
|
|
120
|
+
error.status = res.status;
|
|
121
|
+
error.storeCode = storeCode;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
if (maxBytes === undefined) {
|
|
125
|
+
await pipeline(Readable.from(timedBody(res.body, controller, { timeouts, label: '素材ファイル' })), createWriteStream(destPath));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
async function* limitedBody() {
|
|
129
|
+
let received = 0;
|
|
130
|
+
for await (const chunk of timedBody(res.body, controller, { timeouts, label: '素材ファイル' })) {
|
|
131
|
+
received += chunk.byteLength;
|
|
132
|
+
if (received > maxBytes) {
|
|
133
|
+
controller.abort();
|
|
134
|
+
throw new AssetResolverError(`bytes が宣言値を超えました: ${received} > ${maxBytes}`, 'integrity');
|
|
135
|
+
}
|
|
136
|
+
yield chunk;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await pipeline(Readable.from(limitedBody()), createWriteStream(destPath));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
controller.abort();
|
|
143
|
+
await rm(destPath, { force: true });
|
|
144
|
+
throw error;
|
|
107
145
|
}
|
|
108
|
-
await pipeline(Readable.from(timedBody(res.body, controller, { timeouts, label: '素材ファイル' })), createWriteStream(destPath));
|
|
109
146
|
return;
|
|
110
147
|
}
|
|
111
148
|
await copyFile(location, destPath);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Filter composed catalogue rows without changing their order or contents. */
|
|
2
|
+
export function filterListItems(items, { category, source, tags = [], query } = {}) {
|
|
3
|
+
const needle = query?.toLocaleLowerCase();
|
|
4
|
+
return items.filter(item =>
|
|
5
|
+
(!category || item.category === category)
|
|
6
|
+
&& (!source || item.sourceKind === source)
|
|
7
|
+
&& tags.every(tag => item.tags?.includes(tag))
|
|
8
|
+
&& (!needle || [item.title, item.id, ...(item.tags ?? [])]
|
|
9
|
+
.some(value => typeof value === 'string' && value.toLocaleLowerCase().includes(needle))));
|
|
10
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Pro item の記述子と店の HTTP 応答を検査する。ファイル取得前に全件を検査する。
|
|
2
|
+
import { AssetResolverError } from './errors.mjs';
|
|
3
|
+
import { resolveProAssetUrl } from './env.mjs';
|
|
4
|
+
import { fetchTimed, readTimedJson } from './fetch-file.mjs';
|
|
5
|
+
|
|
6
|
+
function timedOut(error) {
|
|
7
|
+
return error instanceof Error && error.message.includes('時間切れ');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function storeFailure(status, data, context) {
|
|
11
|
+
const storeCode = typeof data?.error === 'string' ? data.error : undefined;
|
|
12
|
+
const message = typeof data?.message === 'string' ? data.message : undefined;
|
|
13
|
+
const details = [storeCode, message].filter(Boolean).join(': ');
|
|
14
|
+
const error = new AssetResolverError(
|
|
15
|
+
`${context}に失敗しました(HTTP ${status ?? '不明'}${details ? `: ${details}` : ''})`,
|
|
16
|
+
status === 401 || (status === 403 && storeCode === 'pro_required') ? 'locked' : 'download_failed',
|
|
17
|
+
);
|
|
18
|
+
error.status = status;
|
|
19
|
+
error.storeCode = storeCode;
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function invalid(reason) {
|
|
24
|
+
throw new AssetResolverError(`Pro 素材の記述子が不正です: ${reason}`, 'integrity');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validateProAssetDescriptor(data, item, descriptorUrl) {
|
|
28
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) invalid('JSON object が必要です');
|
|
29
|
+
if (data.schema !== 'akari-pro-asset/v1') invalid('schema');
|
|
30
|
+
if (data.category !== item.category || data.id !== item.id) invalid('category/id');
|
|
31
|
+
if (!Number.isInteger(data.version) || data.version <= 0) invalid('version');
|
|
32
|
+
if (!Array.isArray(data.files) || data.files.length === 0) invalid('files');
|
|
33
|
+
const origin = new URL(descriptorUrl).origin;
|
|
34
|
+
const names = new Set();
|
|
35
|
+
const files = [];
|
|
36
|
+
for (const file of data.files) {
|
|
37
|
+
if (!file || typeof file !== 'object' || Array.isArray(file)) invalid('file');
|
|
38
|
+
if (Object.keys(file).length !== 4
|
|
39
|
+
|| Object.keys(file).some(key => !['name', 'sha256', 'bytes', 'url'].includes(key))) invalid('file のキー');
|
|
40
|
+
const name = file.name;
|
|
41
|
+
if (typeof name !== 'string' || !name || name.startsWith('/') || name.includes('\\')
|
|
42
|
+
|| name.split('/').some(part => !part || part === '.' || part === '..'
|
|
43
|
+
|| /[:\x00-\x1f\x7f<>"|?*]/.test(part) || /[. ]$/.test(part)
|
|
44
|
+
|| /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/i.test(part))) invalid('name');
|
|
45
|
+
const foldedName = name.toLowerCase();
|
|
46
|
+
if (names.has(foldedName)) invalid('name が重複しています');
|
|
47
|
+
names.add(foldedName);
|
|
48
|
+
if (typeof file.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(file.sha256)) invalid('sha256');
|
|
49
|
+
if (!Number.isInteger(file.bytes) || file.bytes < 0) invalid('bytes');
|
|
50
|
+
let url;
|
|
51
|
+
try { url = new URL(file.url); } catch { invalid('url'); }
|
|
52
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.origin !== origin) invalid('url のオリジン');
|
|
53
|
+
files.push({ name, sha256: file.sha256, bytes: file.bytes, url: file.url });
|
|
54
|
+
}
|
|
55
|
+
if (!files.some(file => file.name === 'meta.json')) invalid('meta.json');
|
|
56
|
+
return { ...data, files };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 404 asset_not_found だけを null として返し、zip 予備経路へ渡す。 */
|
|
60
|
+
export async function fetchProAssetDescriptor(item, credentials, { env, fetchImpl, timeouts }) {
|
|
61
|
+
const url = resolveProAssetUrl(env, credentials, item.category, item.id);
|
|
62
|
+
let res;
|
|
63
|
+
let controller;
|
|
64
|
+
try {
|
|
65
|
+
({ response: res, controller } = await fetchTimed(url, {
|
|
66
|
+
fetchImpl, request: { headers: { authorization: `Bearer ${credentials.token}` }, redirect: 'error' },
|
|
67
|
+
timeouts, label: 'Pro 素材の記述子',
|
|
68
|
+
}));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new AssetResolverError(`Pro 素材の記述子取得に失敗しました: ${error.message}`, timedOut(error) ? 'timeout' : 'download_failed');
|
|
71
|
+
}
|
|
72
|
+
let data;
|
|
73
|
+
try {
|
|
74
|
+
data = await readTimedJson(res, controller, { timeouts, label: 'Pro 素材の記述子' });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (timedOut(error)) throw new AssetResolverError(error.message, 'timeout');
|
|
77
|
+
if (res.ok) invalid('JSON');
|
|
78
|
+
const failure = storeFailure(res.status, null, 'Pro 素材の記述子取得');
|
|
79
|
+
if (res.status === 401) failure.message += '。トークン失効の可能性があります。akari store connect をやり直してください';
|
|
80
|
+
throw failure;
|
|
81
|
+
} finally {
|
|
82
|
+
controller.abort();
|
|
83
|
+
}
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
if (res.status === 404 && data?.error === 'asset_not_found') return null;
|
|
86
|
+
if (res.status === 403 && data?.error === 'pro_required') {
|
|
87
|
+
const error = new AssetResolverError(
|
|
88
|
+
`Pro 素材は all-access-pass(Lifetime パス)または購入済み product_id が必要です: ${item.id}${data?.message ? `(${data.message})` : ''}`,
|
|
89
|
+
'locked',
|
|
90
|
+
);
|
|
91
|
+
error.status = res.status;
|
|
92
|
+
error.storeCode = data.error;
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
if (res.status === 401) {
|
|
96
|
+
const failure = storeFailure(res.status, data, 'Pro 素材の記述子取得');
|
|
97
|
+
failure.message += '。トークン失効の可能性があります。akari store connect をやり直してください';
|
|
98
|
+
throw failure;
|
|
99
|
+
}
|
|
100
|
+
throw storeFailure(res.status, data, 'Pro 素材の記述子取得');
|
|
101
|
+
}
|
|
102
|
+
return validateProAssetDescriptor(data, item, url);
|
|
103
|
+
}
|
|
@@ -157,8 +157,13 @@ export async function readProjectReferences(projectDir) {
|
|
|
157
157
|
|
|
158
158
|
export async function recordProjectReference(projectDir, reference) {
|
|
159
159
|
assertReference(reference);
|
|
160
|
+
const alreadyRecorded = references => references.some(entry =>
|
|
161
|
+
entry.id === reference.id && entry.category === reference.category);
|
|
162
|
+
const current = await readProjectReferences(projectDir);
|
|
163
|
+
if (alreadyRecorded(current)) return current;
|
|
160
164
|
return withReferencesLock(projectDir, async () => {
|
|
161
165
|
const references = await readProjectReferences(projectDir);
|
|
166
|
+
if (alreadyRecorded(references)) return references;
|
|
162
167
|
references.push({ id: reference.id, category: reference.category });
|
|
163
168
|
const normalized = normalizeReferences(references);
|
|
164
169
|
await writeProjectReferences(projectDir, normalized);
|