akari-video 0.1.66 → 0.1.67

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 (86) hide show
  1. package/bin/akari.mjs +2 -0
  2. package/package.json +1 -1
  3. package/src/doctor-command.mjs +5 -0
  4. package/src/generate-command.mjs +16 -0
  5. package/src/messages.mjs +1 -0
  6. package/src/repo-assets.mjs +7 -1
  7. package/src/runtime-diagnostics.mjs +29 -0
  8. package/vendor/.akari-capability-sources.json +6 -0
  9. package/vendor/docs/contract-2026-07-20-plan-json-v0.md +12 -0
  10. package/vendor/docs/contract-2026-07-25-plan-comments-v0.md +10 -11
  11. package/vendor/docs/contract-2026-09-13-generation-v0.md +195 -0
  12. package/vendor/packages/akari-launcher/package.json +1 -1
  13. package/vendor/packages/creator-root/src/index.mjs +6 -0
  14. package/vendor/packages/decision-cards/README.md +12 -0
  15. package/vendor/packages/edit-store/lib/generation-meta.d.ts +4 -1
  16. package/vendor/packages/edit-store/lib/generation-meta.js +10 -7
  17. package/vendor/packages/generate/README.md +6 -0
  18. package/vendor/packages/generate/package.json +5 -1
  19. package/vendor/packages/overlay-runtime/README.md +2 -0
  20. package/vendor/packages/schemas/bin/validate-connections.mjs +21 -1
  21. package/vendor/packages/schemas/bin/validate-kit-manifest.mjs +374 -0
  22. package/vendor/packages/schemas/bin/validate-plan-comments.mjs +2 -2
  23. package/vendor/packages/schemas/bin/validate-world-map.mjs +170 -0
  24. package/vendor/packages/schemas/connections.schema.json +25 -0
  25. package/vendor/packages/schemas/examples/connections-v0-defaults-absent-valid/connections.json +8 -0
  26. package/vendor/packages/schemas/examples/connections-v0-defaults-unknown-key-invalid/connections.json +13 -0
  27. package/vendor/packages/schemas/examples/connections-v0-defaults-valid/connections.json +14 -0
  28. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/LICENSE.md +1 -0
  29. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/README.md +1 -0
  30. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-kind/manifest.json +9 -0
  31. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/LICENSE.md +1 -0
  32. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/README.md +1 -0
  33. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-missing-required/manifest.json +8 -0
  34. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/LICENSE.md +1 -0
  35. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/README.md +1 -0
  36. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-runtime/manifest.json +12 -0
  37. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/LICENSE.md +1 -0
  38. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/README.md +1 -0
  39. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-dir/manifest.json +12 -0
  40. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/LICENSE.md +1 -0
  41. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/README.md +1 -0
  42. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/manifest.json +12 -0
  43. package/vendor/packages/schemas/examples/kit-manifest-v1-invalid-skill-name/skills/manifest-name/SKILL.md +6 -0
  44. package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/LICENSE.md +1 -0
  45. package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/README.md +1 -0
  46. package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/manifest.json +12 -0
  47. package/vendor/packages/schemas/examples/kit-manifest-v1-name-collision/skills/edit-lint/SKILL.md +6 -0
  48. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/LICENSE.md +3 -0
  49. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/README.md +3 -0
  50. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/docs/guide.md +3 -0
  51. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/manifest.json +27 -0
  52. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/skills/sample-kit-skill/SKILL.md +8 -0
  53. package/vendor/packages/schemas/examples/kit-manifest-v1-valid/templates/sample.json +3 -0
  54. package/vendor/packages/schemas/examples/world-map-v1-no-worlds-legacy/planning/world-map.json +12 -0
  55. package/vendor/packages/schemas/examples/world-map-v2-flat-legacy/planning/world-map.json +10 -0
  56. package/vendor/packages/schemas/examples/world-map-v2-spatial-legacy/planning/world-map.json +27 -0
  57. package/vendor/packages/schemas/examples/world-map-v3-flat-valid/planning/world-map.json +34 -0
  58. package/vendor/packages/schemas/examples/world-map-v3-invalid-c10-same-world-portal/planning/world-map.json +34 -0
  59. package/vendor/packages/schemas/examples/world-map-v3-invalid-c3-edge-timing/planning/world-map.json +34 -0
  60. package/vendor/packages/schemas/examples/world-map-v3-invalid-c5-carry/planning/world-map.json +34 -0
  61. package/vendor/packages/schemas/examples/world-map-v3-invalid-c6-push-in-spatial/planning/world-map.json +26 -0
  62. package/vendor/packages/schemas/examples/world-map-v3-invalid-c7-cut-cover-null/planning/world-map.json +34 -0
  63. package/vendor/packages/schemas/examples/world-map-v3-spatial-valid/planning/world-map.json +26 -0
  64. package/vendor/packages/schemas/kit-manifest.schema.json +108 -0
  65. package/vendor/packages/schemas/package.json +3 -1
  66. package/vendor/packages/schemas/plan-comments.schema.json +3 -3
  67. package/vendor/packages/schemas/test/kit-manifest.test.mjs +83 -0
  68. package/vendor/packages/schemas/test/validate-connections.test.mjs +21 -0
  69. package/vendor/packages/schemas/test/validate-plan-comments.test.mjs +22 -0
  70. package/vendor/packages/schemas/test/world-map-schema.test.mjs +116 -0
  71. package/vendor/packages/schemas/world-map.schema.json +166 -0
  72. package/vendor/skills/edit-plan/approvals-and-generation.md +2 -2
  73. package/vendor/skills/edit-plan/plan-json.md +5 -5
  74. package/vendor/skills/edit-plan/workflow.md +6 -5
  75. package/vendor/skills/generate-media/SKILL.md +43 -0
  76. package/vendor/skills/generate-media/chain.md +32 -0
  77. package/vendor/skills/generate-media/engines.md +23 -0
  78. package/vendor/skills/generate-media/still.md +79 -0
  79. package/vendor/skills/generate-media/video.md +54 -0
  80. package/vendor/skills/manage-connections/SKILL.md +20 -5
  81. package/vendor/skills/manage-connections/bin/doctor.mjs +71 -29
  82. package/vendor/skills/manage-connections/bin/resolve-connections.mjs +35 -3
  83. package/vendor/skills/manage-connections/test/doctor-key-source.test.mjs +74 -0
  84. package/vendor/skills/manage-connections/test/resolve-connections-defaults.test.mjs +78 -0
  85. package/vendor/skills/overlay-authoring/3d.md +4 -1
  86. package/vendor/skills/render-cut/SKILL.md +12 -0
package/bin/akari.mjs CHANGED
@@ -13,6 +13,7 @@ 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';
16
17
  import { resolveRuntimePaths } from '../src/runtime-diagnostics.mjs';
17
18
  import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
18
19
  import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
@@ -80,6 +81,7 @@ const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
80
81
  : argv[0] === 'assets' ? runAssetsCommand(argv.slice(1))
81
82
  : argv[0] === 'migrate' ? runMigrateCommand(argv.slice(1))
82
83
  : argv[0] === 'clean' ? runCleanCommand(argv.slice(1))
84
+ : argv[0] === 'generate' ? runGenerateCommand(argv.slice(1))
83
85
  : run(argv);
84
86
 
85
87
  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.66",
3
+ "version": "0.1.67",
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": {
@@ -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/messages.mjs CHANGED
@@ -330,6 +330,7 @@ export function describeCliHelp() {
330
330
  ' update [--force] 更新を確認する(--force で install.sh 経路の本体を入れ直す)',
331
331
  ' status 接続状態を確認する',
332
332
  ' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
333
+ ' generate 台本のビートから静止画クリップを生成',
333
334
  ' akari clean [dir] 使い捨ての中間ファイルを一覧・削除(既定は一覧のみ)',
334
335
  '',
335
336
  '開発者向け:',
@@ -39,6 +39,7 @@ 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');
42
43
 
43
44
  /**
44
45
  * 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold 実装・creator-root
@@ -60,6 +61,7 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
60
61
  const mediaScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'media.mjs');
61
62
  const decisionLogScript = path.join(repoRoot, DECISION_LOG_SCRIPT_RELATIVE);
62
63
  const wordBookScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'word-book.mjs');
64
+ const generateScript = path.join(repoRoot, GENERATE_CLI_RELATIVE);
63
65
 
64
66
  return {
65
67
  repoRoot,
@@ -79,7 +81,8 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
79
81
  eyeBarScript: existsSync(eyeBarScript) ? eyeBarScript : null,
80
82
  mediaScript: existsSync(mediaScript) ? mediaScript : null,
81
83
  ...(existsSync(decisionLogScript) ? { decisionLogScript } : {}),
82
- ...(existsSync(wordBookScript) ? { wordBookScript } : {})
84
+ ...(existsSync(wordBookScript) ? { wordBookScript } : {}),
85
+ ...(existsSync(generateScript) ? { generateScript } : {})
83
86
  };
84
87
  }
85
88
 
@@ -115,6 +118,9 @@ export function resolveLauncherAssets({
115
118
  renderWhenIdleScript: candidate.renderWhenIdleScript ?? vendor.renderWhenIdleScript,
116
119
  eyeBarScript: candidate.eyeBarScript ?? vendor.eyeBarScript,
117
120
  mediaScript: candidate.mediaScript ?? vendor.mediaScript,
121
+ ...(candidate.generateScript ?? vendor.generateScript
122
+ ? { generateScript: candidate.generateScript ?? vendor.generateScript }
123
+ : {}),
118
124
  ...(candidate.decisionLogScript ?? vendor.decisionLogScript ? { decisionLogScript: candidate.decisionLogScript ?? vendor.decisionLogScript } : {}),
119
125
  ...(candidate.wordBookScript ?? vendor.wordBookScript
120
126
  ? { 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
 
@@ -67,6 +67,7 @@
67
67
  "docs/contract-2026-09-06-cut-audio-split-v0.md",
68
68
  "docs/contract-2026-09-06-vgpu-layer-v0.md",
69
69
  "docs/contract-2026-09-12-review-session-viewer.md",
70
+ "docs/contract-2026-09-13-generation-v0.md",
70
71
  "packages/akari-launcher/package.json",
71
72
  "packages/akari-launcher/README.md",
72
73
  "packages/akari-tools/package.json",
@@ -165,6 +166,11 @@
165
166
  "skills/edit-plan/SKILL.md",
166
167
  "skills/edit-plan/workflow.md",
167
168
  "skills/export-nle/SKILL.md",
169
+ "skills/generate-media/chain.md",
170
+ "skills/generate-media/engines.md",
171
+ "skills/generate-media/SKILL.md",
172
+ "skills/generate-media/still.md",
173
+ "skills/generate-media/video.md",
168
174
  "skills/generate-narration/assets/reading-script-v1.md",
169
175
  "skills/generate-narration/avatar-voice.md",
170
176
  "skills/generate-narration/engines.md",
@@ -1,5 +1,9 @@
1
1
  # plan.json v0(仮枠タイムライン)契約
2
2
 
3
+ ## 2026-09-13 追記 — 仮枠役の退役
4
+
5
+ plan.json の仮枠役(`confidence` / `fill` / §6 の処理)は退役した。現在の仮枠は edit.json v2 のタイムライン上の静止画 media item + 素材の隣の `<path>.meta.json` である([生成 v0 契約 §1](./contract-2026-09-13-generation-v0.md#1-継ぎ目仮枠とタイムライン))。企画層(`research-plan.json` / `planning/`)は存続する。以下は旧契約と移行経緯を保持するため本文を残す。
6
+
3
7
  - 日付: 2026-07-20
4
8
  - 状態: 実装ラウンドの SSOT(データ形と検証のみ確定。仮枠コンパイラは §6 の規約先行・実装は次段)
5
9
  - 前提: 仮枠タイムラインの方向性メモ §3〜§5(本契約はこの節群を昇格したもの。
@@ -103,6 +107,8 @@ edit.json v1 の「cuts[] を配列順にギャップなく連結」(contract-
103
107
 
104
108
  ## 3. confidence 状態遷移
105
109
 
110
+ (退役・2026-09-13 追記参照)
111
+
106
112
  `confidence` は**保存フィールド**であり、`media` の有無から導出しない(locked かつ未充填
107
113
  = 「空きスロットだが方向は確定」は正当な状態。ここが導出だと収録前ロックが表現できない)。
108
114
 
@@ -119,6 +125,8 @@ edit.json v1 の「cuts[] を配列順にギャップなく連結」(contract-
119
125
 
120
126
  ## 4. fill — スロットの充填手段
121
127
 
128
+ (退役・2026-09-13 追記参照)
129
+
122
130
  空きスロットはプラン由来の `script` と `target_duration_seconds` を既に知っている。
123
131
  充填手段は 3 種 + 未決:
124
132
 
@@ -151,6 +159,8 @@ plan.json に書く。plan.json 自体は decision id を持たない(`decisio
151
159
 
152
160
  ## 6. プレビュー合成規約 — 普通の edit.json v1 へコンパイルする
153
161
 
162
+ (退役・2026-09-13 追記参照)
163
+
154
164
  仮枠の再生(アニマティクス QA)は専用レンダラーを作らず、**通常の edit.json v1 への
155
165
  コンパイル**で実現する。プレビュー・edit-lint・render-cut が無改造でそのまま使えることが
156
166
  本規約の眼目であり、**edit.json に新しい語彙を 1 つも足さない**。
@@ -224,6 +234,8 @@ plan は企画データであり、検証・表示・下流工程の全体を巻
224
234
 
225
235
  ## 11. 次段(本契約のスコープ外)
226
236
 
237
+ (退役・2026-09-13 追記参照)
238
+
227
239
  - 仮枠コンパイラ実装(§6 の規約に従う。画像生成/TTS の実行・ffmpeg ベイク・manifest 生成)
228
240
  - 質問対話 UI の充実(当面は decision-cards の既存機構で代替。skills/edit-plan/plan-json.md 参照)
229
241
  - タイムラインのゴースト描画(confidence 別表示。akari-annotations の strip 描画へ統合)
@@ -43,8 +43,8 @@
43
43
  "submitted_at": "2026-07-25T09:30:00.000Z",
44
44
  "comments": [
45
45
  {
46
- "target_kind": "slot", // "shot" | "slot" | "cut"
47
- "target_id": "s-demo", // shot/cut は配列インデックス文字列・slotplan.json の slots[].id
46
+ "target_kind": "clip", // "shot" | "clip" | "cut"。"slot" は退役
47
+ "target_id": "item-demo", // scaffold clip edit.json v2 item id
48
48
  "title": "操作デモ", // 提出時点の対象名コピー(並べ替え検知用)
49
49
  "text": "画面収録の前に、書き出しボタンをクリックする瞬間まで映してほしい。"
50
50
  }
@@ -60,9 +60,9 @@
60
60
  | `pass` | enum | 要 | `structure` / `scaffold` / `final`(§4) |
61
61
  | `submitted_at` | string (ISO8601) | 要 | 提出時刻 |
62
62
  | `comments[]` | array | 要(空配列可) | 配列順に意味はない(対象名指しの集合) |
63
- | `comments[].target_kind` | enum | 要 | `shot` / `slot` / `cut` |
64
- | `comments[].target_id` | string | 要 | `shot`・`cut` は対象配列のインデックスを文字列化したもの(例 `"2"`)。`slot` は `plan.json``slots[].id` をそのまま使う(idごと持ち出せるため並べ替えに強い) |
65
- | `comments[].title` | string | 要 | 提出時点の対象名コピー(`shot` の `description` 冒頭 / `slot` `label` / `cut` の `src` 等、読み手が実装時に選ぶ)。**並べ替え検知用の目視補助であり、参照解決には使わない**(参照解決は `target_id` のみで行う) |
63
+ | `comments[].target_kind` | enum | 要 | `shot` / `clip` / `cut`。旧 `slot` は退役 |
64
+ | `comments[].target_id` | string | 要 | `shot`・`cut` は対象配列のインデックスを文字列化したもの(例 `"2"`)。`clip` は edit.json v2 item id |
65
+ | `comments[].title` | string | 要 | 提出時点の対象名コピー(`shot` の `description` 冒頭 / `clip` の表示名 / `cut` の `src` 等、読み手が実装時に選ぶ)。**並べ替え検知用の目視補助であり、参照解決には使わない**(参照解決は `target_id` のみで行う) |
66
66
  | `comments[].text` | string | 要 | 指摘の逐語。要約・言い換えをしない |
67
67
 
68
68
  「要」= 値は空文字列を許さない(`comments[]` 自体は空配列を許す。提出はしたが個別指摘がない回はあり得るため)。
@@ -105,7 +105,7 @@
105
105
  | `pass` | 対象 `target_kind` | 読み手スキル | 対象データ |
106
106
  |---|---|---|---|
107
107
  | `structure` | `shot` | `skills/research-plan/storyboard.md`(`structure-confirm` 決定カードの差し戻し) | `research-plan.json` の `structure.chapters[]` / `structure.shots[]`(`shot` は `structure.shots[]` の配列インデックス) |
108
- | `scaffold` | `slot` | `skills/edit-plan/approvals-and-generation.md`(Checkpoint 2 素材計画の差し戻し) | `plan.json` の `slots[]`(`target_id` は `slots[].id`) |
108
+ | `scaffold` | `clip` | `skills/edit-plan/approvals-and-generation.md`(Checkpoint 2 素材計画の差し戻し) | `edit.json` v2 media item(`target_id` は item id)。旧 `slot` は退役 |
109
109
  | `final` | `cut` | `skills/edit-plan/approvals-and-generation.md`(Checkpoint 3 実行の差し戻し) | `edit.json` の `cuts[]`(`cut` は `cuts[]` の配列インデックス。`cuts[]` は永続 id を持たないため — `contract-2026-07-18-edit-json-v1-sources.md`) |
110
110
 
111
111
  3 つの組は 1 対 1 対応(`pass` が決まれば `target_kind` も決まる)。スキーマは
@@ -131,9 +131,8 @@
131
131
  現在の対象名と目視・機械比較することで「ズレ」を検知するための補助情報(正典は `target_id`)
132
132
  - **`shot`/`cut` がインデックス文字列である理由**: `research-plan.json` の `structure.shots[]` は
133
133
  id を持つが、GUI 側の実装を「今何番目のカードにコメントしたか」で完結させるため
134
- インデックスに揃える。`slot` だけ `plan.json``slots[].id` を使うのは、
135
- plan.json slot が並べ替え時も id で安定して指せる設計(`contract-2026-07-20` §2)だから
136
- ——同じ理由で cut は id を持たないため slot と同じ扱いにできない
134
+ インデックスに揃える。`scaffold` `clip` は edit.json v2 item id を使い、並べ替え後も
135
+ 安定して対象を指す。旧 `slot` plan.json の仮枠役とともに退役した
137
136
  - **`comments[]` を配列(連想配列でない)にした理由**: 同一対象への複数コメントを許す
138
137
  (1 対象 1 コメントに制限しない)
139
138
  - **状態を持たない理由**: `plan-comments.json` はイベント(差し戻し 1 回分)の記録であり、
@@ -147,8 +146,8 @@
147
146
  - **名指しされていない対象まで一緒に直す** — 誤り。名指し対象だけを改訂する(§3 の 3)
148
147
  - **`plan-comments.json` に `confidence` のような永続状態を持たせる** — 誤り。§0・§6。
149
148
  状態は plan.json 側の責務
150
- - **`slot` `target_id` にインデックスを使う** — 誤り。`slot` `plan.json` の `slots[].id`
151
- を使う(`shot`/`cut` とは扱いが違う。§6)
149
+ - **`scaffold` `target_kind: "slot"` を使う** — 誤り。`slot` は退役済み。`target_kind: "clip"`
150
+ edit.json v2 の item id を使う(§4・§6)
152
151
  - **提出のたび追記する(過去の `comments[]` を残したまま足す)** — 誤り。§2。1 回の提出 = 1 回の
153
152
  上書き作成。回収済みの指摘を残さない
154
153
 
@@ -0,0 +1,195 @@
1
+ # 生成 v0 契約 — 仮枠クリップ・9 スロット・meta.json・能力カタログ・状態
2
+
3
+ > 正本の写し(2026-09-13)・技術部分のみ。内部正本の §1〜§9 を公開向けに収録する。
4
+
5
+ ## 1. 継ぎ目(仮枠とタイムライン)
6
+
7
+ 1. 仮枠は **edit.json v2 の media item** で、`sources[].path` が静止画(png / jpg)か文字カード png を指す。cuts で尺を持つ(静止画クリップ契約 2026-08-12 のとおり)
8
+ 2. 生成の意図と状態は、その素材の隣の **`<path>.meta.json`** に持つ(§3)。edit.json には何も足さない
9
+ 3. meta と素材の結線は **素材の sha256** が正。path は手掛かり。移動・改名は path を更新、複製は同じ meta を共有してよい(sha256 が同じ)
10
+ 4. 「動画にする」は同じ item の `source.path` を mp4 に書き換える**差し替え**であり、item id・トラック・尺は変わらない(§7)
11
+ 5. 絵コンテはタイムラインの印刷(9/6 §5)。生成の入力にしない
12
+ 6. plan.json の仮枠役(`confidence` / `fill`)は退役。`plan-comments.json` の `pass: "scaffold"` の対象は `slot` から **clip id** へ
13
+
14
+ ## 2. 9 スロット(生成入力の正規形)
15
+
16
+ すべてのモデルに対して、入力はこの 9 つのどれかに落とす。アダプタが provider の引数名に翻訳する(§5)。
17
+
18
+ | # | スロット | 型 | 備考 |
19
+ |---|---|---|---|
20
+ | ① | `prompt` / `negative_prompt` | string / string? | 動きの記法(⑧)は prompt に合成してから送る |
21
+ | ② | `first_frame` | 参照要素? | 既定 = そのクリップの静止画。**任意**(H3 は最後だけでも生成できる) |
22
+ | ③ | `last_frame` | 参照要素? | 対応は主要 7 家。非対応モデルで値があれば検証エラー |
23
+ | ④ | `reference_images[]` | 参照要素[] | 上限はカタログ。`name` / `role` はモデルが要求するときだけ使う |
24
+ | ⑤ | `reference_videos[]` | 参照要素[] | 本数と合計秒。**動きを真似る元**(貼り込まれない) |
25
+ | ⑥ | `reference_audios[]` | 参照要素[] | 既定候補 = クリップ範囲のナレーション。Wan の BGM 添付はここに入れない |
26
+ | ⑦ | `source_video` + `mode` | 参照要素? + `edit` / `extend` / `motion` / `frame-edit` | **続ける・直す元**。v1 は欄のみ |
27
+ | ⑧ | `camera` | `{ notation: "bracket" / "trajectory" / "prose", value, from_annotation? }` | 注釈ペンからの翻訳先。notation はモデルに合わせてアダプタが選ぶ。**絵には焼かない** |
28
+ | ⑨ | `seed` | integer? | 再現用の表示のみ。決定論は保証しない |
29
+ | 出力ノブ | `duration_s` / `resolution` / `aspect` / `audio_out` | number / string? / string? / boolean? | 尺は cuts から。モデルの許容に丸め、丸めた事実を表示 |
30
+ | 逃げ道 | `extra` | object | モデル固有の引数。**カタログ行の `extra_allowed[]` にある名前だけ**通す |
31
+
32
+ 参照要素 = `{ path, sha256, source_id?, name?, role?, range_s?: [in, out] }`。path はプロジェクト相対。
33
+
34
+ ## 3. meta.json v1(素材サイドカー)
35
+
36
+ 置き場: 生成物は `assets/generated/<file>`、サイドカーは `assets/generated/<file>.meta.json`。実写や既存素材から生成するときも、**生成物の隣**に置く(元素材の隣ではない)。
37
+
38
+ ```json
39
+ {
40
+ "version": 1,
41
+ "kind": "video", // "still" | "video" | "frames"
42
+ "status": "done", // "planned" | "generating" | "done" | "failed"
43
+ "model": { "id": "fal:h3-i2v", "endpoint": "minimax/h3/image-to-video", "as_of": "2026-09-12" },
44
+ "inputs": { // §2 の 9 スロット。値の無いスロットは null / []
45
+ "prompt": "…", "negative_prompt": null,
46
+ "first_frame": { "path": "assets/stills/s03-leaving-desk.png", "sha256": "…", "source_id": "src-03a" },
47
+ "last_frame": { "path": "assets/stills/s03-family-garden.png", "sha256": "…", "source_id": "src-03b" },
48
+ "reference_images": [], "reference_videos": [], "reference_audios": [],
49
+ "source_video": null,
50
+ "camera": { "notation": "bracket", "value": "[Tracking shot]", "from_annotation": null },
51
+ "seed": null, "extra": {}
52
+ },
53
+ "output": { "duration_s": 6, "resolution": "768P", "aspect": null, "audio_out": null },
54
+ "cost": { "estimate_usd": 0.36, "actual_usd": null, "unit": "usd_per_second", "source": "estimate" },
55
+ "job": { "provider": "fal", "request_id": "01a0…", "status_url": "…", "response_url": "…", "started_at": "2026-09-13T…", "stale_after_s": 900 },
56
+ "provenance": { "created_at": "…", "tool": "akari generate video", "key_source": "env:FAL_KEY" },
57
+ "result": { "path": "assets/generated/s03-leaving-to-garden.mp4", "sha256": "…", "bytes": 0, "duration_s_actual": 6.592,
58
+ "width": 1344, "height": 768, "fps": "24/1", "has_audio": true, "expanded_prompt": "…", "elapsed_s": 206 },
59
+ "history": [ { "at": "…", "status": "done", "reason": null } ]
60
+ }
61
+ ```
62
+
63
+ 規則:
64
+
65
+ 1. **送る前に `job` を書く**(request_id・started_at)。落ちても再取得できる(`akari generate resume`)
66
+ 2. `status: generating` かつ `started_at` から `stale_after_s` 超 = **stale**。シェルは「応答なし・再取得」を出す。既定 900 秒
67
+ 3. 状態の遷移は undo に入れない。**差し替え(§7)だけ** edit-store の undo に 1 手として入る
68
+ 4. `history[]` は消さない。失敗も残す
69
+ 5. `kind: "still"` は `inputs.reference_images` に `--image=` の 1 枚(Codex)か複数枚(Nano Banana Pro)。`kind: "frames"` はパラパラ(別契約・§10)
70
+ 6. `result.expanded_prompt` は provider が書き換えた prompt。較正では **送った prompt ではなく expanded を評価対象**に添える
71
+
72
+ ## 4. 能力カタログ `gen-models.json`(公開リポ `packages/schemas/`)
73
+
74
+ ### 4-1. 形(1 行 = 1 エンドポイント)
75
+
76
+ ```json
77
+ {
78
+ "id": "fal:h3-i2v", "kind": "video", "provider": "fal", "family": "MiniMax H3",
79
+ "endpoint": "minimax/h3/image-to-video",
80
+ "inputs": {
81
+ "first_frame": "optional", "last_frame": "optional",
82
+ "reference_images": { "max": 0 }, "reference_videos": { "max": 0 }, "reference_audios": { "max": 0 },
83
+ "source_video": [], "frames_and_refs_exclusive": false,
84
+ "negative_prompt": false, "camera": "bracket", "extra_allowed": ["prompt_expansion_mode"]
85
+ },
86
+ "duration": { "kind": "range", "min": 5, "max": 15, "step": 1, "default": 5, "format": { "type": "integer" } },
87
+ "resolutions": ["480P", "768P", "2K", "4K"], "aspects": null,
88
+ "audio_out": "always",
89
+ "seed": true,
90
+ "price": { "unit": "usd_per_second", "by_resolution": { "480P": 0.05, "768P": 0.06, "2K": 0.13, "4K": 0.16 }, "audio_multiplier": null },
91
+ "as_of": "2026-09-12",
92
+ "source_url": "https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=minimax/h3/image-to-video",
93
+ "price_url": "https://fal.ai/models/minimax/h3/image-to-video",
94
+ "verified": "documented",
95
+ "calibration": ["calibration/2026-08-23-camera-direction", "lab/2026-09-13-w0-spikes"]
96
+ }
97
+ ```
98
+
99
+ - `inputs.first_frame` / `last_frame` は `"required"` / `"optional"` / `"none"` の 3 値
100
+ - `duration.format` は `{type: "integer"}` / `{type: "string"}` / `{type: "string", suffix: "s"}` / `{type: "string", auto: true}`
101
+ - `audio_out` は `true`(切替可)/ `"always"`(欄なしで付く)/ `false`
102
+ - `price` は **null 可**(B-1)。null の行は UI が「見積不可」と出し、明示確認で通す
103
+
104
+ ### 4-2. 7 規律(旧 Akari OS の欠陥の否定形。w1 の受け入れ条件)
105
+
106
+ 1. 能力は真偽と上限の欄で持つ。`capability` スラッグを作らない
107
+ 2. 写像表に無いスロットに値が来たら**送らずに失敗**(fail closed)
108
+ 3. バリデータ 1 本を CLI・シェル UI・エージェントで共用
109
+ 4. 全欄必須・`additionalProperties: false`
110
+ 5. `verified: "documented"` の行だけ収載。推定行は内部リポの下書き
111
+ 6. 主観点数を持たない。価格・尺・入力・音声・`calibration[]` だけ
112
+ 7. レーダーを作らない
113
+
114
+ ### 4-3. 初期収載 12 行
115
+
116
+ Kling v3 standard i2v / Kling v3 pro i2v / Veo 3.1 first-last / Veo 3.1 reference / Seedance 2.0 i2v / Seedance 2.0 reference / Seedance 2.5 i2v / H3 i2v / H3 reference / Wan 2.7 i2v / Grok Imagine i2v / Vidu Q3 i2v。画像: codex-image / nano-banana-pro edit。Sora は OpenAI 直アダプタが出来るまで入れない。
117
+
118
+ ### 4-4. 鮮度とドリフト
119
+
120
+ - `as_of` 必須。UI の費用表示に日付を添える
121
+ - 週次 CI: `source_url` の OpenAPI を取り、行の `inputs` / `duration` / `resolutions` と突き合わせる。差分があれば issue(価格は対象外。価格は `price_url` を人が見る)
122
+ - OpenAPI が取れない provider の行は `as_of` から 90 日で WARN
123
+
124
+ ## 5. アダプタ契約(9 スロット → provider 引数)
125
+
126
+ ### 5-1. 写像表
127
+
128
+ 各アダプタは 9 スロット × 出力ノブの**全セル**に「引数名 + 書式」か「拒否」を持つ。テストは全セルを網羅する。
129
+
130
+ ### 5-2. 尺の書式(スパイク実測)
131
+
132
+ | モデル | 送る形 |
133
+ |---|---|
134
+ | H3 | integer `6` |
135
+ | Kling v3 | string `"6"` |
136
+ | Seedance 2.0 | string `"6"`(`"auto"` 可) |
137
+ | Veo 3.1 | string `"6s"`(4 / 6 / 8 のみ。丸め必須) |
138
+
139
+ ### 5-3. 参照の渡し方
140
+
141
+ - 画像は data URI で送ってよい(5.2 MB で 24 秒)。**20 MB 超は fal storage へ先にアップロード**(後日)
142
+ - 順序タグ(@Image1 …)はアダプタが配列順から生成して prompt に付ける。名前 + 役割(PixVerse)は要素の `name` / `role` から
143
+ - 参照音声は `range_s` で切り出してから送る(クリップ範囲だけ)
144
+
145
+ ## 6. 状態と見え方
146
+
147
+ | 状態 | タイムライン | プレビュー(編集中) | 書き出し |
148
+ |---|---|---|---|
149
+ | `planned`(文字カード) | 点線 + 「planned」 | 文字カード + 左上小札 | 文字カードのまま。lint WARN |
150
+ | 静止画(meta なし or `done` の still) | 「静止画」バッジ | 静止画 + 左上小札「静止画(仮枠)」 | そのまま。小札なし |
151
+ | `generating` | 黄の縞 + 進捗バー | 静止画の上にシマー + 下端の帯 | 静止画のまま |
152
+ | stale | 縞 + 「応答なし・再取得」 | 帯に「応答なし」 | 静止画のまま |
153
+ | `done`(動画) | 通常 + コマ帯 + 「生成」由来バッジ | 動画。表示なし | 通常 |
154
+ | `failed` | 朱枠 + 「失敗」 | 静止画 + 朱の小札 | 静止画のまま |
155
+
156
+ - プレビューに付くのは**左上の小札と下端の帯だけ**。承認・比較の UI はプレビューに置かない
157
+ - **書き出し経路には小札・帯・点線が 0 px**。画素比較で担保(w3-b)
158
+
159
+ ## 7. 差し替え規則(done になったとき)
160
+
161
+ ### 7-1. 何を書き換えるか
162
+
163
+ 同じ item の `sources[].path` を mp4 に。item id・トラック・`at` は不変。静止画の path は `meta.inputs.first_frame` に残る(由来)。**1 手の undo**。
164
+
165
+ ### 7-2. 実尺のずれ
166
+
167
+ | 実尺 | 規則 |
168
+ |---|---|
169
+ | 実尺 ≥ cuts | `out = cuts の長さ`(末尾を切る)。差分は meta に記録 |
170
+ | 実尺 < cuts | `out = 実尺`、`freeze: { at_sec: 実尺, duration_sec: cuts − 実尺 }`(既存の `cutFreeze` 語彙。lint `media.source-range` を通り、タイムライン総尺は不変。**最終コマ停止**) |
171
+
172
+ 差分が 0.5 秒超なら lint が WARN(`generation.duration-mismatch`。lint 側の新規則は w2-b の別票候補)。
173
+
174
+ ### 7-3. 音声
175
+
176
+ 生成クリップの音声トラックは**既定 mute**(`item.mute: true`)。ナレーションと BGM が正。右パネルで解除できる。H3 のように欄なしで音が付くモデルでも同じ。
177
+
178
+ ## 8. 費用承認(有償生成のゲート)
179
+
180
+ 1. 語彙: 有償生成の承認は「**費用承認**」。「判子」は 9/6 契約どおり書き出しの 1 回にだけ使う
181
+ 2. 回数: 1 クリップずつが既定。複数を選んで**合計金額を出して 1 回**は可。「全部を自動で動画にする」は無い
182
+ 3. 見積: `price.by_resolution × duration_s × audio_multiplier`、as_of 付き。`actual_usd` が取れない provider は `estimate` を写して `cost.source: "estimate"`。見積不可(price null)は明示確認で通す
183
+ 4. 停止条件は autonomy §4 と同じ(有償 or 外部送信)。Codex 画像生成は無償扱い
184
+
185
+ ## 9. 用語(混同を防ぐ)
186
+
187
+ | 語 | 意味 |
188
+ |---|---|
189
+ | 仮枠 | 尺と場所を持つ静止画クリップ。完成品でもある |
190
+ | 参照動画 | 動きやカメラワークを**真似る元**。貼り込まれない |
191
+ | 元動画 | **続ける・直す元**(extend / edit / motion / frame-edit) |
192
+ | 費用承認 | 有償生成の実行前ゲート |
193
+ | 判子 | 書き出しの 1 回(9/6) |
194
+ | 事実帯 | モデル選択の 1 行(価格・尺・入力・音声・較正)。レーダーの代わり |
195
+
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
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": [
@@ -202,6 +202,12 @@ export const DEFAULT_CONNECTIONS_REGISTRY = {
202
202
  }
203
203
  }
204
204
  ],
205
+ defaults: {
206
+ generate: {
207
+ still: 'codex:image',
208
+ video: 'fal:h3-i2v'
209
+ }
210
+ },
205
211
  policy: {
206
212
  currency: 'JPY',
207
213
  monthly_budget: null,
@@ -10,6 +10,7 @@
10
10
  | `report-helper.mjs` | `127.0.0.1` のみにバインドするローカル HTTP ヘルパー。`report.html` の配信・`decisions.json` の read/write・commit を仲介する |
11
11
  | `report-template.html` | data 属性でカードを宣言する report.html の雛形。単選・真偽チェック・整数スライダ・配列型の複数選択に対応する。カード種別のハードコードなし。ヘルパー経由なら操作可能、`file://` 直開き等で fetch 失敗時は全操作ボタンが disabled になり案内文言を出す(安全劣化) |
12
12
  | `render-research-plan-report.mjs` | `research-plan.json` から固定 5 面の自己完結 HTML を生成する。絵コンテ面は画像 / 文字プレースホルダーのカード面と、主軸 + カットアウェイの読み取り専用 SVG 構造面を切り替えられる |
13
+ | `render-storyboard-print.mjs` | `edit.json` / `captions.json` から尺バーと 1 item 1 コマの自己完結 HTML を生成し、印刷用の L1 通しマップを出力する |
13
14
  | `examples/report.html` + `examples/report.html.decisions.json` | 動作サンプル一式(既存 4 カード + `direction`)。AI 推奨を既定値に入れた `decisions.json` 雛形付き |
14
15
  | `test/direction-card.test.mjs` | ローカル Chrome を headless 起動して、演出カードの保存と既存 4 カードの非退行を実測するテスト |
15
16
 
@@ -66,6 +67,17 @@ node packages/decision-cards/report-helper.mjs planning/research-plan-report.htm
66
67
 
67
68
  画像は生成時に data URI へ埋め込むため、出力 HTML は外部依存を持たない。`image_path` が無い、または画像を読めないショットは `shot_type` + `description` のプレースホルダーへ安全に劣化する。旧形式に `sequence` / `cutaway_of` が無い場合も生成は成功し、構造面だけが「構造情報なし」になる。
68
69
 
70
+ ### edit.json から印刷用の絵コンテを作る
71
+
72
+ ```sh
73
+ node packages/decision-cards/render-storyboard-print.mjs <projectDir>
74
+ ```
75
+
76
+ 既定では各映像 item の開始直後を `akari capture --full` で撮り、`.akari/reports/storyboard/` に
77
+ `index.html`、`frames/`、`storyboard.json` を出力する。既存の capture 結果を使う場合は
78
+ `--captures <dir>`、画像を撮らず灰色の仮枠にする場合は `--no-capture`、出力先を変える場合は
79
+ `--out <dir>` を指定する。HTML の画像は data URI として埋め込まれる。
80
+
69
81
  ## edit-plan スキルからの参照方法
70
82
 
71
83
  公開リポの `.claude/skills/edit-plan/` は本パッケージを以下のように参照する想定:
@@ -38,5 +38,8 @@ export interface ReadGenerationMetaResult {
38
38
  binding: GenerationBinding | null;
39
39
  }
40
40
  export declare function sidecarPathFor(sourcePath: string): string;
41
- /** fs に触れず、サイドカー自身が表す状態だけを解決する。 */
41
+ /**
42
+ * fs に触れず、サイドカー自身が表す状態だけを解決する。
43
+ * `job.stale_after_s` が未指定・不正な場合は既定 900 秒を使う。
44
+ */
42
45
  export declare function resolveGenerationState(meta: GenerationMetaV1 | null | undefined, now: Date | string | number): GenerationState;
@@ -10,24 +10,27 @@ exports.resolveGenerationState = resolveGenerationState;
10
10
  function sidecarPathFor(sourcePath) {
11
11
  return `${sourcePath}.meta.json`;
12
12
  }
13
- /** fs に触れず、サイドカー自身が表す状態だけを解決する。 */
13
+ /**
14
+ * fs に触れず、サイドカー自身が表す状態だけを解決する。
15
+ * `job.stale_after_s` が未指定・不正な場合は既定 900 秒を使う。
16
+ */
14
17
  function resolveGenerationState(meta, now) {
15
18
  if (!meta)
16
19
  return 'none';
17
20
  if (meta.status === 'failed')
18
21
  return 'failed';
19
22
  if (meta.status === 'generating') {
20
- const nowMs = timeValue(now);
23
+ const nowMs = now instanceof Date ? now.getTime() : typeof now === 'number' ? now : Date.parse(now);
21
24
  const startedMs = Date.parse(String(meta.job?.started_at ?? ''));
22
- const staleAfterS = meta.job?.stale_after_s;
25
+ const declaredStaleAfterS = meta.job?.stale_after_s;
26
+ const staleAfterS = typeof declaredStaleAfterS === 'number'
27
+ && Number.isFinite(declaredStaleAfterS)
28
+ && declaredStaleAfterS >= 0
29
+ ? declaredStaleAfterS : 900;
23
30
  if (Number.isFinite(nowMs)
24
31
  && Number.isFinite(startedMs)
25
- && typeof staleAfterS === 'number'
26
32
  && nowMs - startedMs > staleAfterS * 1000)
27
33
  return 'stale';
28
34
  }
29
35
  return meta.status;
30
36
  }
31
- function timeValue(value) {
32
- return value instanceof Date ? value.getTime() : typeof value === 'number' ? value : Date.parse(value);
33
- }
@@ -10,3 +10,9 @@ const { ok, normalized, rounded, messages, cost } = validateInputs({ inputs, out
10
10
 
11
11
  Messages use `error`, `warn`, or `info` levels with stable codes such as `first_frame.required`, `duration.rounded`, and `price.unknown`.
12
12
  A model without recorded pricing remains valid and returns `cost.needs_explicit_confirm` instead of changing `ok` to false.
13
+
14
+ `akari generate still <projectDir> --spec beats.json` はビートごとの静止画を Codex で生成します。
15
+ `--parallel N` で並列数を指定でき、既定は 4 です。
16
+ `--placeholder` は Codex を呼ばず、無料の文字カード PNG を置きます。
17
+ `--dry-run` は edit.json や素材を書かず、配置予定だけを表示します。
18
+ 生成物と meta は `assets/generated/`、クリップは edit.json v2 の visual トラック末尾に入ります。
@@ -5,7 +5,11 @@
5
5
  "description": "生成入力(9 スロット + 出力ノブ)を能力カタログ行に対して検証・正規化・丸め・費用見積する依存ゼロの共有モジュール。UI / CLI / エージェントが同じ関数を使う。",
6
6
  "type": "module",
7
7
  "main": "src/index.mjs",
8
+ "exports": {
9
+ ".": "./src/index.mjs",
10
+ "./cli": "./src/cli/index.mjs"
11
+ },
8
12
  "scripts": {
9
- "test": "node --test \"test/*.test.mjs\""
13
+ "test": "node --test \"test/**/*.test.mjs\""
10
14
  }
11
15
  }
@@ -6,6 +6,8 @@ Three.js + glTF シーンを決定的な時刻で描画し(`three-runtime.js`
6
6
  選択・ドラッグ移動・拡縮・テキスト編集を行い(`interaction.js`)、ズーム時の
7
7
  全体像インジケータを提供する(`minimap.js`)。
8
8
 
9
+ `three-runtime` の `fog` / `background` は additive(無指定時は従来どおり、2026-09-13)。
10
+
9
11
  **出自**: legacy `akari-video-tauri/ui/` から選別インポート(Wave I-3、
10
12
  2026-07-15)。当初の JS 3 本(`overlay-runtime.js` / `interaction.js` / `minimap.js`)は
11
13
  機能的に無改変で移送。詳しい挙動仕様は `docs/notes-2026-07-14-viewer-ui-round.md`