Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,195 changes: 1,195 additions & 0 deletions .ai-cli-log/gemini-20250714-213153.txt

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
* `bin/`: 包含 CLI 工具的启动脚本。
* **日志格式:**
* **内容:** 日志文件捕获来自 `xterm/headless` 缓冲区的“渲染后”终端输出,真实地呈现用户所看到的内容。这包括所有交互元素、输入和输出。
* **格式:** Markdown (`.md`) 文件,便于阅读和文档化
* **文件名约定:** `[command_prefix]-YYYYMMDD-HHMMSS.md`。`command_prefix` 来自于被包装的命令(例如 `gemini`、`claude`),如果未提供命令,则默认为 `session`。时间戳使用 `YYYYMMDD-HHMMSS` 格式(不含冒号),以提高 URL 友好性。
* **格式:** 当前为纯文本 (`.txt`) 文件。未来计划支持 Markdown (`.md`) 以提供更丰富的渲染能力
* **文件名约定:** 基础格式为 `[command_prefix]-YYYYMMDD-HHMMSS.txt`。当使用 `-s` 或 `--with-summary` 标志启用 AI 摘要时,文件名将扩展为 `[command_prefix]-YYYYMMDD-HHMMSS-[summary_slug].txt`。其中 `[summary_slug]` 是由配置的 AI 摘要器(支持 `gemini`, `ollama`, `claude`, `sgpt` 及自定义脚本)根据会话内容生成的、由连字符分隔的简短描述(例如 `fix-database-error`)。`command_prefix` 来自于被包装的命令(例如 `gemini`、`claude`),如果未提供命令,则默认为 `session`。时间戳使用 `YYYYMMDD-HHMMSS` 格式(不含冒号),以提高 URL 友好性。
* **Xterm.js 配置:**
* `scrollback: Infinity`: 确保捕获整个回滚缓冲区,而不仅仅是可见屏幕。
* `allowProposedApi: true`: 明确启用 `@xterm/headless` 中的实验性 API。
Expand Down
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Here is an example of a manual `config.json`:
"name": "sgpt",
"tool": "custom",
"command": ["sgpt", "--chat", "session-summary", "\"{{prompt}}\""]
},

* **To create a global configuration file (at `~/.config/ai-cli-log/config.json`):**
```bash
Expand Down Expand Up @@ -111,16 +112,14 @@ This command will:

## Development Notes

This project was generated with the assistance of Google Gemini. You can review the detailed development process and interactions in the `.ai-cli-logs` directory, specifically starting with `0001.md` and subsequent log files.
This project was generated with the assistance of Google Gemini. You can review the detailed development process and interactions in the `.ai-cli-log` directory, specifically starting with `0001.txt` and subsequent log files.

Special thanks to Gemini for its invaluable help in the development of this tool!

## TODO

* **Content Handling:**
* Empty log files are now prevented from being saved when the session output is blank or contains only whitespace.
* Trailing whitespace and blank lines are now trimmed from the output to address issues where insufficient content led to large blank areas.
* **Filename Convention:** The current timestamp-based filenames are functional but can be monotonous. Evaluate alternatives for more descriptive filenames, while carefully considering potential information leakage if AI summarization were to be used for naming.
* **Argument Parsing:** Refactor the manual argument parsing to use a robust library like `yargs` or `commander` to improve maintainability and provide standard features like `--help`.
* **Markdown Support:** Investigate and potentially implement saving logs in Markdown format, which could include session metadata (e.g., command, timestamp, summary) in a frontmatter block.

---

Expand Down Expand Up @@ -216,8 +215,8 @@ ai-cli-log <command> [args...]
{
"name": "my-custom-summarizer",
"tool": "custom",
"command": ["/path/to/your/custom_script.sh", "--input", "stdin"],
"prompt": "请为我总结这个会话。你的响应应该是一个包含 'summary' 键的 JSON 对象。",
"command": ["my-summarizer-script", "--prompt", "{{prompt}}"],
"prompt": "You are a log summarizer. Your response MUST be a valid JSON object with one key: \"summary\" (a 3-5 word, lowercase, filename-friendly phrase). Example: {\"summary\": \"refactor-database-schema\"}. The session content is:",
"maxLines": 200
}
]
Expand All @@ -229,16 +228,34 @@ ai-cli-log <command> [args...]

* `summarizer.default` (可选): 默认使用的摘要器配置名称。
* `summarizer.summarizers`: 包含不同摘要器配置的数组。
* **`name`**: 您为摘要器配置指定的唯一名称 (例如, `gemini-pro`, `ollama`, `sgpt`, `my-custom-summarizer`)。
* **`name`**: 您为摘要器配置指定的唯一名称 (例如, `gemini-pro`, `ollama`, `claude-opus`, `my-custom-summarizer`)。
* **`tool`**: 指定摘要器使用的工具类型。
* `gemini`: 使用 `gemini` CLI 工具。
* `ollama`: 使用 `ollama` CLI 工具。
* `claude`: 使用 `claude` CLI 工具。
* `custom`: 使用自定义命令。
* **`model`** (可选): 对于 `ollama` 工具,指定要使用的模型名称 (例如, `llama3`)。
* **`prompt`**: 传递给摘要器命令的提示。会话内容将作为标准输入传递。
* **`maxLines`** (可选): 限制传递给摘要器的会话内容行数。如果会话内容超过此限制,将只采样开头和结尾的行。
* **`command`** (可选): 对于 `custom` 工具,指定要执行的命令数组。例如 `["sgpt", "--chat", "session-summary", ""{{prompt}}""]`。`{{prompt}}` 占位符将被实际的提示替换
* **`command`** (可选): 对于 `custom` 工具,指定要执行的命令数组。会话内容会通过管道传递给该命令的 `stdin`。`{{prompt}}` 占位符将被实际的提示字符串替换。例如: `["my-summarizer-script", "--prompt", "{{prompt}}"]`
* **重要**: 摘要器的输出**必须**是一个有效的 JSON 对象,其中包含一个名为 `summary` 的键(例如,`{"summary": "你的摘要短语"}`)。
```

**Field Descriptions:**

* `summarizer.default` (optional): The name of the default summarizer configuration to use.
* `summarizer.summarizers`: An array of different summarizer configurations.
* **`name`**: A unique name you give to the summarizer configuration (e.g., `gemini-pro`, `ollama`, `claude-opus`, `my-custom-summarizer`).
* **`tool`**: The type of tool the summarizer uses.
* `gemini`: Uses the `gemini` CLI tool.
* `ollama`: Uses the `ollama` CLI tool.
* `claude`: Uses the `claude` CLI tool.
* `custom`: Uses a custom command.
* **`model`** (optional): For the `ollama` tool, specifies the model name to use (e.g., `llama3`).
* **`prompt`**: The prompt passed to the summarizer command. The session content will be passed as standard input.
* **`maxLines`** (optional): Limits the number of session content lines passed to the summarizer. If the session exceeds this limit, only the beginning and end lines will be sampled.
* **`command`** (optional): For the `custom` tool, specifies the command array to execute. The session content is piped to this command's `stdin`. The `{{prompt}}` placeholder will be replaced with the actual prompt string. Example: `["my-summarizer-script", "--prompt", "{{prompt}}"]`.
* **Important**: The output from the summarizer **MUST** be a valid JSON object containing a key named `summary` (e.g., `{"summary": "your-summary-phrase"}`).

### 交互式设置 (`--init`)

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ai-cli-log",
"version": "1.0.4",
"version": "1.0.5",
"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.",
"main": "dist/index.js",
"bin": {
Expand Down
40 changes: 35 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ async function handleInitCommand(isLocal: boolean) {
}

console.log('\nScanning for available AI tools...');
const availableTools: ('gemini' | 'ollama' | 'sgpt')[] = [];
const checkTool = (tool: 'gemini' | 'ollama' | 'sgpt') => new Promise<void>(resolve => {
const availableTools: ('gemini' | 'ollama' | 'sgpt' | 'claude')[] = [];
const checkTool = (tool: 'gemini' | 'ollama' | 'sgpt' | 'claude') => new Promise<void>(resolve => {
const proc = spawn('which', [tool], { stdio: 'ignore' });
proc.on('close', code => {
if (code === 0) {
Expand All @@ -94,10 +94,17 @@ async function handleInitCommand(isLocal: boolean) {
proc.on('error', () => resolve());
});

await Promise.all([checkTool('gemini'), checkTool('ollama'), checkTool('sgpt')]);
await Promise.all([checkTool('gemini'), checkTool('ollama'), checkTool('sgpt'), checkTool('claude')]);

if (availableTools.length === 0) {
console.log('No supported AI tools (gemini, ollama, sgpt) found in your PATH.');
console.log('No supported AI tools (gemini, ollama, sgpt, claude) found in your PATH.');
const createEmpty = await ask('\n> Would you like to create an empty configuration file to manually add a custom summarizer? (y/N): ');
if (createEmpty.toLowerCase() === 'y') {
const config = readConfig(); // Read to not overwrite existing unrelated config
writeConfig(config, isLocal);
} else {
console.log('Initialization cancelled.');
}
return;
}

Expand Down Expand Up @@ -131,6 +138,18 @@ async function handleInitCommand(isLocal: boolean) {
});
}
}

if (availableTools.includes('claude')) {
const add = await ask('\n> Found Claude. Add/update the \'claude-opus\' summarizer? (Y/n): ');
if (add.toLowerCase() !== 'n') {
summarizersToUpdate.push({
name: 'claude-opus',
tool: 'claude',
prompt: newPrompt,
maxLines: 100,
});
}
}

if (availableTools.includes('sgpt')) {
const add = await ask('\n> Found ShellGPT. Add/update the \'sgpt\' summarizer? (Y/n): ');
Expand Down Expand Up @@ -164,7 +183,7 @@ async function handleInitCommand(isLocal: boolean) {

// Set default only if it wasn't set before
if (!config.summarizer.default && config.summarizer.summarizers.length > 0) {
const priority = ['gemini-pro', 'ollama', 'sgpt'];
const priority = ['gemini-pro', 'claude-opus', 'ollama', 'sgpt'];
for (const name of priority) {
if (config.summarizer.summarizers.some(s => s.name === name)) {
config.summarizer.default = name;
Expand Down Expand Up @@ -220,6 +239,10 @@ async function getAiSummary(content: string, summarizerName?: string): Promise<s
command = ['gemini', '-p', prompt];
inputForStdin = sampledContent;
break;
case 'claude':
command = ['claude', '-p', prompt];
inputForStdin = sampledContent;
break;
case 'custom':
if (!customCommand) {
console.error(`Custom summarizer "${name}" is missing the "command" definition.`);
Expand Down Expand Up @@ -378,6 +401,13 @@ function runLoggingSession(command: string, commandArgs: string[], summaryArg?:
function main() {
const args = process.argv.slice(2);

if (args.includes('--version') || args.includes('-v')) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require('../package.json');
console.log(pkg.version);
return;
}

if (args.includes('--init')) {
const isLocal = args.includes('--local');
handleInitCommand(isLocal);
Expand Down