@codelia/storage 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ McpAuthStore: () => McpAuthStore,
34
+ RunEventStoreFactoryImpl: () => RunEventStoreFactoryImpl,
35
+ SessionStateStoreImpl: () => SessionStateStoreImpl,
36
+ SessionStoreWriterImpl: () => SessionStoreWriterImpl,
37
+ StoragePathServiceImpl: () => StoragePathServiceImpl,
38
+ ToolOutputCacheStoreImpl: () => ToolOutputCacheStoreImpl,
39
+ ensureStorageDirs: () => ensureStorageDirs,
40
+ resolveStoragePaths: () => resolveStoragePaths
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/mcp-auth-store.ts
45
+ var import_node_fs = require("fs");
46
+
47
+ // src/paths.ts
48
+ var import_promises = require("fs/promises");
49
+ var import_node_os = __toESM(require("os"), 1);
50
+ var import_node_path = __toESM(require("path"), 1);
51
+ var DEFAULT_ROOT_DIR = ".codelia";
52
+ var LAYOUT_ENV = "CODELIA_LAYOUT";
53
+ var XDG_DIR_NAME = "codelia";
54
+ var CONFIG_FILENAME = "config.json";
55
+ var AUTH_FILENAME = "auth.json";
56
+ var MCP_AUTH_FILENAME = "mcp-auth.json";
57
+ var CACHE_DIRNAME = "cache";
58
+ var TOOL_OUTPUT_CACHE_DIRNAME = "tool-output";
59
+ var SESSIONS_DIRNAME = "sessions";
60
+ var LOGS_DIRNAME = "logs";
61
+ function resolveStoragePaths(options = {}) {
62
+ if (options.rootOverride) {
63
+ return buildHomeLayout(options.rootOverride);
64
+ }
65
+ const layoutValue = options.layout ?? process.env[LAYOUT_ENV];
66
+ const layout = normalizeLayout(layoutValue);
67
+ if (layout === "xdg") {
68
+ return buildXdgLayout();
69
+ }
70
+ return buildHomeLayout(defaultHomeRoot());
71
+ }
72
+ async function ensureStorageDirs(paths) {
73
+ await Promise.all([
74
+ (0, import_promises.mkdir)(paths.configDir, { recursive: true }),
75
+ (0, import_promises.mkdir)(paths.cacheDir, { recursive: true }),
76
+ (0, import_promises.mkdir)(paths.toolOutputCacheDir, { recursive: true }),
77
+ (0, import_promises.mkdir)(paths.sessionsDir, { recursive: true }),
78
+ (0, import_promises.mkdir)(paths.logsDir, { recursive: true })
79
+ ]);
80
+ }
81
+ function normalizeLayout(value) {
82
+ if (value && value.toLowerCase() === "xdg") return "xdg";
83
+ return "home";
84
+ }
85
+ function defaultHomeRoot() {
86
+ return import_node_path.default.join(import_node_os.default.homedir(), DEFAULT_ROOT_DIR);
87
+ }
88
+ function buildHomeLayout(root) {
89
+ const configDir = root;
90
+ return {
91
+ root,
92
+ configDir,
93
+ configFile: import_node_path.default.join(configDir, CONFIG_FILENAME),
94
+ authFile: import_node_path.default.join(configDir, AUTH_FILENAME),
95
+ mcpAuthFile: import_node_path.default.join(configDir, MCP_AUTH_FILENAME),
96
+ cacheDir: import_node_path.default.join(root, CACHE_DIRNAME),
97
+ toolOutputCacheDir: import_node_path.default.join(
98
+ root,
99
+ CACHE_DIRNAME,
100
+ TOOL_OUTPUT_CACHE_DIRNAME
101
+ ),
102
+ sessionsDir: import_node_path.default.join(root, SESSIONS_DIRNAME),
103
+ logsDir: import_node_path.default.join(root, LOGS_DIRNAME)
104
+ };
105
+ }
106
+ function buildXdgLayout() {
107
+ const home = import_node_os.default.homedir();
108
+ const configRoot = process.env.XDG_CONFIG_HOME ?? import_node_path.default.join(home, ".config");
109
+ const cacheRoot = process.env.XDG_CACHE_HOME ?? import_node_path.default.join(home, ".cache");
110
+ const stateRoot = process.env.XDG_STATE_HOME ?? import_node_path.default.join(home, ".local", "state");
111
+ const configDir = import_node_path.default.join(configRoot, XDG_DIR_NAME);
112
+ const cacheDir = import_node_path.default.join(cacheRoot, XDG_DIR_NAME);
113
+ const stateDir = import_node_path.default.join(stateRoot, XDG_DIR_NAME);
114
+ return {
115
+ root: stateDir,
116
+ configDir,
117
+ configFile: import_node_path.default.join(configDir, CONFIG_FILENAME),
118
+ authFile: import_node_path.default.join(configDir, AUTH_FILENAME),
119
+ mcpAuthFile: import_node_path.default.join(configDir, MCP_AUTH_FILENAME),
120
+ cacheDir,
121
+ toolOutputCacheDir: import_node_path.default.join(cacheDir, TOOL_OUTPUT_CACHE_DIRNAME),
122
+ sessionsDir: import_node_path.default.join(stateDir, SESSIONS_DIRNAME),
123
+ logsDir: import_node_path.default.join(stateDir, LOGS_DIRNAME)
124
+ };
125
+ }
126
+
127
+ // src/mcp-auth-store.ts
128
+ var MCP_AUTH_VERSION = 1;
129
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
130
+ var pickString = (value) => typeof value === "string" ? value : void 0;
131
+ var pickNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
132
+ var normalizeTokens = (value) => {
133
+ if (!isRecord(value)) return null;
134
+ const accessToken = pickString(value.access_token);
135
+ if (!accessToken) return null;
136
+ return {
137
+ access_token: accessToken,
138
+ ...pickString(value.refresh_token) ? { refresh_token: pickString(value.refresh_token) } : {},
139
+ ...pickNumber(value.expires_at) ? { expires_at: pickNumber(value.expires_at) } : {},
140
+ ...pickString(value.token_type) ? { token_type: pickString(value.token_type) } : {},
141
+ ...pickString(value.scope) ? { scope: pickString(value.scope) } : {},
142
+ ...pickString(value.client_id) ? { client_id: pickString(value.client_id) } : {},
143
+ ...pickString(value.client_secret) ? { client_secret: pickString(value.client_secret) } : {}
144
+ };
145
+ };
146
+ var McpAuthStore = class {
147
+ paths;
148
+ constructor(paths) {
149
+ this.paths = paths ?? resolveStoragePaths();
150
+ }
151
+ async load() {
152
+ try {
153
+ const text = await import_node_fs.promises.readFile(this.paths.mcpAuthFile, "utf8");
154
+ const parsed = JSON.parse(text);
155
+ if (!isRecord(parsed) || parsed.version !== MCP_AUTH_VERSION) {
156
+ throw new Error("mcp-auth.json has unsupported version");
157
+ }
158
+ const serversRaw = isRecord(parsed.servers) ? parsed.servers : {};
159
+ const servers = {};
160
+ for (const [id, entry] of Object.entries(serversRaw)) {
161
+ const normalized = normalizeTokens(entry);
162
+ if (normalized) {
163
+ servers[id] = normalized;
164
+ }
165
+ }
166
+ return {
167
+ version: MCP_AUTH_VERSION,
168
+ servers
169
+ };
170
+ } catch (error) {
171
+ const err = error;
172
+ if (err?.code === "ENOENT") {
173
+ return { version: MCP_AUTH_VERSION, servers: {} };
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+ async save(auth) {
179
+ await ensureStorageDirs(this.paths);
180
+ const data = JSON.stringify(auth, null, 2);
181
+ await import_node_fs.promises.writeFile(this.paths.mcpAuthFile, data, { mode: 384 });
182
+ try {
183
+ await import_node_fs.promises.chmod(this.paths.mcpAuthFile, 384);
184
+ } catch {
185
+ }
186
+ }
187
+ };
188
+
189
+ // src/session-store.ts
190
+ var import_node_fs2 = require("fs");
191
+ var import_node_path2 = __toESM(require("path"), 1);
192
+ var pad2 = (value) => String(value).padStart(2, "0");
193
+ var resolveDateParts = (startedAt) => {
194
+ const parsed = new Date(startedAt);
195
+ const date = Number.isNaN(parsed.getTime()) ? /* @__PURE__ */ new Date() : parsed;
196
+ return {
197
+ year: String(date.getUTCFullYear()),
198
+ month: pad2(date.getUTCMonth() + 1),
199
+ day: pad2(date.getUTCDate())
200
+ };
201
+ };
202
+ var SessionStoreWriterImpl = class {
203
+ filePath;
204
+ ensureDir;
205
+ writeChain = Promise.resolve();
206
+ constructor(options) {
207
+ const paths = options.paths ?? resolveStoragePaths();
208
+ const parts = resolveDateParts(options.startedAt);
209
+ const baseDir = import_node_path2.default.join(
210
+ paths.sessionsDir,
211
+ parts.year,
212
+ parts.month,
213
+ parts.day
214
+ );
215
+ this.filePath = import_node_path2.default.join(baseDir, `${options.runId}.jsonl`);
216
+ this.ensureDir = import_node_fs2.promises.mkdir(baseDir, { recursive: true }).then(() => {
217
+ });
218
+ }
219
+ append(record) {
220
+ let line;
221
+ try {
222
+ line = `${JSON.stringify(record)}
223
+ `;
224
+ } catch (error) {
225
+ return Promise.reject(error);
226
+ }
227
+ const write = this.writeChain.then(async () => {
228
+ await this.ensureDir;
229
+ await import_node_fs2.promises.appendFile(this.filePath, line, "utf8");
230
+ });
231
+ this.writeChain = write.catch(() => {
232
+ });
233
+ return write;
234
+ }
235
+ };
236
+
237
+ // src/run-event-store.ts
238
+ var RunEventStoreFactoryImpl = class {
239
+ create(init) {
240
+ return new SessionStoreWriterImpl({
241
+ runId: init.runId,
242
+ startedAt: init.startedAt
243
+ });
244
+ }
245
+ };
246
+
247
+ // src/service.ts
248
+ var StoragePathServiceImpl = class {
249
+ resolvePaths(options = {}) {
250
+ return resolveStoragePaths(options);
251
+ }
252
+ };
253
+
254
+ // src/session-state.ts
255
+ var import_node_fs3 = require("fs");
256
+ var import_node_path3 = __toESM(require("path"), 1);
257
+ var import_core = require("@codelia/core");
258
+ var STATE_DIRNAME = "state";
259
+ var resolveStateDir = (paths) => import_node_path3.default.join(paths.sessionsDir, STATE_DIRNAME);
260
+ var extractLastUserMessage = (messages) => {
261
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
262
+ const message = messages[idx];
263
+ if (!message || typeof message !== "object") continue;
264
+ if ("role" in message && message.role === "user" && "content" in message) {
265
+ return (0, import_core.stringifyContent)(message.content, {
266
+ mode: "display"
267
+ });
268
+ }
269
+ }
270
+ return void 0;
271
+ };
272
+ var toSummary = (state) => ({
273
+ session_id: state.session_id,
274
+ updated_at: state.updated_at,
275
+ run_id: state.run_id,
276
+ message_count: Array.isArray(state.messages) ? state.messages.length : void 0,
277
+ last_user_message: Array.isArray(state.messages) ? extractLastUserMessage(state.messages) : void 0
278
+ });
279
+ var SessionStateStoreImpl = class {
280
+ stateDir;
281
+ ensureDir;
282
+ onError;
283
+ constructor(options = {}) {
284
+ const paths = options.paths ?? resolveStoragePaths();
285
+ this.stateDir = resolveStateDir(paths);
286
+ this.ensureDir = import_node_fs3.promises.mkdir(this.stateDir, { recursive: true }).then(() => {
287
+ });
288
+ this.onError = options.onError;
289
+ }
290
+ resolvePath(sessionId) {
291
+ return import_node_path3.default.join(this.stateDir, `${sessionId}.json`);
292
+ }
293
+ async load(sessionId) {
294
+ try {
295
+ const file = await import_node_fs3.promises.readFile(this.resolvePath(sessionId), "utf8");
296
+ const parsed = JSON.parse(file);
297
+ if (!parsed || typeof parsed !== "object") return null;
298
+ return parsed;
299
+ } catch (error) {
300
+ if (error.code === "ENOENT") {
301
+ return null;
302
+ }
303
+ this.onError?.(error, { action: "load", detail: sessionId });
304
+ throw error;
305
+ }
306
+ }
307
+ async save(state) {
308
+ await this.ensureDir;
309
+ const payload = `${JSON.stringify(state)}
310
+ `;
311
+ await import_node_fs3.promises.writeFile(this.resolvePath(state.session_id), payload, "utf8");
312
+ }
313
+ async list() {
314
+ await this.ensureDir;
315
+ let entries;
316
+ try {
317
+ entries = await import_node_fs3.promises.readdir(this.stateDir, {
318
+ withFileTypes: true,
319
+ encoding: "utf8"
320
+ });
321
+ } catch (error) {
322
+ this.onError?.(error, { action: "list" });
323
+ throw error;
324
+ }
325
+ const summaries = [];
326
+ for (const entry of entries) {
327
+ if (!entry.isFile()) continue;
328
+ if (!entry.name.endsWith(".json")) continue;
329
+ const filePath = import_node_path3.default.join(this.stateDir, entry.name);
330
+ try {
331
+ const contents = await import_node_fs3.promises.readFile(filePath, "utf8");
332
+ const parsed = JSON.parse(contents);
333
+ if (!parsed || typeof parsed !== "object") continue;
334
+ if (!parsed.session_id || !parsed.updated_at) continue;
335
+ summaries.push(toSummary(parsed));
336
+ } catch (error) {
337
+ this.onError?.(error, { action: "parse", detail: filePath });
338
+ }
339
+ }
340
+ return summaries;
341
+ }
342
+ };
343
+
344
+ // src/tool-output-cache.ts
345
+ var import_node_fs4 = require("fs");
346
+ var import_node_path4 = __toESM(require("path"), 1);
347
+ var DEFAULT_MAX_MATCHES = 50;
348
+ var normalizeRefId = (refId) => refId.replace(/[^a-zA-Z0-9_-]/g, "_");
349
+ var formatLineNumber = (lineNumber) => String(lineNumber).padStart(5, " ");
350
+ var toLineNumbered = (lines, offset) => lines.map((line, index) => `${formatLineNumber(index + offset + 1)} ${line}`).join("\n");
351
+ var ToolOutputCacheStoreImpl = class {
352
+ baseDir;
353
+ constructor(options = {}) {
354
+ const paths = options.paths ?? resolveStoragePaths();
355
+ this.baseDir = paths.toolOutputCacheDir;
356
+ }
357
+ async save(record) {
358
+ const refId = normalizeRefId(record.tool_call_id);
359
+ const filePath = this.resolvePath(refId);
360
+ await import_node_fs4.promises.mkdir(this.baseDir, { recursive: true });
361
+ await import_node_fs4.promises.writeFile(filePath, record.content, "utf8");
362
+ const byteSize = Buffer.byteLength(record.content, "utf8");
363
+ const lineCount = record.content.split(/\r?\n/).length;
364
+ return { id: refId, byte_size: byteSize, line_count: lineCount };
365
+ }
366
+ async read(refId, options = {}) {
367
+ const filePath = this.resolvePath(refId);
368
+ const content = await import_node_fs4.promises.readFile(filePath, "utf8");
369
+ const lines = content.split(/\r?\n/);
370
+ const offset = options.offset ?? 0;
371
+ const limit = options.limit ?? lines.length;
372
+ if (offset >= lines.length) {
373
+ return "Offset exceeds output length.";
374
+ }
375
+ const slice = lines.slice(offset, offset + limit);
376
+ return toLineNumbered(slice, offset);
377
+ }
378
+ async grep(refId, options) {
379
+ const filePath = this.resolvePath(refId);
380
+ const content = await import_node_fs4.promises.readFile(filePath, "utf8");
381
+ const lines = content.split(/\r?\n/);
382
+ const regex = options.regex ? new RegExp(options.pattern) : new RegExp(options.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
383
+ const before = Math.max(0, options.before ?? 0);
384
+ const after = Math.max(0, options.after ?? 0);
385
+ const maxMatches = Math.max(1, options.max_matches ?? DEFAULT_MAX_MATCHES);
386
+ const results = [];
387
+ for (let i = 0; i < lines.length; i += 1) {
388
+ if (results.length >= maxMatches) break;
389
+ if (!regex.test(lines[i])) continue;
390
+ const start = Math.max(0, i - before);
391
+ const end = Math.min(lines.length, i + after + 1);
392
+ const snippet = toLineNumbered(lines.slice(start, end), start);
393
+ results.push(`ref:${normalizeRefId(refId)}
394
+ ${snippet}`);
395
+ }
396
+ if (!results.length) {
397
+ return `No matches for: ${options.pattern}`;
398
+ }
399
+ return results.length >= maxMatches ? `${results.join("\n\n")}
400
+ ... (truncated)` : results.join("\n\n");
401
+ }
402
+ resolvePath(refId) {
403
+ return import_node_path4.default.join(this.baseDir, `${normalizeRefId(refId)}.txt`);
404
+ }
405
+ };
406
+ // Annotate the CommonJS export names for ESM import in node:
407
+ 0 && (module.exports = {
408
+ McpAuthStore,
409
+ RunEventStoreFactoryImpl,
410
+ SessionStateStoreImpl,
411
+ SessionStoreWriterImpl,
412
+ StoragePathServiceImpl,
413
+ ToolOutputCacheStoreImpl,
414
+ ensureStorageDirs,
415
+ resolveStoragePaths
416
+ });
@@ -0,0 +1,76 @@
1
+ import { StoragePaths, ResolveStorageOptions, RunEventStoreFactory, RunEventStoreInit, SessionStore, StoragePathService, SessionStateStore, SessionState, SessionStateSummary, SessionRecord, ToolOutputCacheStore, ToolOutputCacheRecord, ToolOutputRef, ToolOutputCacheReadOptions, ToolOutputCacheSearchOptions } from '@codelia/core';
2
+ export { ResolveStorageOptions, StorageLayout, StoragePathService, StoragePaths } from '@codelia/core';
3
+
4
+ type McpOAuthTokens = {
5
+ access_token: string;
6
+ refresh_token?: string;
7
+ expires_at?: number;
8
+ token_type?: string;
9
+ scope?: string;
10
+ client_id?: string;
11
+ client_secret?: string;
12
+ };
13
+ type McpAuthFile = {
14
+ version: 1;
15
+ servers: Record<string, McpOAuthTokens>;
16
+ };
17
+ declare class McpAuthStore {
18
+ private readonly paths;
19
+ constructor(paths?: StoragePaths);
20
+ load(): Promise<McpAuthFile>;
21
+ save(auth: McpAuthFile): Promise<void>;
22
+ }
23
+
24
+ declare function resolveStoragePaths(options?: ResolveStorageOptions): StoragePaths;
25
+ declare function ensureStorageDirs(paths: StoragePaths): Promise<void>;
26
+
27
+ declare class RunEventStoreFactoryImpl implements RunEventStoreFactory {
28
+ create(init: RunEventStoreInit): SessionStore;
29
+ }
30
+
31
+ declare class StoragePathServiceImpl implements StoragePathService {
32
+ resolvePaths(options?: ResolveStorageOptions): StoragePaths;
33
+ }
34
+
35
+ type SessionStateStoreOptions = {
36
+ paths?: StoragePaths;
37
+ onError?: (error: unknown, context: {
38
+ action: string;
39
+ detail?: string;
40
+ }) => void;
41
+ };
42
+ declare class SessionStateStoreImpl implements SessionStateStore {
43
+ private readonly stateDir;
44
+ private readonly ensureDir;
45
+ private readonly onError?;
46
+ constructor(options?: SessionStateStoreOptions);
47
+ private resolvePath;
48
+ load(sessionId: string): Promise<SessionState | null>;
49
+ save(state: SessionState): Promise<void>;
50
+ list(): Promise<SessionStateSummary[]>;
51
+ }
52
+
53
+ declare class SessionStoreWriterImpl implements SessionStore {
54
+ private readonly filePath;
55
+ private readonly ensureDir;
56
+ private writeChain;
57
+ constructor(options: {
58
+ runId: string;
59
+ startedAt: string;
60
+ paths?: StoragePaths;
61
+ });
62
+ append(record: SessionRecord): Promise<void>;
63
+ }
64
+
65
+ declare class ToolOutputCacheStoreImpl implements ToolOutputCacheStore {
66
+ private readonly baseDir;
67
+ constructor(options?: {
68
+ paths?: StoragePaths;
69
+ });
70
+ save(record: ToolOutputCacheRecord): Promise<ToolOutputRef>;
71
+ read(refId: string, options?: ToolOutputCacheReadOptions): Promise<string>;
72
+ grep(refId: string, options: ToolOutputCacheSearchOptions): Promise<string>;
73
+ private resolvePath;
74
+ }
75
+
76
+ export { type McpAuthFile, McpAuthStore, type McpOAuthTokens, RunEventStoreFactoryImpl, SessionStateStoreImpl, SessionStoreWriterImpl, StoragePathServiceImpl, ToolOutputCacheStoreImpl, ensureStorageDirs, resolveStoragePaths };
@@ -0,0 +1,76 @@
1
+ import { StoragePaths, ResolveStorageOptions, RunEventStoreFactory, RunEventStoreInit, SessionStore, StoragePathService, SessionStateStore, SessionState, SessionStateSummary, SessionRecord, ToolOutputCacheStore, ToolOutputCacheRecord, ToolOutputRef, ToolOutputCacheReadOptions, ToolOutputCacheSearchOptions } from '@codelia/core';
2
+ export { ResolveStorageOptions, StorageLayout, StoragePathService, StoragePaths } from '@codelia/core';
3
+
4
+ type McpOAuthTokens = {
5
+ access_token: string;
6
+ refresh_token?: string;
7
+ expires_at?: number;
8
+ token_type?: string;
9
+ scope?: string;
10
+ client_id?: string;
11
+ client_secret?: string;
12
+ };
13
+ type McpAuthFile = {
14
+ version: 1;
15
+ servers: Record<string, McpOAuthTokens>;
16
+ };
17
+ declare class McpAuthStore {
18
+ private readonly paths;
19
+ constructor(paths?: StoragePaths);
20
+ load(): Promise<McpAuthFile>;
21
+ save(auth: McpAuthFile): Promise<void>;
22
+ }
23
+
24
+ declare function resolveStoragePaths(options?: ResolveStorageOptions): StoragePaths;
25
+ declare function ensureStorageDirs(paths: StoragePaths): Promise<void>;
26
+
27
+ declare class RunEventStoreFactoryImpl implements RunEventStoreFactory {
28
+ create(init: RunEventStoreInit): SessionStore;
29
+ }
30
+
31
+ declare class StoragePathServiceImpl implements StoragePathService {
32
+ resolvePaths(options?: ResolveStorageOptions): StoragePaths;
33
+ }
34
+
35
+ type SessionStateStoreOptions = {
36
+ paths?: StoragePaths;
37
+ onError?: (error: unknown, context: {
38
+ action: string;
39
+ detail?: string;
40
+ }) => void;
41
+ };
42
+ declare class SessionStateStoreImpl implements SessionStateStore {
43
+ private readonly stateDir;
44
+ private readonly ensureDir;
45
+ private readonly onError?;
46
+ constructor(options?: SessionStateStoreOptions);
47
+ private resolvePath;
48
+ load(sessionId: string): Promise<SessionState | null>;
49
+ save(state: SessionState): Promise<void>;
50
+ list(): Promise<SessionStateSummary[]>;
51
+ }
52
+
53
+ declare class SessionStoreWriterImpl implements SessionStore {
54
+ private readonly filePath;
55
+ private readonly ensureDir;
56
+ private writeChain;
57
+ constructor(options: {
58
+ runId: string;
59
+ startedAt: string;
60
+ paths?: StoragePaths;
61
+ });
62
+ append(record: SessionRecord): Promise<void>;
63
+ }
64
+
65
+ declare class ToolOutputCacheStoreImpl implements ToolOutputCacheStore {
66
+ private readonly baseDir;
67
+ constructor(options?: {
68
+ paths?: StoragePaths;
69
+ });
70
+ save(record: ToolOutputCacheRecord): Promise<ToolOutputRef>;
71
+ read(refId: string, options?: ToolOutputCacheReadOptions): Promise<string>;
72
+ grep(refId: string, options: ToolOutputCacheSearchOptions): Promise<string>;
73
+ private resolvePath;
74
+ }
75
+
76
+ export { type McpAuthFile, McpAuthStore, type McpOAuthTokens, RunEventStoreFactoryImpl, SessionStateStoreImpl, SessionStoreWriterImpl, StoragePathServiceImpl, ToolOutputCacheStoreImpl, ensureStorageDirs, resolveStoragePaths };
package/dist/index.js ADDED
@@ -0,0 +1,372 @@
1
+ // src/mcp-auth-store.ts
2
+ import { promises as fs } from "fs";
3
+
4
+ // src/paths.ts
5
+ import { mkdir } from "fs/promises";
6
+ import os from "os";
7
+ import path from "path";
8
+ var DEFAULT_ROOT_DIR = ".codelia";
9
+ var LAYOUT_ENV = "CODELIA_LAYOUT";
10
+ var XDG_DIR_NAME = "codelia";
11
+ var CONFIG_FILENAME = "config.json";
12
+ var AUTH_FILENAME = "auth.json";
13
+ var MCP_AUTH_FILENAME = "mcp-auth.json";
14
+ var CACHE_DIRNAME = "cache";
15
+ var TOOL_OUTPUT_CACHE_DIRNAME = "tool-output";
16
+ var SESSIONS_DIRNAME = "sessions";
17
+ var LOGS_DIRNAME = "logs";
18
+ function resolveStoragePaths(options = {}) {
19
+ if (options.rootOverride) {
20
+ return buildHomeLayout(options.rootOverride);
21
+ }
22
+ const layoutValue = options.layout ?? process.env[LAYOUT_ENV];
23
+ const layout = normalizeLayout(layoutValue);
24
+ if (layout === "xdg") {
25
+ return buildXdgLayout();
26
+ }
27
+ return buildHomeLayout(defaultHomeRoot());
28
+ }
29
+ async function ensureStorageDirs(paths) {
30
+ await Promise.all([
31
+ mkdir(paths.configDir, { recursive: true }),
32
+ mkdir(paths.cacheDir, { recursive: true }),
33
+ mkdir(paths.toolOutputCacheDir, { recursive: true }),
34
+ mkdir(paths.sessionsDir, { recursive: true }),
35
+ mkdir(paths.logsDir, { recursive: true })
36
+ ]);
37
+ }
38
+ function normalizeLayout(value) {
39
+ if (value && value.toLowerCase() === "xdg") return "xdg";
40
+ return "home";
41
+ }
42
+ function defaultHomeRoot() {
43
+ return path.join(os.homedir(), DEFAULT_ROOT_DIR);
44
+ }
45
+ function buildHomeLayout(root) {
46
+ const configDir = root;
47
+ return {
48
+ root,
49
+ configDir,
50
+ configFile: path.join(configDir, CONFIG_FILENAME),
51
+ authFile: path.join(configDir, AUTH_FILENAME),
52
+ mcpAuthFile: path.join(configDir, MCP_AUTH_FILENAME),
53
+ cacheDir: path.join(root, CACHE_DIRNAME),
54
+ toolOutputCacheDir: path.join(
55
+ root,
56
+ CACHE_DIRNAME,
57
+ TOOL_OUTPUT_CACHE_DIRNAME
58
+ ),
59
+ sessionsDir: path.join(root, SESSIONS_DIRNAME),
60
+ logsDir: path.join(root, LOGS_DIRNAME)
61
+ };
62
+ }
63
+ function buildXdgLayout() {
64
+ const home = os.homedir();
65
+ const configRoot = process.env.XDG_CONFIG_HOME ?? path.join(home, ".config");
66
+ const cacheRoot = process.env.XDG_CACHE_HOME ?? path.join(home, ".cache");
67
+ const stateRoot = process.env.XDG_STATE_HOME ?? path.join(home, ".local", "state");
68
+ const configDir = path.join(configRoot, XDG_DIR_NAME);
69
+ const cacheDir = path.join(cacheRoot, XDG_DIR_NAME);
70
+ const stateDir = path.join(stateRoot, XDG_DIR_NAME);
71
+ return {
72
+ root: stateDir,
73
+ configDir,
74
+ configFile: path.join(configDir, CONFIG_FILENAME),
75
+ authFile: path.join(configDir, AUTH_FILENAME),
76
+ mcpAuthFile: path.join(configDir, MCP_AUTH_FILENAME),
77
+ cacheDir,
78
+ toolOutputCacheDir: path.join(cacheDir, TOOL_OUTPUT_CACHE_DIRNAME),
79
+ sessionsDir: path.join(stateDir, SESSIONS_DIRNAME),
80
+ logsDir: path.join(stateDir, LOGS_DIRNAME)
81
+ };
82
+ }
83
+
84
+ // src/mcp-auth-store.ts
85
+ var MCP_AUTH_VERSION = 1;
86
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
87
+ var pickString = (value) => typeof value === "string" ? value : void 0;
88
+ var pickNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
89
+ var normalizeTokens = (value) => {
90
+ if (!isRecord(value)) return null;
91
+ const accessToken = pickString(value.access_token);
92
+ if (!accessToken) return null;
93
+ return {
94
+ access_token: accessToken,
95
+ ...pickString(value.refresh_token) ? { refresh_token: pickString(value.refresh_token) } : {},
96
+ ...pickNumber(value.expires_at) ? { expires_at: pickNumber(value.expires_at) } : {},
97
+ ...pickString(value.token_type) ? { token_type: pickString(value.token_type) } : {},
98
+ ...pickString(value.scope) ? { scope: pickString(value.scope) } : {},
99
+ ...pickString(value.client_id) ? { client_id: pickString(value.client_id) } : {},
100
+ ...pickString(value.client_secret) ? { client_secret: pickString(value.client_secret) } : {}
101
+ };
102
+ };
103
+ var McpAuthStore = class {
104
+ paths;
105
+ constructor(paths) {
106
+ this.paths = paths ?? resolveStoragePaths();
107
+ }
108
+ async load() {
109
+ try {
110
+ const text = await fs.readFile(this.paths.mcpAuthFile, "utf8");
111
+ const parsed = JSON.parse(text);
112
+ if (!isRecord(parsed) || parsed.version !== MCP_AUTH_VERSION) {
113
+ throw new Error("mcp-auth.json has unsupported version");
114
+ }
115
+ const serversRaw = isRecord(parsed.servers) ? parsed.servers : {};
116
+ const servers = {};
117
+ for (const [id, entry] of Object.entries(serversRaw)) {
118
+ const normalized = normalizeTokens(entry);
119
+ if (normalized) {
120
+ servers[id] = normalized;
121
+ }
122
+ }
123
+ return {
124
+ version: MCP_AUTH_VERSION,
125
+ servers
126
+ };
127
+ } catch (error) {
128
+ const err = error;
129
+ if (err?.code === "ENOENT") {
130
+ return { version: MCP_AUTH_VERSION, servers: {} };
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+ async save(auth) {
136
+ await ensureStorageDirs(this.paths);
137
+ const data = JSON.stringify(auth, null, 2);
138
+ await fs.writeFile(this.paths.mcpAuthFile, data, { mode: 384 });
139
+ try {
140
+ await fs.chmod(this.paths.mcpAuthFile, 384);
141
+ } catch {
142
+ }
143
+ }
144
+ };
145
+
146
+ // src/session-store.ts
147
+ import { promises as fs2 } from "fs";
148
+ import path2 from "path";
149
+ var pad2 = (value) => String(value).padStart(2, "0");
150
+ var resolveDateParts = (startedAt) => {
151
+ const parsed = new Date(startedAt);
152
+ const date = Number.isNaN(parsed.getTime()) ? /* @__PURE__ */ new Date() : parsed;
153
+ return {
154
+ year: String(date.getUTCFullYear()),
155
+ month: pad2(date.getUTCMonth() + 1),
156
+ day: pad2(date.getUTCDate())
157
+ };
158
+ };
159
+ var SessionStoreWriterImpl = class {
160
+ filePath;
161
+ ensureDir;
162
+ writeChain = Promise.resolve();
163
+ constructor(options) {
164
+ const paths = options.paths ?? resolveStoragePaths();
165
+ const parts = resolveDateParts(options.startedAt);
166
+ const baseDir = path2.join(
167
+ paths.sessionsDir,
168
+ parts.year,
169
+ parts.month,
170
+ parts.day
171
+ );
172
+ this.filePath = path2.join(baseDir, `${options.runId}.jsonl`);
173
+ this.ensureDir = fs2.mkdir(baseDir, { recursive: true }).then(() => {
174
+ });
175
+ }
176
+ append(record) {
177
+ let line;
178
+ try {
179
+ line = `${JSON.stringify(record)}
180
+ `;
181
+ } catch (error) {
182
+ return Promise.reject(error);
183
+ }
184
+ const write = this.writeChain.then(async () => {
185
+ await this.ensureDir;
186
+ await fs2.appendFile(this.filePath, line, "utf8");
187
+ });
188
+ this.writeChain = write.catch(() => {
189
+ });
190
+ return write;
191
+ }
192
+ };
193
+
194
+ // src/run-event-store.ts
195
+ var RunEventStoreFactoryImpl = class {
196
+ create(init) {
197
+ return new SessionStoreWriterImpl({
198
+ runId: init.runId,
199
+ startedAt: init.startedAt
200
+ });
201
+ }
202
+ };
203
+
204
+ // src/service.ts
205
+ var StoragePathServiceImpl = class {
206
+ resolvePaths(options = {}) {
207
+ return resolveStoragePaths(options);
208
+ }
209
+ };
210
+
211
+ // src/session-state.ts
212
+ import { promises as fs3 } from "fs";
213
+ import path3 from "path";
214
+ import { stringifyContent } from "@codelia/core";
215
+ var STATE_DIRNAME = "state";
216
+ var resolveStateDir = (paths) => path3.join(paths.sessionsDir, STATE_DIRNAME);
217
+ var extractLastUserMessage = (messages) => {
218
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
219
+ const message = messages[idx];
220
+ if (!message || typeof message !== "object") continue;
221
+ if ("role" in message && message.role === "user" && "content" in message) {
222
+ return stringifyContent(message.content, {
223
+ mode: "display"
224
+ });
225
+ }
226
+ }
227
+ return void 0;
228
+ };
229
+ var toSummary = (state) => ({
230
+ session_id: state.session_id,
231
+ updated_at: state.updated_at,
232
+ run_id: state.run_id,
233
+ message_count: Array.isArray(state.messages) ? state.messages.length : void 0,
234
+ last_user_message: Array.isArray(state.messages) ? extractLastUserMessage(state.messages) : void 0
235
+ });
236
+ var SessionStateStoreImpl = class {
237
+ stateDir;
238
+ ensureDir;
239
+ onError;
240
+ constructor(options = {}) {
241
+ const paths = options.paths ?? resolveStoragePaths();
242
+ this.stateDir = resolveStateDir(paths);
243
+ this.ensureDir = fs3.mkdir(this.stateDir, { recursive: true }).then(() => {
244
+ });
245
+ this.onError = options.onError;
246
+ }
247
+ resolvePath(sessionId) {
248
+ return path3.join(this.stateDir, `${sessionId}.json`);
249
+ }
250
+ async load(sessionId) {
251
+ try {
252
+ const file = await fs3.readFile(this.resolvePath(sessionId), "utf8");
253
+ const parsed = JSON.parse(file);
254
+ if (!parsed || typeof parsed !== "object") return null;
255
+ return parsed;
256
+ } catch (error) {
257
+ if (error.code === "ENOENT") {
258
+ return null;
259
+ }
260
+ this.onError?.(error, { action: "load", detail: sessionId });
261
+ throw error;
262
+ }
263
+ }
264
+ async save(state) {
265
+ await this.ensureDir;
266
+ const payload = `${JSON.stringify(state)}
267
+ `;
268
+ await fs3.writeFile(this.resolvePath(state.session_id), payload, "utf8");
269
+ }
270
+ async list() {
271
+ await this.ensureDir;
272
+ let entries;
273
+ try {
274
+ entries = await fs3.readdir(this.stateDir, {
275
+ withFileTypes: true,
276
+ encoding: "utf8"
277
+ });
278
+ } catch (error) {
279
+ this.onError?.(error, { action: "list" });
280
+ throw error;
281
+ }
282
+ const summaries = [];
283
+ for (const entry of entries) {
284
+ if (!entry.isFile()) continue;
285
+ if (!entry.name.endsWith(".json")) continue;
286
+ const filePath = path3.join(this.stateDir, entry.name);
287
+ try {
288
+ const contents = await fs3.readFile(filePath, "utf8");
289
+ const parsed = JSON.parse(contents);
290
+ if (!parsed || typeof parsed !== "object") continue;
291
+ if (!parsed.session_id || !parsed.updated_at) continue;
292
+ summaries.push(toSummary(parsed));
293
+ } catch (error) {
294
+ this.onError?.(error, { action: "parse", detail: filePath });
295
+ }
296
+ }
297
+ return summaries;
298
+ }
299
+ };
300
+
301
+ // src/tool-output-cache.ts
302
+ import { promises as fs4 } from "fs";
303
+ import path4 from "path";
304
+ var DEFAULT_MAX_MATCHES = 50;
305
+ var normalizeRefId = (refId) => refId.replace(/[^a-zA-Z0-9_-]/g, "_");
306
+ var formatLineNumber = (lineNumber) => String(lineNumber).padStart(5, " ");
307
+ var toLineNumbered = (lines, offset) => lines.map((line, index) => `${formatLineNumber(index + offset + 1)} ${line}`).join("\n");
308
+ var ToolOutputCacheStoreImpl = class {
309
+ baseDir;
310
+ constructor(options = {}) {
311
+ const paths = options.paths ?? resolveStoragePaths();
312
+ this.baseDir = paths.toolOutputCacheDir;
313
+ }
314
+ async save(record) {
315
+ const refId = normalizeRefId(record.tool_call_id);
316
+ const filePath = this.resolvePath(refId);
317
+ await fs4.mkdir(this.baseDir, { recursive: true });
318
+ await fs4.writeFile(filePath, record.content, "utf8");
319
+ const byteSize = Buffer.byteLength(record.content, "utf8");
320
+ const lineCount = record.content.split(/\r?\n/).length;
321
+ return { id: refId, byte_size: byteSize, line_count: lineCount };
322
+ }
323
+ async read(refId, options = {}) {
324
+ const filePath = this.resolvePath(refId);
325
+ const content = await fs4.readFile(filePath, "utf8");
326
+ const lines = content.split(/\r?\n/);
327
+ const offset = options.offset ?? 0;
328
+ const limit = options.limit ?? lines.length;
329
+ if (offset >= lines.length) {
330
+ return "Offset exceeds output length.";
331
+ }
332
+ const slice = lines.slice(offset, offset + limit);
333
+ return toLineNumbered(slice, offset);
334
+ }
335
+ async grep(refId, options) {
336
+ const filePath = this.resolvePath(refId);
337
+ const content = await fs4.readFile(filePath, "utf8");
338
+ const lines = content.split(/\r?\n/);
339
+ const regex = options.regex ? new RegExp(options.pattern) : new RegExp(options.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
340
+ const before = Math.max(0, options.before ?? 0);
341
+ const after = Math.max(0, options.after ?? 0);
342
+ const maxMatches = Math.max(1, options.max_matches ?? DEFAULT_MAX_MATCHES);
343
+ const results = [];
344
+ for (let i = 0; i < lines.length; i += 1) {
345
+ if (results.length >= maxMatches) break;
346
+ if (!regex.test(lines[i])) continue;
347
+ const start = Math.max(0, i - before);
348
+ const end = Math.min(lines.length, i + after + 1);
349
+ const snippet = toLineNumbered(lines.slice(start, end), start);
350
+ results.push(`ref:${normalizeRefId(refId)}
351
+ ${snippet}`);
352
+ }
353
+ if (!results.length) {
354
+ return `No matches for: ${options.pattern}`;
355
+ }
356
+ return results.length >= maxMatches ? `${results.join("\n\n")}
357
+ ... (truncated)` : results.join("\n\n");
358
+ }
359
+ resolvePath(refId) {
360
+ return path4.join(this.baseDir, `${normalizeRefId(refId)}.txt`);
361
+ }
362
+ };
363
+ export {
364
+ McpAuthStore,
365
+ RunEventStoreFactoryImpl,
366
+ SessionStateStoreImpl,
367
+ SessionStoreWriterImpl,
368
+ StoragePathServiceImpl,
369
+ ToolOutputCacheStoreImpl,
370
+ ensureStorageDirs,
371
+ resolveStoragePaths
372
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@codelia/storage",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "dependencies": {
23
+ "@codelia/core": "0.1.0"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }