@shepai/cli 1.51.2 → 1.52.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.
Files changed (108) hide show
  1. package/apis/json-schema/AgentFeature.yaml +1 -0
  2. package/apis/json-schema/AgentSession.yaml +38 -0
  3. package/apis/json-schema/AgentSessionMessage.yaml +27 -0
  4. package/dist/packages/core/src/application/ports/output/agents/agent-session-repository.interface.d.ts +59 -0
  5. package/dist/packages/core/src/application/ports/output/agents/agent-session-repository.interface.d.ts.map +1 -0
  6. package/dist/packages/core/src/application/ports/output/agents/agent-session-repository.interface.js +12 -0
  7. package/dist/packages/core/src/application/ports/output/agents/index.d.ts +1 -0
  8. package/dist/packages/core/src/application/ports/output/agents/index.d.ts.map +1 -1
  9. package/dist/packages/core/src/application/services/agents/agent-session-repository.registry.d.ts +24 -0
  10. package/dist/packages/core/src/application/services/agents/agent-session-repository.registry.d.ts.map +1 -0
  11. package/dist/packages/core/src/application/services/agents/agent-session-repository.registry.js +35 -0
  12. package/dist/packages/core/src/application/use-cases/agents/get-agent-session.use-case.d.ts +24 -0
  13. package/dist/packages/core/src/application/use-cases/agents/get-agent-session.use-case.d.ts.map +1 -0
  14. package/dist/packages/core/src/application/use-cases/agents/get-agent-session.use-case.js +48 -0
  15. package/dist/packages/core/src/application/use-cases/agents/list-agent-sessions.use-case.d.ts +22 -0
  16. package/dist/packages/core/src/application/use-cases/agents/list-agent-sessions.use-case.d.ts.map +1 -0
  17. package/dist/packages/core/src/application/use-cases/agents/list-agent-sessions.use-case.js +47 -0
  18. package/dist/packages/core/src/domain/errors/session-not-found.error.d.ts +10 -0
  19. package/dist/packages/core/src/domain/errors/session-not-found.error.d.ts.map +1 -0
  20. package/dist/packages/core/src/domain/errors/session-not-found.error.js +14 -0
  21. package/dist/packages/core/src/domain/generated/output.d.ts +56 -1
  22. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  23. package/dist/packages/core/src/domain/generated/output.js +1 -0
  24. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  25. package/dist/packages/core/src/infrastructure/di/container.js +20 -0
  26. package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
  27. package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +1 -0
  28. package/dist/packages/core/src/infrastructure/services/agents/sessions/claude-code-session.repository.d.ts +58 -0
  29. package/dist/packages/core/src/infrastructure/services/agents/sessions/claude-code-session.repository.d.ts.map +1 -0
  30. package/dist/packages/core/src/infrastructure/services/agents/sessions/claude-code-session.repository.js +324 -0
  31. package/dist/packages/core/src/infrastructure/services/agents/sessions/stub-session.repository.d.ts +19 -0
  32. package/dist/packages/core/src/infrastructure/services/agents/sessions/stub-session.repository.d.ts.map +1 -0
  33. package/dist/packages/core/src/infrastructure/services/agents/sessions/stub-session.repository.js +39 -0
  34. package/dist/src/presentation/cli/commands/session/index.d.ts +19 -0
  35. package/dist/src/presentation/cli/commands/session/index.d.ts.map +1 -0
  36. package/dist/src/presentation/cli/commands/session/index.js +25 -0
  37. package/dist/src/presentation/cli/commands/session/ls.command.d.ts +20 -0
  38. package/dist/src/presentation/cli/commands/session/ls.command.d.ts.map +1 -0
  39. package/dist/src/presentation/cli/commands/session/ls.command.js +186 -0
  40. package/dist/src/presentation/cli/commands/session/show.command.d.ts +14 -0
  41. package/dist/src/presentation/cli/commands/session/show.command.d.ts.map +1 -0
  42. package/dist/src/presentation/cli/commands/session/show.command.js +213 -0
  43. package/dist/src/presentation/cli/index.js +2 -0
  44. package/dist/tsconfig.build.tsbuildinfo +1 -1
  45. package/package.json +1 -1
  46. package/web/.next/BUILD_ID +1 -1
  47. package/web/.next/build-manifest.json +2 -2
  48. package/web/.next/cache/.previewinfo +1 -1
  49. package/web/.next/cache/.rscinfo +1 -1
  50. package/web/.next/cache/.tsbuildinfo +1 -1
  51. package/web/.next/cache/config.json +3 -3
  52. package/web/.next/fallback-build-manifest.json +2 -2
  53. package/web/.next/prerender-manifest.json +3 -3
  54. package/web/.next/required-server-files.js +1 -1
  55. package/web/.next/required-server-files.json +1 -1
  56. package/web/.next/server/app/_global-error.html +2 -2
  57. package/web/.next/server/app/_global-error.rsc +1 -1
  58. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  65. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  68. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  69. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  70. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  71. package/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  72. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -1
  73. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  74. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  75. package/web/.next/server/pages/500.html +2 -2
  76. package/web/.next/server/server-reference-manifest.js +1 -1
  77. package/web/.next/server/server-reference-manifest.json +15 -15
  78. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  79. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  80. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  81. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  82. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  83. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  84. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  85. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  86. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  87. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  88. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  89. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  90. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  91. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  92. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  93. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  94. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  95. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  96. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  97. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  98. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  99. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  100. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  101. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  102. package/web/.next/static/chunks/{f9953dad6d0c239c.js → 31e5030eed01a941.js} +1 -1
  103. package/web/.next/static/chunks/{f6f61237a0ec20e3.js → 45bb8ee2a6133d9d.js} +2 -2
  104. package/web/.next/trace +1 -1
  105. package/web/.next/trace-build +1 -1
  106. /package/web/.next/static/{w8CFnR5hKioREFJyh8hpW → b2hejP8rfi4yw_GfgY-9J}/_buildManifest.js +0 -0
  107. /package/web/.next/static/{w8CFnR5hKioREFJyh8hpW → b2hejP8rfi4yw_GfgY-9J}/_clientMiddlewareManifest.json +0 -0
  108. /package/web/.next/static/{w8CFnR5hKioREFJyh8hpW → b2hejP8rfi4yw_GfgY-9J}/_ssgManifest.js +0 -0
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Session List Command
3
+ *
4
+ * List agent provider CLI sessions for the configured or specified provider.
5
+ * Sessions are grouped by project path and sorted by last activity within each group.
6
+ *
7
+ * Usage:
8
+ * shep session ls
9
+ * shep session ls --claude-code
10
+ * shep session ls --cursor-cli
11
+ * shep session ls --gemini-cli
12
+ * shep session ls --limit 10
13
+ * shep session ls --flat
14
+ */
15
+ import { Command } from 'commander';
16
+ import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
17
+ import { ListAgentSessionsUseCase } from '../../../../../packages/core/src/application/use-cases/agents/list-agent-sessions.use-case.js';
18
+ import { colors, messages } from '../../ui/index.js';
19
+ import { fmt } from '../../ui/formatters.js';
20
+ import { AgentType } from '../../../../../packages/core/src/domain/generated/output.js';
21
+ export function createLsCommand() {
22
+ return new Command('ls')
23
+ .description('List agent provider CLI sessions')
24
+ .option('--claude-code', 'List sessions for Claude Code')
25
+ .option('--cursor-cli', 'List sessions for Cursor CLI')
26
+ .option('--gemini-cli', 'List sessions for Gemini CLI')
27
+ .option('-n, --limit <n>', 'Maximum number of sessions to show (0 = all)', '20')
28
+ .option('--flat', 'Show flat list without grouping')
29
+ .action(async (opts) => {
30
+ try {
31
+ // Validate mutual exclusivity of provider flags
32
+ const providerFlags = [opts.claudeCode, opts.cursorCli, opts.geminiCli].filter(Boolean);
33
+ if (providerFlags.length > 1) {
34
+ messages.error('Only one provider flag may be specified at a time');
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ const agentType = resolveAgentType(opts);
39
+ const limit = parseInt(opts.limit, 10);
40
+ const useCase = container.resolve(ListAgentSessionsUseCase);
41
+ const sessions = await useCase.execute({ agentType, limit });
42
+ if (sessions.length === 0) {
43
+ messages.newline();
44
+ messages.info('No sessions found');
45
+ messages.newline();
46
+ return;
47
+ }
48
+ const providerLabel = sessions[0]?.agentType ?? 'agent';
49
+ if (opts.flat) {
50
+ renderFlat(sessions, providerLabel);
51
+ }
52
+ else {
53
+ renderGrouped(sessions, providerLabel);
54
+ }
55
+ }
56
+ catch (error) {
57
+ const err = error instanceof Error ? error : new Error(String(error));
58
+ messages.error('Failed to list sessions', err);
59
+ process.exitCode = 1;
60
+ }
61
+ });
62
+ }
63
+ function renderGrouped(sessions, provider) {
64
+ const lines = [];
65
+ lines.push('');
66
+ lines.push(` ${fmt.heading(`${provider} Sessions (${sessions.length})`)}`);
67
+ // Group by project path
68
+ const groups = new Map();
69
+ for (const session of sessions) {
70
+ const key = session.projectPath;
71
+ const group = groups.get(key);
72
+ if (group) {
73
+ group.push(session);
74
+ }
75
+ else {
76
+ groups.set(key, [session]);
77
+ }
78
+ }
79
+ // Sort groups by most recent session activity
80
+ const sortedGroups = [...groups.entries()].sort((a, b) => {
81
+ const aTime = getLatestTime(a[1]);
82
+ const bTime = getLatestTime(b[1]);
83
+ return bTime - aTime;
84
+ });
85
+ for (const [projectPath, groupSessions] of sortedGroups) {
86
+ lines.push('');
87
+ lines.push(` 📂 ${colors.brand(projectPath)}`);
88
+ // Column header
89
+ const header = ` ${pad('ID', 10)} ${pad('Messages', 10)} ${pad('Last Active', 14)} Preview`;
90
+ lines.push(` ${colors.muted(header.trimStart())}`);
91
+ for (const session of groupSessions) {
92
+ const active = isRecentlyActive(session.lastMessageAt ?? session.updatedAt);
93
+ const id = active ? colors.success(session.id.substring(0, 8)) : session.id.substring(0, 8);
94
+ const indicator = active ? colors.success('●') : ' ';
95
+ const msgCount = String(session.messageCount);
96
+ const lastActive = formatRelativeTime(session.lastMessageAt ?? session.updatedAt);
97
+ const preview = session.preview ? truncate(session.preview, 50) : colors.muted('-');
98
+ lines.push(` ${indicator} ${pad(id, 10)} ${pad(msgCount, 10)} ${pad(lastActive, 14)} ${preview}`);
99
+ }
100
+ }
101
+ lines.push('');
102
+ console.log(lines.join('\n'));
103
+ }
104
+ function renderFlat(sessions, provider) {
105
+ const lines = [];
106
+ lines.push('');
107
+ lines.push(` ${fmt.heading(`${provider} Sessions (${sessions.length})`)}`);
108
+ lines.push('');
109
+ const header = `${pad('ID', 10)} ${pad('Path', 40)} ${pad('Msgs', 6)} ${pad('Last Active', 14)} Preview`;
110
+ lines.push(` ${colors.muted(header)}`);
111
+ for (const session of sessions) {
112
+ const active = isRecentlyActive(session.lastMessageAt ?? session.updatedAt);
113
+ const id = active ? colors.success(session.id.substring(0, 8)) : session.id.substring(0, 8);
114
+ const indicator = active ? colors.success('●') : ' ';
115
+ const path = truncate(session.projectPath, 40);
116
+ const msgCount = String(session.messageCount);
117
+ const lastActive = formatRelativeTime(session.lastMessageAt ?? session.updatedAt);
118
+ const preview = session.preview ? truncate(session.preview, 50) : colors.muted('-');
119
+ lines.push(` ${indicator} ${pad(id, 10)} ${pad(path, 40)} ${pad(msgCount, 6)} ${pad(lastActive, 14)} ${preview}`);
120
+ }
121
+ lines.push('');
122
+ console.log(lines.join('\n'));
123
+ }
124
+ /** Sessions active within the last 2 minutes are considered "probably active" */
125
+ function isRecentlyActive(date) {
126
+ const ts = toTimestamp(date);
127
+ if (ts === 0)
128
+ return false;
129
+ return Date.now() - ts < 2 * 60 * 1000;
130
+ }
131
+ function getLatestTime(sessions) {
132
+ let latest = 0;
133
+ for (const s of sessions) {
134
+ const t = toTimestamp(s.lastMessageAt ?? s.updatedAt);
135
+ if (t > latest)
136
+ latest = t;
137
+ }
138
+ return latest;
139
+ }
140
+ function toTimestamp(date) {
141
+ if (!date)
142
+ return 0;
143
+ const d = date instanceof Date ? date : new Date(date);
144
+ return isNaN(d.getTime()) ? 0 : d.getTime();
145
+ }
146
+ function pad(text, width) {
147
+ // Strip ANSI for measurement
148
+ // eslint-disable-next-line no-control-regex
149
+ const visible = text.replace(/\x1B\[[0-9;]*m/g, '').length;
150
+ if (visible >= width)
151
+ return text;
152
+ return text + ' '.repeat(width - visible);
153
+ }
154
+ function resolveAgentType(opts) {
155
+ if (opts.claudeCode)
156
+ return AgentType.ClaudeCode;
157
+ if (opts.cursorCli)
158
+ return AgentType.Cursor;
159
+ if (opts.geminiCli)
160
+ return AgentType.GeminiCli;
161
+ return undefined;
162
+ }
163
+ export function formatRelativeTime(date) {
164
+ if (!date)
165
+ return colors.muted('-');
166
+ const d = date instanceof Date ? date : new Date(date);
167
+ if (isNaN(d.getTime()))
168
+ return colors.muted('-');
169
+ const diffMs = Date.now() - d.getTime();
170
+ const diffSeconds = Math.floor(diffMs / 1000);
171
+ const diffMinutes = Math.floor(diffSeconds / 60);
172
+ const diffHours = Math.floor(diffMinutes / 60);
173
+ const diffDays = Math.floor(diffHours / 24);
174
+ if (diffDays > 0)
175
+ return `${diffDays}d ago`;
176
+ if (diffHours > 0)
177
+ return `${diffHours}h ago`;
178
+ if (diffMinutes > 0)
179
+ return `${diffMinutes}m ago`;
180
+ return 'just now';
181
+ }
182
+ function truncate(text, maxLength) {
183
+ if (text.length <= maxLength)
184
+ return text;
185
+ return `${text.substring(0, maxLength - 1)}…`;
186
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Session Show Command
3
+ *
4
+ * Display details of a specific agent provider CLI session including metadata,
5
+ * tool usage stats, and conversation messages.
6
+ *
7
+ * Usage:
8
+ * shep session show <id>
9
+ * shep session show <id> --messages 50
10
+ * shep session show <id> --claude-code
11
+ */
12
+ import { Command } from 'commander';
13
+ export declare function createShowCommand(): Command;
14
+ //# sourceMappingURL=show.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/session/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,iBAAiB,IAAI,OAAO,CAqC3C"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Session Show Command
3
+ *
4
+ * Display details of a specific agent provider CLI session including metadata,
5
+ * tool usage stats, and conversation messages.
6
+ *
7
+ * Usage:
8
+ * shep session show <id>
9
+ * shep session show <id> --messages 50
10
+ * shep session show <id> --claude-code
11
+ */
12
+ import { Command } from 'commander';
13
+ import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
14
+ import { GetAgentSessionUseCase } from '../../../../../packages/core/src/application/use-cases/agents/get-agent-session.use-case.js';
15
+ import { SessionNotFoundError } from '../../../../../packages/core/src/domain/errors/session-not-found.error.js';
16
+ import { colors, messages } from '../../ui/index.js';
17
+ import { fmt } from '../../ui/formatters.js';
18
+ import { AgentType } from '../../../../../packages/core/src/domain/generated/output.js';
19
+ import { formatRelativeTime } from './ls.command.js';
20
+ export function createShowCommand() {
21
+ return new Command('show')
22
+ .description('Display details of an agent provider session')
23
+ .argument('<id>', 'Session ID')
24
+ .option('--claude-code', 'Query Claude Code sessions')
25
+ .option('--cursor-cli', 'Query Cursor CLI sessions')
26
+ .option('--gemini-cli', 'Query Gemini CLI sessions')
27
+ .option('-m, --messages <n>', 'Number of messages to display (0 = all)', '20')
28
+ .action(async (id, opts) => {
29
+ try {
30
+ // Validate mutual exclusivity of provider flags
31
+ const providerFlags = [opts.claudeCode, opts.cursorCli, opts.geminiCli].filter(Boolean);
32
+ if (providerFlags.length > 1) {
33
+ messages.error('Only one provider flag may be specified at a time');
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+ const agentType = resolveAgentType(opts);
38
+ const messageLimit = parseInt(opts.messages, 10);
39
+ const useCase = container.resolve(GetAgentSessionUseCase);
40
+ // Fetch all messages so we can show first N + last N
41
+ const session = await useCase.execute({ id, agentType, messageLimit: 0 });
42
+ renderSessionDetail(session, messageLimit);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof SessionNotFoundError) {
46
+ messages.error(`Session not found: ${error.message}`);
47
+ process.exitCode = 1;
48
+ return;
49
+ }
50
+ const err = error instanceof Error ? error : new Error(String(error));
51
+ messages.error('Failed to show session', err);
52
+ process.exitCode = 1;
53
+ }
54
+ });
55
+ }
56
+ function resolveAgentType(opts) {
57
+ if (opts.claudeCode)
58
+ return AgentType.ClaudeCode;
59
+ if (opts.cursorCli)
60
+ return AgentType.Cursor;
61
+ if (opts.geminiCli)
62
+ return AgentType.GeminiCli;
63
+ return undefined;
64
+ }
65
+ function renderSessionDetail(session, _messageLimit) {
66
+ const sessionMessages = session.messages ?? [];
67
+ const totalCount = session.messageCount;
68
+ const meta = session.metadata;
69
+ const lines = [];
70
+ lines.push('');
71
+ lines.push(` ${fmt.heading('Session')}`);
72
+ lines.push('');
73
+ // Row 1: ID + Provider
74
+ lines.push(` ${field('ID', session.id)} ${field('Provider', session.agentType)}`);
75
+ // Row 2: Project path (full width — it's long)
76
+ lines.push(` ${field('Path', session.projectPath)}`);
77
+ // Row 3: Git branch + CLI version + Permission mode
78
+ if (meta) {
79
+ const parts = [];
80
+ if (meta.gitBranch)
81
+ parts.push(field('Branch', colors.brand(meta.gitBranch)));
82
+ if (meta.cliVersion)
83
+ parts.push(field('CLI', `v${meta.cliVersion}`));
84
+ if (meta.permissionMode)
85
+ parts.push(field('Mode', meta.permissionMode));
86
+ if (parts.length > 0) {
87
+ lines.push(` ${parts.join(' ')}`);
88
+ }
89
+ }
90
+ // Row 4: Message counts + timing
91
+ const msgParts = [];
92
+ if (meta) {
93
+ msgParts.push(field('Messages', `${totalCount} total (${colors.info(String(meta.userMessageCount))} user, ${colors.success(String(meta.assistantMessageCount))} assistant)`));
94
+ }
95
+ else {
96
+ msgParts.push(field('Messages', String(totalCount)));
97
+ }
98
+ lines.push(` ${msgParts.join(' ')}`);
99
+ // Row 5: Timing
100
+ const timeParts = [];
101
+ const firstActive = session.firstMessageAt ? formatDate(session.firstMessageAt) : null;
102
+ if (firstActive)
103
+ timeParts.push(field('First Active', firstActive));
104
+ timeParts.push(field('Last Active', formatRelativeTime(session.lastMessageAt ?? session.updatedAt)));
105
+ if (firstActive && session.firstMessageAt && session.lastMessageAt) {
106
+ const duration = computeDuration(session.firstMessageAt, session.lastMessageAt);
107
+ if (duration)
108
+ timeParts.push(field('Duration', duration));
109
+ }
110
+ lines.push(` ${timeParts.join(' ')}`);
111
+ // Tool usage section
112
+ if (meta && Object.keys(meta.toolUsage).length > 0) {
113
+ lines.push('');
114
+ lines.push(` ${colors.muted('Tool Usage')}`);
115
+ const sorted = Object.entries(meta.toolUsage).sort((a, b) => b[1] - a[1]);
116
+ const totalTools = sorted.reduce((sum, [, count]) => sum + count, 0);
117
+ // Render tools in 3-column rows
118
+ const toolEntries = sorted.map(([name, count]) => {
119
+ const pct = Math.round((count / totalTools) * 100);
120
+ return `${colors.muted(name)} ${count}${colors.muted(`(${pct}%)`)}`;
121
+ });
122
+ for (let i = 0; i < toolEntries.length; i += 3) {
123
+ const row = toolEntries.slice(i, i + 3);
124
+ lines.push(` ${row.map((e) => pad(e, 26)).join('')}`);
125
+ }
126
+ }
127
+ lines.push('');
128
+ console.log(lines.join('\n'));
129
+ // Messages
130
+ if (sessionMessages.length === 0) {
131
+ messages.info('No messages in this session');
132
+ return;
133
+ }
134
+ // Filter out empty messages for display
135
+ const meaningful = sessionMessages.filter((m) => m.content.trim().length > 0);
136
+ if (meaningful.length === 0) {
137
+ messages.info('No displayable messages in this session');
138
+ return;
139
+ }
140
+ const half = Math.max(1, Math.floor(_messageLimit / 2));
141
+ if (meaningful.length <= _messageLimit || _messageLimit === 0) {
142
+ // Show all
143
+ messages.info(`${meaningful.length} messages`);
144
+ renderMessages(meaningful);
145
+ }
146
+ else {
147
+ // Show first half + gap + last half
148
+ const first = meaningful.slice(0, half);
149
+ const last = meaningful.slice(-half);
150
+ const skipped = meaningful.length - first.length - last.length;
151
+ messages.info(`Showing first ${first.length} and last ${last.length} of ${meaningful.length} messages`);
152
+ renderMessages(first);
153
+ console.log(` ${colors.muted(` ··· ${skipped} messages skipped ···`)}\n`);
154
+ renderMessages(last);
155
+ }
156
+ }
157
+ /** Render a label: value pair */
158
+ function field(label, value) {
159
+ return `${colors.muted(label)} ${value}`;
160
+ }
161
+ /** Pad a string (ANSI-aware) to a minimum visible width */
162
+ function pad(text, width) {
163
+ // eslint-disable-next-line no-control-regex
164
+ const visible = text.replace(/\x1B\[[0-9;]*m/g, '').length;
165
+ if (visible >= width)
166
+ return text;
167
+ return text + ' '.repeat(width - visible);
168
+ }
169
+ function computeDuration(start, end) {
170
+ const s = start instanceof Date ? start : new Date(start);
171
+ const e = end instanceof Date ? end : new Date(end);
172
+ const diffMs = e.getTime() - s.getTime();
173
+ if (isNaN(diffMs) || diffMs < 0)
174
+ return null;
175
+ const diffMinutes = Math.floor(diffMs / 60000);
176
+ const diffHours = Math.floor(diffMinutes / 60);
177
+ const mins = diffMinutes % 60;
178
+ if (diffHours > 0)
179
+ return `${diffHours}h ${mins}m`;
180
+ if (diffMinutes > 0)
181
+ return `${diffMinutes}m`;
182
+ return '<1m';
183
+ }
184
+ function renderMessages(msgs) {
185
+ const lines = [''];
186
+ for (const msg of msgs) {
187
+ const content = msg.content.trim();
188
+ const timestamp = formatDate(msg.timestamp);
189
+ const timeStr = timestamp ? colors.muted(` (${timestamp})`) : '';
190
+ if (msg.role === 'user') {
191
+ lines.push(` ${colors.info('You')}${timeStr}`);
192
+ }
193
+ else {
194
+ lines.push(` ${colors.success('Assistant')}${timeStr}`);
195
+ }
196
+ for (const line of content.split('\n')) {
197
+ lines.push(` ${line}`);
198
+ }
199
+ lines.push('');
200
+ }
201
+ console.log(lines.join('\n'));
202
+ }
203
+ function formatDate(date) {
204
+ if (!date)
205
+ return null;
206
+ try {
207
+ const d = date instanceof Date ? date : new Date(date);
208
+ return d.toLocaleString();
209
+ }
210
+ catch {
211
+ return String(date);
212
+ }
213
+ }
@@ -33,6 +33,7 @@ import { createRunCommand } from './commands/run.command.js';
33
33
  import { createAgentCommand } from './commands/agent/index.js';
34
34
  import { createFeatCommand } from './commands/feat/index.js';
35
35
  import { createRepoCommand } from './commands/repo/index.js';
36
+ import { createSessionCommand } from './commands/session/index.js';
36
37
  import { createIdeOpenCommand } from './commands/ide-open.command.js';
37
38
  import { createInstallCommand } from './commands/install.command.js';
38
39
  import { createUpgradeCommand } from './commands/upgrade.command.js';
@@ -98,6 +99,7 @@ async function bootstrap() {
98
99
  program.addCommand(createAgentCommand());
99
100
  program.addCommand(createFeatCommand());
100
101
  program.addCommand(createRepoCommand());
102
+ program.addCommand(createSessionCommand());
101
103
  program.addCommand(createIdeOpenCommand());
102
104
  program.addCommand(createInstallCommand());
103
105
  program.addCommand(createUpgradeCommand());