@kimuson/claude-code-viewer 0.4.7 → 0.4.9

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/main.js CHANGED
@@ -6,7 +6,7 @@ import { resolve as resolve6 } from "node:path";
6
6
  import { NodeContext as NodeContext2 } from "@effect/platform-node";
7
7
  import { serve } from "@hono/node-server";
8
8
  import { serveStatic } from "@hono/node-server/serve-static";
9
- import { Effect as Effect42 } from "effect";
9
+ import { Effect as Effect45 } from "effect";
10
10
 
11
11
  // src/server/core/agent-session/index.ts
12
12
  import { Layer as Layer3 } from "effect";
@@ -371,11 +371,12 @@ import { Context as Context3, Effect as Effect3, Layer as Layer4, Ref } from "ef
371
371
  import { z as z17 } from "zod";
372
372
  var envSchema = z17.object({
373
373
  NODE_ENV: z17.enum(["development", "production", "test"]).optional().default("development"),
374
- CLAUDE_CODE_VIEWER_CC_EXECUTABLE_PATH: z17.string().optional(),
375
374
  GLOBAL_CLAUDE_DIR: z17.string().optional(),
376
375
  NEXT_PHASE: z17.string().optional(),
377
376
  PORT: z17.string().optional().default("3000").transform((val) => parseInt(val, 10)),
378
- PATH: z17.string().optional()
377
+ PATH: z17.string().optional(),
378
+ CLAUDE_CODE_VIEWER_CC_EXECUTABLE_PATH: z17.string().optional(),
379
+ CLAUDE_CODE_VIEWER_AUTH_PASSWORD: z17.string().optional()
379
380
  });
380
381
 
381
382
  // src/server/core/platform/services/EnvService.ts
@@ -5149,11 +5150,233 @@ var SchedulerController = class extends Context30.Tag("SchedulerController")() {
5149
5150
  }
5150
5151
  };
5151
5152
 
5153
+ // src/server/core/search/presentation/SearchController.ts
5154
+ import { Context as Context32, Effect as Effect38, Layer as Layer34 } from "effect";
5155
+
5156
+ // src/server/core/search/services/SearchService.ts
5157
+ import { FileSystem as FileSystem12, Path as Path15 } from "@effect/platform";
5158
+ import { Context as Context31, Effect as Effect37, Layer as Layer33, Ref as Ref11 } from "effect";
5159
+ import MiniSearch from "minisearch";
5160
+
5161
+ // src/server/core/search/functions/extractSearchableText.ts
5162
+ var extractSearchableText = (conversation) => {
5163
+ if (conversation.type === "x-error") {
5164
+ return null;
5165
+ }
5166
+ if (conversation.type === "user") {
5167
+ return extractUserText(conversation);
5168
+ }
5169
+ if (conversation.type === "assistant") {
5170
+ return extractAssistantText(conversation);
5171
+ }
5172
+ return null;
5173
+ };
5174
+ var extractUserText = (entry) => {
5175
+ const content = entry.message.content;
5176
+ if (typeof content === "string") {
5177
+ return content;
5178
+ }
5179
+ return content.map((item) => {
5180
+ if (typeof item === "string") return item;
5181
+ if ("text" in item && typeof item.text === "string") return item.text;
5182
+ return "";
5183
+ }).filter(Boolean).join(" ");
5184
+ };
5185
+ var extractAssistantText = (entry) => {
5186
+ return entry.message.content.filter((item) => {
5187
+ return item.type === "text" && "text" in item;
5188
+ }).map((item) => item.text).join(" ");
5189
+ };
5190
+
5191
+ // src/server/core/search/services/SearchService.ts
5192
+ var INDEX_TTL_MS = 6e4;
5193
+ var MAX_TEXT_LENGTH = 2e3;
5194
+ var MAX_ASSISTANT_TEXT_LENGTH = 500;
5195
+ var createMiniSearchIndex = () => new MiniSearch({
5196
+ fields: ["text"],
5197
+ storeFields: ["id"],
5198
+ searchOptions: {
5199
+ fuzzy: 0.2,
5200
+ prefix: true,
5201
+ boost: { text: 1 }
5202
+ }
5203
+ });
5204
+ var LayerImpl25 = Effect37.gen(function* () {
5205
+ const fs = yield* FileSystem12.FileSystem;
5206
+ const path = yield* Path15.Path;
5207
+ const context = yield* ApplicationContext;
5208
+ const indexCacheRef = yield* Ref11.make(null);
5209
+ const buildIndex = () => Effect37.gen(function* () {
5210
+ const { claudeProjectsDirPath } = context.claudeCodePaths;
5211
+ const dirExists = yield* fs.exists(claudeProjectsDirPath);
5212
+ if (!dirExists) {
5213
+ return { index: createMiniSearchIndex(), documents: /* @__PURE__ */ new Map() };
5214
+ }
5215
+ const projectEntries = yield* fs.readDirectory(claudeProjectsDirPath);
5216
+ const miniSearch = createMiniSearchIndex();
5217
+ const documentEffects = projectEntries.map(
5218
+ (projectEntry) => Effect37.gen(function* () {
5219
+ const projectPath = path.resolve(claudeProjectsDirPath, projectEntry);
5220
+ const stat = yield* fs.stat(projectPath).pipe(Effect37.catchAll(() => Effect37.succeed(null)));
5221
+ if (stat?.type !== "Directory") {
5222
+ return [];
5223
+ }
5224
+ const projectId = encodeProjectId(projectPath);
5225
+ const projectName = path.basename(projectPath);
5226
+ const sessionEntries = yield* fs.readDirectory(projectPath).pipe(Effect37.catchAll(() => Effect37.succeed([])));
5227
+ const sessionFiles = sessionEntries.filter(isRegularSessionFile);
5228
+ const sessionDocuments = yield* Effect37.all(
5229
+ sessionFiles.map(
5230
+ (sessionFile) => Effect37.gen(function* () {
5231
+ const sessionPath = path.resolve(projectPath, sessionFile);
5232
+ const sessionId = encodeSessionId(sessionPath);
5233
+ const content = yield* fs.readFileString(sessionPath).pipe(Effect37.catchAll(() => Effect37.succeed("")));
5234
+ if (!content) return [];
5235
+ const conversations = parseJsonl(content);
5236
+ const documents = [];
5237
+ for (let i = 0; i < conversations.length; i++) {
5238
+ const conversation = conversations[i];
5239
+ if (conversation === void 0) continue;
5240
+ if (conversation.type !== "user" && conversation.type !== "assistant") {
5241
+ continue;
5242
+ }
5243
+ let text = extractSearchableText(conversation);
5244
+ if (!text || text.length < 3) continue;
5245
+ const maxLen = conversation.type === "user" ? MAX_TEXT_LENGTH : MAX_ASSISTANT_TEXT_LENGTH;
5246
+ if (text.length > maxLen) {
5247
+ text = text.slice(0, maxLen);
5248
+ }
5249
+ documents.push({
5250
+ id: `${sessionId}:${i}`,
5251
+ projectId,
5252
+ projectName,
5253
+ sessionId,
5254
+ conversationIndex: i,
5255
+ type: conversation.type,
5256
+ text,
5257
+ timestamp: "timestamp" in conversation ? conversation.timestamp : ""
5258
+ });
5259
+ }
5260
+ return documents;
5261
+ })
5262
+ ),
5263
+ { concurrency: 20 }
5264
+ );
5265
+ return sessionDocuments.flat();
5266
+ })
5267
+ );
5268
+ const allDocuments = yield* Effect37.all(documentEffects, {
5269
+ concurrency: 10
5270
+ });
5271
+ const flatDocuments = allDocuments.flat();
5272
+ miniSearch.addAll(flatDocuments);
5273
+ const documentsMap = /* @__PURE__ */ new Map();
5274
+ for (const doc of flatDocuments) {
5275
+ documentsMap.set(doc.id, doc);
5276
+ }
5277
+ return { index: miniSearch, documents: documentsMap };
5278
+ });
5279
+ const getIndex = () => Effect37.gen(function* () {
5280
+ const cached = yield* Ref11.get(indexCacheRef);
5281
+ const now = Date.now();
5282
+ if (cached && now - cached.builtAt < INDEX_TTL_MS) {
5283
+ return { index: cached.index, documents: cached.documents };
5284
+ }
5285
+ const { index, documents } = yield* buildIndex();
5286
+ yield* Ref11.set(indexCacheRef, { index, documents, builtAt: now });
5287
+ return { index, documents };
5288
+ });
5289
+ const search = (query2, limit = 20, projectId) => Effect37.gen(function* () {
5290
+ const { claudeProjectsDirPath } = context.claudeCodePaths;
5291
+ const dirExists = yield* fs.exists(claudeProjectsDirPath);
5292
+ if (!dirExists) {
5293
+ return { results: [] };
5294
+ }
5295
+ const { index: miniSearch, documents } = yield* getIndex();
5296
+ const searchResults = miniSearch.search(query2).slice(0, limit * 2);
5297
+ const results = [];
5298
+ for (const result of searchResults) {
5299
+ if (results.length >= limit) break;
5300
+ const doc = documents.get(String(result.id));
5301
+ if (!doc) continue;
5302
+ if (projectId && doc.projectId !== projectId) continue;
5303
+ const score = doc.type === "user" ? result.score * 1.2 : result.score;
5304
+ const snippetLength = 150;
5305
+ const text = doc.text;
5306
+ const queryLower = query2.toLowerCase();
5307
+ const textLower = text.toLowerCase();
5308
+ const matchIndex = textLower.indexOf(queryLower);
5309
+ let snippet;
5310
+ if (matchIndex !== -1) {
5311
+ const start = Math.max(0, matchIndex - 50);
5312
+ const end = Math.min(text.length, start + snippetLength);
5313
+ snippet = (start > 0 ? "..." : "") + text.slice(start, end) + (end < text.length ? "..." : "");
5314
+ } else {
5315
+ snippet = text.slice(0, snippetLength) + (text.length > snippetLength ? "..." : "");
5316
+ }
5317
+ results.push({
5318
+ projectId: doc.projectId,
5319
+ projectName: doc.projectName,
5320
+ sessionId: doc.sessionId,
5321
+ conversationIndex: doc.conversationIndex,
5322
+ type: doc.type,
5323
+ snippet,
5324
+ timestamp: doc.timestamp,
5325
+ score
5326
+ });
5327
+ }
5328
+ return { results };
5329
+ });
5330
+ const invalidateIndex = () => Ref11.set(indexCacheRef, null);
5331
+ return {
5332
+ search,
5333
+ invalidateIndex
5334
+ };
5335
+ });
5336
+ var SearchService = class extends Context31.Tag("SearchService")() {
5337
+ static {
5338
+ this.Live = Layer33.effect(this, LayerImpl25);
5339
+ }
5340
+ };
5341
+
5342
+ // src/server/core/search/presentation/SearchController.ts
5343
+ var LayerImpl26 = Effect38.gen(function* () {
5344
+ const searchService = yield* SearchService;
5345
+ const search = (options) => Effect38.gen(function* () {
5346
+ const { query: query2, limit, projectId } = options;
5347
+ if (query2.trim().length < 2) {
5348
+ return {
5349
+ status: 400,
5350
+ response: {
5351
+ error: "Query must contain at least 2 non-whitespace characters"
5352
+ }
5353
+ };
5354
+ }
5355
+ const { results } = yield* searchService.search(
5356
+ query2.trim(),
5357
+ limit,
5358
+ projectId
5359
+ );
5360
+ return {
5361
+ status: 200,
5362
+ response: { results }
5363
+ };
5364
+ });
5365
+ return {
5366
+ search
5367
+ };
5368
+ });
5369
+ var SearchController = class extends Context32.Tag("SearchController")() {
5370
+ static {
5371
+ this.Live = Layer34.effect(this, LayerImpl26);
5372
+ }
5373
+ };
5374
+
5152
5375
  // src/server/core/session/presentation/SessionController.ts
5153
- import { Context as Context31, Effect as Effect38, Layer as Layer33 } from "effect";
5376
+ import { Context as Context33, Effect as Effect40, Layer as Layer35 } from "effect";
5154
5377
 
5155
5378
  // src/server/core/session/services/ExportService.ts
5156
- import { Effect as Effect37 } from "effect";
5379
+ import { Effect as Effect39 } from "effect";
5157
5380
  var escapeHtml = (text) => {
5158
5381
  const map = {
5159
5382
  "&": "&amp;",
@@ -5412,7 +5635,7 @@ var renderGroupedAssistantEntries = (entries) => {
5412
5635
  </div>
5413
5636
  `;
5414
5637
  };
5415
- var generateSessionHtml = (session, projectId) => Effect37.gen(function* () {
5638
+ var generateSessionHtml = (session, projectId) => Effect39.gen(function* () {
5416
5639
  const grouped = groupConsecutiveAssistantMessages(session.conversations);
5417
5640
  const conversationsHtml = grouped.map((group) => {
5418
5641
  if (group.type === "grouped") {
@@ -5932,9 +6155,9 @@ var generateSessionHtml = (session, projectId) => Effect37.gen(function* () {
5932
6155
  });
5933
6156
 
5934
6157
  // src/server/core/session/presentation/SessionController.ts
5935
- var LayerImpl25 = Effect38.gen(function* () {
6158
+ var LayerImpl27 = Effect40.gen(function* () {
5936
6159
  const sessionRepository = yield* SessionRepository;
5937
- const getSession = (options) => Effect38.gen(function* () {
6160
+ const getSession = (options) => Effect40.gen(function* () {
5938
6161
  const { projectId, sessionId } = options;
5939
6162
  const { session } = yield* sessionRepository.getSession(
5940
6163
  projectId,
@@ -5945,7 +6168,7 @@ var LayerImpl25 = Effect38.gen(function* () {
5945
6168
  response: { session }
5946
6169
  };
5947
6170
  });
5948
- const exportSessionHtml = (options) => Effect38.gen(function* () {
6171
+ const exportSessionHtml = (options) => Effect40.gen(function* () {
5949
6172
  const { projectId, sessionId } = options;
5950
6173
  const { session } = yield* sessionRepository.getSession(
5951
6174
  projectId,
@@ -5968,9 +6191,9 @@ var LayerImpl25 = Effect38.gen(function* () {
5968
6191
  exportSessionHtml
5969
6192
  };
5970
6193
  });
5971
- var SessionController = class extends Context31.Tag("SessionController")() {
6194
+ var SessionController = class extends Context33.Tag("SessionController")() {
5972
6195
  static {
5973
- this.Live = Layer33.effect(this, LayerImpl25);
6196
+ this.Live = Layer35.effect(this, LayerImpl27);
5974
6197
  }
5975
6198
  };
5976
6199
 
@@ -5979,12 +6202,12 @@ import { Hono } from "hono";
5979
6202
  var honoApp = new Hono();
5980
6203
 
5981
6204
  // src/server/hono/initialize.ts
5982
- import { Context as Context32, Effect as Effect39, Layer as Layer34, Ref as Ref11, Schedule as Schedule2 } from "effect";
5983
- var InitializeService = class extends Context32.Tag("InitializeService")() {
6205
+ import { Context as Context34, Effect as Effect41, Layer as Layer36, Ref as Ref12, Schedule as Schedule2 } from "effect";
6206
+ var InitializeService = class extends Context34.Tag("InitializeService")() {
5984
6207
  static {
5985
- this.Live = Layer34.effect(
6208
+ this.Live = Layer36.effect(
5986
6209
  this,
5987
- Effect39.gen(function* () {
6210
+ Effect41.gen(function* () {
5988
6211
  const eventBus = yield* EventBus;
5989
6212
  const fileWatcher = yield* FileWatcherService;
5990
6213
  const projectRepository = yield* ProjectRepository;
@@ -5992,22 +6215,22 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
5992
6215
  const projectMetaService = yield* ProjectMetaService;
5993
6216
  const sessionMetaService = yield* SessionMetaService;
5994
6217
  const virtualConversationDatabase = yield* VirtualConversationDatabase;
5995
- const listenersRef = yield* Ref11.make({});
6218
+ const listenersRef = yield* Ref12.make({});
5996
6219
  const startInitialization = () => {
5997
- return Effect39.gen(function* () {
6220
+ return Effect41.gen(function* () {
5998
6221
  yield* fileWatcher.startWatching();
5999
- const daemon = Effect39.repeat(
6222
+ const daemon = Effect41.repeat(
6000
6223
  eventBus.emit("heartbeat", {}),
6001
6224
  Schedule2.fixed("10 seconds")
6002
6225
  );
6003
6226
  console.log("start heartbeat");
6004
- yield* Effect39.forkDaemon(daemon);
6227
+ yield* Effect41.forkDaemon(daemon);
6005
6228
  console.log("after starting heartbeat fork");
6006
6229
  const onSessionChanged = (event) => {
6007
- Effect39.runFork(
6230
+ Effect41.runFork(
6008
6231
  projectMetaService.invalidateProject(event.projectId)
6009
6232
  );
6010
- Effect39.runFork(
6233
+ Effect41.runFork(
6011
6234
  sessionMetaService.invalidateSession(
6012
6235
  event.projectId,
6013
6236
  event.sessionId
@@ -6016,7 +6239,7 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
6016
6239
  };
6017
6240
  const onSessionProcessChanged = (event) => {
6018
6241
  if ((event.changed.type === "completed" || event.changed.type === "paused") && event.changed.sessionId !== void 0) {
6019
- Effect39.runFork(
6242
+ Effect41.runFork(
6020
6243
  virtualConversationDatabase.deleteVirtualConversations(
6021
6244
  event.changed.sessionId
6022
6245
  )
@@ -6024,18 +6247,18 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
6024
6247
  return;
6025
6248
  }
6026
6249
  };
6027
- yield* Ref11.set(listenersRef, {
6250
+ yield* Ref12.set(listenersRef, {
6028
6251
  sessionChanged: onSessionChanged,
6029
6252
  sessionProcessChanged: onSessionProcessChanged
6030
6253
  });
6031
6254
  yield* eventBus.on("sessionChanged", onSessionChanged);
6032
6255
  yield* eventBus.on("sessionProcessChanged", onSessionProcessChanged);
6033
- yield* Effect39.gen(function* () {
6256
+ yield* Effect41.gen(function* () {
6034
6257
  console.log("Initializing projects cache");
6035
6258
  const { projects } = yield* projectRepository.getProjects();
6036
6259
  console.log(`${projects.length} projects cache initialized`);
6037
6260
  console.log("Initializing sessions cache");
6038
- const results = yield* Effect39.all(
6261
+ const results = yield* Effect41.all(
6039
6262
  projects.map(
6040
6263
  (project) => sessionRepository.getSessions(project.id)
6041
6264
  ),
@@ -6047,13 +6270,13 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
6047
6270
  );
6048
6271
  console.log(`${totalSessions} sessions cache initialized`);
6049
6272
  }).pipe(
6050
- Effect39.catchAll(() => Effect39.void),
6051
- Effect39.withSpan("initialize-cache")
6273
+ Effect41.catchAll(() => Effect41.void),
6274
+ Effect41.withSpan("initialize-cache")
6052
6275
  );
6053
- }).pipe(Effect39.withSpan("start-initialization"));
6276
+ }).pipe(Effect41.withSpan("start-initialization"));
6054
6277
  };
6055
- const stopCleanup = () => Effect39.gen(function* () {
6056
- const listeners = yield* Ref11.get(listenersRef);
6278
+ const stopCleanup = () => Effect41.gen(function* () {
6279
+ const listeners = yield* Ref12.get(listenersRef);
6057
6280
  if (listeners.sessionChanged) {
6058
6281
  yield* eventBus.off("sessionChanged", listeners.sessionChanged);
6059
6282
  }
@@ -6063,7 +6286,7 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
6063
6286
  listeners.sessionProcessChanged
6064
6287
  );
6065
6288
  }
6066
- yield* Ref11.set(listenersRef, {});
6289
+ yield* Ref12.set(listenersRef, {});
6067
6290
  yield* fileWatcher.stop();
6068
6291
  });
6069
6292
  return {
@@ -6075,10 +6298,62 @@ var InitializeService = class extends Context32.Tag("InitializeService")() {
6075
6298
  }
6076
6299
  };
6077
6300
 
6301
+ // src/server/hono/middleware/auth.middleware.ts
6302
+ import { Context as Context35, Effect as Effect42, Layer as Layer37 } from "effect";
6303
+ import { getCookie } from "hono/cookie";
6304
+ import { createMiddleware } from "hono/factory";
6305
+ var generateSessionToken = (password) => {
6306
+ if (!password) return "";
6307
+ return Buffer.from(`ccv-session:${password}`).toString("base64");
6308
+ };
6309
+ var PUBLIC_API_ROUTES = [
6310
+ "/api/auth/login",
6311
+ "/api/auth/check",
6312
+ "/api/auth/logout",
6313
+ "/api/config",
6314
+ // Allow config access for theme/locale loading
6315
+ "/api/version"
6316
+ ];
6317
+ var LayerImpl28 = Effect42.gen(function* () {
6318
+ const envService = yield* EnvService;
6319
+ const anthPassword = yield* envService.getEnv(
6320
+ "CLAUDE_CODE_VIEWER_AUTH_PASSWORD"
6321
+ ) ?? void 0;
6322
+ const authEnabled = anthPassword !== void 0;
6323
+ const validSessionToken = generateSessionToken(anthPassword);
6324
+ const authMiddleware = createMiddleware(async (c, next) => {
6325
+ if (PUBLIC_API_ROUTES.includes(c.req.path)) {
6326
+ return next();
6327
+ }
6328
+ if (!c.req.path.startsWith("/api")) {
6329
+ return next();
6330
+ }
6331
+ if (!authEnabled) {
6332
+ return next();
6333
+ }
6334
+ const sessionToken = getCookie(c, "ccv-session");
6335
+ if (!sessionToken || sessionToken !== validSessionToken) {
6336
+ return c.json({ error: "Unauthorized" }, 401);
6337
+ }
6338
+ await next();
6339
+ });
6340
+ return {
6341
+ authEnabled,
6342
+ anthPassword,
6343
+ validSessionToken,
6344
+ authMiddleware
6345
+ };
6346
+ });
6347
+ var AuthMiddleware = class extends Context35.Tag("AuthMiddleware")() {
6348
+ static {
6349
+ this.Live = Layer37.effect(this, LayerImpl28);
6350
+ }
6351
+ };
6352
+
6078
6353
  // src/server/hono/route.ts
6079
6354
  import { zValidator } from "@hono/zod-validator";
6080
- import { Effect as Effect41, Runtime as Runtime3 } from "effect";
6081
- import { setCookie as setCookie2 } from "hono/cookie";
6355
+ import { Effect as Effect44, Runtime as Runtime3 } from "effect";
6356
+ import { deleteCookie, getCookie as getCookie3, setCookie as setCookie2 } from "hono/cookie";
6082
6357
  import { streamSSE } from "hono/streaming";
6083
6358
  import prexit from "prexit";
6084
6359
  import { z as z28 } from "zod";
@@ -6086,7 +6361,7 @@ import { z as z28 } from "zod";
6086
6361
  // package.json
6087
6362
  var package_default = {
6088
6363
  name: "@kimuson/claude-code-viewer",
6089
- version: "0.4.7",
6364
+ version: "0.4.9",
6090
6365
  type: "module",
6091
6366
  license: "MIT",
6092
6367
  repository: {
@@ -6123,15 +6398,21 @@ var package_default = {
6123
6398
  e2e: "./scripts/e2e/exec_e2e.sh",
6124
6399
  "e2e:start-server": "./scripts/e2e/start_server.sh",
6125
6400
  "e2e:capture-snapshots": "./scripts/e2e/capture_snapshots.sh",
6126
- "lingui:extract": "lingui extract --clean",
6127
- "lingui:compile": "lingui compile --typescript"
6401
+ "lingui:extract": "lingui extract --clean && node ./scripts/lingui-sort.js",
6402
+ "lingui:compile": "lingui compile --typescript",
6403
+ prepare: "lefthook install"
6128
6404
  },
6129
6405
  dependencies: {
6130
6406
  "@anthropic-ai/claude-agent-sdk": "0.1.30",
6131
6407
  "@anthropic-ai/claude-code": "2.0.24",
6132
6408
  "@anthropic-ai/sdk": "0.67.0",
6133
- "@effect/platform": "0.93.2",
6134
- "@effect/platform-node": "0.100.0",
6409
+ "@effect/cluster": "0.55.0",
6410
+ "@effect/experimental": "0.57.11",
6411
+ "@effect/platform": "0.93.6",
6412
+ "@effect/platform-node": "0.103.0",
6413
+ "@effect/rpc": "0.72.2",
6414
+ "@effect/sql": "0.48.6",
6415
+ "@effect/workflow": "0.15.1",
6135
6416
  "@hono/node-server": "1.19.5",
6136
6417
  "@hono/zod-validator": "0.7.4",
6137
6418
  "@lingui/core": "5.5.1",
@@ -6141,7 +6422,7 @@ var package_default = {
6141
6422
  "@radix-ui/react-collapsible": "1.1.12",
6142
6423
  "@radix-ui/react-dialog": "1.1.15",
6143
6424
  "@radix-ui/react-hover-card": "1.1.15",
6144
- "@radix-ui/react-popover": "^1.1.15",
6425
+ "@radix-ui/react-popover": "1.1.15",
6145
6426
  "@radix-ui/react-select": "2.2.6",
6146
6427
  "@radix-ui/react-slot": "1.2.3",
6147
6428
  "@radix-ui/react-tabs": "1.1.13",
@@ -6154,27 +6435,29 @@ var package_default = {
6154
6435
  "class-variance-authority": "0.7.1",
6155
6436
  clsx: "2.1.1",
6156
6437
  "date-fns": "4.1.0",
6157
- effect: "3.19.3",
6438
+ effect: "3.19.9",
6158
6439
  "es-toolkit": "1.41.0",
6159
6440
  hono: "4.10.3",
6160
6441
  jotai: "2.15.0",
6161
6442
  "lucide-react": "0.548.0",
6443
+ minisearch: "7.2.0",
6162
6444
  "parse-git-diff": "0.0.19",
6163
6445
  prexit: "2.3.0",
6164
6446
  react: "19.2.0",
6165
6447
  "react-dom": "19.2.0",
6166
6448
  "react-error-boundary": "6.0.0",
6167
- "react-helmet-async": "^2.0.5",
6449
+ "react-helmet-async": "2.0.5",
6168
6450
  "react-markdown": "10.1.0",
6169
6451
  "react-syntax-highlighter": "15.6.6",
6170
6452
  "remark-gfm": "4.0.1",
6171
6453
  sonner: "2.0.7",
6172
6454
  "tailwind-merge": "3.3.1",
6173
6455
  ulid: "3.0.1",
6174
- zod: "4.1.12"
6456
+ zod: "4.1.13"
6175
6457
  },
6176
6458
  devDependencies: {
6177
6459
  "@biomejs/biome": "2.3.1",
6460
+ "@effect/language-service": "0.60.0",
6178
6461
  "@lingui/cli": "5.5.1",
6179
6462
  "@lingui/conf": "5.5.1",
6180
6463
  "@lingui/format-json": "5.5.1",
@@ -6190,6 +6473,7 @@ var package_default = {
6190
6473
  "@vitejs/plugin-react-swc": "4.2.0",
6191
6474
  dotenv: "17.2.3",
6192
6475
  esbuild: "0.25.11",
6476
+ lefthook: "2.0.8",
6193
6477
  "npm-run-all2": "8.0.4",
6194
6478
  playwright: "1.56.1",
6195
6479
  "release-it": "19.0.5",
@@ -6351,16 +6635,16 @@ var userConfigSchema = z27.object({
6351
6635
  var defaultUserConfig = userConfigSchema.parse({});
6352
6636
 
6353
6637
  // src/server/lib/effect/toEffectResponse.ts
6354
- import { Effect as Effect40 } from "effect";
6638
+ import { Effect as Effect43 } from "effect";
6355
6639
  var effectToResponse = async (ctx, effect) => {
6356
- const result = await Effect40.runPromise(effect);
6640
+ const result = await Effect43.runPromise(effect);
6357
6641
  const result2 = ctx.json(result.response, result.status);
6358
6642
  return result2;
6359
6643
  };
6360
6644
 
6361
6645
  // src/server/hono/middleware/config.middleware.ts
6362
- import { getCookie, setCookie } from "hono/cookie";
6363
- import { createMiddleware } from "hono/factory";
6646
+ import { getCookie as getCookie2, setCookie } from "hono/cookie";
6647
+ import { createMiddleware as createMiddleware2 } from "hono/factory";
6364
6648
 
6365
6649
  // src/server/lib/config/parseUserConfig.ts
6366
6650
  var parseUserConfig = (configJson) => {
@@ -6375,9 +6659,9 @@ var parseUserConfig = (configJson) => {
6375
6659
  };
6376
6660
 
6377
6661
  // src/server/hono/middleware/config.middleware.ts
6378
- var configMiddleware = createMiddleware(
6662
+ var configMiddleware = createMiddleware2(
6379
6663
  async (c, next) => {
6380
- const cookie = getCookie(c, "ccv-config");
6664
+ const cookie = getCookie2(c, "ccv-config");
6381
6665
  const parsed = parseUserConfig(cookie);
6382
6666
  if (cookie === void 0) {
6383
6667
  const preferredLocale = detectLocaleFromAcceptLanguage(c.req.header("accept-language")) ?? DEFAULT_LOCALE;
@@ -6396,7 +6680,7 @@ var configMiddleware = createMiddleware(
6396
6680
  );
6397
6681
 
6398
6682
  // src/server/hono/route.ts
6399
- var routes = (app) => Effect41.gen(function* () {
6683
+ var routes = (app) => Effect44.gen(function* () {
6400
6684
  const projectController = yield* ProjectController;
6401
6685
  const sessionController = yield* SessionController;
6402
6686
  const agentSessionController = yield* AgentSessionController;
@@ -6408,24 +6692,60 @@ var routes = (app) => Effect41.gen(function* () {
6408
6692
  const claudeCodeController = yield* ClaudeCodeController;
6409
6693
  const schedulerController = yield* SchedulerController;
6410
6694
  const featureFlagController = yield* FeatureFlagController;
6695
+ const searchController = yield* SearchController;
6411
6696
  const envService = yield* EnvService;
6412
6697
  const userConfigService = yield* UserConfigService;
6413
6698
  const claudeCodeLifeCycleService = yield* ClaudeCodeLifeCycleService;
6414
6699
  const initializeService = yield* InitializeService;
6415
- const runtime = yield* Effect41.runtime();
6700
+ const { authMiddleware, validSessionToken, authEnabled, anthPassword } = yield* AuthMiddleware;
6701
+ const runtime = yield* Effect44.runtime();
6416
6702
  if ((yield* envService.getEnv("NEXT_PHASE")) !== "phase-production-build") {
6417
6703
  yield* initializeService.startInitialization();
6418
6704
  prexit(async () => {
6419
6705
  await Runtime3.runPromise(runtime)(initializeService.stopCleanup());
6420
6706
  });
6421
6707
  }
6422
- return app.use(configMiddleware).use(async (c, next) => {
6423
- await Effect41.runPromise(
6708
+ return app.use(configMiddleware).use(authMiddleware).use(async (c, next) => {
6709
+ await Effect44.runPromise(
6424
6710
  userConfigService.setUserConfig({
6425
6711
  ...c.get("userConfig")
6426
6712
  })
6427
6713
  );
6428
6714
  await next();
6715
+ }).post(
6716
+ "/api/auth/login",
6717
+ zValidator("json", z28.object({ password: z28.string() })),
6718
+ async (c) => {
6719
+ const { password } = c.req.valid("json");
6720
+ if (!authEnabled) {
6721
+ return c.json(
6722
+ {
6723
+ error: "Authentication not configured. Set CLAUDE_CODE_VIEWER_AUTH_PASSWORD environment variable."
6724
+ },
6725
+ 500
6726
+ );
6727
+ }
6728
+ if (password !== anthPassword) {
6729
+ return c.json({ error: "Invalid password" }, 401);
6730
+ }
6731
+ setCookie2(c, "ccv-session", validSessionToken, {
6732
+ httpOnly: true,
6733
+ secure: false,
6734
+ // Set to true in production with HTTPS
6735
+ sameSite: "Lax",
6736
+ path: "/",
6737
+ maxAge: 60 * 60 * 24 * 7
6738
+ // 7 days
6739
+ });
6740
+ return c.json({ success: true });
6741
+ }
6742
+ ).post("/api/auth/logout", async (c) => {
6743
+ deleteCookie(c, "ccv-session", { path: "/" });
6744
+ return c.json({ success: true });
6745
+ }).get("/api/auth/check", async (c) => {
6746
+ const sessionToken = getCookie3(c, "ccv-session");
6747
+ const isAuthenticated = authEnabled ? sessionToken === validSessionToken : true;
6748
+ return c.json({ authenticated: isAuthenticated, authEnabled });
6429
6749
  }).get("/api/config", async (c) => {
6430
6750
  return c.json({
6431
6751
  config: c.get("userConfig")
@@ -6455,7 +6775,7 @@ var routes = (app) => Effect41.gen(function* () {
6455
6775
  projectController.getProject({
6456
6776
  ...c.req.param(),
6457
6777
  ...c.req.valid("query")
6458
- }).pipe(Effect41.provide(runtime))
6778
+ }).pipe(Effect44.provide(runtime))
6459
6779
  );
6460
6780
  return response;
6461
6781
  }
@@ -6472,7 +6792,7 @@ var routes = (app) => Effect41.gen(function* () {
6472
6792
  c,
6473
6793
  projectController.createProject({
6474
6794
  ...c.req.valid("json")
6475
- }).pipe(Effect41.provide(runtime))
6795
+ }).pipe(Effect44.provide(runtime))
6476
6796
  );
6477
6797
  return response;
6478
6798
  }
@@ -6481,13 +6801,13 @@ var routes = (app) => Effect41.gen(function* () {
6481
6801
  c,
6482
6802
  projectController.getProjectLatestSession({
6483
6803
  ...c.req.param()
6484
- }).pipe(Effect41.provide(runtime))
6804
+ }).pipe(Effect44.provide(runtime))
6485
6805
  );
6486
6806
  return response;
6487
6807
  }).get("/api/projects/:projectId/sessions/:sessionId", async (c) => {
6488
6808
  const response = await effectToResponse(
6489
6809
  c,
6490
- sessionController.getSession({ ...c.req.param() }).pipe(Effect41.provide(runtime))
6810
+ sessionController.getSession({ ...c.req.param() }).pipe(Effect44.provide(runtime))
6491
6811
  );
6492
6812
  return response;
6493
6813
  }).get(
@@ -6495,7 +6815,7 @@ var routes = (app) => Effect41.gen(function* () {
6495
6815
  async (c) => {
6496
6816
  const response = await effectToResponse(
6497
6817
  c,
6498
- sessionController.exportSessionHtml({ ...c.req.param() }).pipe(Effect41.provide(runtime))
6818
+ sessionController.exportSessionHtml({ ...c.req.param() }).pipe(Effect44.provide(runtime))
6499
6819
  );
6500
6820
  return response;
6501
6821
  }
@@ -6506,7 +6826,7 @@ var routes = (app) => Effect41.gen(function* () {
6506
6826
  agentSessionController.getAgentSession({
6507
6827
  projectId,
6508
6828
  agentId
6509
- }).pipe(Effect41.provide(runtime))
6829
+ }).pipe(Effect44.provide(runtime))
6510
6830
  );
6511
6831
  return response;
6512
6832
  }).get("/api/projects/:projectId/git/current-revisions", async (c) => {
@@ -6514,7 +6834,7 @@ var routes = (app) => Effect41.gen(function* () {
6514
6834
  c,
6515
6835
  gitController.getCurrentRevisions({
6516
6836
  ...c.req.param()
6517
- }).pipe(Effect41.provide(runtime))
6837
+ }).pipe(Effect44.provide(runtime))
6518
6838
  );
6519
6839
  return response;
6520
6840
  }).post(
@@ -6532,7 +6852,7 @@ var routes = (app) => Effect41.gen(function* () {
6532
6852
  gitController.getGitDiff({
6533
6853
  ...c.req.param(),
6534
6854
  ...c.req.valid("json")
6535
- }).pipe(Effect41.provide(runtime))
6855
+ }).pipe(Effect44.provide(runtime))
6536
6856
  );
6537
6857
  return response;
6538
6858
  }
@@ -6545,7 +6865,7 @@ var routes = (app) => Effect41.gen(function* () {
6545
6865
  gitController.commitFiles({
6546
6866
  ...c.req.param(),
6547
6867
  ...c.req.valid("json")
6548
- }).pipe(Effect41.provide(runtime))
6868
+ }).pipe(Effect44.provide(runtime))
6549
6869
  );
6550
6870
  return response;
6551
6871
  }
@@ -6558,7 +6878,7 @@ var routes = (app) => Effect41.gen(function* () {
6558
6878
  gitController.pushCommits({
6559
6879
  ...c.req.param(),
6560
6880
  ...c.req.valid("json")
6561
- }).pipe(Effect41.provide(runtime))
6881
+ }).pipe(Effect44.provide(runtime))
6562
6882
  );
6563
6883
  return response;
6564
6884
  }
@@ -6571,7 +6891,7 @@ var routes = (app) => Effect41.gen(function* () {
6571
6891
  gitController.commitAndPush({
6572
6892
  ...c.req.param(),
6573
6893
  ...c.req.valid("json")
6574
- }).pipe(Effect41.provide(runtime))
6894
+ }).pipe(Effect44.provide(runtime))
6575
6895
  );
6576
6896
  return response;
6577
6897
  }
@@ -6580,7 +6900,7 @@ var routes = (app) => Effect41.gen(function* () {
6580
6900
  c,
6581
6901
  claudeCodeController.getClaudeCommands({
6582
6902
  ...c.req.param()
6583
- }).pipe(Effect41.provide(runtime))
6903
+ }).pipe(Effect44.provide(runtime))
6584
6904
  );
6585
6905
  return response;
6586
6906
  }).get("/api/projects/:projectId/mcp/list", async (c) => {
@@ -6588,19 +6908,19 @@ var routes = (app) => Effect41.gen(function* () {
6588
6908
  c,
6589
6909
  claudeCodeController.getMcpListRoute({
6590
6910
  ...c.req.param()
6591
- }).pipe(Effect41.provide(runtime))
6911
+ }).pipe(Effect44.provide(runtime))
6592
6912
  );
6593
6913
  return response;
6594
6914
  }).get("/api/cc/meta", async (c) => {
6595
6915
  const response = await effectToResponse(
6596
6916
  c,
6597
- claudeCodeController.getClaudeCodeMeta().pipe(Effect41.provide(runtime))
6917
+ claudeCodeController.getClaudeCodeMeta().pipe(Effect44.provide(runtime))
6598
6918
  );
6599
6919
  return response;
6600
6920
  }).get("/api/cc/features", async (c) => {
6601
6921
  const response = await effectToResponse(
6602
6922
  c,
6603
- claudeCodeController.getAvailableFeatures().pipe(Effect41.provide(runtime))
6923
+ claudeCodeController.getAvailableFeatures().pipe(Effect44.provide(runtime))
6604
6924
  );
6605
6925
  return response;
6606
6926
  }).get("/api/cc/session-processes", async (c) => {
@@ -6644,7 +6964,7 @@ var routes = (app) => Effect41.gen(function* () {
6644
6964
  claudeCodeSessionProcessController.continueSessionProcess({
6645
6965
  ...c.req.param(),
6646
6966
  ...c.req.valid("json")
6647
- }).pipe(Effect41.provide(runtime))
6967
+ }).pipe(Effect44.provide(runtime))
6648
6968
  );
6649
6969
  return response;
6650
6970
  }
@@ -6653,7 +6973,7 @@ var routes = (app) => Effect41.gen(function* () {
6653
6973
  zValidator("json", z28.object({ projectId: z28.string() })),
6654
6974
  async (c) => {
6655
6975
  const { sessionProcessId } = c.req.param();
6656
- void Effect41.runFork(
6976
+ void Effect44.runFork(
6657
6977
  claudeCodeLifeCycleService.abortTask(sessionProcessId)
6658
6978
  );
6659
6979
  return c.json({ message: "Task aborted" });
@@ -6681,7 +7001,7 @@ var routes = (app) => Effect41.gen(function* () {
6681
7001
  c,
6682
7002
  async (rawStream) => {
6683
7003
  await Runtime3.runPromise(runtime)(
6684
- sseController.handleSSE(rawStream).pipe(Effect41.provide(TypeSafeSSE.make(rawStream)))
7004
+ sseController.handleSSE(rawStream).pipe(Effect44.provide(TypeSafeSSE.make(rawStream)))
6685
7005
  );
6686
7006
  },
6687
7007
  async (err) => {
@@ -6691,7 +7011,7 @@ var routes = (app) => Effect41.gen(function* () {
6691
7011
  }).get("/api/scheduler/jobs", async (c) => {
6692
7012
  const response = await effectToResponse(
6693
7013
  c,
6694
- schedulerController.getJobs().pipe(Effect41.provide(runtime))
7014
+ schedulerController.getJobs().pipe(Effect44.provide(runtime))
6695
7015
  );
6696
7016
  return response;
6697
7017
  }).post(
@@ -6702,7 +7022,7 @@ var routes = (app) => Effect41.gen(function* () {
6702
7022
  c,
6703
7023
  schedulerController.addJob({
6704
7024
  job: c.req.valid("json")
6705
- }).pipe(Effect41.provide(runtime))
7025
+ }).pipe(Effect44.provide(runtime))
6706
7026
  );
6707
7027
  return response;
6708
7028
  }
@@ -6715,7 +7035,7 @@ var routes = (app) => Effect41.gen(function* () {
6715
7035
  schedulerController.updateJob({
6716
7036
  id: c.req.param("id"),
6717
7037
  job: c.req.valid("json")
6718
- }).pipe(Effect41.provide(runtime))
7038
+ }).pipe(Effect44.provide(runtime))
6719
7039
  );
6720
7040
  return response;
6721
7041
  }
@@ -6724,7 +7044,7 @@ var routes = (app) => Effect41.gen(function* () {
6724
7044
  c,
6725
7045
  schedulerController.deleteJob({
6726
7046
  id: c.req.param("id")
6727
- }).pipe(Effect41.provide(runtime))
7047
+ }).pipe(Effect44.provide(runtime))
6728
7048
  );
6729
7049
  return response;
6730
7050
  }).get(
@@ -6763,10 +7083,28 @@ var routes = (app) => Effect41.gen(function* () {
6763
7083
  );
6764
7084
  return response;
6765
7085
  }
7086
+ ).get(
7087
+ "/api/search",
7088
+ zValidator(
7089
+ "query",
7090
+ z28.object({
7091
+ q: z28.string().min(2),
7092
+ limit: z28.string().optional().transform((val) => val ? parseInt(val, 10) : void 0),
7093
+ projectId: z28.string().optional()
7094
+ })
7095
+ ),
7096
+ async (c) => {
7097
+ const { q, limit, projectId } = c.req.valid("query");
7098
+ const response = await effectToResponse(
7099
+ c,
7100
+ searchController.search({ query: q, limit, projectId }).pipe(Effect44.provide(runtime))
7101
+ );
7102
+ return response;
7103
+ }
6766
7104
  ).get("/api/flags", async (c) => {
6767
7105
  const response = await effectToResponse(
6768
7106
  c,
6769
- featureFlagController.getFlags().pipe(Effect41.provide(runtime))
7107
+ featureFlagController.getFlags().pipe(Effect44.provide(runtime))
6770
7108
  );
6771
7109
  return response;
6772
7110
  });
@@ -6774,13 +7112,13 @@ var routes = (app) => Effect41.gen(function* () {
6774
7112
 
6775
7113
  // src/server/lib/effect/layers.ts
6776
7114
  import { NodeContext } from "@effect/platform-node";
6777
- import { Layer as Layer35 } from "effect";
6778
- var platformLayer = Layer35.mergeAll(
7115
+ import { Layer as Layer38 } from "effect";
7116
+ var platformLayer = Layer38.mergeAll(
6779
7117
  ApplicationContext.Live,
6780
7118
  UserConfigService.Live,
6781
7119
  EventBus.Live,
6782
7120
  EnvService.Live
6783
- ).pipe(Layer35.provide(EnvService.Live), Layer35.provide(NodeContext.layer));
7121
+ ).pipe(Layer38.provide(EnvService.Live), Layer38.provide(NodeContext.layer));
6784
7122
 
6785
7123
  // src/server/main.ts
6786
7124
  var isDevelopment = process.env.NODE_ENV === "development";
@@ -6803,44 +7141,47 @@ if (!isDevelopment) {
6803
7141
  }
6804
7142
  var program = routes(honoApp).pipe(
6805
7143
  /** Presentation */
6806
- Effect42.provide(ProjectController.Live),
6807
- Effect42.provide(SessionController.Live),
6808
- Effect42.provide(AgentSessionController.Live),
6809
- Effect42.provide(GitController.Live),
6810
- Effect42.provide(ClaudeCodeController.Live),
6811
- Effect42.provide(ClaudeCodeSessionProcessController.Live),
6812
- Effect42.provide(ClaudeCodePermissionController.Live),
6813
- Effect42.provide(FileSystemController.Live),
6814
- Effect42.provide(SSEController.Live),
6815
- Effect42.provide(SchedulerController.Live),
6816
- Effect42.provide(FeatureFlagController.Live)
7144
+ Effect45.provide(ProjectController.Live),
7145
+ Effect45.provide(SessionController.Live),
7146
+ Effect45.provide(AgentSessionController.Live),
7147
+ Effect45.provide(GitController.Live),
7148
+ Effect45.provide(ClaudeCodeController.Live),
7149
+ Effect45.provide(ClaudeCodeSessionProcessController.Live),
7150
+ Effect45.provide(ClaudeCodePermissionController.Live),
7151
+ Effect45.provide(FileSystemController.Live),
7152
+ Effect45.provide(SSEController.Live),
7153
+ Effect45.provide(SchedulerController.Live),
7154
+ Effect45.provide(FeatureFlagController.Live),
7155
+ Effect45.provide(SearchController.Live)
6817
7156
  ).pipe(
6818
7157
  /** Application */
6819
- Effect42.provide(InitializeService.Live),
6820
- Effect42.provide(FileWatcherService.Live)
7158
+ Effect45.provide(InitializeService.Live),
7159
+ Effect45.provide(FileWatcherService.Live),
7160
+ Effect45.provide(AuthMiddleware.Live)
6821
7161
  ).pipe(
6822
7162
  /** Domain */
6823
- Effect42.provide(ClaudeCodeLifeCycleService.Live),
6824
- Effect42.provide(ClaudeCodePermissionService.Live),
6825
- Effect42.provide(ClaudeCodeSessionProcessService.Live),
6826
- Effect42.provide(ClaudeCodeService.Live),
6827
- Effect42.provide(GitService.Live),
6828
- Effect42.provide(SchedulerService.Live),
6829
- Effect42.provide(SchedulerConfigBaseDir.Live)
7163
+ Effect45.provide(ClaudeCodeLifeCycleService.Live),
7164
+ Effect45.provide(ClaudeCodePermissionService.Live),
7165
+ Effect45.provide(ClaudeCodeSessionProcessService.Live),
7166
+ Effect45.provide(ClaudeCodeService.Live),
7167
+ Effect45.provide(GitService.Live),
7168
+ Effect45.provide(SchedulerService.Live),
7169
+ Effect45.provide(SchedulerConfigBaseDir.Live),
7170
+ Effect45.provide(SearchService.Live)
6830
7171
  ).pipe(
6831
7172
  /** Infrastructure */
6832
- Effect42.provide(ProjectRepository.Live),
6833
- Effect42.provide(SessionRepository.Live),
6834
- Effect42.provide(ProjectMetaService.Live),
6835
- Effect42.provide(SessionMetaService.Live),
6836
- Effect42.provide(VirtualConversationDatabase.Live),
6837
- Effect42.provide(AgentSessionLayer)
7173
+ Effect45.provide(ProjectRepository.Live),
7174
+ Effect45.provide(SessionRepository.Live),
7175
+ Effect45.provide(ProjectMetaService.Live),
7176
+ Effect45.provide(SessionMetaService.Live),
7177
+ Effect45.provide(VirtualConversationDatabase.Live),
7178
+ Effect45.provide(AgentSessionLayer)
6838
7179
  ).pipe(
6839
7180
  /** Platform */
6840
- Effect42.provide(platformLayer),
6841
- Effect42.provide(NodeContext2.layer)
7181
+ Effect45.provide(platformLayer),
7182
+ Effect45.provide(NodeContext2.layer)
6842
7183
  );
6843
- await Effect42.runPromise(program);
7184
+ await Effect45.runPromise(program);
6844
7185
  var port = isDevelopment ? (
6845
7186
  // biome-ignore lint/style/noProcessEnv: allow only here
6846
7187
  process.env.DEV_BE_PORT ?? "3401"