akari-video 0.1.2 → 0.1.3
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/bin/akari.mjs +7 -3
- package/package.json +1 -1
- package/src/cli.mjs +22 -7
- package/src/first-run.mjs +217 -0
- package/src/init-command.mjs +135 -0
- package/src/messages.mjs +55 -0
- package/src/repo-assets.mjs +9 -4
- package/vendor/packages/creator-root/package.json +11 -0
- package/vendor/packages/creator-root/src/index.mjs +408 -0
- package/vendor/packages/creator-root/test/adopt-project.test.mjs +118 -0
- package/vendor/packages/creator-root/test/create-creator-root.test.mjs +79 -0
- package/vendor/packages/creator-root/test/default-root-path.test.mjs +29 -0
- package/vendor/packages/creator-root/test/helpers.mjs +13 -0
- package/vendor/packages/creator-root/test/machine-pointer.test.mjs +52 -0
- package/vendor/packages/creator-root/test/move-directory.test.mjs +60 -0
- package/vendor/packages/creator-root/test/resolve-creator-root.test.mjs +98 -0
- package/vendor/packages/creator-root/test/root-manifest.test.mjs +70 -0
- package/vendor/skills/edit-plan/beat-sync.md +14 -1
- package/vendor/skills/export-nle/SKILL.md +72 -0
- package/vendor/skills/overlay-authoring/3d.md +67 -15
- package/vendor/skills/setup-audio-library/SKILL.md +25 -29
- package/vendor/skills/setup-audio-library/assisted-fetch.md +38 -0
- package/vendor/skills/setup-audio-library/candidate-list.md +6 -6
- package/vendor/skills/setup-remote/SKILL.md +37 -0
- package/vendor/skills/setup-remote/bin/doctor.mjs +145 -0
- package/vendor/skills/setup-remote/doctor-and-install.md +42 -0
- package/vendor/skills/setup-remote/serve-preview.md +30 -0
- package/vendor/skills/setup-remote/taildrop-inbox.md +34 -0
package/bin/akari.mjs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { run, runUpdateCommand } from '../src/cli.mjs';
|
|
3
|
+
import { runInitCommand } from '../src/init-command.mjs';
|
|
3
4
|
|
|
4
5
|
const argv = process.argv.slice(2);
|
|
5
|
-
// `akari update` は claude
|
|
6
|
-
// (契約 §4-1
|
|
7
|
-
|
|
6
|
+
// `akari update` / `akari init` は claude へ転送せず、専用のサブコマンドとして扱う
|
|
7
|
+
// (契約 §4-1、および `akari init` はタスク契約 tasks/2026-08-02-launcher-init)。
|
|
8
|
+
// それ以外の引数はすべて従来どおり claude へ転送する。
|
|
9
|
+
const invoke = argv[0] === 'update' ? runUpdateCommand(argv.slice(1))
|
|
10
|
+
: argv[0] === 'init' ? runInitCommand(argv.slice(1))
|
|
11
|
+
: run(argv);
|
|
8
12
|
|
|
9
13
|
const result = await invoke.catch((error) => {
|
|
10
14
|
console.error(error instanceof Error ? error.message : String(error));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { detectProjectState } from './project-state.mjs';
|
|
|
7
7
|
import { findClaudeExecutable, findOpencodeExecutable } from './path-lookup.mjs';
|
|
8
8
|
import { loadTaskLabels } from './task-labels.mjs';
|
|
9
9
|
import { describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
|
|
10
|
+
import { resolveEffectiveProjectRoot } from './first-run.mjs';
|
|
10
11
|
import {
|
|
11
12
|
checkForUpdateSync,
|
|
12
13
|
readCacheSync,
|
|
@@ -19,13 +20,13 @@ import {
|
|
|
19
20
|
/**
|
|
20
21
|
* `akari` ランチャーの本体。3 入口契約(ターミナル `akari` / セッション内 `/akari` /
|
|
21
22
|
* アプリ接続ボタン)のうち、ターミナル入口を実装する:
|
|
22
|
-
* doctor(接続チェック)→
|
|
23
|
+
* 作業場(creator-root)の初回動線(`first-run.mjs`)→ doctor(接続チェック)→
|
|
24
|
+
* 未セットアップなら案内 + scaffold → 最後に `claude` を exec。
|
|
23
25
|
*
|
|
24
|
-
* すべての副作用(scaffold・doctor 実行・claude 起動・claude 探索)は
|
|
25
|
-
*
|
|
26
|
+
* すべての副作用(creator-root 解決・scaffold・doctor 実行・claude 起動・claude 探索)は
|
|
27
|
+
* options 経由で差し替え可能にしてあり、node --test から実プロセスを起動せずに分岐を検証できる。
|
|
26
28
|
*/
|
|
27
29
|
export async function run(args, options = {}) {
|
|
28
|
-
const projectRoot = options.projectRoot ?? process.cwd();
|
|
29
30
|
const log = options.log ?? ((line) => console.log(line));
|
|
30
31
|
const assets = options.assets ?? resolveLauncherAssets();
|
|
31
32
|
const scaffold = options.scaffold ?? defaultScaffold;
|
|
@@ -35,12 +36,26 @@ export async function run(args, options = {}) {
|
|
|
35
36
|
const spawnClaude = options.spawnClaude ?? defaultSpawnClaude;
|
|
36
37
|
const spawnOpencode = options.spawnOpencode ?? defaultSpawnOpencode;
|
|
37
38
|
const env = options.env ?? process.env;
|
|
39
|
+
const platform = options.platform ?? process.platform;
|
|
38
40
|
const currentVersion = options.currentVersion ?? readOwnVersion();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const now = options.now ?? new Date();
|
|
42
|
+
|
|
43
|
+
// --opencode / --claude / --claudecode / --yes / --here フラグを解析
|
|
41
44
|
const useOpencode = args.includes('--opencode');
|
|
42
45
|
const autoConfirm = args.includes('--yes') || args.includes('-y');
|
|
43
|
-
const
|
|
46
|
+
const hereOnly = args.includes('--here');
|
|
47
|
+
const filteredArgs = args.filter(arg =>
|
|
48
|
+
arg !== '--opencode' && arg !== '--claude' && arg !== '--claudecode'
|
|
49
|
+
&& arg !== '--yes' && arg !== '-y' && arg !== '--here'
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
let projectRoot = options.projectRoot ?? process.cwd();
|
|
53
|
+
|
|
54
|
+
// 作業場(creator-root)の初回動線(契約 §5・§6-1)。`--here` はお試しモード強制
|
|
55
|
+
// (現行動作)のため丸ごとスキップする(契約 §9・非 TTY と同じ現行動作互換の扱い)。
|
|
56
|
+
if (!hereOnly) {
|
|
57
|
+
projectRoot = await resolveEffectiveProjectRoot({ projectRoot, env, platform, now, log, assets, autoConfirm, options });
|
|
58
|
+
}
|
|
44
59
|
|
|
45
60
|
let state = detectProjectState(projectRoot);
|
|
46
61
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { detectProjectState } from './project-state.mjs';
|
|
7
|
+
import {
|
|
8
|
+
creatorRootFoundNotice,
|
|
9
|
+
creatorRootNewProjectNotice,
|
|
10
|
+
creatorRootCreatedNotice,
|
|
11
|
+
creatorRootCreateFailedNotice,
|
|
12
|
+
creatorRootPromptText
|
|
13
|
+
} from './messages.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `akari` の初回動線(契約 `docs/contract-2026-08-02-creator-root-v1.md` §5・§6-1)。
|
|
17
|
+
*
|
|
18
|
+
* `packages/creator-root` が「作業場操作の単一実装」を提供する pure Node ESM モジュールで、
|
|
19
|
+
* 本モジュールはそれを `repo-assets.mjs` の `scaffoldModulePath` と同型の流儀で動的 import し、
|
|
20
|
+
* cwd から作業場を解決 → (a)/(b)/(c) の分岐に従ってプロジェクトの展開先ディレクトリを決める
|
|
21
|
+
* 配線を担う。creator-root モジュールが解決できない場合(npm 配布で vendor 未同梱等)は、
|
|
22
|
+
* 「最後に claude を exec」の不変条件を守るため現行動作へ静かにフォールバックする。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// creator-root が未解決の場合の既定チャンネル名フォールバック
|
|
26
|
+
// (`packages/creator-root` の `DEFAULT_CHANNEL_NAME` と同値。値の重複は許容する —
|
|
27
|
+
// creator-root を静的 import できないため定数を直接参照できず、モジュールが解決できた
|
|
28
|
+
// ときは常に本物の export を優先して使う。ここに来るのはモジュール自体が読めない
|
|
29
|
+
// 極端なケースのみ)。
|
|
30
|
+
const DEFAULT_CHANNEL_NAME_FALLBACK = 'my-channel';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* creator-root モジュールを `assets.creatorRootModulePath` から動的 import する。
|
|
34
|
+
* `cli.mjs` の `defaultScaffold` が `scaffoldModulePath` を扱うのと同型。
|
|
35
|
+
* 見つからなければ null(呼び出し側は現行動作へフォールバックする)。
|
|
36
|
+
*/
|
|
37
|
+
export async function defaultLoadCreatorRootModule(assets) {
|
|
38
|
+
if (!assets?.creatorRootModulePath) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return import(pathToFileURL(assets.creatorRootModulePath).href);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `node:readline/promises` を使った既定の TTY プロンプト実装。 */
|
|
45
|
+
export async function defaultPrompt(promptText) {
|
|
46
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
47
|
+
try {
|
|
48
|
+
return await rl.question(promptText);
|
|
49
|
+
} finally {
|
|
50
|
+
rl.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `manifest.channels[0]` を既定チャンネルとして使う。配列が欠如・空の手作り root.json にも
|
|
56
|
+
* 備え、その場合は `fallback`(creator-root の `DEFAULT_CHANNEL_NAME`)へフォールバックする。
|
|
57
|
+
*/
|
|
58
|
+
function pickChannelName(manifest, fallback) {
|
|
59
|
+
return Array.isArray(manifest?.channels) && manifest.channels.length > 0
|
|
60
|
+
? manifest.channels[0]
|
|
61
|
+
: fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatDateSlug(date) {
|
|
65
|
+
const year = date.getFullYear();
|
|
66
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
67
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
68
|
+
return `${year}-${month}-${day}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `videosDir` 配下に日付ベースの一意なプロジェクトディレクトリパスを決める
|
|
73
|
+
* (`YYYY-MM-DD-video`、衝突時 `-2`, `-3`… 契約 §5 (b)/(c))。ディレクトリはまだ作らない
|
|
74
|
+
* (作成は既存の scaffold 経路に委ねる — 「孤児プロジェクトを作らない」を、新しい作成経路を
|
|
75
|
+
* 増やさず既存の唯一の scaffold 実装を再利用することで自然に満たす設計)。
|
|
76
|
+
*/
|
|
77
|
+
export function resolveUniqueProjectDir(videosDir, date) {
|
|
78
|
+
const baseName = `${formatDateSlug(date)}-video`;
|
|
79
|
+
let candidate = path.join(videosDir, baseName);
|
|
80
|
+
let suffix = 2;
|
|
81
|
+
while (existsSync(candidate)) {
|
|
82
|
+
candidate = path.join(videosDir, `${baseName}-${suffix}`);
|
|
83
|
+
suffix += 1;
|
|
84
|
+
}
|
|
85
|
+
return candidate;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 現在の `projectRoot` から、実際にプロジェクトを展開すべき先のディレクトリを決めて返す。
|
|
90
|
+
* 何もすることがなければ(既存プロジェクト内 / creator-root モジュール未解決 / お試しモード
|
|
91
|
+
* 選択時)`projectRoot` をそのまま返す。ここではディレクトリの作成もプロジェクトの scaffold
|
|
92
|
+
* も行わない。呼び出し側(`cli.mjs` の `run()`)が返り値を新しい `projectRoot` として、
|
|
93
|
+
* 既存の scaffold 経路にそのまま渡す。
|
|
94
|
+
*/
|
|
95
|
+
export async function resolveEffectiveProjectRoot({
|
|
96
|
+
projectRoot,
|
|
97
|
+
env,
|
|
98
|
+
platform,
|
|
99
|
+
now,
|
|
100
|
+
log,
|
|
101
|
+
assets,
|
|
102
|
+
autoConfirm,
|
|
103
|
+
options = {}
|
|
104
|
+
}) {
|
|
105
|
+
let creatorRootModule = null;
|
|
106
|
+
try {
|
|
107
|
+
creatorRootModule = await (options.loadCreatorRootModule ?? defaultLoadCreatorRootModule)(assets);
|
|
108
|
+
} catch {
|
|
109
|
+
// creator-root モジュールの読み込みに失敗 → 現行動作へフォールバック(不変条件)
|
|
110
|
+
creatorRootModule = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const resolveCreatorRootFn = options.resolveCreatorRoot ?? creatorRootModule?.resolveCreatorRoot;
|
|
114
|
+
const createCreatorRootFn = options.createCreatorRoot ?? creatorRootModule?.createCreatorRoot;
|
|
115
|
+
const updateMachinePointerFn = options.updateMachinePointer ?? creatorRootModule?.updateMachinePointer;
|
|
116
|
+
const defaultRootPathFn = options.defaultRootPath ?? creatorRootModule?.defaultRootPath;
|
|
117
|
+
const defaultChannelName = options.defaultChannelName ?? creatorRootModule?.DEFAULT_CHANNEL_NAME ?? DEFAULT_CHANNEL_NAME_FALLBACK;
|
|
118
|
+
|
|
119
|
+
if (!resolveCreatorRootFn) {
|
|
120
|
+
return projectRoot;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const localState = detectProjectState(projectRoot);
|
|
124
|
+
|
|
125
|
+
let resolved = null;
|
|
126
|
+
try {
|
|
127
|
+
resolved = await resolveCreatorRootFn({ cwd: projectRoot, env, platform });
|
|
128
|
+
} catch {
|
|
129
|
+
resolved = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (localState.scaffolded) {
|
|
133
|
+
// (a) 既存プロジェクト内 → 現行動作のまま。作業場内なら log に 1 行添える。
|
|
134
|
+
if (resolved && resolved.manifest) {
|
|
135
|
+
log(creatorRootFoundNotice(resolved.rootDir));
|
|
136
|
+
}
|
|
137
|
+
return projectRoot;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (resolved && resolved.error) {
|
|
141
|
+
// 祖先/マシンポインタに壊れた作業場(未知 schema・壊れた JSON 等)が見つかった場合、
|
|
142
|
+
// 新規に別の作業場を作りにいかず安全側(お試しモード = 現行動作)に倒す。
|
|
143
|
+
return projectRoot;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (resolved && resolved.manifest) {
|
|
147
|
+
// (b) 作業場の中だがプロジェクトではない → 既定チャンネルの videos/ へ新規プロジェクト
|
|
148
|
+
const channel = pickChannelName(resolved.manifest, defaultChannelName);
|
|
149
|
+
const videosDir = path.join(resolved.rootDir, 'channels', channel, 'videos');
|
|
150
|
+
const newProjectDir = resolveUniqueProjectDir(videosDir, now);
|
|
151
|
+
|
|
152
|
+
if (updateMachinePointerFn) {
|
|
153
|
+
try {
|
|
154
|
+
await updateMachinePointerFn(resolved.rootDir, env, { platform });
|
|
155
|
+
} catch {
|
|
156
|
+
// ポインタ更新の失敗でプロジェクト作成・claude 起動までは止めない
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
log(creatorRootNewProjectNotice(resolved.rootDir, newProjectDir));
|
|
161
|
+
return newProjectDir;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// (c) どちらでもない
|
|
165
|
+
if (!createCreatorRootFn || !defaultRootPathFn) {
|
|
166
|
+
return projectRoot;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const isTTY = options.isTTY ?? Boolean(process.stdin.isTTY);
|
|
170
|
+
|
|
171
|
+
let chosenRootPath = null;
|
|
172
|
+
if (autoConfirm) {
|
|
173
|
+
// --yes: 既定パスに自動作成
|
|
174
|
+
chosenRootPath = defaultRootPathFn(env, { platform });
|
|
175
|
+
} else if (isTTY) {
|
|
176
|
+
const promptFn = options.prompt ?? defaultPrompt;
|
|
177
|
+
const defaultPath = defaultRootPathFn(env, { platform });
|
|
178
|
+
const answerRaw = await promptFn(creatorRootPromptText(defaultPath));
|
|
179
|
+
const answer = typeof answerRaw === 'string' ? answerRaw.trim() : '';
|
|
180
|
+
if (answer.length === 0) {
|
|
181
|
+
chosenRootPath = defaultPath;
|
|
182
|
+
} else if (answer.toLowerCase() === 'n') {
|
|
183
|
+
chosenRootPath = null;
|
|
184
|
+
} else {
|
|
185
|
+
chosenRootPath = path.resolve(projectRoot, answer);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
// 非 TTY: 現行動作(自動化互換・契約 §9)
|
|
189
|
+
chosenRootPath = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!chosenRootPath) {
|
|
193
|
+
return projectRoot;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let createResult;
|
|
197
|
+
try {
|
|
198
|
+
createResult = await createCreatorRootFn(chosenRootPath);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
log(creatorRootCreateFailedNotice(error instanceof Error ? error.message : String(error)));
|
|
201
|
+
return projectRoot;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (updateMachinePointerFn) {
|
|
205
|
+
try {
|
|
206
|
+
await updateMachinePointerFn(createResult.rootDir, env, { platform });
|
|
207
|
+
} catch {
|
|
208
|
+
// ポインタ更新の失敗でプロジェクト作成・claude 起動までは止めない
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const channel = pickChannelName(createResult.manifest, defaultChannelName);
|
|
213
|
+
const videosDir = path.join(createResult.rootDir, 'channels', channel, 'videos');
|
|
214
|
+
const newProjectDir = resolveUniqueProjectDir(videosDir, now);
|
|
215
|
+
log(creatorRootCreatedNotice(createResult.rootDir, newProjectDir));
|
|
216
|
+
return newProjectDir;
|
|
217
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
4
|
+
import { defaultLoadCreatorRootModule } from './first-run.mjs';
|
|
5
|
+
import {
|
|
6
|
+
initFoundNotice,
|
|
7
|
+
initCreatedNotice,
|
|
8
|
+
initModuleMissingError,
|
|
9
|
+
initFailedError
|
|
10
|
+
} from './messages.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `akari init [path] [--channel <name>]` — 作業場(creator-root)の作成・確認だけを行う
|
|
14
|
+
* 純粋な入口サブコマンド(タスク契約 tasks/2026-08-02-launcher-init)。プロジェクト作成も
|
|
15
|
+
* claude 起動も一切行わない。シェルの相棒(パートナーエージェント)が headless で
|
|
16
|
+
* 安全に叩ける口として、plugin 配線(並列タスク B)がこのコマンドを呼ぶ前提で書かれている。
|
|
17
|
+
*
|
|
18
|
+
* `run()`(`first-run.mjs` 経由の初回動線)とは役割が違う: あちらは creator-root
|
|
19
|
+
* モジュールが解決できないとき「最後に claude を exec」という不変条件を守るため現行動作へ
|
|
20
|
+
* 静かにフォールバックするが、`init` にはフォールバック先そのものが無いため、
|
|
21
|
+
* モジュール未解決はそのままエラー(exit 1)として扱う。
|
|
22
|
+
*
|
|
23
|
+
* 解決した creator-root の各関数(`resolveCreatorRoot` / `createCreatorRoot` /
|
|
24
|
+
* `updateMachinePointer` / `defaultRootPath`)は options 経由で差し替え可能にしてあり、
|
|
25
|
+
* node --test から実 creator-root モジュールを使わずに分岐を検証できる。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
function parseInitArgs(args) {
|
|
29
|
+
let channelName;
|
|
30
|
+
const positional = [];
|
|
31
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
if (arg === '--channel') {
|
|
34
|
+
channelName = args[i + 1];
|
|
35
|
+
i += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (arg === '--yes' || arg === '-y') {
|
|
39
|
+
// 互換余地として受け取るが無視する(init は TTY プロンプトを持たないため無意味)。
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
positional.push(arg);
|
|
43
|
+
}
|
|
44
|
+
return { targetPath: positional[0], channelName };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function errorMessageOf(error) {
|
|
48
|
+
return error instanceof Error ? error.message : String(error);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function runInitCommand(args, options = {}) {
|
|
52
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
53
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
54
|
+
const env = options.env ?? process.env;
|
|
55
|
+
const platform = options.platform ?? process.platform;
|
|
56
|
+
const cwd = options.cwd ?? process.cwd();
|
|
57
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
58
|
+
|
|
59
|
+
const { targetPath: explicitPath, channelName } = parseInitArgs(args);
|
|
60
|
+
|
|
61
|
+
let creatorRootModule = null;
|
|
62
|
+
try {
|
|
63
|
+
creatorRootModule = await (options.loadCreatorRootModule ?? defaultLoadCreatorRootModule)(assets);
|
|
64
|
+
} catch {
|
|
65
|
+
creatorRootModule = null;
|
|
66
|
+
}
|
|
67
|
+
if (!creatorRootModule) {
|
|
68
|
+
logError(initModuleMissingError());
|
|
69
|
+
return { exitCode: 1 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolveCreatorRootFn = options.resolveCreatorRoot ?? creatorRootModule.resolveCreatorRoot;
|
|
73
|
+
const createCreatorRootFn = options.createCreatorRoot ?? creatorRootModule.createCreatorRoot;
|
|
74
|
+
const updateMachinePointerFn = options.updateMachinePointer ?? creatorRootModule.updateMachinePointer;
|
|
75
|
+
const defaultRootPathFn = options.defaultRootPath ?? creatorRootModule.defaultRootPath;
|
|
76
|
+
|
|
77
|
+
let targetDir;
|
|
78
|
+
|
|
79
|
+
if (explicitPath) {
|
|
80
|
+
// `akari init <path>`: 指定パスに作成(既に有効な作業場なら createCreatorRoot が
|
|
81
|
+
// no-op で既存 manifest を返す=冪等)。resolveCreatorRoot は経由しない。
|
|
82
|
+
targetDir = path.resolve(cwd, explicitPath);
|
|
83
|
+
} else {
|
|
84
|
+
// `akari init`(引数なし): resolveCreatorRoot(env → cwd 祖先 → マシンポインタ)で
|
|
85
|
+
// 既存の作業場が見つかれば ensure(何も作らない)。見つからなければ defaultRootPath に作成。
|
|
86
|
+
let resolved;
|
|
87
|
+
try {
|
|
88
|
+
resolved = await resolveCreatorRootFn({ cwd, env, platform });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logError(initFailedError(errorMessageOf(error)));
|
|
91
|
+
return { exitCode: 1 };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (resolved?.error) {
|
|
95
|
+
// 祖先/マシンポインタに壊れた作業場(未知 schema・壊れた JSON 等)が見つかった場合、
|
|
96
|
+
// 新規に別の作業場を作りにいかず、既存ファイルに一切触れずエラーで終了する。
|
|
97
|
+
logError(initFailedError(resolved.error.message));
|
|
98
|
+
return { exitCode: 1 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (resolved?.manifest) {
|
|
102
|
+
// ensure: 既存の作業場を確認しただけで何も作らない。
|
|
103
|
+
try {
|
|
104
|
+
await updateMachinePointerFn(resolved.rootDir, env, { platform });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logError(initFailedError(errorMessageOf(error)));
|
|
107
|
+
return { exitCode: 1 };
|
|
108
|
+
}
|
|
109
|
+
log(resolved.rootDir);
|
|
110
|
+
log(initFoundNotice(resolved.rootDir));
|
|
111
|
+
return { exitCode: 0 };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
targetDir = defaultRootPathFn(env, { platform });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let createResult;
|
|
118
|
+
try {
|
|
119
|
+
createResult = await createCreatorRootFn(targetDir, channelName ? { channelName } : {});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logError(initFailedError(errorMessageOf(error)));
|
|
122
|
+
return { exitCode: 1 };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await updateMachinePointerFn(createResult.rootDir, env, { platform });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logError(initFailedError(errorMessageOf(error)));
|
|
129
|
+
return { exitCode: 1 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
log(createResult.rootDir);
|
|
133
|
+
log(createResult.created ? initCreatedNotice(createResult.rootDir) : initFoundNotice(createResult.rootDir));
|
|
134
|
+
return { exitCode: 0 };
|
|
135
|
+
}
|
package/src/messages.mjs
CHANGED
|
@@ -73,6 +73,36 @@ export function describeVersionStatus(currentVersion, cache) {
|
|
|
73
73
|
return `バージョン: v${currentVersion}(更新フィード: 取得済み・${fetchedAt} 時点)`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* 初回動線(作業場・creator-root)関連の 1 行メッセージ群(契約
|
|
78
|
+
* `docs/contract-2026-08-02-creator-root-v1.md` §5)。既存の流儀に従い日本語・1 行主義。
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/** (a) 既存プロジェクトが作業場の中にある場合に添える 1 行。 */
|
|
82
|
+
export function creatorRootFoundNotice(rootDir) {
|
|
83
|
+
return `作業場: ${rootDir}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** (b) 作業場の中だがプロジェクトではない cwd から新規プロジェクトを作るときの 1 行。 */
|
|
87
|
+
export function creatorRootNewProjectNotice(rootDir, projectDir) {
|
|
88
|
+
return `作業場 ${rootDir} に新規プロジェクトを作成します: ${projectDir}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** (c) 作業場を新規作成してプロジェクトを作るときの 1 行。 */
|
|
92
|
+
export function creatorRootCreatedNotice(rootDir, projectDir) {
|
|
93
|
+
return `作業場を作成しました: ${rootDir}(新規プロジェクト: ${projectDir})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** (c) 作業場の作成でエラーが発生した場合の 1 行(このフォルダでの単体運用を続ける)。 */
|
|
97
|
+
export function creatorRootCreateFailedNotice(errorMessage) {
|
|
98
|
+
return `作業場の作成でエラーが発生しました(このフォルダでの単体運用を続けます): ${errorMessage}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** (c) の TTY プロンプト文言。既定パスを 1 行で提示する。 */
|
|
102
|
+
export function creatorRootPromptText(defaultPath) {
|
|
103
|
+
return `作業場を作って始めますか? [Enter: ${defaultPath} / パスを入力 / n: このフォルダだけで試す] `;
|
|
104
|
+
}
|
|
105
|
+
|
|
76
106
|
/**
|
|
77
107
|
* `akari update` の出力本文(複数行)。フィード未取得・最新・新版ありで案内が変わる。
|
|
78
108
|
* `dismissed` は今回の実行で dismiss 記録を書いたかどうか(表示文言の切り替えのみに使う)。
|
|
@@ -106,3 +136,28 @@ export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
|
|
|
106
136
|
);
|
|
107
137
|
return lines;
|
|
108
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `akari init` の出力文言(タスク契約 tasks/2026-08-02-launcher-init)。作業場
|
|
142
|
+
* (creator-root)の作成・確認だけを行う入口コマンド専用。1 行主義の既存流儀に従う。
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/** 既存の作業場が見つかり、何も作らず確認しただけの場合の人間向け行。 */
|
|
146
|
+
export function initFoundNotice(rootDir) {
|
|
147
|
+
return `既存の作業場を確認しました: ${rootDir}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** 新規に作業場を作成した場合の人間向け行。 */
|
|
151
|
+
export function initCreatedNotice(rootDir) {
|
|
152
|
+
return `作業場を作成しました: ${rootDir}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** creator-root モジュールが解決できない場合のエラー(stderr 1 行。init にフォールバック先は無い)。 */
|
|
156
|
+
export function initModuleMissingError() {
|
|
157
|
+
return '作業場モジュール(creator-root)が見つかりませんでした。';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 作業場の初期化に失敗した場合のエラー(root.json 破損・未知 schema・書き込み不能など。stderr 1 行)。 */
|
|
161
|
+
export function initFailedError(errorMessage) {
|
|
162
|
+
return `作業場の初期化に失敗しました: ${errorMessage}`;
|
|
163
|
+
}
|
package/src/repo-assets.mjs
CHANGED
|
@@ -21,10 +21,13 @@ const TEMPLATE_MARKER = path.join('templates', 'project-default', 'CLAUDE.md');
|
|
|
21
21
|
const SCHEMAS_MARKER = path.join('packages', 'schemas', 'analysis.schema.json');
|
|
22
22
|
const DOCTOR_SCRIPT_RELATIVE = path.join('skills', 'manage-connections', 'bin', 'doctor.mjs');
|
|
23
23
|
const SCAFFOLD_MODULE_RELATIVE = path.join('packages', 'project-scaffold', 'src', 'index.mjs');
|
|
24
|
+
// 作業場(creator-root)モジュール。① Wave(packages/creator-root)の成果物で、本パッケージ
|
|
25
|
+
// からは読み取り専用(動的 import のみ)。scaffoldModulePath と同型の解決方式。
|
|
26
|
+
const CREATOR_ROOT_MODULE_RELATIVE = path.join('packages', 'creator-root', 'src', 'index.mjs');
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
|
-
* 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold
|
|
27
|
-
*
|
|
29
|
+
* 指定ルート配下に同梱されているスキル正本・雛形・schemas・scaffold 実装・creator-root
|
|
30
|
+
* 実装を探す。見つからないフィールドは null になり、呼び出し側はそれに応じて機能をスキップする。
|
|
28
31
|
*/
|
|
29
32
|
export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
30
33
|
const hasSkills = existsSync(path.join(repoRoot, SKILLS_MARKER));
|
|
@@ -32,6 +35,7 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
32
35
|
const hasSchemas = existsSync(path.join(repoRoot, SCHEMAS_MARKER));
|
|
33
36
|
const doctorScript = path.join(repoRoot, DOCTOR_SCRIPT_RELATIVE);
|
|
34
37
|
const scaffoldModulePath = path.join(repoRoot, SCAFFOLD_MODULE_RELATIVE);
|
|
38
|
+
const creatorRootModulePath = path.join(repoRoot, CREATOR_ROOT_MODULE_RELATIVE);
|
|
35
39
|
|
|
36
40
|
return {
|
|
37
41
|
repoRoot,
|
|
@@ -39,7 +43,8 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
39
43
|
templateDir: hasTemplate ? path.join(repoRoot, 'templates', 'project-default') : null,
|
|
40
44
|
schemasSourceDir: hasSchemas ? path.join(repoRoot, 'packages', 'schemas') : null,
|
|
41
45
|
doctorScript: existsSync(doctorScript) ? doctorScript : null,
|
|
42
|
-
scaffoldModulePath: existsSync(scaffoldModulePath) ? scaffoldModulePath : null
|
|
46
|
+
scaffoldModulePath: existsSync(scaffoldModulePath) ? scaffoldModulePath : null,
|
|
47
|
+
creatorRootModulePath: existsSync(creatorRootModulePath) ? creatorRootModulePath : null
|
|
43
48
|
};
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -54,6 +59,6 @@ export function resolveLauncherAssets({
|
|
|
54
59
|
} = {}) {
|
|
55
60
|
const checkout = resolveRepoAssets(candidateRoot);
|
|
56
61
|
const found = checkout.skillsSourceDir || checkout.templateDir || checkout.schemasSourceDir
|
|
57
|
-
|| checkout.doctorScript || checkout.scaffoldModulePath;
|
|
62
|
+
|| checkout.doctorScript || checkout.scaffoldModulePath || checkout.creatorRootModulePath;
|
|
58
63
|
return found ? checkout : resolveRepoAssets(vendorRoot);
|
|
59
64
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akari-video/creator-root",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "AKARI Video の作業場(CreatorRoot)を解決・生成・養子縁組する Electron 非依存・pure Node ESM・外部 npm 依存ゼロの共有モジュール。",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "src/index.mjs",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node --test \"test/*.mjs\""
|
|
10
|
+
}
|
|
11
|
+
}
|