myshell-tools 1.0.0 → 2.0.0-alpha.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 (150) 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 +106 -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/core/assess.d.ts +25 -0
  14. package/dist/core/assess.js +142 -0
  15. package/dist/core/assess.js.map +1 -0
  16. package/dist/core/classify.d.ts +19 -0
  17. package/dist/core/classify.js +80 -0
  18. package/dist/core/classify.js.map +1 -0
  19. package/dist/core/escalate.d.ts +32 -0
  20. package/dist/core/escalate.js +57 -0
  21. package/dist/core/escalate.js.map +1 -0
  22. package/dist/core/index.d.ts +13 -0
  23. package/dist/core/index.js +12 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/orchestrate.d.ts +42 -0
  26. package/dist/core/orchestrate.js +439 -0
  27. package/dist/core/orchestrate.js.map +1 -0
  28. package/dist/core/policy.d.ts +9 -0
  29. package/dist/core/policy.js +27 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/prompt.d.ts +26 -0
  32. package/dist/core/prompt.js +125 -0
  33. package/dist/core/prompt.js.map +1 -0
  34. package/dist/core/review.d.ts +46 -0
  35. package/dist/core/review.js +148 -0
  36. package/dist/core/review.js.map +1 -0
  37. package/dist/core/route.d.ts +28 -0
  38. package/dist/core/route.js +52 -0
  39. package/dist/core/route.js.map +1 -0
  40. package/dist/core/types.d.ts +141 -0
  41. package/dist/core/types.js +14 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/infra/atomic.d.ts +53 -0
  44. package/dist/infra/atomic.js +171 -0
  45. package/dist/infra/atomic.js.map +1 -0
  46. package/dist/infra/clock.d.ts +9 -0
  47. package/dist/infra/clock.js +15 -0
  48. package/dist/infra/clock.js.map +1 -0
  49. package/dist/infra/index.d.ts +9 -0
  50. package/dist/infra/index.js +7 -0
  51. package/dist/infra/index.js.map +1 -0
  52. package/dist/infra/ledger.d.ts +49 -0
  53. package/dist/infra/ledger.js +90 -0
  54. package/dist/infra/ledger.js.map +1 -0
  55. package/dist/infra/paths.d.ts +28 -0
  56. package/dist/infra/paths.js +38 -0
  57. package/dist/infra/paths.js.map +1 -0
  58. package/dist/infra/pricing.d.ts +47 -0
  59. package/dist/infra/pricing.js +151 -0
  60. package/dist/infra/pricing.js.map +1 -0
  61. package/dist/infra/session.d.ts +28 -0
  62. package/dist/infra/session.js +61 -0
  63. package/dist/infra/session.js.map +1 -0
  64. package/dist/interface/render.d.ts +27 -0
  65. package/dist/interface/render.js +134 -0
  66. package/dist/interface/render.js.map +1 -0
  67. package/dist/interface/repl.d.ts +23 -0
  68. package/dist/interface/repl.js +90 -0
  69. package/dist/interface/repl.js.map +1 -0
  70. package/dist/interface/run.d.ts +20 -0
  71. package/dist/interface/run.js +31 -0
  72. package/dist/interface/run.js.map +1 -0
  73. package/dist/providers/claude-parse.d.ts +24 -0
  74. package/dist/providers/claude-parse.js +113 -0
  75. package/dist/providers/claude-parse.js.map +1 -0
  76. package/dist/providers/claude.d.ts +45 -0
  77. package/dist/providers/claude.js +122 -0
  78. package/dist/providers/claude.js.map +1 -0
  79. package/dist/providers/codex-parse.d.ts +32 -0
  80. package/dist/providers/codex-parse.js +145 -0
  81. package/dist/providers/codex-parse.js.map +1 -0
  82. package/dist/providers/codex.d.ts +44 -0
  83. package/dist/providers/codex.js +124 -0
  84. package/dist/providers/codex.js.map +1 -0
  85. package/dist/providers/detect.d.ts +49 -0
  86. package/dist/providers/detect.js +125 -0
  87. package/dist/providers/detect.js.map +1 -0
  88. package/dist/providers/errors.d.ts +49 -0
  89. package/dist/providers/errors.js +189 -0
  90. package/dist/providers/errors.js.map +1 -0
  91. package/dist/providers/index.d.ts +9 -0
  92. package/dist/providers/index.js +7 -0
  93. package/dist/providers/index.js.map +1 -0
  94. package/dist/providers/port.d.ts +74 -0
  95. package/dist/providers/port.js +16 -0
  96. package/dist/providers/port.js.map +1 -0
  97. package/dist/providers/registry.d.ts +21 -0
  98. package/dist/providers/registry.js +34 -0
  99. package/dist/providers/registry.js.map +1 -0
  100. package/dist/ui/banner.d.ts +19 -0
  101. package/dist/ui/banner.js +32 -0
  102. package/dist/ui/banner.js.map +1 -0
  103. package/dist/ui/spinner.d.ts +27 -0
  104. package/dist/ui/spinner.js +67 -0
  105. package/dist/ui/spinner.js.map +1 -0
  106. package/dist/ui/theme.d.ts +32 -0
  107. package/dist/ui/theme.js +56 -0
  108. package/dist/ui/theme.js.map +1 -0
  109. package/package.json +55 -49
  110. package/data/orchestrator.json +0 -113
  111. package/src/auth/recovery.mjs +0 -328
  112. package/src/auth/refresh.mjs +0 -373
  113. package/src/chef.mjs +0 -348
  114. package/src/cli/doctor.mjs +0 -568
  115. package/src/cli/reset.mjs +0 -447
  116. package/src/cli/status.mjs +0 -379
  117. package/src/cli.mjs +0 -429
  118. package/src/commands/doctor.mjs +0 -375
  119. package/src/commands/help.mjs +0 -324
  120. package/src/commands/status.mjs +0 -331
  121. package/src/monitor/health.mjs +0 -486
  122. package/src/monitor/performance.mjs +0 -442
  123. package/src/monitor/report.mjs +0 -535
  124. package/src/orchestrator/classify.mjs +0 -391
  125. package/src/orchestrator/confidence.mjs +0 -151
  126. package/src/orchestrator/handoffs.mjs +0 -231
  127. package/src/orchestrator/review.mjs +0 -222
  128. package/src/providers/balance.mjs +0 -201
  129. package/src/providers/claude.mjs +0 -236
  130. package/src/providers/codex.mjs +0 -255
  131. package/src/providers/detect.mjs +0 -185
  132. package/src/providers/errors.mjs +0 -373
  133. package/src/providers/select.mjs +0 -162
  134. package/src/repl-enhanced.mjs +0 -417
  135. package/src/repl.mjs +0 -321
  136. package/src/state/archive.mjs +0 -366
  137. package/src/state/atomic.mjs +0 -116
  138. package/src/state/cleanup.mjs +0 -440
  139. package/src/state/recovery.mjs +0 -461
  140. package/src/state/session.mjs +0 -147
  141. package/src/ui/errors.mjs +0 -456
  142. package/src/ui/formatter.mjs +0 -327
  143. package/src/ui/icons.mjs +0 -318
  144. package/src/ui/progress.mjs +0 -468
  145. package/templates/prompts/confidence-format.txt +0 -14
  146. package/templates/prompts/ic-with-feedback.txt +0 -41
  147. package/templates/prompts/ic.txt +0 -13
  148. package/templates/prompts/manager-review.txt +0 -40
  149. package/templates/prompts/manager.txt +0 -14
  150. 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
- }