@ectplsm/relic 0.4.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.
Files changed (34) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/local/local-engram-repository.d.ts +3 -1
  3. package/dist/adapters/local/local-engram-repository.js +25 -0
  4. package/dist/adapters/mikoshi/client.d.ts +3 -1
  5. package/dist/adapters/mikoshi/client.js +33 -3
  6. package/dist/core/entities/engram.d.ts +28 -0
  7. package/dist/core/entities/engram.js +4 -0
  8. package/dist/core/ports/engram-repository.d.ts +17 -1
  9. package/dist/core/ports/mikoshi.d.ts +24 -0
  10. package/dist/core/ports/mikoshi.js +17 -0
  11. package/dist/core/sync/avatar.d.ts +121 -0
  12. package/dist/core/sync/avatar.js +352 -0
  13. package/dist/core/usecases/archive-cursor-update.d.ts +6 -0
  14. package/dist/core/usecases/archive-cursor-update.js +18 -0
  15. package/dist/core/usecases/archive-pending.d.ts +18 -1
  16. package/dist/core/usecases/archive-pending.js +37 -2
  17. package/dist/core/usecases/index.d.ts +3 -3
  18. package/dist/core/usecases/index.js +2 -2
  19. package/dist/core/usecases/memory-write.d.ts +5 -0
  20. package/dist/core/usecases/memory-write.js +26 -16
  21. package/dist/core/usecases/mikoshi-download.d.ts +5 -0
  22. package/dist/core/usecases/mikoshi-download.js +17 -1
  23. package/dist/core/usecases/mikoshi-pull.d.ts +26 -0
  24. package/dist/core/usecases/mikoshi-pull.js +47 -3
  25. package/dist/core/usecases/mikoshi-push.d.ts +129 -2
  26. package/dist/core/usecases/mikoshi-push.js +316 -11
  27. package/dist/interfaces/cli/commands/config.js +1 -1
  28. package/dist/interfaces/cli/commands/mikoshi.js +150 -13
  29. package/dist/interfaces/mcp/index.js +109 -38
  30. package/dist/shared/config.d.ts +2 -2
  31. package/dist/shared/config.js +3 -3
  32. package/dist/shared/engram-composer.d.ts +1 -1
  33. package/dist/shared/engram-composer.js +9 -5
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -112,7 +112,7 @@ As you use a Construct, conversation logs are automatically saved to `archive.md
112
112
 
113
113
  > **"Organize my memories"**
114
114
 
115
- The Construct will review recent conversations, extract key facts and decisions into `memory/*.md`, promote important long-term insights to `MEMORY.md`, and update your preferences in `USER.md`. These distilled memories are then loaded into future sessions automatically.
115
+ The Construct will review recent conversations, group them by the actual dates recorded in `archive.md`, extract key facts and decisions into the matching `memory/*.md` files, promote important long-term insights to `MEMORY.md`, and update your preferences in `USER.md`. These distilled memories are then loaded into future sessions automatically.
116
116
 
117
117
  ### 5. Learn More
118
118
 
@@ -184,9 +184,11 @@ For Engram creation, the smoothest path is to use your LLM with the `relic_engra
184
184
 
185
185
  For LLM-assisted creation, persona authoring, template examples, and deletion rules, see [docs/engram-guide.md](docs/engram-guide.md).
186
186
 
187
- ## Cloud Storage and Sharing
187
+ ## Cloud Storage and Sharing (Mikoshi)
188
188
 
189
- Relic can push plaintext persona files and 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.
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
+
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`.
190
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
 
@@ -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
- headers["Content-Type"] = "application/json";
64
- init.body = JSON.stringify(body);
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;