akari-video 0.1.73 → 0.1.74

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.
Files changed (62) hide show
  1. package/README.md +6 -2
  2. package/package.json +1 -1
  3. package/src/cli.mjs +9 -1
  4. package/src/kits.mjs +24 -25
  5. package/src/store-command.mjs +12 -6
  6. package/vendor/docs/contract-2026-07-13-asset-library.md +8 -4
  7. package/vendor/docs/contract-2026-08-02-creator-root-v1.md +6 -2
  8. package/vendor/docs/contract-2026-09-02-asset-reference-model.md +55 -5
  9. package/vendor/packages/akari-launcher/README.md +6 -2
  10. package/vendor/packages/akari-launcher/package.json +1 -1
  11. package/vendor/packages/asset-resolver/README.md +30 -5
  12. package/vendor/packages/asset-resolver/bin/akari-assets.mjs +13 -4
  13. package/vendor/packages/asset-resolver/src/bundle.mjs +3 -5
  14. package/vendor/packages/asset-resolver/src/installed.mjs +39 -29
  15. package/vendor/packages/asset-resolver/src/library.mjs +24 -35
  16. package/vendor/packages/asset-resolver/src/resolve.mjs +11 -9
  17. package/vendor/packages/asset-resolver/src/state.mjs +3 -1
  18. package/vendor/packages/asset-resolver/test/installed-assets.test.mjs +1 -1
  19. package/vendor/packages/asset-resolver/test/library-migration.test.mjs +49 -0
  20. package/vendor/packages/audio-library-setup/README.md +6 -2
  21. package/vendor/packages/audio-library-setup/bin/beat-grid.mjs +6 -5
  22. package/vendor/packages/audio-library-setup/bin/declare-helper.mjs +2 -2
  23. package/vendor/packages/audio-library-setup/bin/fetch-akari-sounds.mjs +5 -4
  24. package/vendor/packages/audio-library-setup/bin/gallery-helper.mjs +2 -2
  25. package/vendor/packages/audio-library-setup/bin/register-drop-folder.mjs +3 -2
  26. package/vendor/packages/audio-library-setup/bin/review-sfx-mapping.mjs +6 -5
  27. package/vendor/packages/audio-library-setup/bin/suggest-bgm.mjs +7 -6
  28. package/vendor/packages/audio-library-setup/bin/suggest-sfx.mjs +6 -6
  29. package/vendor/packages/audio-library-setup/declare-server.mjs +7 -20
  30. package/vendor/packages/audio-library-setup/gallery-server.mjs +7 -14
  31. package/vendor/packages/audio-library-setup/shared/library-roots.mjs +33 -0
  32. package/vendor/packages/audio-library-setup/test/library-location.test.mjs +68 -0
  33. package/vendor/packages/creator-root/src/index.mjs +295 -1
  34. package/vendor/packages/creator-root/test/library-migration.test.mjs +229 -0
  35. package/vendor/packages/edit-lint/src/edit-lint.mjs +4 -4
  36. package/vendor/packages/edit-lint/src/library-reference.mjs +33 -16
  37. package/vendor/packages/edit-store/lib/edit-v2-item-write.d.ts +1 -0
  38. package/vendor/packages/edit-store/lib/edit-v2-item-write.js +15 -0
  39. package/vendor/packages/edit-store/lib/generation-meta.d.ts +40 -2
  40. package/vendor/packages/edit-store/lib/generation-meta.js +21 -1
  41. package/vendor/packages/schemas/fixtures/generation-meta/generating-placeholder.json +61 -0
  42. package/vendor/packages/schemas/fixtures/generation-meta/planned-next.json +85 -0
  43. package/vendor/packages/schemas/fixtures/generation-meta/still-next.json +106 -0
  44. package/vendor/packages/schemas/generation-meta.schema.json +150 -0
  45. package/vendor/packages/schemas/test/generation-meta.test.mjs +12 -1
  46. package/vendor/skills/akari/SKILL.md +5 -0
  47. package/vendor/skills/declare-audio/SKILL.md +8 -3
  48. package/vendor/skills/declare-audio/after-save.md +7 -2
  49. package/vendor/skills/declare-audio/launch.md +8 -4
  50. package/vendor/skills/edit-plan/beat-sync.md +7 -3
  51. package/vendor/skills/edit-plan/expression-selection.md +5 -2
  52. package/vendor/skills/edit-plan/report-guide.md +6 -2
  53. package/vendor/skills/harvest-asset/SKILL.md +7 -2
  54. package/vendor/skills/setup-audio-library/SKILL.md +7 -2
  55. package/vendor/skills/setup-audio-library/assisted-fetch.md +6 -2
  56. package/vendor/skills/setup-audio-library/drop-folder.md +6 -2
  57. package/vendor/skills/setup-audio-library/first-party.md +6 -2
  58. package/vendor/skills/setup-audio-library/gallery.md +7 -4
  59. package/vendor/skills/setup-audio-library/store-unlock.md +5 -1
  60. package/vendor/skills/setup-library/fetch-and-validate.md +5 -1
  61. package/vendor/templates/project-default/AGENTS.md +6 -2
  62. package/vendor/templates/project-default/CLAUDE.md +6 -2
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # akari-launcher(`akari` コマンド)
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  「UI に依存したくない。opencode 単体でも、どんなディレクトリでも始められるように」を
4
8
  実現する薄いラッパー CLI。npm パッケージ名は `akari-video`、提供するコマンド名は `akari`
5
9
  (npm の `akari` は別プロダクトが取得済みのため。オーナー裁定 2026-07-21 §8-2)。
@@ -24,7 +28,7 @@ akari
24
28
  ├─ 4. 公式音源ライブラリ(AKARI Sounds)の初回セットアップ(src/sounds-setup.mjs):
25
29
  │ 未導入かつ TTY のとき生涯 1 回だけ [Y/n](既定 Yes)を聞き、Yes なら
26
30
  │ packages/audio-library-setup/bin/fetch-akari-sounds.mjs で一括ダウンロード。
27
- │ n は marker(~/.akari/assets/audio/.akari-sounds-declined.json)を書いて以後
31
+ │ n は marker(<ライブラリの置き場>/audio/.akari-sounds-declined.json)を書いて以後
28
32
  │ 聞かない。再入口は `akari sounds`。失敗しても起動は止めない
29
33
 
30
34
  └─ 5. 最後に opencode を exec する
@@ -48,7 +52,7 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
48
52
  `akari store <connect|status|install|download|disconnect>`(AKARI Store 連携。マイページで発行した
49
53
  接続トークンを `~/.akari/store-credentials.json`(0600)に保存し、購入済み一覧の確認と
50
54
  配布物の取得ができる。`install <productId> [--from <zip>]` は購入パックを展開し、`PACK.json` の
51
- 収載素材を `~/.akari/assets/installed.json` へ登録する。`--from` は開発・オフライン導入時に手元の
55
+ 収載素材を `<ライブラリの置き場>/installed.json` へ登録する。`--from` は開発・オフライン導入時に手元の
52
56
  zip を使う。`src/store-command.mjs`)/
53
57
  `akari assets <list|fetch|sync|...>`(素材カタログの一覧・取得・同期。
54
58
  `packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
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/cli.mjs CHANGED
@@ -7,7 +7,7 @@ import { detectProjectState } from './project-state.mjs';
7
7
  import { findClaudeExecutable, findExecutable, findOpencodeExecutable } from './path-lookup.mjs';
8
8
  import { loadTaskLabels } from './task-labels.mjs';
9
9
  import { describeForceReinstall, describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCacheFallback, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
10
- import { resolveEffectiveProjectRoot } from './first-run.mjs';
10
+ import { defaultLoadCreatorRootModule, resolveEffectiveProjectRoot } from './first-run.mjs';
11
11
  import { maybeShowAssetIntroNotice } from './sounds-setup.mjs';
12
12
  import {
13
13
  checkForUpdateSync,
@@ -82,6 +82,14 @@ export async function run(args, options = {}) {
82
82
  projectRoot = await resolveEffectiveProjectRoot({ projectRoot, env, platform, now, log, assets, autoConfirm, options });
83
83
  }
84
84
 
85
+ try {
86
+ const creatorRoot = await defaultLoadCreatorRootModule(assets);
87
+ const migration = await creatorRoot?.migrateAssetLibrary({ env, platform, automatic: true, notify: log });
88
+ for (const failure of migration?.failures ?? []) log(`素材の移行を再試行します: ${failure.message}`);
89
+ } catch (error) {
90
+ log(`素材の移行を再試行します: ${error.message}`);
91
+ }
92
+
85
93
  let state = detectProjectState(projectRoot);
86
94
 
87
95
  if (!state.scaffolded) {
package/src/kits.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { pathToFileURL } from 'node:url';
1
2
  import { spawnSync } from 'node:child_process';
2
3
  import { createHash } from 'node:crypto';
3
4
  import {
@@ -7,6 +8,12 @@ import {
7
8
  import path from 'node:path';
8
9
 
9
10
  import { resolveLauncherAssets } from './repo-assets.mjs';
11
+ const creatorRootModulePath = resolveLauncherAssets().creatorRootModulePath;
12
+ const creatorRoot = creatorRootModulePath ? await import(pathToFileURL(creatorRootModulePath).href) : null;
13
+ function resolveAssetLibraryRoots(env) {
14
+ if (!creatorRoot) throw new Error('creator-root が見つからないため素材の置き場を解決できません。AKARI Video を再インストールしてください。');
15
+ return creatorRoot.resolveAssetLibraryRoots(env);
16
+ }
10
17
 
11
18
  const KITS_SCHEMA = 'akari-installed-kits/v0';
12
19
  const INSTALLED_ASSETS_SCHEMA = 'akari-installed-assets/v0';
@@ -152,16 +159,8 @@ export function linkKitAssets(kitDir, manifest, home, options = {}) {
152
159
  } else {
153
160
  warnings.push(`素材 ${asset.category}/${asset.id} の検査ツールが見つからないため検査をスキップしました。`);
154
161
  }
155
- const destination = path.join(home, 'assets', asset.category, asset.id);
156
- const productRoot = path.join(home, 'assets', 'store', manifest.id);
157
- const kitSubdir = path.relative(productRoot, kitDir);
158
- const result = replaceSymlink(source, destination, {
159
- ...options,
160
- relativeTarget: path.join(
161
- '..', '..', 'assets', 'store', manifest.id, kitSubdir,
162
- 'assets', asset.category, asset.id
163
- )
164
- });
162
+ const destination = path.join(resolveAssetLibraryRoots({ ...(options.env ?? process.env), AKARI_HOME: home }).write, asset.category, asset.id);
163
+ const result = replaceSymlink(source, destination, options);
165
164
  if (result.status === 'occupied') {
166
165
  warnings.push(`既存の実ディレクトリを保持しました: ${destination}`);
167
166
  } else if (result.status === 'permission-denied') {
@@ -174,8 +173,8 @@ export function linkKitAssets(kitDir, manifest, home, options = {}) {
174
173
  return { linked, items, warnings };
175
174
  }
176
175
 
177
- function readInstalledAssetsIndex(home) {
178
- const indexPath = path.join(home, 'assets', 'installed.json');
176
+ function readInstalledAssetsIndex(home, env = process.env) {
177
+ const indexPath = path.join(resolveAssetLibraryRoots({ ...env, AKARI_HOME: home }).write, 'installed.json');
179
178
  if (!existsSync(indexPath)) return { schema: INSTALLED_ASSETS_SCHEMA, packs: {} };
180
179
  const index = JSON.parse(readFileSync(indexPath, 'utf8'));
181
180
  if (index?.schema !== INSTALLED_ASSETS_SCHEMA
@@ -185,38 +184,38 @@ function readInstalledAssetsIndex(home) {
185
184
  return index;
186
185
  }
187
186
 
188
- function writeInstalledAssetsIndex(home, index) {
189
- const indexPath = path.join(home, 'assets', 'installed.json');
187
+ function writeInstalledAssetsIndex(home, index, env = process.env) {
188
+ const indexPath = path.join(resolveAssetLibraryRoots({ ...env, AKARI_HOME: home }).write, 'installed.json');
190
189
  mkdirSync(path.dirname(indexPath), { recursive: true });
191
190
  const temporary = `${indexPath}.tmp-${process.pid}`;
192
191
  writeFileSync(temporary, `${JSON.stringify(index, null, 2)}\n`, { mode: 0o600 });
193
192
  renameSync(temporary, indexPath);
194
193
  }
195
194
 
196
- export function registerKitAssets(home, manifest, kitDir, items) {
195
+ export function registerKitAssets(home, manifest, kitDir, items, env = process.env) {
197
196
  const root = path.resolve(kitDir);
198
- const storeRoot = path.join(path.resolve(home), 'assets', 'store', manifest.id);
197
+ const storeRoot = path.join(resolveAssetLibraryRoots({ ...env, AKARI_HOME: home }).write, 'store', manifest.id);
199
198
  if (root !== storeRoot && !root.startsWith(`${storeRoot}${path.sep}`)) {
200
199
  throw new Error(`キット素材の root が展開先の外を指しています: ${root}`);
201
200
  }
202
- const index = readInstalledAssetsIndex(home);
201
+ const index = readInstalledAssetsIndex(home, env);
203
202
  index.packs[manifest.id] = {
204
203
  version: manifest.version,
205
204
  installedAt: new Date().toISOString(),
206
205
  root,
207
206
  items
208
207
  };
209
- writeInstalledAssetsIndex(home, index);
208
+ writeInstalledAssetsIndex(home, index, env);
210
209
  return items;
211
210
  }
212
211
 
213
- function unregisterKitAssets(home, productId) {
214
- const indexPath = path.join(home, 'assets', 'installed.json');
212
+ function unregisterKitAssets(home, productId, env = process.env) {
213
+ const indexPath = path.join(resolveAssetLibraryRoots({ ...env, AKARI_HOME: home }).write, 'installed.json');
215
214
  if (!existsSync(indexPath)) return;
216
- const index = readInstalledAssetsIndex(home);
215
+ const index = readInstalledAssetsIndex(home, env);
217
216
  if (!Object.hasOwn(index.packs, productId)) return;
218
217
  delete index.packs[productId];
219
- writeInstalledAssetsIndex(home, index);
218
+ writeInstalledAssetsIndex(home, index, env);
220
219
  }
221
220
 
222
221
  export function linkKitSkills(kitDir, manifest, home, options = {}) {
@@ -276,20 +275,20 @@ export function writeKitsLedger(home, entry) {
276
275
  return ledger;
277
276
  }
278
277
 
279
- export function removeKit(home, productId) {
278
+ export function removeKit(home, productId, env = process.env) {
280
279
  const ledger = readKitsLedger(home);
281
280
  const entry = ledger.kits.find((kit) => kit.id === productId);
282
281
  if (!entry) return false;
283
282
  for (const name of entry.skills ?? []) removeOwnedSymlink(path.join(home, 'kits', 'plugin', 'skills', name), entry.kitDir);
284
283
  for (const asset of entry.assets ?? []) {
285
- removeOwnedSymlink(path.join(home, 'assets', asset.category, asset.id), entry.kitDir);
284
+ for (const root of resolveAssetLibraryRoots({ ...env, AKARI_HOME: home }).read) removeOwnedSymlink(path.join(root, asset.category, asset.id), entry.kitDir);
286
285
  }
287
286
  ledger.kits = ledger.kits.filter((kit) => kit.id !== productId);
288
287
  const ledgerPath = path.join(home, 'kits', 'installed.json');
289
288
  const temporary = `${ledgerPath}.tmp-${process.pid}`;
290
289
  writeFileSync(temporary, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
291
290
  renameSync(temporary, ledgerPath);
292
- unregisterKitAssets(home, productId);
291
+ unregisterKitAssets(home, productId, env);
293
292
  return true;
294
293
  }
295
294
 
@@ -20,6 +20,12 @@ import {
20
20
  } from './store-device-connect.mjs';
21
21
  import { readOwnVersion } from './update-check.mjs';
22
22
  import { resolveLauncherAssets } from './repo-assets.mjs';
23
+ const creatorRootModulePath = resolveLauncherAssets().creatorRootModulePath;
24
+ const creatorRoot = creatorRootModulePath ? await import(pathToFileURL(creatorRootModulePath).href) : null;
25
+ function resolveAssetLibraryRoots(env) {
26
+ if (!creatorRoot) throw new Error('creator-root が見つからないため素材の置き場を解決できません。AKARI Video を再インストールしてください。');
27
+ return creatorRoot.resolveAssetLibraryRoots(env);
28
+ }
23
29
  import {
24
30
  checkRequires,
25
31
  enableHint,
@@ -154,8 +160,7 @@ function flattenPackContents(pack, packRoot) {
154
160
  }
155
161
 
156
162
  function registerInstalledPack(env, productId, packPath) {
157
- const home = resolveAkariHome(env);
158
- const indexPath = path.join(home, 'assets', 'installed.json');
163
+ const indexPath = path.join(resolveAssetLibraryRoots(env).write, 'installed.json');
159
164
  const packRoot = path.dirname(packPath);
160
165
  const pack = readJsonFile(packPath);
161
166
  if ((typeof pack?.version !== 'string' && typeof pack?.version !== 'number')) {
@@ -319,7 +324,7 @@ export async function runStoreCommand(args, options = {}) {
319
324
  log('使い方: akari store uninstall <productId>');
320
325
  return { exitCode: 1 };
321
326
  }
322
- if (!removeKit(resolveAkariHome(env), productId)) {
327
+ if (!removeKit(resolveAkariHome(env), productId, env)) {
323
328
  log(`導入済みの拡張キットが見つかりません: ${productId}`);
324
329
  return { exitCode: 1 };
325
330
  }
@@ -430,7 +435,7 @@ export async function runStoreCommand(args, options = {}) {
430
435
  log('パック内に declarations.json が見つかりませんでした。zip の中身を確認してください。');
431
436
  return { exitCode: 1 };
432
437
  }
433
- const destDir = path.join(resolveAkariHome(env), 'assets', 'audio');
438
+ const destDir = path.join(resolveAssetLibraryRoots(env).write, 'audio');
434
439
  mkdirSync(destDir, { recursive: true });
435
440
  const dest = path.join(destDir, 'declarations.json');
436
441
  if (existsSync(dest)) {
@@ -445,7 +450,7 @@ export async function runStoreCommand(args, options = {}) {
445
450
  }
446
451
 
447
452
  // 既知の導入手順が無い商品は素材置き場に展開して README を案内
448
- const destDir = path.join(resolveAkariHome(env), 'assets', 'store', productId);
453
+ const destDir = path.join(resolveAssetLibraryRoots(env).write, 'store', productId);
449
454
  rmSync(destDir, { recursive: true, force: true });
450
455
  cpSync(extractDir, destDir, { recursive: true });
451
456
  const readme = findFile(destDir, 'README.md');
@@ -531,6 +536,7 @@ export async function runStoreCommand(args, options = {}) {
531
536
 
532
537
  const hadInstalledKit = readKitsLedger(home).kits.length > 0;
533
538
  const assetLinks = linkKitAssets(kitDir, manifest, home, {
539
+ env,
534
540
  assets: options.assets,
535
541
  spawnSyncImpl: options.spawnSync,
536
542
  platform: options.platform
@@ -541,7 +547,7 @@ export async function runStoreCommand(args, options = {}) {
541
547
  for (const blocker of skillLinks.blockers) log(`導入できません: ${blocker}`);
542
548
  return { exitCode: 1 };
543
549
  }
544
- registerKitAssets(home, manifest, kitDir, assetLinks.items);
550
+ registerKitAssets(home, manifest, kitDir, assetLinks.items, env);
545
551
  writeKitsLedger(home, {
546
552
  id: manifest.id,
547
553
  version: manifest.version,
@@ -1,5 +1,9 @@
1
1
  # 素材ライブラリ契約 v0
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  - 日付: 2026-07-13
4
8
  - 状態: 設計確定
5
9
  - 前提: 本体(エンジン)は合成だけ。素材とその知識は全部外側に置く
@@ -215,7 +219,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
215
219
  |---|---|---|
216
220
  | `local` | `<プロジェクト>/assets/` | そのプロジェクトのみ |
217
221
  | `shared` | プロジェクトから上位へ辿った各ディレクトリの `.akari/assets/`(2026-07-25 第三裁定で確定) | そのディレクトリ配下の全プロジェクト(事業・組織単位。複数層可) |
218
- | `user` | `~/.akari/assets/`(2026-07-25 第三裁定で確定) | そのマシンの全プロジェクト |
222
+ | `user` | `<ライブラリの置き場>/`(2026-07-25 第三裁定で確定) | そのマシンの全プロジェクト |
219
223
  | `builtin` | 本リポの `assets/` | 製品出荷デフォルト |
220
224
  | `catalog` | 本リポの `catalog/`(remote) | 取得して任意の層へ入庫 |
221
225
 
@@ -241,7 +245,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
241
245
  ~~**確定裁定(2026-07-25 再裁定): プロジェクト外の置き場所は `~/.akari-video/` をベースに
242
246
  統一する。** 初期案保留(旧「`.akari-video/` は初期案」項)はこれで解消。~~
243
247
 
244
- **確定裁定(2026-07-25 第三裁定): プロジェクト外の置き場所は `~/.akari/` 直下に統一する。**
248
+ **履歴(2026-07-25 第三裁定): 当時の user 素材は `~/.akari/` 直下だった。2026-09-21 から素材は作業場 `library/` に移行し、マシン状態のみ `~/.akari/` に残す。**
245
249
  `~/.akari/` 直下には他ソフトウェアの既存物が併存する場合があるため、共存規約
246
250
  (本節末尾を参照)を必ず守る。
247
251
 
@@ -250,7 +254,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
250
254
 
251
255
  確定事項(2026-07-25 第三裁定で更新。カッコ内は第二裁定時点の値):
252
256
 
253
- - `user` 層: `~/.akari/assets/`(第二裁定時点: `~/.akari-video/assets/`)
257
+ - `user` 層: `<ライブラリの置き場>/`(第二裁定時点: `~/.akari-video/assets/`)
254
258
  - `shared` 層: 上位ディレクトリの `.akari/assets/`(第二裁定時点: `.akari-video/assets/`)
255
259
  - レシピ: `~/.akari/recipes/`(recipe v0 の現行表記のまま・変更不要。出所は
256
260
  `docs/contract-2026-07-25-recipe-v0.md` §2 に追記)
@@ -380,7 +384,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
380
384
  - **カテゴリを増やさない**のが本改訂の要点である。新しい主題(ホワイトボード、ノート風、付箋…)は
381
385
  既存カテゴリ + tags で表す。カテゴリ追加を提案するときは「既存 6 つのどの形にも当てはまらない
382
386
  配布・消費のされ方か」を先に示す
383
- - `audio` を `media` に畳む案は見送った。`~/.akari/assets/audio/`(user 層)に実データがあり、
387
+ - `audio` を `media` に畳む案は見送った。`<ライブラリの置き場>/audio/`(user 層)に実データがあり、
384
388
  リポ外のユーザー資産の移設を伴うため。`audio` と `broll` は消費経路(音声トラック / 映像トラック)が
385
389
  異なるので、形の軸としても分けたままで筋が通る
386
390
  - 移行時の実績: `assets/` 3 件・`catalog/` 3 件のディレクトリ移動と `category` 値の書き換え、
@@ -1,5 +1,9 @@
1
1
  # creator-root-v1(作業場)契約
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  - 日付: 2026-08-02
4
8
  - 状態: **v1 ドラフト・要オーナーレビュー**(設計方針はオーナー承認済み 2026-08-02。
5
9
  判断根拠・出典調査は非公開の内部記録で管理する)
@@ -41,8 +45,8 @@ AKARI Video がマシン上に持ってよい場所は次の 3 つ**だけ**で
41
45
  - マシン状態の実パスは**既存実装の規約を追認**する(`~/.akari/` = 更新キャッシュ・接続マーカー・
42
46
  可搬 Node runtime 等 / 資格情報は `~/.config/akari-video/credentials.env`)。新たな隠し場所を増やさない
43
47
  - 原則: マシン状態には**ユーザーの内容物(作品・素材・記憶)を置かない**。
44
- 既存の `~/.akari/assets/audio`(audio library 既定)は v1 時点の既存例外であり、
45
- 作業場 `library/` への移行は別タスクで扱う
48
+ 旧音源ライブラリの例外は 2026-09-21 の移行で回収した。素材は作業場 `library/` へ移し、
49
+ マシン設定の `library-location.json` に置き場を固定する(作業場なし・同期フォルダは移さない)。
46
50
 
47
51
  - **secrets(API キー・トークン類)はマシン設定にのみ置く**。作業場は可視・バックアップ・
48
52
  同期対象なので、secrets を混ぜてはならない
@@ -1,5 +1,9 @@
1
1
  # contract — 素材の参照モデル v0(共有ライブラリ参照の台帳と解決規則)
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  - 状態: 実装済み(機械層のみ。シェル UI の採用は後続)
4
8
  - 決定日: 2026-09-02
5
9
  - 実装: `packages/asset-resolver`(記帳・実体化)/ `packages/render-cut`・`packages/edit-lint`(解決)
@@ -7,7 +11,7 @@
7
11
  ## 1. 目的
8
12
 
9
13
  カタログ素材をプロジェクトごとに実体コピーすると、同じ素材が何度もダウンロード・複製されて
10
- プロジェクトが肥大する。実体は**マシン単位の共有ライブラリ**(`~/.akari/assets/<category>/<id>/`)に
14
+ プロジェクトが肥大する。実体は**マシン単位の共有ライブラリ**(`<ライブラリの置き場>/<category>/<id>/`)に
11
15
  1 部だけ置き、プロジェクトには**参照だけを記録**できるようにする。
12
16
 
13
17
  ## 2. 設計の要点
@@ -25,11 +29,11 @@
25
29
  edit.json の version とは無関係。
26
30
  - **解決規則**: 宣言されたプロジェクト相対パスが `assets/<category>/<id>/<rest>` の形で、
27
31
  (1) プロジェクト実体が存在せず、(2) 台帳に `{category, id}` があるとき、
28
- `<AKARI_HOME>/assets/<category>/<id>/<rest>`(AKARI_HOME 既定 `~/.akari`・env で上書き可)へ
29
- フォールバックする。解決先は realpath 後も `<AKARI_HOME>/assets` 配下に収まる正規ファイルで
32
+ `resolveAssetLibraryRoots().read` の順(新しい置き場 従来の置き場)にフォールバックする。
33
+ 解決先は各ルートで字句・realpath containment を満たす正規ファイルで
30
34
  あること(`..` 等の脱出は fail-closed で拒否)。
31
35
  - render-cut は解決した入力を render inputs 記録に `scope: "library"` として残す
32
- (既存 `scope: "akari"` と同列の additive 記録)。edit-lint は解決できる参照を欠落と報告せず、
36
+ (既存 `scope: "akari"` と同列の additive 記録)。採用した実ルートを `library_root` に保存して後段でも検査する。edit-lint は解決できる参照を欠落と報告せず、
33
37
  台帳にあるが実体が無い参照は「共有ライブラリ参照(未取得)」として欠落報告する。
34
38
  - render-cut / edit-lint は依存ゼロ CLI のため、解決ロジックは各パッケージ内に**同一実装を重複**して
35
39
  持つ(`src/library-reference.mjs`)。挙動同一性は両テストの同一ケース表で担保する。
@@ -47,7 +51,53 @@ akari-assets bundle --project <dir> [--dry-run]
47
51
  bundle は台帳の各参照をキャッシュから `assets/<category>/<id>/` へ実体化して台帳から除去する。
48
52
  未取得の参照は resolve(取得)を試み、取得できないものは台帳に残して部分成功(exit 非 0)で報告する。冪等。
49
53
 
50
- ## 4. スコープ外(後続)
54
+ ## 4. 書き出しエンジンの配信
55
+
56
+ OSR / GPU の共通配信サーバーは、render-cut が解決した入力のうち `scope: "library"` の
57
+ 全件を許可表として受け取る。映像だけでなく、音・静止画・オーバーレイ内の素材も対象。
58
+ キーは宣言パス `assets/<category>/<id>/<rest>`(区切りは `/`)、値は
59
+ `{ absolute: <実体の絶対パス>, library_root: <採用ルート> }`。
60
+ 配信側は置き場の探索や台帳からの再解決を行わない。
61
+
62
+ ffmpeg の計画には、同じ render inputs の解決結果で素材パス欄だけを絶対パスへ置き換えた
63
+ edit の写しを渡す。BGM・SFX・ナレーション・分離音声・レイヤー音声の入力と尺の probe が対象。
64
+ 映像ソースの cut 音声は既存の解決済み capabilities を使い、contact sheet は書き出した動画を読む。
65
+ 元の edit.json、配信用の宣言パス、receipt の入力パスは書き換えない。
66
+
67
+ 別プロセスへの受け渡しには
68
+ `<projectRoot>/.akari/render-tmp/media-references-<render-cut の PID>.json` を使う。
69
+ 表の形式は `{ "token": "<合言葉>", "references": { "<宣言パス>": { "absolute": "<実体の絶対パス>", "library_root": "<採用ルート>" } } }`。
70
+ render-cut は実行ごとに `crypto.randomBytes(32)` の合言葉を hex 文字列として生成し、
71
+ 同じ値を環境変数 `AKARI_RENDER_MEDIA_REFERENCES_TOKEN` に設定してから書き出しを起動する。
72
+ この変数は Electron 子プロセスへ継承される。終了時は `finally` で元の値へ戻し、元が未定義なら削除する。
73
+ 合言葉はログ・エラー文・render.json に出さない。
74
+ render-cut は子プロセスの起動前に排他的に作成し、OSR / GPU(GPU から OSR への再試行を含む)の
75
+ 終了時に `finally` で成功・失敗とも削除する。Electron は親 PID からパスを決め、サーバー生成時に
76
+ 一度だけ読む。別 CLI の並行実行は表を共有しない。プロセス内の使用中パス集合で
77
+ 同じプロジェクトの重複実行(ネスト呼び出しを含む)を拒否する。集合に無い自 PID の既存表は、
78
+ クラッシュや PID 再利用による残存表として削除してから排他的に作り直す。
79
+ 使用中の記録は作成・書き出し・後片付けの失敗時にも解除する。他 PID の表は回収しない。
80
+ ファイル経由の表は、環境変数が無い・空、表の token が文字列でない、UTF-8 バイト長が異なる、
81
+ または `crypto.timingSafeEqual` で一致しない場合は一切使わない。token の無い旧形式や壊れた JSON も空の表として扱う。
82
+ これにより単体 CLI など render-cut を通らない起動では、プロジェクトに植え込まれた表から外部ファイルを配信しない。
83
+ 配信 API の明示引数 `mediaReferences` がある場合は従来どおりそちらを優先し、
84
+ 表が無い場合や認証できない場合はプロジェクト内だけを配信する。
85
+
86
+ `/media/<宣言パス>` は次の順で検査する。
87
+
88
+ 1. デコード後に `..` セグメント(`/` と `\` の両区切り)を含む要求は 403。
89
+ 2. プロジェクト内の実体があれば優先する。字句・realpath の両方でプロジェクト内に収まることを
90
+ 検査し、symlink による脱出は 403、正規ファイルでなければ 404。
91
+ 3. プロジェクトに実体が無い場合のみ、許可表の完全一致キーを調べる。表に無いファイルは
92
+ ライブラリ配下に存在しても 404。
93
+ 4. 表の実体が採用ルート内にあることを字句で確認し、さらに実体と採用ルートを毎回 realpath で
94
+ 解決して包含を再検査する。脱出は 403、欠落・非正規ファイルは 404。
95
+ 5. 許可した実体は既存のファイル配信を使い、通常取得は 200、Range 取得は 206 とする。
96
+
97
+ 台帳にあっても置き場に無い必須素材は、従来の render inputs / 計画段階のエラーで停止する。
98
+ 配信ページの 404 を待ってから失敗させない。
99
+
100
+ ## 5. スコープ外(後続)
51
101
 
52
102
  - シェル UI の採用(取り込みフローの reference 既定化・プロジェクト面の「参照」バッジ・
53
103
  プレビュー経路のフォールバック)
@@ -1,5 +1,9 @@
1
1
  # akari-launcher(`akari` コマンド)
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  「UI に依存したくない。opencode 単体でも、どんなディレクトリでも始められるように」を
4
8
  実現する薄いラッパー CLI。npm パッケージ名は `akari-video`、提供するコマンド名は `akari`
5
9
  (npm の `akari` は別プロダクトが取得済みのため。オーナー裁定 2026-07-21 §8-2)。
@@ -24,7 +28,7 @@ akari
24
28
  ├─ 4. 公式音源ライブラリ(AKARI Sounds)の初回セットアップ(src/sounds-setup.mjs):
25
29
  │ 未導入かつ TTY のとき生涯 1 回だけ [Y/n](既定 Yes)を聞き、Yes なら
26
30
  │ packages/audio-library-setup/bin/fetch-akari-sounds.mjs で一括ダウンロード。
27
- │ n は marker(~/.akari/assets/audio/.akari-sounds-declined.json)を書いて以後
31
+ │ n は marker(<ライブラリの置き場>/audio/.akari-sounds-declined.json)を書いて以後
28
32
  │ 聞かない。再入口は `akari sounds`。失敗しても起動は止めない
29
33
 
30
34
  └─ 5. 最後に opencode を exec する
@@ -48,7 +52,7 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
48
52
  `akari store <connect|status|install|download|disconnect>`(AKARI Store 連携。マイページで発行した
49
53
  接続トークンを `~/.akari/store-credentials.json`(0600)に保存し、購入済み一覧の確認と
50
54
  配布物の取得ができる。`install <productId> [--from <zip>]` は購入パックを展開し、`PACK.json` の
51
- 収載素材を `~/.akari/assets/installed.json` へ登録する。`--from` は開発・オフライン導入時に手元の
55
+ 収載素材を `<ライブラリの置き場>/installed.json` へ登録する。`--from` は開発・オフライン導入時に手元の
52
56
  zip を使う。`src/store-command.mjs`)/
53
57
  `akari assets <list|fetch|sync|...>`(素材カタログの一覧・取得・同期。
54
58
  `packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
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,9 +1,13 @@
1
1
  # @akari-video/asset-resolver
2
2
 
3
+ ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
4
+ `akari-assets list`(または `akari assets list`)の先頭行で実際の置き場を確認する。
5
+ 以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。
6
+
3
7
  無料素材の参照配布 + オンデマンド取得 resolver v0。
4
8
 
5
9
  「このアカウントで使える素材(無料 + 購入済み)」をカタログ + entitlements + ローカル取得状態から
6
- 1 リストに合成し、**使った素材だけ**を `~/.akari/assets/<category>/<id>/` へ取得・検証して登録する。
10
+ 1 リストに合成し、**使った素材だけ**を `<ライブラリの置き場>/<category>/<id>/` へ取得・検証して登録する。
7
11
  「全部ダウンロード」を既定にしない、という設計契約(内部リポ
8
12
  `planning/notes-2026-08-04-asset-reference-distribution.md`)の本体側実装。
9
13
 
@@ -17,6 +21,7 @@
17
21
  ```sh
18
22
  akari-assets list [--category <c>] [--json] # 合成カタログ一覧(取得状態バッジ込み)
19
23
  akari-assets fetch <id> [--project <dir>] [--force] # 素材を解決してローカルへ登録
24
+ akari-assets migrate [--dry-run] # 旧置き場を再移行(dry-run は変更しない)
20
25
  akari-assets sync # カタログを取得してローカルにキャッシュ
21
26
  akari-assets browse [--port <n>] # ローカル HTTP サーバでカタログを閲覧・投入
22
27
  ```
@@ -25,14 +30,14 @@ akari-assets browse [--port <n>] # ローカル HTTP サ
25
30
  `[installed]` `akari store install` で導入済み。
26
31
 
27
32
  `akari store install <productId> [--from <zip>]` が `PACK.json` を持つ購入パックを展開すると、
28
- 収載素材は `~/.akari/assets/installed.json` に登録される。resolver はこの索引をリモートカタログへ
33
+ 収載素材は `<ライブラリの置き場>/installed.json` に登録される。resolver はこの索引をリモートカタログへ
29
34
  マージし、同じ id があれば導入済みのローカル実体を優先する。`fetch` はネットワークや entitlement
30
35
  照会を使わずパックからコピーし、`PACK.json` 記載の sha256 と照合してから通常の素材ライブラリへ
31
36
  原子的に登録する。
32
37
 
33
38
  `fetch` はキャッシュヒットなら即座にそのパスを返す。未取得なら、カタログの `files[]` を全部
34
39
  一時ディレクトリへ実体化 → sha256 検証 → (`meta.json` を含む素材は)`validate-asset.mjs` で
35
- 契約検証 → 全部通ってから `~/.akari/assets/<category>/<id>/` へ原子的に登録する。
40
+ 契約検証 → 全部通ってから `<ライブラリの置き場>/<category>/<id>/` へ原子的に登録する。
36
41
  途中で失敗したら一時ディレクトリを破棄し、登録先には一切書き込まない(fail-closed。部分状態を
37
42
  残さない)。有料で未購入(`price > 0` かつ entitlements に無い)は `fetch` を拒否する
38
43
  (一度取得済みのキャッシュはそのまま使える — ゲートは「新規取得」だけにかかる)。
@@ -54,7 +59,7 @@ BSD `cp -Rc`(`clonefile(2)` 直呼び)を試し、失敗時のみ上記 `fs.
54
59
  直下)で全ファイルの sha256 を検証
55
60
  3. `README.md` / `LICENSE.md` / `checksums.txt` を除く素材ペイロードを一時ディレクトリへコピー
56
61
  4. (`meta.json` を含む素材は)`validate-asset.mjs` で契約検証
57
- 5. 全部通ってから `~/.akari/assets/<category>/<id>/` へ原子的に登録
62
+ 5. 全部通ってから `<ライブラリの置き場>/<category>/<id>/` へ原子的に登録
58
63
 
59
64
  無料経路と同じ fail-closed の規律(1 件でも失敗したら一時ディレクトリを破棄し、登録先には
60
65
  一切書き込まない)を踏襲する。ダウンロード失敗(オフライン・トークン失効)・zip 構成不正・
@@ -101,7 +106,8 @@ checksums 不一致は、いずれも `AssetResolverError`(`code: 'download_fa
101
106
 
102
107
  | 変数 | 既定値 | 用途 |
103
108
  | --- | --- | --- |
104
- | `AKARI_HOME` | `~/.akari` | ライブラリ(`assets/`)・カタログキャッシュ・`store-credentials.json` の置き場 |
109
+ | `AKARI_HOME` | `~/.akari` | マシン状態(library-location.json・カタログキャッシュ・store-credentials.json)。作業場なしでは旧 assets/ もここに置く |
110
+ | `AKARI_LIBRARY_ROOT` | 未設定 | ライブラリの書き込み先の明示上書き(読みは旧置き場にもフォールバック) |
105
111
  | `AKARI_ASSETS_CATALOG` | `https://akari-oss.app/assets/catalog.json` | カタログの取得元。**URL** ならリモート fetch、それ以外はローカルファイルパスとして読む(未デプロイの開発時は store リポのローカル出力を指す) |
106
112
  | `AKARI_ASSETS_BASE` | カタログの `base` フィールド | 素材実体の配信ベースの上書き(ローカル開発でディレクトリを直接指すときに使う) |
107
113
  | `AKARI_STORE_API` | `https://akari-oss.app` | entitlements API のホスト上書き。未設定時は `~/.akari/store-credentials.json` の `url`(`akari store connect` が書き込む値)から組み立てる |
@@ -145,3 +151,22 @@ node --test
145
151
  - `packages/akari-launcher/src/store-command.mjs` の `akari store connect` が書く
146
152
  `~/.akari/store-credentials.json`(`{ url, token, email }`)をそのまま読む
147
153
  (依存追加を避けるため import はせず、同じファイル規約だけを踏襲)
154
+
155
+ ## ライブラリの解決と移行
156
+
157
+ `resolveAssetLibraryRoots(env, { platform })` の正本は `packages/creator-root/src/index.mjs`。
158
+ 書き込みは `AKARI_LIBRARY_ROOT` → `<AKARI_HOME>/library-location.json` → 従来の `assets/` の順で 1 か所に決まる。
159
+ location が有効なのは migrating / done のときだけ。pending / declined では旧置き場への読み書きを維持し、同期フォルダへは書き込まない。
160
+ 明示の AKARI_LIBRARY_ROOT は最優先。読み取りは有効な置き場と従来の置き場を重複なく見る(新しい方を優先)。cwd の上方探索はしない。
161
+ `list --json` は互換性のため従来どおり素材配列のみを返す。置き場表示は通常の `list` の先頭行。
162
+
163
+ シェルと launcher の起動時に共通の移行実装を呼ぶ。`library-location.json` は version 0、
164
+ root(絶対パス)、state(pending / migrating / done / declined)、decidedAt、migratedAt、notifiedAt を持ち、
165
+ 一時ファイル + rename で原子的に更新する。不在・壊れは従来の置き場へフォールバックする。
166
+ 移行先を初めて決めるときだけ `AKARI_CREATOR_ROOT` とマシンポインタの `lastRoot` を使う。
167
+
168
+ 同一ディスクは rename、EXDEV は複製・サイズと sha256 照合・旧側削除。同名素材は上書きせず旧側に残し、
169
+ 結果の skipped に記録する。カテゴリの中の新規素材は再移行できる。付随ファイルとキットの参照も追随する。
170
+ 途中失敗は migrating のまま再開可能。done 後に旧 CLI が追加した素材は `akari-assets migrate` で寄せる。
171
+ 作業場なしは何もしない。OneDrive / Dropbox / iCloud Drive / Google Drive 配下は pending に留め、
172
+ 結果に同期先と総容量を返す。ドロップフォルダと reviews はマシン状態として元の場所に残す。
@@ -7,6 +7,7 @@
7
7
  // akari-assets sync
8
8
  // akari-assets browse [--port <n>]
9
9
 
10
+ import { migrateAssetLibrary } from '../../creator-root/src/index.mjs';
10
11
  import { startBrowseServer } from '../src/browse-server.mjs';
11
12
  import { bundleProjectReferences } from '../src/bundle.mjs';
12
13
  import { cacheCatalog, loadCatalog } from '../src/catalog.mjs';
@@ -29,7 +30,7 @@ function badgeOf(item) {
29
30
  async function cmdList(args, env) {
30
31
  const category = flagValue(args, '--category');
31
32
  const asJson = args.includes('--json');
32
- const { home, items } = await composeState({ env });
33
+ const { libraryRoots, items } = await composeState({ env });
33
34
  const filtered = category ? items.filter((item) => item.category === category) : items;
34
35
 
35
36
  if (asJson) {
@@ -37,7 +38,7 @@ async function cmdList(args, env) {
37
38
  return;
38
39
  }
39
40
 
40
- console.log(`使える素材 ${filtered.length} 件(ライブラリ: ${home})`);
41
+ console.log(`使える素材 ${filtered.length} 件(ライブラリ: ${libraryRoots.write})`);
41
42
  for (const item of filtered) {
42
43
  console.log(` ${badgeOf(item)} ${item.id}\t[${item.category}]\t${item.title}`);
43
44
  }
@@ -119,17 +120,19 @@ async function cmdBrowse(args, env) {
119
120
  }
120
121
 
121
122
  function printUsage() {
122
- console.log(`使い方: akari-assets <list|fetch|bundle|sync|browse> [options]
123
+ console.log(`使い方: akari-assets <list|fetch|bundle|migrate|sync|browse> [options]
123
124
 
124
125
  list [--category <c>] [--json] 合成カタログ一覧(取得状態バッジ込み)
125
126
  fetch <id> [--project <dir>] [--reference] [--force]
126
127
  素材を解決して登録(--reference はコピーせず参照を記帳)
127
128
  bundle --project <dir> [--dry-run] 参照素材をプロジェクトへ実体化(素材をまとめる)
129
+ migrate [--dry-run] ライブラリを作業場へ移行
128
130
  sync カタログを取得してローカルにキャッシュ(オフライン用)
129
131
  browse [--port <n>] ローカル HTTP サーバでカタログを閲覧・投入(既定 8910)
130
132
 
131
133
  環境変数:
132
- AKARI_HOME ライブラリの置き場(既定: ~/.akari)
134
+ AKARI_HOME マシン設定の置き場(既定: ~/.akari)
135
+ AKARI_LIBRARY_ROOT ライブラリの置き場の上書き
133
136
  AKARI_ASSETS_CATALOG カタログの取得元。URL またはローカルパス(既定: akari-oss.app/assets/catalog.json)
134
137
  AKARI_ASSETS_BASE 素材実体の配信ベースの上書き(既定はカタログの "base" フィールド)
135
138
  AKARI_STORE_API entitlements API のホスト上書き(既定: akari-oss.app)`);
@@ -139,6 +142,12 @@ async function main() {
139
142
  const [sub, ...rest] = process.argv.slice(2);
140
143
  const env = process.env;
141
144
 
145
+ if (sub === 'migrate') {
146
+ const result = await migrateAssetLibrary({ env, dryRun: rest.includes('--dry-run') });
147
+ console.log(JSON.stringify(result, null, 2));
148
+ if (result.failures.length) process.exitCode = 1;
149
+ return;
150
+ }
142
151
  if (sub === 'list') return cmdList(rest, env);
143
152
  if (sub === 'fetch') return cmdFetch(rest, env);
144
153
  if (sub === 'bundle') return cmdBundle(rest, env);
@@ -1,5 +1,4 @@
1
- import { resolveAkariHome } from './env.mjs';
2
- import { isAssetCached, localAssetDir } from './library.mjs';
1
+ import { isAssetCached, cachedAssetDir } from './library.mjs';
3
2
  import { readProjectReferences, removeProjectReference } from './project-references.mjs';
4
3
  import { copyIntoProject, resolve as resolveAsset } from './resolve.mjs';
5
4
 
@@ -12,10 +11,9 @@ export async function bundleProjectReferences({
12
11
  const result = { planned, materialized: [], failures: [] };
13
12
  if (dryRun) return result;
14
13
 
15
- const home = resolveAkariHome(env);
16
14
  for (const reference of planned) {
17
15
  try {
18
- if (!isAssetCached(home, reference.category, reference.id)) {
16
+ if (!isAssetCached(env, reference.category, reference.id)) {
19
17
  const resolved = await resolveAsset(reference.id, { env });
20
18
  if (resolved.category !== reference.category) {
21
19
  throw new Error(
@@ -23,7 +21,7 @@ export async function bundleProjectReferences({
23
21
  );
24
22
  }
25
23
  }
26
- const sourceDir = localAssetDir(home, reference.category, reference.id);
24
+ const sourceDir = cachedAssetDir(env, reference.category, reference.id);
27
25
  const projectDir = await copyIntoProject(
28
26
  sourceDir,
29
27
  project,