cc-sentiment 0.1.1__tar.gz
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.
- cc_sentiment-0.1.1/.gitignore +61 -0
- cc_sentiment-0.1.1/AGENTS.md +143 -0
- cc_sentiment-0.1.1/CLAUDE.md +1 -0
- cc_sentiment-0.1.1/LICENSE +21 -0
- cc_sentiment-0.1.1/PKG-INFO +84 -0
- cc_sentiment-0.1.1/README.md +52 -0
- cc_sentiment-0.1.1/cc_sentiment/__init__.py +0 -0
- cc_sentiment-0.1.1/cc_sentiment/benchmark.py +367 -0
- cc_sentiment-0.1.1/cc_sentiment/cli.py +42 -0
- cc_sentiment-0.1.1/cc_sentiment/engines.py +471 -0
- cc_sentiment-0.1.1/cc_sentiment/labeled_data.py +436 -0
- cc_sentiment-0.1.1/cc_sentiment/models.py +212 -0
- cc_sentiment-0.1.1/cc_sentiment/patches/__init__.py +23 -0
- cc_sentiment-0.1.1/cc_sentiment/patches/pr999.patch +233 -0
- cc_sentiment-0.1.1/cc_sentiment/pipeline.py +200 -0
- cc_sentiment-0.1.1/cc_sentiment/sentiment.py +157 -0
- cc_sentiment-0.1.1/cc_sentiment/signing.py +235 -0
- cc_sentiment-0.1.1/cc_sentiment/transcripts.py +166 -0
- cc_sentiment-0.1.1/cc_sentiment/tui.py +1085 -0
- cc_sentiment-0.1.1/cc_sentiment/upload.py +94 -0
- cc_sentiment-0.1.1/pyproject.toml +72 -0
- cc_sentiment-0.1.1/tests/__init__.py +0 -0
- cc_sentiment-0.1.1/tests/fixtures/sample_transcript.jsonl +12 -0
- cc_sentiment-0.1.1/tests/helpers.py +34 -0
- cc_sentiment-0.1.1/tests/test_cli.py +52 -0
- cc_sentiment-0.1.1/tests/test_engines.py +220 -0
- cc_sentiment-0.1.1/tests/test_models.py +146 -0
- cc_sentiment-0.1.1/tests/test_pipeline.py +201 -0
- cc_sentiment-0.1.1/tests/test_sentiment.py +149 -0
- cc_sentiment-0.1.1/tests/test_signing.py +157 -0
- cc_sentiment-0.1.1/tests/test_transcripts.py +217 -0
- cc_sentiment-0.1.1/tests/test_tui.py +445 -0
- cc_sentiment-0.1.1/tests/test_upload.py +104 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
._*
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
*.so
|
|
10
|
+
build/
|
|
11
|
+
dist/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
*.egg
|
|
14
|
+
.Python
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
|
|
20
|
+
# uv
|
|
21
|
+
uv.lock
|
|
22
|
+
|
|
23
|
+
# pytest / coverage
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
htmlcov/
|
|
26
|
+
.coverage
|
|
27
|
+
.coverage.*
|
|
28
|
+
|
|
29
|
+
# mypy / pyright / ruff
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
|
|
33
|
+
# Node / Bun
|
|
34
|
+
node_modules/
|
|
35
|
+
.svelte-kit/
|
|
36
|
+
bun.lockb
|
|
37
|
+
|
|
38
|
+
# MLX model artifacts
|
|
39
|
+
*.safetensors
|
|
40
|
+
*.gguf
|
|
41
|
+
*.bin
|
|
42
|
+
*.npz
|
|
43
|
+
models/
|
|
44
|
+
mlx_models/
|
|
45
|
+
.mlx_cache/
|
|
46
|
+
|
|
47
|
+
# Modal
|
|
48
|
+
.modal/
|
|
49
|
+
|
|
50
|
+
# IDE
|
|
51
|
+
.idea/
|
|
52
|
+
.vscode/
|
|
53
|
+
*.swp
|
|
54
|
+
*.swo
|
|
55
|
+
|
|
56
|
+
# Project
|
|
57
|
+
.serena/
|
|
58
|
+
.context/
|
|
59
|
+
|
|
60
|
+
.claude/settings.local.json
|
|
61
|
+
.vercel
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# client/ — macOS CLI
|
|
2
|
+
|
|
3
|
+
macOS Apple Silicon CLI tool. Discovers Claude Code conversation transcripts, runs them through Gemma 4 via MLX for local sentiment analysis, signs results with GitHub SSH keys, and uploads to the server.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- **Runtime**: Python 3.12+, macOS Apple Silicon only
|
|
8
|
+
- **ML inference**: MLX (`mlx-lm`) for local Gemma 4 inference on Apple Silicon GPU
|
|
9
|
+
- **Model**: `mlx-community/gemma-4-e4b-it-4bit` (4-bit quantized, ~2.5GB) — also benchmark `unsloth/gemma-4-E4B-it-UD-MLX-4bit`
|
|
10
|
+
- **CLI**: `click` or `typer`
|
|
11
|
+
- **HTTP**: `httpx` for async uploads
|
|
12
|
+
- **Signing**: `ssh-keygen -Y sign` via subprocess
|
|
13
|
+
- **Packaging**: `uv tool install` from pyproject.toml with `[project.scripts]` entry point
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync # Install dependencies
|
|
19
|
+
uv run cc-sentiment scan # Discover and score new transcripts
|
|
20
|
+
uv run cc-sentiment upload # Upload pending scores to server
|
|
21
|
+
uv run cc-sentiment scan --upload # Scan and upload in one step
|
|
22
|
+
uv run cc-sentiment setup # Configure GitHub username, verify SSH keys
|
|
23
|
+
uv run pytest client/ # Run tests
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Directory Structure (planned)
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
client/
|
|
30
|
+
├── pyproject.toml
|
|
31
|
+
├── cli.py # CLI entry point (click/typer commands)
|
|
32
|
+
├── transcripts.py # Transcript discovery and parsing
|
|
33
|
+
├── sentiment.py # MLX inference, prompt construction, score extraction
|
|
34
|
+
├── signing.py # GitHub SSH key discovery and payload signing
|
|
35
|
+
├── upload.py # HTTP client for server API
|
|
36
|
+
├── models.py # Pydantic models for transcripts, scores, payloads
|
|
37
|
+
└── tests/
|
|
38
|
+
├── test_transcripts.py
|
|
39
|
+
├── test_sentiment.py
|
|
40
|
+
├── test_signing.py
|
|
41
|
+
└── fixtures/
|
|
42
|
+
└── sample_transcript.jsonl
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Transcript Discovery
|
|
46
|
+
|
|
47
|
+
Claude Code stores conversations at `~/.claude/projects/<project-slug>/<uuid>.jsonl`. Each line is a JSON object representing a conversation turn.
|
|
48
|
+
|
|
49
|
+
The client:
|
|
50
|
+
1. Walks `~/.claude/projects/` recursively for `.jsonl` files
|
|
51
|
+
2. Tracks already-processed files in `~/.cc-sentiment/state.json`
|
|
52
|
+
3. Skips files unchanged since last scan (by mtime)
|
|
53
|
+
4. Parses each JSONL file into a conversation
|
|
54
|
+
|
|
55
|
+
### JSONL Structure
|
|
56
|
+
|
|
57
|
+
Each line contains (at minimum):
|
|
58
|
+
- `type`: message type (`human`, `assistant`, `tool_use`, `tool_result`, etc.)
|
|
59
|
+
- `message`: the content object
|
|
60
|
+
- Timestamps in conversation metadata
|
|
61
|
+
|
|
62
|
+
Extract user messages (`type: "human"`) as the primary sentiment signal. Error tool results and assistant apologies are secondary signals.
|
|
63
|
+
|
|
64
|
+
## MLX Inference
|
|
65
|
+
|
|
66
|
+
### Model Loading
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from mlx_lm import load, generate
|
|
70
|
+
|
|
71
|
+
model, tokenizer = load("mlx-community/gemma-4-e4b-it-4bit")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Model is downloaded once and cached in `~/.cache/huggingface/`.
|
|
75
|
+
|
|
76
|
+
### Sentiment Prompt
|
|
77
|
+
|
|
78
|
+
Score each conversation on a 1-5 Likert scale:
|
|
79
|
+
- **1** — Deeply frustrated, angry, giving up
|
|
80
|
+
- **2** — Annoyed, things aren't working
|
|
81
|
+
- **3** — Neutral, transactional
|
|
82
|
+
- **4** — Satisfied, things are working
|
|
83
|
+
- **5** — Delighted, impressed, flow state
|
|
84
|
+
|
|
85
|
+
The prompt must:
|
|
86
|
+
- Present the conversation (or representative sample if too long)
|
|
87
|
+
- Ask for a single integer score with brief justification
|
|
88
|
+
- Use structured output (JSON) for reliable extraction
|
|
89
|
+
- Be versioned — prompt changes shift the dataset, so the version is included in uploaded records
|
|
90
|
+
|
|
91
|
+
### Inference Config
|
|
92
|
+
|
|
93
|
+
- `max_tokens`: 100 (score + short justification)
|
|
94
|
+
- `temperature`: 0.0 (deterministic scoring)
|
|
95
|
+
- Each conversation gets its own inference call (context matters)
|
|
96
|
+
|
|
97
|
+
## GitHub SSH Signing
|
|
98
|
+
|
|
99
|
+
### Key Discovery
|
|
100
|
+
|
|
101
|
+
Look for SSH keys in order:
|
|
102
|
+
1. `~/.ssh/id_ed25519` (preferred)
|
|
103
|
+
2. `~/.ssh/id_rsa`
|
|
104
|
+
3. Keys listed in `~/.ssh/config`
|
|
105
|
+
|
|
106
|
+
GitHub username from `git config github.user` or `~/.cc-sentiment/config.toml`.
|
|
107
|
+
|
|
108
|
+
### Setup Flow
|
|
109
|
+
|
|
110
|
+
`cc-sentiment setup`:
|
|
111
|
+
1. Ask for GitHub username (or read from git config)
|
|
112
|
+
2. Fetch `https://github.com/<username>.keys`
|
|
113
|
+
3. Find matching local private key
|
|
114
|
+
4. If no match: print instructions for adding an SSH key to GitHub
|
|
115
|
+
5. Save config to `~/.cc-sentiment/config.toml`
|
|
116
|
+
|
|
117
|
+
### Signing Protocol
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
echo '<canonical_json>' | ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n cc-sentiment
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Namespace `cc-sentiment` prevents signature reuse across applications. Canonical JSON = sorted keys, compact encoding, no whitespace.
|
|
124
|
+
|
|
125
|
+
## Upload Protocol
|
|
126
|
+
|
|
127
|
+
1. Collect pending records (scored but not yet uploaded) from `~/.cc-sentiment/state.json`
|
|
128
|
+
2. Serialize records to canonical JSON
|
|
129
|
+
3. Sign with user's SSH key
|
|
130
|
+
4. `POST` to server `/upload` with signed payload
|
|
131
|
+
5. On success, mark records as uploaded in state
|
|
132
|
+
6. On failure, retain for retry on next run
|
|
133
|
+
|
|
134
|
+
## Style Specifics
|
|
135
|
+
|
|
136
|
+
All rules from root `AGENTS.md` apply, plus:
|
|
137
|
+
|
|
138
|
+
- **MLX isolated in `sentiment.py`.** No MLX imports leak into other modules. Keeps the CLI testable on non-Apple-Silicon (mock sentiment module).
|
|
139
|
+
- **Subprocess calls use explicit argument lists.** Never `shell=True`. Always `subprocess.run(["ssh-keygen", "-Y", "sign", ...])`.
|
|
140
|
+
- **Local state is a single JSON file.** `~/.cc-sentiment/state.json` for processed transcripts and pending uploads. No database for the client.
|
|
141
|
+
- **CLI commands are thin.** Parse args, call library modules, format output. No business logic in CLI handlers.
|
|
142
|
+
- **All network calls through `upload.py`.** Single module owns the HTTP client, base URL, retry logic.
|
|
143
|
+
- **Fail loudly on wrong platform.** If MLX unavailable (not Apple Silicon), crash at import time with a clear message.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yasyf Mohamedali
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cc-sentiment
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: macOS CLI for Claude Code conversation sentiment analysis
|
|
5
|
+
Project-URL: Homepage, https://sentiments.cc
|
|
6
|
+
Project-URL: Repository, https://github.com/yasyf/cc-sentiment
|
|
7
|
+
Project-URL: Issues, https://github.com/yasyf/cc-sentiment/issues
|
|
8
|
+
Author-email: Yasyf Mohamedali <yasyfm@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: apple-silicon,claude-code,mlx,sentiment
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.13.11
|
|
21
|
+
Requires-Dist: anyio>=4.4
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: mlx-lm>=0.20
|
|
25
|
+
Requires-Dist: orjson>=3.10
|
|
26
|
+
Requires-Dist: pydantic>=2.7
|
|
27
|
+
Requires-Dist: python-gnupg>=0.5
|
|
28
|
+
Requires-Dist: rich>=13.0
|
|
29
|
+
Requires-Dist: tenacity>=8.0
|
|
30
|
+
Requires-Dist: textual>=3.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# cc-sentiment
|
|
34
|
+
|
|
35
|
+
A macOS CLI that scores your Claude Code conversations on-device and contributes the numbers to an open dashboard at [sentiments.cc](https://sentiments.cc).
|
|
36
|
+
|
|
37
|
+
Your conversations stay on your Mac. Only anonymous numeric scores are uploaded.
|
|
38
|
+
|
|
39
|
+
## Install & run
|
|
40
|
+
|
|
41
|
+
The fast path — keeps the [omlx](https://github.com/jundot/omlx) grammar-constrained inference engine:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uvx --from https://sentiments.cc/run cc-sentiment
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or from PyPI (falls back to the pure `mlx-lm` engine):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uvx cc-sentiment
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires macOS on Apple Silicon, Python 3.13+, and [uv](https://docs.astral.sh/uv/).
|
|
54
|
+
|
|
55
|
+
The bare command walks you through setup (linking your GitHub account so uploads are attributable), scores your transcripts, and uploads the scores.
|
|
56
|
+
|
|
57
|
+
## What gets uploaded
|
|
58
|
+
|
|
59
|
+
Only numbers and timestamps. For each 5-minute bucket of a conversation:
|
|
60
|
+
|
|
61
|
+
- Sentiment score (1–5, scored locally by Gemma 4)
|
|
62
|
+
- Read:edit ratio, edits-without-prior-read %, write:edit ratio, tool calls per turn, subagent spawn rate
|
|
63
|
+
- Turn count, thinking present/chars
|
|
64
|
+
- Claude model and Claude Code version
|
|
65
|
+
- Your GitHub handle (so uploads are attributable)
|
|
66
|
+
|
|
67
|
+
Your conversation text, file contents, file paths, and tool inputs/outputs never leave your machine.
|
|
68
|
+
|
|
69
|
+
## Commands
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `cc-sentiment` | Run the whole flow — set up if needed, then scan and upload |
|
|
74
|
+
| `cc-sentiment setup` | Link your GitHub account for attributable uploads |
|
|
75
|
+
| `cc-sentiment scan --upload` | Score new transcripts and upload |
|
|
76
|
+
| `cc-sentiment scan` | Score transcripts without uploading |
|
|
77
|
+
| `cc-sentiment upload` | Upload previously scored results |
|
|
78
|
+
| `cc-sentiment rescan` | Clear state and re-score everything |
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- Dashboard: [sentiments.cc](https://sentiments.cc)
|
|
83
|
+
- Source: [github.com/yasyf/cc-sentiment](https://github.com/yasyf/cc-sentiment)
|
|
84
|
+
- Issues: [github.com/yasyf/cc-sentiment/issues](https://github.com/yasyf/cc-sentiment/issues)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# cc-sentiment
|
|
2
|
+
|
|
3
|
+
A macOS CLI that scores your Claude Code conversations on-device and contributes the numbers to an open dashboard at [sentiments.cc](https://sentiments.cc).
|
|
4
|
+
|
|
5
|
+
Your conversations stay on your Mac. Only anonymous numeric scores are uploaded.
|
|
6
|
+
|
|
7
|
+
## Install & run
|
|
8
|
+
|
|
9
|
+
The fast path — keeps the [omlx](https://github.com/jundot/omlx) grammar-constrained inference engine:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uvx --from https://sentiments.cc/run cc-sentiment
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or from PyPI (falls back to the pure `mlx-lm` engine):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uvx cc-sentiment
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires macOS on Apple Silicon, Python 3.13+, and [uv](https://docs.astral.sh/uv/).
|
|
22
|
+
|
|
23
|
+
The bare command walks you through setup (linking your GitHub account so uploads are attributable), scores your transcripts, and uploads the scores.
|
|
24
|
+
|
|
25
|
+
## What gets uploaded
|
|
26
|
+
|
|
27
|
+
Only numbers and timestamps. For each 5-minute bucket of a conversation:
|
|
28
|
+
|
|
29
|
+
- Sentiment score (1–5, scored locally by Gemma 4)
|
|
30
|
+
- Read:edit ratio, edits-without-prior-read %, write:edit ratio, tool calls per turn, subagent spawn rate
|
|
31
|
+
- Turn count, thinking present/chars
|
|
32
|
+
- Claude model and Claude Code version
|
|
33
|
+
- Your GitHub handle (so uploads are attributable)
|
|
34
|
+
|
|
35
|
+
Your conversation text, file contents, file paths, and tool inputs/outputs never leave your machine.
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
| Command | Description |
|
|
40
|
+
|---------|-------------|
|
|
41
|
+
| `cc-sentiment` | Run the whole flow — set up if needed, then scan and upload |
|
|
42
|
+
| `cc-sentiment setup` | Link your GitHub account for attributable uploads |
|
|
43
|
+
| `cc-sentiment scan --upload` | Score new transcripts and upload |
|
|
44
|
+
| `cc-sentiment scan` | Score transcripts without uploading |
|
|
45
|
+
| `cc-sentiment upload` | Upload previously scored results |
|
|
46
|
+
| `cc-sentiment rescan` | Clear state and re-score everything |
|
|
47
|
+
|
|
48
|
+
## Links
|
|
49
|
+
|
|
50
|
+
- Dashboard: [sentiments.cc](https://sentiments.cc)
|
|
51
|
+
- Source: [github.com/yasyf/cc-sentiment](https://github.com/yasyf/cc-sentiment)
|
|
52
|
+
- Issues: [github.com/yasyf/cc-sentiment/issues](https://github.com/yasyf/cc-sentiment/issues)
|
|
File without changes
|