akari-video 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/bin/akari.mjs +13 -4
- package/package.json +1 -1
- package/src/assets-command.mjs +42 -0
- package/src/internal-command.mjs +52 -0
- package/src/messages.mjs +6 -1
- package/{vendor/skills/generate-narration/bin/generate-narration.mjs → src/narration-command.mjs} +48 -31
- package/src/new-command.mjs +119 -0
- package/src/project-state.mjs +10 -1
- package/src/repo-assets.mjs +20 -4
- package/src/status-command.mjs +18 -1
- package/src/status-core/display-name.mjs +22 -0
- package/src/status-core/status.mjs +9 -1
- package/src/status-core/workspace.mjs +123 -0
- package/vendor/.akari-capability-sources.json +6 -0
- package/vendor/docs/contract-2026-07-14-edit-json-v1-crop.md +8 -0
- package/vendor/docs/contract-2026-07-22-render-basics.md +40 -3
- package/vendor/docs/contract-2026-07-23-analysis-person-matte.md +1 -1
- package/vendor/docs/contract-2026-08-02-creator-root-v1.md +15 -2
- package/vendor/docs/contract-2026-08-02-preview-parity.md +458 -1
- package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +21 -0
- package/vendor/docs/contract-2026-08-05-fx-v0.md +89 -0
- package/vendor/docs/contract-2026-08-06-direction-recipes-v0.md +280 -0
- package/vendor/packages/akari-launcher/README.md +4 -1
- package/vendor/packages/akari-launcher/bin/akari.mjs +13 -4
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/akari-tools/package.json +16 -0
- package/vendor/packages/asset-resolver/README.md +30 -2
- package/vendor/packages/asset-resolver/browse/app.js +124 -0
- package/vendor/packages/asset-resolver/browse/index.html +74 -0
- package/vendor/packages/asset-resolver/src/browse-server.mjs +151 -0
- package/vendor/packages/asset-resolver/src/catalog.mjs +58 -0
- package/vendor/packages/asset-resolver/src/entitlements.mjs +36 -0
- package/vendor/packages/asset-resolver/src/env.mjs +92 -0
- package/vendor/packages/asset-resolver/src/errors.mjs +10 -0
- package/vendor/packages/asset-resolver/src/fetch-file.mjs +60 -0
- package/vendor/packages/asset-resolver/src/hash.mjs +15 -0
- package/vendor/packages/asset-resolver/src/library.mjs +38 -0
- package/vendor/packages/asset-resolver/src/paid-zip.mjs +116 -0
- package/vendor/packages/asset-resolver/src/resolve.mjs +226 -0
- package/vendor/packages/asset-resolver/src/state.mjs +34 -0
- package/vendor/packages/asset-resolver/test/browse-server.test.mjs +42 -0
- package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +37 -0
- package/vendor/packages/asset-resolver/test/entitlements.test.mjs +56 -0
- package/vendor/packages/asset-resolver/test/fixtures/build-fixture-library.mjs +106 -0
- package/vendor/packages/asset-resolver/test/helpers.mjs +28 -0
- package/vendor/packages/asset-resolver/test/resolve-integrity.test.mjs +32 -0
- package/vendor/packages/asset-resolver/test/resolve-locked.test.mjs +45 -0
- package/vendor/packages/asset-resolver/test/resolve-paid-zip.test.mjs +219 -0
- package/vendor/packages/asset-resolver/test/resolve-project-copy-darwin.test.mjs +84 -0
- package/vendor/packages/asset-resolver/test/resolve-success.test.mjs +55 -0
- package/vendor/packages/creator-root/src/index.mjs +238 -2
- package/vendor/packages/creator-root/test/create-creator-root.test.mjs +31 -1
- package/vendor/packages/creator-root/test/default-root-path.test.mjs +5 -5
- package/vendor/packages/direction/README.md +86 -0
- package/vendor/packages/direction/bin/expand-direction.mjs +193 -0
- package/vendor/packages/direction/package.json +13 -0
- package/vendor/packages/overlay-runtime/README.md +22 -2
- package/vendor/packages/overlay-runtime/package.json +1 -1
- package/vendor/packages/preview-server/src/server.mjs +134 -17
- package/vendor/packages/project-scaffold/src/index.mjs +5 -1
- package/vendor/packages/project-scaffold/test/create-project.test.mjs +22 -0
- package/vendor/packages/render-cut/bin/akari-apply-textstyle.mjs +3 -3
- package/vendor/packages/schemas/asset-meta.schema.json +39 -0
- package/vendor/packages/schemas/bin/validate-asset.mjs +48 -3
- package/vendor/packages/schemas/bin/validate-captions.mjs +149 -5
- package/vendor/packages/schemas/bin/validate-edit.mjs +294 -0
- package/vendor/packages/schemas/bin/validate-intake.mjs +11 -1
- package/vendor/packages/schemas/captions.schema.json +148 -3
- package/vendor/packages/schemas/edit.schema.json +155 -7
- package/vendor/packages/schemas/examples/edit-cuts-fx-color-overlay-missing-color-invalid/edit.json +9 -0
- package/vendor/packages/schemas/examples/edit-cuts-fx-intensity-out-of-range-invalid/edit.json +9 -0
- package/vendor/packages/schemas/examples/edit-cuts-fx-invalid-id/edit.json +9 -0
- package/vendor/packages/schemas/examples/edit-cuts-fx-unknown-key-invalid/edit.json +9 -0
- package/vendor/packages/schemas/examples/edit-cuts-fx-valid/edit.json +19 -0
- package/vendor/packages/schemas/examples/edit-layers-invalid-crop-out-of-bounds/edit.json +18 -0
- package/vendor/packages/schemas/examples/edit-layers-invalid-perspective-degenerate/edit.json +18 -0
- package/vendor/packages/schemas/examples/edit-layers-invalid-perspective-out-of-range/edit.json +18 -0
- package/vendor/packages/schemas/examples/edit-layers-invalid-perspective-wrong-corner-count/edit.json +18 -0
- package/vendor/packages/schemas/examples/edit-layers-keyframes-valid/edit.json +38 -0
- package/vendor/packages/schemas/examples/edit-layers-valid/edit.json +2 -0
- package/vendor/packages/schemas/fixtures/intake/invalid-title-wrong-type/intake.json +9 -0
- package/vendor/packages/schemas/fixtures/intake/valid-with-title/intake.json +9 -0
- package/vendor/packages/schemas/intake.schema.json +8 -0
- package/vendor/packages/schemas/test/captions-animation-contract.test.mjs +38 -0
- package/vendor/packages/schemas/test/validate-captions.test.mjs +13 -0
- package/vendor/packages/schemas/test/validate-edit.test.mjs +355 -0
- package/vendor/packages/schemas/test/validate-intake.test.mjs +17 -0
- package/vendor/skills/analyze-footage/bin/is-main-module.mjs +11 -0
- package/vendor/skills/analyze-footage/bin/person-matte/alpha-tag.mjs +4 -0
- package/vendor/skills/analyze-footage/bin/person-matte/person-matte.mjs +5 -2
- package/vendor/skills/analyze-footage/bin/transcribe-cloud.mjs +2 -3
- package/vendor/skills/analyze-footage/test/entrypoint-symlink.test.mjs +36 -0
- package/vendor/skills/analyze-footage/test/person-matte-alpha-tag.test.mjs +21 -0
- package/vendor/skills/beat-sync-edit/SKILL.md +3 -3
- package/vendor/skills/beat-sync-edit/generator.md +2 -2
- package/vendor/skills/beat-sync-edit/templates/gen-timeline.mjs +1 -1
- package/vendor/skills/beat-sync-edit/verify-loop.md +3 -2
- package/vendor/skills/create-project/SKILL.md +2 -2
- package/vendor/skills/edit-plan/expression-selection.md +4 -0
- package/vendor/skills/generate-narration/avatar-voice.md +3 -3
- package/vendor/skills/generate-narration/engines.md +2 -2
- package/vendor/skills/generate-narration/voice-profile-setup.md +2 -2
- package/vendor/skills/manage-connections/SKILL.md +9 -5
- package/vendor/skills/manage-connections/bin/doctor.mjs +114 -3
- package/vendor/skills/manage-connections/bin/resolve-connections.mjs +348 -0
- package/vendor/skills/manage-connections/test/resolve-connections.test.mjs +151 -0
- package/vendor/skills/overlay-authoring/3d.md +56 -0
- package/vendor/skills/overlay-authoring/motion.md +21 -0
- package/vendor/skills/overlay-authoring/telop.md +23 -0
- package/vendor/skills/setup-library/SKILL.md +12 -0
- package/vendor/templates/project-default/.akari/connections.json +1 -225
- package/vendor/templates/project-default/AGENTS.md +8 -0
- package/vendor/templates/project-default/CLAUDE.md +6 -0
- package/vendor/skills/beat-sync-edit/bin/beatmap.mjs +0 -131
- package/vendor/skills/beat-sync-edit/bin/probe-frame.mjs +0 -96
- package/vendor/skills/beat-sync-edit/bin/render-when-idle.sh +0 -46
- package/vendor/skills/create-project/bin/create-project.mjs +0 -88
package/README.md
CHANGED
|
@@ -32,6 +32,9 @@ akari
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
サブコマンド: `akari update`(更新確認)/ `akari init`(作業場の作成・確認のみ)/
|
|
35
|
+
`akari new <target-dir> [--template <path>]`(新規プロジェクト作成)/
|
|
36
|
+
`akari narration generate ...`(VOICEVOX / fal-qwen3 ナレーション生成)/
|
|
37
|
+
`akari internal beat-sync-<beatmap|probe-frame|render-when-idle> ...`(beat-sync-edit 内部実行物)/
|
|
35
38
|
`akari sounds [--variant wav] [--force]`(公式音源の一括ダウンロード。プロンプトなし・headless 可)/
|
|
36
39
|
`akari store <connect|status|download|disconnect>`(AKARI Store 連携。マイページで発行した
|
|
37
40
|
接続トークンを `~/.akari/store-credentials.json`(0600)に保存し、購入済み一覧の確認と
|
|
@@ -67,7 +70,7 @@ AKARI Video は「同じファイル契約(`.akari/` 配下の JSON)に収
|
|
|
67
70
|
| アプリ | 接続ボタン(AKARI Video アプリ) | アプリの「はじめる」画面から接続 → はじめかた選択 |
|
|
68
71
|
|
|
69
72
|
3 つとも最終的に同じもの(`.akari/connections.json` / `.akari/intake.json` /
|
|
70
|
-
`
|
|
73
|
+
`akari new` が使う共通 project-scaffold)を読み書きするため、どの入口から始めても続きは他の入口から
|
|
71
74
|
再開できる。
|
|
72
75
|
|
|
73
76
|
## インストール
|
package/bin/akari.mjs
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { run, runUpdateCommand } from '../src/cli.mjs';
|
|
3
3
|
import { runInitCommand } from '../src/init-command.mjs';
|
|
4
|
+
import { runNewCommand } from '../src/new-command.mjs';
|
|
5
|
+
import { runNarrationCommand } from '../src/narration-command.mjs';
|
|
6
|
+
import { runInternalCommand } from '../src/internal-command.mjs';
|
|
4
7
|
import { runSoundsCommand } from '../src/sounds-setup.mjs';
|
|
5
8
|
import { runStatusCommand } from '../src/status-command.mjs';
|
|
6
9
|
import { runAcceptCommand } from '../src/accept-command.mjs';
|
|
7
10
|
import { runCapabilityCommand } from '../src/capability-command.mjs';
|
|
8
11
|
import { runStoreCommand } from '../src/store-command.mjs';
|
|
12
|
+
import { runAssetsCommand } from '../src/assets-command.mjs';
|
|
9
13
|
|
|
10
14
|
const argv = process.argv.slice(2);
|
|
11
|
-
// `akari update` / `akari init` / `akari
|
|
12
|
-
// `akari capability` / `akari store`
|
|
13
|
-
//
|
|
14
|
-
// 音源カタログ既定化のオーナー裁定 2026-08-03 /
|
|
15
|
+
// `akari update` / `akari init` / `akari new` / `akari narration` / `akari internal` /
|
|
16
|
+
// `akari sounds` / `akari status` / `akari accept` / `akari capability` / `akari store` /
|
|
17
|
+
// `akari assets` は claude へ転送せず、専用のサブコマンドとして扱う(契約 §4-1 /
|
|
18
|
+
// タスク契約 launcher-init(内部リポ)/ 音源カタログ既定化のオーナー裁定 2026-08-03 /
|
|
19
|
+
// AKARI Store 連携 / タスク契約 2026-08-09-agent-assets-discovery)。
|
|
15
20
|
// それ以外の引数はすべて従来どおり claude へ転送する。
|
|
16
21
|
const invoke = argv[0] === 'update' ? runUpdateCommand(argv.slice(1))
|
|
17
22
|
: argv[0] === 'init' ? runInitCommand(argv.slice(1))
|
|
23
|
+
: argv[0] === 'new' ? runNewCommand(argv.slice(1))
|
|
24
|
+
: argv[0] === 'narration' ? runNarrationCommand(argv.slice(1))
|
|
25
|
+
: argv[0] === 'internal' ? runInternalCommand(argv.slice(1))
|
|
18
26
|
: argv[0] === 'sounds' ? runSoundsCommand(argv.slice(1))
|
|
19
27
|
: argv[0] === 'status' ? runStatusCommand(argv.slice(1))
|
|
20
28
|
: argv[0] === 'accept' ? runAcceptCommand(argv.slice(1))
|
|
21
29
|
: argv[0] === 'capability' ? runCapabilityCommand(argv.slice(1))
|
|
22
30
|
: argv[0] === 'store' ? runStoreCommand(argv.slice(1))
|
|
31
|
+
: argv[0] === 'assets' ? runAssetsCommand(argv.slice(1))
|
|
23
32
|
: run(argv);
|
|
24
33
|
|
|
25
34
|
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.
|
|
3
|
+
"version": "0.1.6",
|
|
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,42 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
4
|
+
import { assetsResolverUnavailableError } from './messages.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `akari assets <list|fetch|sync|...>` — `packages/asset-resolver` の CLI
|
|
8
|
+
* (`bin/akari-assets.mjs`)への薄い委譲(タスク契約 2026-08-09-agent-assets-discovery)。
|
|
9
|
+
*
|
|
10
|
+
* カタログ合成・entitlements 判定・取得・sha256 検証・validate-asset・fail-closed の実ロジックは
|
|
11
|
+
* すべて resolver 側の責務のまま変更しない。認証(`~/.akari/store-credentials.json`)も
|
|
12
|
+
* resolver がそのまま読む。ここでは引数をそのまま子プロセスへ転送し、resolver 側の usage /
|
|
13
|
+
* エラー文言 / exit code をそのまま利用者に返すだけ。
|
|
14
|
+
*
|
|
15
|
+
* 規約は他の launcher 委譲コマンド(`akari sounds` = sounds-setup.mjs)と同じ:
|
|
16
|
+
* - 同梱の実体解決は `resolveLauncherAssets()`(モノレポ checkout → npm 配布時の vendor/)
|
|
17
|
+
* - 副作用(spawn)は options で注入可能・node --test で実プロセス不要
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
function defaultSpawnAssetsCli(cliPath, args, env) {
|
|
21
|
+
// stdio: inherit — list の一覧表示・fetch の進捗ログ・JSON 出力をそのまま利用者に見せる
|
|
22
|
+
// (sounds-setup.mjs の defaultSpawnFetch と同じ流儀)。env を明示的に渡すのは必須 —
|
|
23
|
+
// 注入された AKARI_HOME(テスト・隔離実行)が resolver 側の解決にも効くようにする。
|
|
24
|
+
return spawnSync(process.execPath, [cliPath, ...args], { stdio: 'inherit', env });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runAssetsCommand(args, options = {}) {
|
|
28
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
29
|
+
const env = options.env ?? process.env;
|
|
30
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
31
|
+
|
|
32
|
+
const cliPath = assets?.assetResolverCliPath;
|
|
33
|
+
if (!cliPath) {
|
|
34
|
+
logError(assetsResolverUnavailableError());
|
|
35
|
+
return { exitCode: 1 };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const spawnAssetsCli = options.spawnAssetsCli ?? defaultSpawnAssetsCli;
|
|
39
|
+
const result = spawnAssetsCli(cliPath, args, env);
|
|
40
|
+
const exitCode = typeof result.status === 'number' ? result.status : (result.error ? 1 : 0);
|
|
41
|
+
return { exitCode };
|
|
42
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
4
|
+
|
|
5
|
+
const commands = [
|
|
6
|
+
'beat-sync-beatmap',
|
|
7
|
+
'beat-sync-probe-frame',
|
|
8
|
+
'beat-sync-render-when-idle'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const usage = [
|
|
12
|
+
'使い方: akari internal <subcommand> [args...]',
|
|
13
|
+
'',
|
|
14
|
+
'サブコマンド:',
|
|
15
|
+
...commands.map((command) => ` ${command}`)
|
|
16
|
+
].join('\n');
|
|
17
|
+
|
|
18
|
+
export async function runInternalCommand(args, options = {}) {
|
|
19
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
20
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
21
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
22
|
+
const spawn = options.spawn ?? spawnSync;
|
|
23
|
+
const subcommand = args[0];
|
|
24
|
+
|
|
25
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
26
|
+
log(usage);
|
|
27
|
+
return { exitCode: 0 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const definitions = {
|
|
31
|
+
'beat-sync-beatmap': { path: assets.beatmapScript, node: true },
|
|
32
|
+
'beat-sync-probe-frame': { path: assets.probeFrameScript, node: true },
|
|
33
|
+
'beat-sync-render-when-idle': { path: assets.renderWhenIdleScript, node: false }
|
|
34
|
+
};
|
|
35
|
+
const definition = definitions[subcommand];
|
|
36
|
+
if (!definition) {
|
|
37
|
+
logError(`不明な internal サブコマンドです: ${subcommand}`);
|
|
38
|
+
log(usage);
|
|
39
|
+
return { exitCode: 1 };
|
|
40
|
+
}
|
|
41
|
+
if (!definition.path) {
|
|
42
|
+
logError(`内部コマンド ${subcommand} の実行スクリプトが見つかりません。AKARI Video の完全な checkout または配布物を確認してください。`);
|
|
43
|
+
return { exitCode: 1 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const forwardedArgs = args.slice(1);
|
|
47
|
+
const result = definition.node
|
|
48
|
+
? spawn(process.execPath, [definition.path, ...forwardedArgs], { stdio: 'inherit' })
|
|
49
|
+
: spawn(definition.path, forwardedArgs, { stdio: 'inherit' });
|
|
50
|
+
const exitCode = typeof result?.status === 'number' ? result.status : 1;
|
|
51
|
+
return { exitCode };
|
|
52
|
+
}
|
package/src/messages.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export function claudeMissingGuidance() {
|
|
|
48
48
|
export function opencodeMissingGuidance() {
|
|
49
49
|
return [
|
|
50
50
|
'opencode コマンドが見つかりませんでした。',
|
|
51
|
-
'opencode をインストールしてください: npm install -g opencode',
|
|
51
|
+
'opencode をインストールしてください: npm install -g opencode-ai',
|
|
52
52
|
'インストール後、このフォルダーで再度 `akari --opencode` を実行してください。'
|
|
53
53
|
].join('\n');
|
|
54
54
|
}
|
|
@@ -191,3 +191,8 @@ export function soundsFailedNotice() {
|
|
|
191
191
|
export function soundsUnavailableError() {
|
|
192
192
|
return '音源セットアップスクリプト(audio-library-setup)が見つかりませんでした。';
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** `akari assets`: 素材 resolver(asset-resolver)が同梱されていない場合のエラー(stderr 1 行)。 */
|
|
196
|
+
export function assetsResolverUnavailableError() {
|
|
197
|
+
return '素材 resolver(asset-resolver)が見つかりませんでした。`akari assets` は使えません。';
|
|
198
|
+
}
|
package/{vendor/skills/generate-narration/bin/generate-narration.mjs → src/narration-command.mjs}
RENAMED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
// 原稿テキストから VOICEVOX(ローカル)または fal Qwen3-TTS(自声クローン)でナレーション音声を
|
|
4
2
|
// 生成し、docs/contract-2026-07-20-edit-json-v1-narration.md 準拠のエントリを組み立てる。
|
|
5
3
|
|
|
@@ -17,12 +15,20 @@ const FAL_USD_PER_1000_CHARS = 0.09;
|
|
|
17
15
|
|
|
18
16
|
const usage = [
|
|
19
17
|
"使い方:",
|
|
20
|
-
"
|
|
18
|
+
" akari narration generate \\",
|
|
21
19
|
" --project <projectDir> --engine <voicevox|fal-qwen3> \\",
|
|
22
20
|
" --reading-file <読み原稿.txt> [--script-file <表示原稿.txt>] \\",
|
|
23
21
|
" --t <タイムライン秒> [--gain-db 0] [--id n-0001] \\",
|
|
24
22
|
" [--speaker 3] [--profile owner-ja] [--dry-run] [--yes] [--apply]",
|
|
25
23
|
].join("\n");
|
|
24
|
+
const commandUsage = [
|
|
25
|
+
"使い方: akari narration <subcommand> [options]",
|
|
26
|
+
"",
|
|
27
|
+
"サブコマンド:",
|
|
28
|
+
" generate 原稿からナレーション音声を生成する",
|
|
29
|
+
"",
|
|
30
|
+
usage,
|
|
31
|
+
].join("\n");
|
|
26
32
|
|
|
27
33
|
class PublicError extends Error {
|
|
28
34
|
constructor(message, exitCode = 1) {
|
|
@@ -31,8 +37,8 @@ class PublicError extends Error {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
function printJson(value) {
|
|
35
|
-
|
|
40
|
+
function printJson(value, log = (line) => console.log(line)) {
|
|
41
|
+
log(JSON.stringify(value, null, 2));
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
function sleep(ms) {
|
|
@@ -390,10 +396,10 @@ async function synthesizeVoicevox(readingText, speakerId) {
|
|
|
390
396
|
// --- edit.json への --apply ---
|
|
391
397
|
|
|
392
398
|
function validateEditScriptPath() {
|
|
393
|
-
return fileURLToPath(new URL("
|
|
399
|
+
return fileURLToPath(new URL("../../schemas/bin/validate-edit.mjs", import.meta.url));
|
|
394
400
|
}
|
|
395
401
|
|
|
396
|
-
function applyToEditJson(projectDir, entry) {
|
|
402
|
+
function applyToEditJson(projectDir, entry, io) {
|
|
397
403
|
const editPath = path.join(projectDir, "edit.json");
|
|
398
404
|
let originalText;
|
|
399
405
|
try {
|
|
@@ -422,7 +428,7 @@ function applyToEditJson(projectDir, entry) {
|
|
|
422
428
|
`validate-edit が NG のため edit.json への書き込みをロールバックしました。\n${detail}`,
|
|
423
429
|
);
|
|
424
430
|
}
|
|
425
|
-
|
|
431
|
+
io.logError(`edit.json に ${entry.id} を追加しました(validate-edit: PASS)。`);
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
// --- dry-run ---
|
|
@@ -432,7 +438,7 @@ function maskKey(secret) {
|
|
|
432
438
|
return secret ? "***configured***" : "***unconfigured***";
|
|
433
439
|
}
|
|
434
440
|
|
|
435
|
-
async function runDryRun(options, readingText) {
|
|
441
|
+
async function runDryRun(options, readingText, io) {
|
|
436
442
|
const outputPath = relativeOutputPath(options.id ?? computeNextId(options.project), options.engine);
|
|
437
443
|
if (options.engine === "voicevox") {
|
|
438
444
|
printJson({
|
|
@@ -449,7 +455,7 @@ async function runDryRun(options, readingText) {
|
|
|
449
455
|
speaker: Number(options.speaker),
|
|
450
456
|
text: readingText,
|
|
451
457
|
},
|
|
452
|
-
});
|
|
458
|
+
}, io.log);
|
|
453
459
|
return;
|
|
454
460
|
}
|
|
455
461
|
|
|
@@ -467,18 +473,18 @@ async function runDryRun(options, readingText) {
|
|
|
467
473
|
headers: { Authorization: `Key ${maskKey(falKey)}` },
|
|
468
474
|
body: payload,
|
|
469
475
|
},
|
|
470
|
-
});
|
|
476
|
+
}, io.log);
|
|
471
477
|
}
|
|
472
478
|
|
|
473
479
|
// --- generate 本体 ---
|
|
474
480
|
|
|
475
|
-
async function runGenerate(options) {
|
|
481
|
+
async function runGenerate(options, io) {
|
|
476
482
|
const readingText = readTextFile(options.readingFile, "読み原稿");
|
|
477
483
|
const scriptText = options.scriptFile ? readTextFile(options.scriptFile, "表示原稿") : null;
|
|
478
484
|
|
|
479
485
|
if (options.dryRun) {
|
|
480
|
-
await runDryRun(options, readingText);
|
|
481
|
-
return;
|
|
486
|
+
await runDryRun(options, readingText, io);
|
|
487
|
+
return 0;
|
|
482
488
|
}
|
|
483
489
|
|
|
484
490
|
const id = options.id ?? computeNextId(options.project);
|
|
@@ -514,18 +520,15 @@ async function runGenerate(options) {
|
|
|
514
520
|
const falKey = resolveFalKey();
|
|
515
521
|
const meta = readProfileMeta(options.profile);
|
|
516
522
|
const estimatedCostUsd = estimateFalCostUsd(readingText);
|
|
517
|
-
|
|
518
|
-
`fal-qwen3 推定費用: 約 $${estimatedCostUsd}(${readingText.length} 文字 × $${FAL_USD_PER_1000_CHARS}/1000字)\n`,
|
|
519
|
-
);
|
|
523
|
+
io.logError(`fal-qwen3 推定費用: 約 $${estimatedCostUsd}(${readingText.length} 文字 × $${FAL_USD_PER_1000_CHARS}/1000字)`);
|
|
520
524
|
if (!options.yes) {
|
|
521
525
|
printJson({
|
|
522
526
|
sent: false,
|
|
523
527
|
engine: "fal-qwen3",
|
|
524
528
|
estimated_cost_usd: estimatedCostUsd,
|
|
525
529
|
reason: "費用承認(--yes)がありません。実リクエストは送信していません。",
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
return;
|
|
530
|
+
}, io.log);
|
|
531
|
+
return 2;
|
|
529
532
|
}
|
|
530
533
|
// --yes が明示された場合のみ、ここで初めて課金の発生する fal API を呼び出す。
|
|
531
534
|
audioBuffer = await synthesizeFal(readingText, meta, falKey);
|
|
@@ -550,23 +553,37 @@ async function runGenerate(options) {
|
|
|
550
553
|
provenance,
|
|
551
554
|
};
|
|
552
555
|
|
|
553
|
-
if (options.apply) applyToEditJson(options.project, entry);
|
|
556
|
+
if (options.apply) applyToEditJson(options.project, entry, io);
|
|
554
557
|
|
|
555
|
-
printJson(entry);
|
|
558
|
+
printJson(entry, io.log);
|
|
559
|
+
return 0;
|
|
556
560
|
}
|
|
557
561
|
|
|
558
|
-
async function
|
|
559
|
-
|
|
562
|
+
export async function runNarrationCommand(args, commandOptions = {}) {
|
|
563
|
+
const io = {
|
|
564
|
+
log: commandOptions.log ?? ((line) => console.log(line)),
|
|
565
|
+
logError: commandOptions.logError ?? ((line) => console.error(line)),
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
569
|
+
io.log(commandUsage);
|
|
570
|
+
return { exitCode: 0 };
|
|
571
|
+
}
|
|
572
|
+
if (args[0] === 'generate' && (args.includes('--help') || args.includes('-h'))) {
|
|
573
|
+
io.log(usage);
|
|
574
|
+
return { exitCode: 0 };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let parsedOptions;
|
|
560
578
|
try {
|
|
561
|
-
|
|
562
|
-
await runGenerate(
|
|
579
|
+
parsedOptions = parseArguments(args);
|
|
580
|
+
const exitCode = await runGenerate(parsedOptions, io);
|
|
581
|
+
return { exitCode };
|
|
563
582
|
} catch (error) {
|
|
564
583
|
const exitCode = error instanceof PublicError ? error.exitCode : 1;
|
|
565
584
|
const message = error instanceof PublicError ? error.message : `内部処理に失敗しました: ${error?.message ?? error}`;
|
|
566
|
-
|
|
567
|
-
printJson({ error: message });
|
|
568
|
-
|
|
585
|
+
io.logError(message);
|
|
586
|
+
printJson({ error: message }, io.log);
|
|
587
|
+
return { exitCode };
|
|
569
588
|
}
|
|
570
589
|
}
|
|
571
|
-
|
|
572
|
-
await main();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
6
|
+
|
|
7
|
+
const usage = '使い方: akari new <target-dir> [--template <path>]';
|
|
8
|
+
|
|
9
|
+
function parseArguments(args) {
|
|
10
|
+
let target;
|
|
11
|
+
let template;
|
|
12
|
+
|
|
13
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
14
|
+
const argument = args[index];
|
|
15
|
+
if (argument === '--template') {
|
|
16
|
+
const value = args[index + 1];
|
|
17
|
+
if (!value || value.startsWith('--')) {
|
|
18
|
+
throw new Error(`${usage}\n--template にはパスが必要です。`);
|
|
19
|
+
}
|
|
20
|
+
template = value;
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (argument.startsWith('-')) {
|
|
23
|
+
throw new Error(`${usage}\n不明なオプションです: ${argument}`);
|
|
24
|
+
} else if (target === undefined) {
|
|
25
|
+
target = argument;
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(`${usage}\n作成先は 1 つだけ指定してください。`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (target === undefined) {
|
|
32
|
+
throw new Error(usage);
|
|
33
|
+
}
|
|
34
|
+
return { target, template };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function directoryHasFile(directory, relativeFile) {
|
|
38
|
+
if (!directory) return false;
|
|
39
|
+
try {
|
|
40
|
+
return (await fs.stat(path.join(directory, relativeFile))).isFile();
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runNewCommand(args, options = {}) {
|
|
47
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
48
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
49
|
+
const cwd = options.cwd ?? process.cwd();
|
|
50
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
51
|
+
|
|
52
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
53
|
+
log(usage);
|
|
54
|
+
return { exitCode: 0 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = parseArguments(args);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
62
|
+
return { exitCode: 1 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { target, template } = parsed;
|
|
66
|
+
const targetDir = path.resolve(cwd, target);
|
|
67
|
+
const templateDir = template ? path.resolve(cwd, template) : assets.templateDir;
|
|
68
|
+
|
|
69
|
+
if (!templateDir) {
|
|
70
|
+
logError('プロジェクト雛形が見つかりません。');
|
|
71
|
+
return { exitCode: 1 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const templateStat = await fs.stat(templateDir);
|
|
76
|
+
if (!templateStat.isDirectory()) {
|
|
77
|
+
throw new Error('not a directory');
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
logError(`雛形が見つかりません: ${templateDir}`);
|
|
81
|
+
return { exitCode: 1 };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const skillsSourceDir = assets.skillsSourceDir;
|
|
85
|
+
const hasSkills = await directoryHasFile(skillsSourceDir, 'analyze-footage/SKILL.md');
|
|
86
|
+
if (!hasSkills) {
|
|
87
|
+
logError(`スキル正本が見つからないため、スキル同梱をスキップします: ${skillsSourceDir ?? '未検出'}`);
|
|
88
|
+
}
|
|
89
|
+
const schemasSourceDir = assets.schemasSourceDir;
|
|
90
|
+
const hasSchemas = await directoryHasFile(schemasSourceDir, 'analysis.schema.json');
|
|
91
|
+
|
|
92
|
+
if (!assets.scaffoldModulePath && !options.createProject) {
|
|
93
|
+
logError('プロジェクト作成モジュールが見つかりません。');
|
|
94
|
+
return { exitCode: 1 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const createProject = options.createProject ?? await defaultCreateProject(assets.scaffoldModulePath);
|
|
99
|
+
const result = await createProject(targetDir, templateDir, hasSkills
|
|
100
|
+
? { skillsSourceDir, schemasSourceDir: hasSchemas ? schemasSourceDir : undefined }
|
|
101
|
+
: {});
|
|
102
|
+
log(`プロジェクトを作成しました: ${result.destination}`);
|
|
103
|
+
log(`コピー: ${result.copy.copiedFiles.length} 件`);
|
|
104
|
+
log(`フォールバック補完: ${result.fallback.writtenFiles.length} 件`);
|
|
105
|
+
log(`シンボリックリンクのスキップ: ${result.copy.skippedSymlinks.length} 件`);
|
|
106
|
+
log(`スキル同梱: ${hasSkills ? `実施(${skillsSourceDir})` : 'スキップ'}`);
|
|
107
|
+
log(`git: ${result.git.action}`);
|
|
108
|
+
log(`作成結果レポート: ${result.reportPath}`);
|
|
109
|
+
return { exitCode: 0, result };
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
112
|
+
return { exitCode: 1 };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function defaultCreateProject(scaffoldModulePath) {
|
|
117
|
+
const { createProject } = await import(pathToFileURL(scaffoldModulePath).href);
|
|
118
|
+
return createProject;
|
|
119
|
+
}
|
package/src/project-state.mjs
CHANGED
|
@@ -5,13 +5,22 @@ import path from 'node:path';
|
|
|
5
5
|
* カレントプロジェクトが AKARI Video プロジェクトとしてセットアップ済みかを判定する。
|
|
6
6
|
* `.akari/connections.json` の有無を「セットアップ済み」の判定基準にする
|
|
7
7
|
* (`create-project` / `project-scaffold` が必ずこのファイルを雛形から生成するため)。
|
|
8
|
+
*
|
|
9
|
+
* 例外: `.akari/root.json`(作業場マニフェスト)を持つディレクトリは作業場ルートであって
|
|
10
|
+
* プロジェクトではない。作業場層 `.akari/connections.json`(`packages/creator-root` の
|
|
11
|
+
* `createCreatorRoot()` が既定レジストリを書き出す)が存在しても、それはプロジェクトの
|
|
12
|
+
* セットアップ済みマーカーとして扱わない(さもないと作業場ルート自体が既存プロジェクトと
|
|
13
|
+
* 誤判定され、`(b) 作業場の中だがプロジェクトではない cwd` の新規プロジェクト作成分岐
|
|
14
|
+
* [`first-run.mjs`] が壊れる)。
|
|
8
15
|
*/
|
|
9
16
|
export function detectProjectState(projectRoot) {
|
|
10
17
|
const akariDir = path.join(projectRoot, '.akari');
|
|
11
18
|
const connectionsPath = path.join(akariDir, 'connections.json');
|
|
12
19
|
const intakePath = path.join(akariDir, 'intake.json');
|
|
20
|
+
const rootManifestPath = path.join(akariDir, 'root.json');
|
|
13
21
|
|
|
14
|
-
const
|
|
22
|
+
const isWorkspaceRoot = existsSync(rootManifestPath);
|
|
23
|
+
const scaffolded = !isWorkspaceRoot && existsSync(connectionsPath);
|
|
15
24
|
let intake = null;
|
|
16
25
|
if (existsSync(intakePath)) {
|
|
17
26
|
try {
|
package/src/repo-assets.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
5
|
// このファイルの場所(packages/akari-launcher/src/)から見て、モノレポの checkout
|
|
6
|
-
// なら 2
|
|
7
|
-
//
|
|
6
|
+
// なら 2 つ上がリポジトリルート。「スクリプト自身の位置からの相対解決」方式で、
|
|
7
|
+
// cwd には依存しない(cwd は
|
|
8
8
|
// スキャフォールド先のプロジェクトルートであり、リポ checkout の位置とは無関係)。
|
|
9
9
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
10
|
const DEFAULT_REPO_ROOT_CANDIDATE = path.resolve(PACKAGE_ROOT, '..', '..');
|
|
@@ -27,6 +27,13 @@ const CREATOR_ROOT_MODULE_RELATIVE = path.join('packages', 'creator-root', 'src'
|
|
|
27
27
|
// 公式音源ライブラリ(AKARI Sounds)の一括取得スクリプト。初回動線(sounds-setup.mjs)と
|
|
28
28
|
// `akari sounds` が子プロセスとして起動する。未同梱なら null(機能スキップ)。
|
|
29
29
|
const AUDIO_FETCH_SCRIPT_RELATIVE = path.join('packages', 'audio-library-setup', 'bin', 'fetch-akari-sounds.mjs');
|
|
30
|
+
// 素材 resolver(アカウントの素材 = 無料 + 購入済みの一覧・取得)の CLI 実体。
|
|
31
|
+
// `akari assets <list|fetch|sync|...>`(assets-command.mjs)が子プロセスとして起動する。
|
|
32
|
+
// 未同梱なら null(`akari assets` はその旨のエラーを返す。他コマンドは無影響)。
|
|
33
|
+
const ASSET_RESOLVER_CLI_RELATIVE = path.join('packages', 'asset-resolver', 'bin', 'akari-assets.mjs');
|
|
34
|
+
const BEATMAP_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'beatmap.mjs');
|
|
35
|
+
const PROBE_FRAME_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'probe-frame.mjs');
|
|
36
|
+
const RENDER_WHEN_IDLE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'render-when-idle.sh');
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
39
|
* 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold 実装・creator-root
|
|
@@ -40,6 +47,10 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
40
47
|
const scaffoldModulePath = path.join(repoRoot, SCAFFOLD_MODULE_RELATIVE);
|
|
41
48
|
const creatorRootModulePath = path.join(repoRoot, CREATOR_ROOT_MODULE_RELATIVE);
|
|
42
49
|
const audioFetchScriptPath = path.join(repoRoot, AUDIO_FETCH_SCRIPT_RELATIVE);
|
|
50
|
+
const assetResolverCliPath = path.join(repoRoot, ASSET_RESOLVER_CLI_RELATIVE);
|
|
51
|
+
const beatmapScript = path.join(repoRoot, BEATMAP_SCRIPT_RELATIVE);
|
|
52
|
+
const probeFrameScript = path.join(repoRoot, PROBE_FRAME_SCRIPT_RELATIVE);
|
|
53
|
+
const renderWhenIdleScript = path.join(repoRoot, RENDER_WHEN_IDLE_SCRIPT_RELATIVE);
|
|
43
54
|
|
|
44
55
|
return {
|
|
45
56
|
repoRoot,
|
|
@@ -49,7 +60,11 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
49
60
|
doctorScript: existsSync(doctorScript) ? doctorScript : null,
|
|
50
61
|
scaffoldModulePath: existsSync(scaffoldModulePath) ? scaffoldModulePath : null,
|
|
51
62
|
creatorRootModulePath: existsSync(creatorRootModulePath) ? creatorRootModulePath : null,
|
|
52
|
-
audioFetchScriptPath: existsSync(audioFetchScriptPath) ? audioFetchScriptPath : null
|
|
63
|
+
audioFetchScriptPath: existsSync(audioFetchScriptPath) ? audioFetchScriptPath : null,
|
|
64
|
+
assetResolverCliPath: existsSync(assetResolverCliPath) ? assetResolverCliPath : null,
|
|
65
|
+
beatmapScript: existsSync(beatmapScript) ? beatmapScript : null,
|
|
66
|
+
probeFrameScript: existsSync(probeFrameScript) ? probeFrameScript : null,
|
|
67
|
+
renderWhenIdleScript: existsSync(renderWhenIdleScript) ? renderWhenIdleScript : null
|
|
53
68
|
};
|
|
54
69
|
}
|
|
55
70
|
|
|
@@ -64,6 +79,7 @@ export function resolveLauncherAssets({
|
|
|
64
79
|
} = {}) {
|
|
65
80
|
const checkout = resolveRepoAssets(candidateRoot);
|
|
66
81
|
const found = checkout.skillsSourceDir || checkout.templateDir || checkout.schemasSourceDir
|
|
67
|
-
|| checkout.doctorScript || checkout.scaffoldModulePath || checkout.creatorRootModulePath
|
|
82
|
+
|| checkout.doctorScript || checkout.scaffoldModulePath || checkout.creatorRootModulePath
|
|
83
|
+
|| checkout.beatmapScript || checkout.probeFrameScript || checkout.renderWhenIdleScript;
|
|
68
84
|
return found ? checkout : resolveRepoAssets(vendorRoot);
|
|
69
85
|
}
|
package/src/status-command.mjs
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
resolveProjectStatus,
|
|
3
|
+
resolveFullProjectStatus,
|
|
4
|
+
serializeStatus,
|
|
5
|
+
formatStatusSummary,
|
|
6
|
+
detectStatusScope,
|
|
7
|
+
resolveWorkspaceStatus,
|
|
8
|
+
serializeWorkspaceStatus,
|
|
9
|
+
formatWorkspaceStatusSummary,
|
|
10
|
+
} from "./status-core/status.mjs";
|
|
2
11
|
|
|
3
12
|
export async function runStatusCommand(argv, options = {}) {
|
|
4
13
|
const log = options.log ?? ((line) => process.stdout.write(line));
|
|
@@ -10,6 +19,14 @@ export async function runStatusCommand(argv, options = {}) {
|
|
|
10
19
|
error(cause instanceof Error ? cause.message : String(cause));
|
|
11
20
|
return { exitCode: 2 };
|
|
12
21
|
}
|
|
22
|
+
if (detectStatusScope(parsed.projectRoot) === "workspace") {
|
|
23
|
+
const workspace = resolveWorkspaceStatus(parsed.projectRoot);
|
|
24
|
+
// fail-safe: a broken root.json falls through to the unchanged project-scope path below.
|
|
25
|
+
if (workspace) {
|
|
26
|
+
log(parsed.json ? serializeWorkspaceStatus(workspace) : `${formatWorkspaceStatusSummary(workspace)}\n`);
|
|
27
|
+
return { exitCode: 0, workspace };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
13
30
|
const status = parsed.full
|
|
14
31
|
? await resolveFullProjectStatus(parsed.projectRoot)
|
|
15
32
|
: resolveProjectStatus(parsed.projectRoot, { mode: "fast" });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// プロジェクトの人間向け表示名の解決(task 2026-08-09-project-display-title)。
|
|
5
|
+
// フォルダ名は作成時刻ベースの機械 ID のまま変えない(オーナー裁定: フォルダ名 = 機械の ID /
|
|
6
|
+
// 表示名 = 別に持つ)。人が読む名前は `.akari/intake.json` の `title` に置き、
|
|
7
|
+
// `akari status`(プロジェクトスコープ・作業場スコープ双方)はここを通す。
|
|
8
|
+
|
|
9
|
+
/** `.akari/intake.json` の title を読む。無い・壊れている・型違いは null(フェイルセーフ側)。 */
|
|
10
|
+
export function readProjectTitle(projectRoot) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(readFileSync(join(projectRoot, ".akari", "intake.json"), "utf8"));
|
|
13
|
+
return typeof parsed?.title === "string" && parsed.title.trim().length > 0 ? parsed.title : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** title ?? フォルダ名。title が無い(既存プロジェクト)・null・空文字はフォルダ名へフォールバックする。 */
|
|
20
|
+
export function resolveProjectDisplayName(title, folderName) {
|
|
21
|
+
return typeof title === "string" && title.trim().length > 0 ? title : folderName;
|
|
22
|
+
}
|
|
@@ -3,9 +3,17 @@ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "
|
|
|
3
3
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { readProjectEvents, resolveActiveAcceptance } from "./events.mjs";
|
|
6
|
+
import { resolveProjectDisplayName } from "./display-name.mjs";
|
|
6
7
|
import { inspectFullIntegrity } from "./integrity.mjs";
|
|
7
8
|
import { validateAndCountReview } from "./review.mjs";
|
|
8
9
|
|
|
10
|
+
export {
|
|
11
|
+
detectStatusScope,
|
|
12
|
+
resolveWorkspaceStatus,
|
|
13
|
+
serializeWorkspaceStatus,
|
|
14
|
+
formatWorkspaceStatusSummary,
|
|
15
|
+
} from "./workspace.mjs";
|
|
16
|
+
|
|
9
17
|
const MEDIA_EXTENSIONS = new Set([
|
|
10
18
|
".aac", ".aif", ".aiff", ".avi", ".bmp", ".flac", ".gif", ".jpeg", ".jpg",
|
|
11
19
|
".m4a", ".m4v", ".mkv", ".mov", ".mp3", ".mp4", ".ogg", ".png", ".wav",
|
|
@@ -105,7 +113,7 @@ export function resolveProjectStatus(input = process.cwd(), { mode = "fast" } =
|
|
|
105
113
|
return {
|
|
106
114
|
version: 1,
|
|
107
115
|
mode,
|
|
108
|
-
project: { name: projectName, scaffolded },
|
|
116
|
+
project: { name: projectName, scaffolded, display_name: resolveProjectDisplayName(intake?.title, projectName) },
|
|
109
117
|
workflow_stage: stage,
|
|
110
118
|
state_health: stateHealth,
|
|
111
119
|
waiting_on: routing.waiting_on,
|