ai-cli-log 1.0.4 → 1.0.6
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/.ai-cli-log/0001.txt +360 -0
- package/.ai-cli-log/0002.txt +1791 -0
- package/.ai-cli-log/0003.txt +338 -0
- package/.ai-cli-log/0004.txt +116 -0
- package/.ai-cli-log/0005.txt +105 -0
- package/.ai-cli-log/config.json +32 -0
- package/.ai-cli-log/gemini-2025-07-13T13-33-13-a-quick-gemini-test.txt +174 -0
- package/.ai-cli-log/gemini-2025-07-13T13-37-00-typescript-check-pass.txt +105 -0
- package/.ai-cli-log/gemini-2025-07-13T13-44-15-rename-ai-cli-logs-to-log.txt +164 -0
- package/.ai-cli-log/gemini-20250705-154601.txt +1320 -0
- package/.ai-cli-log/gemini-20250705-155547.txt +726 -0
- package/.ai-cli-log/gemini-20250705-165038.txt +66 -0
- package/.ai-cli-log/gemini-20250705-171429.txt +216 -0
- package/.ai-cli-log/gemini-20250705-191202.txt +448 -0
- package/.ai-cli-log/gemini-20250705-193741.txt +901 -0
- package/.ai-cli-log/gemini-20250705-194435.txt +110 -0
- package/.ai-cli-log/gemini-20250705-195926.txt +415 -0
- package/.ai-cli-log/gemini-20250705-201738.txt +246 -0
- package/.ai-cli-log/gemini-20250713-204921.txt +3036 -0
- package/.ai-cli-log/gemini-20250713-215941-update-ai-cli-log-documentation.txt +400 -0
- package/.ai-cli-log/gemini-20250713-220544-removed-debug-logs-successfully.txt +258 -0
- package/.ai-cli-log/gemini-20250713-221128-sessionsummarytxt.txt +112 -0
- package/.ai-cli-log/gemini-20250714-084659.txt +86 -0
- package/.ai-cli-log/gemini-20250714-085847-update-prompt-configuration.txt +189 -0
- package/.ai-cli-log/gemini-20250714-090905-add-sgpt-custom-summarizer.txt +284 -0
- package/.ai-cli-log/gemini-20250714-092329.txt +110 -0
- package/.ai-cli-log/gemini-20250714-092935.txt +183 -0
- package/.ai-cli-log/gemini-20250714-093205-heres-a-concise-summary-of-the-terminal-session-fix-sgpt-option-error-this-captures-1-the-action-fix-2-the-tool-involved-sgpt-3-the-issue-option-error-4-follows-the-requested-lowercase-hyphenated-format.txt +140 -0
- package/.ai-cli-log/gemini-20250714-094141-heres-the-concise-summary-build-check-clean-this-captures-1.txt +111 -0
- package/.ai-cli-log/gemini-20250714-094405-heres-the-concise-summary-clean-build-check-this-captures-1.txt +111 -0
- package/.ai-cli-log/gemini-20250714-094816-json-summary-format.txt +132 -0
- package/.ai-cli-log/gemini-20250714-094833-optimize-summary-logic.txt +342 -0
- package/.ai-cli-log/gemini-20250714-133202-refactor-config-initialization.txt +1729 -0
- package/.ai-cli-log/gemini-20250714-134138-update-summary-logic.txt +153 -0
- package/.ai-cli-log/gemini-20250714-134749-json-summary-format.txt +214 -0
- package/.ai-cli-log/gemini-20250714-140527.txt +715 -0
- package/.ai-cli-log/gemini-20250714-142018.txt +86 -0
- package/.ai-cli-log/gemini-20250714-142027-update-summary-format.txt +86 -0
- package/.ai-cli-log/gemini-20250714-142100-session-complete.txt +86 -0
- package/.ai-cli-log/gemini-20250714-142129-refactor-readme-structure.txt +584 -0
- package/.ai-cli-log/gemini-20250714-213153.txt +1195 -0
- package/.ai-cli-log/gemini-20250714-222508-refactor-command-parser.txt +2110 -0
- package/.ai-cli-log/session-20250705-150655.txt +174 -0
- package/.ai-cli-log/session-20250705-151726.txt +313 -0
- package/.github/workflows/node.js.yml +30 -0
- package/GEMINI.md +5 -4
- package/README.md +229 -45
- package/dist/index.js +373 -80
- package/package.json +3 -1
- package/src/index.ts +424 -89
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
4
|
if (k2 === undefined) k2 = k;
|
|
@@ -32,100 +33,392 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
33
|
return result;
|
|
33
34
|
};
|
|
34
35
|
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
35
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
40
|
const pty = __importStar(require("node-pty"));
|
|
37
41
|
const fs = __importStar(require("fs"));
|
|
38
42
|
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
39
45
|
const headless_1 = require("@xterm/headless");
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const readline_1 = __importDefault(require("readline"));
|
|
47
|
+
const commander_1 = require("commander");
|
|
48
|
+
// --- 1. CONFIGURATION & TYPE DEFINITIONS ---
|
|
49
|
+
const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.config', 'ai-cli-log');
|
|
50
|
+
const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, 'config.json');
|
|
51
|
+
const LOCAL_CONFIG_PATH = path.join(process.cwd(), '.ai-cli-log', 'config.json');
|
|
52
|
+
function findConfigPath() {
|
|
53
|
+
if (fs.existsSync(LOCAL_CONFIG_PATH))
|
|
54
|
+
return LOCAL_CONFIG_PATH;
|
|
55
|
+
if (fs.existsSync(GLOBAL_CONFIG_PATH))
|
|
56
|
+
return GLOBAL_CONFIG_PATH;
|
|
57
|
+
return null;
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
function readConfig() {
|
|
60
|
+
const configPath = findConfigPath();
|
|
61
|
+
if (!configPath)
|
|
62
|
+
return { summarizer: { summarizers: [] } };
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
65
|
+
return JSON.parse(content);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(`Error reading or parsing config file at ${configPath}:`, error);
|
|
69
|
+
return { summarizer: { summarizers: [] } };
|
|
70
|
+
}
|
|
50
71
|
}
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
cwd: process.cwd(),
|
|
65
|
-
env: process.env,
|
|
66
|
-
});
|
|
67
|
-
// Pipe pty output to xterm.js and also to stdout
|
|
68
|
-
term.onData((data) => {
|
|
69
|
-
process.stdout.write(data);
|
|
70
|
-
xterm.write(data);
|
|
71
|
-
});
|
|
72
|
-
// Pipe stdin to pty
|
|
73
|
-
if (process.stdin.isTTY) {
|
|
74
|
-
process.stdin.on('data', (data) => {
|
|
75
|
-
term.write(data.toString());
|
|
76
|
-
});
|
|
77
|
-
process.stdin.setRawMode(true);
|
|
78
|
-
process.stdin.resume();
|
|
72
|
+
function writeConfig(config, isLocal) {
|
|
73
|
+
const targetPath = isLocal ? LOCAL_CONFIG_PATH : GLOBAL_CONFIG_PATH;
|
|
74
|
+
const targetDir = path.dirname(targetPath);
|
|
75
|
+
try {
|
|
76
|
+
if (!fs.existsSync(targetDir)) {
|
|
77
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2));
|
|
80
|
+
console.log(`✔ Configuration successfully saved to ${targetPath}`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error(`Error writing config file to ${targetPath}:`, error);
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
// --- 2. COMMAND IMPLEMENTATIONS ---
|
|
87
|
+
async function handleInitCommand(isLocal) {
|
|
88
|
+
const targetPath = isLocal ? LOCAL_CONFIG_PATH : GLOBAL_CONFIG_PATH;
|
|
89
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
90
|
+
const ask = (question) => new Promise(resolve => rl.question(question, resolve));
|
|
91
|
+
try {
|
|
92
|
+
if (fs.existsSync(targetPath)) {
|
|
93
|
+
const warning = `Configuration file already exists. Continuing will allow you to add or update default summarizers (gemini-pro, ollama, sgpt) with the latest recommended settings, while preserving any other custom summarizers you have added.`;
|
|
94
|
+
console.log(warning);
|
|
95
|
+
const answer = await ask('Do you want to continue? (y/N): ');
|
|
96
|
+
if (answer.toLowerCase() !== 'y') {
|
|
97
|
+
console.log('Initialization cancelled.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log('\nScanning for available AI tools...');
|
|
102
|
+
const availableTools = [];
|
|
103
|
+
const checkTool = (tool) => new Promise(resolve => {
|
|
104
|
+
const proc = (0, child_process_1.spawn)('which', [tool], { stdio: 'ignore' });
|
|
105
|
+
proc.on('close', code => {
|
|
106
|
+
if (code === 0) {
|
|
107
|
+
console.log(` - Found ${tool}!`);
|
|
108
|
+
availableTools.push(tool);
|
|
109
|
+
}
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
proc.on('error', () => resolve());
|
|
113
|
+
});
|
|
114
|
+
await Promise.all([checkTool('gemini'), checkTool('ollama'), checkTool('sgpt'), checkTool('claude')]);
|
|
115
|
+
if (availableTools.length === 0) {
|
|
116
|
+
console.log('No supported AI tools (gemini, ollama, sgpt, claude) found in your PATH.');
|
|
117
|
+
const createEmpty = await ask('\n> Would you like to create an empty configuration file to manually add a custom summarizer? (y/N): ');
|
|
118
|
+
if (createEmpty.toLowerCase() === 'y') {
|
|
119
|
+
const config = readConfig(); // Read to not overwrite existing unrelated config
|
|
120
|
+
writeConfig(config, isLocal);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log('Initialization cancelled.');
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const config = readConfig();
|
|
128
|
+
const summarizersToUpdate = [];
|
|
129
|
+
const newPrompt = 'You are a log summarizer. Your response MUST be a raw, valid JSON object and nothing else. Do not wrap it in markdown blocks like ```json. The JSON object must have a single key "summary" which is a 3-5 word, lowercase, filename-friendly phrase. Example: {"summary": "refactor-database-schema"}. The session content is:';
|
|
130
|
+
if (availableTools.includes('gemini')) {
|
|
131
|
+
const add = await ask('\n> Found Gemini. Add/update the \'gemini-pro\' summarizer? (Y/n): ');
|
|
132
|
+
if (add.toLowerCase() !== 'n') {
|
|
133
|
+
summarizersToUpdate.push({
|
|
134
|
+
name: 'gemini-pro',
|
|
135
|
+
tool: 'gemini',
|
|
136
|
+
prompt: newPrompt,
|
|
137
|
+
maxLines: 100,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (availableTools.includes('ollama')) {
|
|
142
|
+
const add = await ask('\n> Found Ollama. Add/update the \'ollama\' summarizer? (Y/n): ');
|
|
143
|
+
if (add.toLowerCase() !== 'n') {
|
|
144
|
+
const modelInput = await ask(' - Which Ollama model to use? (press Enter for \'llama3\'): ');
|
|
145
|
+
const model = modelInput || 'llama3';
|
|
146
|
+
summarizersToUpdate.push({
|
|
147
|
+
name: 'ollama',
|
|
148
|
+
tool: 'ollama',
|
|
149
|
+
model: model,
|
|
150
|
+
prompt: newPrompt,
|
|
151
|
+
maxLines: 50,
|
|
152
|
+
});
|
|
92
153
|
}
|
|
93
154
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
155
|
+
if (availableTools.includes('claude')) {
|
|
156
|
+
const add = await ask('\n> Found Claude. Add/update the \'claude-opus\' summarizer? (Y/n): ');
|
|
157
|
+
if (add.toLowerCase() !== 'n') {
|
|
158
|
+
summarizersToUpdate.push({
|
|
159
|
+
name: 'claude-opus',
|
|
160
|
+
tool: 'claude',
|
|
161
|
+
prompt: newPrompt,
|
|
162
|
+
maxLines: 100,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (availableTools.includes('sgpt')) {
|
|
167
|
+
const add = await ask('\n> Found ShellGPT. Add/update the \'sgpt\' summarizer? (Y/n): ');
|
|
168
|
+
if (add.toLowerCase() !== 'n') {
|
|
169
|
+
summarizersToUpdate.push({
|
|
170
|
+
name: 'sgpt',
|
|
171
|
+
tool: 'custom',
|
|
172
|
+
command: ['sgpt', '--chat', 'session-summary', '"{{prompt}}"'],
|
|
173
|
+
prompt: newPrompt,
|
|
174
|
+
maxLines: 100,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
97
177
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const year = now.getFullYear();
|
|
101
|
-
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
|
102
|
-
const day = now.getDate().toString().padStart(2, '0');
|
|
103
|
-
const hours = now.getHours().toString().padStart(2, '0');
|
|
104
|
-
const minutes = now.getMinutes().toString().padStart(2, '0');
|
|
105
|
-
const seconds = now.getSeconds().toString().padStart(2, '0');
|
|
106
|
-
const prefix = command || 'session';
|
|
107
|
-
const logFileName = `${prefix}-${year}${month}${day}-${hours}${minutes}${seconds}.txt`;
|
|
108
|
-
const logFilePath = path.join(logsDir, logFileName);
|
|
109
|
-
if (renderedOutput.trim().length === 0) {
|
|
110
|
-
console.log('Session had no output, not saving log file.');
|
|
111
|
-
process.exit(exitCode);
|
|
178
|
+
if (summarizersToUpdate.length === 0) {
|
|
179
|
+
console.log('\nNo configurations were added or updated.');
|
|
112
180
|
return;
|
|
113
181
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
182
|
+
// "Update-or-add" logic
|
|
183
|
+
summarizersToUpdate.forEach(newS => {
|
|
184
|
+
const existingIndex = config.summarizer.summarizers.findIndex(s => s.name === newS.name);
|
|
185
|
+
if (existingIndex !== -1) {
|
|
186
|
+
config.summarizer.summarizers[existingIndex] = newS; // Update
|
|
187
|
+
console.log(` - Updated existing summarizer: "${newS.name}"`);
|
|
117
188
|
}
|
|
118
189
|
else {
|
|
119
|
-
|
|
190
|
+
config.summarizer.summarizers.push(newS); // Add
|
|
191
|
+
console.log(` - Added new summarizer: "${newS.name}"`);
|
|
120
192
|
}
|
|
121
|
-
process.exit(exitCode);
|
|
122
193
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
194
|
+
// Set default only if it wasn't set before
|
|
195
|
+
if (!config.summarizer.default && config.summarizer.summarizers.length > 0) {
|
|
196
|
+
const priority = ['gemini-pro', 'claude-opus', 'ollama', 'sgpt'];
|
|
197
|
+
for (const name of priority) {
|
|
198
|
+
if (config.summarizer.summarizers.some(s => s.name === name)) {
|
|
199
|
+
config.summarizer.default = name;
|
|
200
|
+
console.log(`\n✔ Set "${config.summarizer.default}" as the default summarizer.`);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
writeConfig(config, isLocal);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
rl.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function getAiSummary(content, summarizerName) {
|
|
212
|
+
const config = readConfig();
|
|
213
|
+
const name = summarizerName || config.summarizer.default;
|
|
214
|
+
if (!name) {
|
|
215
|
+
console.warn(`\nWarning: No default summarizer set. Please run 'ai-cli-log --init'.`);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const summarizer = config.summarizer.summarizers.find(s => s.name === name);
|
|
219
|
+
if (!summarizer) {
|
|
220
|
+
console.warn(`\nWarning: No summarizer named "${name}" found. Please check your configuration.`);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const { tool, model, prompt, maxLines = 0, command: customCommand } = summarizer;
|
|
224
|
+
let sampledContent = content;
|
|
225
|
+
const lines = content.split('\n');
|
|
226
|
+
if (maxLines > 0 && lines.length > maxLines * 2) {
|
|
227
|
+
const head = lines.slice(0, maxLines).join('\n');
|
|
228
|
+
const tail = lines.slice(-maxLines).join('\n');
|
|
229
|
+
sampledContent = `${head}\n\n[... Session content truncated ...]\n\n${tail}`;
|
|
230
|
+
console.log(`\n(Session content long, sampling first and last ${maxLines} lines for summary)`);
|
|
231
|
+
}
|
|
232
|
+
let command;
|
|
233
|
+
let inputForStdin;
|
|
234
|
+
switch (tool) {
|
|
235
|
+
case 'ollama':
|
|
236
|
+
command = ['ollama', 'run', model || ''];
|
|
237
|
+
inputForStdin = `${prompt}\n\n${sampledContent}`;
|
|
238
|
+
break;
|
|
239
|
+
case 'gemini':
|
|
240
|
+
command = ['gemini', '-p', prompt];
|
|
241
|
+
inputForStdin = sampledContent;
|
|
242
|
+
break;
|
|
243
|
+
case 'claude':
|
|
244
|
+
command = ['claude', '-p', prompt];
|
|
245
|
+
inputForStdin = sampledContent;
|
|
246
|
+
break;
|
|
247
|
+
case 'custom':
|
|
248
|
+
if (!customCommand) {
|
|
249
|
+
console.error(`Custom summarizer "${name}" is missing the "command" definition.`);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
command = customCommand.map(arg => arg.replace('{{prompt}}', prompt));
|
|
253
|
+
inputForStdin = sampledContent;
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
console.error(`Tool "${tool}" is not directly supported yet.`);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
command = command.filter(Boolean);
|
|
260
|
+
const [cmd, ...args] = command;
|
|
261
|
+
return new Promise((resolve) => {
|
|
262
|
+
const proc = (0, child_process_1.spawn)(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
263
|
+
let summary = '', errorOutput = '';
|
|
264
|
+
proc.stdout.on('data', data => {
|
|
265
|
+
const output = data.toString();
|
|
266
|
+
summary += output;
|
|
267
|
+
});
|
|
268
|
+
proc.stderr.on('data', data => {
|
|
269
|
+
const error = data.toString();
|
|
270
|
+
errorOutput += error;
|
|
271
|
+
});
|
|
272
|
+
proc.on('close', code => {
|
|
273
|
+
if (code !== 0) {
|
|
274
|
+
console.error(`\nSummarizer command exited with code ${code}. Stderr: ${errorOutput}`);
|
|
275
|
+
resolve(null);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
resolve(summary.trim());
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
proc.on('error', err => {
|
|
282
|
+
console.error(`\nFailed to start summarizer command "${cmd}". Is it in your PATH?`, err);
|
|
283
|
+
resolve(null);
|
|
284
|
+
});
|
|
285
|
+
proc.stdin.write(inputForStdin);
|
|
286
|
+
proc.stdin.end();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function runLoggingSession(command, commandArgs, summaryArg) {
|
|
290
|
+
const logsDir = path.dirname(LOCAL_CONFIG_PATH);
|
|
291
|
+
if (!fs.existsSync(logsDir))
|
|
292
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
293
|
+
const xterm = new headless_1.Terminal({
|
|
294
|
+
rows: process.stdout.rows || 24,
|
|
295
|
+
cols: process.stdout.columns || 80,
|
|
296
|
+
scrollback: Infinity,
|
|
297
|
+
allowProposedApi: true,
|
|
298
|
+
});
|
|
299
|
+
const term = pty.spawn(command, commandArgs, {
|
|
300
|
+
name: 'xterm-color',
|
|
301
|
+
cols: process.stdout.columns || 80,
|
|
302
|
+
rows: process.stdout.rows || 24,
|
|
303
|
+
cwd: process.cwd(),
|
|
304
|
+
env: process.env,
|
|
305
|
+
});
|
|
306
|
+
const onData = (data) => {
|
|
307
|
+
process.stdout.write(data);
|
|
308
|
+
xterm.write(data);
|
|
309
|
+
};
|
|
310
|
+
term.onData(onData);
|
|
311
|
+
const onStdin = (data) => term.write(data.toString());
|
|
312
|
+
if (process.stdin.isTTY) {
|
|
313
|
+
process.stdin.setRawMode(true);
|
|
314
|
+
process.stdin.resume();
|
|
315
|
+
process.stdin.on('data', onStdin);
|
|
316
|
+
}
|
|
317
|
+
const onExit = async ({ exitCode }) => {
|
|
318
|
+
term.kill();
|
|
319
|
+
if (process.stdin.isTTY) {
|
|
320
|
+
process.stdin.removeListener('data', onStdin);
|
|
321
|
+
process.stdin.setRawMode(false);
|
|
322
|
+
process.stdin.pause();
|
|
323
|
+
}
|
|
324
|
+
setTimeout(async () => {
|
|
325
|
+
var _a;
|
|
326
|
+
let renderedOutput = '';
|
|
327
|
+
for (let i = 0; i < xterm.buffer.active.baseY + xterm.rows; i++) {
|
|
328
|
+
renderedOutput += ((_a = xterm.buffer.active.getLine(i)) === null || _a === void 0 ? void 0 : _a.translateToString(true)) + '\n';
|
|
329
|
+
}
|
|
330
|
+
renderedOutput = renderedOutput.trim();
|
|
331
|
+
if (renderedOutput.trim().length === 0) {
|
|
332
|
+
console.log('\nSession had no output, not saving log file.');
|
|
333
|
+
process.exit(exitCode);
|
|
334
|
+
}
|
|
335
|
+
const now = new Date();
|
|
336
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
337
|
+
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
338
|
+
const prefix = command || 'session';
|
|
339
|
+
let logFileName = `${prefix}-${timestamp}.txt`;
|
|
340
|
+
if (summaryArg) {
|
|
341
|
+
const startTime = Date.now();
|
|
342
|
+
const rawSummaryJson = await getAiSummary(renderedOutput, typeof summaryArg === 'string' ? summaryArg : undefined);
|
|
343
|
+
const endTime = Date.now();
|
|
344
|
+
if (rawSummaryJson) {
|
|
345
|
+
const duration = (endTime - startTime) / 1000;
|
|
346
|
+
const config = readConfig();
|
|
347
|
+
const summarizerName = (typeof summaryArg === 'string' ? summaryArg : config.summarizer.default) || 'default';
|
|
348
|
+
try {
|
|
349
|
+
// Pre-process the raw summary to extract JSON from markdown blocks if present
|
|
350
|
+
const jsonMatch = rawSummaryJson.match(/\{.*\}/s);
|
|
351
|
+
const cleanJson = jsonMatch ? jsonMatch[0] : rawSummaryJson;
|
|
352
|
+
const summaryData = JSON.parse(cleanJson);
|
|
353
|
+
const slug = summaryData.summary;
|
|
354
|
+
if (typeof slug !== 'string') {
|
|
355
|
+
throw new Error('Invalid JSON structure from summarizer: "summary" key is missing or not a string.');
|
|
356
|
+
}
|
|
357
|
+
console.log(`\nSummary by ${summarizerName} (took ${duration.toFixed(1)}s): "${slug}"`);
|
|
358
|
+
logFileName = `${prefix}-${timestamp}-${slug}.txt`;
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
console.error(`\nError parsing summary JSON from ${summarizerName}. Using raw output as fallback.`);
|
|
362
|
+
console.error(`Raw output: ${rawSummaryJson}`);
|
|
363
|
+
const slugify = (text) => text.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
364
|
+
const slug = slugify(rawSummaryJson).split('-').slice(0, 10).join('-');
|
|
365
|
+
logFileName = `${prefix}-${timestamp}-${slug}.txt`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const logFilePath = path.join(logsDir, logFileName);
|
|
370
|
+
fs.writeFile(logFilePath, renderedOutput, (err) => {
|
|
371
|
+
if (err) {
|
|
372
|
+
console.error('\nError writing log file:', err);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
console.log(`\nSession logged to ${path.relative(process.cwd(), logFilePath)}`);
|
|
376
|
+
}
|
|
377
|
+
process.exit(exitCode);
|
|
378
|
+
});
|
|
379
|
+
}, 200);
|
|
380
|
+
};
|
|
381
|
+
term.onExit(onExit);
|
|
382
|
+
process.on('resize', () => {
|
|
383
|
+
term.resize(process.stdout.columns, process.stdout.rows);
|
|
384
|
+
xterm.resize(process.stdout.columns, process.stdout.rows);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// --- 3. MAIN ENTRY POINT & ARGUMENT PARSER ---
|
|
388
|
+
function main() {
|
|
389
|
+
const program = new commander_1.Command();
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
391
|
+
const pkg = require('../package.json');
|
|
392
|
+
program
|
|
393
|
+
.name('ai-cli-log')
|
|
394
|
+
.description('A CLI tool to seamlessly log terminal sessions with AI models.')
|
|
395
|
+
.version(pkg.version, '-v, --version', 'Output the current version')
|
|
396
|
+
.option('-s, --with-summary [summarizer]', 'Enable AI summary for the session. Optionally specify a summarizer.');
|
|
397
|
+
program
|
|
398
|
+
.command('init')
|
|
399
|
+
.description('Initialize or update the configuration file.')
|
|
400
|
+
.option('--local', 'Create the configuration file in the current directory\'s .ai-cli-log folder.', false)
|
|
401
|
+
.action((options) => {
|
|
402
|
+
handleInitCommand(options.local);
|
|
403
|
+
});
|
|
404
|
+
program
|
|
405
|
+
.command('run')
|
|
406
|
+
.usage('<command> [args...]')
|
|
407
|
+
.description("Run a command and log the session. Any options for ai-cli-log itself (like -s) must come before the 'run' command.")
|
|
408
|
+
.argument('<command>', 'The command to execute and log.')
|
|
409
|
+
.argument('[args...]', 'Arguments for the command.')
|
|
410
|
+
.allowUnknownOption()
|
|
411
|
+
.action((command, args) => {
|
|
412
|
+
const options = program.opts();
|
|
413
|
+
let summaryArg = false;
|
|
414
|
+
if (options.withSummary) {
|
|
415
|
+
summaryArg = typeof options.withSummary === 'string' ? options.withSummary : true;
|
|
416
|
+
}
|
|
417
|
+
runLoggingSession(command, args, summaryArg);
|
|
418
|
+
});
|
|
419
|
+
program.parse(process.argv);
|
|
420
|
+
if (!process.argv.slice(2).length) {
|
|
421
|
+
program.help();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cli-log",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Seamlessly log your AI-powered coding conversations. This command-line interface (CLI) tool captures your terminal interactions with AI models like Gemini and Claude, saving entire sessions as clean plain text documents for easy review and documentation.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/alingse/ai-cli-log#readme",
|
|
25
25
|
"devDependencies": {
|
|
26
|
+
"@types/commander": "^2.12.0",
|
|
26
27
|
"@types/node": "^24.0.10",
|
|
27
28
|
"ts-node": "^10.9.2",
|
|
28
29
|
"typescript": "^5.8.3"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@xterm/headless": "^5.5.0",
|
|
33
|
+
"commander": "^14.0.0",
|
|
32
34
|
"node-pty": "^1.0.0"
|
|
33
35
|
}
|
|
34
36
|
}
|