akari-video 1.3.0-beta.6 → 1.3.0-beta.7

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 (71) hide show
  1. package/package.json +1 -1
  2. package/src/vendor-sources.mjs +3 -0
  3. package/vendor/packages/akari-launcher/package.json +1 -1
  4. package/vendor/packages/akari-tools/bin/tools.mjs +26 -3
  5. package/vendor/packages/akari-tools/src/media/whisper-model-candidates.mjs +41 -0
  6. package/vendor/packages/akari-tools/src/media/whisper-model.mjs +82 -0
  7. package/vendor/packages/i18n/locales/en/partner.json +3 -0
  8. package/vendor/packages/i18n/locales/en/surfaces.json +21 -1
  9. package/vendor/packages/i18n/locales/en/tools.json +18 -1
  10. package/vendor/packages/i18n/locales/ja/partner.json +3 -0
  11. package/vendor/packages/i18n/locales/ja/surfaces.json +21 -1
  12. package/vendor/packages/i18n/locales/ja/tools.json +18 -1
  13. package/vendor/packages/render-cut/src/i18n.mjs +9 -2
  14. package/vendor/skills/address-review/SKILL.md +2 -2
  15. package/vendor/skills/analyze-footage/SKILL.md +5 -5
  16. package/vendor/skills/analyze-footage/analysis-json.md +3 -3
  17. package/vendor/skills/analyze-footage/bin/speechanalyzer-helper.swift +21 -4
  18. package/vendor/skills/analyze-footage/events-and-hooks.md +3 -3
  19. package/vendor/skills/analyze-footage/keyframes-and-review.md +1 -1
  20. package/vendor/skills/analyze-footage/media-and-transcript.md +3 -3
  21. package/vendor/skills/analyze-footage/person-matte.md +1 -1
  22. package/vendor/skills/analyze-footage/vision-tracks.md +4 -4
  23. package/vendor/skills/analyze-footage/workflow.md +2 -2
  24. package/vendor/skills/analyze-project/SKILL.md +3 -3
  25. package/vendor/skills/analyze-project/interpretation.md +3 -3
  26. package/vendor/skills/analyze-project/references/interpretation-shape.md +2 -2
  27. package/vendor/skills/bake-3d/SKILL.md +2 -2
  28. package/vendor/skills/beat-sync-edit/SKILL.md +1 -1
  29. package/vendor/skills/compile-review-session/compilation-rules.md +1 -1
  30. package/vendor/skills/create-project/SKILL.md +2 -2
  31. package/vendor/skills/critique-cut/SKILL.md +1 -1
  32. package/vendor/skills/critique-cut/workflow.md +1 -1
  33. package/vendor/skills/declare-audio/SKILL.md +1 -1
  34. package/vendor/skills/edit-lint/SKILL.md +11 -11
  35. package/vendor/skills/edit-plan/SKILL.md +5 -5
  36. package/vendor/skills/edit-plan/approvals-and-generation.md +1 -1
  37. package/vendor/skills/edit-plan/avatar-resolution.md +3 -3
  38. package/vendor/skills/edit-plan/beat-sync.md +3 -3
  39. package/vendor/skills/edit-plan/beats.md +3 -3
  40. package/vendor/skills/edit-plan/emphasis-detection.md +4 -4
  41. package/vendor/skills/edit-plan/execution.md +4 -4
  42. package/vendor/skills/edit-plan/expression-selection.md +1 -1
  43. package/vendor/skills/edit-plan/plan-json.md +2 -2
  44. package/vendor/skills/edit-plan/recipe.md +2 -2
  45. package/vendor/skills/edit-plan/report-guide.md +3 -3
  46. package/vendor/skills/edit-plan/workflow.md +3 -3
  47. package/vendor/skills/export-nle/SKILL.md +2 -2
  48. package/vendor/skills/generate-media/SKILL.md +1 -1
  49. package/vendor/skills/generate-narration/SKILL.md +4 -4
  50. package/vendor/skills/generate-narration/lang/ja.md +2 -2
  51. package/vendor/skills/generate-narration/reading-text.md +1 -1
  52. package/vendor/skills/harvest-asset/SKILL.md +2 -2
  53. package/vendor/skills/manage-connections/SKILL.md +1 -1
  54. package/vendor/skills/overlay-authoring/3d.md +2 -2
  55. package/vendor/skills/overlay-authoring/SKILL.md +4 -4
  56. package/vendor/skills/overlay-authoring/motion.md +1 -1
  57. package/vendor/skills/overlay-authoring/table.md +1 -1
  58. package/vendor/skills/overlay-authoring/telop.md +2 -2
  59. package/vendor/skills/overlay-authoring/text-behind-person.md +2 -2
  60. package/vendor/skills/render-cut/SKILL.md +6 -6
  61. package/vendor/skills/research-plan/SKILL.md +3 -3
  62. package/vendor/skills/research-plan/lang/ja.md +1 -1
  63. package/vendor/skills/research-plan/storyboard.md +1 -1
  64. package/vendor/skills/setup-audio-library/SKILL.md +3 -3
  65. package/vendor/skills/setup-chat-approval/SKILL.md +3 -3
  66. package/vendor/skills/setup-chat-approval/setup-token.md +1 -1
  67. package/vendor/skills/setup-library/SKILL.md +1 -1
  68. package/vendor/skills/setup-library/fetch-and-validate.md +2 -2
  69. package/vendor/skills/setup-library/tools-check.md +1 -1
  70. package/vendor/skills/setup-remote/SKILL.md +3 -3
  71. package/vendor/skills/verify/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "1.3.0-beta.6",
3
+ "version": "1.3.0-beta.7",
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": {
@@ -40,6 +40,9 @@ export const VENDOR_SOURCES = [
40
40
  'packages/akari-tools/bin/tools.mjs',
41
41
  'packages/akari-tools/src/common/main-module.mjs',
42
42
  'packages/akari-tools/src/media/common.mjs',
43
+ // bin/tools.mjs の install / status whisper が引く(akari tools install whisper)。
44
+ 'packages/akari-tools/src/media/whisper-model.mjs',
45
+ 'packages/akari-tools/src/media/whisper-model-candidates.mjs',
43
46
  'packages/akari-tools/src/i18n.mjs',
44
47
  'packages/akari-tools/package.json',
45
48
  'packages/akari-voice/src',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "1.3.0-beta.6",
3
+ "version": "1.3.0-beta.7",
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": [
@@ -6,6 +6,10 @@ import { homedir } from 'node:os';
6
6
  import { dirname, isAbsolute, join, posix, resolve } from 'node:path';
7
7
  import { gunzipSync } from 'node:zlib';
8
8
  import { isMainModule } from '../src/common/main-module.mjs';
9
+ import { whisperModelCandidates, isWhisperModelExcluded } from '../src/media/whisper-model-candidates.mjs';
10
+ import { WHISPER_MODEL, installWhisperModel } from '../src/media/whisper-model.mjs';
11
+ import { existsSync } from 'node:fs';
12
+ import { fileURLToPath } from 'node:url';
9
13
 
10
14
  const modelPins = [
11
15
  { file: 'wespeaker_en_voxceleb_resnet34_LM.onnx', url: 'https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/wespeaker_en_voxceleb_resnet34_LM.onnx', sha256: 'e9848563da86f263117134dfd7ad63c92355b37de492b55e325400c9d9c39012', bytes: 26530550, license: 'CC-BY-4.0' },
@@ -275,13 +279,14 @@ export async function installVoiceEngine({ voiceHome = resolveVoiceHome(), platf
275
279
  }
276
280
 
277
281
  export async function runToolsCli(argv, { env = process.env, homeDir = homedir(), stdout = process.stdout, stderr = process.stderr,
278
- fetchImpl = fetch, statfs = fs.statfs, platform = process.platform, arch = process.arch, pins = VOICE_ENGINE_PINS } = {}) {
279
- const usage = toolsText('tools.voice.cli.usage');
282
+ fetchImpl = fetch, statfs = fs.statfs, platform = process.platform, arch = process.arch, pins = VOICE_ENGINE_PINS, whisperModel = WHISPER_MODEL } = {}) {
283
+ const usage = toolsText('tools.whisper.cli.usage');
280
284
  if (argv.includes('--help')) { stdout.write(`${usage}\n`); return 0; }
281
285
  const [verb, subject, ...rest] = argv;
282
- if (!['install', 'status'].includes(verb) || subject !== 'voice' || rest.some(value => value !== '--json')) {
286
+ if (!['install', 'status'].includes(verb) || !['voice', 'whisper'].includes(subject) || rest.some(value => value !== '--json')) {
283
287
  stderr.write(`${usage}\n`); return 2;
284
288
  }
289
+ if (subject === 'whisper') return runWhisperCli(verb, rest.includes('--json'), { env, homeDir, stdout, stderr, fetchImpl, whisperModel });
285
290
  const voiceHome = resolveVoiceHome({ env, homeDir });
286
291
  const json = rest.includes('--json');
287
292
  if (verb === 'status') {
@@ -299,6 +304,24 @@ export async function runToolsCli(argv, { env = process.env, homeDir = homedir()
299
304
  return ok ? 0 : 1;
300
305
  }
301
306
 
307
+ async function runWhisperCli(verb, json, { env, homeDir, stdout, stderr, fetchImpl, whisperModel }) {
308
+ if (verb === 'status') {
309
+ const model = whisperModelCandidates({ env, homeDir, repoRoot: resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..'), bin: '' })
310
+ .find(candidate => existsSync(candidate) && !isWhisperModelExcluded(candidate));
311
+ stdout.write(`${json ? JSON.stringify({ state: model ? 'ready' : 'missing', model: model ?? null }) : model ? toolsText('tools.whisper.cli.statusReady', { path: model }) : toolsText('tools.whisper.cli.statusMissing')}\n`);
312
+ return model ? 0 : 1;
313
+ }
314
+ let lastStep = -1;
315
+ const result = await installWhisperModel({ homeDir, fetchImpl, model: whisperModel, onProgress: ({ downloadedBytes, totalBytes }) => {
316
+ if (!totalBytes) return;
317
+ const step = Math.min(10, Math.floor(downloadedBytes / totalBytes * 10));
318
+ if (step > lastStep) { lastStep = step; stderr.write(`${toolsText('tools.whisper.cli.downloading')} ${step * 10}%\n`); }
319
+ } });
320
+ const ok = result.outcome === 'installed' || result.outcome === 'unchanged';
321
+ stdout.write(`${json ? JSON.stringify({ ok, outcome: result.outcome, message: result.message, path: result.path }) : result.message}\n`);
322
+ return ok ? 0 : 1;
323
+ }
324
+
302
325
  if (isMainModule(import.meta.url)) {
303
326
  runToolsCli(process.argv.slice(2)).then(code => { process.exitCode = code; }, error => { console.error(error); process.exitCode = 1; });
304
327
  }
@@ -0,0 +1,41 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /** Ordered search locations shared by the CLI and shell. The override is a file. */
5
+ export function whisperModelLocations({ env, homeDir, repoRoot, bin }) {
6
+ return [
7
+ { path: env.WHISPER_CPP_MODEL, recursive: false },
8
+ { path: path.join(homeDir, ".akari", "tools", "models"), recursive: true },
9
+ { path: path.join(repoRoot, "models"), recursive: true },
10
+ { path: path.join(repoRoot, "whisper.cpp", "models"), recursive: true },
11
+ { path: path.join(homeDir, ".cache", "whisper.cpp"), recursive: true },
12
+ { path: path.join(homeDir, "Library", "Caches", "whisper.cpp"), recursive: true },
13
+ { path: path.resolve(path.dirname(bin), "..", "share", "whisper-cpp"), recursive: true },
14
+ { path: "/opt/homebrew/share/whisper-cpp", recursive: true },
15
+ { path: "/usr/local/share/whisper-cpp", recursive: true },
16
+ { path: path.join(homeDir, "Library", "Application Support", "com.prakashjoshipax.VoiceInk", "WhisperModels"), recursive: true },
17
+ ].filter(location => Boolean(location.path));
18
+ }
19
+
20
+ export function isWhisperModelFilename(candidate) {
21
+ return /^ggml-.*\.bin$/i.test(path.basename(candidate));
22
+ }
23
+
24
+ export function isWhisperModelExcluded(candidate, lang = "auto") {
25
+ return path.basename(candidate).startsWith("for-tests-") || (lang !== "en" && path.basename(candidate).includes(".en."));
26
+ }
27
+
28
+ function findModels(root) {
29
+ if (!existsSync(root)) return [];
30
+ try {
31
+ return readdirSync(root, { recursive: true })
32
+ .map(entry => path.join(root, String(entry)))
33
+ .filter(isWhisperModelFilename);
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+
39
+ export function whisperModelCandidates(options) {
40
+ return whisperModelLocations(options).flatMap(location => location.recursive ? findModels(location.path) : [location.path]);
41
+ }
@@ -0,0 +1,82 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream, createWriteStream, existsSync, statSync } from "node:fs";
3
+ import { mkdir, rename, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ // アプリの backend(esbuild の cjs 出力)にも束ねられるので、top-level await を含む ../i18n.mjs ではなく
7
+ // bin/tools.mjs と同じ i18n の本体から引く。
8
+ import { t as toolsText } from "../../../i18n/index.mjs";
9
+
10
+ /**
11
+ * 認識モデルの既定(アプリの初回セットアップ / 設定 → 道具 と同じもの)。
12
+ * アプリ側 (tool-install.ts) もここを参照するので、URL と sha256 の正本はこの 1 か所。
13
+ * HF の `resolve/main` はブランチ追従だが、内容が変われば sha256 不一致で失敗するので版固定と同じ効果になる。
14
+ */
15
+ export const WHISPER_MODEL = {
16
+ file: "ggml-large-v3-turbo-q5_0.bin",
17
+ url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin",
18
+ sha256: "394221709cd5ad1f40c46e6031ca61bce88931e6e088c188294c6d5a55ffa7e2",
19
+ };
20
+
21
+ /** `~/.akari/tools/models`(whisper-model-candidates.mjs の探索対象と一致させる)。 */
22
+ export function whisperModelsDir(homeDir = os.homedir()) {
23
+ return path.join(homeDir, ".akari", "tools", "models");
24
+ }
25
+
26
+ async function sha256OfFile(file) {
27
+ const hash = createHash("sha256");
28
+ for await (const chunk of createReadStream(file)) hash.update(chunk);
29
+ return hash.digest("hex");
30
+ }
31
+
32
+ /**
33
+ * モデルを tmp に落として sha256 を検証してから rename する。置き済みで sha256 が合えば何もしない。
34
+ * model / fetchImpl / homeDir は差し替え可能(テストは小さい偽ファイルで回す)。
35
+ */
36
+ export async function installWhisperModel({ homeDir = os.homedir(), fetchImpl = fetch, model = WHISPER_MODEL, onProgress } = {}) {
37
+ const directory = whisperModelsDir(homeDir);
38
+ const destination = path.join(directory, model.file);
39
+ if (existsSync(destination) && statSync(destination).isFile() && await sha256OfFile(destination) === model.sha256) {
40
+ return { outcome: "unchanged", message: toolsText("tools.whisper.cli.alreadyReady", { path: destination }), path: destination };
41
+ }
42
+ await mkdir(directory, { recursive: true });
43
+ const temporary = `${destination}.tmp-${process.pid}`;
44
+ try {
45
+ const response = await fetchImpl(model.url, { redirect: "follow" });
46
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
47
+ const total = Number(response.headers?.get?.("content-length")) || undefined;
48
+ const hash = createHash("sha256");
49
+ const output = createWriteStream(temporary);
50
+ let downloaded = 0;
51
+ const write = (chunk) => new Promise((resolve, reject) => output.write(chunk, (error) => error ? reject(error) : resolve()));
52
+ const take = async (chunk) => {
53
+ hash.update(chunk);
54
+ await write(chunk);
55
+ downloaded += chunk.length;
56
+ onProgress?.({ downloadedBytes: downloaded, totalBytes: total });
57
+ };
58
+ try {
59
+ if (response.body?.getReader) {
60
+ const reader = response.body.getReader();
61
+ for (;;) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+ await take(value);
65
+ }
66
+ } else {
67
+ await take(Buffer.from(await response.arrayBuffer()));
68
+ }
69
+ } finally {
70
+ await new Promise((resolve) => output.end(resolve));
71
+ }
72
+ if (hash.digest("hex") !== model.sha256) {
73
+ return { outcome: "failed", message: toolsText("tools.whisper.cli.verifyFailed"), path: destination };
74
+ }
75
+ await rename(temporary, destination);
76
+ return { outcome: "installed", message: toolsText("tools.whisper.cli.installed", { path: destination }), path: destination };
77
+ } catch (error) {
78
+ return { outcome: "failed", message: toolsText("tools.whisper.cli.downloadFailed", { error: error instanceof Error ? error.message : String(error) }), path: destination };
79
+ } finally {
80
+ await rm(temporary, { force: true });
81
+ }
82
+ }
@@ -75,6 +75,8 @@
75
75
  "partner.partnerPanel.extensionUpdater.extensionNotInstalledStatus": "Extension is not installed",
76
76
  "partner.partnerPanel.extensionUpdater.extensionUpdateAvailableText": "An extension update is available",
77
77
  "partner.partnerPanel.extensionUpdater.extensionUpDateText": "Extension is up to date",
78
+ "partner.partnerPanel.cliUpdate.availableText": "A new version of {value1} is available: {value2} (current {value3})",
79
+ "partner.partnerPanel.cliUpdate.openSettingsAction": "Update in Settings",
78
80
  "partner.partnerPanel.extensionUpdater.laterLabel": "Later",
79
81
  "partner.partnerPanel.extensionUpdater.newVersionAvailableText": "A new version of {value1} is available: {value2}",
80
82
  "partner.partnerPanel.extensionUpdater.reloadAction": "Reload Now",
@@ -212,6 +214,7 @@
212
214
  "partner.partnerSetup.bootstrap.fetchingCodexReleaseInformationText": "Fetching Codex release information: {value1}",
213
215
  "partner.partnerSetup.bootstrap.foundExistingClaudeText": "Found existing claude: {value1}",
214
216
  "partner.partnerSetup.bootstrap.foundExistingCodexText": "Found existing codex: {value1}",
217
+ "partner.partnerSetup.bootstrap.managedCodexPackageIncompleteText": "The codex package installed by AKARI is incomplete; reinstalling: {value1}",
215
218
  "partner.partnerSetup.bootstrap.foundExistingStatus": "Found existing {value1}: {value2}",
216
219
  "partner.partnerSetup.bootstrap.foundExistingVersionStatus": "Found existing {value1} {value2}: {value3}",
217
220
  "partner.partnerSetup.bootstrap.foundLabel": "Found {value1} {value2}: {value3}",
@@ -465,6 +465,17 @@
465
465
  "surfaces.settings.partnerChecking.label": "Checking…",
466
466
  "surfaces.settings.partnerInstalled.status": "Installed",
467
467
  "surfaces.settings.partnerNotInstalled.status": "Not installed",
468
+ "surfaces.settings.partnerNow.label": "Now {version}",
469
+ "surfaces.settings.partnerLatest.label": "Latest {version}",
470
+ "surfaces.settings.partnerUpdate.button": "Update",
471
+ "surfaces.settings.partnerUpdating.status": "Updating…",
472
+ "surfaces.settings.partnerSelfUpdating.label": "Updates itself automatically",
473
+ "surfaces.settings.partnerExternalUpdate.message": "A newer version is available. This copy was installed outside AKARI (Homebrew, npm, etc.), so update it the same way you installed it.",
474
+ "surfaces.settings.partnerUpdated.message": "Updated ({version})",
475
+ "surfaces.settings.partnerUpdateFailed.message": "Could not update: {reason}",
476
+ "surfaces.settings.partnerUpdateUnsupported.message": "AKARI cannot update this CLI.",
477
+ "surfaces.settings.partnerUpdateRunning.message": "An update is already running.",
478
+ "surfaces.settings.partnerUpdateBusy.message": "It is in use and cannot be replaced. Close the partner panel and try again.",
468
479
  "surfaces.settings.partnerInsert.label": "Installation",
469
480
  "surfaces.settings.partnerStart.label": "Start",
470
481
  "surfaces.settings.partnerClaudeCodeExtension.label": "Claude Code extension",
@@ -1688,6 +1699,10 @@
1688
1699
  "surfaces.onboarding.coachExportStandard.title": "Keep Standard selected, then Export",
1689
1700
  "surfaces.onboarding.coachChangeImageQualitySaveDestination.description": "<p>Change quality and destination here if needed.</p>",
1690
1701
  "surfaces.onboarding.coachExporting.title": "Exporting…",
1702
+ "surfaces.onboarding.coachExportFailed.title": "Export failed",
1703
+ "surfaces.onboarding.coachExportFailed.description": "<p>Check the settings and try exporting again, or skip this step for now.</p>",
1704
+ "surfaces.onboarding.coachExportRetry.label": "Try exporting again",
1705
+ "surfaces.onboarding.coachExportSkip.label": "Skip export and continue",
1691
1706
  "surfaces.onboarding.coachProgressDisplayedBottomRightOkayContinueWorking.description": "<p>Progress appears in the bottom right. You can keep working.</p>",
1692
1707
  "surfaces.onboarding.coachExportComplete.title": "Export complete",
1693
1708
  "surfaces.onboarding.coachCreatedFilesListedCompletedAlsoExportAsking.description": "<p>Exported files appear under “Completed.”</p><p>You can also ask partner to export; it shows the result and asks for confirmation first.</p>",
@@ -1714,6 +1729,7 @@
1714
1729
  "surfaces.onboarding.coachMarkupBack.label": "<button class=\"ao-back\" data-ao=\"back\">← Back</button>",
1715
1730
  "surfaces.onboarding.prepareDownloadSampleAssetCheckInternetConnection.message": "Could not download the sample asset. Check your internet connection.",
1716
1731
  "surfaces.onboarding.prepareSampleAssetCheckDestinationFreeSpace.message": "Could not prepare the sample asset. Check the destination and free space.",
1732
+ "surfaces.onboarding.preparePartnerStopFailed.message": "Could not stop the active partner, so the project was not switched. Stop the partner's work and try again.",
1717
1733
  "surfaces.onboarding.pressAssetSeeContents.title": "Select an Asset to preview it",
1718
1734
  "surfaces.onboarding.tryClickingIncludedVideo.description": "<p>Select the imported video.</p>",
1719
1735
  "surfaces.onboarding.clickCaptionFixSpot.title": "Select a Caption to edit it in place",
@@ -2224,5 +2240,9 @@
2224
2240
  "surfaces.tools.voice.name.label": "Speaker separation (voice engine)",
2225
2241
  "surfaces.tools.voice.provider.label": "k2-fsa (sherpa-onnx)",
2226
2242
  "surfaces.tools.voice.purpose.message": "Separates speakers in footage and suggests who spoke. Voices are processed only on this computer.",
2227
- "surfaces.tools.voice.size.label": "About 160 MB"
2243
+ "surfaces.tools.voice.size.label": "About 160 MB",
2244
+ "surfaces.projects.contextMenuOpenProject.action": "Open project",
2245
+ "surfaces.projects.contextMenuCopyPath.action": "Copy path",
2246
+ "surfaces.projects.pathCopied.message": "Copied the project path.",
2247
+ "surfaces.projects.couldNotCopyPath.message": "Could not copy the path."
2228
2248
  }
@@ -241,5 +241,22 @@
241
241
  "tools.voice.engine.error.sampleOutsideChannel": "Voice samples outside the channel cannot be used",
242
242
  "tools.voice.engine.error.setupRequired": "Speaker separation needs setup (run akari tools install voice or use Create Captions in the app).",
243
243
  "tools.voice.engine.error.sidecarMissing": "Voice sidecar file is missing",
244
- "tools.voice.engine.notice.countFallback": "Could not separate into {requested} speakers; using the automatically detected {used} speakers."
244
+ "tools.voice.engine.notice.countFallback": "Could not separate into {requested} speakers; using the automatically detected {used} speakers.",
245
+ "tools.transcribe.error.whisperFallback.speechFailed": "SpeechAnalyzer could not transcribe this file, and the whisper.cpp fallback is unavailable.",
246
+ "tools.transcribe.error.whisperFallback.localeUnsupported": "SpeechAnalyzer does not support {lang}, and the whisper.cpp fallback is unavailable.",
247
+ "tools.transcribe.error.whisperFallback.noModel": "No whisper.cpp ggml model was found.\nSearched:\n{locations}\nFix (any one):\n - akari tools install whisper (downloads the model into ~/.akari/tools/models/)\n - Install it from Settings -> Tools -> Whisper\n - Put a ggml-*.bin in ~/.akari/tools/models/, or point the WHISPER_CPP_MODEL environment variable at the file",
248
+ "tools.transcribe.error.whisperFallback.noBin": "whisper-cli was not found. You need the one bundled with the app, or one installed with brew install whisper-cpp (also installable from Settings -> Tools -> Whisper). Get the model with akari tools install whisper.",
249
+ "tools.transcribe.error.whisperFallback.reason": "SpeechAnalyzer error: {reason}",
250
+ "tools.media.help.in": " --in <time> start of the range to transcribe (seconds or mm:ss)",
251
+ "tools.media.help.out": " --out <time> end of the range to transcribe (seconds or mm:ss)",
252
+ "tools.media.help.backend": " --backend <speech-analyzer|whisper-cpp|cloud:<id>> pick the engine (default: auto)",
253
+ "tools.media.help.lang": " --lang <code> language code, e.g. ja, en (default: auto)",
254
+ "tools.whisper.cli.alreadyReady": "The Whisper model is already installed: {path}",
255
+ "tools.whisper.cli.installed": "Downloaded the Whisper model: {path}",
256
+ "tools.whisper.cli.verifyFailed": "The model failed verification (the download may be corrupted). Please try again.",
257
+ "tools.whisper.cli.downloadFailed": "Could not download the model ({error}). Please try again.",
258
+ "tools.whisper.cli.downloading": "Downloading the model",
259
+ "tools.whisper.cli.statusReady": "ready: {path}",
260
+ "tools.whisper.cli.statusMissing": "missing: no ggml model found. Run akari tools install whisper",
261
+ "tools.whisper.cli.usage": "Usage: akari tools install voice|whisper [--json] / akari tools status voice|whisper [--json]"
245
262
  }
@@ -75,6 +75,8 @@
75
75
  "partner.partnerPanel.extensionUpdater.extensionNotInstalledStatus": "拡張は未インストールです",
76
76
  "partner.partnerPanel.extensionUpdater.extensionUpdateAvailableText": "拡張の更新があります",
77
77
  "partner.partnerPanel.extensionUpdater.extensionUpDateText": "拡張は最新です",
78
+ "partner.partnerPanel.cliUpdate.availableText": "{value1} の新しい版 {value2} があります(今は {value3})",
79
+ "partner.partnerPanel.cliUpdate.openSettingsAction": "設定でアップデート",
78
80
  "partner.partnerPanel.extensionUpdater.laterLabel": "後で",
79
81
  "partner.partnerPanel.extensionUpdater.newVersionAvailableText": "{value1} の新しい版 {value2} があります",
80
82
  "partner.partnerPanel.extensionUpdater.reloadAction": "今すぐ再読み込み",
@@ -212,6 +214,7 @@
212
214
  "partner.partnerSetup.bootstrap.fetchingCodexReleaseInformationText": "Codex リリース情報を取得しています: {value1}",
213
215
  "partner.partnerSetup.bootstrap.foundExistingClaudeText": "既存の claude を検出: {value1}",
214
216
  "partner.partnerSetup.bootstrap.foundExistingCodexText": "既存の codex を検出: {value1}",
217
+ "partner.partnerSetup.bootstrap.managedCodexPackageIncompleteText": "AKARI が入れた codex のパッケージが欠けているため入れ直します: {value1}",
215
218
  "partner.partnerSetup.bootstrap.foundExistingStatus": "既存の {value1} を検出: {value2}",
216
219
  "partner.partnerSetup.bootstrap.foundExistingVersionStatus": "既存の {value1} {value2} を検出: {value3}",
217
220
  "partner.partnerSetup.bootstrap.foundLabel": "{value1} {value2} を検出: {value3}",
@@ -465,6 +465,17 @@
465
465
  "surfaces.settings.partnerChecking.label": "調べています",
466
466
  "surfaces.settings.partnerInstalled.status": "インストール済み",
467
467
  "surfaces.settings.partnerNotInstalled.status": "未インストール",
468
+ "surfaces.settings.partnerNow.label": "今 {version}",
469
+ "surfaces.settings.partnerLatest.label": "最新 {version}",
470
+ "surfaces.settings.partnerUpdate.button": "アップデート",
471
+ "surfaces.settings.partnerUpdating.status": "アップデート中…",
472
+ "surfaces.settings.partnerSelfUpdating.label": "自動で更新されます",
473
+ "surfaces.settings.partnerExternalUpdate.message": "最新版があります。このコピーは AKARI の外(Homebrew・npm など)で入れられたものなので、入れた方法で更新してください。",
474
+ "surfaces.settings.partnerUpdated.message": "アップデートしました({version})",
475
+ "surfaces.settings.partnerUpdateFailed.message": "アップデートできませんでした: {reason}",
476
+ "surfaces.settings.partnerUpdateUnsupported.message": "この CLI は AKARI からはアップデートできません。",
477
+ "surfaces.settings.partnerUpdateRunning.message": "すでにアップデート中です。",
478
+ "surfaces.settings.partnerUpdateBusy.message": "使用中のため置き換えられません。パートナーのパネルを閉じてから、もう一度お試しください。",
468
479
  "surfaces.settings.partnerInsert.label": "入れ方",
469
480
  "surfaces.settings.partnerStart.label": "起動",
470
481
  "surfaces.settings.partnerClaudeCodeExtension.label": "Claude Code 拡張",
@@ -1688,6 +1699,10 @@
1688
1699
  "surfaces.onboarding.coachExportStandard.title": "標準のまま「書き出す」",
1689
1700
  "surfaces.onboarding.coachChangeImageQualitySaveDestination.description": "<p>画質や保存先は、ここで変えられます。</p>",
1690
1701
  "surfaces.onboarding.coachExporting.title": "書き出しています",
1702
+ "surfaces.onboarding.coachExportFailed.title": "書き出せませんでした",
1703
+ "surfaces.onboarding.coachExportFailed.description": "<p>設定を確かめて、もう一度書き出せます。ここでは飛ばして先へ進むこともできます。</p>",
1704
+ "surfaces.onboarding.coachExportRetry.label": "もう一度書き出す",
1705
+ "surfaces.onboarding.coachExportSkip.label": "書き出しを飛ばして先へ",
1691
1706
  "surfaces.onboarding.coachProgressDisplayedBottomRightOkayContinueWorking.description": "<p>進み具合は右下に出ます。そのまま作業を続けても大丈夫です。</p>",
1692
1707
  "surfaces.onboarding.coachExportComplete.title": "書き出せました",
1693
1708
  "surfaces.onboarding.coachCreatedFilesListedCompletedAlsoExportAsking.description": "<p>できたファイルは「できたもの」に並びます。</p><p>AI パートナーに「書き出して」と頼んでも書き出せます(中身を見せて、確認してから実行します)。</p>",
@@ -1714,6 +1729,7 @@
1714
1729
  "surfaces.onboarding.coachMarkupBack.label": "<button class=\"ao-back\" data-ao=\"back\">← 戻る</button>",
1715
1730
  "surfaces.onboarding.prepareDownloadSampleAssetCheckInternetConnection.message": "サンプル素材を取得できませんでした。インターネット接続を確認してください。",
1716
1731
  "surfaces.onboarding.prepareSampleAssetCheckDestinationFreeSpace.message": "サンプル素材の準備に失敗しました。保存先と空き容量を確認してください。",
1732
+ "surfaces.onboarding.preparePartnerStopFailed.message": "作業中のパートナーを止められなかったので、切り替えていません。パートナーの作業を止めてから、もう一度お試しください。",
1717
1733
  "surfaces.onboarding.pressAssetSeeContents.title": "素材を押すと、中身が見られます",
1718
1734
  "surfaces.onboarding.tryClickingIncludedVideo.description": "<p>入った動画を押してみてください。</p>",
1719
1735
  "surfaces.onboarding.clickCaptionFixSpot.title": "字幕を押すと、その場で直せます",
@@ -2224,5 +2240,9 @@
2224
2240
  "surfaces.tools.voice.name.label": "話者を分ける(声のエンジン)",
2225
2241
  "surfaces.tools.voice.provider.label": "k2-fsa(sherpa-onnx)",
2226
2242
  "surfaces.tools.voice.purpose.message": "素材の話者を分けて、誰の声かを提案するのに使います。声はこのパソコンの中だけで処理します。",
2227
- "surfaces.tools.voice.size.label": "約 160MB"
2243
+ "surfaces.tools.voice.size.label": "約 160MB",
2244
+ "surfaces.projects.contextMenuOpenProject.action": "プロジェクトを開く",
2245
+ "surfaces.projects.contextMenuCopyPath.action": "パスをコピー",
2246
+ "surfaces.projects.pathCopied.message": "プロジェクトのパスをコピーしました。",
2247
+ "surfaces.projects.couldNotCopyPath.message": "パスをコピーできませんでした。"
2228
2248
  }
@@ -241,5 +241,22 @@
241
241
  "tools.voice.engine.error.sampleOutsideChannel": "チャンネル外の見本は使えません",
242
242
  "tools.voice.engine.error.setupRequired": "話者を分けるには準備が要ります(akari tools install voice か、アプリの『字幕を作る』から)",
243
243
  "tools.voice.engine.error.sidecarMissing": "声の側ファイルがありません",
244
- "tools.voice.engine.notice.countFallback": "指定の {requested} 人に分けられなかったので、自動で分けた結果({used} 人)を使います"
244
+ "tools.voice.engine.notice.countFallback": "指定の {requested} 人に分けられなかったので、自動で分けた結果({used} 人)を使います",
245
+ "tools.transcribe.error.whisperFallback.speechFailed": "SpeechAnalyzer で文字起こしできず、代わりの whisper.cpp も使えません。",
246
+ "tools.transcribe.error.whisperFallback.localeUnsupported": "SpeechAnalyzer は言語 {lang} に対応しておらず、代わりの whisper.cpp も使えません。",
247
+ "tools.transcribe.error.whisperFallback.noModel": "whisper.cpp の ggml モデルが見つかりません。\n探した場所:\n{locations}\n対処(どれか 1 つ):\n - akari tools install whisper (モデルを ~/.akari/tools/models/ に取得)\n - 設定 → 道具 → Whisper からインストール\n - ggml-*.bin を ~/.akari/tools/models/ に置く、または環境変数 WHISPER_CPP_MODEL にファイルを指定",
248
+ "tools.transcribe.error.whisperFallback.noBin": "whisper-cli が見つかりません。アプリ同梱のもの、または brew install whisper-cpp で入れたものが必要です(設定 → 道具 → Whisper からも入れられます)。モデルは akari tools install whisper で取得できます。",
249
+ "tools.transcribe.error.whisperFallback.reason": "SpeechAnalyzer のエラー: {reason}",
250
+ "tools.media.help.in": " --in <time> 文字起こしの開始位置(秒 または mm:ss)",
251
+ "tools.media.help.out": " --out <time> 文字起こしの終了位置(秒 または mm:ss)",
252
+ "tools.media.help.backend": " --backend <speech-analyzer|whisper-cpp|cloud:<id>> 文字起こしエンジンを指定(既定は自動)",
253
+ "tools.media.help.lang": " --lang <code> 言語コード(例: ja, en。既定は自動)",
254
+ "tools.whisper.cli.alreadyReady": "Whisper の認識モデルは取得済みです: {path}",
255
+ "tools.whisper.cli.installed": "Whisper の認識モデルを取得しました: {path}",
256
+ "tools.whisper.cli.verifyFailed": "認識モデルの検証に失敗しました(データが壊れている可能性があります)。もう一度お試しください。",
257
+ "tools.whisper.cli.downloadFailed": "認識モデルの取得に失敗しました({error})。もう一度お試しください。",
258
+ "tools.whisper.cli.downloading": "認識モデルを取得中",
259
+ "tools.whisper.cli.statusReady": "ready: {path}",
260
+ "tools.whisper.cli.statusMissing": "missing: ggml モデルが見つかりません。akari tools install whisper で取得できます",
261
+ "tools.whisper.cli.usage": "使い方: akari tools install voice|whisper [--json] / akari tools status voice|whisper [--json]"
245
262
  }
@@ -4,8 +4,15 @@ const normalizeLanguage = core?.normalizeLanguage ?? ((value) => typeof value ==
4
4
  const getLanguage = core?.getLanguage ?? (() => normalizeLanguage(process.env.AKARI_LANG ?? Intl.DateTimeFormat().resolvedOptions().locale));
5
5
 
6
6
  function catalog(language) {
7
- try { return JSON.parse(readFileSync(new URL(`../../i18n/locales/${language}/render.json`, import.meta.url), 'utf8')); }
8
- catch { return {}; }
7
+ let base = {};
8
+ let local = {};
9
+ try { base = JSON.parse(readFileSync(new URL(`../../i18n/locales/${language}/render.json`, import.meta.url), 'utf8')); }
10
+ catch {}
11
+ try {
12
+ local = JSON.parse(readFileSync(new URL(`./locales/${language}.json`, import.meta.url), 'utf8'));
13
+ }
14
+ catch {}
15
+ return { ...base, ...local };
9
16
  }
10
17
 
11
18
  const catalogs = { ja: catalog('ja'), en: catalog('en') };
@@ -22,7 +22,7 @@ Canonical source: private internal contracts (`akari-video-internal`): review-an
22
22
  Include only when their IDs are explicitly specified through `--ids`. `[要確認]` is a question for the owner,
23
23
  not something the agent may independently interpret and process.
24
24
  4. **Pass edit-lint after implementing edit.json changes, before respond**: Caption instructions follow the
25
- caption contract ([Captions and cut editing](../../docs/notes-2026-07-14-captions-and-cut-editing.md), Japanese:
25
+ caption contract ([Captions and cut editing](https://github.com/AkariLabs/akari-video/blob/main/docs/notes-2026-07-14-captions-and-cut-editing.md), Japanese:
26
26
  gap principle, actual speech intervals, minimum 1.0s). Run `respond` only after
27
27
  `node packages/edit-lint/bin/edit-lint.mjs <project> --media` PASS; never mark addressed before PASS.
28
28
  5. **The agent judges interpretation/implementation; bin handles only mechanical listing/state transitions.**
@@ -42,7 +42,7 @@ Canonical source: private internal contracts (`akari-video-internal`): review-an
42
42
  `strokes` (with sessionRef). `--all-open` automatically skips `[要確認]` annotations (machine confirmation marker)
43
43
  and includes skipped IDs/count in output.
44
44
 
45
- Do not Read all of `edit.json` / `captions.json`; grep by ID and read matching lines only ([Reading edit.json](../../docs/guides/edit-json-access.md)).
45
+ Do not Read all of `edit.json` / `captions.json`; grep by ID and read matching lines only ([Reading edit.json](https://github.com/AkariLabs/akari-video/blob/main/docs/guides/edit-json-access.md)).
46
46
  Write through relevant-line Edit or edit-store's script API.
47
47
 
48
48
  2. Read each ticket's `text` instructions (including source seconds/target) and decide the response.
@@ -7,11 +7,11 @@ description: Analyze one footage file in pull-driven levels L0–L3 and record o
7
7
 
8
8
  > **Language**: Respond in the user's language — conversation, questions, approval prompts, and the reports and records you write (including decision-log rows) follow the language the user writes in.
9
9
 
10
- Read lang/<content-language>.md for language-specific rules (content language: --lang → intake.json "language" → transcript language → ja; see docs/contract-2026-10-11-content-language.md). If that file does not exist, read lang/en.md.
10
+ Read lang/<content-language>.md for language-specific rules (content language: --lang → intake.json "language" → transcript language → ja; see https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-10-11-content-language.md). If that file does not exist, read lang/en.md.
11
11
 
12
12
  ## Analysis levels
13
13
 
14
- The authoritative source for terminology, inputs, outputs, and ledger updates is [`akari media` Observation Command Contract v0 §3 / §4](../../docs/contract-2026-08-29-media-inspect-cli-v0.md). Use `akari media` to observe the source. This skill interprets and scores evidence, then records only observed results.
14
+ The authoritative source for terminology, inputs, outputs, and ledger updates is [`akari media` Observation Command Contract v0 §3 / §4](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-29-media-inspect-cli-v0.md). Use `akari media` to observe the source. This skill interprets and scores evidence, then records only observed results.
15
15
 
16
16
  | Level | One-line summary |
17
17
  |---|---|
@@ -26,17 +26,17 @@ The authoritative source for terminology, inputs, outputs, and ledger updates is
26
26
 
27
27
  - Analyze one footage file per run, recording every time in seconds from the uncut source.
28
28
  - Never modify the source. Keep outputs at the canonical destination in [workflow.md](workflow.md) to prevent collisions between files with the same name.
29
- - Invoke `akari media <sub> <target> [options]` under the [`akari media` contract §1 / §2](../../docs/contract-2026-08-29-media-inspect-cli-v0.md). Parse stdout as JSON or JSON Lines; do not call a media backend directly from this workflow.
29
+ - Invoke `akari media <sub> <target> [options]` under the [`akari media` contract §1 / §2](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-29-media-inspect-cli-v0.md). Parse stdout as JSON or JSON Lines; do not call a media backend directly from this workflow.
30
30
  - Create a 720p proxy only if visual review at L2 or above needs it. At L0 and L1, pass the original file to `probe`, `waveform`, and `transcribe` without making a proxy.
31
31
  - Follow contract §2.5 for transcription backend selection. A cloud connection must be registered in `.akari/connections.json` with doctor status `ok`, explicitly selected, and approved by a person through a decision card. Never upload audio by default. Access keys only through `credentials.env` and never print their values in chat, artifacts, or logs.
32
32
  - Obtain word timing through `akari media transcribe`, which snaps word starts to speech after silence. Do not use word times from a direct whisper-cli call: its default `offsets` are estimates and place words before speech when initial or inter-sentence silence is present (#92 observed a first word at zero despite two seconds of silence). With flash attention enabled by default, `-dtw <preset>` is silently ineffective unless used with `-nfa` and read from `tokens[].t_dtw`.
33
33
  - If no backend is available or no speech is present, do not invent words. Leave `transcript: []` and report why.
34
34
  - Write `note` after actually viewing the images used in L2. Do not write findings on unseen images.
35
35
  - Create only `filler | trouble | chapter | highlight | hook` events. Record a hook only for a window chosen for scoring, and score all five axes there with integers from 1 to 5.
36
- - Add no fields outside [analysis.schema.json](../../packages/schemas/analysis.schema.json). Finalize only JSON that passes structural and semantic checks. `keyframes: []` and `events: []` are valid both when L2 was not observed and when nothing qualified; distinguish the cases by the type and range of `observations[]`.
36
+ - Add no fields outside [analysis.schema.json](https://github.com/AkariLabs/akari-video/blob/main/packages/schemas/analysis.schema.json). Finalize only JSON that passes structural and semantic checks. `keyframes: []` and `events: []` are valid both when L2 was not observed and when nothing qualified; distinguish the cases by the type and range of `observations[]`.
37
37
  - Do not generate L3 for every file. Generate only the person-track types and intervals needed for the chosen effect. By default, `tracks.person_matte: null`; omit optional person-track keys until generated.
38
38
  - Use OpenMontage for structural reference only; do not copy AGPL text or code.
39
- - Follow the "existing contract sidecars" layer of the [project-structure-v0 contract](../../docs/contract-2026-07-25-project-structure-v0.md) for `.akari/sidecars/` output paths.
39
+ - Follow the "existing contract sidecars" layer of the [project-structure-v0 contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-07-25-project-structure-v0.md) for `.akari/sidecars/` output paths.
40
40
 
41
41
  Pass --lang for the work's content language on transcription calls. The default SpeechAnalyzer backend uses that language when its locale is supported.
42
42
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## Principles
10
10
 
11
- Use [packages/schemas/analysis.schema.json](../../packages/schemas/analysis.schema.json) as the sole structural definition. Unknown fields are prohibited at the top level and within objects. Do not add working notes, confidence scores, degraded-result reasons, or generator metadata as extra fields.
11
+ Use [packages/schemas/analysis.schema.json](https://github.com/AkariLabs/akari-video/blob/main/packages/schemas/analysis.schema.json) as the sole structural definition. Unknown fields are prohibited at the top level and within objects. Do not add working notes, confidence scores, degraded-result reasons, or generator metadata as extra fields.
12
12
 
13
13
  ## Minimum valid form
14
14
 
@@ -118,12 +118,12 @@ Track shape:
118
118
  }
119
119
  ```
120
120
 
121
- Set `person_matte` only after generation and verification; otherwise use `null`. Matte generation is optional and `null` is the default; see [person-matte.md](person-matte.md). A generated matte requires `path` and `fps`; `quality`, `generated_at`, and `tool` are optional. Resolve its relative path from the directory containing `analysis.json`, using `/` as the separator. The [person-matte data contract](../../docs/contract-2026-07-23-analysis-person-matte.md) is authoritative.
121
+ Set `person_matte` only after generation and verification; otherwise use `null`. Matte generation is optional and `null` is the default; see [person-matte.md](person-matte.md). A generated matte requires `path` and `fps`; `quality`, `generated_at`, and `tool` are optional. Resolve its relative path from the directory containing `analysis.json`, using `/` as the separator. The [person-matte data contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-07-23-analysis-person-matte.md) is authoritative.
122
122
 
123
123
  The legacy single-path string (`"mattes/person-alpha.mov"`) may be read as shorthand for `{ "path": ... }`, but must not be written in new data. Each face's speaker ID must exist in `tracks.speakers[].id`.
124
124
  `tracks.speakers` also receives `{ id, spans }` from speaker intervals when `--diarize` is selected. Do not store embeddings here either.
125
125
 
126
- Unlike person_matte, `tracks.face_landmarks` and `tracks.hand_pose` are optional keys: omit them entirely when not generated instead of writing `null`. The agent does not assemble these tracks here. `vision-tracks.mjs`, described in [vision-tracks.md](vision-tracks.md), reads and updates the finalized analysis.json directly. Follow the [vision-track data contract](../../docs/contract-2026-08-11-analysis-vision-tracks-v0.md).
126
+ Unlike person_matte, `tracks.face_landmarks` and `tracks.hand_pose` are optional keys: omit them entirely when not generated instead of writing `null`. The agent does not assemble these tracks here. `vision-tracks.mjs`, described in [vision-tracks.md](vision-tracks.md), reads and updates the finalized analysis.json directly. Follow the [vision-track data contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-11-analysis-vision-tracks-v0.md).
127
127
 
128
128
  ## Semantic constraints beyond the schema
129
129
 
@@ -79,11 +79,28 @@ struct SpeechAnalyzerHelper {
79
79
  let statusBefore = await AssetInventory.status(forModules: modules)
80
80
  let installStart = ContinuousClock.now
81
81
  if statusBefore != .installed {
82
- guard let request = try await AssetInventory.assetInstallationRequest(supporting: modules) else {
83
- throw NSError(domain: "SpeechAnalyzerHelper", code: 5,
84
- userInfo: [NSLocalizedDescriptionKey: "Speech model installation request unavailable"])
82
+ if let request = try await AssetInventory.assetInstallationRequest(supporting: modules) {
83
+ try await request.downloadAndInstall()
84
+ } else {
85
+ // 要求が nil のときは「入れる必要がない(導入済み)」「この OS にモデルが無い」
86
+ // 「他で導入中」の区別がつかないため、状態を取り直して理由を分ける。
87
+ let statusRetry = await AssetInventory.status(forModules: modules)
88
+ let localeId = supportedLocale.identifier
89
+ switch statusRetry {
90
+ case .installed:
91
+ break
92
+ case .unsupported:
93
+ // 文言の先頭は JS 側の whisper フォールバック判定(Unsupported SpeechTranscriber locale)と揃える
94
+ throw NSError(domain: "SpeechAnalyzerHelper", code: 6,
95
+ userInfo: [NSLocalizedDescriptionKey: "Unsupported SpeechTranscriber locale: \(localeId) (this macOS does not provide a SpeechAnalyzer model for \(localeId); asset status: unsupported, before: \(statusName(statusBefore)))"])
96
+ case .downloading:
97
+ throw NSError(domain: "SpeechAnalyzerHelper", code: 7,
98
+ userInfo: [NSLocalizedDescriptionKey: "The speech model for \(localeId) is still downloading; try again later (asset status: downloading, before: \(statusName(statusBefore)))"])
99
+ default:
100
+ throw NSError(domain: "SpeechAnalyzerHelper", code: 5,
101
+ userInfo: [NSLocalizedDescriptionKey: "Speech model installation request unavailable for \(localeId) (asset status: \(statusName(statusRetry)), before: \(statusName(statusBefore)))"])
102
+ }
85
103
  }
86
- try await request.downloadAndInstall()
87
104
  }
88
105
  let installDuration = ContinuousClock.now - installStart
89
106
  let installationSeconds = Double(installDuration.components.seconds)
@@ -20,7 +20,7 @@ Some transcription backends normalize away fillers. Prefer a backend transcript
20
20
 
21
21
  ## Identify highlights: important statements
22
22
 
23
- This section follows the [M5 contract addendum (2026-07-14)](../../docs/contract-2026-07-13-m5-analysis-report.md). A hook supports a short opening or excerpt; a highlight captures an important statement useful to broader editorial decisions. The same interval may be both when it serves both purposes.
23
+ This section follows the [M5 contract addendum (2026-07-14)](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-07-13-m5-analysis-report.md). A hook supports a short opening or excerpt; a highlight captures an important statement useful to broader editorial decisions. The same interval may be both when it serves both purposes.
24
24
 
25
25
  - Write `quote` **faithfully to the actual transcript**. Do not summarize, paraphrase, or invent words. Formatting is limited to adding punctuation.
26
26
  - In `reason`, explain why the statement matters and name the evidence category, such as "states a release date (decision)" or "quantifies an implementation result (number/result)."
@@ -67,11 +67,11 @@ These are **unvalidated starting thresholds**, not official platform success cri
67
67
 
68
68
  Rank candidates by total score; break ties using `hook`, then `self_contained`, then `density`. This ranking rule also requires validation and is not an absolute measure of quality.
69
69
 
70
- The scale comes from the [M5 analysis.json v0 contract](../../docs/contract-2026-07-13-m5-analysis-report.md).
70
+ The scale comes from the [M5 analysis.json v0 contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-07-13-m5-analysis-report.md).
71
71
 
72
72
  ## Supply evidence for highlight extraction
73
73
 
74
- Later work in [edit-plan's beats.md](../edit-plan/beats.md) derives highlight markers in `edit.json` `beats[]` from the `hook`, `chapter`, and `highlight` events and hook scores recorded here; see the [data contract](../../docs/contract-2026-07-22-edit-json-v1-beats.md). Keep the extraction method above and meet these two additional quality conditions:
74
+ Later work in [edit-plan's beats.md](../edit-plan/beats.md) derives highlight markers in `edit.json` `beats[]` from the `hook`, `chapter`, and `highlight` events and hook scores recorded here; see the [data contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-07-22-edit-json-v1-beats.md). Keep the extraction method above and meet these two additional quality conditions:
75
75
 
76
76
  - **Do not emit an unscored highlight.** If `importance` is absent, later stages cannot determine its strength and will use a default. When a statement is important enough to guide an edit, assign an integer from 1 to 5; omit candidates too weak to assess.
77
77
  - **Use source seconds for event time.** The event's `t` and any interval `start` / `end` refer to the uncut source. Beat markers persist source-time anchors, so mixing in timeline seconds creates errors later stages cannot detect.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Principles
6
6
 
7
- Read this file only when L2 is explicitly requested. Use `akari media filmstrip|grab` to generate candidates, then inspect the actual images before recording findings. The [`akari media` contract §1.1 / §2.2 / §2.3](../../docs/contract-2026-08-29-media-inspect-cli-v0.md) governs commands and image limits.
7
+ Read this file only when L2 is explicitly requested. Use `akari media filmstrip|grab` to generate candidates, then inspect the actual images before recording findings. The [`akari media` contract §1.1 / §2.2 / §2.3](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-29-media-inspect-cli-v0.md) governs commands and image limits.
8
8
 
9
9
  ## 1. Scan the source coarsely
10
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Authoritative contract
4
4
 
5
- The [`akari media` inspection contract v0 §1–§4](../../docs/contract-2026-08-29-media-inspect-cli-v0.md) governs invocation, stdout, output artifacts, ledger updates, and analysis levels. In brief: **observe through `akari media`, make judgments in this skill, and append results to the project's analysis.json**.
5
+ The [`akari media` inspection contract v0 §1–§4](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-29-media-inspect-cli-v0.md) governs invocation, stdout, output artifacts, ledger updates, and analysis levels. In brief: **observe through `akari media`, make judgments in this skill, and append results to the project's analysis.json**.
6
6
 
7
7
  ## L0: probe
8
8
 
@@ -43,7 +43,7 @@ akari media transcribe "$SOURCE" --in 120 --out 180 --lang ja
43
43
 
44
44
  Pass --lang for the work's content language on every transcription call. SpeechAnalyzer receives a matching locale when supported; otherwise the local backend may fall back to whisper.cpp. The command above is a Japanese-language example. Read the relevant language pack for transcription and filler guidance.
45
45
 
46
- The [`akari media` contract §2.5](../../docs/contract-2026-08-29-media-inspect-cli-v0.md) defines the three-layer backend choice, caching, `segments[]` format, no-speech result, and error handling. This skill does not discover backend executables or models or convert input audio itself.
46
+ The [`akari media` contract §2.5](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-08-29-media-inspect-cli-v0.md) defines the three-layer backend choice, caching, `segments[]` format, no-speech result, and error handling. This skill does not discover backend executables or models or convert input audio itself.
47
47
 
48
48
  ### Separate speakers (only when requested or multiple voices are likely)
49
49
 
@@ -60,7 +60,7 @@ akari speakers set "$SOURCE" A --person channel/p-123
60
60
  akari speakers list "$SOURCE"
61
61
  ```
62
62
 
63
- With `--identify`, compare only against consented (`self` or `subject`) voice samples for channel people. The result is a suggestion; a person decides the name. Confirm even a `strong` match before writing it with `akari speakers set`. Show `maybe` as a candidate for review and leave `none` unnamed. If the voice engine is missing and the command exits 2, do not guess speakers. Stop and guide the user to `akari tools install voice` or preparation in the app's caption creation flow. Follow the [speaker separation contract](../../docs/contract-2026-10-11-speaker-split-v0.md) for flags, output, and destinations.
63
+ With `--identify`, compare only against consented (`self` or `subject`) voice samples for channel people. The result is a suggestion; a person decides the name. Confirm even a `strong` match before writing it with `akari speakers set`. Show `maybe` as a candidate for review and leave `none` unnamed. If the voice engine is missing and the command exits 2, do not guess speakers. Stop and guide the user to `akari tools install voice` or preparation in the app's caption creation flow. Follow the [speaker separation contract](https://github.com/AkariLabs/akari-video/blob/main/docs/contract-2026-10-11-speaker-split-v0.md) for flags, output, and destinations.
64
64
 
65
65
 
66
66
  ### Cloud use requires approval