@luckydraw/cumulus 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/dist/cli/cumulus.d.ts +3 -0
- package/dist/cli/cumulus.d.ts.map +1 -0
- package/dist/cli/cumulus.js +233 -0
- package/dist/cli/cumulus.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +86 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +241 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/content-detector.d.ts +46 -0
- package/dist/lib/content-detector.d.ts.map +1 -0
- package/dist/lib/content-detector.js +359 -0
- package/dist/lib/content-detector.js.map +1 -0
- package/dist/lib/content-store.d.ts +255 -0
- package/dist/lib/content-store.d.ts.map +1 -0
- package/dist/lib/content-store.js +955 -0
- package/dist/lib/content-store.js.map +1 -0
- package/dist/lib/context-budget.d.ts +83 -0
- package/dist/lib/context-budget.d.ts.map +1 -0
- package/dist/lib/context-budget.js +101 -0
- package/dist/lib/context-budget.js.map +1 -0
- package/dist/lib/embeddings.d.ts +64 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/lib/embeddings.js +176 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/history.d.ts +120 -0
- package/dist/lib/history.d.ts.map +1 -0
- package/dist/lib/history.js +205 -0
- package/dist/lib/history.js.map +1 -0
- package/dist/lib/image-utils.d.ts +41 -0
- package/dist/lib/image-utils.d.ts.map +1 -0
- package/dist/lib/image-utils.js +288 -0
- package/dist/lib/image-utils.js.map +1 -0
- package/dist/lib/migrate.d.ts +35 -0
- package/dist/lib/migrate.d.ts.map +1 -0
- package/dist/lib/migrate.js +196 -0
- package/dist/lib/migrate.js.map +1 -0
- package/dist/lib/retriever.d.ts +56 -0
- package/dist/lib/retriever.d.ts.map +1 -0
- package/dist/lib/retriever.js +644 -0
- package/dist/lib/retriever.js.map +1 -0
- package/dist/lib/revert.d.ts +23 -0
- package/dist/lib/revert.d.ts.map +1 -0
- package/dist/lib/revert.js +75 -0
- package/dist/lib/revert.js.map +1 -0
- package/dist/lib/session.d.ts +65 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +289 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/snapshots.d.ts +39 -0
- package/dist/lib/snapshots.d.ts.map +1 -0
- package/dist/lib/snapshots.js +99 -0
- package/dist/lib/snapshots.js.map +1 -0
- package/dist/lib/stream-processor.d.ts +149 -0
- package/dist/lib/stream-processor.d.ts.map +1 -0
- package/dist/lib/stream-processor.js +389 -0
- package/dist/lib/stream-processor.js.map +1 -0
- package/dist/lib/summarizer.d.ts +67 -0
- package/dist/lib/summarizer.d.ts.map +1 -0
- package/dist/lib/summarizer.js +213 -0
- package/dist/lib/summarizer.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +16 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/proxy.d.ts +19 -0
- package/dist/mcp/proxy.d.ts.map +1 -0
- package/dist/mcp/proxy.js +120 -0
- package/dist/mcp/proxy.js.map +1 -0
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +29 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/shared-server.d.ts +21 -0
- package/dist/mcp/shared-server.d.ts.map +1 -0
- package/dist/mcp/shared-server.js +210 -0
- package/dist/mcp/shared-server.js.map +1 -0
- package/dist/mcp/tool-handler.d.ts +20 -0
- package/dist/mcp/tool-handler.d.ts.map +1 -0
- package/dist/mcp/tool-handler.js +1405 -0
- package/dist/mcp/tool-handler.js.map +1 -0
- package/dist/tui/components/App.d.ts +11 -0
- package/dist/tui/components/App.d.ts.map +1 -0
- package/dist/tui/components/App.js +607 -0
- package/dist/tui/components/App.js.map +1 -0
- package/dist/tui/components/DebugContextView.d.ts +13 -0
- package/dist/tui/components/DebugContextView.d.ts.map +1 -0
- package/dist/tui/components/DebugContextView.js +78 -0
- package/dist/tui/components/DebugContextView.js.map +1 -0
- package/dist/tui/components/IncludeMenu.d.ts +12 -0
- package/dist/tui/components/IncludeMenu.d.ts.map +1 -0
- package/dist/tui/components/IncludeMenu.js +127 -0
- package/dist/tui/components/IncludeMenu.js.map +1 -0
- package/dist/tui/components/InputArea.d.ts +27 -0
- package/dist/tui/components/InputArea.d.ts.map +1 -0
- package/dist/tui/components/InputArea.js +366 -0
- package/dist/tui/components/InputArea.js.map +1 -0
- package/dist/tui/components/MarkdownText.d.ts +38 -0
- package/dist/tui/components/MarkdownText.d.ts.map +1 -0
- package/dist/tui/components/MarkdownText.js +234 -0
- package/dist/tui/components/MarkdownText.js.map +1 -0
- package/dist/tui/components/MessageBubble.d.ts +11 -0
- package/dist/tui/components/MessageBubble.d.ts.map +1 -0
- package/dist/tui/components/MessageBubble.js +16 -0
- package/dist/tui/components/MessageBubble.js.map +1 -0
- package/dist/tui/components/MessageHistory.d.ts +11 -0
- package/dist/tui/components/MessageHistory.d.ts.map +1 -0
- package/dist/tui/components/MessageHistory.js +12 -0
- package/dist/tui/components/MessageHistory.js.map +1 -0
- package/dist/tui/components/RevertMenu.d.ts +17 -0
- package/dist/tui/components/RevertMenu.d.ts.map +1 -0
- package/dist/tui/components/RevertMenu.js +144 -0
- package/dist/tui/components/RevertMenu.js.map +1 -0
- package/dist/tui/components/StatusBar.d.ts +14 -0
- package/dist/tui/components/StatusBar.d.ts.map +1 -0
- package/dist/tui/components/StatusBar.js +13 -0
- package/dist/tui/components/StatusBar.js.map +1 -0
- package/dist/tui/components/StreamingResponse.d.ts +15 -0
- package/dist/tui/components/StreamingResponse.d.ts.map +1 -0
- package/dist/tui/components/StreamingResponse.js +52 -0
- package/dist/tui/components/StreamingResponse.js.map +1 -0
- package/dist/tui/hooks/useAppState.d.ts +147 -0
- package/dist/tui/hooks/useAppState.d.ts.map +1 -0
- package/dist/tui/hooks/useAppState.js +110 -0
- package/dist/tui/hooks/useAppState.js.map +1 -0
- package/dist/tui/hooks/useClaudeProcess.d.ts +19 -0
- package/dist/tui/hooks/useClaudeProcess.d.ts.map +1 -0
- package/dist/tui/hooks/useClaudeProcess.js +185 -0
- package/dist/tui/hooks/useClaudeProcess.js.map +1 -0
- package/dist/tui/index.d.ts +10 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +11 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/utils/streamParser.d.ts +31 -0
- package/dist/tui/utils/streamParser.d.ts.map +1 -0
- package/dist/tui/utils/streamParser.js +63 -0
- package/dist/tui/utils/streamParser.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loading and always-include file management.
|
|
3
|
+
*
|
|
4
|
+
* Handles global and per-thread configuration, including reading
|
|
5
|
+
* files that should always be included in the context window.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { estimateTokens } from './context-budget.js';
|
|
11
|
+
const CUMULUS_DIR = path.join(os.homedir(), '.cumulus');
|
|
12
|
+
const THREADS_DIR = path.join(CUMULUS_DIR, 'threads');
|
|
13
|
+
/**
|
|
14
|
+
* Load the global configuration from ~/.cumulus/config.json
|
|
15
|
+
*/
|
|
16
|
+
export async function loadGlobalConfig() {
|
|
17
|
+
const configPath = path.join(CUMULUS_DIR, 'config.json');
|
|
18
|
+
return loadConfigFile(configPath);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load thread-specific configuration from ~/.cumulus/threads/{thread}.config.json
|
|
22
|
+
*/
|
|
23
|
+
export async function loadThreadConfig(threadName) {
|
|
24
|
+
const configPath = path.join(THREADS_DIR, `${threadName}.config.json`);
|
|
25
|
+
return loadConfigFile(configPath);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load a configuration file, returning empty config if not found or invalid.
|
|
29
|
+
*/
|
|
30
|
+
async function loadConfigFile(configPath) {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(configPath)) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
36
|
+
const config = JSON.parse(content);
|
|
37
|
+
// Validate structure
|
|
38
|
+
if (typeof config !== 'object' || config === null) {
|
|
39
|
+
console.warn(`[Config] Invalid config format in ${configPath}, using empty config`);
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
// Validate alwaysInclude if present
|
|
43
|
+
if (config.alwaysInclude !== undefined) {
|
|
44
|
+
if (!Array.isArray(config.alwaysInclude)) {
|
|
45
|
+
console.warn(`[Config] alwaysInclude must be an array in ${configPath}`);
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
for (const item of config.alwaysInclude) {
|
|
49
|
+
if (typeof item !== 'string') {
|
|
50
|
+
console.warn(`[Config] alwaysInclude items must be strings in ${configPath}`);
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Validate projectDir if present
|
|
56
|
+
if (config.projectDir !== undefined) {
|
|
57
|
+
if (typeof config.projectDir !== 'string') {
|
|
58
|
+
console.warn(`[Config] projectDir must be a string in ${configPath}`);
|
|
59
|
+
delete config.projectDir;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return config;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof SyntaxError) {
|
|
66
|
+
console.warn(`[Config] Failed to parse ${configPath}: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Merge global and thread configs. Thread config adds to global (doesn't override).
|
|
73
|
+
*/
|
|
74
|
+
export function mergeConfigs(global, thread) {
|
|
75
|
+
const merged = {};
|
|
76
|
+
// Merge alwaysInclude arrays (deduplicated, thread additions come after global)
|
|
77
|
+
const globalIncludes = global.alwaysInclude ?? [];
|
|
78
|
+
const threadIncludes = thread.alwaysInclude ?? [];
|
|
79
|
+
if (globalIncludes.length > 0 || threadIncludes.length > 0) {
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
merged.alwaysInclude = [];
|
|
82
|
+
for (const item of [...globalIncludes, ...threadIncludes]) {
|
|
83
|
+
if (!seen.has(item)) {
|
|
84
|
+
seen.add(item);
|
|
85
|
+
merged.alwaysInclude.push(item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// projectDir: thread overrides global (simple override, not array merge)
|
|
90
|
+
const projectDir = thread.projectDir ?? global.projectDir;
|
|
91
|
+
if (projectDir) {
|
|
92
|
+
merged.projectDir = projectDir;
|
|
93
|
+
}
|
|
94
|
+
return merged;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a file path that may use ~ or be relative.
|
|
98
|
+
* @param filePath - The path from config (may use ~, be relative, or absolute)
|
|
99
|
+
* @param cwd - Current working directory for relative paths
|
|
100
|
+
*/
|
|
101
|
+
export function resolveFilePath(filePath, cwd) {
|
|
102
|
+
// Handle ~ for home directory
|
|
103
|
+
if (filePath.startsWith('~/')) {
|
|
104
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
105
|
+
}
|
|
106
|
+
if (filePath === '~') {
|
|
107
|
+
return os.homedir();
|
|
108
|
+
}
|
|
109
|
+
// Handle absolute paths
|
|
110
|
+
if (path.isAbsolute(filePath)) {
|
|
111
|
+
return filePath;
|
|
112
|
+
}
|
|
113
|
+
// Relative path - resolve from cwd
|
|
114
|
+
return path.resolve(cwd, filePath);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Read and format all always-include files from config.
|
|
118
|
+
* @param config - Merged configuration
|
|
119
|
+
* @param cwd - Current working directory for relative paths
|
|
120
|
+
*/
|
|
121
|
+
export async function readAlwaysIncludeFiles(config, cwd) {
|
|
122
|
+
const files = [];
|
|
123
|
+
let totalTokens = 0;
|
|
124
|
+
const paths = config.alwaysInclude ?? [];
|
|
125
|
+
for (const filePath of paths) {
|
|
126
|
+
const resolvedPath = resolveFilePath(filePath, cwd);
|
|
127
|
+
const fileInfo = {
|
|
128
|
+
path: filePath,
|
|
129
|
+
resolvedPath,
|
|
130
|
+
content: '',
|
|
131
|
+
tokens: 0,
|
|
132
|
+
truncated: false,
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
136
|
+
fileInfo.error = 'File not found';
|
|
137
|
+
console.warn(`[Config] Always-include file not found: ${resolvedPath}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const stat = fs.statSync(resolvedPath);
|
|
141
|
+
if (stat.isDirectory()) {
|
|
142
|
+
fileInfo.error = 'Path is a directory';
|
|
143
|
+
console.warn(`[Config] Always-include path is a directory: ${resolvedPath}`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
147
|
+
fileInfo.content = content;
|
|
148
|
+
fileInfo.tokens = estimateTokens(content);
|
|
149
|
+
totalTokens += fileInfo.tokens;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
155
|
+
fileInfo.error = errorMsg;
|
|
156
|
+
console.warn(`[Config] Failed to read always-include file ${resolvedPath}: ${errorMsg}`);
|
|
157
|
+
}
|
|
158
|
+
files.push(fileInfo);
|
|
159
|
+
}
|
|
160
|
+
// Format the context string
|
|
161
|
+
const formattedContext = formatAlwaysIncludeContext(files);
|
|
162
|
+
return {
|
|
163
|
+
files,
|
|
164
|
+
totalTokens,
|
|
165
|
+
formattedContext,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Format always-include files into a context string for the system prompt.
|
|
170
|
+
*/
|
|
171
|
+
function formatAlwaysIncludeContext(files) {
|
|
172
|
+
const validFiles = files.filter(f => !f.error && f.content);
|
|
173
|
+
if (validFiles.length === 0) {
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
const lines = ['\nALWAYS-INCLUDED FILES (project context):', '---'];
|
|
177
|
+
for (const file of validFiles) {
|
|
178
|
+
lines.push(`[${file.path}]`);
|
|
179
|
+
lines.push(file.content);
|
|
180
|
+
lines.push('');
|
|
181
|
+
}
|
|
182
|
+
lines.push('---\n');
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Save configuration to a file.
|
|
187
|
+
*/
|
|
188
|
+
export async function saveConfig(config, threadName) {
|
|
189
|
+
const configPath = threadName
|
|
190
|
+
? path.join(THREADS_DIR, `${threadName}.config.json`)
|
|
191
|
+
: path.join(CUMULUS_DIR, 'config.json');
|
|
192
|
+
// Ensure directory exists
|
|
193
|
+
const dir = path.dirname(configPath);
|
|
194
|
+
if (!fs.existsSync(dir)) {
|
|
195
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Add a file to the always-include list.
|
|
201
|
+
* @param filePath - Path to add
|
|
202
|
+
* @param threadName - Thread name for thread-specific config, undefined for global
|
|
203
|
+
*/
|
|
204
|
+
export async function addAlwaysIncludeFile(filePath, threadName) {
|
|
205
|
+
const config = threadName ? await loadThreadConfig(threadName) : await loadGlobalConfig();
|
|
206
|
+
const alwaysInclude = config.alwaysInclude ?? [];
|
|
207
|
+
if (!alwaysInclude.includes(filePath)) {
|
|
208
|
+
alwaysInclude.push(filePath);
|
|
209
|
+
config.alwaysInclude = alwaysInclude;
|
|
210
|
+
await saveConfig(config, threadName);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Remove a file from the always-include list.
|
|
215
|
+
* @param filePath - Path to remove
|
|
216
|
+
* @param threadName - Thread name for thread-specific config, undefined for global
|
|
217
|
+
*/
|
|
218
|
+
export async function removeAlwaysIncludeFile(filePath, threadName) {
|
|
219
|
+
const config = threadName ? await loadThreadConfig(threadName) : await loadGlobalConfig();
|
|
220
|
+
const alwaysInclude = config.alwaysInclude ?? [];
|
|
221
|
+
const index = alwaysInclude.indexOf(filePath);
|
|
222
|
+
if (index !== -1) {
|
|
223
|
+
alwaysInclude.splice(index, 1);
|
|
224
|
+
config.alwaysInclude = alwaysInclude.length > 0 ? alwaysInclude : undefined;
|
|
225
|
+
await saveConfig(config, threadName);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* List all always-include files from config (without reading them).
|
|
230
|
+
*/
|
|
231
|
+
export async function listAlwaysIncludeFiles(threadName) {
|
|
232
|
+
const globalConfig = await loadGlobalConfig();
|
|
233
|
+
const threadConfig = threadName ? await loadThreadConfig(threadName) : {};
|
|
234
|
+
const mergedConfig = mergeConfigs(globalConfig, threadConfig);
|
|
235
|
+
return {
|
|
236
|
+
global: globalConfig.alwaysInclude ?? [],
|
|
237
|
+
thread: threadConfig.alwaysInclude ?? [],
|
|
238
|
+
merged: mergedConfig.alwaysInclude ?? [],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ContentType } from './content-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detailed content analysis result.
|
|
4
|
+
*/
|
|
5
|
+
export interface ContentAnalysis {
|
|
6
|
+
/** Detected content type */
|
|
7
|
+
contentType: ContentType;
|
|
8
|
+
/** Confidence score (0-1) */
|
|
9
|
+
confidence: number;
|
|
10
|
+
/** Detected language/format if applicable */
|
|
11
|
+
language?: string;
|
|
12
|
+
/** Key features detected */
|
|
13
|
+
features: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for summary generation.
|
|
17
|
+
*/
|
|
18
|
+
export interface SummaryOptions {
|
|
19
|
+
/** Maximum summary length in characters */
|
|
20
|
+
maxLength?: number;
|
|
21
|
+
/** Whether to use LLM for summarization */
|
|
22
|
+
useLLM?: boolean;
|
|
23
|
+
/** Model to use for LLM summarization */
|
|
24
|
+
model?: 'haiku' | 'sonnet';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Analyze content to determine its type and characteristics.
|
|
28
|
+
*/
|
|
29
|
+
export declare function analyzeContent(content: string): ContentAnalysis;
|
|
30
|
+
/**
|
|
31
|
+
* Generate a heuristic summary without LLM.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateHeuristicSummary(content: string, contentType: ContentType, maxLength?: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* Generate summary using LLM (via claude CLI).
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateLLMSummary(content: string, contentType: ContentType, options?: SummaryOptions): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Generate summary for content, optionally using LLM.
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateSummary(content: string, contentType: ContentType, options?: SummaryOptions): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Detect content type from content string.
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectContentType(content: string): ContentType;
|
|
46
|
+
//# sourceMappingURL=content-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-detector.d.ts","sourceRoot":"","sources":["../../src/lib/content-detector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,WAAW,EAAE,WAAW,CAAC;IACzB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAgJ/D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,SAAS,GAAE,MAAY,GACtB,MAAM,CA8JR;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAyDjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAE9D"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Analyze content to determine its type and characteristics.
|
|
4
|
+
*/
|
|
5
|
+
export function analyzeContent(content) {
|
|
6
|
+
const lines = content.split('\n');
|
|
7
|
+
const sample = content.slice(0, 5000);
|
|
8
|
+
const features = [];
|
|
9
|
+
let language;
|
|
10
|
+
// Score different content types
|
|
11
|
+
const scores = {
|
|
12
|
+
code: 0,
|
|
13
|
+
logs: 0,
|
|
14
|
+
json: 0,
|
|
15
|
+
prose: 0,
|
|
16
|
+
mixed: 0,
|
|
17
|
+
};
|
|
18
|
+
// JSON detection
|
|
19
|
+
if (sample.trim().startsWith('{') || sample.trim().startsWith('[')) {
|
|
20
|
+
try {
|
|
21
|
+
JSON.parse(content);
|
|
22
|
+
scores.json = 10;
|
|
23
|
+
features.push('valid_json');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
scores.json = 2; // Looks like JSON but invalid
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Code detection patterns
|
|
30
|
+
const codePatterns = [
|
|
31
|
+
// JavaScript/TypeScript
|
|
32
|
+
{ pattern: /^import\s+.*from\s+['"]/, weight: 3, lang: 'javascript' },
|
|
33
|
+
{
|
|
34
|
+
pattern: /^export\s+(default\s+)?(?:function|class|const|let|interface|type)/,
|
|
35
|
+
weight: 3,
|
|
36
|
+
lang: 'javascript',
|
|
37
|
+
},
|
|
38
|
+
{ pattern: /^(?:const|let|var)\s+\w+\s*[=:]/, weight: 2, lang: 'javascript' },
|
|
39
|
+
{ pattern: /=>\s*[{(]/, weight: 2, lang: 'javascript' },
|
|
40
|
+
// Python
|
|
41
|
+
{ pattern: /^def\s+\w+\s*\(/, weight: 3, lang: 'python' },
|
|
42
|
+
{ pattern: /^class\s+\w+.*:$/, weight: 3, lang: 'python' },
|
|
43
|
+
{ pattern: /^import\s+\w+$/, weight: 2, lang: 'python' },
|
|
44
|
+
{ pattern: /^from\s+\w+\s+import/, weight: 3, lang: 'python' },
|
|
45
|
+
// Rust
|
|
46
|
+
{ pattern: /^fn\s+\w+/, weight: 3, lang: 'rust' },
|
|
47
|
+
{ pattern: /^impl\s+/, weight: 3, lang: 'rust' },
|
|
48
|
+
{ pattern: /^use\s+\w+::/, weight: 2, lang: 'rust' },
|
|
49
|
+
// Go
|
|
50
|
+
{ pattern: /^func\s+(\(\w+\s+\*?\w+\)\s+)?\w+/, weight: 3, lang: 'go' },
|
|
51
|
+
{ pattern: /^package\s+\w+/, weight: 3, lang: 'go' },
|
|
52
|
+
// General
|
|
53
|
+
{ pattern: /^\s*(?:if|for|while|switch)\s*\(/, weight: 1 },
|
|
54
|
+
{ pattern: /^\s*(?:public|private|protected)\s+/, weight: 2, lang: 'java' },
|
|
55
|
+
{ pattern: /^\s*#include\s*</, weight: 3, lang: 'c' },
|
|
56
|
+
{ pattern: /^\s*@\w+/, weight: 1 }, // Decorators/annotations
|
|
57
|
+
];
|
|
58
|
+
const langCounts = {};
|
|
59
|
+
for (const { pattern, weight, lang } of codePatterns) {
|
|
60
|
+
const matches = sample.match(new RegExp(pattern.source, 'gm'));
|
|
61
|
+
if (matches) {
|
|
62
|
+
scores.code += weight * Math.min(matches.length, 5);
|
|
63
|
+
if (lang) {
|
|
64
|
+
langCounts[lang] = (langCounts[lang] ?? 0) + weight * matches.length;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Detect most likely language
|
|
69
|
+
if (Object.keys(langCounts).length > 0) {
|
|
70
|
+
language = Object.entries(langCounts).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
71
|
+
features.push(`language_${language}`);
|
|
72
|
+
}
|
|
73
|
+
// Log detection patterns
|
|
74
|
+
const logPatterns = [
|
|
75
|
+
{ pattern: /^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/, weight: 3, feature: 'timestamps' },
|
|
76
|
+
{ pattern: /^\[\d{4}-\d{2}-\d{2}/, weight: 3, feature: 'bracketed_timestamps' },
|
|
77
|
+
{ pattern: /^\[(?:DEBUG|INFO|WARN|ERROR|FATAL)\]/, weight: 3, feature: 'log_levels' },
|
|
78
|
+
{ pattern: /^(?:DEBUG|INFO|WARN|ERROR|FATAL)[:\s]/, weight: 2, feature: 'log_levels' },
|
|
79
|
+
{ pattern: /^\d+:\d+:\d+\.\d+/, weight: 2, feature: 'timestamps' },
|
|
80
|
+
{
|
|
81
|
+
pattern: /^[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}/,
|
|
82
|
+
weight: 2,
|
|
83
|
+
feature: 'syslog_timestamps',
|
|
84
|
+
},
|
|
85
|
+
{ pattern: /^\s*at\s+[\w.$]+\s*\(/, weight: 2, feature: 'stack_traces' },
|
|
86
|
+
{ pattern: /^Traceback \(most recent call last\):/, weight: 3, feature: 'python_traceback' },
|
|
87
|
+
];
|
|
88
|
+
for (const { pattern, weight, feature } of logPatterns) {
|
|
89
|
+
const matches = sample.match(new RegExp(pattern.source, 'gm'));
|
|
90
|
+
if (matches) {
|
|
91
|
+
scores.logs += weight * Math.min(matches.length, 10);
|
|
92
|
+
if (feature && !features.includes(feature)) {
|
|
93
|
+
features.push(feature);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Prose detection
|
|
98
|
+
const avgLineLength = content.length / Math.max(lines.length, 1);
|
|
99
|
+
const nonEmptyLines = lines.filter(l => l.trim()).length;
|
|
100
|
+
const avgWordsPerLine = content.split(/\s+/).length / Math.max(nonEmptyLines, 1);
|
|
101
|
+
// Prose characteristics: long lines, sentence structure, paragraphs
|
|
102
|
+
if (avgLineLength > 60)
|
|
103
|
+
scores.prose += 2;
|
|
104
|
+
if (avgWordsPerLine > 10)
|
|
105
|
+
scores.prose += 2;
|
|
106
|
+
if (/[.!?]\s+[A-Z]/.test(sample))
|
|
107
|
+
scores.prose += 3; // Sentence boundaries
|
|
108
|
+
if (/\n\n/.test(content))
|
|
109
|
+
scores.prose += 1; // Paragraphs
|
|
110
|
+
if (!/[{}()[\];]/.test(sample.slice(0, 500)))
|
|
111
|
+
scores.prose += 2; // No code punctuation
|
|
112
|
+
// Determine winner
|
|
113
|
+
const maxScore = Math.max(scores.code, scores.logs, scores.json, scores.prose);
|
|
114
|
+
let contentType;
|
|
115
|
+
let confidence;
|
|
116
|
+
if (maxScore === 0) {
|
|
117
|
+
contentType = 'mixed';
|
|
118
|
+
confidence = 0.3;
|
|
119
|
+
}
|
|
120
|
+
else if (scores.json === maxScore && scores.json >= 5) {
|
|
121
|
+
contentType = 'json';
|
|
122
|
+
confidence = Math.min(0.95, 0.5 + scores.json * 0.05);
|
|
123
|
+
features.push('structured_data');
|
|
124
|
+
}
|
|
125
|
+
else if (scores.code === maxScore && scores.code >= 4) {
|
|
126
|
+
contentType = 'code';
|
|
127
|
+
confidence = Math.min(0.9, 0.4 + scores.code * 0.03);
|
|
128
|
+
}
|
|
129
|
+
else if (scores.logs === maxScore && scores.logs >= 4) {
|
|
130
|
+
contentType = 'logs';
|
|
131
|
+
confidence = Math.min(0.9, 0.4 + scores.logs * 0.03);
|
|
132
|
+
}
|
|
133
|
+
else if (scores.prose === maxScore && scores.prose >= 4) {
|
|
134
|
+
contentType = 'prose';
|
|
135
|
+
confidence = Math.min(0.85, 0.4 + scores.prose * 0.05);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Mixed or unclear
|
|
139
|
+
contentType = 'mixed';
|
|
140
|
+
confidence = 0.5;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
contentType,
|
|
144
|
+
confidence,
|
|
145
|
+
language,
|
|
146
|
+
features,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Generate a heuristic summary without LLM.
|
|
151
|
+
*/
|
|
152
|
+
export function generateHeuristicSummary(content, contentType, maxLength = 300) {
|
|
153
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
154
|
+
const lineCount = lines.length;
|
|
155
|
+
const charCount = content.length;
|
|
156
|
+
let summary = `${lineCount} lines, ${Math.round(charCount / 1024)}KB. `;
|
|
157
|
+
switch (contentType) {
|
|
158
|
+
case 'code': {
|
|
159
|
+
const items = [];
|
|
160
|
+
// Extract import sources (module paths)
|
|
161
|
+
const importSourceMatches = content.match(/(?:from\s+['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]|use\s+([\w:]+))/gm);
|
|
162
|
+
if (importSourceMatches && importSourceMatches.length > 0) {
|
|
163
|
+
const sources = importSourceMatches
|
|
164
|
+
.map(m => {
|
|
165
|
+
const fromMatch = m.match(/from\s+['"]([^'"]+)['"]/);
|
|
166
|
+
if (fromMatch)
|
|
167
|
+
return fromMatch[1];
|
|
168
|
+
const reqMatch = m.match(/require\(\s*['"]([^'"]+)['"]/);
|
|
169
|
+
if (reqMatch)
|
|
170
|
+
return reqMatch[1];
|
|
171
|
+
const useMatch = m.match(/use\s+([\w:]+)/);
|
|
172
|
+
if (useMatch)
|
|
173
|
+
return useMatch[1];
|
|
174
|
+
return null;
|
|
175
|
+
})
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.slice(0, 5);
|
|
178
|
+
if (sources.length > 0) {
|
|
179
|
+
items.push(`imports: ${sources.join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Fallback: count import lines
|
|
184
|
+
const importLines = content.match(/^(?:import|from|require|use)\s+.+$/gm);
|
|
185
|
+
if (importLines && importLines.length > 0) {
|
|
186
|
+
items.push(`${importLines.length} imports`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Extract exports/public API
|
|
190
|
+
const exportMatches = content.match(/^export\s+(?:default\s+)?(?:function|class|const|interface|type)\s+(\w+)/gm);
|
|
191
|
+
if (exportMatches) {
|
|
192
|
+
const names = exportMatches
|
|
193
|
+
.map(m => m.match(/(\w+)$/)?.[1])
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.slice(0, 5);
|
|
196
|
+
if (names.length > 0) {
|
|
197
|
+
items.push(`exports: ${names.join(', ')}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Extract function signatures with parameters
|
|
201
|
+
const funcSigMatches = content.match(/(?:(?:export\s+)?(?:async\s+)?function\s+\w+\s*\([^)]*\)|(?:async\s+)?def\s+\w+\s*\([^)]*\)|(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\([^)]*\)|func\s+(?:\([^)]*\)\s*)?\w+\s*\([^)]*\))/gm);
|
|
202
|
+
if (funcSigMatches) {
|
|
203
|
+
const sigs = funcSigMatches.map(m => m.replace(/\s+/g, ' ').trim()).slice(0, 5);
|
|
204
|
+
if (sigs.length > 0 && !items.some(i => i.includes('exports'))) {
|
|
205
|
+
items.push(`functions: ${sigs.join(', ')}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Extract class hierarchies (extends/implements)
|
|
209
|
+
const classHierarchyMatches = content.match(/(?:class|struct)\s+\w+(?:\s+(?:extends|implements)\s+[\w,\s]+)?/gm);
|
|
210
|
+
if (classHierarchyMatches) {
|
|
211
|
+
const classes = classHierarchyMatches.map(m => m.replace(/\s+/g, ' ').trim()).slice(0, 3);
|
|
212
|
+
if (classes.length > 0) {
|
|
213
|
+
items.push(`classes: ${classes.join(', ')}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Extract type/interface names
|
|
217
|
+
const typeMatches = content.match(/(?:interface|type|enum)\s+(\w+)/g);
|
|
218
|
+
if (typeMatches) {
|
|
219
|
+
const names = typeMatches
|
|
220
|
+
.map(m => m.split(/\s+/)[1])
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
.slice(0, 5);
|
|
223
|
+
if (names.length > 0) {
|
|
224
|
+
items.push(`types: ${names.join(', ')}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Extract UPPER_CASE constants
|
|
228
|
+
const constMatches = content.match(/(?:const|let|var)\s+([A-Z][A-Z0-9_]{2,})\s*=/gm);
|
|
229
|
+
if (constMatches) {
|
|
230
|
+
const names = constMatches
|
|
231
|
+
.map(m => m.match(/([A-Z][A-Z0-9_]{2,})/)?.[1])
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
.slice(0, 5);
|
|
234
|
+
if (names.length > 0) {
|
|
235
|
+
items.push(`constants: ${names.join(', ')}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
summary += items.length > 0 ? items.join('; ') : 'Code file';
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'logs': {
|
|
242
|
+
const items = [];
|
|
243
|
+
// Count log levels
|
|
244
|
+
const errorCount = (content.match(/\b(?:ERROR|FATAL|Exception|Error:)/gi) || []).length;
|
|
245
|
+
const warnCount = (content.match(/\bWARN(?:ING)?/gi) || []).length;
|
|
246
|
+
const infoCount = (content.match(/\bINFO/gi) || []).length;
|
|
247
|
+
if (errorCount > 0)
|
|
248
|
+
items.push(`${errorCount} errors`);
|
|
249
|
+
if (warnCount > 0)
|
|
250
|
+
items.push(`${warnCount} warnings`);
|
|
251
|
+
if (infoCount > 0)
|
|
252
|
+
items.push(`${infoCount} info`);
|
|
253
|
+
// Try to extract time range
|
|
254
|
+
const timestamps = content.match(/\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/g);
|
|
255
|
+
if (timestamps && timestamps.length >= 2) {
|
|
256
|
+
items.push(`from ${timestamps[0]} to ${timestamps[timestamps.length - 1]}`);
|
|
257
|
+
}
|
|
258
|
+
summary += items.length > 0 ? items.join(', ') : 'Log output';
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'json': {
|
|
262
|
+
try {
|
|
263
|
+
const parsed = JSON.parse(content);
|
|
264
|
+
if (Array.isArray(parsed)) {
|
|
265
|
+
summary += `Array with ${parsed.length} items`;
|
|
266
|
+
if (parsed.length > 0 && typeof parsed[0] === 'object') {
|
|
267
|
+
const keys = Object.keys(parsed[0]).slice(0, 5);
|
|
268
|
+
summary += `. Item keys: ${keys.join(', ')}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (typeof parsed === 'object' && parsed !== null) {
|
|
272
|
+
const keys = Object.keys(parsed);
|
|
273
|
+
summary += `Object with ${keys.length} keys: ${keys.slice(0, 7).join(', ')}`;
|
|
274
|
+
if (keys.length > 7)
|
|
275
|
+
summary += '...';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
summary += 'JSON data';
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case 'prose': {
|
|
284
|
+
// Take first paragraph or sentences
|
|
285
|
+
const firstPara = content.split(/\n\n/)[0] ?? '';
|
|
286
|
+
const preview = firstPara.slice(0, maxLength - summary.length - 10);
|
|
287
|
+
summary += preview + (firstPara.length > preview.length ? '...' : '');
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
default:
|
|
291
|
+
summary += 'Mixed content';
|
|
292
|
+
}
|
|
293
|
+
return summary.slice(0, maxLength);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Generate summary using LLM (via claude CLI).
|
|
297
|
+
*/
|
|
298
|
+
export async function generateLLMSummary(content, contentType, options = {}) {
|
|
299
|
+
const maxLength = options.maxLength ?? 300;
|
|
300
|
+
const model = options.model ?? 'haiku';
|
|
301
|
+
// Truncate content if too long
|
|
302
|
+
const truncatedContent = content.slice(0, 15000);
|
|
303
|
+
const prompt = `Summarize this ${contentType} content in ${maxLength} characters or less. Focus on:
|
|
304
|
+
- What it is/does
|
|
305
|
+
- Key elements (functions, errors, data structure)
|
|
306
|
+
- Important details
|
|
307
|
+
|
|
308
|
+
Content:
|
|
309
|
+
${truncatedContent}
|
|
310
|
+
|
|
311
|
+
Summary (be concise):`;
|
|
312
|
+
return new Promise(resolve => {
|
|
313
|
+
const args = ['--print', '--model', model, prompt];
|
|
314
|
+
const cleanEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('CLAUDE') && key !== 'CLAUDECODE'));
|
|
315
|
+
const claude = spawn('claude', args, {
|
|
316
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
317
|
+
env: cleanEnv,
|
|
318
|
+
});
|
|
319
|
+
let stdout = '';
|
|
320
|
+
const timeout = setTimeout(() => {
|
|
321
|
+
claude.kill();
|
|
322
|
+
// Fall back to heuristic summary on timeout
|
|
323
|
+
resolve(generateHeuristicSummary(content, contentType, maxLength));
|
|
324
|
+
}, 30000);
|
|
325
|
+
claude.stdout.on('data', (data) => {
|
|
326
|
+
stdout += data.toString();
|
|
327
|
+
});
|
|
328
|
+
claude.on('close', code => {
|
|
329
|
+
clearTimeout(timeout);
|
|
330
|
+
if (code === 0 && stdout.trim()) {
|
|
331
|
+
resolve(stdout.trim().slice(0, maxLength));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// Fall back to heuristic summary
|
|
335
|
+
resolve(generateHeuristicSummary(content, contentType, maxLength));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
claude.on('error', () => {
|
|
339
|
+
clearTimeout(timeout);
|
|
340
|
+
resolve(generateHeuristicSummary(content, contentType, maxLength));
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Generate summary for content, optionally using LLM.
|
|
346
|
+
*/
|
|
347
|
+
export async function generateSummary(content, contentType, options = {}) {
|
|
348
|
+
if (options.useLLM) {
|
|
349
|
+
return generateLLMSummary(content, contentType, options);
|
|
350
|
+
}
|
|
351
|
+
return generateHeuristicSummary(content, contentType, options.maxLength);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Detect content type from content string.
|
|
355
|
+
*/
|
|
356
|
+
export function detectContentType(content) {
|
|
357
|
+
return analyzeContent(content).contentType;
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=content-detector.js.map
|