akari-video 0.1.23 → 0.1.25

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 (92) hide show
  1. package/README.md +14 -0
  2. package/bin/akari.mjs +7 -3
  3. package/package.json +1 -1
  4. package/src/chrome-command.mjs +100 -0
  5. package/src/cli.mjs +22 -5
  6. package/src/doctor-command.mjs +65 -0
  7. package/src/messages.mjs +35 -10
  8. package/src/path-lookup.mjs +10 -20
  9. package/src/runtime-diagnostics.mjs +323 -0
  10. package/src/status-command.mjs +5 -1
  11. package/src/update-check.mjs +1 -1
  12. package/vendor/.akari-capability-sources.json +12 -2
  13. package/vendor/docs/contract-2026-07-20-edit-json-v1-narration.md +12 -0
  14. package/vendor/docs/contract-2026-07-22-render-basics.md +25 -1
  15. package/vendor/docs/contract-2026-07-23-analysis-person-matte.md +40 -0
  16. package/vendor/docs/contract-2026-07-25-r6-audio-tracks-and-trim.md +8 -0
  17. package/vendor/docs/contract-2026-08-02-preview-parity.md +252 -619
  18. package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +2 -2
  19. package/vendor/docs/contract-2026-08-12-still-image-cut-source-v0.md +4 -9
  20. package/vendor/docs/contract-2026-08-18-v1-render-parity.md +4 -0
  21. package/vendor/docs/contract-2026-08-28-gpu-export-v0.md +133 -0
  22. package/vendor/docs/contract-2026-08-28-osr-export-v0.md +158 -0
  23. package/vendor/docs/contract-2026-08-28-v2-approximation-ledger.md +46 -0
  24. package/vendor/docs/contract-2026-08-28-v2-audio-roles-v0.md +123 -0
  25. package/vendor/packages/akari-launcher/README.md +14 -0
  26. package/vendor/packages/akari-launcher/package.json +3 -3
  27. package/vendor/packages/analysis-report/render-analysis-report.mjs +50 -6
  28. package/vendor/packages/analysis-report/template.html +11 -1
  29. package/vendor/packages/bake-layer/package.json +2 -2
  30. package/vendor/packages/chat-bridge/package.json +2 -2
  31. package/vendor/packages/decision-cards/package.json +2 -2
  32. package/vendor/packages/decision-log-report/README.md +50 -0
  33. package/vendor/packages/decision-log-report/package.json +13 -0
  34. package/vendor/packages/decision-log-report/render-decision-log-report.mjs +484 -0
  35. package/vendor/packages/decision-log-report/template.html +471 -0
  36. package/vendor/packages/edit-lint/README.md +44 -0
  37. package/vendor/packages/edit-lint/src/edit-lint.mjs +364 -25
  38. package/vendor/packages/edit-store/lib/audio-schedule.d.ts +62 -0
  39. package/vendor/packages/edit-store/lib/audio-schedule.js +284 -0
  40. package/vendor/packages/edit-store/lib/ducking.d.ts +25 -0
  41. package/vendor/packages/edit-store/lib/ducking.js +31 -0
  42. package/vendor/packages/edit-store/lib/edit-store.d.ts +3 -0
  43. package/vendor/packages/edit-store/lib/edit-v2.d.ts +5 -2
  44. package/vendor/packages/edit-store/lib/edit-v2.js +8 -1
  45. package/vendor/packages/edit-store/lib/index.d.ts +2 -0
  46. package/vendor/packages/edit-store/lib/index.js +2 -0
  47. package/vendor/packages/edit-store/lib/internal-model.js +18 -4
  48. package/vendor/packages/edit-store/lib/migrate/index.js +37 -9
  49. package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +4 -0
  50. package/vendor/packages/edit-store/lib/webview-kernel.d.ts +2 -0
  51. package/vendor/packages/edit-store/lib/webview-kernel.js +304 -0
  52. package/vendor/packages/export-nle/package.json +2 -2
  53. package/vendor/packages/frame-engine/README.ja.md +24 -3
  54. package/vendor/packages/frame-engine/README.md +24 -3
  55. package/vendor/packages/frame-engine/package.json +9 -1
  56. package/vendor/packages/gpu-export/README.ja.md +30 -0
  57. package/vendor/packages/gpu-export/README.md +30 -0
  58. package/vendor/packages/gpu-export/package.json +30 -0
  59. package/vendor/packages/intake-form/package.json +2 -2
  60. package/vendor/packages/media-bin/src/alpha-intake.mjs +272 -0
  61. package/vendor/packages/media-bin/test/alpha-intake.test.mjs +114 -0
  62. package/vendor/packages/osr-export/README.md +73 -0
  63. package/vendor/packages/osr-export/package.json +29 -0
  64. package/vendor/packages/preview-server/package.json +4 -3
  65. package/vendor/packages/render-cut/package.json +2 -2
  66. package/vendor/packages/schemas/analysis.schema.json +11 -1
  67. package/vendor/packages/schemas/bin/validate-edit.mjs +2 -2
  68. package/vendor/packages/schemas/edit.schema.json +9 -4
  69. package/vendor/packages/schemas/examples/edit-narration-trim-valid/edit.json +33 -0
  70. package/vendor/packages/schemas/test/edit-narration-trim.test.mjs +30 -0
  71. package/vendor/packages/schemas/test/validate-edit.test.mjs +9 -0
  72. package/vendor/packages/template-render/package.json +2 -2
  73. package/vendor/skills/analyze-footage/bin/person-matte/mask-format.mjs +25 -0
  74. package/vendor/skills/analyze-footage/bin/person-matte/mask-from-alpha.mjs +253 -0
  75. package/vendor/skills/analyze-footage/bin/person-matte/mask-roundtrip.mjs +206 -0
  76. package/vendor/skills/analyze-footage/bin/person-matte/person-cutout.mjs +3 -0
  77. package/vendor/skills/analyze-footage/bin/person-matte/person-matte.mjs +119 -4
  78. package/vendor/skills/analyze-footage/person-matte.md +20 -0
  79. package/vendor/skills/analyze-footage/test/person-matte-mask.test.mjs +85 -0
  80. package/vendor/skills/analyze-project/decision-record.md +3 -0
  81. package/vendor/skills/edit-lint/SKILL.md +26 -5
  82. package/vendor/skills/edit-plan/SKILL.md +3 -1
  83. package/vendor/skills/edit-plan/approvals-and-generation.md +2 -1
  84. package/vendor/skills/edit-plan/execution.md +1 -0
  85. package/vendor/skills/edit-plan/report-guide.md +8 -0
  86. package/vendor/skills/edit-plan/workflow.md +3 -0
  87. package/vendor/skills/render-cut/SKILL.md +26 -4
  88. package/vendor/templates/project-default/.claude/skills/README.md +9 -7
  89. package/vendor/templates/project-default/AGENTS.md +23 -13
  90. package/vendor/templates/project-default/CLAUDE.md +27 -3
  91. package/vendor/packages/preview-engine/README.md +0 -271
  92. package/vendor/packages/preview-engine/package.json +0 -25
package/README.md CHANGED
@@ -52,6 +52,20 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
52
52
  `packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
53
53
  sha256 検証・fail-closed は resolver 側の責務のまま。`src/assets-command.mjs`)。
54
54
 
55
+ ### Chrome の導入
56
+
57
+ 動画の書き出しに使う Chrome for Testing は、次のコマンドで導入できる。
58
+
59
+ ```sh
60
+ akari chrome install
61
+ akari chrome install --cache-dir /path/to/cache
62
+ akari chrome install --build-id 150.0.7339.16
63
+ ```
64
+
65
+ 保存先の既定は `~/.cache/puppeteer`。`--cache-dir` で保存先、`--build-id` で導入する
66
+ buildId を指定できる。実行環境から `@puppeteer/browsers` を解決できる必要があり、
67
+ AKARI Video アプリの配布物には必要なモジュールが同梱されている。
68
+
55
69
  `akari` に渡した引数はそのまま `opencode` に転送する(例: `akari --continue` は
56
70
  `opencode --continue` を起動する)。
57
71
 
package/bin/akari.mjs CHANGED
@@ -11,6 +11,8 @@ import { runCapabilityCommand } from '../src/capability-command.mjs';
11
11
  import { runStoreCommand } from '../src/store-command.mjs';
12
12
  import { runAssetsCommand } from '../src/assets-command.mjs';
13
13
  import { runMigrateCommand } from '../src/migrate-command.mjs';
14
+ import { runDoctorCommand } from '../src/doctor-command.mjs';
15
+ import { resolveRuntimePaths } from '../src/runtime-diagnostics.mjs';
14
16
  import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
15
17
  import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
16
18
 
@@ -21,7 +23,7 @@ async function printVersion() {
21
23
  const versionInfo = resolveInstalledVersionInfo({ env: process.env });
22
24
  // 1 行目は update / rollback の既存機械観測契約として CLI 版だけを維持する。
23
25
  console.log(`v${versionInfo.cliVersion}`);
24
- for (const line of describeInstalledVersions(versionInfo)) {
26
+ for (const line of describeInstalledVersions(versionInfo, resolveRuntimePaths({ env: process.env }))) {
25
27
  console.log(line);
26
28
  }
27
29
  return { exitCode: 0 };
@@ -47,13 +49,14 @@ async function printCliHelp() {
47
49
  // 読み直せば新版の内容を観測できる — 同一 argv での re-exec はしない設計判断
48
50
  // (理由は report.md 参照))。失敗は他の起動時副作用と同じく握りつぶし、
49
51
  // claude/opencode 起動やサブコマンド実行を止めない。
52
+ const argv = process.argv.slice(2);
53
+ const doctorJson = argv[0] === 'doctor' && argv.includes('--json');
50
54
  try {
51
- maybeApplyPendingUpdateOnLaunch({ env: process.env, log: (line) => console.log(line) });
55
+ maybeApplyPendingUpdateOnLaunch({ env: process.env, log: doctorJson ? () => {} : (line) => console.log(line) });
52
56
  } catch (error) {
53
57
  console.error(`自動更新の適用確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
54
58
  }
55
59
 
56
- const argv = process.argv.slice(2);
57
60
  // `akari update` / `akari init` / `akari new` / `akari narration` / `akari internal` /
58
61
  // `akari sounds` / `akari status` / `akari accept` / `akari capability` / `akari store` /
59
62
  // `akari assets` は claude へ転送せず、専用のサブコマンドとして扱う(契約 §4-1 /
@@ -62,6 +65,7 @@ const argv = process.argv.slice(2);
62
65
  // それ以外の引数はすべて従来どおり claude へ転送する。
63
66
  const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
64
67
  : (argv[0] === '--help' || argv[0] === '-h') ? printCliHelp()
68
+ : argv[0] === 'doctor' ? runDoctorCommand(argv.slice(1))
65
69
  : argv[0] === 'update' ? runUpdateCommand(argv.slice(1))
66
70
  : argv[0] === 'init' ? runInitCommand(argv.slice(1))
67
71
  : argv[0] === 'new' ? runNewCommand(argv.slice(1))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
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": {
@@ -0,0 +1,100 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+
4
+ const usage = [
5
+ '使い方: akari chrome install [options]',
6
+ '',
7
+ 'オプション:',
8
+ ' --cache-dir <dir> Chrome の保存先(既定: ~/.cache/puppeteer)',
9
+ ' --build-id <id> 導入する Chrome の buildId(既定: stable の最新版)',
10
+ ' -h, --help このヘルプを表示',
11
+ ].join('\n');
12
+
13
+ class PublicError extends Error {
14
+ constructor(message, exitCode = 1) {
15
+ super(message);
16
+ this.exitCode = exitCode;
17
+ }
18
+ }
19
+
20
+ export function resolveChromeCacheDir(homeDirectory = homedir(), cacheDir) {
21
+ return cacheDir ? resolve(cacheDir) : join(homeDirectory, '.cache', 'puppeteer');
22
+ }
23
+
24
+ function parseInstallArguments(argv, homeDirectory) {
25
+ const parsed = {
26
+ cacheDir: resolveChromeCacheDir(homeDirectory),
27
+ buildId: undefined,
28
+ };
29
+ for (let index = 1; index < argv.length; index += 1) {
30
+ const argument = argv[index];
31
+ if (argument !== '--cache-dir' && argument !== '--build-id') {
32
+ throw new PublicError(`不明な引数です: ${argument}`, 2);
33
+ }
34
+ const value = argv[index + 1];
35
+ if (value === undefined || value.startsWith('--')) {
36
+ throw new PublicError(`${argument} の値がありません`, 2);
37
+ }
38
+ index += 1;
39
+ if (argument === '--cache-dir') parsed.cacheDir = resolveChromeCacheDir(homeDirectory, value);
40
+ else parsed.buildId = value;
41
+ }
42
+ return parsed;
43
+ }
44
+
45
+ export async function runChromeCommand(argv, options = {}) {
46
+ const log = options.log ?? ((line) => console.log(line));
47
+ const error = options.error ?? ((line) => console.error(line));
48
+ const loadBrowsers = options.loadBrowsers ?? (() => import('@puppeteer/browsers'));
49
+ const homeDirectory = options.homeDirectory ?? homedir();
50
+
51
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h'
52
+ || (argv[0] === 'install' && (argv.includes('--help') || argv.includes('-h')))) {
53
+ log(usage);
54
+ return { exitCode: 0 };
55
+ }
56
+ if (argv[0] !== 'install') {
57
+ error(`不明なサブコマンドです: ${argv[0]}\n${usage}`);
58
+ return { exitCode: 2 };
59
+ }
60
+
61
+ let parsed;
62
+ try {
63
+ parsed = parseInstallArguments(argv, homeDirectory);
64
+ } catch (cause) {
65
+ error(`${cause instanceof Error ? cause.message : String(cause)}\n${usage}`);
66
+ return { exitCode: cause instanceof PublicError ? cause.exitCode : 1 };
67
+ }
68
+
69
+ let browsers;
70
+ try {
71
+ browsers = await loadBrowsers();
72
+ } catch (cause) {
73
+ error([
74
+ `@puppeteer/browsers を読み込めないため Chrome を導入できません: ${cause instanceof Error ? cause.message : String(cause)}`,
75
+ 'システムの node がある場合は `npx puppeteer browsers install chrome` でも導入できます。',
76
+ ].join('\n'));
77
+ return { exitCode: 1 };
78
+ }
79
+
80
+ try {
81
+ const platform = browsers.detectBrowserPlatform();
82
+ const buildId = parsed.buildId
83
+ ?? await browsers.resolveBuildId('chrome', platform, 'stable');
84
+ const installed = await browsers.install({
85
+ browser: 'chrome',
86
+ buildId,
87
+ cacheDir: parsed.cacheDir,
88
+ downloadProgressCallback: 'default',
89
+ });
90
+ if (typeof installed?.executablePath !== 'string' || installed.executablePath.length === 0) {
91
+ throw new Error('導入後の実行ファイルを特定できませんでした。');
92
+ }
93
+ log(`buildId: ${buildId}`);
94
+ log(`実行ファイル: ${resolve(installed.executablePath)}`);
95
+ return { exitCode: 0 };
96
+ } catch (cause) {
97
+ error(`Chrome の導入に失敗しました: ${cause instanceof Error ? cause.message : String(cause)}`);
98
+ return { exitCode: 1 };
99
+ }
100
+ }
package/src/cli.mjs CHANGED
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
4
4
 
5
5
  import { resolveLauncherAssets } from './repo-assets.mjs';
6
6
  import { detectProjectState } from './project-state.mjs';
7
- import { findClaudeExecutable, findOpencodeExecutable } from './path-lookup.mjs';
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
10
  import { resolveEffectiveProjectRoot } from './first-run.mjs';
@@ -22,6 +22,8 @@ import {
22
22
  triggerBackgroundRefresh
23
23
  } from './update-check.mjs';
24
24
  import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
25
+ import { runChromeCommand } from './chrome-command.mjs';
26
+ import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
25
27
 
26
28
  /**
27
29
  * `akari` ランチャーの本体。3 入口契約(ターミナル `akari` / セッション内 `/akari` /
@@ -33,6 +35,8 @@ import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self
33
35
  * options 経由で差し替え可能にしてあり、node --test から実プロセスを起動せずに分岐を検証できる。
34
36
  */
35
37
  export async function run(args, options = {}) {
38
+ if (args[0] === 'chrome') return runChromeCommand(args.slice(1), options);
39
+
36
40
  const log = options.log ?? ((line) => console.log(line));
37
41
  const assets = options.assets ?? resolveLauncherAssets();
38
42
  const scaffold = options.scaffold ?? defaultScaffold;
@@ -95,7 +99,8 @@ export async function run(args, options = {}) {
95
99
  } catch (error) {
96
100
  log(`接続確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
97
101
  }
98
- log(describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env))));
102
+ const runtimeDiagnostics = options.runtimeDiagnostics ?? resolveRuntimePaths({ ...options, env, platform });
103
+ log(describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env)), runtimeDiagnostics));
99
104
  }
100
105
 
101
106
  // 新版通知(契約 §4-1): キャッシュの読み比較のみ・ネットワークには一切触れない
@@ -201,6 +206,11 @@ export async function runUpdateCommand(args, options = {}) {
201
206
  const dismissRequested = args.includes('--dismiss');
202
207
  const rollbackRequested = args.includes('--rollback');
203
208
  const forceRequested = args.includes('--force');
209
+ const runtimeDiagnostics = options.runtimeDiagnostics ?? resolveRuntimePaths({ ...options, env });
210
+ const pathEnv = Object.hasOwn(env, 'PATH') ? (env.PATH ?? '') : (process.env.PATH ?? '');
211
+ const npmAvailable = options.npmAvailable
212
+ ?? !!(options.findExecutable ?? findExecutable)('npm', pathEnv, options.platform ?? process.platform, env.PATHEXT);
213
+ const describeOptions = { runtimeDiagnostics, npmAvailable };
204
214
  let usingCachedFeed = false;
205
215
 
206
216
  if (rollbackRequested) {
@@ -214,7 +224,7 @@ export async function runUpdateCommand(args, options = {}) {
214
224
  dismissed = true;
215
225
  }
216
226
  const finalCache = dismissed ? readCacheSync(cachePath) : cache;
217
- for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache: finalCache, dismissed })) {
227
+ for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache: finalCache, dismissed, ...describeOptions })) {
218
228
  log(line);
219
229
  }
220
230
  return { exitCode: 0 };
@@ -238,16 +248,22 @@ export async function runUpdateCommand(args, options = {}) {
238
248
  && (hasManagedApp || (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot }));
239
249
 
240
250
  if (!selfUpdateEligible) {
241
- for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed: false, usingCachedFeed })) {
251
+ for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed: false, usingCachedFeed, ...describeOptions })) {
242
252
  log(line);
243
253
  }
254
+ if (forceRequested
255
+ && versionInfo.installRefStatus === 'missing'
256
+ && runtimeDiagnostics.render_cut.origin !== 'monorepo') {
257
+ log('この CLI からは install.sh 経路の本体を入れ直せません。');
258
+ log('導入するには `curl -fsSL https://raw.githubusercontent.com/AkariLabs/akari-video/main/install.sh | bash` を実行してください(デスクトップ版だけで使う場合は不要です)。');
259
+ }
244
260
  return { exitCode: 0 };
245
261
  }
246
262
 
247
263
  if (usingCachedFeed) {
248
264
  log(describeUpdateCacheFallback(cache));
249
265
  }
250
- for (const line of describeInstalledVersions(versionInfo)) {
266
+ for (const line of describeInstalledVersions(versionInfo, runtimeDiagnostics)) {
251
267
  log(line);
252
268
  }
253
269
  log(`最新バージョン: v${feed.product}`);
@@ -265,6 +281,7 @@ export async function runUpdateCommand(args, options = {}) {
265
281
  }
266
282
 
267
283
  function resolveCommandVersionInfo(options, env) {
284
+ if (options.versionInfo) return options.versionInfo;
268
285
  // 既存テスト/埋め込み利用の currentVersion 注入は CLI 版注入としても扱う。
269
286
  const cliVersion = options.cliVersion ?? options.currentVersion ?? readOwnVersion();
270
287
  if (options.currentVersion !== undefined) {
@@ -0,0 +1,65 @@
1
+ import { doctorExitCode, resolveDoctorReport } from './runtime-diagnostics.mjs';
2
+
3
+ const usage = [
4
+ '使い方: akari doctor [--json]',
5
+ '',
6
+ '必須部品の実在、解決元、PATH を確認します。',
7
+ ' --json 機械可読な診断結果を 1 オブジェクトで出力',
8
+ ].join('\n');
9
+
10
+ export async function runDoctorCommand(argv, options = {}) {
11
+ const log = options.log ?? ((line) => process.stdout.write(`${line}\n`));
12
+ const error = options.error ?? ((line) => process.stderr.write(`${line}\n`));
13
+ if (argv.includes('--help') || argv.includes('-h')) {
14
+ log(usage);
15
+ return { exitCode: 0 };
16
+ }
17
+ const unknown = argv.find((argument) => argument !== '--json');
18
+ if (unknown) {
19
+ error(`不明な doctor オプションです: ${unknown}\n${usage}`);
20
+ return { exitCode: 2 };
21
+ }
22
+
23
+ const report = options.report ?? await (options.resolveReport ?? resolveDoctorReport)(options);
24
+ if (argv.includes('--json')) log(JSON.stringify(report));
25
+ else log(formatDoctorReport(report));
26
+ return { exitCode: doctorExitCode(report.verdict), report };
27
+ }
28
+
29
+ export function formatDoctorReport(report) {
30
+ const rows = [
31
+ ['cli', 'ok', `v${report.cli.version} — ${report.cli.entry_path}`],
32
+ ['app_managed', report.app_managed.status, detail(report.app_managed.path, report.app_managed.version)],
33
+ ['app_bundle', report.app_bundle.found ? 'found' : 'missing', detail(report.app_bundle.path, report.app_bundle.version)],
34
+ ['render_cut', report.render_cut.origin, report.render_cut.path ?? '見つかりません'],
35
+ ['edit_lint', report.edit_lint.origin, report.edit_lint.path ?? '見つかりません'],
36
+ ['ffmpeg', report.ffmpeg.origin, report.ffmpeg.path ?? '見つかりません'],
37
+ ['ffprobe', report.ffprobe.origin, report.ffprobe.path ?? '見つかりません'],
38
+ ['chrome', report.chrome.found ? 'found' : 'missing', report.chrome.path ?? report.chrome.cache_dir],
39
+ ['gpu_export', report.gpu_export?.available ? 'ok' : 'unavailable', report.gpu_export?.reason ?? '診断情報がありません'],
40
+ ['path', report.path.on_path ? 'ok' : 'missing', report.path.cli_shim_dir],
41
+ ];
42
+ const widths = [
43
+ Math.max('項目'.length, ...rows.map((row) => row[0].length)),
44
+ Math.max('状態'.length, ...rows.map((row) => row[1].length)),
45
+ ];
46
+ const lines = [
47
+ 'AKARI Video doctor',
48
+ '',
49
+ `${'項目'.padEnd(widths[0])} ${'状態'.padEnd(widths[1])} 詳細`,
50
+ `${'-'.repeat(widths[0])} ${'-'.repeat(widths[1])} ${'-'.repeat(20)}`,
51
+ ...rows.map((row) => `${row[0].padEnd(widths[0])} ${row[1].padEnd(widths[1])} ${row[2]}`),
52
+ '',
53
+ `判定: ${report.verdict}`,
54
+ ];
55
+ if (report.next_steps.length > 0) {
56
+ lines.push('次の手順:');
57
+ lines.push(...report.next_steps.map((step) => ` - ${step}`));
58
+ }
59
+ return lines.join('\n');
60
+ }
61
+
62
+ function detail(path, version) {
63
+ const location = path ?? '見つかりません';
64
+ return version ? `v${version} — ${location}` : location;
65
+ }
package/src/messages.mjs CHANGED
@@ -71,7 +71,7 @@ export function formatUpdateNotice(status) {
71
71
  }
72
72
 
73
73
  /** `akari doctor` 系出力に足す 1 行(現在版 + フィード取得状態)。 */
74
- export function describeVersionStatus(versionOrInfo, cache) {
74
+ export function describeVersionStatus(versionOrInfo, cache, runtimeDiagnostics) {
75
75
  if (typeof versionOrInfo === 'string') {
76
76
  if (!cache?.feed) {
77
77
  return `バージョン: v${versionOrInfo}(更新フィード: 未取得)`;
@@ -80,7 +80,7 @@ export function describeVersionStatus(versionOrInfo, cache) {
80
80
  return `バージョン: v${versionOrInfo}(更新フィード: 取得済み・${fetchedAt} 時点)`;
81
81
  }
82
82
  const info = normalizeVersionInfo(versionOrInfo);
83
- const installed = describeInstalledVersions(info).join(' / ');
83
+ const installed = describeInstalledVersions(info, runtimeDiagnostics).join(' / ');
84
84
  if (!cache?.feed) {
85
85
  return `${installed}(更新フィード: 未取得)`;
86
86
  }
@@ -88,7 +88,7 @@ export function describeVersionStatus(versionOrInfo, cache) {
88
88
  return `${installed}(更新フィード: 取得済み・${fetchedAt} 時点)`;
89
89
  }
90
90
 
91
- export function describeInstalledVersions(versionOrInfo) {
91
+ export function describeInstalledVersions(versionOrInfo, runtimeDiagnostics) {
92
92
  const info = normalizeVersionInfo(versionOrInfo);
93
93
  if (info.installRefNeedsRepair) {
94
94
  const installRefPath = info.installRefPath ?? '~/.akari/app/.akari-install-ref';
@@ -99,6 +99,21 @@ export function describeInstalledVersions(versionOrInfo) {
99
99
  ];
100
100
  }
101
101
  if (!info.appVersion) {
102
+ if (info.installRefStatus === 'missing' && runtimeDiagnostics?.render_cut) {
103
+ const renderCut = runtimeDiagnostics.render_cut;
104
+ const availability = renderCut.origin === 'none'
105
+ ? 'render-cut が見つからないため書き出しできません'
106
+ : `書き出しは ${humanRenderOrigin(renderCut.origin)} の render-cut を使います`;
107
+ const lines = [
108
+ `現在のバージョン: v${info.currentVersion}`,
109
+ `CLI バージョン: v${info.cliVersion}`,
110
+ `install.sh 経路の本体は未導入(${availability}。詳細: \`akari doctor\`)`,
111
+ ];
112
+ if (renderCut.origin === 'none') {
113
+ lines.push('復旧するにはデスクトップ版を導入するか、install.sh 経路の本体を導入してください。');
114
+ }
115
+ return lines;
116
+ }
102
117
  return [
103
118
  `現在のバージョン: v${info.currentVersion}`,
104
119
  `CLI バージョン: v${info.cliVersion}`,
@@ -112,11 +127,15 @@ export function describeInstalledVersions(versionOrInfo) {
112
127
  return lines;
113
128
  }
114
129
 
130
+ function humanRenderOrigin(origin) {
131
+ return origin === 'monorepo' ? '開発リポジトリ' : origin;
132
+ }
133
+
115
134
  export function describeForceReinstall(versionOrInfo, targetVersion) {
116
135
  const info = normalizeVersionInfo(versionOrInfo);
117
136
  return info.installRefNeedsRepair
118
- ? `--force: 版を判定できない本体 → v${targetVersion} を入れ直します。`
119
- : `--force: 本体 v${info.currentVersion} → v${targetVersion} を入れ直します。`;
137
+ ? `--force: 版を判定できない install.sh 経路の本体 → v${targetVersion} を入れ直します。`
138
+ : `--force: install.sh 経路の本体 v${info.currentVersion} → v${targetVersion} を入れ直します。`;
120
139
  }
121
140
 
122
141
  function normalizeVersionInfo(value) {
@@ -172,9 +191,9 @@ export function creatorRootPromptText(defaultPath) {
172
191
  * `akari update` の出力本文(複数行)。フィード未取得・最新・新版ありで案内が変わる。
173
192
  * `dismissed` は今回の実行で dismiss 記録を書いたかどうか(表示文言の切り替えのみに使う)。
174
193
  */
175
- export function describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed, usingCachedFeed = false }) {
194
+ export function describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed, usingCachedFeed = false, runtimeDiagnostics, npmAvailable = true }) {
176
195
  const info = versionInfo ?? normalizeVersionInfo(currentVersion);
177
- const lines = versionInfo ? describeInstalledVersions(info) : [`現在のバージョン: v${currentVersion}`];
196
+ const lines = versionInfo ? describeInstalledVersions(info, runtimeDiagnostics) : [`現在のバージョン: v${currentVersion}`];
178
197
  const feed = cache?.feed;
179
198
  if (!feed) {
180
199
  lines.push('最新情報をまだ取得できていません(オフライン、または初回起動直後の可能性があります)。');
@@ -202,8 +221,12 @@ export function describeUpdateCommand({ currentVersion, versionInfo, cache, dism
202
221
  }
203
222
 
204
223
  const tarballUrl = feed.components?.cli?.tarball?.url;
205
- lines.push('更新するには、次のコマンドを実行してください(自動実行はしません):');
206
- lines.push(tarballUrl ? ` npm i -g ${tarballUrl}` : ' npm i -g akari-video@latest');
224
+ if (npmAvailable) {
225
+ lines.push('CLI を更新するには、次のコマンドを実行してください(自動実行はしません):');
226
+ lines.push(tarballUrl ? ` npm i -g ${tarballUrl}` : ' npm i -g akari-video@latest');
227
+ } else {
228
+ lines.push('npm が PATH に無いため、CLI の npm 更新コマンドは表示しません。');
229
+ }
207
230
  lines.push(
208
231
  dismissed
209
232
  ? `この版(v${feed.product})の通知は今後表示しません。`
@@ -303,7 +326,9 @@ export function describeCliHelp() {
303
326
  ' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
304
327
  ' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
305
328
  ' sounds 公式音源ライブラリを一括ダウンロード(無料)',
306
- ' update [--force] 更新を確認する(--force で本体を入れ直す)',
329
+ ' chrome install 動画書き出しに使う Chrome をダウンロードして導入する',
330
+ ' doctor [--json] 必須部品の実在と解決元を診断する',
331
+ ' update [--force] 更新を確認する(--force で install.sh 経路の本体を入れ直す)',
307
332
  ' status 接続状態を確認する',
308
333
  ' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
309
334
  '',
@@ -27,33 +27,23 @@ function resolveWindowsExtensions(pathExt) {
27
27
  * を判定するため、拡張子なしファイル(npm の POSIX シムなど)は直接 spawn できない。
28
28
  */
29
29
  export function findClaudeExecutable(pathEnv = process.env.PATH ?? '', platform = process.platform, pathExt = process.env.PATHEXT) {
30
- const directories = pathEnv.split(path.delimiter).filter(Boolean);
31
- const candidateNames = platform === 'win32'
32
- ? resolveWindowsExtensions(pathExt).map((extension) => `claude${extension.toLowerCase()}`)
33
- : ['claude'];
34
-
35
- for (const directory of directories) {
36
- for (const name of candidateNames) {
37
- const candidate = path.join(directory, name);
38
- try {
39
- accessSync(candidate, fsConstants.X_OK);
40
- return candidate;
41
- } catch {
42
- // このディレクトリには無い。次を探す。
43
- }
44
- }
45
- }
46
- return null;
30
+ return findExecutable('claude', pathEnv, platform, pathExt);
47
31
  }
48
32
 
49
33
  /**
50
34
  * PATH 上に `opencode` 実行ファイルがあるかを探す。`findClaudeExecutable` と同じ規約。
51
35
  */
52
36
  export function findOpencodeExecutable(pathEnv = process.env.PATH ?? '', platform = process.platform, pathExt = process.env.PATHEXT) {
53
- const directories = pathEnv.split(path.delimiter).filter(Boolean);
37
+ return findExecutable('opencode', pathEnv, platform, pathExt);
38
+ }
39
+
40
+ /** PATH 上の任意のコマンドを探索する共有ヘルパー。doctor / update も同じ判定を使う。 */
41
+ export function findExecutable(command, pathEnv = process.env.PATH ?? '', platform = process.platform, pathExt = process.env.PATHEXT) {
42
+ const delimiter = platform === 'win32' ? ';' : path.delimiter;
43
+ const directories = pathEnv.split(delimiter).filter(Boolean);
54
44
  const candidateNames = platform === 'win32'
55
- ? resolveWindowsExtensions(pathExt).map((extension) => `opencode${extension.toLowerCase()}`)
56
- : ['opencode'];
45
+ ? resolveWindowsExtensions(pathExt).map((extension) => `${command}${extension.toLowerCase()}`)
46
+ : [command];
57
47
 
58
48
  for (const directory of directories) {
59
49
  for (const name of candidateNames) {