akari-video 0.1.68 → 0.1.69
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/package.json +1 -1
- package/vendor/docs/contract-2026-09-13-extension-kit-v0.md +4 -0
- package/vendor/docs/contract-2026-09-13-world-map-v0.md +4 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/asset-resolver/src/state.mjs +32 -1
- package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +36 -0
- package/vendor/packages/edit-store/lib/generation-meta.d.ts +13 -0
- package/vendor/packages/edit-store/lib/generation-meta.js +35 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
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": {
|
|
@@ -70,6 +70,10 @@ claude plugin install akari-kits@akari-kits
|
|
|
70
70
|
|
|
71
71
|
Codex、Cursor、opencode では、プロジェクトの `.agents/.codex/.cursor/.opencode/skills` へキットスキルも合成する。同名があれば純正スキルを優先する。plugin が利用できない環境でも `~/.akari/kits/plugin/skills/<name>/SKILL.md` を直接読める。
|
|
72
72
|
|
|
73
|
+
## 5. アプリ(ホームの拡張キットカード)
|
|
74
|
+
|
|
75
|
+
ホームの AKARI Store カードの隣に拡張キットカードを 1 枚出し、未接続では出さず、導入済みは id・version・スキル名・素材数の一覧と未有効化時の有効化案内、購入済み・未導入は `akari store install <id>` の案内、未購入は教材の引換ページの案内、という 3 状態とする(アプリはコマンドを実行せず、コピーと外部ブラウザ起動だけを行う)。
|
|
76
|
+
|
|
73
77
|
## 6. 更新と版
|
|
74
78
|
|
|
75
79
|
キットの版は Store の整数版とする。`akari store status` が導入済みの id、version、スキル名、素材数を表示し、同じ `akari store install` で新版へ置換する。
|
|
@@ -39,6 +39,10 @@ spatial world は three 断片で構成し、座標・床・背景・霧を宣
|
|
|
39
39
|
|
|
40
40
|
## 5. 地図 UI
|
|
41
41
|
|
|
42
|
+
- 実装のマーカー判定は `akari-shell-strip` の ContextKey `akari.worldMap` に一元化する。
|
|
43
|
+
- main の「地図」タブは `akari-world-view` が担う。
|
|
44
|
+
- タイムラインのワールド帯と地図インスペクターは `akari-annotations` が担う。
|
|
45
|
+
|
|
42
46
|
地図 UI は world-map を読み取り専用で表示する。2D 俯瞰、ワールド帯、再生時刻に追従する撮影枠、選択中の stop / edge 詳細を提供し、データの編集機能は持たない。
|
|
43
47
|
|
|
44
48
|
## 6. 制作フロー
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -2,10 +2,37 @@
|
|
|
2
2
|
// 「このアカウントで使える素材 = 無料全部 + 購入済み」の 1 ビュー(設計契約 §8)の核。
|
|
3
3
|
|
|
4
4
|
import { loadCatalog } from './catalog.mjs';
|
|
5
|
-
import { resolveAkariHome, resolveEffectiveBase } from './env.mjs';
|
|
5
|
+
import { resolveAkariHome, resolveEffectiveBase, resolveEntitlementsUrl } from './env.mjs';
|
|
6
6
|
import { fetchEntitlements, readStoreCredentials } from './entitlements.mjs';
|
|
7
7
|
import { scanLocalLibrary } from './library.mjs';
|
|
8
8
|
|
|
9
|
+
async function fetchEntitledProducts({ env, fetchImpl }) {
|
|
10
|
+
const credentials = await readStoreCredentials(env);
|
|
11
|
+
if (!credentials) return [];
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetchImpl(resolveEntitlementsUrl(env, credentials), {
|
|
14
|
+
headers: { authorization: `Bearer ${credentials.token}` },
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) return [];
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
if (!Array.isArray(data?.entitlements)) return [];
|
|
19
|
+
return data.entitlements.flatMap((entry) => {
|
|
20
|
+
if (typeof entry === 'string') {
|
|
21
|
+
return entry ? [{ id: entry, kind: null, currentVersion: null }] : [];
|
|
22
|
+
}
|
|
23
|
+
const id = entry?.product_id ?? entry?.id;
|
|
24
|
+
if (typeof id !== 'string' || !id) return [];
|
|
25
|
+
return [{
|
|
26
|
+
id,
|
|
27
|
+
kind: typeof entry.kind === 'string' ? entry.kind : null,
|
|
28
|
+
currentVersion: Number.isFinite(entry.current_version) ? entry.current_version : null,
|
|
29
|
+
}];
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
9
36
|
/**
|
|
10
37
|
* @returns {Promise<{ home: string, base: string, catalogVersion: string|null, entitlementsStatus: 'ok'|'no_credentials'|'unauthorized'|'error', items: Array }>}
|
|
11
38
|
* items の各要素はカタログ項目に `state`('cached' | 'available' | 'locked')を足したもの。
|
|
@@ -22,6 +49,9 @@ export async function composeState({ env = process.env, fetchImpl = fetch } = {}
|
|
|
22
49
|
const entitlementsResult = hasPaidItems
|
|
23
50
|
? await fetchEntitlements({ env, fetchImpl })
|
|
24
51
|
: { ids: new Set(), status: await readStoreCredentials(env) ? 'ok' : 'no_credentials' };
|
|
52
|
+
// entitlements.mjs は id/status だけを返し編集できないため、商品 kind/version は
|
|
53
|
+
// 同じ API への 2 回目の fail-soft 取得で補う。
|
|
54
|
+
const entitledProducts = await fetchEntitledProducts({ env, fetchImpl });
|
|
25
55
|
|
|
26
56
|
const items = catalog.items.map((item) => {
|
|
27
57
|
const key = `${item.category}/${item.id}`;
|
|
@@ -38,6 +68,7 @@ export async function composeState({ env = process.env, fetchImpl = fetch } = {}
|
|
|
38
68
|
base,
|
|
39
69
|
catalogVersion: catalog.version ?? null,
|
|
40
70
|
entitlementsStatus: entitlementsResult.status,
|
|
71
|
+
entitledProducts,
|
|
41
72
|
items,
|
|
42
73
|
};
|
|
43
74
|
}
|
|
@@ -50,3 +50,39 @@ test('composeState: ローカルに実体があるものは cached になる', a
|
|
|
50
50
|
const free = items.find((i) => i.id === 'mini-still');
|
|
51
51
|
assert.equal(free.state, 'cached');
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
test('composeState: entitledProducts に kit 商品を載せ、資格情報なしでは空配列にする', async () => {
|
|
55
|
+
const connected = setupFixtureEnv();
|
|
56
|
+
writeFileSync(
|
|
57
|
+
path.join(connected.home, 'store-credentials.json'),
|
|
58
|
+
`${JSON.stringify({ url: 'https://example.invalid/api/store', token: 'akst_test' }, null, 2)}\n`,
|
|
59
|
+
);
|
|
60
|
+
let fetchCount = 0;
|
|
61
|
+
const state = await composeState({
|
|
62
|
+
env: connected.env,
|
|
63
|
+
fetchImpl: async () => {
|
|
64
|
+
fetchCount += 1;
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
status: 200,
|
|
68
|
+
json: async () => ({
|
|
69
|
+
entitlements: [{ product_id: 'world-kit', kind: 'kit', current_version: 3 }],
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
assert.deepEqual(state.entitledProducts, [{ id: 'world-kit', kind: 'kit', currentVersion: 3 }]);
|
|
75
|
+
assert.equal(fetchCount, 2);
|
|
76
|
+
|
|
77
|
+
const disconnected = setupFixtureEnv();
|
|
78
|
+
let disconnectedFetchCount = 0;
|
|
79
|
+
const disconnectedState = await composeState({
|
|
80
|
+
env: disconnected.env,
|
|
81
|
+
fetchImpl: async () => {
|
|
82
|
+
disconnectedFetchCount += 1;
|
|
83
|
+
throw new Error('資格情報なしでは呼ばれない');
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
assert.deepEqual(disconnectedState.entitledProducts, []);
|
|
87
|
+
assert.equal(disconnectedFetchCount, 0);
|
|
88
|
+
});
|
|
@@ -11,6 +11,8 @@ export interface GenerationMetaV1 {
|
|
|
11
11
|
inputs?: {
|
|
12
12
|
first_frame?: {
|
|
13
13
|
sha256?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
source_id?: string | null;
|
|
14
16
|
} | null;
|
|
15
17
|
[key: string]: unknown;
|
|
16
18
|
};
|
|
@@ -47,3 +49,14 @@ export declare function bindingShaFor(meta: GenerationMetaV1 | null | undefined)
|
|
|
47
49
|
* `job.stale_after_s` が未指定・不正な場合は既定 900 秒を使う。
|
|
48
50
|
*/
|
|
49
51
|
export declare function resolveGenerationState(meta: GenerationMetaV1 | null | undefined, now: Date | string | number): GenerationState;
|
|
52
|
+
/**
|
|
53
|
+
* item が現在指している素材へ、生成物側の video サイドカーを逆引きする。
|
|
54
|
+
* 選択だけを行い、各 surface 固有の orphan / 契約外 status の解決は呼び出し側へ残す。
|
|
55
|
+
*/
|
|
56
|
+
export declare function selectGenerationSidecarForSource<T extends {
|
|
57
|
+
sourcePath: string;
|
|
58
|
+
meta?: GenerationMetaV1 | null;
|
|
59
|
+
binding?: {
|
|
60
|
+
matches?: boolean;
|
|
61
|
+
} | null;
|
|
62
|
+
}>(sourcePath: string | undefined, entries: readonly T[], now: Date | string | number): T | undefined;
|
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.sidecarPathFor = sidecarPathFor;
|
|
9
9
|
exports.bindingShaFor = bindingShaFor;
|
|
10
10
|
exports.resolveGenerationState = resolveGenerationState;
|
|
11
|
+
exports.selectGenerationSidecarForSource = selectGenerationSidecarForSource;
|
|
11
12
|
function sidecarPathFor(sourcePath) {
|
|
12
13
|
return `${sourcePath}.meta.json`;
|
|
13
14
|
}
|
|
@@ -15,8 +16,7 @@ function bindingShaFor(meta) {
|
|
|
15
16
|
if (meta?.status === 'done' && typeof meta.result?.sha256 === 'string') {
|
|
16
17
|
return { sha256: meta.result.sha256, source: 'result' };
|
|
17
18
|
}
|
|
18
|
-
if (
|
|
19
|
-
&& typeof meta.inputs?.first_frame?.sha256 === 'string') {
|
|
19
|
+
if (typeof meta?.inputs?.first_frame?.sha256 === 'string') {
|
|
20
20
|
return { sha256: meta.inputs.first_frame.sha256, source: 'first_frame' };
|
|
21
21
|
}
|
|
22
22
|
return null;
|
|
@@ -45,3 +45,36 @@ function resolveGenerationState(meta, now) {
|
|
|
45
45
|
}
|
|
46
46
|
return meta.status;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* item が現在指している素材へ、生成物側の video サイドカーを逆引きする。
|
|
50
|
+
* 選択だけを行い、各 surface 固有の orphan / 契約外 status の解決は呼び出し側へ残す。
|
|
51
|
+
*/
|
|
52
|
+
function selectGenerationSidecarForSource(sourcePath, entries, now) {
|
|
53
|
+
if (typeof sourcePath !== 'string')
|
|
54
|
+
return undefined;
|
|
55
|
+
const normalizePath = (value) => value.trim().replace(/\\/gu, '/').replace(/^(?:\.\/)+/u, '');
|
|
56
|
+
const normalizedSourcePath = normalizePath(sourcePath);
|
|
57
|
+
const direct = entries.find(entry => normalizePath(entry.sourcePath) === normalizedSourcePath);
|
|
58
|
+
if (direct?.meta?.kind === 'video')
|
|
59
|
+
return direct;
|
|
60
|
+
let selected;
|
|
61
|
+
let selectedStartedAt = Number.NEGATIVE_INFINITY;
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const meta = entry.meta;
|
|
64
|
+
if (meta?.kind !== 'video' || entry.binding?.matches === false)
|
|
65
|
+
continue;
|
|
66
|
+
const firstFramePath = meta.inputs?.first_frame?.path;
|
|
67
|
+
if (typeof firstFramePath !== 'string' || normalizePath(firstFramePath) !== normalizedSourcePath)
|
|
68
|
+
continue;
|
|
69
|
+
const state = resolveGenerationState(meta, now);
|
|
70
|
+
if (state !== 'generating' && state !== 'stale' && state !== 'failed')
|
|
71
|
+
continue;
|
|
72
|
+
const startedAt = Date.parse(String(meta.job?.started_at ?? ''));
|
|
73
|
+
const sortableStartedAt = Number.isFinite(startedAt) ? startedAt : Number.NEGATIVE_INFINITY;
|
|
74
|
+
if (!selected || sortableStartedAt > selectedStartedAt) {
|
|
75
|
+
selected = entry;
|
|
76
|
+
selectedStartedAt = sortableStartedAt;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return selected ?? direct;
|
|
80
|
+
}
|