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.
Files changed (50) hide show
  1. package/.ai-cli-log/0001.txt +360 -0
  2. package/.ai-cli-log/0002.txt +1791 -0
  3. package/.ai-cli-log/0003.txt +338 -0
  4. package/.ai-cli-log/0004.txt +116 -0
  5. package/.ai-cli-log/0005.txt +105 -0
  6. package/.ai-cli-log/config.json +32 -0
  7. package/.ai-cli-log/gemini-2025-07-13T13-33-13-a-quick-gemini-test.txt +174 -0
  8. package/.ai-cli-log/gemini-2025-07-13T13-37-00-typescript-check-pass.txt +105 -0
  9. package/.ai-cli-log/gemini-2025-07-13T13-44-15-rename-ai-cli-logs-to-log.txt +164 -0
  10. package/.ai-cli-log/gemini-20250705-154601.txt +1320 -0
  11. package/.ai-cli-log/gemini-20250705-155547.txt +726 -0
  12. package/.ai-cli-log/gemini-20250705-165038.txt +66 -0
  13. package/.ai-cli-log/gemini-20250705-171429.txt +216 -0
  14. package/.ai-cli-log/gemini-20250705-191202.txt +448 -0
  15. package/.ai-cli-log/gemini-20250705-193741.txt +901 -0
  16. package/.ai-cli-log/gemini-20250705-194435.txt +110 -0
  17. package/.ai-cli-log/gemini-20250705-195926.txt +415 -0
  18. package/.ai-cli-log/gemini-20250705-201738.txt +246 -0
  19. package/.ai-cli-log/gemini-20250713-204921.txt +3036 -0
  20. package/.ai-cli-log/gemini-20250713-215941-update-ai-cli-log-documentation.txt +400 -0
  21. package/.ai-cli-log/gemini-20250713-220544-removed-debug-logs-successfully.txt +258 -0
  22. package/.ai-cli-log/gemini-20250713-221128-sessionsummarytxt.txt +112 -0
  23. package/.ai-cli-log/gemini-20250714-084659.txt +86 -0
  24. package/.ai-cli-log/gemini-20250714-085847-update-prompt-configuration.txt +189 -0
  25. package/.ai-cli-log/gemini-20250714-090905-add-sgpt-custom-summarizer.txt +284 -0
  26. package/.ai-cli-log/gemini-20250714-092329.txt +110 -0
  27. package/.ai-cli-log/gemini-20250714-092935.txt +183 -0
  28. 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
  29. package/.ai-cli-log/gemini-20250714-094141-heres-the-concise-summary-build-check-clean-this-captures-1.txt +111 -0
  30. package/.ai-cli-log/gemini-20250714-094405-heres-the-concise-summary-clean-build-check-this-captures-1.txt +111 -0
  31. package/.ai-cli-log/gemini-20250714-094816-json-summary-format.txt +132 -0
  32. package/.ai-cli-log/gemini-20250714-094833-optimize-summary-logic.txt +342 -0
  33. package/.ai-cli-log/gemini-20250714-133202-refactor-config-initialization.txt +1729 -0
  34. package/.ai-cli-log/gemini-20250714-134138-update-summary-logic.txt +153 -0
  35. package/.ai-cli-log/gemini-20250714-134749-json-summary-format.txt +214 -0
  36. package/.ai-cli-log/gemini-20250714-140527.txt +715 -0
  37. package/.ai-cli-log/gemini-20250714-142018.txt +86 -0
  38. package/.ai-cli-log/gemini-20250714-142027-update-summary-format.txt +86 -0
  39. package/.ai-cli-log/gemini-20250714-142100-session-complete.txt +86 -0
  40. package/.ai-cli-log/gemini-20250714-142129-refactor-readme-structure.txt +584 -0
  41. package/.ai-cli-log/gemini-20250714-213153.txt +1195 -0
  42. package/.ai-cli-log/gemini-20250714-222508-refactor-command-parser.txt +2110 -0
  43. package/.ai-cli-log/session-20250705-150655.txt +174 -0
  44. package/.ai-cli-log/session-20250705-151726.txt +313 -0
  45. package/.github/workflows/node.js.yml +30 -0
  46. package/GEMINI.md +5 -4
  47. package/README.md +229 -45
  48. package/dist/index.js +373 -80
  49. package/package.json +3 -1
  50. 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 args = process.argv.slice(2);
41
- const command = args[0];
42
- const commandArgs = args.slice(1);
43
- if (!command) {
44
- console.error('Usage: ai-cli-log <command> [args...]');
45
- process.exit(1);
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
- const logsDir = path.join(process.cwd(), '.ai-cli-logs');
48
- if (!fs.existsSync(logsDir)) {
49
- fs.mkdirSync(logsDir);
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
- // Initialize xterm.js in headless mode
52
- const defaultRows = 24;
53
- const defaultCols = 80;
54
- const xterm = new headless_1.Terminal({
55
- rows: process.stdout.rows || defaultRows,
56
- cols: process.stdout.columns || defaultCols,
57
- scrollback: Infinity, // Set scrollback to Infinity for unlimited buffer
58
- allowProposedApi: true,
59
- });
60
- const term = pty.spawn(command, commandArgs, {
61
- name: 'xterm-color',
62
- cols: process.stdout.columns || defaultCols,
63
- rows: process.stdout.rows || defaultRows,
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
- term.onExit(({ exitCode, signal }) => {
81
- // Add a small delay to ensure xterm.js has processed all output
82
- setTimeout(() => {
83
- // Extract rendered text from xterm.js buffer
84
- let renderedOutputLines = [];
85
- // Iterate over the entire buffer, including scrollback.
86
- // The total number of lines is the sum of lines in scrollback (baseY) and visible rows.
87
- for (let i = 0; i < xterm.buffer.active.baseY + xterm.rows; i++) {
88
- const line = xterm.buffer.active.getLine(i);
89
- if (line) {
90
- // translateToString(true) gets the line content, and we trim trailing whitespace.
91
- renderedOutputLines.push(line.translateToString(true));
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
- // Remove trailing blank lines
95
- while (renderedOutputLines.length > 0 && renderedOutputLines[renderedOutputLines.length - 1].trim() === '') {
96
- renderedOutputLines.pop();
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
- const renderedOutput = renderedOutputLines.join('\n');
99
- const now = new Date();
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
- fs.writeFile(logFilePath, renderedOutput, (err) => {
115
- if (err) {
116
- console.error('Error writing log file:', err);
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
- console.log(`Session logged to ${path.relative(process.cwd(), logFilePath)}`);
190
+ config.summarizer.summarizers.push(newS); // Add
191
+ console.log(` - Added new summarizer: "${newS.name}"`);
120
192
  }
121
- process.exit(exitCode);
122
193
  });
123
- }, 500); // 500ms delay
124
- });
125
- process.on('SIGINT', () => {
126
- term.kill('SIGINT');
127
- });
128
- process.on('resize', () => {
129
- term.resize(process.stdout.columns, process.stdout.rows);
130
- xterm.resize(process.stdout.columns, process.stdout.rows);
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.4",
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
  }