@ectplsm/relic 0.5.0 → 0.5.1
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 +2 -0
- package/dist/adapters/local/local-engram-repository.d.ts +3 -1
- package/dist/adapters/local/local-engram-repository.js +25 -0
- package/dist/adapters/mikoshi/client.d.ts +3 -1
- package/dist/adapters/mikoshi/client.js +33 -3
- package/dist/core/entities/engram.d.ts +28 -0
- package/dist/core/entities/engram.js +4 -0
- package/dist/core/ports/engram-repository.d.ts +17 -1
- package/dist/core/ports/mikoshi.d.ts +24 -0
- package/dist/core/ports/mikoshi.js +17 -0
- package/dist/core/sync/avatar.d.ts +121 -0
- package/dist/core/sync/avatar.js +352 -0
- package/dist/core/usecases/mikoshi-download.d.ts +5 -0
- package/dist/core/usecases/mikoshi-download.js +17 -1
- package/dist/core/usecases/mikoshi-pull.d.ts +26 -0
- package/dist/core/usecases/mikoshi-pull.js +47 -3
- package/dist/core/usecases/mikoshi-push.d.ts +129 -2
- package/dist/core/usecases/mikoshi-push.js +316 -11
- package/dist/interfaces/cli/commands/mikoshi.js +150 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -188,6 +188,8 @@ For LLM-assisted creation, persona authoring, template examples, and deletion ru
|
|
|
188
188
|
|
|
189
189
|
Relic can push plaintext persona files and (end-to-end) encrypted memory files to [Mikoshi](https://mikoshi.ectplsm.com), so you can keep Engrams in the cloud and move them across machines without turning Mikoshi into your authoring source of truth.
|
|
190
190
|
|
|
191
|
+
This includes avatar-aware persona push/pull: local image paths and external `https://` avatar URLs can be snapshotted into Mikoshi-managed storage during `relic mikoshi push`.
|
|
192
|
+
|
|
191
193
|
For setup, API key configuration, persona push/pull, encrypted memory sync, and the recommended command flow, see [docs/mikoshi.md](docs/mikoshi.md).
|
|
192
194
|
|
|
193
195
|
## Configuration
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Engram, EngramMeta } from "../../core/entities/engram.js";
|
|
1
|
+
import type { Engram, EngramManifest, EngramMeta } from "../../core/entities/engram.js";
|
|
2
2
|
import type { EngramRepository } from "../../core/ports/engram-repository.js";
|
|
3
3
|
/**
|
|
4
4
|
* LocalEngramRepository — ローカルファイルシステム上の
|
|
@@ -24,6 +24,8 @@ export declare class LocalEngramRepository implements EngramRepository {
|
|
|
24
24
|
get(id: string): Promise<Engram | null>;
|
|
25
25
|
save(engram: Engram): Promise<void>;
|
|
26
26
|
delete(id: string): Promise<void>;
|
|
27
|
+
getEngramPath(id: string): string;
|
|
28
|
+
updateManifest(id: string, manifest: EngramManifest): Promise<void>;
|
|
27
29
|
copyArchiveFiles(fromId: string, toId: string): Promise<boolean>;
|
|
28
30
|
private readMeta;
|
|
29
31
|
private toProfile;
|
|
@@ -91,6 +91,27 @@ export class LocalEngramRepository {
|
|
|
91
91
|
await rm(engramDir, { recursive: true });
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
getEngramPath(id) {
|
|
95
|
+
return join(this.basePath, id);
|
|
96
|
+
}
|
|
97
|
+
async updateManifest(id, manifest) {
|
|
98
|
+
const engramDir = join(this.basePath, id);
|
|
99
|
+
if (!existsSync(engramDir))
|
|
100
|
+
return;
|
|
101
|
+
const manifestPath = join(engramDir, MANIFEST_FILE);
|
|
102
|
+
const payload = {
|
|
103
|
+
id: manifest.id,
|
|
104
|
+
createdAt: manifest.createdAt,
|
|
105
|
+
updatedAt: manifest.updatedAt,
|
|
106
|
+
...(manifest.avatarHash !== undefined
|
|
107
|
+
? { avatarHash: manifest.avatarHash }
|
|
108
|
+
: {}),
|
|
109
|
+
...(manifest.avatarSourceUrl !== undefined
|
|
110
|
+
? { avatarSourceUrl: manifest.avatarSourceUrl }
|
|
111
|
+
: {}),
|
|
112
|
+
};
|
|
113
|
+
await writeFile(manifestPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
114
|
+
}
|
|
94
115
|
async copyArchiveFiles(fromId, toId) {
|
|
95
116
|
const ARCHIVE_FILES = ["archive.md", "archive.cursor"];
|
|
96
117
|
const fromDir = join(this.basePath, fromId);
|
|
@@ -148,6 +169,10 @@ export class LocalEngramRepository {
|
|
|
148
169
|
id: meta.id,
|
|
149
170
|
createdAt: meta.createdAt,
|
|
150
171
|
updatedAt: meta.updatedAt,
|
|
172
|
+
...(meta.avatarHash !== undefined ? { avatarHash: meta.avatarHash } : {}),
|
|
173
|
+
...(meta.avatarSourceUrl !== undefined
|
|
174
|
+
? { avatarSourceUrl: meta.avatarSourceUrl }
|
|
175
|
+
: {}),
|
|
151
176
|
};
|
|
152
177
|
}
|
|
153
178
|
async writeMetaFiles(engramDir, meta) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MikoshiClient, type MikoshiEngram, type MikoshiEngramDetail, type CreateEngramInput, type CreateEngramResponse, type SyncStatusResponse, type UpdatePersonaInput, type UpdatePersonaResponse, type UploadMemoryInput, type UploadMemoryResponse, type DownloadMemoryResponse } from "../../core/ports/mikoshi.js";
|
|
1
|
+
import { type MikoshiClient, type MikoshiEngram, type MikoshiEngramDetail, type CreateEngramInput, type CreateEngramResponse, type SyncStatusResponse, type UpdatePersonaInput, type UpdatePersonaResponse, type UploadMemoryInput, type UploadMemoryResponse, type DownloadMemoryResponse, type UploadEngramAvatarResponse, type DeleteEngramAvatarResponse } from "../../core/ports/mikoshi.js";
|
|
2
2
|
/**
|
|
3
3
|
* MikoshiApiClient — Mikoshi REST API v1 の adapter 実装
|
|
4
4
|
*
|
|
@@ -17,5 +17,7 @@ export declare class MikoshiApiClient implements MikoshiClient {
|
|
|
17
17
|
updatePersona(engramId: string, input: UpdatePersonaInput): Promise<UpdatePersonaResponse>;
|
|
18
18
|
uploadMemory(engramId: string, input: UploadMemoryInput): Promise<UploadMemoryResponse>;
|
|
19
19
|
downloadMemory(engramId: string): Promise<DownloadMemoryResponse>;
|
|
20
|
+
uploadEngramAvatar(engramId: string, data: Buffer, mimeType: string): Promise<UploadEngramAvatarResponse>;
|
|
21
|
+
deleteEngramAvatar(engramId: string): Promise<DeleteEngramAvatarResponse>;
|
|
20
22
|
private request;
|
|
21
23
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MikoshiApiError, MikoshiEngramSchema, MikoshiEngramDetailSchema, CreateEngramResponseSchema, SyncStatusResponseSchema, UpdatePersonaResponseSchema, UploadMemoryResponseSchema, DownloadMemoryResponseSchema, } from "../../core/ports/mikoshi.js";
|
|
1
|
+
import { MikoshiApiError, MikoshiEngramSchema, MikoshiEngramDetailSchema, CreateEngramResponseSchema, SyncStatusResponseSchema, UpdatePersonaResponseSchema, UploadMemoryResponseSchema, DownloadMemoryResponseSchema, UploadEngramAvatarResponseSchema, DeleteEngramAvatarResponseSchema, } from "../../core/ports/mikoshi.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
/**
|
|
4
4
|
* MikoshiApiClient — Mikoshi REST API v1 の adapter 実装
|
|
@@ -49,6 +49,18 @@ export class MikoshiApiClient {
|
|
|
49
49
|
const data = await this.request("GET", `/api/v1/engrams/${enc(engramId)}/memory`);
|
|
50
50
|
return DownloadMemoryResponseSchema.parse(data);
|
|
51
51
|
}
|
|
52
|
+
async uploadEngramAvatar(engramId, data, mimeType) {
|
|
53
|
+
const form = new FormData();
|
|
54
|
+
// Blob は Uint8Array を受け付ける (Node 18+ 標準)
|
|
55
|
+
const blob = new Blob([new Uint8Array(data)], { type: mimeType });
|
|
56
|
+
form.append("file", blob, filenameForMimeType(mimeType));
|
|
57
|
+
const raw = await this.request("PUT", `/api/v1/engrams/${enc(engramId)}/avatar`, form);
|
|
58
|
+
return UploadEngramAvatarResponseSchema.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
async deleteEngramAvatar(engramId) {
|
|
61
|
+
const raw = await this.request("DELETE", `/api/v1/engrams/${enc(engramId)}/avatar`);
|
|
62
|
+
return DeleteEngramAvatarResponseSchema.parse(raw);
|
|
63
|
+
}
|
|
52
64
|
// -----------------------------------------------------------------------
|
|
53
65
|
// Internal
|
|
54
66
|
// -----------------------------------------------------------------------
|
|
@@ -60,8 +72,14 @@ export class MikoshiApiClient {
|
|
|
60
72
|
};
|
|
61
73
|
const init = { method, headers };
|
|
62
74
|
if (body !== undefined) {
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
if (body instanceof FormData) {
|
|
76
|
+
// multipart boundary は fetch に自動で決めさせる
|
|
77
|
+
init.body = body;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
headers["Content-Type"] = "application/json";
|
|
81
|
+
init.body = JSON.stringify(body);
|
|
82
|
+
}
|
|
65
83
|
}
|
|
66
84
|
const res = await fetch(url, init);
|
|
67
85
|
if (!res.ok) {
|
|
@@ -82,3 +100,15 @@ export class MikoshiApiClient {
|
|
|
82
100
|
function enc(segment) {
|
|
83
101
|
return encodeURIComponent(segment);
|
|
84
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* multipart/form-data 送信時のダミーファイル名。
|
|
105
|
+
* Mikoshi 側は MIME と内容で判定するので拡張子さえ合っていればよい。
|
|
106
|
+
*/
|
|
107
|
+
function filenameForMimeType(mimeType) {
|
|
108
|
+
switch (mimeType) {
|
|
109
|
+
case "image/jpeg": return "avatar.jpg";
|
|
110
|
+
case "image/png": return "avatar.png";
|
|
111
|
+
case "image/webp": return "avatar.webp";
|
|
112
|
+
default: return "avatar";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -66,14 +66,22 @@ export declare const EngramManifestSchema: z.ZodObject<{
|
|
|
66
66
|
createdAt: z.ZodString;
|
|
67
67
|
/** 最終更新日時 */
|
|
68
68
|
updatedAt: z.ZodString;
|
|
69
|
+
/** 前回 push 時の avatar 画像ファイルの SHA-256 ハッシュ */
|
|
70
|
+
avatarHash: z.ZodOptional<z.ZodString>;
|
|
71
|
+
/** 前回 push 時に使った外部 avatar URL */
|
|
72
|
+
avatarSourceUrl: z.ZodOptional<z.ZodString>;
|
|
69
73
|
}, "strip", z.ZodTypeAny, {
|
|
70
74
|
id: string;
|
|
71
75
|
createdAt: string;
|
|
72
76
|
updatedAt: string;
|
|
77
|
+
avatarHash?: string | undefined;
|
|
78
|
+
avatarSourceUrl?: string | undefined;
|
|
73
79
|
}, {
|
|
74
80
|
id: string;
|
|
75
81
|
createdAt: string;
|
|
76
82
|
updatedAt: string;
|
|
83
|
+
avatarHash?: string | undefined;
|
|
84
|
+
avatarSourceUrl?: string | undefined;
|
|
77
85
|
}>;
|
|
78
86
|
export type EngramManifest = z.infer<typeof EngramManifestSchema>;
|
|
79
87
|
/**
|
|
@@ -93,6 +101,10 @@ export declare const EngramMetaSchema: z.ZodObject<{
|
|
|
93
101
|
createdAt: z.ZodString;
|
|
94
102
|
/** 最終更新日時 */
|
|
95
103
|
updatedAt: z.ZodString;
|
|
104
|
+
/** 前回 push 時の avatar 画像ファイルの SHA-256 ハッシュ */
|
|
105
|
+
avatarHash: z.ZodOptional<z.ZodString>;
|
|
106
|
+
/** 前回 push 時に使った外部 avatar URL */
|
|
107
|
+
avatarSourceUrl: z.ZodOptional<z.ZodString>;
|
|
96
108
|
}, "strip", z.ZodTypeAny, {
|
|
97
109
|
name: string;
|
|
98
110
|
id: string;
|
|
@@ -100,6 +112,8 @@ export declare const EngramMetaSchema: z.ZodObject<{
|
|
|
100
112
|
updatedAt: string;
|
|
101
113
|
description?: string | undefined;
|
|
102
114
|
tags?: string[] | undefined;
|
|
115
|
+
avatarHash?: string | undefined;
|
|
116
|
+
avatarSourceUrl?: string | undefined;
|
|
103
117
|
}, {
|
|
104
118
|
name: string;
|
|
105
119
|
id: string;
|
|
@@ -107,6 +121,8 @@ export declare const EngramMetaSchema: z.ZodObject<{
|
|
|
107
121
|
updatedAt: string;
|
|
108
122
|
description?: string | undefined;
|
|
109
123
|
tags?: string[] | undefined;
|
|
124
|
+
avatarHash?: string | undefined;
|
|
125
|
+
avatarSourceUrl?: string | undefined;
|
|
110
126
|
}>;
|
|
111
127
|
export type EngramMeta = z.infer<typeof EngramMetaSchema>;
|
|
112
128
|
/**
|
|
@@ -128,6 +144,10 @@ export declare const EngramSchema: z.ZodObject<{
|
|
|
128
144
|
createdAt: z.ZodString;
|
|
129
145
|
/** 最終更新日時 */
|
|
130
146
|
updatedAt: z.ZodString;
|
|
147
|
+
/** 前回 push 時の avatar 画像ファイルの SHA-256 ハッシュ */
|
|
148
|
+
avatarHash: z.ZodOptional<z.ZodString>;
|
|
149
|
+
/** 前回 push 時に使った外部 avatar URL */
|
|
150
|
+
avatarSourceUrl: z.ZodOptional<z.ZodString>;
|
|
131
151
|
}, "strip", z.ZodTypeAny, {
|
|
132
152
|
name: string;
|
|
133
153
|
id: string;
|
|
@@ -135,6 +155,8 @@ export declare const EngramSchema: z.ZodObject<{
|
|
|
135
155
|
updatedAt: string;
|
|
136
156
|
description?: string | undefined;
|
|
137
157
|
tags?: string[] | undefined;
|
|
158
|
+
avatarHash?: string | undefined;
|
|
159
|
+
avatarSourceUrl?: string | undefined;
|
|
138
160
|
}, {
|
|
139
161
|
name: string;
|
|
140
162
|
id: string;
|
|
@@ -142,6 +164,8 @@ export declare const EngramSchema: z.ZodObject<{
|
|
|
142
164
|
updatedAt: string;
|
|
143
165
|
description?: string | undefined;
|
|
144
166
|
tags?: string[] | undefined;
|
|
167
|
+
avatarHash?: string | undefined;
|
|
168
|
+
avatarSourceUrl?: string | undefined;
|
|
145
169
|
}>;
|
|
146
170
|
files: z.ZodObject<{
|
|
147
171
|
/** 人格の核となる指示・行動原理 */
|
|
@@ -183,6 +207,8 @@ export declare const EngramSchema: z.ZodObject<{
|
|
|
183
207
|
updatedAt: string;
|
|
184
208
|
description?: string | undefined;
|
|
185
209
|
tags?: string[] | undefined;
|
|
210
|
+
avatarHash?: string | undefined;
|
|
211
|
+
avatarSourceUrl?: string | undefined;
|
|
186
212
|
};
|
|
187
213
|
files: {
|
|
188
214
|
soul: string;
|
|
@@ -201,6 +227,8 @@ export declare const EngramSchema: z.ZodObject<{
|
|
|
201
227
|
updatedAt: string;
|
|
202
228
|
description?: string | undefined;
|
|
203
229
|
tags?: string[] | undefined;
|
|
230
|
+
avatarHash?: string | undefined;
|
|
231
|
+
avatarSourceUrl?: string | undefined;
|
|
204
232
|
};
|
|
205
233
|
files: {
|
|
206
234
|
soul: string;
|
|
@@ -43,6 +43,10 @@ export const EngramManifestSchema = z.object({
|
|
|
43
43
|
createdAt: z.string().datetime(),
|
|
44
44
|
/** 最終更新日時 */
|
|
45
45
|
updatedAt: z.string().datetime(),
|
|
46
|
+
/** 前回 push 時の avatar 画像ファイルの SHA-256 ハッシュ */
|
|
47
|
+
avatarHash: z.string().optional(),
|
|
48
|
+
/** 前回 push 時に使った外部 avatar URL */
|
|
49
|
+
avatarSourceUrl: z.string().url().optional(),
|
|
46
50
|
});
|
|
47
51
|
/**
|
|
48
52
|
* Engramメタデータ — プロフィールとマニフェストを結合した利用時ビュー
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Engram, EngramMeta } from "../entities/engram.js";
|
|
1
|
+
import type { Engram, EngramManifest, EngramMeta } from "../entities/engram.js";
|
|
2
2
|
/**
|
|
3
3
|
* EngramRepository — Engram永続化層の抽象ポート
|
|
4
4
|
*
|
|
@@ -12,8 +12,24 @@ export interface EngramRepository {
|
|
|
12
12
|
get(id: string): Promise<Engram | null>;
|
|
13
13
|
/** Engramを保存(作成 or 更新) */
|
|
14
14
|
save(engram: Engram): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* マニフェストだけを差分書き換えする軽量更新。
|
|
17
|
+
*
|
|
18
|
+
* `save()` は全ファイルを書き戻してしまうので、avatarHash のような
|
|
19
|
+
* マニフェスト内のフィールドだけを安全に更新したい時に使う。
|
|
20
|
+
* 対象 Engram が存在しない場合の挙動は実装に委ねる(例外または no-op)。
|
|
21
|
+
*/
|
|
22
|
+
updateManifest(id: string, manifest: EngramManifest): Promise<void>;
|
|
15
23
|
/** Engramを削除 */
|
|
16
24
|
delete(id: string): Promise<void>;
|
|
17
25
|
/** アーカイブファイル (archive.md, archive.cursor) を別Engramへコピー */
|
|
18
26
|
copyArchiveFiles(fromId: string, toId: string): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Engramのディレクトリ絶対パスを返す。
|
|
29
|
+
*
|
|
30
|
+
* 存在判定は行わず、リポジトリが想定するパスを返すだけ。
|
|
31
|
+
* avatar 画像の読み書きなどファイルシステム直接アクセスが必要な
|
|
32
|
+
* usecase から使う。リモート専用リポジトリでは null を返してよい。
|
|
33
|
+
*/
|
|
34
|
+
getEngramPath(id: string): string | null;
|
|
19
35
|
}
|
|
@@ -906,6 +906,26 @@ export declare const DownloadMemoryResponseSchema: z.ZodDiscriminatedUnion<"hasM
|
|
|
906
906
|
engramId: string;
|
|
907
907
|
}>]>;
|
|
908
908
|
export type DownloadMemoryResponse = z.infer<typeof DownloadMemoryResponseSchema>;
|
|
909
|
+
/** クライアントが許可する avatar MIME タイプ */
|
|
910
|
+
export declare const AVATAR_SUPPORTED_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/webp"];
|
|
911
|
+
/** クライアント側事前バリデーションの最大バイト数 (Mikoshi 側と一致) */
|
|
912
|
+
export declare const AVATAR_MAX_BYTES: number;
|
|
913
|
+
export declare const UploadEngramAvatarResponseSchema: z.ZodObject<{
|
|
914
|
+
avatarUrl: z.ZodString;
|
|
915
|
+
}, "strip", z.ZodTypeAny, {
|
|
916
|
+
avatarUrl: string;
|
|
917
|
+
}, {
|
|
918
|
+
avatarUrl: string;
|
|
919
|
+
}>;
|
|
920
|
+
export type UploadEngramAvatarResponse = z.infer<typeof UploadEngramAvatarResponseSchema>;
|
|
921
|
+
export declare const DeleteEngramAvatarResponseSchema: z.ZodObject<{
|
|
922
|
+
avatarUrl: z.ZodNull;
|
|
923
|
+
}, "strip", z.ZodTypeAny, {
|
|
924
|
+
avatarUrl: null;
|
|
925
|
+
}, {
|
|
926
|
+
avatarUrl: null;
|
|
927
|
+
}>;
|
|
928
|
+
export type DeleteEngramAvatarResponse = z.infer<typeof DeleteEngramAvatarResponseSchema>;
|
|
909
929
|
export declare class MikoshiApiError extends Error {
|
|
910
930
|
readonly status: number;
|
|
911
931
|
readonly code: string | undefined;
|
|
@@ -934,4 +954,8 @@ export interface MikoshiClient {
|
|
|
934
954
|
uploadMemory(engramId: string, input: UploadMemoryInput): Promise<UploadMemoryResponse>;
|
|
935
955
|
/** 暗号化メモリバンドルをダウンロード */
|
|
936
956
|
downloadMemory(engramId: string): Promise<DownloadMemoryResponse>;
|
|
957
|
+
/** Avatar 画像をアップロード (multipart/form-data) */
|
|
958
|
+
uploadEngramAvatar(engramId: string, data: Buffer, mimeType: string): Promise<UploadEngramAvatarResponse>;
|
|
959
|
+
/** Avatar 画像を削除 */
|
|
960
|
+
deleteEngramAvatar(engramId: string): Promise<DeleteEngramAvatarResponse>;
|
|
937
961
|
}
|
|
@@ -188,6 +188,23 @@ export const DownloadMemoryResponseSchema = z.discriminatedUnion("hasMemory", [
|
|
|
188
188
|
}),
|
|
189
189
|
]);
|
|
190
190
|
// ---------------------------------------------------------------------------
|
|
191
|
+
// Avatar upload / delete
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
/** クライアントが許可する avatar MIME タイプ */
|
|
194
|
+
export const AVATAR_SUPPORTED_MIME_TYPES = [
|
|
195
|
+
"image/jpeg",
|
|
196
|
+
"image/png",
|
|
197
|
+
"image/webp",
|
|
198
|
+
];
|
|
199
|
+
/** クライアント側事前バリデーションの最大バイト数 (Mikoshi 側と一致) */
|
|
200
|
+
export const AVATAR_MAX_BYTES = 2 * 1024 * 1024;
|
|
201
|
+
export const UploadEngramAvatarResponseSchema = z.object({
|
|
202
|
+
avatarUrl: z.string(),
|
|
203
|
+
});
|
|
204
|
+
export const DeleteEngramAvatarResponseSchema = z.object({
|
|
205
|
+
avatarUrl: z.null(),
|
|
206
|
+
});
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
191
208
|
// Error types
|
|
192
209
|
// ---------------------------------------------------------------------------
|
|
193
210
|
export class MikoshiApiError extends Error {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export type AvatarRef = {
|
|
2
|
+
kind: "path";
|
|
3
|
+
value: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "url";
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
8
|
+
export interface FetchedAvatar {
|
|
9
|
+
bytes: Buffer;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
finalUrl: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* IDENTITY.md から Avatar フィールドの生の値を抽出する。
|
|
15
|
+
*
|
|
16
|
+
* - マッチしなければ null
|
|
17
|
+
* - 値が URL(http://, https://)の場合は Mikoshi 管理外として null
|
|
18
|
+
* - 空白のみの値も null として扱う
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseAvatarPath(identity: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* IDENTITY.md から Avatar フィールドの参照先を抽出する。
|
|
23
|
+
*
|
|
24
|
+
* - マッチしなければ null
|
|
25
|
+
* - 空白のみの値も null
|
|
26
|
+
* - `http(s)://` は URL として返す
|
|
27
|
+
* - それ以外は path として返す
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseAvatarRef(identity: string): AvatarRef | null;
|
|
30
|
+
/**
|
|
31
|
+
* Avatar パスを絶対パスに解決する。
|
|
32
|
+
*
|
|
33
|
+
* - 絶対パス → そのまま
|
|
34
|
+
* - 相対パス → Engram ディレクトリ基準で resolve
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveAvatarPath(rawPath: string, engramDir: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* ファイル内容の SHA-256 ハッシュを計算する。
|
|
39
|
+
*
|
|
40
|
+
* 出力形式: "sha256:<64 lowercase hex>"
|
|
41
|
+
*
|
|
42
|
+
* ストリーム処理なので 2MB 制限を超える巨大ファイルでも OOM しない。
|
|
43
|
+
*/
|
|
44
|
+
export declare function computeAvatarHash(filePath: string): Promise<string>;
|
|
45
|
+
export declare function computeAvatarHashFromBytes(bytes: Uint8Array): string;
|
|
46
|
+
/**
|
|
47
|
+
* 拡張子から MIME タイプを判定する。
|
|
48
|
+
*
|
|
49
|
+
* 許可フォーマット: JPEG, PNG, WebP
|
|
50
|
+
* 対応外なら null(呼び出し側でエラーにする)
|
|
51
|
+
*
|
|
52
|
+
* バイナリ判定は Mikoshi 側の sharp に任せる方針。
|
|
53
|
+
* クライアントは軽量な事前バリデーションのみ。
|
|
54
|
+
*/
|
|
55
|
+
export declare function detectAvatarMimeType(filePath: string): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* レスポンス header / magic number の両方から MIME を判定する。
|
|
58
|
+
*
|
|
59
|
+
* 両方あって不一致なら null を返す。
|
|
60
|
+
*/
|
|
61
|
+
export declare function detectAvatarMimeTypeFromBytes(bytes: Uint8Array, headerMimeType?: string | null): string | null;
|
|
62
|
+
export declare function isHttpsUrl(raw: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* ホストが private / loopback / link-local なら true。
|
|
65
|
+
*
|
|
66
|
+
* hostname の場合は DNS lookup して、返ってきた全アドレスを検査する。
|
|
67
|
+
* DNS rebinding の完全対策ではない。事故防止用。
|
|
68
|
+
*/
|
|
69
|
+
export declare function isPrivateHost(url: URL): Promise<boolean>;
|
|
70
|
+
export declare function fetchAvatarFromUrl(url: string, maxBytes: number, timeoutMs: number): Promise<FetchedAvatar>;
|
|
71
|
+
/**
|
|
72
|
+
* IDENTITY.md を Avatar 行の前後で分離した結果。
|
|
73
|
+
*
|
|
74
|
+
* Avatar 行が存在しない場合は `rawValue === null`、`before` に全内容、
|
|
75
|
+
* `after` に空文字が入る(呼び出し側が「Avatar 行無し」として判定できる)。
|
|
76
|
+
*/
|
|
77
|
+
export interface AvatarSplit {
|
|
78
|
+
/** Avatar 行の値の直前までの文字列(`- **Avatar:** ` prefix を含む) */
|
|
79
|
+
before: string;
|
|
80
|
+
/** Avatar 行の値(行末改行含まず)。Avatar 行が無ければ null */
|
|
81
|
+
rawValue: string | null;
|
|
82
|
+
/** Avatar 行の値の直後以降の文字列(行末改行とその後の内容) */
|
|
83
|
+
after: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* IDENTITY.md を Avatar 行の値を境に前後分離する。
|
|
87
|
+
*
|
|
88
|
+
* 書き換えと drift 検出の両方で使う基本操作。
|
|
89
|
+
*/
|
|
90
|
+
export declare function splitAroundAvatar(identity: string): AvatarSplit;
|
|
91
|
+
/**
|
|
92
|
+
* IDENTITY.md の Avatar 行の値だけを差し替えて新しい文字列を返す。
|
|
93
|
+
*
|
|
94
|
+
* Avatar 行が存在しない場合は元の identity をそのまま返す
|
|
95
|
+
* (新しい Avatar 行は挿入しない — 保守的な挙動)。
|
|
96
|
+
*/
|
|
97
|
+
export declare function rewriteAvatarValue(identity: string, newValue: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* avatar-only drift の検出結果。
|
|
100
|
+
*
|
|
101
|
+
* drift === true の場合のみ `localValue` / `remoteValue` が
|
|
102
|
+
* 非 null(両方に Avatar 行が存在し、値だけが違う)。
|
|
103
|
+
*/
|
|
104
|
+
export interface AvatarOnlyDrift {
|
|
105
|
+
drift: boolean;
|
|
106
|
+
localValue: string | null;
|
|
107
|
+
remoteValue: string | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 2つの IDENTITY.md の差分が Avatar 行の値だけかどうかを判定する。
|
|
111
|
+
*
|
|
112
|
+
* 判定条件:
|
|
113
|
+
* - 両方に Avatar 行が存在する
|
|
114
|
+
* - Avatar 行の前後 (`before` / `after`) が完全一致
|
|
115
|
+
* - Avatar 行の値だけ異なる
|
|
116
|
+
*
|
|
117
|
+
* PATH 前後の format 揺れ(改行数・空白数など)は
|
|
118
|
+
* `before` / `after` の文字列一致にそのまま反映されるため、
|
|
119
|
+
* 揺れがあれば avatar-only drift とは判定されない(false positive を避ける)。
|
|
120
|
+
*/
|
|
121
|
+
export declare function isAvatarOnlyDrift(local: string, remote: string): AvatarOnlyDrift;
|