chat-history-logger-plugin-opencode-claudecode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/chat-history-enabled +1 -0
- package/.claude/commands/chat-history-cloud-config.md +91 -0
- package/.claude/commands/chat-history-cloud-off.md +11 -0
- package/.claude/commands/chat-history-cloud-on.md +71 -0
- package/.claude/commands/chat-history-cloud-status.md +37 -0
- package/.claude/commands/chat-history-off.md +7 -0
- package/.claude/commands/chat-history-on.md +7 -0
- package/.claude/hooks/chat-history-logger.sh +288 -0
- package/.claude/settings.json +24 -0
- package/.opencode/chat-history-enabled +1 -0
- package/.opencode/commands/chat-history-cloud-config.md +74 -0
- package/.opencode/commands/chat-history-cloud-off.md +11 -0
- package/.opencode/commands/chat-history-cloud-on.md +71 -0
- package/.opencode/commands/chat-history-cloud-status.md +37 -0
- package/.opencode/commands/chat-history-off.md +7 -0
- package/.opencode/commands/chat-history-on.md +7 -0
- package/.opencode/package.json +8 -0
- package/.opencode/plugins/chat-history-logger.ts +461 -0
- package/LICENSE +21 -0
- package/README.md +298 -0
- package/bin/install.js +695 -0
- package/package.json +39 -0
- package/requirements.txt +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
enabled
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Update cloud sync configuration (MongoDB or Supabase)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Update the cloud sync configuration for chat history logging.
|
|
6
|
+
|
|
7
|
+
Follow these steps:
|
|
8
|
+
|
|
9
|
+
1. Read existing config from `.claude/chat-history-cloud-config.json` if it exists. Show current settings (mask sensitive values).
|
|
10
|
+
|
|
11
|
+
2. Ask the user what they want to update:
|
|
12
|
+
- **Switch provider** - Change between MongoDB and Supabase
|
|
13
|
+
- **Update connection details** - Update credentials/URLs for the current provider
|
|
14
|
+
|
|
15
|
+
3. Based on their choice:
|
|
16
|
+
|
|
17
|
+
**For MongoDB:**
|
|
18
|
+
- Ask for: Connection string, Database name, Collection name
|
|
19
|
+
- Show current values as defaults (mask the connection string password)
|
|
20
|
+
- Remind them to install pymongo: `pip install pymongo`
|
|
21
|
+
|
|
22
|
+
**For Supabase:**
|
|
23
|
+
- Ask for: Project URL, API Key, Table name
|
|
24
|
+
- Show current values as defaults (mask the API key)
|
|
25
|
+
- Remind them to install supabase: `pip install supabase`
|
|
26
|
+
- If switching to Supabase, remind them to create the table:
|
|
27
|
+
```sql
|
|
28
|
+
CREATE TABLE chat_history (
|
|
29
|
+
id BIGSERIAL PRIMARY KEY,
|
|
30
|
+
session_id TEXT NOT NULL,
|
|
31
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
32
|
+
date TEXT NOT NULL,
|
|
33
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
34
|
+
content TEXT NOT NULL,
|
|
35
|
+
source TEXT NOT NULL DEFAULT 'claude-code',
|
|
36
|
+
project_path TEXT,
|
|
37
|
+
project_name TEXT,
|
|
38
|
+
local_file TEXT,
|
|
39
|
+
message_id TEXT
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
4. Save the updated configuration to `.claude/chat-history-cloud-config.json`
|
|
44
|
+
|
|
45
|
+
**MongoDB format (use these exact keys):**
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"provider": "mongodb",
|
|
49
|
+
"connectionString": "mongodb+srv://username:password@cluster.mongodb.net",
|
|
50
|
+
"database": "your_db_name",
|
|
51
|
+
"collection": "your_collection_name"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Supabase format (use these exact keys):**
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"provider": "supabase",
|
|
59
|
+
"supabase_url": "https://your-project.supabase.co",
|
|
60
|
+
"supabase_key": "your-anon-or-service-key",
|
|
61
|
+
"supabase_table": "chat_history"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
5. If cloud sync is currently enabled (enabled here meas `chat-history-cloud-enabled` exists inside `.claude` folder), inform the user the new settings will take effect on the next hook invocation.
|
|
66
|
+
|
|
67
|
+
6. If the file `chat-history-cloud-enabled` doesn't exist inside `.claude` folder, create the file `chat-history-cloud-enabled` inside `.claude` folder with the content "enabled".
|
|
68
|
+
|
|
69
|
+
7. Remind the user to keep `.claude/chat-history-cloud-config.json` in their `.gitignore`.
|
|
70
|
+
|
|
71
|
+
8. Remind the user that provider in `.claude/chat-history-cloud-config.json` should only have just one provider at a time, it doesn't support having both supabase and mongodb at the same time.
|
|
72
|
+
|
|
73
|
+
9. Remind the user that if they use Supabase in the `.claude/chat-history-cloud-config.json` as the provider, tell them they need to create the table in Supabase first with this SQL:
|
|
74
|
+
```sql
|
|
75
|
+
CREATE TABLE chat_history (
|
|
76
|
+
id BIGSERIAL PRIMARY KEY,
|
|
77
|
+
session_id TEXT NOT NULL,
|
|
78
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
79
|
+
date TEXT NOT NULL,
|
|
80
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
81
|
+
content TEXT NOT NULL,
|
|
82
|
+
source TEXT NOT NULL DEFAULT 'claude-code',
|
|
83
|
+
project_path TEXT,
|
|
84
|
+
project_name TEXT,
|
|
85
|
+
local_file TEXT,
|
|
86
|
+
message_id TEXT,
|
|
87
|
+
truncated BOOLEAN DEFAULT FALSE
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
After completing, respond with: "Cloud sync configuration updated."
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Disable cloud sync for chat history
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Disable cloud synchronization for chat history logging. Local logging will continue to work.
|
|
6
|
+
|
|
7
|
+
Steps:
|
|
8
|
+
1. Delete the file `.claude/chat-history-cloud-enabled` if it exists.
|
|
9
|
+
2. Keep the `.claude/chat-history-cloud-config.json` file intact so the user can re-enable cloud sync later without reconfiguring.
|
|
10
|
+
|
|
11
|
+
After deleting the file, respond with: "Cloud sync is now OFF. Local chat history logging remains active. Your cloud configuration has been preserved for future use."
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enable cloud sync for chat history (MongoDB or Supabase)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Enable cloud synchronization for chat history logging. This syncs your local chat history to a cloud database.
|
|
6
|
+
|
|
7
|
+
Follow these steps:
|
|
8
|
+
|
|
9
|
+
1. First check if `.claude/chat-history-enabled` exists. If not, create it first since local logging must be on for cloud sync to work. The content should be "enabled" and that's it.
|
|
10
|
+
|
|
11
|
+
2. Create the file `.claude/chat-history-cloud-enabled` with the content "enabled".
|
|
12
|
+
|
|
13
|
+
3. Check if `.claude/chat-history-cloud-config.json` exists.
|
|
14
|
+
|
|
15
|
+
4. If the config file does NOT exist, ask the user which cloud provider they want to use:
|
|
16
|
+
- **MongoDB** - For MongoDB Atlas or self-hosted MongoDB
|
|
17
|
+
- **Supabase** - For Supabase PostgreSQL database
|
|
18
|
+
|
|
19
|
+
5. Based on their choice, ask for the required connection details:
|
|
20
|
+
|
|
21
|
+
**For MongoDB:**
|
|
22
|
+
- Ask for: Connection string, Database name, Collection name
|
|
23
|
+
- Show current values as defaults (mask the connection string password)
|
|
24
|
+
|
|
25
|
+
Then create `.claude/chat-history-cloud-config.json` with this format (use these exact keys):
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"provider": "mongodb",
|
|
29
|
+
"connectionString": "mongodb+srv://username:password@cluster.mongodb.net",
|
|
30
|
+
"database": "your_db_name",
|
|
31
|
+
"collection": "your_collection_name"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**For Supabase:**
|
|
36
|
+
- Ask for: Project URL, API Key, Table name
|
|
37
|
+
- Show current values as defaults (mask the API key)
|
|
38
|
+
|
|
39
|
+
Then create `.claude/chat-history-cloud-config.json` with this format (use these exact keys):
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"provider": "supabase",
|
|
43
|
+
"supabase_url": "https://your-project.supabase.co",
|
|
44
|
+
"supabase_key": "your-anon-or-service-key",
|
|
45
|
+
"supabase_table": "chat_history"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Important for Supabase users:** Tell them they need to create the table in Supabase first with this SQL:
|
|
50
|
+
```sql
|
|
51
|
+
CREATE TABLE chat_history (
|
|
52
|
+
id BIGSERIAL PRIMARY KEY,
|
|
53
|
+
session_id TEXT NOT NULL,
|
|
54
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
55
|
+
date TEXT NOT NULL,
|
|
56
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
57
|
+
content TEXT NOT NULL,
|
|
58
|
+
source TEXT NOT NULL DEFAULT 'claude-code',
|
|
59
|
+
project_path TEXT,
|
|
60
|
+
project_name TEXT,
|
|
61
|
+
local_file TEXT,
|
|
62
|
+
message_id TEXT,
|
|
63
|
+
truncated BOOLEAN DEFAULT FALSE
|
|
64
|
+
);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
6. If `.claude/chat-history-cloud-config.json` already exists, just confirm cloud sync is now enabled.
|
|
68
|
+
|
|
69
|
+
7. Remind the user to add `.claude/chat-history-cloud-config.json` to their `.gitignore` to avoid committing credentials.
|
|
70
|
+
|
|
71
|
+
After completing, respond with: "Cloud sync is now ON. Chat history will be synced to [provider name]. Make sure to add `.claude/chat-history-cloud-config.json` to your `.gitignore`."
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Check cloud sync status and connection info
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Check the current status of chat history cloud synchronization.
|
|
6
|
+
|
|
7
|
+
Follow these steps:
|
|
8
|
+
|
|
9
|
+
1. Check if `.claude/chat-history-enabled` exists:
|
|
10
|
+
- If yes: "Local logging: ON"
|
|
11
|
+
- If no: "Local logging: OFF"
|
|
12
|
+
|
|
13
|
+
2. Check if `.claude/chat-history-cloud-enabled` exists:
|
|
14
|
+
- If yes: "Cloud sync: ON"
|
|
15
|
+
- If no: "Cloud sync: OFF"
|
|
16
|
+
|
|
17
|
+
3. If cloud sync is ON, read `.claude/chat-history-cloud-config.json` and display:
|
|
18
|
+
- Cloud Provider: [mongodb or supabase]
|
|
19
|
+
- For MongoDB:
|
|
20
|
+
- Database: [database name]
|
|
21
|
+
- Collection: [collection name]
|
|
22
|
+
- Connection: [mask the connection string - show only the host part, hide username/password]
|
|
23
|
+
- For Supabase:
|
|
24
|
+
- Project URL: [supabase URL]
|
|
25
|
+
- Table: [table name]
|
|
26
|
+
- Key: [show only first 8 characters followed by ****]
|
|
27
|
+
|
|
28
|
+
4. Provide a summary like:
|
|
29
|
+
```
|
|
30
|
+
Chat History Logger Status
|
|
31
|
+
-------------------------
|
|
32
|
+
Local logging: ON/OFF
|
|
33
|
+
Cloud sync: ON/OFF
|
|
34
|
+
Cloud provider: MongoDB/Supabase/Not configured
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If neither local logging nor cloud sync is enabled, suggest running `/chat-history-on` first, then `/chat-history-cloud-on`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Turn on chat history logging
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Create the file `.claude/chat-history-enabled` with the content "enabled" to enable chat history logging.
|
|
6
|
+
|
|
7
|
+
After creating the file, respond with: "Chat history logging is now ON. All messages will be saved to `chat_history/YYYY_MM_DD.md`"
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Chat History Logger - Claude Code Hook
|
|
4
|
+
=======================================
|
|
5
|
+
Logs user prompts and assistant responses to daily Markdown files
|
|
6
|
+
in chat_history/YYYY_MM_DD.md
|
|
7
|
+
|
|
8
|
+
This script handles two hook events:
|
|
9
|
+
- UserPromptSubmit: logs the user's prompt directly from the hook input
|
|
10
|
+
- Stop: logs the assistant's response by parsing the transcript JSONL
|
|
11
|
+
|
|
12
|
+
Toggle: create/delete .claude/chat-history-enabled to enable/disable logging.
|
|
13
|
+
|
|
14
|
+
Cloud Sync: create/delete .claude/chat-history-cloud-enabled and configure
|
|
15
|
+
.claude/chat-history-cloud-config.json to enable/disable cloud sync.
|
|
16
|
+
Supports MongoDB and Supabase.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# Content truncation settings
|
|
26
|
+
MAX_CONTENT_SIZE = 14 * 1024 * 1024 # 14MB (leave 2MB buffer for metadata)
|
|
27
|
+
TRUNCATION_SUFFIX = "...."
|
|
28
|
+
|
|
29
|
+
def truncate_content(content: str) -> tuple[str, bool]:
|
|
30
|
+
"""Truncate content if it exceeds the size limit. Returns (content, was_truncated)."""
|
|
31
|
+
if len(content.encode('utf-8')) >= MAX_CONTENT_SIZE:
|
|
32
|
+
truncated = content.encode('utf-8')[:MAX_CONTENT_SIZE - len(TRUNCATION_SUFFIX)].decode('utf-8', errors='ignore')
|
|
33
|
+
return truncated + TRUNCATION_SUFFIX, True
|
|
34
|
+
return content, False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_cloud_config(cwd):
|
|
38
|
+
"""Load cloud sync configuration from JSON file."""
|
|
39
|
+
config_path = os.path.join(cwd, '.claude', 'chat-history-cloud-config.json')
|
|
40
|
+
if not os.path.exists(config_path):
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
with open(config_path, 'r') as f:
|
|
44
|
+
return json.load(f)
|
|
45
|
+
except (json.JSONDecodeError, IOError):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sync_to_mongodb(config, doc):
|
|
50
|
+
"""Sync a chat document to MongoDB."""
|
|
51
|
+
try:
|
|
52
|
+
from pymongo import MongoClient
|
|
53
|
+
|
|
54
|
+
# Support both nested config (config.mongodb.*) and flat config (config.*)
|
|
55
|
+
mongo_cfg = config.get('mongodb', config)
|
|
56
|
+
connection_string = mongo_cfg.get('connection_string', mongo_cfg.get('connectionString', ''))
|
|
57
|
+
database_name = mongo_cfg.get('database_name', mongo_cfg.get('database', 'chat_history'))
|
|
58
|
+
collection_name = mongo_cfg.get('collection_name', mongo_cfg.get('collection', 'conversations'))
|
|
59
|
+
|
|
60
|
+
client = MongoClient(connection_string, serverSelectionTimeoutMS=5000)
|
|
61
|
+
db = client[database_name]
|
|
62
|
+
collection = db[collection_name]
|
|
63
|
+
|
|
64
|
+
# Ensure indexes exist (idempotent)
|
|
65
|
+
collection.create_index('date')
|
|
66
|
+
collection.create_index('project_name')
|
|
67
|
+
collection.create_index([('timestamp', -1)])
|
|
68
|
+
collection.create_index('session_id')
|
|
69
|
+
|
|
70
|
+
doc['_created_at'] = datetime.now(timezone.utc)
|
|
71
|
+
collection.insert_one(doc)
|
|
72
|
+
client.close()
|
|
73
|
+
return True
|
|
74
|
+
except ImportError:
|
|
75
|
+
print("[ChatHistoryLogger] pymongo not installed. Run: pip install pymongo", file=sys.stderr)
|
|
76
|
+
return False
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"[ChatHistoryLogger] MongoDB sync failed: {e}", file=sys.stderr)
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sync_to_supabase(config, doc):
|
|
83
|
+
"""Sync a chat document to Supabase."""
|
|
84
|
+
try:
|
|
85
|
+
from supabase import create_client
|
|
86
|
+
|
|
87
|
+
# Support both nested config (config.supabase.*) and flat config (config.*)
|
|
88
|
+
supa_cfg = config.get('supabase', config)
|
|
89
|
+
supabase_url = supa_cfg.get('supabase_url', supa_cfg.get('url', ''))
|
|
90
|
+
supabase_key = supa_cfg.get('supabase_key', supa_cfg.get('key', ''))
|
|
91
|
+
table_name = supa_cfg.get('supabase_table', supa_cfg.get('table', 'chat_history'))
|
|
92
|
+
|
|
93
|
+
client = create_client(supabase_url, supabase_key)
|
|
94
|
+
|
|
95
|
+
row = {
|
|
96
|
+
'session_id': doc['session_id'],
|
|
97
|
+
'timestamp': doc['timestamp'],
|
|
98
|
+
'date': doc['date'],
|
|
99
|
+
'role': doc['role'],
|
|
100
|
+
'content': doc['content'],
|
|
101
|
+
'source': doc['source'],
|
|
102
|
+
'project_path': doc['project_path'],
|
|
103
|
+
'project_name': doc['project_name'],
|
|
104
|
+
'local_file': doc['metadata']['local_file'],
|
|
105
|
+
'message_id': doc['metadata']['message_id'],
|
|
106
|
+
'truncated': doc['metadata'].get('truncated', False),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
result = client.table(table_name).insert(row).execute()
|
|
110
|
+
return True
|
|
111
|
+
except ImportError:
|
|
112
|
+
print("[ChatHistoryLogger] supabase not installed. Run: pip install supabase", file=sys.stderr)
|
|
113
|
+
return False
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"[ChatHistoryLogger] Supabase sync failed: {e}", file=sys.stderr)
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def sync_to_cloud(cwd, role, content, transcript_path=''):
|
|
120
|
+
"""Sync a message to cloud if cloud sync is enabled."""
|
|
121
|
+
cloud_enabled_file = os.path.join(cwd, '.claude', 'chat-history-cloud-enabled')
|
|
122
|
+
if not os.path.exists(cloud_enabled_file):
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
config = load_cloud_config(cwd)
|
|
126
|
+
if not config:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
truncated_content, was_truncated = truncate_content(content)
|
|
130
|
+
|
|
131
|
+
now = datetime.now(timezone.utc)
|
|
132
|
+
date_str = now.strftime('%Y_%m_%d')
|
|
133
|
+
project_name = os.path.basename(os.path.abspath(cwd))
|
|
134
|
+
|
|
135
|
+
# Use transcript path as session ID for grouping, or generate one
|
|
136
|
+
session_id = transcript_path if transcript_path else f"claude_{int(now.timestamp())}"
|
|
137
|
+
|
|
138
|
+
doc = {
|
|
139
|
+
'session_id': session_id,
|
|
140
|
+
'timestamp': now.isoformat(),
|
|
141
|
+
'date': date_str,
|
|
142
|
+
'role': role,
|
|
143
|
+
'content': truncated_content,
|
|
144
|
+
'source': 'claude-code',
|
|
145
|
+
'project_path': os.path.abspath(cwd),
|
|
146
|
+
'project_name': project_name,
|
|
147
|
+
'metadata': {
|
|
148
|
+
'local_file': f'chat_history/{date_str}.md',
|
|
149
|
+
'message_id': f'{role}_{int(now.timestamp())}',
|
|
150
|
+
'truncated': was_truncated,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
provider = config.get('provider', '')
|
|
155
|
+
if provider == 'mongodb':
|
|
156
|
+
sync_to_mongodb(config, doc)
|
|
157
|
+
elif provider == 'supabase':
|
|
158
|
+
sync_to_supabase(config, doc)
|
|
159
|
+
else:
|
|
160
|
+
print(f"[ChatHistoryLogger] Unknown cloud provider: {provider}", file=sys.stderr)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main():
|
|
164
|
+
try:
|
|
165
|
+
# Read JSON input from stdin
|
|
166
|
+
input_data = json.loads(sys.stdin.read())
|
|
167
|
+
except (json.JSONDecodeError, EOFError):
|
|
168
|
+
sys.exit(0)
|
|
169
|
+
|
|
170
|
+
hook_event = input_data.get('hook_event_name', '')
|
|
171
|
+
cwd = input_data.get('cwd', os.getcwd())
|
|
172
|
+
transcript_path = input_data.get('transcript_path', '')
|
|
173
|
+
|
|
174
|
+
# Check if logging is enabled via toggle file
|
|
175
|
+
enabled_file = os.path.join(cwd, '.claude', 'chat-history-enabled')
|
|
176
|
+
if not os.path.exists(enabled_file):
|
|
177
|
+
sys.exit(0)
|
|
178
|
+
|
|
179
|
+
# Set up chat_history directory
|
|
180
|
+
chat_history_dir = os.path.join(cwd, 'chat_history')
|
|
181
|
+
os.makedirs(chat_history_dir, exist_ok=True)
|
|
182
|
+
|
|
183
|
+
# Date formatting
|
|
184
|
+
today = datetime.now()
|
|
185
|
+
date_file = today.strftime('%Y_%m_%d')
|
|
186
|
+
date_header = today.strftime('%A, %B %d, %Y')
|
|
187
|
+
timestamp = today.strftime('%H:%M')
|
|
188
|
+
|
|
189
|
+
file_path = os.path.join(chat_history_dir, f'{date_file}.md')
|
|
190
|
+
|
|
191
|
+
# Create header if file doesn't exist
|
|
192
|
+
if not os.path.exists(file_path):
|
|
193
|
+
with open(file_path, 'w') as f:
|
|
194
|
+
f.write(f'# Chat History - {date_header}\n\n---\n\n')
|
|
195
|
+
|
|
196
|
+
# Handle UserPromptSubmit: log the user's message
|
|
197
|
+
if hook_event == 'UserPromptSubmit':
|
|
198
|
+
prompt = input_data.get('prompt', '')
|
|
199
|
+
if prompt:
|
|
200
|
+
with open(file_path, 'a') as f:
|
|
201
|
+
f.write(f'### User [{timestamp}]\n\n{prompt}\n\n---\n\n')
|
|
202
|
+
|
|
203
|
+
# Cloud sync (non-blocking best effort)
|
|
204
|
+
try:
|
|
205
|
+
sync_to_cloud(cwd, 'user', prompt, transcript_path)
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# Handle Stop: log the assistant's response
|
|
210
|
+
elif hook_event == 'Stop':
|
|
211
|
+
assistant_text = ''
|
|
212
|
+
|
|
213
|
+
# Try to parse the transcript JSONL to extract the latest assistant response
|
|
214
|
+
if transcript_path and os.path.exists(transcript_path):
|
|
215
|
+
import time
|
|
216
|
+
# Small delay to ensure the transcript file is fully written
|
|
217
|
+
time.sleep(0.1)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
# Read the transcript file from the end to find the most recent assistant message
|
|
221
|
+
with open(transcript_path, 'r') as f:
|
|
222
|
+
lines = f.readlines()
|
|
223
|
+
|
|
224
|
+
# Search backwards through the JSONL for the last assistant message with text content
|
|
225
|
+
for line in reversed(lines):
|
|
226
|
+
line = line.strip()
|
|
227
|
+
if not line:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
msg = json.loads(line)
|
|
232
|
+
except json.JSONDecodeError:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# Skip non-assistant entries
|
|
236
|
+
if msg.get('type') != 'assistant':
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# Check for role="assistant" in a nested message field (most common format)
|
|
240
|
+
message_obj = msg.get('message', {})
|
|
241
|
+
if message_obj.get('role') == 'assistant':
|
|
242
|
+
content = message_obj.get('content', [])
|
|
243
|
+
if isinstance(content, list):
|
|
244
|
+
text_parts = [
|
|
245
|
+
block.get('text', '')
|
|
246
|
+
for block in content
|
|
247
|
+
if block.get('type') == 'text' and block.get('text')
|
|
248
|
+
]
|
|
249
|
+
if text_parts:
|
|
250
|
+
assistant_text = '\n'.join(text_parts)
|
|
251
|
+
break
|
|
252
|
+
|
|
253
|
+
# Check for role="assistant" at the top level (fallback)
|
|
254
|
+
if msg.get('role') == 'assistant':
|
|
255
|
+
content = msg.get('content', [])
|
|
256
|
+
if isinstance(content, list):
|
|
257
|
+
text_parts = [
|
|
258
|
+
block.get('text', '')
|
|
259
|
+
for block in content
|
|
260
|
+
if block.get('type') == 'text' and block.get('text')
|
|
261
|
+
]
|
|
262
|
+
if text_parts:
|
|
263
|
+
assistant_text = '\n'.join(text_parts)
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
# Log the assistant response
|
|
270
|
+
if assistant_text:
|
|
271
|
+
with open(file_path, 'a') as f:
|
|
272
|
+
f.write(f'### Assistant [{timestamp}]\n\n{assistant_text}\n\n---\n\n')
|
|
273
|
+
|
|
274
|
+
# Cloud sync (non-blocking best effort)
|
|
275
|
+
try:
|
|
276
|
+
sync_to_cloud(cwd, 'assistant', assistant_text, transcript_path)
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
else:
|
|
280
|
+
# Fallback: log a placeholder if we couldn't extract the text
|
|
281
|
+
with open(file_path, 'a') as f:
|
|
282
|
+
f.write(f'### Assistant [{timestamp}]\n\n*[Response logged]*\n\n---\n\n')
|
|
283
|
+
|
|
284
|
+
sys.exit(0)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == '__main__':
|
|
288
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"UserPromptSubmit": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/chat-history-logger.sh"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"Stop": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/chat-history-logger.sh"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
enabled
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Update cloud sync configuration (MongoDB or Supabase)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Update the cloud sync configuration for chat history logging.
|
|
6
|
+
|
|
7
|
+
Follow these steps:
|
|
8
|
+
|
|
9
|
+
1. Read existing config from `.opencode/chat-history-cloud-config.json` if it exists. Show current settings (mask sensitive values).
|
|
10
|
+
|
|
11
|
+
2. Ask the user what they want to update:
|
|
12
|
+
- **Switch provider** - Change between MongoDB and Supabase
|
|
13
|
+
- **Update connection details** - Update credentials/URLs for the current provider
|
|
14
|
+
|
|
15
|
+
3. Based on their choice:
|
|
16
|
+
|
|
17
|
+
**For MongoDB:**
|
|
18
|
+
- Ask for: Connection string, Database name, Collection name
|
|
19
|
+
- Show current values as defaults (mask the connection string password)
|
|
20
|
+
|
|
21
|
+
**For Supabase:**
|
|
22
|
+
- Ask for: Project URL, API Key, Table name
|
|
23
|
+
- Show current values as defaults (mask the API key)
|
|
24
|
+
|
|
25
|
+
4. Save the updated configuration to `.opencode/chat-history-cloud-config.json`
|
|
26
|
+
|
|
27
|
+
**MongoDB format (use these exact keys):**
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"provider": "mongodb",
|
|
31
|
+
"connectionString": "mongodb+srv://username:password@cluster.mongodb.net",
|
|
32
|
+
"database": "your_db_name",
|
|
33
|
+
"collection": "your_collection_name"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Supabase format (use these exact keys):**
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"provider": "supabase",
|
|
41
|
+
"supabase_url": "https://your-project.supabase.co",
|
|
42
|
+
"supabase_key": "your-anon-or-service-key",
|
|
43
|
+
"supabase_table": "chat_history"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
5. If cloud sync is currently enabled (enabled here meas `chat-history-cloud-enabled` exists inside `.opencode` folder), inform the user the new settings will take effect on the next hook invocation.
|
|
48
|
+
|
|
49
|
+
6. If the file `chat-history-cloud-enabled` doesn't exist inside `.opencode` folder, create the file `chat-history-cloud-enabled` inside `.opencode` folder with the content "enabled".
|
|
50
|
+
|
|
51
|
+
7. Remind the user to keep `.opencode/chat-history-cloud-config.json` in their `.gitignore`.
|
|
52
|
+
|
|
53
|
+
8. Remind the user that provider in `.opencode/chat-history-cloud-config.json` should only have just one provider at a time, it doesn't support having both supabase and mongodb at the same time.
|
|
54
|
+
|
|
55
|
+
9. Remind the user that if they use Supabase in the `.opencode/chat-history-cloud-config.json` as the provider, tell them they need to create the table in Supabase first with this SQL:
|
|
56
|
+
```sql
|
|
57
|
+
CREATE TABLE chat_history (
|
|
58
|
+
id BIGSERIAL PRIMARY KEY,
|
|
59
|
+
session_id TEXT NOT NULL,
|
|
60
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
61
|
+
date TEXT NOT NULL,
|
|
62
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
63
|
+
content TEXT NOT NULL,
|
|
64
|
+
source TEXT NOT NULL DEFAULT 'opencode',
|
|
65
|
+
project_path TEXT,
|
|
66
|
+
project_name TEXT,
|
|
67
|
+
local_file TEXT,
|
|
68
|
+
message_id TEXT,
|
|
69
|
+
truncated BOOLEAN DEFAULT FALSE
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
After completing, respond with: "Cloud sync configuration updated."
|
|
74
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Disable cloud sync for chat history
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Disable cloud synchronization for chat history logging. Local logging will continue to work.
|
|
6
|
+
|
|
7
|
+
Steps:
|
|
8
|
+
1. Delete the file `.opencode/chat-history-cloud-enabled` if it exists.
|
|
9
|
+
2. Keep the `.opencode/chat-history-cloud-config.json` file intact so the user can re-enable cloud sync later without reconfiguring.
|
|
10
|
+
|
|
11
|
+
After deleting the file, respond with: "Cloud sync is now OFF. Local chat history logging remains active. Your cloud configuration has been preserved for future use."
|