@vitorcen/context-resume 1.0.1 → 1.0.3

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.
@@ -4,10 +4,11 @@ import { glob } from 'glob';
4
4
  import os from 'os';
5
5
  // --- Claude Adapter ---
6
6
  function getClaudeEncodedPath(projectPath) {
7
- // Claude encodes paths by replacing / and . with -
7
+ // Claude encodes paths by replacing /, ., and _ with -
8
8
  // e.g. /home/user/project -> -home-user-project
9
9
  // e.g. /path/v1.0 -> -path-v1-0
10
- return projectPath.replace(/[\/\.]/g, '-');
10
+ // e.g. /home/work_ro -> -home-work-ro
11
+ return projectPath.replace(/[\/\._]/g, '-');
11
12
  }
12
13
  export async function getClaudeSessions(cwd, limit = 10) {
13
14
  const homeDir = os.homedir();
package/dist/index.js CHANGED
@@ -3,11 +3,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Command } from 'commander';
4
4
  import { render } from 'ink';
5
5
  import App from './ui/app.js';
6
+ import { createRequire } from 'module';
7
+ const require = createRequire(import.meta.url);
8
+ const { version } = require('../package.json');
6
9
  const program = new Command();
7
10
  program
8
11
  .name('context')
9
12
  .description('Context Resume CLI')
10
- .version('1.0.0', '-v, --version');
13
+ .version(version, '-v, --version');
11
14
  program
12
15
  .option('-n, --number <count>', 'Number of sessions to show per source (claude/codex)', '10')
13
16
  .action(async (options) => {
package/dist/ui/app.js CHANGED
@@ -3,6 +3,22 @@ import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput, useApp } from 'ink';
4
4
  import SelectInput from 'ink-select-input';
5
5
  import { getClaudeSessions, getCodexSessions } from '../adapters/index.js';
6
+ import stringWidth from 'string-width';
7
+ // Truncate by display width (handles CJK characters correctly)
8
+ const truncateByWidth = (str, maxWidth) => {
9
+ if (stringWidth(str) <= maxWidth)
10
+ return str;
11
+ let result = '';
12
+ let width = 0;
13
+ for (const char of str) {
14
+ const charWidth = stringWidth(char);
15
+ if (width + charWidth > maxWidth)
16
+ break;
17
+ result += char;
18
+ width += charWidth;
19
+ }
20
+ return result + '...';
21
+ };
6
22
  const App = ({ cwd, limit = 10, onSubmit }) => {
7
23
  const [claudeItems, setClaudeItems] = useState([]);
8
24
  const [codexItems, setCodexItems] = useState([]);
@@ -33,16 +49,24 @@ const App = ({ cwd, limit = 10, onSubmit }) => {
33
49
  ]);
34
50
  // Sort by timestamp desc
35
51
  const sortFn = (a, b) => b.timestamp - a.timestamp;
36
- const cItems = claudeSessions.sort(sortFn).map(s => ({
37
- label: `${s.title} (${new Date(s.timestamp).toLocaleDateString()})`,
38
- value: s.id,
39
- session: s
40
- }));
41
- const cxItems = codexSessions.sort(sortFn).map(s => ({
42
- label: `${s.title} (${new Date(s.timestamp).toLocaleDateString()})`,
43
- value: s.id,
44
- session: s
45
- }));
52
+ const cItems = claudeSessions.sort(sortFn).map(s => {
53
+ const title = s.title.replace(/\n/g, ' ').trim();
54
+ const truncatedTitle = truncateByWidth(title, 45);
55
+ return {
56
+ label: `${truncatedTitle} (${new Date(s.timestamp).toLocaleDateString()})`,
57
+ value: s.id,
58
+ session: s
59
+ };
60
+ });
61
+ const cxItems = codexSessions.sort(sortFn).map(s => {
62
+ const title = s.title.replace(/\n/g, ' ').trim();
63
+ const truncatedTitle = truncateByWidth(title, 45);
64
+ return {
65
+ label: `${truncatedTitle} (${new Date(s.timestamp).toLocaleDateString()})`,
66
+ value: s.id,
67
+ session: s
68
+ };
69
+ });
46
70
  setClaudeItems(cItems);
47
71
  setCodexItems(cxItems);
48
72
  if (cItems.length > 0)
@@ -66,12 +90,12 @@ const App = ({ cwd, limit = 10, onSubmit }) => {
66
90
  const currentItem = activePanel === 'claude' ? activeClaudeItem : activeCodexItem;
67
91
  // Filter out empty prompts
68
92
  const prompts = (currentItem?.session.userPrompts || []).filter(p => p && p.trim().length > 0);
69
- // Truncate prompts logic
93
+ // Truncate prompts logic (~60 display width: ~120 ASCII chars or ~60 CJK chars)
70
94
  const previewText = prompts.map((p, i) => {
71
95
  const clean = p.replace(/\n/g, ' ').trim();
72
- const truncated = clean.length > 50 ? clean.slice(0, 50) + '...' : clean;
96
+ const truncated = truncateByWidth(clean, 120);
73
97
  return `${i + 1}. ${truncated}`;
74
98
  }).join('\n');
75
- return (_jsxs(Box, { flexDirection: "column", height: 35, children: [_jsxs(Box, { height: 12, borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Preview (User Prompts)" }), _jsx(Text, { children: previewText || 'Select a session to view prompts' })] }), _jsxs(Box, { flexDirection: "row", height: 20, children: [_jsxs(Box, { width: "50%", borderStyle: activePanel === 'claude' ? 'double' : 'single', flexDirection: "column", borderColor: activePanel === 'claude' ? 'green' : 'white', children: [_jsx(Text, { bold: true, underline: true, color: activePanel === 'claude' ? 'green' : 'white', children: "Claude Sessions" }), claudeItems.length === 0 ? (_jsx(Text, { children: "No sessions found." })) : (_jsx(SelectInput, { items: claudeItems, onSelect: handleSelect, onHighlight: handleHighlightClaude, isFocused: activePanel === 'claude' }))] }), _jsxs(Box, { width: "50%", borderStyle: activePanel === 'codex' ? 'double' : 'single', flexDirection: "column", borderColor: activePanel === 'codex' ? 'green' : 'white', children: [_jsx(Text, { bold: true, underline: true, color: activePanel === 'codex' ? 'green' : 'white', children: "Codex Sessions" }), codexItems.length === 0 ? (_jsx(Text, { children: "No sessions found." })) : (_jsx(SelectInput, { items: codexItems, onSelect: handleSelect, onHighlight: handleHighlightCodex, isFocused: activePanel === 'codex' }))] })] }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: "TAB/Arrows: Switch Panel | ENTER: Select | ESC: Exit" }) })] }));
99
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Preview (User Prompts)" }), _jsx(Text, { children: previewText || 'Select a session to view prompts' })] }), _jsxs(Box, { flexDirection: "row", minHeight: 20, children: [_jsxs(Box, { width: "50%", borderStyle: activePanel === 'claude' ? 'double' : 'single', flexDirection: "column", borderColor: activePanel === 'claude' ? 'green' : 'white', children: [_jsx(Text, { bold: true, underline: true, color: activePanel === 'claude' ? 'green' : 'white', children: "Claude Sessions" }), claudeItems.length === 0 ? (_jsx(Text, { children: "No sessions found." })) : (_jsx(SelectInput, { items: claudeItems, onSelect: handleSelect, onHighlight: handleHighlightClaude, isFocused: activePanel === 'claude' }))] }), _jsxs(Box, { width: "50%", borderStyle: activePanel === 'codex' ? 'double' : 'single', flexDirection: "column", borderColor: activePanel === 'codex' ? 'green' : 'white', children: [_jsx(Text, { bold: true, underline: true, color: activePanel === 'codex' ? 'green' : 'white', children: "Codex Sessions" }), codexItems.length === 0 ? (_jsx(Text, { children: "No sessions found." })) : (_jsx(SelectInput, { items: codexItems, onSelect: handleSelect, onHighlight: handleHighlightCodex, isFocused: activePanel === 'codex' }))] })] }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: "TAB/Arrows: Switch Panel | ENTER: Select | ESC: Exit" }) })] }));
76
100
  };
77
101
  export default App;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitorcen/context-resume",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Context Resume CLI - Browse and mutually restore the conversation history of Claude Code and Codex",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",