myshell-tools 1.0.0 → 2.0.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 (153) hide show
  1. package/CHANGELOG.md +44 -69
  2. package/LICENSE +21 -21
  3. package/README.md +178 -318
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +130 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/cost.d.ts +36 -0
  8. package/dist/commands/cost.js +103 -0
  9. package/dist/commands/cost.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +36 -0
  11. package/dist/commands/doctor.js +115 -0
  12. package/dist/commands/doctor.js.map +1 -0
  13. package/dist/commands/login.d.ts +20 -0
  14. package/dist/commands/login.js +60 -0
  15. package/dist/commands/login.js.map +1 -0
  16. package/dist/core/assess.d.ts +25 -0
  17. package/dist/core/assess.js +142 -0
  18. package/dist/core/assess.js.map +1 -0
  19. package/dist/core/classify.d.ts +19 -0
  20. package/dist/core/classify.js +80 -0
  21. package/dist/core/classify.js.map +1 -0
  22. package/dist/core/escalate.d.ts +32 -0
  23. package/dist/core/escalate.js +57 -0
  24. package/dist/core/escalate.js.map +1 -0
  25. package/dist/core/index.d.ts +13 -0
  26. package/dist/core/index.js +12 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/core/orchestrate.d.ts +42 -0
  29. package/dist/core/orchestrate.js +439 -0
  30. package/dist/core/orchestrate.js.map +1 -0
  31. package/dist/core/policy.d.ts +9 -0
  32. package/dist/core/policy.js +27 -0
  33. package/dist/core/policy.js.map +1 -0
  34. package/dist/core/prompt.d.ts +26 -0
  35. package/dist/core/prompt.js +125 -0
  36. package/dist/core/prompt.js.map +1 -0
  37. package/dist/core/review.d.ts +46 -0
  38. package/dist/core/review.js +148 -0
  39. package/dist/core/review.js.map +1 -0
  40. package/dist/core/route.d.ts +28 -0
  41. package/dist/core/route.js +52 -0
  42. package/dist/core/route.js.map +1 -0
  43. package/dist/core/types.d.ts +141 -0
  44. package/dist/core/types.js +14 -0
  45. package/dist/core/types.js.map +1 -0
  46. package/dist/infra/atomic.d.ts +53 -0
  47. package/dist/infra/atomic.js +171 -0
  48. package/dist/infra/atomic.js.map +1 -0
  49. package/dist/infra/clock.d.ts +9 -0
  50. package/dist/infra/clock.js +15 -0
  51. package/dist/infra/clock.js.map +1 -0
  52. package/dist/infra/index.d.ts +9 -0
  53. package/dist/infra/index.js +7 -0
  54. package/dist/infra/index.js.map +1 -0
  55. package/dist/infra/ledger.d.ts +49 -0
  56. package/dist/infra/ledger.js +90 -0
  57. package/dist/infra/ledger.js.map +1 -0
  58. package/dist/infra/paths.d.ts +28 -0
  59. package/dist/infra/paths.js +38 -0
  60. package/dist/infra/paths.js.map +1 -0
  61. package/dist/infra/pricing.d.ts +47 -0
  62. package/dist/infra/pricing.js +151 -0
  63. package/dist/infra/pricing.js.map +1 -0
  64. package/dist/infra/session.d.ts +28 -0
  65. package/dist/infra/session.js +61 -0
  66. package/dist/infra/session.js.map +1 -0
  67. package/dist/interface/render.d.ts +27 -0
  68. package/dist/interface/render.js +134 -0
  69. package/dist/interface/render.js.map +1 -0
  70. package/dist/interface/repl.d.ts +23 -0
  71. package/dist/interface/repl.js +90 -0
  72. package/dist/interface/repl.js.map +1 -0
  73. package/dist/interface/run.d.ts +20 -0
  74. package/dist/interface/run.js +31 -0
  75. package/dist/interface/run.js.map +1 -0
  76. package/dist/providers/claude-parse.d.ts +24 -0
  77. package/dist/providers/claude-parse.js +113 -0
  78. package/dist/providers/claude-parse.js.map +1 -0
  79. package/dist/providers/claude.d.ts +45 -0
  80. package/dist/providers/claude.js +122 -0
  81. package/dist/providers/claude.js.map +1 -0
  82. package/dist/providers/codex-parse.d.ts +32 -0
  83. package/dist/providers/codex-parse.js +145 -0
  84. package/dist/providers/codex-parse.js.map +1 -0
  85. package/dist/providers/codex.d.ts +44 -0
  86. package/dist/providers/codex.js +124 -0
  87. package/dist/providers/codex.js.map +1 -0
  88. package/dist/providers/detect.d.ts +49 -0
  89. package/dist/providers/detect.js +125 -0
  90. package/dist/providers/detect.js.map +1 -0
  91. package/dist/providers/errors.d.ts +49 -0
  92. package/dist/providers/errors.js +189 -0
  93. package/dist/providers/errors.js.map +1 -0
  94. package/dist/providers/index.d.ts +9 -0
  95. package/dist/providers/index.js +7 -0
  96. package/dist/providers/index.js.map +1 -0
  97. package/dist/providers/port.d.ts +74 -0
  98. package/dist/providers/port.js +16 -0
  99. package/dist/providers/port.js.map +1 -0
  100. package/dist/providers/registry.d.ts +21 -0
  101. package/dist/providers/registry.js +34 -0
  102. package/dist/providers/registry.js.map +1 -0
  103. package/dist/ui/banner.d.ts +19 -0
  104. package/dist/ui/banner.js +32 -0
  105. package/dist/ui/banner.js.map +1 -0
  106. package/dist/ui/spinner.d.ts +27 -0
  107. package/dist/ui/spinner.js +67 -0
  108. package/dist/ui/spinner.js.map +1 -0
  109. package/dist/ui/theme.d.ts +32 -0
  110. package/dist/ui/theme.js +56 -0
  111. package/dist/ui/theme.js.map +1 -0
  112. package/package.json +55 -49
  113. package/data/orchestrator.json +0 -113
  114. package/src/auth/recovery.mjs +0 -328
  115. package/src/auth/refresh.mjs +0 -373
  116. package/src/chef.mjs +0 -348
  117. package/src/cli/doctor.mjs +0 -568
  118. package/src/cli/reset.mjs +0 -447
  119. package/src/cli/status.mjs +0 -379
  120. package/src/cli.mjs +0 -429
  121. package/src/commands/doctor.mjs +0 -375
  122. package/src/commands/help.mjs +0 -324
  123. package/src/commands/status.mjs +0 -331
  124. package/src/monitor/health.mjs +0 -486
  125. package/src/monitor/performance.mjs +0 -442
  126. package/src/monitor/report.mjs +0 -535
  127. package/src/orchestrator/classify.mjs +0 -391
  128. package/src/orchestrator/confidence.mjs +0 -151
  129. package/src/orchestrator/handoffs.mjs +0 -231
  130. package/src/orchestrator/review.mjs +0 -222
  131. package/src/providers/balance.mjs +0 -201
  132. package/src/providers/claude.mjs +0 -236
  133. package/src/providers/codex.mjs +0 -255
  134. package/src/providers/detect.mjs +0 -185
  135. package/src/providers/errors.mjs +0 -373
  136. package/src/providers/select.mjs +0 -162
  137. package/src/repl-enhanced.mjs +0 -417
  138. package/src/repl.mjs +0 -321
  139. package/src/state/archive.mjs +0 -366
  140. package/src/state/atomic.mjs +0 -116
  141. package/src/state/cleanup.mjs +0 -440
  142. package/src/state/recovery.mjs +0 -461
  143. package/src/state/session.mjs +0 -147
  144. package/src/ui/errors.mjs +0 -456
  145. package/src/ui/formatter.mjs +0 -327
  146. package/src/ui/icons.mjs +0 -318
  147. package/src/ui/progress.mjs +0 -468
  148. package/templates/prompts/confidence-format.txt +0 -14
  149. package/templates/prompts/ic-with-feedback.txt +0 -41
  150. package/templates/prompts/ic.txt +0 -13
  151. package/templates/prompts/manager-review.txt +0 -40
  152. package/templates/prompts/manager.txt +0 -14
  153. package/templates/prompts/worker.txt +0 -12
package/src/repl.mjs DELETED
@@ -1,321 +0,0 @@
1
- /**
2
- * repl.mjs — Enhanced interactive conversation loop with rich hierarchy display
3
- */
4
-
5
- import * as readline from 'readline';
6
- import { chef } from './chef.mjs';
7
- import { addMessage, loadSession, getSessionSummary } from './state/session.mjs';
8
- import { endPerformanceSession } from './monitor/performance.mjs';
9
- import { displayQuickSummary } from './monitor/report.mjs';
10
- import { fmt, separator, formatTier, formatDuration, timeAgo, formatConfidence } from './ui/formatter.mjs';
11
- import { status, tier, session as sessionIcons, getConfidenceIcon } from './ui/icons.mjs';
12
- import { createSpinner, HierarchicalProgress } from './ui/progress.mjs';
13
- import { displayError, handleError } from './ui/errors.mjs';
14
-
15
- /**
16
- * Create and configure readline interface
17
- */
18
- function createReadlineInterface() {
19
- const rl = readline.createInterface({
20
- input: process.stdin,
21
- output: process.stdout,
22
- prompt: 'You: ',
23
- history: [],
24
- historySize: 100
25
- });
26
-
27
- // Handle Ctrl+C gracefully with enhanced output
28
- rl.on('SIGINT', () => {
29
- console.log('\n');
30
- const spinner = createSpinner('Finalizing session...');
31
- spinner.start();
32
-
33
- setTimeout(() => {
34
- spinner.success('Session finalized');
35
- endPerformanceSession();
36
- displayQuickSummary();
37
- console.log(fmt.success('Session saved. Goodbye!'));
38
- process.exit(0);
39
- }, 500);
40
- });
41
-
42
- return rl;
43
- }
44
-
45
- /**
46
- * Display enhanced session resume info
47
- */
48
- function displaySessionResume() {
49
- const summary = getSessionSummary();
50
-
51
- if (summary.messageCount > 0) {
52
- const sessionIcon = sessionIcons.active;
53
- const lastTime = summary.lastMessage
54
- ? timeAgo(summary.lastMessage.timestamp)
55
- : 'Unknown';
56
- const duration = summary.duration ? formatDuration(summary.duration) : '';
57
-
58
- console.log(`\n${sessionIcon} ${fmt.cyan('Resuming active session')}`);
59
- console.log(` ${fmt.dim('Messages:')} ${fmt.bold(summary.userMessageCount)} exchanges`);
60
- if (duration) {
61
- console.log(` ${fmt.dim('Duration:')} ${fmt.bold(duration)}`);
62
- }
63
- console.log(` ${fmt.dim('Last activity:')} ${fmt.bold(lastTime)}`);
64
-
65
- // Show recent context
66
- if (summary.lastMessage) {
67
- const preview = summary.lastMessage.content.slice(0, 80);
68
- const truncated = summary.lastMessage.content.length > 80 ? '...' : '';
69
- console.log(` ${fmt.dim('Context:')} ${fmt.dim(`"${preview}${truncated}"`)}`);
70
- }
71
- } else {
72
- console.log(`\n${sessionIcons.new} ${fmt.green('Starting new session')}`);
73
- console.log(` ${fmt.dim('Ready for AI-powered development workflow')}`);
74
- }
75
- }
76
-
77
- /**
78
- * Display enhanced result with rich formatting and visual hierarchy
79
- */
80
- function displayResult(result) {
81
- if (result.success) {
82
- console.log('\n' + separator(60, '═', fmt.green('')));
83
-
84
- // Status line with tier and model info
85
- const tierIcon = tier[result.tier] || tier.worker;
86
- const formattedTier = formatTier(result.tier);
87
- console.log(`${status.success} ${fmt.success('Completed by')} ${tierIcon} ${formattedTier}`);
88
-
89
- // Model information
90
- if (result.model) {
91
- const modelDisplay = `${result.model.provider}/${result.model.model}`;
92
- console.log(` ${fmt.dim('Model:')} ${fmt.formatModel(result.model.provider, result.model.model)}`);
93
- }
94
-
95
- // Confidence display with visual indicator
96
- if (result.confidence !== null) {
97
- const confidenceIcon = getConfidenceIcon(result.confidence);
98
- const confidenceText = formatConfidence(result.confidence);
99
- console.log(` ${confidenceIcon} ${fmt.dim('Confidence:')} ${confidenceText}`);
100
- }
101
-
102
- // Escalation information
103
- if (result.totalAttempts > 1) {
104
- const escalations = result.totalAttempts - 1;
105
- console.log(` ${status.loading} ${fmt.warning(`Escalations: ${escalations}`)}`);
106
- }
107
-
108
- // Duration with smart formatting
109
- if (result.durationMs) {
110
- const duration = formatDuration(result.durationMs);
111
- const durationColor = result.durationMs > 30000 ? fmt.yellow : fmt.green;
112
- console.log(` ${fmt.dim('Duration:')} ${durationColor(duration)}`);
113
- }
114
-
115
- // Token efficiency (if available)
116
- if (result.tokensUsed) {
117
- console.log(` ${fmt.dim('Tokens:')} ${fmt.cyan(result.tokensUsed.toLocaleString())}`);
118
- }
119
-
120
- console.log(separator(60, '═', fmt.green('')));
121
- console.log('\n' + result.output);
122
-
123
- } else {
124
- console.log('\n' + separator(60, '═', fmt.red('')));
125
-
126
- // Error status line
127
- const tierIcon = result.tier ? tier[result.tier] || tier.worker : status.error;
128
- console.log(`${status.error} ${fmt.error('Task Failed')}`);
129
-
130
- // Final tier information
131
- if (result.tier) {
132
- const formattedTier = formatTier(result.tier);
133
- console.log(` ${fmt.dim('Final tier:')} ${tierIcon} ${formattedTier}`);
134
- }
135
-
136
- // Attempt information
137
- if (result.totalAttempts) {
138
- console.log(` ${fmt.dim('Attempts:')} ${fmt.yellow(result.totalAttempts)}`);
139
- }
140
-
141
- // Duration
142
- if (result.durationMs) {
143
- const duration = formatDuration(result.durationMs);
144
- console.log(` ${fmt.dim('Duration:')} ${fmt.dim(duration)}`);
145
- }
146
-
147
- console.log(separator(60, '═', fmt.red('')));
148
-
149
- // Error message with proper formatting
150
- const errorMsg = result.output || result.error || 'Unknown error';
151
- console.log('\n' + fmt.error(errorMsg));
152
- }
153
- console.log('\n');
154
- }
155
-
156
- /**
157
- * Main REPL loop
158
- */
159
- export async function startREPL(context) {
160
- const rl = createReadlineInterface();
161
-
162
- displaySessionResume();
163
-
164
- console.log('\nType your request and press Enter. Use Ctrl+C to exit.\n');
165
-
166
- const askQuestion = () => {
167
- rl.prompt();
168
- };
169
-
170
- rl.on('line', async (input) => {
171
- const userInput = input.trim();
172
-
173
- if (!userInput) {
174
- askQuestion();
175
- return;
176
- }
177
-
178
- // Handle special commands
179
- if (userInput === '/quit' || userInput === '/exit') {
180
- console.log('\nGoodbye! Session saved.');
181
- rl.close();
182
- return;
183
- }
184
-
185
- if (userInput === '/clear') {
186
- console.clear();
187
- displaySessionResume();
188
- askQuestion();
189
- return;
190
- }
191
-
192
- if (userInput === '/help') {
193
- console.log(fmt.bold('\n📋 Available Commands:'));
194
- console.log(separator(50, '─'));
195
-
196
- const helpSections = [
197
- {
198
- title: 'Chat Commands:',
199
- commands: [
200
- { cmd: '/help', desc: 'Show this help' },
201
- { cmd: '/clear', desc: 'Clear screen and show session info' },
202
- { cmd: '/status', desc: 'Show detailed session status' },
203
- { cmd: '/quit or /exit', desc: 'Exit Cortex gracefully' }
204
- ]
205
- },
206
- {
207
- title: 'AI Org Chart Hierarchy:',
208
- commands: [
209
- { cmd: `${tier.worker} WORKER`, desc: 'Simple tasks, file search, documentation' },
210
- { cmd: `${tier.ic} IC`, desc: 'Implementation, coding, main development work' },
211
- { cmd: `${tier.manager} MANAGER`, desc: 'Architecture, reviews, complex decisions' }
212
- ]
213
- }
214
- ];
215
-
216
- for (const section of helpSections) {
217
- console.log(`\n ${fmt.bold(section.title)}`);
218
- for (const item of section.commands) {
219
- const cmdPadded = item.cmd.padEnd(20);
220
- console.log(` ${fmt.cyan(cmdPadded)} ${fmt.dim('—')} ${item.desc}`);
221
- }
222
- }
223
-
224
- console.log(`\n ${fmt.dim('Cortex automatically routes tasks to the right tier and escalates when needed.')}`);
225
- console.log('');
226
- askQuestion();
227
- return;
228
- }
229
-
230
- if (userInput === '/status') {
231
- const summary = getSessionSummary();
232
-
233
- console.log('\n' + fmt.bold('📊 Session Status:'));
234
- console.log(separator(40, '─'));
235
-
236
- console.log(` ${sessionIcons.active} ${fmt.bold('Active Session')}`);
237
- console.log(` Messages: ${fmt.cyan(summary.messageCount)} (${summary.userMessageCount} user, ${summary.assistantMessageCount} assistant)`);
238
-
239
- if (summary.duration) {
240
- const duration = formatDuration(summary.duration);
241
- console.log(` Duration: ${fmt.cyan(duration)}`);
242
- }
243
-
244
- if (summary.lastMessage) {
245
- const lastTime = timeAgo(summary.lastMessage.timestamp);
246
- console.log(` Last activity: ${fmt.cyan(lastTime)}`);
247
-
248
- // Show performance stats if available
249
- const avgResponseTime = '1.3s'; // Mock - replace with real data
250
- const successRate = '96%'; // Mock - replace with real data
251
- console.log(` Performance: ${fmt.green(avgResponseTime)} avg, ${fmt.green(successRate)} success`);
252
- }
253
-
254
- console.log('');
255
- askQuestion();
256
- return;
257
- }
258
-
259
- // Save user message
260
- addMessage('user', userInput);
261
-
262
- // Process with the chef with enhanced progress indication
263
- try {
264
- // Show processing indicator
265
- const spinner = createSpinner('Processing request...');
266
- spinner.start();
267
-
268
- const result = await chef(userInput, context);
269
-
270
- // Stop spinner and show result
271
- spinner.stop();
272
-
273
- // Save assistant response
274
- addMessage('assistant', result.output, {
275
- tier: result.tier,
276
- confidence: result.confidence,
277
- success: result.success,
278
- model: result.model ? `${result.model.provider}/${result.model.model}` : null,
279
- duration: result.durationMs,
280
- tokensUsed: result.tokensUsed
281
- });
282
-
283
- // Display enhanced result
284
- displayResult(result);
285
-
286
- } catch (error) {
287
- // Enhanced error display
288
- console.log('\n' + separator(60, '═', fmt.red('')));
289
- console.log(`${status.error} ${fmt.error('Orchestration Error')}`);
290
- console.log(` ${fmt.dim('Details:')} ${error.message}`);
291
- console.log(` ${status.info} Try rephrasing your request or check --doctor`);
292
- console.log(separator(60, '═', fmt.red('')) + '\n');
293
-
294
- // Save error message
295
- addMessage('assistant', `Error: ${error.message}`, {
296
- success: false,
297
- error: true,
298
- timestamp: Date.now()
299
- });
300
- }
301
-
302
- askQuestion();
303
- });
304
-
305
- rl.on('close', () => {
306
- console.log('\n');
307
- const spinner = createSpinner('Finalizing session...');
308
- spinner.start();
309
-
310
- setTimeout(() => {
311
- spinner.success('Session finalized');
312
- endPerformanceSession();
313
- displayQuickSummary();
314
- console.log(fmt.success('Session saved. Goodbye!'));
315
- process.exit(0);
316
- }, 500);
317
- });
318
-
319
- // Start the conversation
320
- askQuestion();
321
- }
@@ -1,366 +0,0 @@
1
- /**
2
- * archive.mjs — Session archiving and long-term state management
3
- */
4
-
5
- import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
6
- import { join } from 'path';
7
- import { atomicWriteJSON } from './atomic.mjs';
8
- import { loadSession, getSessionSummary } from './session.mjs';
9
-
10
- /**
11
- * Get archive directory
12
- */
13
- function getArchiveDir(workspace = process.cwd()) {
14
- const archiveDir = join(workspace, '.cortex', 'archive');
15
- if (!existsSync(archiveDir)) {
16
- mkdirSync(archiveDir, { recursive: true });
17
- }
18
- return archiveDir;
19
- }
20
-
21
- /**
22
- * Create a comprehensive session archive
23
- */
24
- export function createSessionArchive(reason = 'manual', metadata = {}, workspace = process.cwd()) {
25
- const summary = getSessionSummary(workspace);
26
-
27
- if (summary.messageCount === 0) {
28
- return { archived: false, reason: 'No session data to archive' };
29
- }
30
-
31
- const archiveId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 8)}`;
32
- const archiveDir = getArchiveDir(workspace);
33
-
34
- const archive = {
35
- id: archiveId,
36
- timestamp: new Date().toISOString(),
37
- reason,
38
- summary,
39
- messages: loadSession(workspace),
40
- workspace: workspace,
41
- metadata: {
42
- ...metadata,
43
- cortexVersion: '1.0.0',
44
- nodeVersion: process.version,
45
- platform: process.platform
46
- }
47
- };
48
-
49
- // Add performance metrics if available
50
- if (summary.duration) {
51
- archive.performance = {
52
- totalDuration: summary.duration,
53
- avgResponseTime: summary.duration / Math.max(summary.assistantMessageCount, 1),
54
- messagesPerMinute: (summary.messageCount / (summary.duration / 60000)).toFixed(2)
55
- };
56
- }
57
-
58
- const archivePath = join(archiveDir, `${archiveId}.json`);
59
- atomicWriteJSON(archivePath, archive);
60
-
61
- // Create human-readable summary
62
- const summaryPath = join(archiveDir, `${archiveId}-summary.md`);
63
- const summaryContent = createMarkdownSummary(archive);
64
- writeFileSync(summaryPath, summaryContent);
65
-
66
- return {
67
- archived: true,
68
- archiveId,
69
- archivePath,
70
- summaryPath,
71
- messageCount: summary.messageCount,
72
- duration: summary.duration
73
- };
74
- }
75
-
76
- /**
77
- * Create markdown summary of the session
78
- */
79
- function createMarkdownSummary(archive) {
80
- const { summary, messages, performance, reason, timestamp } = archive;
81
-
82
- let content = `# Cortex Session Archive\n\n`;
83
- content += `**Archive ID:** ${archive.id}\n`;
84
- content += `**Created:** ${new Date(timestamp).toLocaleString()}\n`;
85
- content += `**Reason:** ${reason}\n`;
86
- content += `**Workspace:** ${archive.workspace}\n\n`;
87
-
88
- content += `## Summary\n\n`;
89
- content += `- **Total Messages:** ${summary.messageCount}\n`;
90
- content += `- **User Messages:** ${summary.userMessageCount}\n`;
91
- content += `- **Assistant Messages:** ${summary.assistantMessageCount}\n`;
92
-
93
- if (summary.duration) {
94
- const durationMin = Math.round(summary.duration / 60000);
95
- content += `- **Duration:** ${durationMin} minutes\n`;
96
- }
97
-
98
- if (performance) {
99
- content += `\n## Performance\n\n`;
100
- content += `- **Average Response Time:** ${Math.round(performance.avgResponseTime / 1000)}s\n`;
101
- content += `- **Messages Per Minute:** ${performance.messagesPerMinute}\n`;
102
- }
103
-
104
- // Add sample of conversations
105
- const userMessages = messages.filter(m => m.role === 'user');
106
- if (userMessages.length > 0) {
107
- content += `\n## Conversation Topics\n\n`;
108
- const sampleSize = Math.min(5, userMessages.length);
109
- const samples = userMessages.slice(0, sampleSize);
110
-
111
- for (const [index, msg] of samples.entries()) {
112
- const preview = msg.content.length > 80 ?
113
- msg.content.substring(0, 80) + '...' :
114
- msg.content;
115
- content += `${index + 1}. ${preview}\n`;
116
- }
117
-
118
- if (userMessages.length > sampleSize) {
119
- content += `\n_(${userMessages.length - sampleSize} more messages not shown)_\n`;
120
- }
121
- }
122
-
123
- // Add handoff statistics if available
124
- const handoffMessages = messages.filter(m => m.type === 'handoff');
125
- if (handoffMessages.length > 0) {
126
- content += `\n## Hierarchy Activity\n\n`;
127
- content += `- **Total Handoffs:** ${handoffMessages.length}\n`;
128
-
129
- const escalations = handoffMessages.filter(m => m.operation === 'escalate').length;
130
- const delegations = handoffMessages.filter(m => m.operation === 'delegate').length;
131
- const bounces = handoffMessages.filter(m => m.operation === 'bounce').length;
132
-
133
- if (escalations > 0) content += `- **Escalations:** ${escalations}\n`;
134
- if (delegations > 0) content += `- **Delegations:** ${delegations}\n`;
135
- if (bounces > 0) content += `- **Bounces:** ${bounces}\n`;
136
- }
137
-
138
- content += `\n## Archive Details\n\n`;
139
- content += `Full conversation data is available in: \`${archive.id}.json\`\n`;
140
-
141
- return content;
142
- }
143
-
144
- /**
145
- * List available archives with metadata
146
- */
147
- export function listArchives(workspace = process.cwd()) {
148
- const archiveDir = getArchiveDir(workspace);
149
-
150
- if (!existsSync(archiveDir)) {
151
- return [];
152
- }
153
-
154
- try {
155
- const archiveFiles = readdirSync(archiveDir)
156
- .filter(f => f.endsWith('.json') && f.startsWith('session-'))
157
- .map(f => join(archiveDir, f))
158
- .map(filePath => {
159
- try {
160
- const archive = JSON.parse(readFileSync(filePath, 'utf8'));
161
- const stat = statSync(filePath);
162
-
163
- return {
164
- id: archive.id,
165
- timestamp: archive.timestamp,
166
- reason: archive.reason,
167
- messageCount: archive.summary.messageCount,
168
- duration: archive.summary.duration,
169
- fileSize: stat.size,
170
- filePath
171
- };
172
- } catch {
173
- return null;
174
- }
175
- })
176
- .filter(Boolean)
177
- .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
178
-
179
- return archiveFiles;
180
- } catch {
181
- return [];
182
- }
183
- }
184
-
185
- /**
186
- * Get archive by ID
187
- */
188
- export function getArchive(archiveId, workspace = process.cwd()) {
189
- const archiveDir = getArchiveDir(workspace);
190
- const archivePath = join(archiveDir, `${archiveId}.json`);
191
-
192
- if (!existsSync(archivePath)) {
193
- throw new Error(`Archive ${archiveId} not found`);
194
- }
195
-
196
- try {
197
- return JSON.parse(readFileSync(archivePath, 'utf8'));
198
- } catch (error) {
199
- throw new Error(`Failed to load archive ${archiveId}: ${error.message}`);
200
- }
201
- }
202
-
203
- /**
204
- * Delete old archives to manage storage
205
- */
206
- export function cleanupOldArchives(maxAge = 30, workspace = process.cwd()) {
207
- const archiveDir = getArchiveDir(workspace);
208
- const maxAgeMs = maxAge * 24 * 60 * 60 * 1000; // Convert days to ms
209
- const cutoff = Date.now() - maxAgeMs;
210
-
211
- if (!existsSync(archiveDir)) {
212
- return { deleted: 0, preserved: 0, errors: [] };
213
- }
214
-
215
- const archives = listArchives(workspace);
216
- let deleted = 0;
217
- let preserved = 0;
218
- const errors = [];
219
-
220
- for (const archive of archives) {
221
- const archiveDate = new Date(archive.timestamp).getTime();
222
-
223
- if (archiveDate < cutoff) {
224
- try {
225
- const archivePath = archive.filePath;
226
- const summaryPath = archivePath.replace('.json', '-summary.md');
227
-
228
- // Delete both files
229
- if (existsSync(archivePath)) {
230
- require('fs').unlinkSync(archivePath);
231
- }
232
- if (existsSync(summaryPath)) {
233
- require('fs').unlinkSync(summaryPath);
234
- }
235
-
236
- deleted++;
237
- } catch (error) {
238
- errors.push(`Failed to delete ${archive.id}: ${error.message}`);
239
- }
240
- } else {
241
- preserved++;
242
- }
243
- }
244
-
245
- return { deleted, preserved, errors };
246
- }
247
-
248
- /**
249
- * Export archives to external format
250
- */
251
- export function exportArchives(format = 'json', outputPath = null, workspace = process.cwd()) {
252
- const archives = listArchives(workspace);
253
-
254
- if (archives.length === 0) {
255
- return { exported: false, reason: 'No archives to export' };
256
- }
257
-
258
- const exportData = {
259
- exported: new Date().toISOString(),
260
- cortexVersion: '1.0.0',
261
- workspace: workspace,
262
- archiveCount: archives.length,
263
- archives: archives.map(meta => {
264
- try {
265
- return getArchive(meta.id, workspace);
266
- } catch (error) {
267
- return { error: error.message, id: meta.id };
268
- }
269
- })
270
- };
271
-
272
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
273
- const filename = outputPath || join(workspace, `.cortex-export-${timestamp}.${format}`);
274
-
275
- try {
276
- if (format === 'json') {
277
- writeFileSync(filename, JSON.stringify(exportData, null, 2));
278
- } else {
279
- throw new Error(`Unsupported export format: ${format}`);
280
- }
281
-
282
- return {
283
- exported: true,
284
- filename,
285
- archiveCount: archives.length,
286
- fileSize: require('fs').statSync(filename).size
287
- };
288
- } catch (error) {
289
- return {
290
- exported: false,
291
- error: error.message
292
- };
293
- }
294
- }
295
-
296
- /**
297
- * Get storage usage statistics
298
- */
299
- export function getStorageStats(workspace = process.cwd()) {
300
- const cortexDir = join(workspace, '.cortex');
301
-
302
- if (!existsSync(cortexDir)) {
303
- return { totalSize: 0, breakdown: {} };
304
- }
305
-
306
- const stats = {
307
- totalSize: 0,
308
- breakdown: {
309
- sessions: 0,
310
- archives: 0,
311
- plans: 0,
312
- auth: 0,
313
- other: 0
314
- }
315
- };
316
-
317
- function calculateDirSize(dir, category = 'other') {
318
- if (!existsSync(dir)) return 0;
319
-
320
- let size = 0;
321
- try {
322
- const entries = readdirSync(dir);
323
-
324
- for (const entry of entries) {
325
- const fullPath = join(dir, entry);
326
- const stat = statSync(fullPath);
327
-
328
- if (stat.isDirectory()) {
329
- size += calculateDirSize(fullPath, category);
330
- } else {
331
- size += stat.size;
332
- }
333
- }
334
- } catch {
335
- // Permission or access error
336
- }
337
-
338
- stats.breakdown[category] += size;
339
- return size;
340
- }
341
-
342
- // Calculate sizes by category
343
- calculateDirSize(join(cortexDir, 'sessions'), 'sessions');
344
- calculateDirSize(join(cortexDir, 'archive'), 'archives');
345
- calculateDirSize(join(cortexDir, 'plans'), 'plans');
346
- calculateDirSize(join(cortexDir, 'auth'), 'auth');
347
-
348
- // Calculate other files
349
- try {
350
- const cortexEntries = readdirSync(cortexDir);
351
- for (const entry of cortexEntries) {
352
- const fullPath = join(cortexDir, entry);
353
- const stat = statSync(fullPath);
354
-
355
- if (stat.isFile()) {
356
- stats.breakdown.other += stat.size;
357
- }
358
- }
359
- } catch {
360
- // Access error
361
- }
362
-
363
- stats.totalSize = Object.values(stats.breakdown).reduce((sum, size) => sum + size, 0);
364
-
365
- return stats;
366
- }