@moshi-labs/snitch 1.0.2 → 1.0.4
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/README.md +73 -22
- package/cli/collector.js +31 -9
- package/cli/config.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,53 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🐀 Snitch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
See what your teammates' Claude Code sessions are working on in real-time.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Dashboard:** https://snitch.moshi.ai
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start (5 minutes)
|
|
10
|
+
|
|
11
|
+
### Step 1: Install
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
14
|
npm install -g @moshi-labs/snitch
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
### Step 2: Configure
|
|
12
18
|
|
|
13
19
|
```bash
|
|
14
20
|
snitch init
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
You'll be
|
|
18
|
-
- **Server URL** - Your team's snitch server
|
|
19
|
-
- **API Key** - Team API key for authentication
|
|
20
|
-
- **Your name** - How you appear on the dashboard
|
|
23
|
+
You'll be asked for three things:
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
| Prompt | What to enter |
|
|
26
|
+
|--------|---------------|
|
|
27
|
+
| **Server URL** | `https://snitch.moshi.ai` |
|
|
28
|
+
| **API Key** | Ask James for the team key |
|
|
29
|
+
| **Your name** | Your name (this shows on the dashboard) |
|
|
30
|
+
|
|
31
|
+
### Step 3: Start the daemon
|
|
23
32
|
|
|
24
33
|
```bash
|
|
25
|
-
snitch start
|
|
26
|
-
snitch stop # Stop the daemon
|
|
27
|
-
snitch status # Check if daemon is running
|
|
28
|
-
snitch logs # View recent logs
|
|
29
|
-
snitch logs -f # Follow logs in real-time
|
|
34
|
+
snitch start
|
|
30
35
|
```
|
|
31
36
|
|
|
37
|
+
That's it! Your Claude Code activity will now appear on the team dashboard.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
| Command | What it does |
|
|
44
|
+
|---------|--------------|
|
|
45
|
+
| `snitch start` | Start tracking (runs in background) |
|
|
46
|
+
| `snitch stop` | Stop tracking |
|
|
47
|
+
| `snitch status` | Check if it's running |
|
|
48
|
+
| `snitch logs` | View recent logs |
|
|
49
|
+
| `snitch logs -f` | Follow logs live |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Troubleshooting
|
|
54
|
+
|
|
55
|
+
### "Is it working?"
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
snitch status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Should say "Daemon is running". If not, run `snitch start`.
|
|
62
|
+
|
|
63
|
+
### "I don't see my activity on the dashboard"
|
|
64
|
+
|
|
65
|
+
1. Make sure you're working in a moshi repo (moshi-api, moshi-frontend, etc.)
|
|
66
|
+
2. Check logs for errors: `snitch logs`
|
|
67
|
+
3. Verify your config: `cat ~/.snitch/config.json`
|
|
68
|
+
|
|
69
|
+
### "I want to reconfigure"
|
|
70
|
+
|
|
71
|
+
Just run `snitch init` again. Press Enter to keep existing values.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
32
75
|
## How It Works
|
|
33
76
|
|
|
34
|
-
1.
|
|
35
|
-
2.
|
|
36
|
-
3.
|
|
37
|
-
4. Server
|
|
77
|
+
1. Snitch watches `~/.claude/projects/` for Claude Code session files
|
|
78
|
+
2. When you use Claude Code in a moshi repo, it detects tool calls and messages
|
|
79
|
+
3. Activity is sent to the team server
|
|
80
|
+
4. Server uses Claude to categorize your work into tickets
|
|
81
|
+
5. Dashboard shows what everyone's working on
|
|
82
|
+
|
|
83
|
+
---
|
|
38
84
|
|
|
39
|
-
## Config
|
|
85
|
+
## Config File
|
|
40
86
|
|
|
41
|
-
Stored
|
|
87
|
+
Stored at `~/.snitch/config.json`:
|
|
42
88
|
|
|
43
89
|
```json
|
|
44
90
|
{
|
|
45
|
-
"serverUrl": "
|
|
91
|
+
"serverUrl": "https://snitch.moshi.ai",
|
|
46
92
|
"apiKey": "your-team-key",
|
|
47
|
-
"userName": "Your Name"
|
|
93
|
+
"userName": "Your Name",
|
|
94
|
+
"allowedRepos": ["moshi-api", "moshi-frontend", "moshi-e2e", "terraform", "snitch"]
|
|
48
95
|
}
|
|
49
96
|
```
|
|
50
97
|
|
|
98
|
+
You can edit `allowedRepos` to track additional repositories.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
51
102
|
## License
|
|
52
103
|
|
|
53
104
|
MIT
|
package/cli/collector.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, statSync, existsSync } from 'fs';
|
|
|
3
3
|
import { join, basename, dirname } from 'path';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
+
import { load } from './config.js';
|
|
6
7
|
|
|
7
8
|
const CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
8
9
|
|
|
@@ -16,8 +17,13 @@ export class ActivityCollector {
|
|
|
16
17
|
this.watcher = null;
|
|
17
18
|
this.filePositions = new Map(); // Track read position per file
|
|
18
19
|
this.activeThresholdMs = 5 * 60 * 1000; // 5 minutes = active
|
|
19
|
-
//
|
|
20
|
-
|
|
20
|
+
// Load allowed repos from config
|
|
21
|
+
const config = load();
|
|
22
|
+
this.allowedRepos = config.allowedRepos || [];
|
|
23
|
+
// Build regex pattern from allowed repos
|
|
24
|
+
this.repoPattern = this.allowedRepos.length > 0
|
|
25
|
+
? new RegExp(this.allowedRepos.map(r => r.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'))
|
|
26
|
+
: null;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
start() {
|
|
@@ -102,6 +108,7 @@ export class ActivityCollector {
|
|
|
102
108
|
events: [],
|
|
103
109
|
repo: null,
|
|
104
110
|
branch: null,
|
|
111
|
+
prUrl: null,
|
|
105
112
|
};
|
|
106
113
|
|
|
107
114
|
for (const event of events) {
|
|
@@ -117,6 +124,19 @@ export class ActivityCollector {
|
|
|
117
124
|
});
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
// Extract PR URLs from tool results
|
|
128
|
+
if (event.type === 'user' && Array.isArray(event.message?.content)) {
|
|
129
|
+
for (const block of event.message.content) {
|
|
130
|
+
if (block.type === 'tool_result' && typeof block.content === 'string') {
|
|
131
|
+
// Look for GitHub PR URLs in tool output
|
|
132
|
+
const prMatch = block.content.match(/https:\/\/github\.com\/[^\/]+\/[^\/]+\/pull\/\d+/);
|
|
133
|
+
if (prMatch) {
|
|
134
|
+
activity.prUrl = prMatch[0];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
// Extract assistant tool calls
|
|
121
141
|
if (event.type === 'assistant' && event.message?.content) {
|
|
122
142
|
for (const block of event.message.content) {
|
|
@@ -137,9 +157,9 @@ export class ActivityCollector {
|
|
|
137
157
|
}
|
|
138
158
|
// Detect cd into repos
|
|
139
159
|
const cdMatch = cmd.match(/cd\s+['"]*([^'"&|;\s]+)/);
|
|
140
|
-
if (cdMatch) {
|
|
160
|
+
if (cdMatch && this.repoPattern) {
|
|
141
161
|
const path = cdMatch[1];
|
|
142
|
-
const repoMatch = path.match(
|
|
162
|
+
const repoMatch = path.match(this.repoPattern);
|
|
143
163
|
if (repoMatch) {
|
|
144
164
|
activity.repo = repoMatch[0];
|
|
145
165
|
}
|
|
@@ -150,9 +170,11 @@ export class ActivityCollector {
|
|
|
150
170
|
// Extract repo hints from file operations
|
|
151
171
|
if (['Read', 'Edit', 'Write'].includes(block.name) && block.input?.file_path) {
|
|
152
172
|
const path = block.input.file_path;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
if (this.repoPattern) {
|
|
174
|
+
const repoMatch = path.match(this.repoPattern);
|
|
175
|
+
if (repoMatch) {
|
|
176
|
+
activity.repo = repoMatch[0];
|
|
177
|
+
}
|
|
156
178
|
}
|
|
157
179
|
toolEvent.hint = path;
|
|
158
180
|
}
|
|
@@ -166,8 +188,8 @@ export class ActivityCollector {
|
|
|
166
188
|
if (event.gitBranch) {
|
|
167
189
|
activity.branch = event.gitBranch;
|
|
168
190
|
}
|
|
169
|
-
if (event.cwd) {
|
|
170
|
-
const repoMatch = event.cwd.match(
|
|
191
|
+
if (event.cwd && this.repoPattern) {
|
|
192
|
+
const repoMatch = event.cwd.match(this.repoPattern);
|
|
171
193
|
if (repoMatch) {
|
|
172
194
|
activity.repo = repoMatch[0];
|
|
173
195
|
}
|
package/cli/config.js
CHANGED