@claude-sessions/core 0.4.4 → 0.4.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,14 +33,10 @@ const sessions = await Effect.runPromise(listSessions(projectName))
33
33
  const messages = await Effect.runPromise(readSession(projectName, sessionId))
34
34
 
35
35
  // Search sessions (title only)
36
- const titleResults = await Effect.runPromise(
37
- searchSessions('query', { searchContent: false })
38
- )
36
+ const titleResults = await Effect.runPromise(searchSessions('query', { searchContent: false }))
39
37
 
40
38
  // Search sessions (title + content)
41
- const allResults = await Effect.runPromise(
42
- searchSessions('query', { searchContent: true })
43
- )
39
+ const allResults = await Effect.runPromise(searchSessions('query', { searchContent: true }))
44
40
 
45
41
  // Rename a session
46
42
  await Effect.runPromise(renameSession(projectName, sessionId, 'New Title'))
@@ -53,51 +49,52 @@ await Effect.runPromise(deleteSession(projectName, sessionId))
53
49
 
54
50
  ### Session Operations
55
51
 
56
- | Function | Description |
57
- |----------|-------------|
58
- | `listProjects()` | List all Claude Code projects |
59
- | `listSessions(projectName)` | List sessions in a project |
60
- | `readSession(projectName, sessionId)` | Read all messages in a session |
61
- | `searchSessions(query, options?)` | Search sessions by title or content |
62
- | `renameSession(projectName, sessionId, title)` | Rename a session |
63
- | `deleteSession(projectName, sessionId)` | Delete a session (backup) |
64
- | `deleteMessage(projectName, sessionId, uuid)` | Delete a message |
65
- | `splitSession(projectName, sessionId, uuid)` | Split session at a message |
66
- | `moveSession(source, sessionId, target)` | Move session to another project |
67
- | `getSessionFiles(projectName, sessionId)` | Get files changed in session |
68
- | `previewCleanup(projectName?)` | Preview cleanup candidates |
69
- | `clearSessions(options)` | Clear empty/invalid sessions |
52
+ | Function | Description |
53
+ | ---------------------------------------------- | ----------------------------------- |
54
+ | `listProjects()` | List all Claude Code projects |
55
+ | `listSessions(projectName)` | List sessions in a project |
56
+ | `readSession(projectName, sessionId)` | Read all messages in a session |
57
+ | `searchSessions(query, options?)` | Search sessions by title or content |
58
+ | `renameSession(projectName, sessionId, title)` | Rename a session |
59
+ | `deleteSession(projectName, sessionId)` | Delete a session (backup) |
60
+ | `deleteMessage(projectName, sessionId, uuid)` | Delete a message |
61
+ | `splitSession(projectName, sessionId, uuid)` | Split session at a message |
62
+ | `moveSession(source, sessionId, target)` | Move session to another project |
63
+ | `getSessionFiles(projectName, sessionId)` | Get files changed in session |
64
+ | `previewCleanup(projectName?)` | Preview cleanup candidates |
65
+ | `clearSessions(options)` | Clear empty/invalid sessions |
70
66
 
71
67
  ### Path Utilities
72
68
 
73
- | Function | Description |
74
- |----------|-------------|
75
- | `getSessionsDir()` | Get Claude Code sessions directory |
76
- | `getTodosDir()` | Get Claude Code todos directory |
77
- | `folderNameToDisplayPath(name)` | Convert folder name to display path |
78
- | `displayPathToFolderName(path)` | Convert display path to folder name |
69
+ | Function | Description |
70
+ | ----------------------------------- | ----------------------------------------------------------------- |
71
+ | `getSessionsDir()` | Get Claude Code sessions directory |
72
+ | `getTodosDir()` | Get Claude Code todos directory |
73
+ | `folderNameToDisplayPath(name)` | Convert folder name to display path |
74
+ | `pathToFolderName(path)` | Convert absolute path to folder name (official Claude Code logic) |
75
+ | `resolvePathFromClaudeConfig(name)` | Resolve folder name to path via ~/.claude.json |
79
76
 
80
77
  ### Message Utilities
81
78
 
82
- | Function | Description |
83
- |----------|-------------|
84
- | `extractTextContent(message)` | Extract text from message content |
85
- | `extractTitle(messages)` | Extract session title from messages |
79
+ | Function | Description |
80
+ | --------------------------------- | ------------------------------------------ |
81
+ | `extractTextContent(message)` | Extract text from message content |
82
+ | `extractTitle(messages)` | Extract session title from messages |
86
83
  | `isInvalidApiKeyMessage(message)` | Check if message has invalid API key error |
87
- | `isContinuationSummary(message)` | Check if message is a continuation summary |
84
+ | `isContinuationSummary(message)` | Check if message is a continuation summary |
88
85
 
89
86
  ### Agent & Todo Management
90
87
 
91
- | Function | Description |
92
- |----------|-------------|
88
+ | Function | Description |
89
+ | ------------------------------------------ | ----------------------------- |
93
90
  | `findLinkedAgents(projectName, sessionId)` | Find agents linked to session |
94
- | `findOrphanAgents(projectName?)` | Find orphan agent files |
95
- | `deleteOrphanAgents(projectName?)` | Delete orphan agents |
96
- | `findLinkedTodos(projectName, sessionId)` | Find todos linked to session |
97
- | `sessionHasTodos(projectName, sessionId)` | Check if session has todos |
98
- | `findOrphanTodos(projectName?)` | Find orphan todo files |
99
- | `deleteOrphanTodos(projectName?)` | Delete orphan todos |
91
+ | `findOrphanAgents(projectName?)` | Find orphan agent files |
92
+ | `deleteOrphanAgents(projectName?)` | Delete orphan agents |
93
+ | `findLinkedTodos(projectName, sessionId)` | Find todos linked to session |
94
+ | `sessionHasTodos(projectName, sessionId)` | Check if session has todos |
95
+ | `findOrphanTodos(projectName?)` | Find orphan todo files |
96
+ | `deleteOrphanTodos(projectName?)` | Delete orphan todos |
100
97
 
101
98
  ## License
102
99
 
103
- MIT
100
+ MIT
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ interface FileSystem {
17
17
  declare const getSessionsDir: () => string;
18
18
  /** Get Claude todos directory (~/.claude/todos) */
19
19
  declare const getTodosDir: () => string;
20
+ /** Expand ~ path to absolute path with OS-native separators */
21
+ declare const expandHomePath: (tildePath: string, homeDir: string) => string;
20
22
  /**
21
23
  * Convert project folder name to display path
22
24
  * Unix: -home-user-projects -> /home/user/projects
@@ -24,16 +26,10 @@ declare const getTodosDir: () => string;
24
26
  * Handle dot-prefixed folders: --claude -> /.claude
25
27
  */
26
28
  declare const folderNameToDisplayPath: (folderName: string) => string;
27
- /**
28
- * Convert display path to folder name (reverse of folderNameToDisplayPath)
29
- * @deprecated Use pathToFolderName for absolute paths. This function exists
30
- * for roundtrip testing and does not handle non-ASCII or normalize drive case.
31
- */
32
- declare const displayPathToFolderName: (displayPath: string) => string;
33
29
  /**
34
30
  * Convert absolute path to project folder name
35
- * Non-ASCII characters are converted to '-' per character
36
- * Windows drive letter is normalized to lowercase (C: -> c--)
31
+ * All non-alphanumeric characters are converted to '-'
32
+ * Matches official Claude Code: A.replace(/[^a-zA-Z0-9]/g, '-')
37
33
  */
38
34
  declare const pathToFolderName: (absolutePath: string) => string;
39
35
  /**
@@ -48,7 +44,7 @@ declare const getRealPathFromSession: (folderName: string, sessionsDir?: string,
48
44
  * Convert folder name to relative or absolute path for display
49
45
  * If path is under home directory, show relative (~/...)
50
46
  */
51
- declare const folderNameToPath: (folderName: string) => string;
47
+ declare const folderNameToPath: (folderName: string) => Promise<string>;
52
48
  /**
53
49
  * Find project folder name that matches the given workspace path
54
50
  * Searches through all projects and checks if any session's cwd matches
@@ -535,6 +531,7 @@ interface ProgressError {
535
531
  line: number;
536
532
  hookEvent?: string;
537
533
  hookName?: string;
534
+ messageType?: string;
538
535
  }
539
536
  interface ValidationResult {
540
537
  valid: boolean;
@@ -662,4 +659,4 @@ declare const getLogger: () => Logger;
662
659
  */
663
660
  declare const createLogger: (namespace: string) => Logger;
664
661
 
665
- export { AgentInfo, type ChainError, CompressSessionOptions, ConversationLine, FileChange, type GenericMessage, type Logger, Message, MessagePayload$1 as MessagePayload, MoveSessionResult, type ProgressError, Project, ProjectTreeData, SearchResult, SessionIndexEntry, SessionSortOptions, SessionTodos, SessionsIndex, SummarizeSessionOptions, SummaryInfo, TREE_ICONS, type ToolUseResultError, type TreeItemType, type ValidationResult, analyzeSession, autoRepairChain, canMoveSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, deleteMessageWithChainRepair, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractProjectKnowledge, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, formatRelativeTime, generateTreeNodeId, getDisplayTitle, getIndexEntryDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionSortTimestamp, getSessionTooltip, getSessionsDir, getTodoIcon, getTodosDir, getTotalTodoCount, hasSessionsIndex, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, loadSessionsIndex, maskHomePath, moveSession, parseCommandMessage, parseJsonlLines, parseTreeNodeId, pathToFolderName, previewCleanup, readJsonlFile, readSession, renameSession, repairChain, repairParentUuidChain, restoreMessage, searchSessions, sessionHasSubItems, sessionHasTodos, setLogger, sortIndexEntriesByModified, sortProjects, splitSession, summarizeSession, tryParseJsonLine, updateSessionSummary, validateChain, validateProgressMessages, validateToolUseResult };
662
+ export { AgentInfo, type ChainError, CompressSessionOptions, ConversationLine, FileChange, type GenericMessage, type Logger, Message, MessagePayload$1 as MessagePayload, MoveSessionResult, type ProgressError, Project, ProjectTreeData, SearchResult, SessionIndexEntry, SessionSortOptions, SessionTodos, SessionsIndex, SummarizeSessionOptions, SummaryInfo, TREE_ICONS, type ToolUseResultError, type TreeItemType, type ValidationResult, analyzeSession, autoRepairChain, canMoveSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, deleteMessageWithChainRepair, deleteOrphanAgents, deleteOrphanTodos, deleteSession, expandHomePath, extractProjectKnowledge, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, formatRelativeTime, generateTreeNodeId, getDisplayTitle, getIndexEntryDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionSortTimestamp, getSessionTooltip, getSessionsDir, getTodoIcon, getTodosDir, getTotalTodoCount, hasSessionsIndex, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, loadSessionsIndex, maskHomePath, moveSession, parseCommandMessage, parseJsonlLines, parseTreeNodeId, pathToFolderName, previewCleanup, readJsonlFile, readSession, renameSession, repairChain, repairParentUuidChain, restoreMessage, searchSessions, sessionHasSubItems, sessionHasTodos, setLogger, sortIndexEntriesByModified, sortProjects, splitSession, summarizeSession, tryParseJsonLine, updateSessionSummary, validateChain, validateProgressMessages, validateToolUseResult };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/paths.ts
2
2
  import * as fs2 from "fs";
3
+ import * as fsp from "fs/promises";
3
4
  import * as os from "os";
4
5
  import * as path from "path";
5
6
 
@@ -229,18 +230,11 @@ var WINDOWS_PATTERNS = {
229
230
  /** Matches Windows folder name format: C-- */
230
231
  folderName: /^([A-Za-z])--/
231
232
  };
232
- var parseWindowsAbsPath = (p) => {
233
- const match = p.match(WINDOWS_PATTERNS.absolutePath);
234
- return match ? { isWindows: true, drive: match[1], rest: p.slice(3) } : { isWindows: false };
235
- };
236
233
  var parseWindowsFolderName = (name) => {
237
234
  const match = name.match(WINDOWS_PATTERNS.folderName);
238
235
  return match ? { isWindows: true, drive: match[1], rest: name.slice(3) } : { isWindows: false };
239
236
  };
240
237
  var isWindowsPath = (p) => WINDOWS_PATTERNS.absolutePath.test(p);
241
- var convertNonAscii = (str) => [...str].map((c) => c.charCodeAt(0) <= 127 ? c : "-").join("");
242
- var pathCharsToFolderName = (str) => str.replace(/[/\\]\./g, "--").replace(/[/\\]/g, "-").replace(/\./g, "-");
243
- var separatorsToFolderName = (str) => str.replace(/[/\\]\./g, "--").replace(/[/\\]/g, "-");
244
238
  var extractCwdFromContent = (content, filePath) => {
245
239
  const lines = content.split("\n").filter((l) => l.trim());
246
240
  for (let i = 0; i < lines.length; i++) {
@@ -252,6 +246,11 @@ var extractCwdFromContent = (content, filePath) => {
252
246
  return null;
253
247
  };
254
248
  var isSessionFile = (filename) => filename.endsWith(".jsonl") && !filename.startsWith("agent-");
249
+ var expandHomePath = (tildePath, homeDir) => {
250
+ if (!tildePath.startsWith("~")) return tildePath;
251
+ const relativePart = tildePath.slice(1);
252
+ return path.join(homeDir, ...relativePart.split("/").filter(Boolean));
253
+ };
255
254
  var toRelativePath = (absolutePath, homeDir) => {
256
255
  const normalizedPath = absolutePath.replace(/\\/g, "/");
257
256
  const normalizedHome = homeDir.replace(/\\/g, "/");
@@ -263,11 +262,10 @@ var toRelativePath = (absolutePath, homeDir) => {
263
262
  }
264
263
  const startsWithHome = isWin ? pathLower.startsWith(homeLower + "/") : normalizedPath.startsWith(normalizedHome + "/");
265
264
  if (startsWithHome) {
266
- const sep = isWin ? "\\" : "/";
267
265
  const relativePart = absolutePath.slice(homeDir.length);
268
- return "~" + relativePart.replace(/[\\/]/g, sep);
266
+ return "~" + relativePart.replace(/\\/g, "/");
269
267
  }
270
- return absolutePath;
268
+ return absolutePath.replace(/\\/g, "/");
271
269
  };
272
270
  var folderNameToDisplayPath = (folderName) => {
273
271
  const parsed = parseWindowsFolderName(folderName);
@@ -276,20 +274,7 @@ var folderNameToDisplayPath = (folderName) => {
276
274
  }
277
275
  return folderName.replace(/^-/, "/").replace(/--/g, "/.").replace(/-/g, "/");
278
276
  };
279
- var displayPathToFolderName = (displayPath) => {
280
- const parsed = parseWindowsAbsPath(displayPath);
281
- if (parsed.isWindows) {
282
- return parsed.drive + "--" + separatorsToFolderName(parsed.rest);
283
- }
284
- return displayPath.replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-");
285
- };
286
- var pathToFolderName = (absolutePath) => {
287
- const parsed = parseWindowsAbsPath(absolutePath);
288
- if (parsed.isWindows) {
289
- return parsed.drive.toLowerCase() + "--" + pathCharsToFolderName(convertNonAscii(parsed.rest));
290
- }
291
- return convertNonAscii(absolutePath).replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-").replace(/\./g, "-");
292
- };
277
+ var pathToFolderName = (absolutePath) => absolutePath.replace(/[^a-zA-Z0-9]/g, "-");
293
278
  var tryGetCwdFromFile = (filePath, fileSystem = fs2, logger2 = log) => {
294
279
  const basename3 = path.basename(filePath);
295
280
  try {
@@ -323,20 +308,39 @@ var getRealPathFromSession = (folderName, sessionsDir = getSessionsDir(), fileSy
323
308
  logger2.warn(
324
309
  `getRealPathFromSession: ${folderName} -> no match, cwds found: ${cwdList.join(", ")}`
325
310
  );
326
- } else {
327
- logger2.warn(`getRealPathFromSession: ${folderName} -> no valid cwd in any session`);
328
311
  }
329
312
  return null;
330
313
  } catch {
331
314
  return null;
332
315
  }
333
316
  };
334
- var folderNameToPath = (folderName) => {
317
+ var getClaudeConfigPath = () => path.join(os.homedir(), ".claude.json");
318
+ var resolvePathFromClaudeConfig = async (folderName, fileSystem = fsp) => {
319
+ try {
320
+ const configPath = getClaudeConfigPath();
321
+ const content = await fileSystem.readFile(configPath, "utf-8");
322
+ const config = JSON.parse(content);
323
+ if (!config.projects) return null;
324
+ for (const projectPath of Object.keys(config.projects)) {
325
+ if (pathToFolderName(projectPath) === folderName) {
326
+ return projectPath;
327
+ }
328
+ }
329
+ return null;
330
+ } catch {
331
+ return null;
332
+ }
333
+ };
334
+ var folderNameToPath = async (folderName) => {
335
335
  const homeDir = os.homedir();
336
336
  const realPath = getRealPathFromSession(folderName);
337
337
  if (realPath) {
338
338
  return toRelativePath(realPath, homeDir);
339
339
  }
340
+ const configPath = await resolvePathFromClaudeConfig(folderName);
341
+ if (configPath) {
342
+ return toRelativePath(configPath, homeDir);
343
+ }
340
344
  const absolutePath = folderNameToDisplayPath(folderName);
341
345
  return toRelativePath(absolutePath, homeDir);
342
346
  };
@@ -808,9 +812,10 @@ var listProjects = Effect4.gen(function* () {
808
812
  const projectPath = path4.join(sessionsDir, entry.name);
809
813
  const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
810
814
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
815
+ const displayName = yield* Effect4.tryPromise(() => folderNameToPath(entry.name));
811
816
  return {
812
817
  name: entry.name,
813
- displayName: folderNameToPath(entry.name),
818
+ displayName,
814
819
  path: projectPath,
815
820
  sessionCount: sessionFiles.length
816
821
  };
@@ -899,6 +904,12 @@ function validateProgressMessages(messages) {
899
904
  hookName
900
905
  });
901
906
  }
907
+ } else if (msg.type === "saved_hook_context") {
908
+ errors.push({
909
+ type: "unwanted_progress",
910
+ line: i + 1,
911
+ messageType: "saved_hook_context"
912
+ });
902
913
  }
903
914
  }
904
915
  return {
@@ -2342,7 +2353,7 @@ export {
2342
2353
  deleteOrphanAgents,
2343
2354
  deleteOrphanTodos,
2344
2355
  deleteSession,
2345
- displayPathToFolderName,
2356
+ expandHomePath,
2346
2357
  extractProjectKnowledge,
2347
2358
  extractTextContent,
2348
2359
  extractTitle,