Compare commits
136 Commits
ad2
...
26df2c47b9
| Author | SHA1 | Date | |
|---|---|---|---|
| 26df2c47b9 | |||
| b0db273e1e | |||
| a92d2d3f2c | |||
| 9694b4d521 | |||
| 4eb0d208f2 | |||
| 8944432941 | |||
| 245454b155 | |||
| a00f1b0c3e | |||
| acc6308352 | |||
| 5c59e7c57e | |||
| af31c3a60c | |||
| 94585fe426 | |||
| 0c136cd2ee | |||
| 98ba8bc060 | |||
| d37cc238d2 | |||
| 492fbbf4c9 | |||
| b28152a358 | |||
| f58f5c58b7 | |||
| 80c89a8599 | |||
| fd64877ba7 | |||
| 74a8fa5968 | |||
| 2088bd9f0d | |||
| 51f96e8802 | |||
| 96285e8693 | |||
| fd00f2d592 | |||
| 39fb617965 | |||
| 0fc1c5986e | |||
| 1cd25f6f41 | |||
| a3b9ab9f41 | |||
| a6180b8ebf | |||
| b8403305d7 | |||
| e226d2857e | |||
| c4fdb5a233 | |||
| c44a01f5dd | |||
| ed16744db0 | |||
| 1bac987009 | |||
| 41b7648133 | |||
| a8692a9074 | |||
| 17865c30fc | |||
| 002a3ff69b | |||
| dfcc3cefef | |||
| 8ec777245a | |||
| 6ca389135a | |||
| 9c820c16fa | |||
| cb300a193c | |||
| f732848c24 | |||
| 2b05bf6130 | |||
| afd5eb2a2c | |||
| 4bf151ca7b | |||
| 74890d51ec | |||
| a173c70633 | |||
| d2e375df8a | |||
| 6a135ac111 | |||
| 975adda092 | |||
| 7660cb4a16 | |||
| 5b8813af4d | |||
| c957ef33ef | |||
| 68153cf9b6 | |||
| 273342ee9f | |||
| a80ea236ba | |||
| 3358cecdcc | |||
| fe3b5b0382 | |||
| 0a7f3368a6 | |||
| 3eb621a8b7 | |||
| 4220b8f57c | |||
| 4886c8cc2a | |||
| 5a31946083 | |||
| 71c9ddce9e | |||
| e695743149 | |||
| 5995511011 | |||
| b99f8512e4 | |||
| 68d9836245 | |||
| dd8e45de80 | |||
| 32888ea9d4 | |||
| ac4ceb65c0 | |||
| 392c42710c | |||
| 046175af3a | |||
| 6bb00601b7 | |||
| 996dd515b1 | |||
| f190f7813f | |||
| a3fe1b9a9b | |||
| d13d4e4909 | |||
| 8d975c1b44 | |||
| 6eaba02b71 | |||
| f5acf9f453 | |||
| 8a094529ab | |||
| 6f6a77f8e4 | |||
| 100a491ac6 | |||
| a18157b5fa | |||
| 43c116f0c6 | |||
| ea48061389 | |||
| 232f463325 | |||
| d033dbe8a2 | |||
| 148ac75a25 | |||
| 2937c29f07 | |||
| fdd0bb0c1f | |||
| 5abf9ba670 | |||
| f01d9d5538 | |||
| 733d87f20e | |||
| eae9d7f644 | |||
| dd5c5afd4b | |||
| 72105233a2 | |||
| d0dbfed5ec | |||
| 04bdac0448 | |||
| 7326fbb05c | |||
| c9eba69753 | |||
| 7c467b0d2c | |||
| 178d580190 | |||
| 9a6d67fdc5 | |||
| 2e6d1a67dd | |||
| 9940faf34a | |||
| 9ab36352ae | |||
| 5169936cfc | |||
| a78fb96f95 | |||
| a32681321b | |||
| 45083f4735 | |||
| 499fd5d01a | |||
| a45f96ea19 | |||
| 0d46de672f | |||
| fcf4efefc9 | |||
| b6a2faa9a2 | |||
| e9c41f1fb4 | |||
| 6475ae26db | |||
| 53cadd0f97 | |||
| 459f6b36d5 | |||
| bff7d9dbbf | |||
| 6e4ebc2db9 | |||
| 3d363e481d | |||
| 3f53e167ab | |||
| 7485d8b230 | |||
| 4c08b0f700 | |||
| c73dcfd9a8 | |||
| af71d317b0 | |||
| a47a97219c | |||
| b26e185a80 | |||
| e34f51fe5d |
@@ -1,38 +0,0 @@
|
||||
# Agent Coordination Rules
|
||||
|
||||
**Purpose:** Reference for agents about their responsibilities and coordination patterns.
|
||||
**Main Claude behavioral rules are in CLAUDE.md - this file is for agent reference only.**
|
||||
|
||||
---
|
||||
|
||||
## Agent Responsibilities
|
||||
|
||||
| Agent | Authority | Examples |
|
||||
|-------|-----------|----------|
|
||||
| Database Agent | ALL data operations | Queries, inserts, updates, deletes, API calls |
|
||||
| Coding Agent | Production code | Python, PowerShell, Bash; new code and modifications |
|
||||
| Testing Agent | Test execution | pytest, validation scripts, performance tests |
|
||||
| Code Review Agent | Code quality (MANDATORY) | Security, standards, quality checks before commits |
|
||||
| Gitea Agent | Git/version control | Commits, pushes, branches, tags |
|
||||
| Backup Agent | Backup/restore | Create backups, restore data, verify integrity |
|
||||
|
||||
## Coordination Flow
|
||||
|
||||
```
|
||||
User request -> Main Claude (coordinator) -> Launches agent(s) -> Agent returns summary -> Main Claude presents to user
|
||||
```
|
||||
|
||||
- Main Claude NEVER queries databases, writes production code, runs tests, or commits directly
|
||||
- Agents return concise summaries, not raw data
|
||||
- Independent operations run in parallel
|
||||
- Use Sequential Thinking MCP for genuinely complex problems
|
||||
|
||||
## Skills vs Agents
|
||||
|
||||
- **Skills** (Skill tool): Specialized enhancements - frontend-design validation, design patterns
|
||||
- **Agents** (Task tool): Core operations - database, code, testing, git, backups
|
||||
- **Rule:** Skills enhance/validate. Agents execute/operate.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-02-17
|
||||
347
.claude/AUTO_CONTEXT_SYSTEM.md
Normal file
347
.claude/AUTO_CONTEXT_SYSTEM.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# Automatic Context Loading System
|
||||
|
||||
## The Problem
|
||||
|
||||
Claude instances don't proactively review previous work before starting. User must say "review previous work first" which defeats the purpose of the context recovery system.
|
||||
|
||||
**Example failure:**
|
||||
```
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
Claude: "What work have we done on this?" # WRONG - should check CONTEXT.md first
|
||||
```
|
||||
|
||||
## The Solution: Tiered Hint System
|
||||
|
||||
### Tier 1: Quick Hints (CONTEXT.md)
|
||||
- High-level overview (infrastructure, current state, anti-patterns)
|
||||
- Fast to read (~30 seconds)
|
||||
- Points to detailed resources
|
||||
- **Location:** [project-root]/CONTEXT.md
|
||||
|
||||
### Tier 2: Detailed Resources
|
||||
- Recent session logs (full commands, decisions)
|
||||
- Implementation plans, technical specs
|
||||
- Pointed to by CONTEXT.md
|
||||
|
||||
### Tier 3: Deep Archive
|
||||
- Historical session logs
|
||||
- Git history
|
||||
- Full technical documentation
|
||||
|
||||
## Automatic Loading Rules
|
||||
|
||||
### Rule 1: Project Mention Detection
|
||||
|
||||
**When user message contains project keywords, auto-load CONTEXT.md:**
|
||||
|
||||
| Keywords | Load | Read Before Responding |
|
||||
|----------|------|------------------------|
|
||||
| "GuruRMM", "tunnel", "agents", "rmm-api" | projects/msp-tools/guru-rmm/CONTEXT.md | Yes |
|
||||
| "Dataforth", "DFWDS", "testdatadb", "AD2", "VASLOG" | projects/dataforth-dos/CONTEXT.md | Yes |
|
||||
| "ClaudeTools API", "work tracking" | CONTEXT.md (root) | Yes |
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
import re
|
||||
|
||||
PROJECT_PATTERNS = {
|
||||
'gururmm': {
|
||||
'keywords': r'\b(GuruRMM|tunnel|rmm-api|gururmm|agent.*status)\b',
|
||||
'context_file': 'projects/msp-tools/guru-rmm/CONTEXT.md',
|
||||
},
|
||||
'dataforth': {
|
||||
'keywords': r'\b(Dataforth|DFWDS|testdatadb|AD2|AD1|VASLOG|SCMVAS|SCMHVAS)\b',
|
||||
'context_file': 'projects/dataforth-dos/CONTEXT.md',
|
||||
},
|
||||
'claudetools': {
|
||||
'keywords': r'\b(ClaudeTools API|work tracking|claudetools database)\b',
|
||||
'context_file': 'CONTEXT.md',
|
||||
},
|
||||
}
|
||||
|
||||
def detect_project(user_message):
|
||||
for project, config in PROJECT_PATTERNS.items():
|
||||
if re.search(config['keywords'], user_message, re.IGNORECASE):
|
||||
return config['context_file']
|
||||
return None
|
||||
```
|
||||
|
||||
### Rule 2: Continuation Context Detection
|
||||
|
||||
**When user says "continue", "let's work on", "back to", auto-check for active project:**
|
||||
|
||||
```
|
||||
User: "Let's continue working on the tunnel"
|
||||
Claude: [Detects "tunnel" → reads GuruRMM CONTEXT.md]
|
||||
"I see tunnel Phase 1 is complete (v0.6.0). Phase 2 is channel implementation..."
|
||||
```
|
||||
|
||||
### Rule 3: Uncertainty Threshold
|
||||
|
||||
**When Claude's certainty < 95%, auto-check hints:**
|
||||
|
||||
```python
|
||||
def should_check_hints(user_message, current_knowledge):
|
||||
"""Check if we should read CONTEXT.md before responding"""
|
||||
|
||||
# Rule 1: Project keyword mentioned
|
||||
if detect_project(user_message):
|
||||
return True
|
||||
|
||||
# Rule 2: Continuation words
|
||||
if re.search(r'\b(continue|back to|work on|finish|resume)\b', user_message, re.I):
|
||||
return True
|
||||
|
||||
# Rule 3: Infrastructure questions
|
||||
if re.search(r'\b(server|database|deploy|credentials|IP|password)\b', user_message, re.I):
|
||||
return True
|
||||
|
||||
# Rule 4: Reference to past work
|
||||
if re.search(r'\b(last time|previous|recent|we did|earlier)\b', user_message, re.I):
|
||||
return True
|
||||
|
||||
return False
|
||||
```
|
||||
|
||||
## CONTEXT.md Standard Format
|
||||
|
||||
**Every project CONTEXT.md must have these sections:**
|
||||
|
||||
### Required Sections
|
||||
1. **Quick Start - Infrastructure Overview** (table format)
|
||||
2. **Current State (READ THIS FIRST)** - Recent work, versions deployed
|
||||
3. **Anti-Patterns (DON'T DO THIS)** - Common mistakes to avoid
|
||||
4. **Where to Find Things** - File structure, key paths
|
||||
5. **Common Operations** - Copy-paste commands for frequent tasks
|
||||
6. **Recent Session Logs** - Links to latest work
|
||||
|
||||
### Optional Sections
|
||||
- Key Technical Decisions (ADRs)
|
||||
- Troubleshooting (FAQ format)
|
||||
- Roadmap
|
||||
- Quick Reference (API endpoints, log formats, etc.)
|
||||
|
||||
## Implementation in .claude/CLAUDE.md
|
||||
|
||||
**Add this section to CLAUDE.md:**
|
||||
|
||||
```markdown
|
||||
## Automatic Context Loading (MANDATORY)
|
||||
|
||||
**BEFORE responding to ANY user message, check these triggers:**
|
||||
|
||||
### Trigger 1: Project Keywords
|
||||
If user mentions GuruRMM, Dataforth, tunnel, VASLOG, AD2, etc:
|
||||
1. Identify project from keyword
|
||||
2. Read [project]/CONTEXT.md ENTIRELY
|
||||
3. Note current state, infrastructure, anti-patterns
|
||||
4. THEN respond with full context
|
||||
|
||||
### Trigger 2: Continuation Words
|
||||
If user says "continue", "let's work on", "back to", "resume":
|
||||
1. Check for project in message
|
||||
2. Read CONTEXT.md if found
|
||||
3. Check recent session logs mentioned in CONTEXT.md
|
||||
4. THEN proceed with work
|
||||
|
||||
### Trigger 3: Infrastructure Questions
|
||||
If user asks about servers, databases, credentials, deployment:
|
||||
1. Check root CONTEXT.md for project list
|
||||
2. Read relevant project CONTEXT.md
|
||||
3. Answer from CONTEXT.md (don't ask user)
|
||||
|
||||
### Trigger 4: Uncertainty
|
||||
If you're <95% certain about infrastructure, recent work, or next steps:
|
||||
1. Search for CONTEXT.md in current working directory
|
||||
2. Search for CONTEXT.md in projects/*/
|
||||
3. Read any found before asking user
|
||||
|
||||
**ANTI-PATTERN EXAMPLE:**
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
You: "I don't recall what we've done with Dataforth" # WRONG
|
||||
|
||||
**CORRECT PATTERN:**
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
You: [Detects "Dataforth" → reads projects/dataforth-dos/CONTEXT.md]
|
||||
"I see from CONTEXT.md that recent work (2026-04-12) extended the SCMVAS/SCMHVAS
|
||||
pipeline. DFWDS folders are at C:\Shares\testdatadb\ on AD2 (192.168.0.6)..."
|
||||
```
|
||||
|
||||
## Hook Integration
|
||||
|
||||
**Create .claude/hooks/pre-response:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Pre-response hook: Check for CONTEXT.md
|
||||
|
||||
USER_MSG="$1"
|
||||
|
||||
# Check for project keywords
|
||||
if echo "$USER_MSG" | grep -qi "GuruRMM\|tunnel\|rmm-api"; then
|
||||
echo "[HINT] Load projects/msp-tools/guru-rmm/CONTEXT.md"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$USER_MSG" | grep -qi "Dataforth\|DFWDS\|testdatadb\|AD2\|VASLOG"; then
|
||||
echo "[HINT] Load projects/dataforth-dos/CONTEXT.md"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for continuation words
|
||||
if echo "$USER_MSG" | grep -qi "continue\|back to\|work on\|resume"; then
|
||||
echo "[HINT] Check for active project CONTEXT.md"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for infrastructure questions
|
||||
if echo "$USER_MSG" | grep -qi "server\|database\|deploy\|credentials"; then
|
||||
echo "[HINT] Check CONTEXT.md in current directory or projects/*/"
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
## Session Start Protocol
|
||||
|
||||
**At the start of EVERY new session/conversation:**
|
||||
|
||||
1. Check current working directory for CONTEXT.md
|
||||
2. If found, read it BEFORE asking user anything
|
||||
3. If not found, check for projects/*/CONTEXT.md
|
||||
4. List available projects from found CONTEXT.md files
|
||||
5. Wait for user to specify what to work on
|
||||
6. When they do, load that project's CONTEXT.md
|
||||
|
||||
**Example session start:**
|
||||
|
||||
```
|
||||
[Claude starts, reads CONTEXT.md in /Users/azcomputerguru/ClaudeTools/]
|
||||
|
||||
Claude: "I've loaded context for ClaudeTools project. Available subprojects:
|
||||
- GuruRMM (tunnel Phase 2 pending)
|
||||
- Dataforth DOS (SCMVAS/SCMHVAS pipeline deployed)
|
||||
|
||||
What would you like to work on?"
|
||||
|
||||
User: "GuruRMM tunnel"
|
||||
|
||||
[Claude reads projects/msp-tools/guru-rmm/CONTEXT.md]
|
||||
|
||||
Claude: "Loaded GuruRMM context:
|
||||
- Server: 172.16.3.30:3001
|
||||
- Phase 1 complete (v0.6.0)
|
||||
- 2/6 agents online
|
||||
- Next: Channel implementation
|
||||
|
||||
Ready to proceed with Phase 2."
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### For User
|
||||
- ✅ Never repeat context ("What's the server IP?")
|
||||
- ✅ Claude starts work immediately with full context
|
||||
- ✅ No "let me check session logs" delays
|
||||
- ✅ Consistent infrastructure knowledge
|
||||
|
||||
### For Claude
|
||||
- ✅ Clear decision tree for when to load context
|
||||
- ✅ Structured hints (CONTEXT.md) are fast to read
|
||||
- ✅ Anti-patterns prevent repeated mistakes
|
||||
- ✅ Recent session log pointers for deep-dive
|
||||
|
||||
### For System
|
||||
- ✅ Scales to N projects (just add CONTEXT.md)
|
||||
- ✅ Works across machines (Git-synced)
|
||||
- ✅ Low overhead (only read when triggered)
|
||||
- ✅ Degrades gracefully (if CONTEXT.md missing, ask user)
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Phase 1: Create CONTEXT.md files (DONE ✅)
|
||||
- [x] Root: CONTEXT.md
|
||||
- [x] GuruRMM: projects/msp-tools/guru-rmm/CONTEXT.md
|
||||
- [x] Dataforth: projects/dataforth-dos/CONTEXT.md
|
||||
|
||||
### Phase 2: Update CLAUDE.md with auto-load rules
|
||||
```bash
|
||||
# Add "Automatic Context Loading" section to .claude/CLAUDE.md
|
||||
# Include triggers, anti-patterns, examples
|
||||
```
|
||||
|
||||
### Phase 3: Train via /refresh-directives
|
||||
```bash
|
||||
# Every time Claude starts session:
|
||||
# 1. Read CLAUDE.md (includes auto-load rules)
|
||||
# 2. Apply triggers to user's first message
|
||||
# 3. Load relevant CONTEXT.md before responding
|
||||
```
|
||||
|
||||
### Phase 4: Create hook (optional)
|
||||
```bash
|
||||
# .claude/hooks/pre-response
|
||||
# Automated hint detection, prints to stderr
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
**Test cases to verify system works:**
|
||||
|
||||
| User Message | Expected Behavior | Pass/Fail |
|
||||
|--------------|-------------------|-----------|
|
||||
| "Look at Dataforth DFWDS" | Reads dataforth-dos/CONTEXT.md, knows AD2 IP | ✅ |
|
||||
| "Continue GuruRMM tunnel" | Reads guru-rmm/CONTEXT.md, knows Phase 1 done | ✅ |
|
||||
| "What's the database password?" | Checks CONTEXT.md, finds vault command | ✅ |
|
||||
| "Deploy to AD2" | Reads dataforth/CONTEXT.md, knows deploy script | ✅ |
|
||||
| "Check agent status" | Reads guru-rmm/CONTEXT.md, knows API endpoint | ✅ |
|
||||
|
||||
## Example: Before and After
|
||||
|
||||
### BEFORE (Current Problem)
|
||||
```
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
|
||||
Claude: "I'm not sure what work we've done on Dataforth. Let me search session logs..."
|
||||
[Searches for 5 minutes]
|
||||
[Finds multiple sessions]
|
||||
"Can you tell me which server has the DFWDS folders?"
|
||||
|
||||
User: [Frustrated] "It's AD2 at 192.168.0.6, we just worked on this!"
|
||||
```
|
||||
|
||||
### AFTER (With Auto-Context)
|
||||
```
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
|
||||
Claude: [Detects "Dataforth" → reads projects/dataforth-dos/CONTEXT.md]
|
||||
"I see from CONTEXT.md that DFWDS folders are at C:\Shares\testdatadb\
|
||||
on AD2 (192.168.0.6). Recent work (2026-04-12) extended the SCMVAS/SCMHVAS
|
||||
pipeline. The testdatadb service is running on port 3000.
|
||||
|
||||
What would you like me to check in those folders?"
|
||||
|
||||
User: [Happy] "Perfect, let's verify the Engineering-Tested files imported correctly"
|
||||
```
|
||||
|
||||
## Rollout Plan
|
||||
|
||||
**Immediate (2026-04-14):**
|
||||
- [x] Create CONTEXT.md for 3 main projects
|
||||
- [ ] Update CLAUDE.md with auto-load rules
|
||||
- [ ] Test with /refresh-directives
|
||||
|
||||
**Short-term (1 week):**
|
||||
- [ ] Add CONTEXT.md for remaining projects
|
||||
- [ ] Create pre-response hook
|
||||
- [ ] Document in README
|
||||
|
||||
**Long-term (ongoing):**
|
||||
- [ ] Update CONTEXT.md after major sessions
|
||||
- [ ] Add new projects' CONTEXT.md as they start
|
||||
- [ ] Refine triggers based on user feedback
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status:** Phase 1 Complete (CONTEXT.md files created)
|
||||
**Next Step:** Update .claude/CLAUDE.md with automatic loading rules
|
||||
**Goal:** Claude is >95% certain before responding, never asks for context that's in CONTEXT.md
|
||||
@@ -1,246 +1,493 @@
|
||||
# ClaudeTools on AD2 (Dataforth Domain Controller)
|
||||
|
||||
## Identity
|
||||
|
||||
This is the AD2 workstation instance of ClaudeTools. This machine is a Windows Server on the Dataforth LAN (192.168.0.6). Your scope is Dataforth-only -- you do not need context about other clients.
|
||||
|
||||
## NO EMOJIS
|
||||
|
||||
Use ASCII markers: [OK], [ERROR], [WARNING], [SUCCESS], [INFO]
|
||||
|
||||
---
|
||||
|
||||
## Git & Sync
|
||||
|
||||
### Gitea Credentials (no 1Password on this machine)
|
||||
- URL: https://git.azcomputerguru.com
|
||||
- Username: mike@azcomputerguru.com
|
||||
- Password: Gptf*77ttb123!@#-git
|
||||
- URL-encoded password: Gptf%2A77ttb123%21%40%23-git
|
||||
- API Token: 9b1da4b79a38ef782268341d25a4b6880572063f
|
||||
- Remote: https://mike%40azcomputerguru.com:Gptf%2A77ttb123%21%40%23-git@git.azcomputerguru.com/azcomputerguru/claudetools.git
|
||||
|
||||
### Branch: ad2
|
||||
This machine operates on the `ad2` branch. The main workstation merges into main.
|
||||
|
||||
### /save behavior
|
||||
Save session logs to `session-logs/YYYY-MM-DD-session-ad2.md` (note the -ad2 suffix).
|
||||
After saving, commit and push to origin/ad2.
|
||||
|
||||
### /sync behavior
|
||||
```
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
git push origin ad2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dataforth Network
|
||||
|
||||
| Host | IP | Role | Notes |
|
||||
|------|-----|------|-------|
|
||||
| AD1 | 192.168.0.27 | Primary DC | Disk at 90%, C:\Engineering = 787 GB |
|
||||
| **AD2** | **192.168.0.6** | **This machine** | Secondary DC, TestDataDB, file shares |
|
||||
| D2TESTNAS | 192.168.0.9 | SMB1 proxy for DOS | Debian 13, Samba, SSH root/Paper123!@#-nas |
|
||||
| UDM | 192.168.0.254 | Gateway/Router | UniFi Dream Machine |
|
||||
| ESXi-122 | 192.168.0.122 | Hypervisor | ESXi |
|
||||
| ESXi-124 | 192.168.0.124 | Hypervisor | ESXi |
|
||||
| DOS stations | TS-01 to TS-30+ | Test stations | DOS 6.22, QuickBASIC ATE software |
|
||||
|
||||
### Credentials
|
||||
- AD Sysadmin: INTRANET\sysadmin / Paper123!@#
|
||||
- D2TESTNAS SSH: root@192.168.0.9 / Paper123!@#-nas
|
||||
- D2TESTNAS Samba: guest access (no password)
|
||||
- WINS/NPS: 192.168.0.27:1812/1813
|
||||
- M365 Tenant: 7dfa3ce8-c496-4b51-ab8d-bd3dcd78b584
|
||||
- Rsync daemon (NAS): port 873, module "test", user rsync / IQ203s32119
|
||||
|
||||
---
|
||||
|
||||
## Local Resources
|
||||
|
||||
| Resource | Path |
|
||||
|----------|------|
|
||||
| TestDataDB app | C:\Shares\testdatadb\ |
|
||||
| Test database | C:\Shares\testdatadb\database\testdata.db (SQLite, 2.2M+ records) |
|
||||
| TestDataDB API | http://localhost:3000 |
|
||||
| Parsers | C:\Shares\testdatadb\parsers\ (multiline.js, csvline.js, shtfile.js, spec-reader.js) |
|
||||
| Templates | C:\Shares\testdatadb\templates\datasheet-exact.js |
|
||||
| Import script | C:\Shares\testdatadb\database\import.js |
|
||||
| Export script | C:\Shares\testdatadb\database\export-datasheets.js |
|
||||
| Stage import | C:\Shares\testdatadb\import-all-stage.js |
|
||||
| NAS share | \\D2TESTNAS\test (mapped as T:) |
|
||||
| Datasheets share | X:\For_Web |
|
||||
| ProdSW (BAT files) | C:\Shares\test\COMMON\ProdSW\ |
|
||||
| Sync script | C:\Shares\test\scripts\Sync-FromNAS.ps1 (bidirectional, 15-min schedule) |
|
||||
|
||||
---
|
||||
|
||||
## DOS Update System - Batch Files
|
||||
|
||||
### Boot Sequence on DOS Machines
|
||||
```
|
||||
AUTOEXEC.BAT (v4.1)
|
||||
-> STARTNET.BAT (v2.0) -- init network, map T: and X: drives
|
||||
-> ATESYNC.BAT
|
||||
-> CTONW.BAT (v5.0) -- upload test data to network
|
||||
-> CTONWTXT.BAT (v2.3) -- upload C:\STAGE\*.TXT to T:\STAGE\%MACHINE%
|
||||
-> NWTOC.BAT (v5.0) -- download updates from network
|
||||
```
|
||||
|
||||
### Current Production Versions (on AD2 & NAS)
|
||||
| File | Version | Last Update | Purpose |
|
||||
|------|---------|-------------|---------|
|
||||
| AUTOEXEC.BAT | v4.1 | 2026-03-12 | Startup config |
|
||||
| STARTNET.BAT | v2.0 | 2026-01-20 | Network init |
|
||||
| NWTOC.BAT | v5.0 | 2026-03-16 | Download updates from network |
|
||||
| CTONW.BAT | v5.0 | 2026-03-28 | Upload test data (5 steps with echo) |
|
||||
| CTONWTXT.BAT | v2.3 | 2026-03-28 | Upload Stage TXT files (no MD, dirs pre-created) |
|
||||
| CHECKUPD.BAT | v1.3 | 2026-01-20 | Check for updates |
|
||||
| UPDATE.BAT | v2.3 | 2026-01-20 | Full system backup |
|
||||
| STAGE.BAT | v1.0 | Original | Stage system file updates |
|
||||
| DEPLOY.BAT | v1.0 | 2026-01-20 | One-time deployment installer |
|
||||
|
||||
### DOS 6.22 Compatibility Rules
|
||||
- NO `IF NOT` -- unreliable on DOS 6.22. Use positive `IF EXIST` with GOTO
|
||||
- NO `IF /I` (case-insensitive compare)
|
||||
- NO `FOR /F` loops
|
||||
- NO `%COMPUTERNAME%` -- use `%MACHINE%` (set during DEPLOY)
|
||||
- `XCOPY /D` requires date parameter (`/D:mm-dd-yy`)
|
||||
- `MD` fails with error on existing directories -- pre-create dirs server-side
|
||||
- `COPY` without `/Y` hangs on overwrite prompts
|
||||
- All paths UPPERCASE for Samba compatibility
|
||||
- Line endings MUST be CRLF (0D 0A)
|
||||
|
||||
---
|
||||
|
||||
## Serial Number Encoding (DOS 8.3 filenames)
|
||||
|
||||
QuickBASIC ATE encodes long serial numbers for 8.3 filenames:
|
||||
```
|
||||
First 2 digits replaced with hex letter if serial too long:
|
||||
178236-12 -> H8236-12.TXT (17 -> H, charCode 72 - 55 = 17)
|
||||
10819-1 -> A819-1.TXT (10 -> A, charCode 65 - 55 = 10)
|
||||
|
||||
Decode: letter.charCodeAt(0) - 55 = numeric prefix
|
||||
Only applies when filename starts with [A-Z] followed by digits.
|
||||
|
||||
H-prefix files have decoded SN inside the file (SN: 178236-12)
|
||||
A-prefix files have encoded SN inside the file (SN: A819-1) -- must decode to 10819-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Datasheet Pipeline
|
||||
|
||||
### 5-Stage Architecture
|
||||
1. **DOS Test Programs** -> Write DAT files to C:\ATE\*LOG\ and TXT to C:\STAGE\
|
||||
2. **Boot Upload** -> CTONW.BAT copies DAT to T:\%MACHINE%\LOGS\, CTONWTXT copies TXT to T:\STAGE\%MACHINE%
|
||||
3. **NAS <-> AD2 Sync** -> Rsync every 15 min (Sync-FromNAS.ps1 scheduled task)
|
||||
4. **TestDataDB Import** -> import.js parses DAT into SQLite; export-datasheets.js generates TXT to X:\For_Web
|
||||
5. **Web Share** -> X:\For_Web\ holds validated datasheets (501K+ files)
|
||||
|
||||
### import-all-stage.js (ready to run)
|
||||
Located at `C:\Shares\testdatadb\import-all-stage.js`. Processes ~8,100 TXT files:
|
||||
- Scans \\D2TESTNAS\test\STAGE\TS-*\*.TXT
|
||||
- Decodes hex-prefix serial numbers
|
||||
- Cross-references testdata.db by (serial_number, model_number)
|
||||
- Inserts missing records as log_type='SHT'
|
||||
- Copies to X:\For_Web\{decoded_serial}.TXT
|
||||
|
||||
```
|
||||
cd C:\Shares\testdatadb
|
||||
node import-all-stage.js
|
||||
```
|
||||
|
||||
### Machine data volumes in STAGE
|
||||
| Machine | Files |
|
||||
|---------|-------|
|
||||
| TS-4L | 3,082 |
|
||||
| TS-4R | 2,741 |
|
||||
| TS-1R | 509 |
|
||||
| TS-8R | 478 |
|
||||
| TS-3R | 435 |
|
||||
| TS-11R | 325 |
|
||||
| TS-8L | 285 |
|
||||
| TS-11L | 248 |
|
||||
| TS-27 | 10 (already imported) |
|
||||
| TS-1L | 1 |
|
||||
|
||||
### Web Share Layout (X:\)
|
||||
- X:\For_Web -- Validated datasheets (production)
|
||||
- X:\For_Web_PDF -- PDF versions (4.7K files)
|
||||
- X:\Test_Datasheets -- Incoming/staging
|
||||
- X:\Bad_Datasheets -- Invalid files (18K)
|
||||
- X:\Datasheets_Log -- Processing logs
|
||||
|
||||
---
|
||||
|
||||
## Known Issues & Pending Work
|
||||
|
||||
### HIGH PRIORITY
|
||||
1. **Run import-all-stage.js** -- 8,100 TXT files need cross-referencing and ingestion
|
||||
2. **Website Upload Replacement** -- Old ASP.NET endpoints (Uploader.aspx) return 404. Need new approach.
|
||||
3. **7B Series Datasheets** -- ~830K records can't generate datasheets (missing 7BMAIN.DAT spec file). Check ENGR share.
|
||||
4. **Service Permissions** -- testdatadb runs as SYSTEM, causing file permission issues. Change to INTRANET\sysadmin.
|
||||
|
||||
### MEDIUM PRIORITY
|
||||
5. **C2 IP Blocking** -- iptables rules added to UDM for 80.76.49.18 and 45.88.91.99. Need permanent rules in UniFi UI.
|
||||
6. **MFA Enforcement** -- 19/38 users ready. Report-only until April 4, 2026. Monitor registration.
|
||||
7. **Joel Lohr Account** -- Retiring March 31. Disable account post-retirement. Auto-reply set to Dan Center.
|
||||
|
||||
---
|
||||
|
||||
## Security Incident (2026-03-27)
|
||||
|
||||
**DF-JOEL2 (192.168.0.143) compromised via phishing:**
|
||||
- Joel Lohr clicked phishing link in personal Yahoo email
|
||||
- ScreenConnect C2 installed, "Angel Raya" connected remotely
|
||||
- Two C2 backdoors deployed via PowerShell
|
||||
- C2 IPs: 80.76.49.18, 45.88.91.99 (AS399486, suspended by host)
|
||||
- IC3 Complaint: 1c32ade367084be9acd548f23705736f
|
||||
- ConnectWise Case: 03464184
|
||||
- **Remediation complete:** IPs blocked, 3 rogue clients removed, password reset, sessions revoked
|
||||
- **No lateral movement detected** (32 machines scanned clean)
|
||||
|
||||
---
|
||||
|
||||
## Key Contacts
|
||||
|
||||
| Person | Email | Role |
|
||||
|--------|-------|------|
|
||||
| John Lehman | jlehman@dataforth.com | Engineering, QB code, test specs |
|
||||
| Dan Center | dcenter@dataforth.com | Operations (replacing Joel) |
|
||||
| Peter Iliya | pIliya@dataforth.com | Applications Engineer |
|
||||
| AJ | dataforthgit@... | Engineering contact |
|
||||
| Ken Hoffman | (unresponsive) | TestDataSheetUploader author |
|
||||
| Georg Haubner | ghaubner@dataforth.com | Has pre-crypto backup on D: drive |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```powershell
|
||||
# Check BAT files on NAS
|
||||
ssh root@192.168.0.9 'ls -la /data/test/COMMON/ProdSW/'
|
||||
|
||||
# Trigger NAS sync
|
||||
Start-ScheduledTask -TaskName 'Sync-FromNAS'
|
||||
|
||||
# Check sync log
|
||||
Get-Content 'C:\Shares\test\scripts\sync-from-nas.log' -Tail 20
|
||||
|
||||
# Check TestDataDB health
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Query test records
|
||||
node -e "const db=require('better-sqlite3')('C:\\Shares\\testdatadb\\database\\testdata.db',{readonly:true});console.log(db.prepare('SELECT COUNT(*) as cnt FROM test_records').get())"
|
||||
|
||||
# Check Stage files on NAS
|
||||
ssh root@192.168.0.9 'find /data/test/STAGE -name "*.TXT" | wc -l'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-29
|
||||
# ClaudeTools Project Context
|
||||
|
||||
## Multi-User Environment (CHECK FIRST)
|
||||
|
||||
This repo is shared across multiple team members. **At every session start, BEFORE doing anything else:**
|
||||
|
||||
1. **Read `.claude/identity.json`** (local, gitignored). If it exists, greet the user by name and proceed.
|
||||
2. **If identity.json does NOT exist** (first sync on a new machine):
|
||||
- Read `.claude/users.json` for the known user list
|
||||
- Ask: "This looks like a new machine. Are you **Mike Swanson** or **Howard Enos**? (Or someone new?)"
|
||||
- Based on their answer, create `.claude/identity.json`:
|
||||
```json
|
||||
{
|
||||
"user": "mike",
|
||||
"full_name": "Mike Swanson",
|
||||
"email": "mike@azcomputerguru.com",
|
||||
"role": "admin",
|
||||
"machine": "<HOSTNAME>"
|
||||
}
|
||||
```
|
||||
- Also set local git config for this repo:
|
||||
```bash
|
||||
git config user.name "<full_name>"
|
||||
git config user.email "<email>"
|
||||
```
|
||||
- Set git remote to use the user's own Gitea account (read `gitea_username` from users.json):
|
||||
```bash
|
||||
git remote set-url origin https://<gitea_username>@git.azcomputerguru.com/azcomputerguru/claudetools.git
|
||||
```
|
||||
- Add the machine hostname to the user's `known_machines` list in `.claude/users.json` and commit.
|
||||
- **IMPORTANT: Show the user `.claude/ONBOARDING.md`** — present it section by section, explain what each part does and WHY, answer any questions. This is their orientation to the system.
|
||||
3. **If the hostname doesn't match any known machine** for the identified user, update their `known_machines` in users.json.
|
||||
|
||||
### Session log attribution
|
||||
|
||||
Every session log MUST include a `## User` section at the top:
|
||||
```markdown
|
||||
## User
|
||||
- **User:** Mike Swanson (mike)
|
||||
- **Machine:** DESKTOP-0O8A1RL
|
||||
- **Role:** admin
|
||||
```
|
||||
|
||||
### Git commit attribution
|
||||
|
||||
Commits use the local git config (user.name / user.email), which is set per-user during identity setup. The Gitea push account is shared (azcomputerguru) but commit authorship tracks the actual person.
|
||||
|
||||
### Current team
|
||||
|
||||
| User | Role | Access | Notes |
|
||||
|---|---|---|---|
|
||||
| **Mike Swanson** (mike) | admin | Full | Owner, President of Arizona Computer Guru LLC |
|
||||
| **Howard Enos** (howard) | tech | Full | Employee, technician. Full trust — same access as admin for all MSP tracking and daily work. |
|
||||
|
||||
Both users have identical access. No permission gating between them. If a new team member is added later, their role and access scope should be defined in `.claude/users.json` before they sync.
|
||||
|
||||
## Work Mode (auto-detect + color)
|
||||
|
||||
Claude operates in one of five modes. Mode determines terminal color and operational posture. **Auto-detect on every user message** using these priority rules (first match wins):
|
||||
|
||||
1. **remediation** (purple) — "remediation tool", "365", "breach", "tenant sweep", M365 keywords
|
||||
2. **client** (orange) — client name mentioned (check `clients/` dirs), work under `clients/`, "for \<client\>"
|
||||
3. **infra** (red) — server names/IPs (AD2, Jupiter, 172.16.x.x), SSH, firewall, DNS, deploy, service restart
|
||||
4. **dev** (cyan) — code, build, compile, Rust/cargo, npm, GuruRMM dev, testing, work under `projects/`
|
||||
5. **general** (blue) — default
|
||||
|
||||
**On mode change:** update `identity.json` "mode" field, change terminal color, announce briefly: `[MODE -> infra]`. Don't interrupt workflow.
|
||||
|
||||
**Manual override:** user can run `/mode <name>` to force a mode. `/mode auto` re-runs detection. `/mode` shows current.
|
||||
|
||||
**Posture by mode:**
|
||||
- **client (orange):** careful with data, session logs go to `clients/`, always name the client
|
||||
- **dev (cyan):** delegate freely to Coding/Testing agents, less confirmation friction
|
||||
- **infra (red):** confirm before destructive ops, backup-first, double-check IPs
|
||||
- **general (blue):** lightweight, default
|
||||
- **remediation (purple):** Graph API focus, compliance language, full audit trail
|
||||
|
||||
Full details: `.claude/commands/mode.md`
|
||||
|
||||
---
|
||||
|
||||
## Identity: You Are a Coordinator
|
||||
|
||||
You are NOT an executor. You coordinate specialized agents and preserve your context window.
|
||||
|
||||
**Delegate ALL significant work:**
|
||||
|
||||
| Operation | Delegate To |
|
||||
|-----------|------------|
|
||||
| Database queries/inserts/updates | Database Agent |
|
||||
| Production code generation | Coding Agent |
|
||||
| Code review (MANDATORY after changes) | Code Review Agent |
|
||||
| Test execution | Testing Agent |
|
||||
| Git commits/push/branch | Gitea Agent |
|
||||
| Backups/restore | Backup Agent |
|
||||
| File exploration (broad) | Explore Agent |
|
||||
| Semantic code search | deep-explore Agent (uses GrepAI) |
|
||||
| Complex reasoning | General-purpose + Sequential Thinking |
|
||||
|
||||
**Do yourself:** Simple responses, reading 1-2 files, presenting results, planning, decisions.
|
||||
**Rule:** >500 tokens of work = delegate. Code or database = ALWAYS delegate.
|
||||
|
||||
**DO NOT** query databases directly (no SSH/mysql/curl to API). **DO NOT** write production code. **DO NOT** run tests. **DO NOT** commit/push. Use the appropriate agent.
|
||||
|
||||
### Model Routing (Complexity-Based)
|
||||
|
||||
Before spawning an agent, pick a tier:
|
||||
|
||||
| Tier | Model | When |
|
||||
|------|-------|------|
|
||||
| 0 | **Ollama** (local) | Low-stakes: summarize, classify, extract, draft, format — no code changes, output reviewed before use |
|
||||
| 1 | `haiku` | Ollama unavailable, or task needs tool use / file access an agent provides |
|
||||
| 2 | (inherit) | Standard code, DB, tests, git — most work |
|
||||
| 3 | `opus` | Architecture, security, ambiguous failures, production risk |
|
||||
|
||||
**Tier 0 rule:** Always try Ollama first for low-stakes work. It's free, fast, and private. Use `qwen3:14b` for general tasks; `codestral:22b` for code suggestions. Fall back to Haiku only if Ollama is unreachable or the task requires agent tool use.
|
||||
|
||||
**Bump rule:** if the request involves `security`, `auth`, `credential`, `migration`, `production`, or `data loss` — bump one tier up (Ollama → Haiku, Haiku → inherit, inherit → opus).
|
||||
|
||||
Pass `model: "haiku"` or `model: "opus"` explicitly. Omit for Tier 2 (inherits session model). Tier 0 is a direct Bash call, not an agent spawn — see Ollama section below.
|
||||
|
||||
### Coordination Flow
|
||||
|
||||
```
|
||||
User request -> Main Claude (coordinator) -> Launches agent(s) -> Agent returns summary -> Main Claude presents to user
|
||||
```
|
||||
|
||||
- Independent operations run in parallel
|
||||
- Skills (Skill tool) enhance/validate. Agents (Agent tool) execute/operate.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Context Loading (CRITICAL)
|
||||
|
||||
**BEFORE responding to user's first message or when switching projects, AUTOMATICALLY load context:**
|
||||
|
||||
### Trigger 1: Project Keywords Detected
|
||||
If user mentions **GuruRMM**, **Dataforth**, **tunnel**, **VASLOG**, **AD2**, **testdatadb**, etc:
|
||||
1. **Immediately read** the matching project CONTEXT.md:
|
||||
- GuruRMM keywords → `projects/msp-tools/guru-rmm/CONTEXT.md`
|
||||
- Dataforth keywords → `projects/dataforth-dos/CONTEXT.md`
|
||||
- General → `CONTEXT.md` (root)
|
||||
2. **If a `PROJECT_STATE.md` exists alongside the CONTEXT.md, read it immediately after.** This file tracks live inter-session state: active locks (what other Claude instances are touching), architecture decisions that are locked, recent changes since the last session, and pending work. Check it for blocking conflicts before starting any task.
|
||||
3. Read ENTIRE file (infrastructure, current state, anti-patterns)
|
||||
3. Note recent session logs mentioned in CONTEXT.md
|
||||
4. THEN respond with full context
|
||||
|
||||
### Trigger 2: Continuation/Resume Words
|
||||
If user says "continue", "let's work on", "back to", "resume", "finish":
|
||||
1. Detect project from message
|
||||
2. Read project CONTEXT.md if found
|
||||
3. Check "Current State" and "Recent Session Logs" sections
|
||||
4. Proceed without asking for context
|
||||
|
||||
### Trigger 3: Infrastructure/Deployment Questions
|
||||
If user asks about **servers**, **databases**, **credentials**, **deploy**, **IP**, **password**:
|
||||
1. Check current directory for CONTEXT.md
|
||||
2. If not found, check projects/*/CONTEXT.md
|
||||
3. Answer from CONTEXT.md (never ask user for info that's in CONTEXT.md)
|
||||
|
||||
### Trigger 4: Uncertainty >5%
|
||||
If you're <95% certain about infrastructure, recent work, or next steps:
|
||||
1. Search for CONTEXT.md in working directory
|
||||
2. Search for CONTEXT.md in projects/*/
|
||||
3. Read before asking user
|
||||
|
||||
### ANTI-PATTERN Examples (NEVER DO THIS):
|
||||
|
||||
❌ **Wrong:**
|
||||
```
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
You: "I don't recall what we've done with Dataforth. Let me search session logs..."
|
||||
```
|
||||
|
||||
✅ **Correct:**
|
||||
```
|
||||
User: "Look at the Dataforth DFWDS folders"
|
||||
You: [Detects "Dataforth" → reads projects/dataforth-dos/CONTEXT.md in <3 seconds]
|
||||
"I see from CONTEXT.md that DFWDS is at C:\Shares\testdatadb\ on AD2 (192.168.0.6).
|
||||
Recent work (2026-04-12) extended SCMVAS/SCMHVAS pipeline. Service is testdatadb on port 3000.
|
||||
What would you like me to check?"
|
||||
```
|
||||
|
||||
❌ **Wrong:**
|
||||
```
|
||||
User: "Continue working on GuruRMM tunnel"
|
||||
You: "What phase are we on? Which server is this deployed to?"
|
||||
```
|
||||
|
||||
✅ **Correct:**
|
||||
```
|
||||
User: "Continue working on GuruRMM tunnel"
|
||||
You: [Reads projects/msp-tools/guru-rmm/CONTEXT.md]
|
||||
"Tunnel Phase 1 is complete (v0.6.0, deployed to 172.16.3.30:3001).
|
||||
Phase 2 is channel implementation (Terminal, File, Registry, Service).
|
||||
2/6 agents online. Ready to proceed."
|
||||
```
|
||||
|
||||
### Session Start Protocol
|
||||
|
||||
At session start:
|
||||
1. Check for CONTEXT.md in current working directory
|
||||
2. If found, read it silently (don't announce to user)
|
||||
3. Be ready to answer questions about any project listed
|
||||
4. When user specifies project, load that project's CONTEXT.md automatically
|
||||
|
||||
### Benefits
|
||||
|
||||
- ✅ Never ask "What's the server IP?" (it's in CONTEXT.md)
|
||||
- ✅ Never ask "What did we do last time?" (recent logs in CONTEXT.md)
|
||||
- ✅ Never ask "Where's the database?" (infrastructure table in CONTEXT.md)
|
||||
- ✅ Start work immediately with full context
|
||||
- ✅ Follow anti-patterns automatically (CONTEXT.md lists common mistakes)
|
||||
|
||||
**See:** `.claude/AUTO_CONTEXT_SYSTEM.md` for full implementation details
|
||||
|
||||
---
|
||||
|
||||
## PROJECT_STATE.md Action Protocol (MANDATORY for any project that has this file)
|
||||
|
||||
This protocol prevents conflicts between concurrent Claude sessions working on the same project. It is **not optional** — follow it for every significant action.
|
||||
|
||||
### What counts as an action
|
||||
|
||||
Any of the following requires the full read → lock → update → release cycle:
|
||||
- Editing or creating source code files
|
||||
- Git commit or push
|
||||
- SSH command that modifies a server (deploy, install, config change, service restart)
|
||||
- Database schema change or data migration
|
||||
- Build pipeline modification
|
||||
- Any operation listed in the project's Component Ownership Map
|
||||
|
||||
Reading files, planning, and answering questions do NOT require a lock.
|
||||
|
||||
### The protocol
|
||||
|
||||
**Step 1 — Read before acting**
|
||||
Before starting any action, re-read PROJECT_STATE.md:
|
||||
- Check Active Session Locks: is anything locked that you need to touch?
|
||||
- If there is a conflicting lock that is NOT stale (< 2 hours old): stop, report the conflict to the user, ask how to proceed.
|
||||
- If locks are stale (> 2 hours, last updated timestamp): note it to the user, clear the stale row, proceed.
|
||||
|
||||
**Step 2 — Claim your lock**
|
||||
Immediately before performing the action, add a row to the Active Session Locks table:
|
||||
|
||||
| Session | Working On | Status | Blocks | Started |
|
||||
|---------|-----------|--------|--------|---------|
|
||||
| DESKTOP-0O8A1RL/Claude | Brief description | IN_PROGRESS | What others must avoid | HH:MM UTC |
|
||||
|
||||
Use `{machine}/{Claude or agent description}` as Session identifier.
|
||||
Use the machine name from identity.json.
|
||||
|
||||
**Step 3 — Perform the action**
|
||||
|
||||
**Step 4 — Update on completion OR failure**
|
||||
Immediately after the action finishes (regardless of outcome):
|
||||
1. Remove your lock row from Active Session Locks
|
||||
2. Add an entry to Recent Changes:
|
||||
- Status: `COMPLETE`, `FAILED`, `PARTIAL`, or `ROLLED_BACK`
|
||||
- If FAILED or PARTIAL: describe what state things were left in
|
||||
3. Update the Current Project State table if any component status changed
|
||||
4. Check off completed items in Pending / Next Up if applicable
|
||||
|
||||
### Anti-patterns (never do these)
|
||||
|
||||
❌ Starting work without reading PROJECT_STATE.md first
|
||||
❌ Forgetting to claim a lock before touching a component
|
||||
❌ Leaving a lock row in place after finishing (even on failure)
|
||||
❌ Skipping the Recent Changes entry when work completes
|
||||
❌ Letting a lock go stale — update the timestamp if a task takes longer than expected
|
||||
|
||||
### Stale lock rule
|
||||
|
||||
A lock older than 2 hours with no timestamp update is considered abandoned. Any session may clear it and claim the component, but must note in Recent Changes: `[Cleared stale lock from {session}]` before proceeding.
|
||||
|
||||
### Example — correct flow
|
||||
|
||||
```
|
||||
[Reading PROJECT_STATE.md — no active locks on server/src/]
|
||||
[Updating PROJECT_STATE.md — adding lock: "DESKTOP-0O8A1RL/Claude | POST /api/enroll endpoint | IN_PROGRESS | server/src/, migrations/ | 14:32 UTC"]
|
||||
[... writing code, running migration ...]
|
||||
[Updating PROJECT_STATE.md — removing lock, adding Recent Changes: "POST /api/enroll implemented | COMPLETE", updating component status: "Enrollment endpoint | IMPLEMENTED"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projects
|
||||
|
||||
**ClaudeTools** -- MSP Work Tracking System (Production-Ready)
|
||||
- Database: MariaDB 10.6.22 @ 172.16.3.30:3306 | API: http://172.16.3.30:8001
|
||||
- 95+ endpoints, 38 tables, JWT auth, AES-256-GCM encryption
|
||||
- DB creds in vault: `bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password`
|
||||
|
||||
**GuruRMM** -- Remote Monitoring & Management (Active Development)
|
||||
- Server: Rust/Axum @ 172.16.3.30:3001 | Dashboard: https://rmm.azcomputerguru.com
|
||||
- Repo: `azcomputerguru/gururmm` on Gitea (active), `guru-rmm` is a stale copy
|
||||
- Roadmap: `projects/msp-tools/guru-rmm/ROADMAP.md`
|
||||
|
||||
---
|
||||
|
||||
## Key Rules
|
||||
|
||||
- **NO EMOJIS** - Use ASCII markers: `[OK]`, `[ERROR]`, `[WARNING]`, `[SUCCESS]`, `[INFO]`
|
||||
- **No hardcoded credentials** - Use SOPS vault (`vault get-field <path> <field>`) or 1Password as fallback
|
||||
- **SSH:** Use system OpenSSH (on Windows: `C:\Windows\System32\OpenSSH\ssh.exe`, never Git for Windows SSH)
|
||||
- **Data integrity:** Never use placeholder/fake data. Check SOPS vault, credentials.md, or ask user.
|
||||
- **Coding standards:** `.claude/CODING_GUIDELINES.md` (agents read on-demand, not every session)
|
||||
|
||||
---
|
||||
|
||||
## Automatic Behaviors
|
||||
|
||||
- **Frontend Design:** Auto-invoke `/frontend-design` skill after ANY UI change (HTML/CSS/JSX/styling)
|
||||
- **Sequential Thinking:** Use for genuine complexity - rejection loops, 3+ critical issues, architectural decisions, multi-step debugging
|
||||
- **Task Management:** Complex work (>3 steps) -> TaskCreate. Persist to `.claude/active-tasks.json`.
|
||||
|
||||
---
|
||||
|
||||
## Context Recovery
|
||||
|
||||
When user references previous work, use `/context` command. Never ask user for info in:
|
||||
- `credentials.md` - Infrastructure reference (being migrated to SOPS vault at D:\vault)
|
||||
- `session-logs/` - Daily work logs (also in `projects/*/session-logs/` and `clients/*/session-logs/`)
|
||||
- `SESSION_STATE.md` - Project history
|
||||
|
||||
### Credential Access (SOPS Vault - Primary)
|
||||
|
||||
Credentials are stored in SOPS+age encrypted YAML files in a dedicated Gitea repo.
|
||||
|
||||
**Vault repo:** `D:\vault` (git.azcomputerguru.com/azcomputerguru/vault, private)
|
||||
**Structure:** infrastructure/, clients/, services/, projects/, msp-tools/
|
||||
|
||||
**To read credentials:**
|
||||
```bash
|
||||
bash D:/vault/scripts/vault.sh search "keyword" # Search (no decryption needed)
|
||||
bash D:/vault/scripts/vault.sh get-field <path> <field> # Get specific field
|
||||
bash D:/vault/scripts/vault.sh get <path> # Decrypt full entry
|
||||
bash D:/vault/scripts/vault.sh list # List all entries
|
||||
```
|
||||
|
||||
**Encryption:** AES-256 via age. Metadata stays plaintext for searchability.
|
||||
|
||||
**age key location:** `%APPDATA%\sops\age\keys.txt` (Windows) / `~/.config/sops/age/keys.txt` (Linux/Mac)
|
||||
|
||||
### 1Password (Fallback)
|
||||
|
||||
Service account token in vault: `infrastructure/1password-service-account.sops.yaml`
|
||||
|
||||
---
|
||||
|
||||
## Commands & Skills
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/checkpoint` | Dual checkpoint: git commit + database context |
|
||||
| `/save` | Comprehensive session log (credentials, decisions, changes) |
|
||||
| `/context` | Search session logs, credentials.md, and 1Password |
|
||||
| `/1password` | 1Password secrets management integration |
|
||||
| `/sync` | Sync config from Gitea repository |
|
||||
| `/create-spec` | Create app specification for AutoCoder |
|
||||
| `/frontend-design` | Modern frontend design patterns (auto-invoke after UI changes) |
|
||||
| `/remediation-tool` | M365 breach checks, tenant sweeps, gated remediation via Claude-MSP-Access Graph API app |
|
||||
|
||||
---
|
||||
|
||||
## File Placement (Quick Rules)
|
||||
|
||||
- **Dataforth DOS work** -> `projects/dataforth-dos/`
|
||||
- **ClaudeTools API code** -> `api/`, `migrations/` (existing structure)
|
||||
- **GuruRMM work** -> `projects/msp-tools/guru-rmm/`
|
||||
- **Client work** -> `clients/[client-name]/`
|
||||
- **Session logs** -> project or client `session-logs/` subfolder; general -> root `session-logs/`
|
||||
- **Full guide:** `.claude/FILE_PLACEMENT_GUIDE.md` (read when saving files, not every session)
|
||||
|
||||
---
|
||||
|
||||
## Local AI (Ollama)
|
||||
|
||||
Ollama runs on Mike's workstation (DESKTOP-0O8A1RL) with GPU acceleration. Available to all team members via Tailscale.
|
||||
|
||||
| Model | Size | Use For |
|
||||
|-------|------|---------|
|
||||
| `qwen3:14b` | 9.3 GB | Summarization, classification, data extraction, drafting |
|
||||
| `codestral:22b` | 12 GB | Code generation, refactoring suggestions, docstrings |
|
||||
| `nomic-embed-text` | 274 MB | Embeddings only (used by GrepAI) |
|
||||
|
||||
### How to connect
|
||||
|
||||
**On Mike's workstation (local):**
|
||||
```bash
|
||||
curl -s http://localhost:11434/api/generate -d '{"model":"qwen3:14b","prompt":"...","stream":false}' | jq -r '.response'
|
||||
```
|
||||
|
||||
**On any other machine via Tailscale:**
|
||||
```bash
|
||||
curl -s http://100.92.127.64:11434/api/generate -d '{"model":"qwen3:14b","prompt":"...","stream":false}' | jq -r '.response'
|
||||
```
|
||||
|
||||
### Per-machine setup
|
||||
|
||||
Read `.claude/identity.json` to determine which machine you're on:
|
||||
- **DESKTOP-0O8A1RL** (Mike's workstation): Ollama runs locally. Use `localhost:11434`.
|
||||
- **Any other machine** (Howard's laptop, other workstations): Ollama is remote via Tailscale. Use `100.92.127.64:11434`. Requires Tailscale to be connected.
|
||||
|
||||
**To check if Ollama is reachable:**
|
||||
```bash
|
||||
curl -s http://100.92.127.64:11434/api/tags | python -c "import sys,json; [print(m['name']) for m in json.load(sys.stdin).get('models',[])]"
|
||||
```
|
||||
|
||||
If it fails: verify Tailscale is connected (`tailscale status`), and that Mike's workstation is online.
|
||||
|
||||
### Access control
|
||||
|
||||
- Firewall rule on Mike's workstation allows port 11434 ONLY from Tailscale subnet (100.0.0.0/8)
|
||||
- NOT exposed to LAN, VPN, or internet
|
||||
- Binding: `OLLAMA_HOST=0.0.0.0:11434` (all interfaces, firewall restricts)
|
||||
|
||||
### Delegation pattern (Tier 0 — use instead of spawning a Haiku agent)
|
||||
|
||||
Determine the endpoint from identity.json, then call directly with the Bash tool:
|
||||
|
||||
```bash
|
||||
# Resolve endpoint once per session
|
||||
OLLAMA=$([ "$(jq -r .machine ~/.claude/identity.json 2>/dev/null)" = "DESKTOP-0O8A1RL" ] \
|
||||
&& echo "http://localhost:11434" || echo "http://100.92.127.64:11434")
|
||||
|
||||
# General task (summarize, classify, extract, draft)
|
||||
curl -s "$OLLAMA/api/generate" \
|
||||
-d "{\"model\":\"qwen3:14b\",\"prompt\":\"$(echo "$PROMPT" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'| tr -d '\"')\",\"stream\":false}" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin).get('response',''))"
|
||||
|
||||
# Code suggestion (refactor ideas, docstrings — NOT production code)
|
||||
# Same call, model: "codestral:22b"
|
||||
```
|
||||
|
||||
**Practical shorthand** — for one-off inline prompts, use python3 to avoid escaping issues:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import urllib.request, json, sys
|
||||
url = 'http://localhost:11434/api/generate' # or 100.92.127.64
|
||||
body = json.dumps({'model':'qwen3:14b','prompt': sys.argv[1],'stream':False}).encode()
|
||||
res = json.loads(urllib.request.urlopen(urllib.request.Request(url, body)).read())
|
||||
print(res['response'])
|
||||
" "Summarize these changes in one sentence: ..."
|
||||
```
|
||||
|
||||
**When to use which model:**
|
||||
|
||||
| Task | Model |
|
||||
|------|-------|
|
||||
| Summarize logs, diffs, session notes | qwen3:14b |
|
||||
| Classify bug type, severity, category | qwen3:14b |
|
||||
| Extract structured data from text output | qwen3:14b |
|
||||
| Draft commit message from diff | qwen3:14b |
|
||||
| Suggest refactor for a function (review output) | codestral:22b |
|
||||
| Docstring / comment generation | codestral:22b |
|
||||
|
||||
**Review policy:**
|
||||
- Low-stakes output (summary, label, draft) — use directly, no review needed
|
||||
- Code suggestions from codestral — always review before applying
|
||||
- Never use Ollama for: auth decisions, credential handling, production migrations, security review
|
||||
|
||||
**Review policy:** Always review Critical/High impact Ollama outputs (auth, security, migrations, production). Trust Low impact (classification, formatting). Flag uncertainty to user.
|
||||
|
||||
### GrepAI (Semantic Code Search)
|
||||
|
||||
Use for intent-based search ("how does auth work"), exploring unfamiliar code, context recovery.
|
||||
- **MCP tool:** `grepai` server tools
|
||||
- **Agent:** `deep-explore` agent
|
||||
- **CLI:** `grepai search "query" --json --compact`
|
||||
|
||||
---
|
||||
|
||||
## Memory (Shared Across Machines)
|
||||
|
||||
Stored in-repo at `.claude/memory/` -- syncs via Gitea to all workstations.
|
||||
Index: `.claude/memory/MEMORY.md`
|
||||
|
||||
**IMPORTANT:** Always write to `.claude/memory/` (repo-relative), NOT `~/.claude/projects/*/memory/`.
|
||||
|
||||
---
|
||||
|
||||
## Reference (read on-demand)
|
||||
|
||||
- **Project structure, endpoints, workflows:** `.claude/REFERENCE.md`
|
||||
- **Agent definitions:** `.claude/agents/*.md`
|
||||
- **MCP servers:** `MCP_SERVERS.md`
|
||||
- **Coding standards:** `.claude/CODING_GUIDELINES.md`
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-04-02
|
||||
|
||||
@@ -1,364 +1,57 @@
|
||||
# ClaudeTools - Coding Guidelines
|
||||
|
||||
## General Principles
|
||||
|
||||
These guidelines ensure code quality, consistency, and maintainability across the ClaudeTools project.
|
||||
Project-specific standards. Generic language conventions (PEP 8, etc.) are assumed knowledge.
|
||||
|
||||
---
|
||||
|
||||
## Character Encoding and Text
|
||||
## Character Encoding
|
||||
|
||||
### NO EMOJIS - EVER
|
||||
|
||||
**Rule:** Never use emojis in any code files, including:
|
||||
- Python scripts (.py)
|
||||
- PowerShell scripts (.ps1)
|
||||
- Bash scripts (.sh)
|
||||
- Configuration files
|
||||
- Documentation within code
|
||||
- Log messages
|
||||
- Output strings
|
||||
Never use emojis in code, scripts, config files, log messages, or output strings.
|
||||
|
||||
**Rationale:**
|
||||
- Emojis cause encoding issues (UTF-8 vs ASCII)
|
||||
- PowerShell parsing errors with special Unicode characters
|
||||
- Cross-platform compatibility problems
|
||||
- Terminal rendering inconsistencies
|
||||
- Version control diff issues
|
||||
**Rationale:** Causes PowerShell parsing errors, encoding issues, terminal rendering problems.
|
||||
|
||||
**Instead of emojis, use:**
|
||||
```powershell
|
||||
# BAD - causes parsing errors
|
||||
Write-Host "✓ Success!"
|
||||
Write-Host "⚠ Warning!"
|
||||
|
||||
# GOOD - ASCII text markers
|
||||
Write-Host "[OK] Success!"
|
||||
Write-Host "[SUCCESS] Task completed!"
|
||||
Write-Host "[WARNING] Check settings!"
|
||||
Write-Host "[ERROR] Failed to connect!"
|
||||
**Use instead:**
|
||||
```
|
||||
[OK] [SUCCESS] [INFO] [WARNING] [ERROR] [CRITICAL]
|
||||
```
|
||||
|
||||
**Allowed in:**
|
||||
- User-facing web UI (where Unicode is properly handled)
|
||||
- Database content (with proper UTF-8 encoding)
|
||||
- Markdown documentation (README.md, etc.) - use sparingly
|
||||
**Exception:** User-facing web UI with proper UTF-8 handling.
|
||||
|
||||
---
|
||||
|
||||
## Python Code Standards
|
||||
## Naming Conventions
|
||||
|
||||
### Style
|
||||
- Follow PEP 8 style guide
|
||||
- Use 4 spaces for indentation (no tabs)
|
||||
- Maximum line length: 100 characters (relaxed from 79)
|
||||
- Use type hints for function parameters and return values
|
||||
|
||||
### Imports
|
||||
```python
|
||||
# Standard library imports
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Third-party imports
|
||||
from fastapi import FastAPI
|
||||
from sqlalchemy import Column
|
||||
|
||||
# Local imports
|
||||
from api.models import User
|
||||
from api.utils import encrypt_data
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- Classes: `PascalCase` (e.g., `UserService`, `CredentialModel`)
|
||||
- Functions/methods: `snake_case` (e.g., `get_user`, `create_session`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `API_BASE_URL`, `MAX_RETRIES`)
|
||||
- Private methods: `_leading_underscore` (e.g., `_internal_helper`)
|
||||
- **Python:** snake_case functions, PascalCase classes, UPPER_SNAKE constants
|
||||
- **PowerShell:** PascalCase variables ($TaskName), approved verbs (Get-/Set-/New-)
|
||||
- **Bash:** lowercase_underscore functions, quote all variables
|
||||
- **DB tables:** lowercase plural (users, user_sessions), FK as {table}_id
|
||||
- **DB columns:** created_at/updated_at timestamps, is_/has_ boolean prefixes
|
||||
|
||||
---
|
||||
|
||||
## PowerShell Code Standards
|
||||
## Security
|
||||
|
||||
### Style
|
||||
- Use 4 spaces for indentation
|
||||
- Use PascalCase for variables: `$TaskName`, `$PythonPath`
|
||||
- Use approved verbs for functions: `Get-`, `Set-`, `New-`, `Remove-`
|
||||
|
||||
### Error Handling
|
||||
```powershell
|
||||
# Always use -ErrorAction for cmdlets that might fail
|
||||
$Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $Task) {
|
||||
Write-Host "[ERROR] Task not found"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
```powershell
|
||||
# Use clear status markers
|
||||
Write-Host "[INFO] Starting process..."
|
||||
Write-Host "[SUCCESS] Task completed"
|
||||
Write-Host "[ERROR] Failed to connect"
|
||||
Write-Host "[WARNING] Configuration missing"
|
||||
```
|
||||
- Never hardcode credentials -- use SOPS vault or environment variables
|
||||
- JWT tokens for API auth, Argon2 for password hashing
|
||||
- Log all authentication attempts and sensitive operations
|
||||
- `.env` files are gitignored, never committed
|
||||
|
||||
---
|
||||
|
||||
## Bash Script Standards
|
||||
## API Standards
|
||||
|
||||
### Style
|
||||
- Use 2 spaces for indentation
|
||||
- Always use `#!/bin/bash` shebang
|
||||
- Quote all variables: `"$variable"` not `$variable`
|
||||
- Use `set -e` for error handling (exit on error)
|
||||
|
||||
### Functions
|
||||
```bash
|
||||
# Use lowercase with underscores
|
||||
function check_connection() {
|
||||
local host="$1"
|
||||
echo "[INFO] Checking connection to $host"
|
||||
}
|
||||
```
|
||||
- RESTful with plural nouns: `/api/users`
|
||||
- Consistent error format: `{"detail": "...", "error_code": "...", "status_code": N}`
|
||||
- Paginate large result sets
|
||||
- Document with OpenAPI (automatic with FastAPI)
|
||||
|
||||
---
|
||||
|
||||
## API Development Standards
|
||||
## Output Markers
|
||||
|
||||
### Endpoints
|
||||
- Use RESTful conventions
|
||||
- Use plural nouns: `/api/users` not `/api/user`
|
||||
- Use HTTP methods appropriately: GET, POST, PUT, DELETE
|
||||
- Version APIs if breaking changes: `/api/v2/users`
|
||||
|
||||
### Error Responses
|
||||
```python
|
||||
# Return consistent error format
|
||||
{
|
||||
"detail": "User not found",
|
||||
"error_code": "USER_NOT_FOUND",
|
||||
"status_code": 404
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation
|
||||
- Every endpoint must have a docstring
|
||||
- Use Pydantic schemas for request/response validation
|
||||
- Document in OpenAPI (automatic with FastAPI)
|
||||
|
||||
---
|
||||
|
||||
## Database Standards
|
||||
|
||||
### Table Naming
|
||||
- Use lowercase with underscores: `user_sessions`, `billable_time`
|
||||
- Use plural nouns: `users` not `user`
|
||||
- Use consistent prefixes for related tables
|
||||
|
||||
### Columns
|
||||
- Primary key: `id` (UUID)
|
||||
- Timestamps: `created_at`, `updated_at`
|
||||
- Foreign keys: `{table}_id` (e.g., `user_id`, `project_id`)
|
||||
- Boolean: `is_active`, `has_access` (prefix with is_/has_)
|
||||
|
||||
### Indexes
|
||||
```python
|
||||
# Add indexes for frequently queried fields
|
||||
Index('idx_users_email', 'email')
|
||||
Index('idx_sessions_project_id', 'project_id')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Standards
|
||||
|
||||
### Credentials
|
||||
- Never hardcode credentials in code
|
||||
- Use environment variables for sensitive data
|
||||
- Use `.env` files (gitignored) for local development
|
||||
- Encrypt passwords with AES-256-GCM (Fernet)
|
||||
|
||||
### Authentication
|
||||
- Use JWT tokens for API authentication
|
||||
- Hash passwords with Argon2
|
||||
- Include token expiration
|
||||
- Log all authentication attempts
|
||||
|
||||
### Audit Logging
|
||||
```python
|
||||
# Log all sensitive operations
|
||||
audit_log = CredentialAuditLog(
|
||||
credential_id=credential.id,
|
||||
action="password_updated",
|
||||
user_id=current_user.id,
|
||||
details="Password updated via API"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Files
|
||||
- Name: `test_{module_name}.py`
|
||||
- Location: Same directory as code being tested
|
||||
- Use pytest framework
|
||||
|
||||
### Test Structure
|
||||
```python
|
||||
def test_create_user():
|
||||
"""Test user creation with valid data."""
|
||||
# Arrange
|
||||
user_data = {"email": "test@example.com", "name": "Test"}
|
||||
|
||||
# Act
|
||||
result = create_user(user_data)
|
||||
|
||||
# Assert
|
||||
assert result.email == "test@example.com"
|
||||
assert result.id is not None
|
||||
```
|
||||
|
||||
### Coverage
|
||||
- Aim for 80%+ code coverage
|
||||
- Test happy path and error cases
|
||||
- Mock external dependencies (database, APIs)
|
||||
|
||||
---
|
||||
|
||||
## Git Commit Standards
|
||||
|
||||
### Commit Messages
|
||||
```
|
||||
[Type] Brief description (50 chars max)
|
||||
|
||||
Detailed explanation if needed (wrap at 72 chars)
|
||||
|
||||
- Change 1
|
||||
- Change 2
|
||||
- Change 3
|
||||
```
|
||||
|
||||
### Types
|
||||
- `[Feature]` - New feature
|
||||
- `[Fix]` - Bug fix
|
||||
- `[Refactor]` - Code refactoring
|
||||
- `[Docs]` - Documentation only
|
||||
- `[Test]` - Test updates
|
||||
- `[Config]` - Configuration changes
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
project/
|
||||
├── api/ # API application code
|
||||
│ ├── models/ # Database models
|
||||
│ ├── routers/ # API endpoints
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ ├── services/ # Business logic
|
||||
│ └── utils/ # Helper functions
|
||||
├── .claude/ # Claude Code configuration
|
||||
│ ├── hooks/ # Git-style hooks
|
||||
│ └── agents/ # Agent instructions
|
||||
├── scripts/ # Utility scripts
|
||||
└── migrations/ # Database migrations
|
||||
```
|
||||
|
||||
### File Naming
|
||||
- Python: `snake_case.py`
|
||||
- Classes: Match class name (e.g., `UserService` in `user_service.py`)
|
||||
- Scripts: Descriptive names (e.g., `setup_database.sh`, `test_api.py`)
|
||||
|
||||
---
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Code Comments
|
||||
```python
|
||||
# Use comments for WHY, not WHAT
|
||||
# Good: "Retry 3 times to handle transient network errors"
|
||||
# Bad: "Set retry count to 3"
|
||||
|
||||
def fetch_data(url: str) -> dict:
|
||||
"""
|
||||
Fetch data from API endpoint.
|
||||
|
||||
Args:
|
||||
url: Full URL to fetch from
|
||||
|
||||
Returns:
|
||||
Parsed JSON response
|
||||
|
||||
Raises:
|
||||
ConnectionError: If API is unreachable
|
||||
ValueError: If response is invalid JSON
|
||||
"""
|
||||
```
|
||||
|
||||
### README Files
|
||||
- Include quick start guide
|
||||
- Document prerequisites
|
||||
- Provide examples
|
||||
- Keep up to date
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Python
|
||||
```python
|
||||
# Use specific exceptions
|
||||
try:
|
||||
result = api_call()
|
||||
except ConnectionError as e:
|
||||
logger.error(f"[ERROR] Connection failed: {e}")
|
||||
raise
|
||||
except ValueError as e:
|
||||
logger.warning(f"[WARNING] Invalid data: {e}")
|
||||
return None
|
||||
```
|
||||
|
||||
### PowerShell
|
||||
```powershell
|
||||
# Use try/catch for error handling
|
||||
try {
|
||||
$Result = Invoke-RestMethod -Uri $Url
|
||||
} catch {
|
||||
Write-Host "[ERROR] Request failed: $_"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging Standards
|
||||
|
||||
### Log Levels
|
||||
- `DEBUG` - Detailed diagnostic info (development only)
|
||||
- `INFO` - General informational messages
|
||||
- `WARNING` - Warning messages (non-critical issues)
|
||||
- `ERROR` - Error messages (failures)
|
||||
- `CRITICAL` - Critical errors (system failures)
|
||||
|
||||
### Log Format
|
||||
```python
|
||||
# Use structured logging
|
||||
logger.info(
|
||||
"[INFO] User login",
|
||||
extra={
|
||||
"user_id": user.id,
|
||||
"ip_address": request.client.host,
|
||||
"timestamp": datetime.utcnow()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Output Markers
|
||||
All scripts and tools use ASCII status markers:
|
||||
```
|
||||
[INFO] Starting process
|
||||
[SUCCESS] Task completed
|
||||
@@ -369,60 +62,12 @@ logger.info(
|
||||
|
||||
---
|
||||
|
||||
## Performance Guidelines
|
||||
## Git
|
||||
|
||||
### Database Queries
|
||||
- Use indexes for frequently queried fields
|
||||
- Avoid N+1 queries (use joins or eager loading)
|
||||
- Paginate large result sets
|
||||
- Use connection pooling
|
||||
|
||||
### API Responses
|
||||
- Return only necessary fields
|
||||
- Use pagination for lists
|
||||
- Compress large payloads
|
||||
- Cache frequently accessed data
|
||||
|
||||
### File Operations
|
||||
- Use context managers (`with` statements)
|
||||
- Stream large files (don't load into memory)
|
||||
- Clean up temporary files
|
||||
- Commit types: feat, fix, refactor, docs, test, config
|
||||
- Always include `Co-Authored-By` line for Claude commits
|
||||
- Never commit .env, credentials, venv, __pycache__, *.log
|
||||
|
||||
---
|
||||
|
||||
## Version Control
|
||||
|
||||
### .gitignore
|
||||
Always exclude:
|
||||
- `.env` files (credentials)
|
||||
- `__pycache__/` (Python cache)
|
||||
- `*.pyc` (compiled Python)
|
||||
- `.venv/`, `venv/` (virtual environments)
|
||||
- `.claude/*.json` (local state)
|
||||
- `*.log` (log files)
|
||||
|
||||
### Branching
|
||||
- `main` - Production-ready code
|
||||
- `develop` - Integration branch
|
||||
- `feature/*` - New features
|
||||
- `fix/*` - Bug fixes
|
||||
- `hotfix/*` - Urgent production fixes
|
||||
|
||||
---
|
||||
|
||||
## Review Checklist
|
||||
|
||||
Before committing code, verify:
|
||||
- [ ] No emojis or special Unicode characters
|
||||
- [ ] All variables and functions have descriptive names
|
||||
- [ ] No hardcoded credentials or sensitive data
|
||||
- [ ] Error handling is implemented
|
||||
- [ ] Code is formatted consistently
|
||||
- [ ] Tests pass (if applicable)
|
||||
- [ ] Documentation is updated
|
||||
- [ ] No debugging print statements left in code
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Status:** Active
|
||||
**Last Updated:** 2026-04-02
|
||||
|
||||
74
.claude/COMPLEXITY_ROUTING.md
Normal file
74
.claude/COMPLEXITY_ROUTING.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Complexity-Based Model Routing
|
||||
|
||||
When spawning an agent, pick a tier based on the request signals below, then pass `model` accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Haiku (fast/cheap)
|
||||
|
||||
**Signals:** single lookup, no code changes, classification, formatting, summarization, status check, documentation
|
||||
|
||||
**Examples:**
|
||||
- "What's the status of X?"
|
||||
- Summarize or format a session log
|
||||
- Search/grep for a value
|
||||
- Convert or extract data
|
||||
- Write/update a markdown doc
|
||||
|
||||
**Agents that default here:** documentation-squire, explore (quick searches), photo
|
||||
|
||||
**Agent call:** `model: "haiku"`
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Sonnet (default, inherit)
|
||||
|
||||
**Signals:** standard code generation, routine DB queries, test execution, API work, multi-file reads, git operations
|
||||
|
||||
**Examples:**
|
||||
- Add or modify an endpoint
|
||||
- Run tests and report results
|
||||
- Write a DB migration
|
||||
- Fetch credentials, configure a service
|
||||
- Commit and push changes
|
||||
|
||||
**Agents that default here:** coding, database, testing, gitea, general-purpose, deep-explore (standard search)
|
||||
|
||||
**Agent call:** omit `model` (inherits session model)
|
||||
|
||||
---
|
||||
|
||||
## Tier 3 — Opus (high-stakes reasoning)
|
||||
|
||||
**Signals:** architectural decision, security/auth, 3+ interacting systems, ambiguous root cause, production data risk, anything that fails badly if wrong
|
||||
|
||||
**Examples:**
|
||||
- Redesign an auth or data flow
|
||||
- Security or code review of a critical PR
|
||||
- Debug a multi-service race condition
|
||||
- Schema migration on production data
|
||||
- Evaluate competing architectural approaches
|
||||
|
||||
**Agents that default here:** code-review (when Sequential Thinking triggers), deep-explore (architecture questions)
|
||||
|
||||
**Agent call:** `model: "opus"`
|
||||
|
||||
---
|
||||
|
||||
## Bump Rule
|
||||
|
||||
If the request contains ANY of these keywords, bump one tier up regardless of other signals:
|
||||
|
||||
`security`, `auth`, `token`, `credential`, `migration`, `production`, `race condition`, `data loss`, `breach`, `encrypt`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Tier | Model | Typical cost | Use when |
|
||||
|------|-------|-------------|----------|
|
||||
| 1 | `haiku` | ~10x cheaper | Lookup, format, summarize, doc |
|
||||
| 2 | (inherit) | baseline | Standard code, DB, tests |
|
||||
| 3 | `opus` | ~5x more expensive | Architecture, security, ambiguous failures |
|
||||
|
||||
Err toward Tier 2 when uncertain. Only use Opus when the reasoning stakes justify the cost.
|
||||
@@ -1,418 +0,0 @@
|
||||
# Directives Enforcement Mechanism
|
||||
|
||||
**Created:** 2026-01-19
|
||||
**Purpose:** Ensure Claude consistently follows operational directives and stops taking shortcuts
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
Claude (Main Instance) has a tendency to:
|
||||
- Take shortcuts by querying database directly instead of using Database Agent
|
||||
- Use emojis despite explicit prohibition (causes PowerShell errors)
|
||||
- Execute operations directly instead of coordinating via agents
|
||||
- Forget directives after conversation compaction or long sessions
|
||||
|
||||
**Result:** Violated architecture, broken scripts, inconsistent behavior
|
||||
|
||||
---
|
||||
|
||||
## The Solution: Multi-Layered Enforcement
|
||||
|
||||
### Layer 1: Prominent Directive Reference in claude.md
|
||||
|
||||
**File:** `.claude/claude.md` (line 3-15)
|
||||
|
||||
```markdown
|
||||
**FIRST: READ YOUR DIRECTIVES**
|
||||
|
||||
Before doing ANYTHING in this project, read and internalize `directives.md` in the project root.
|
||||
|
||||
This file defines:
|
||||
- Your identity (Coordinator, not Executor)
|
||||
- What you DO and DO NOT do
|
||||
- Agent coordination rules (NEVER query database directly)
|
||||
- Enforcement checklist (NO EMOJIS, ASCII markers only)
|
||||
|
||||
**If you haven't read directives.md in this session, STOP and read it now.**
|
||||
|
||||
Command: `Read directives.md` (in project root: D:\ClaudeTools\directives.md)
|
||||
```
|
||||
|
||||
**Effect:** First thing Claude sees when loading project context
|
||||
|
||||
---
|
||||
|
||||
### Layer 2: /refresh-directives Command
|
||||
|
||||
**File:** `.claude/commands/refresh-directives.md`
|
||||
|
||||
**Purpose:** Command to re-read and internalize directives
|
||||
|
||||
**User invocation:**
|
||||
```
|
||||
/refresh-directives
|
||||
```
|
||||
|
||||
**Auto-invocation points:**
|
||||
- After `/checkpoint` command
|
||||
- After `/save` command
|
||||
- After conversation compaction (detected automatically)
|
||||
- After large task completion (3+ agents)
|
||||
- Every 50 tool uses (optional counter-based)
|
||||
|
||||
**What it does:**
|
||||
1. Reads `directives.md` completely
|
||||
2. Performs self-assessment for violations
|
||||
3. Commits to following directives
|
||||
4. Reports status to user
|
||||
|
||||
**Output:**
|
||||
```markdown
|
||||
## Directives Refreshed
|
||||
|
||||
I've re-read my operational directives.
|
||||
|
||||
**Key commitments:**
|
||||
- [OK] Coordinate via agents, not execute
|
||||
- [OK] Database Agent for ALL data operations
|
||||
- [OK] ASCII markers only (no emojis)
|
||||
- [OK] Preserve context by delegating
|
||||
|
||||
**Self-assessment:** Clean - no violations detected
|
||||
|
||||
**Status:** Ready to coordinate effectively.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Layer 3: Integration with /checkpoint Command
|
||||
|
||||
**File:** `.claude/commands/checkpoint.md` (step 8)
|
||||
|
||||
**After git + database checkpoint:**
|
||||
```markdown
|
||||
8. **Refresh directives** (MANDATORY):
|
||||
- After checkpoint completion, auto-invoke `/refresh-directives`
|
||||
- Re-read `directives.md` to prevent shortcut-taking
|
||||
- Perform self-assessment for any violations
|
||||
- Confirm commitment to agent coordination rules
|
||||
- Report directives refreshed to user
|
||||
```
|
||||
|
||||
**Effect:** Every checkpoint automatically refreshes directives
|
||||
|
||||
---
|
||||
|
||||
### Layer 4: Integration with /save Command
|
||||
|
||||
**File:** `.claude/commands/save.md` (step 4)
|
||||
|
||||
**After saving session log:**
|
||||
```markdown
|
||||
4. **Refresh directives** (MANDATORY):
|
||||
- Auto-invoke `/refresh-directives`
|
||||
- Re-read `directives.md` to prevent shortcut-taking
|
||||
- Perform self-assessment for violations
|
||||
- Confirm commitment to coordination rules
|
||||
- Report directives refreshed
|
||||
```
|
||||
|
||||
**Effect:** Every session save automatically refreshes directives
|
||||
|
||||
---
|
||||
|
||||
### Layer 5: directives.md (The Source of Truth)
|
||||
|
||||
**File:** `directives.md` (project root)
|
||||
|
||||
**Contains:**
|
||||
- Identity definition (Coordinator, not Executor)
|
||||
- What Claude DOES and DOES NOT do
|
||||
- Complete agent coordination rules
|
||||
- Coding standards (NO EMOJIS - ASCII only)
|
||||
- Enforcement checklist
|
||||
- Pre-action verification questions
|
||||
|
||||
**Key sections:**
|
||||
1. My Identity
|
||||
2. Core Operating Principle
|
||||
3. What I DO [OK]
|
||||
4. What I DO NOT DO [ERROR]
|
||||
5. Agent Coordination Rules
|
||||
6. Skills vs Agents
|
||||
7. Automatic Behaviors
|
||||
8. Coding Standards (NO EMOJIS)
|
||||
9. Enforcement Checklist
|
||||
|
||||
---
|
||||
|
||||
## Automatic Trigger Points
|
||||
|
||||
### Session Start
|
||||
```
|
||||
Claude loads project → Sees claude.md → "READ DIRECTIVES FIRST"
|
||||
→ Reads directives.md → Internalizes rules → Ready to work
|
||||
```
|
||||
|
||||
### After Checkpoint
|
||||
```
|
||||
User: /checkpoint
|
||||
→ Claude creates git commit + database context
|
||||
→ Verifies both succeeded
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Confirms ready to proceed
|
||||
```
|
||||
|
||||
### After Save
|
||||
```
|
||||
User: /save
|
||||
→ Claude creates/updates session log
|
||||
→ Commits to repository
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Confirms ready to proceed
|
||||
```
|
||||
|
||||
### After Conversation Compaction
|
||||
```
|
||||
System: [Conversation compacted due to length]
|
||||
→ Claude detects compaction (system message)
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Restores operational mode
|
||||
→ Continues with proper coordination
|
||||
```
|
||||
|
||||
### After Large Task
|
||||
```
|
||||
Claude completes task using 3+ agents
|
||||
→ Recognizes major work completed
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Resets to coordination mode
|
||||
→ Ready for next task
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Violation Detection
|
||||
|
||||
### Self-Assessment Process
|
||||
|
||||
**During /refresh-directives, Claude checks:**
|
||||
|
||||
**Database Operations:**
|
||||
- [ ] Did I query database directly via ssh/mysql/curl? → VIOLATION
|
||||
- [ ] Did I call ClaudeTools API directly? → VIOLATION
|
||||
- [ ] Did I use Database Agent for data operations? → CORRECT
|
||||
|
||||
**Code Generation:**
|
||||
- [ ] Did I write production code myself? → VIOLATION
|
||||
- [ ] Did I delegate to Coding Agent? → CORRECT
|
||||
|
||||
**Emoji Usage:**
|
||||
- [ ] Did I use [OK][ERROR][WARNING] or other emojis? → VIOLATION
|
||||
- [ ] Did I use [OK]/[ERROR]/[WARNING]? → CORRECT
|
||||
|
||||
**Agent Coordination:**
|
||||
- [ ] Did I execute operations directly? → VIOLATION
|
||||
- [ ] Did I coordinate via agents? → CORRECT
|
||||
|
||||
**If violations detected:**
|
||||
```markdown
|
||||
[WARNING] Detected 2 directive violations:
|
||||
- Direct database query at timestamp X
|
||||
- Emoji usage in output at timestamp Y
|
||||
|
||||
[OK] Corrective actions committed:
|
||||
- Will use Database Agent for all database operations
|
||||
- Will use ASCII markers [OK]/[ERROR] instead of emojis
|
||||
|
||||
[SUCCESS] Directives re-internalized. Proper coordination restored.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
### Prevents Shortcut-Taking
|
||||
- Regular reminders not to query database directly
|
||||
- Reinforces agent coordination model
|
||||
- Stops emoji usage before it causes errors
|
||||
|
||||
### Context Recovery
|
||||
- Restores operational mode after compaction
|
||||
- Ensures consistency across sessions
|
||||
- Maintains proper coordination principles
|
||||
|
||||
### Self-Correction
|
||||
- Detects violations automatically
|
||||
- Commits to corrective behavior
|
||||
- Provides accountability to user
|
||||
|
||||
### User Visibility
|
||||
- User sees when directives refreshed
|
||||
- Transparent operational changes
|
||||
- Builds trust in coordination model
|
||||
|
||||
---
|
||||
|
||||
## Enforcement Checklist
|
||||
|
||||
### For Claude (Self-Check Before Any Action)
|
||||
|
||||
**Before database operation:**
|
||||
- [ ] Read directives.md this session? If no → STOP and read
|
||||
- [ ] Am I about to query database? → Use Database Agent instead
|
||||
- [ ] Am I about to use curl/API? → Use Database Agent instead
|
||||
|
||||
**Before writing code:**
|
||||
- [ ] Am I writing production code? → Delegate to Coding Agent
|
||||
- [ ] Am I using emojis? → STOP, use [OK]/[ERROR]/[WARNING]
|
||||
|
||||
**Before git operations:**
|
||||
- [ ] Am I about to commit? → Delegate to Gitea Agent
|
||||
- [ ] Am I about to push? → Delegate to Gitea Agent
|
||||
|
||||
**After major operations:**
|
||||
- [ ] Completed checkpoint/save? → Auto-invoke /refresh-directives
|
||||
- [ ] Completed large task? → Auto-invoke /refresh-directives
|
||||
- [ ] Conversation compacted? → Auto-invoke /refresh-directives
|
||||
|
||||
---
|
||||
|
||||
## User Commands
|
||||
|
||||
### Manual Refresh
|
||||
```
|
||||
/refresh-directives
|
||||
```
|
||||
Manually trigger directive re-reading and self-assessment
|
||||
|
||||
### Checkpoint (Auto-refresh)
|
||||
```
|
||||
/checkpoint
|
||||
```
|
||||
Creates git commit + database context, then auto-refreshes directives
|
||||
|
||||
### Save (Auto-refresh)
|
||||
```
|
||||
/save
|
||||
```
|
||||
Creates session log, then auto-refreshes directives
|
||||
|
||||
### Sync
|
||||
```
|
||||
/sync
|
||||
```
|
||||
Pulls latest from Gitea (directives.md included if updated)
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### User Can Monitor Compliance
|
||||
|
||||
**Check for violations:**
|
||||
- Look for direct `ssh`, `mysql`, or `curl` commands to database
|
||||
- Look for emoji characters ([OK][ERROR][WARNING]) in output
|
||||
- Look for direct code generation (should delegate to Coding Agent)
|
||||
|
||||
**If violations detected:**
|
||||
```
|
||||
User: /refresh-directives
|
||||
```
|
||||
Forces Claude to re-read and commit to directives
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Updating directives.md
|
||||
|
||||
**When to update:**
|
||||
- New agent added to system
|
||||
- New restriction discovered
|
||||
- Behavior patterns change
|
||||
- New shortcut tendencies identified
|
||||
|
||||
**Process:**
|
||||
1. Edit `directives.md` with new rules
|
||||
2. Commit changes to repository
|
||||
3. Push to Gitea
|
||||
4. Invoke `/sync` on other machines
|
||||
5. Invoke `/refresh-directives` to apply immediately
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Five-layer enforcement:**
|
||||
1. **claude.md** - Prominent reference at top (first thing Claude sees)
|
||||
2. **/refresh-directives command** - Explicit directive re-reading
|
||||
3. **/checkpoint integration** - Auto-refresh after checkpoints
|
||||
4. **/save integration** - Auto-refresh after session saves
|
||||
5. **directives.md** - Complete operational ruleset
|
||||
|
||||
**Automatic triggers:**
|
||||
- Session start
|
||||
- After /checkpoint
|
||||
- After /save
|
||||
- After conversation compaction
|
||||
- After large tasks
|
||||
|
||||
**Result:** Claude consistently follows directives, stops taking shortcuts, maintains proper agent coordination architecture.
|
||||
|
||||
---
|
||||
|
||||
## Example: Full Enforcement Flow
|
||||
|
||||
```
|
||||
Session Start:
|
||||
→ Claude loads .claude/claude.md
|
||||
→ Sees "READ YOUR DIRECTIVES FIRST"
|
||||
→ Reads directives.md completely
|
||||
→ Internalizes rules
|
||||
→ Ready to coordinate (not execute)
|
||||
|
||||
User Request:
|
||||
→ "How many projects in database?"
|
||||
→ Claude recognizes database operation
|
||||
→ Checks directives: "Database Agent handles ALL database operations"
|
||||
→ Launches Database Agent with task
|
||||
→ Receives count from agent
|
||||
→ Presents to user
|
||||
|
||||
After /checkpoint:
|
||||
→ Git commit created
|
||||
→ Database context saved
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Self-assessment: Clean
|
||||
→ Confirms: "Directives refreshed. Ready to coordinate."
|
||||
|
||||
Conversation Compacted:
|
||||
→ System compacts conversation
|
||||
→ Claude detects compaction
|
||||
→ AUTO-INVOKES /refresh-directives
|
||||
→ Re-reads directives.md
|
||||
→ Restores coordination mode
|
||||
→ Continues properly
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**This enforcement mechanism ensures Claude maintains proper operational behavior throughout the entire session lifecycle.**
|
||||
|
||||
---
|
||||
|
||||
**Created:** 2026-01-19
|
||||
**Files Modified:**
|
||||
- `.claude/claude.md` - Added directive reference at top
|
||||
- `.claude/commands/checkpoint.md` - Added step 8 (refresh directives)
|
||||
- `.claude/commands/save.md` - Added step 4 (refresh directives)
|
||||
- `.claude/commands/refresh-directives.md` - New command definition
|
||||
|
||||
**Status:** Active enforcement system
|
||||
109
.claude/MCP_SERVERS.md
Normal file
109
.claude/MCP_SERVERS.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# MCP Servers — Configuration Reference
|
||||
|
||||
MCP (Model Context Protocol) servers extend Claude Code with external tool
|
||||
capabilities. Each server runs as a child process and exposes tools that
|
||||
Claude can call.
|
||||
|
||||
**Config file:** `.mcp.json` in repo root (shared across machines via git).
|
||||
|
||||
---
|
||||
|
||||
## Active Servers
|
||||
|
||||
### TickTick
|
||||
|
||||
Task management integration for TickTick (todo/project tracking app).
|
||||
|
||||
**Tools provided:**
|
||||
- `ticktick_create_task`, `ticktick_update_task`, `ticktick_complete_task`, `ticktick_delete_task`
|
||||
- `ticktick_create_project`, `ticktick_update_project`, `ticktick_delete_project`
|
||||
- `ticktick_list_projects`, `ticktick_get_project`
|
||||
|
||||
**Auth:** OAuth token stored in vault at `services/ticktick.sops.yaml`. Token file
|
||||
auto-generated by `mcp-servers/ticktick/ticktick_auth.py` on first use.
|
||||
|
||||
**Config in `.mcp.json`:**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"ticktick": {
|
||||
"command": "python",
|
||||
"args": ["D:\\claudetools\\mcp-servers\\ticktick\\ticktick_mcp.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Claude-in-Chrome (browser automation)
|
||||
|
||||
Installed as a Chrome browser extension. Provides browser automation tools
|
||||
for web interaction, form filling, page reading, screenshots, GIF recording.
|
||||
|
||||
**Not configured in `.mcp.json`** — runs as a Chrome extension that connects
|
||||
automatically when the Claude Code extension is active and Chrome is open.
|
||||
|
||||
**Tools provided:** `tabs_context_mcp`, `tabs_create_mcp`, `navigate`, `computer`
|
||||
(click/type/screenshot), `read_page`, `find`, `form_input`, `javascript_tool`,
|
||||
`get_page_text`, `read_console_messages`, `gif_creator`, etc.
|
||||
|
||||
**Requires:** Chrome browser with the Claude-in-Chrome extension installed.
|
||||
|
||||
---
|
||||
|
||||
## Available but Not Wired
|
||||
|
||||
These server directories exist but aren't in `.mcp.json`. Add them when needed.
|
||||
|
||||
### GrepAI MCP Server
|
||||
|
||||
Semantic code search over the indexed codebase. Alternative to using the
|
||||
`grepai search` CLI directly.
|
||||
|
||||
**To activate:** Add to `.mcp.json`:
|
||||
```json
|
||||
{
|
||||
"grepai": {
|
||||
"command": "D:\\claudetools\\grepai.exe",
|
||||
"args": ["mcp-serve"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Requires:** GrepAI initialized (`grepai init`) + Ollama running with
|
||||
`nomic-embed-text` model. Index builds automatically via `grepai watch`.
|
||||
|
||||
### Ollama Assistant
|
||||
|
||||
Local LLM integration for delegating simple tasks (summarization,
|
||||
classification, drafting) to locally-running models.
|
||||
|
||||
**Location:** `mcp-servers/ollama-assistant/`
|
||||
|
||||
**To activate:** Check the server's README for the exact `.mcp.json` entry.
|
||||
Requires Ollama running at `http://localhost:11434` with models pulled.
|
||||
|
||||
### Feature Management
|
||||
|
||||
Feature flag management server.
|
||||
|
||||
**Location:** `mcp-servers/feature-management/`
|
||||
|
||||
**Status:** Exists but purpose unclear. Check directory for README.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New MCP Server
|
||||
|
||||
1. Create directory: `mcp-servers/<name>/`
|
||||
2. Write the server script (Python or Node recommended)
|
||||
3. Add entry to `.mcp.json` with `command` and `args`
|
||||
4. Restart Claude Code to pick up the new server
|
||||
5. Document in this file
|
||||
|
||||
**Important:** `.mcp.json` is tracked in git. Changes sync to all machines.
|
||||
Machine-specific server paths should use absolute paths that work on all
|
||||
team workstations (or use relative paths from repo root).
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-04-16*
|
||||
261
.claude/ONBOARDING.md
Normal file
261
.claude/ONBOARDING.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# Welcome to ClaudeTools — Onboarding Guide
|
||||
|
||||
Hey! This guide explains how our Claude Code setup works, WHY it's built the way it is, and how to use it effectively for daily MSP work. Read this once, then use it as reference when something feels unfamiliar.
|
||||
|
||||
---
|
||||
|
||||
## What is this?
|
||||
|
||||
ClaudeTools is our shared workspace for **Claude Code** — the AI coding + automation assistant. It's a git repo that syncs across our workstations via Gitea (our self-hosted Git server). Everything Claude learns, every session log, every automation script, every project we build — it all lives here and stays in sync.
|
||||
|
||||
**Why a repo instead of just using Claude directly?**
|
||||
- Claude Code loses context between sessions. This repo IS the memory.
|
||||
- Session logs preserve what we did, what creds we used, what decisions we made.
|
||||
- CLAUDE.md tells Claude HOW to behave specifically for our org (not generic defaults).
|
||||
- Skills and commands give us reusable shortcuts for common MSP tasks.
|
||||
- The vault (separate repo) stores all credentials encrypted so Claude can access them without us typing passwords every session.
|
||||
|
||||
---
|
||||
|
||||
## First time setup
|
||||
|
||||
When you open Claude Code for the first time on a new machine, Claude will ask who you are. Just answer with your name. Claude then:
|
||||
|
||||
1. Creates a local identity file (so it knows who's at the keyboard)
|
||||
2. Sets your git name/email for commits
|
||||
3. Registers your machine in the shared users list
|
||||
|
||||
After that, every session log and git commit is attributed to you.
|
||||
|
||||
### GuruRMM repo — one-time setup per machine
|
||||
|
||||
The GuruRMM repo (`projects/msp-tools/guru-rmm/`) requires one extra step after cloning or first use. Run this from the repo root:
|
||||
|
||||
```bash
|
||||
bash scripts/install-hooks.sh
|
||||
```
|
||||
|
||||
This does three things permanently:
|
||||
- Points git at `scripts/hooks/` so pre-commit checks run automatically (and stay current as hooks evolve — no re-install after updates)
|
||||
- Sets `core.autocrlf=false` and `core.eol=lf` for this repo (prevents sqlx migration checksum drift from Windows CRLF line endings)
|
||||
- Sets `core.autocrlf=false` globally on this machine
|
||||
|
||||
**Why this matters:** sqlx verifies migration files by sha384 hash. A file committed with CRLF line endings hashes differently than the same file with LF — the server sees the mismatch and refuses to start. The `.gitattributes` file handles new commits automatically; this command configures the git client for existing checkouts.
|
||||
|
||||
---
|
||||
|
||||
## The slash commands (most important daily tools)
|
||||
|
||||
Type these in Claude Code's prompt. They're shortcuts for common operations.
|
||||
|
||||
| Command | What it does | When to use |
|
||||
|---------|-------------|-------------|
|
||||
| `/save` | Saves a comprehensive session log (what you did, creds used, decisions made) | **End of every significant work session.** This is how future-you (or future-me) recovers context. |
|
||||
| `/sync` | Pull + push changes to/from Gitea | Start of session (get latest), end of session (push yours) |
|
||||
| `/context` | Searches session logs and credentials for previous work | "What did we do for Dataforth last week?" or "What's the password for AD2?" |
|
||||
| `/checkpoint` | Git commit + database context save | After completing a feature or fix |
|
||||
| `/scc` | Save + Commit + Push (all three in one shot) | Quick end-of-session wrap-up |
|
||||
| `/1password` | Access secrets from 1Password | When vault doesn't have a credential |
|
||||
|
||||
### Why these exist
|
||||
|
||||
Without `/save`, you'd lose everything when a session ends. Without `/sync`, your work stays on one machine. Without `/context`, you'd re-discover the same information every session. These three commands are 90% of daily usage.
|
||||
|
||||
---
|
||||
|
||||
## The SOPS vault (how credentials work)
|
||||
|
||||
We store ALL credentials in an encrypted vault at `D:\vault\` (separate git repo). Files are YAML encrypted with age/SOPS. Claude can decrypt them on the fly.
|
||||
|
||||
**How Claude accesses a credential:**
|
||||
```bash
|
||||
bash D:/vault/scripts/vault.sh get-field clients/dataforth/ad2.sops.yaml credentials.password
|
||||
```
|
||||
|
||||
**Why this matters:**
|
||||
- We never hardcode passwords in scripts or session logs (they're vault references)
|
||||
- The vault syncs across machines via Gitea (same as claudetools)
|
||||
- Encryption uses an age key at `%APPDATA%\sops\age\keys.txt` — this key needs to be on each machine that decrypts
|
||||
|
||||
**Your machine needs the age key.** Mike will give you the key file. Drop it at:
|
||||
```
|
||||
C:\Users\<you>\AppData\Roaming\sops\age\keys.txt
|
||||
```
|
||||
|
||||
Without this file, vault commands fail. Everything else works fine.
|
||||
|
||||
---
|
||||
|
||||
## How Claude knows about our infrastructure
|
||||
|
||||
### CLAUDE.md (the brain)
|
||||
|
||||
`.claude/CLAUDE.md` is the master instructions file. Claude reads it at the start of every session. It tells Claude:
|
||||
|
||||
- **Who we are** (AZ Computer Guru, MSP)
|
||||
- **How to behave** (delegate to agents, no emojis, use vault for creds)
|
||||
- **What projects exist** (GuruRMM, Dataforth, ClaudeTools API)
|
||||
- **How to load context** automatically when you mention a project keyword
|
||||
|
||||
**Key behavior:** If you say "work on Dataforth", Claude automatically reads `projects/dataforth-dos/CONTEXT.md` before responding. Same for "GuruRMM" → reads `projects/msp-tools/guru-rmm/CONTEXT.md`. This means Claude starts every project conversation with full context — server IPs, current state, recent work, anti-patterns to avoid.
|
||||
|
||||
### CONTEXT.md files (per-project state)
|
||||
|
||||
Each major project has a `CONTEXT.md` that captures:
|
||||
- Server IPs, ports, credentials references
|
||||
- Current deployment state
|
||||
- Recent session logs (what was done last)
|
||||
- Anti-patterns (things NOT to do, learned from past mistakes)
|
||||
- What to work on next
|
||||
|
||||
These files are the **single source of truth** for "where are we on this project."
|
||||
|
||||
### Session logs (the history)
|
||||
|
||||
Every significant work session gets a log saved to `session-logs/` (root for general, or `projects/*/session-logs/` for project-specific). These include:
|
||||
- What was accomplished
|
||||
- Full credentials used (unredacted — needed for future sessions)
|
||||
- Infrastructure changes made
|
||||
- Commands that worked and errors that didn't
|
||||
- What's still pending
|
||||
|
||||
**This is why `/save` matters.** Without it, the next person (or the next Claude session) starts from scratch.
|
||||
|
||||
---
|
||||
|
||||
## Skills (auto-invoked behaviors)
|
||||
|
||||
Skills are more powerful than commands — some trigger automatically.
|
||||
|
||||
| Skill | Auto-invokes? | What it does |
|
||||
|-------|--------------|-------------|
|
||||
| `frontend-design` | YES — after any UI change | Validates visual correctness, accessibility, design quality |
|
||||
| `stop-slop` | YES — always active | Prevents generic/lazy AI output. Enforces quality. |
|
||||
| `remediation-tool` | When you say "remediation tool" or "365" | M365 tenant investigation via our Graph API app |
|
||||
| `skill-creator` | On request | Helps build new custom skills |
|
||||
| `theme-factory` | On request | Apply visual themes to HTML artifacts |
|
||||
|
||||
### Why "stop-slop" exists
|
||||
|
||||
Without it, Claude defaults to generic patterns (purple gradients, Inter font, emoji-heavy prose). Our `stop-slop` skill enforces our standards: ASCII markers instead of emojis, specific rather than vague, no filler phrases.
|
||||
|
||||
---
|
||||
|
||||
## Agents (specialized workers)
|
||||
|
||||
Claude Code can spawn sub-agents for specific tasks. These are defined in `.claude/agents/`. The main ones you'll encounter:
|
||||
|
||||
| Agent | What it does | When Claude uses it |
|
||||
|-------|-------------|-------------------|
|
||||
| **Database Agent** | Runs SQL queries on our databases | Any database operation — Claude should NEVER query directly |
|
||||
| **Code Review Agent** | Reviews code changes for quality/security | After any code modification |
|
||||
| **Coding Agent** | Writes production code | When Claude needs to generate code (not just edit) |
|
||||
| **Explore Agent** | Searches codebases quickly | When looking for files, patterns, or understanding code |
|
||||
| **Gitea Agent** | Git commits, pushes, branch operations | Commit workflow |
|
||||
| **Backup Agent** | Backup operations | Before destructive changes |
|
||||
|
||||
**Why agents?** Claude has a limited context window. If it does everything itself, it runs out of memory mid-conversation. Agents handle heavy work in isolation and return just the summary. Also: separation of concerns — the Code Review Agent can independently evaluate code the Coding Agent wrote.
|
||||
|
||||
---
|
||||
|
||||
## Local AI tools (when available)
|
||||
|
||||
### Ollama (local LLM)
|
||||
|
||||
Ollama runs AI models locally on your GPU. Used for tasks that don't need Claude's full reasoning power — summarization, classification, data extraction.
|
||||
|
||||
**Models we use:**
|
||||
- `qwen3:14b` — general purpose (summarization, drafting)
|
||||
- `codestral:22b` — code generation assistance
|
||||
- `nomic-embed-text` — embeddings for semantic search
|
||||
|
||||
**Ollama runs on Mike's workstation** and is shared via Tailscale. You don't need to install it locally.
|
||||
|
||||
**To use from your machine (Tailscale must be connected):**
|
||||
```bash
|
||||
curl -s http://100.92.127.64:11434/api/tags
|
||||
```
|
||||
|
||||
If that returns models, you're connected. Claude automatically uses the right URL based on which machine you're on (reads from `identity.json`).
|
||||
|
||||
If it fails: check that Tailscale is connected (`tailscale status`) and Mike's workstation is online.
|
||||
|
||||
### GrepAI (semantic code search)
|
||||
|
||||
Searches code by MEANING rather than exact text. "How does auth work?" finds authentication code even if the word "auth" doesn't appear.
|
||||
|
||||
**Status:** Requires setup per-machine (index build). The `deep-explore` agent uses it. If it's not installed, Claude uses regular grep (still works, just less smart).
|
||||
|
||||
---
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
D:\claudetools\
|
||||
.claude/ — Claude's brain (CLAUDE.md, agents, skills, memory, commands)
|
||||
session-logs/ — General work logs
|
||||
projects/
|
||||
dataforth-dos/ — Dataforth test datasheet pipeline (AD2, testdatadb)
|
||||
msp-tools/
|
||||
guru-rmm/ — GuruRMM agent + server (Rust, our product)
|
||||
newsletter/ — Marketing newsletters
|
||||
clients/
|
||||
dataforth/ — Dataforth-specific client docs
|
||||
pavon/ — Pavon/client docs
|
||||
... — Other clients
|
||||
credentials.md — Quick-reference credentials (vault is source of truth)
|
||||
CONTEXT.md — Root-level project context
|
||||
|
||||
D:\vault\ — SOPS-encrypted credentials (separate repo)
|
||||
infrastructure/ — Our servers (Jupiter, Uranus, pfSense, etc.)
|
||||
clients/ — Client credentials
|
||||
services/ — Service credentials (Cloudflare, Azure, Gitea, etc.)
|
||||
projects/ — Project-specific secrets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Daily workflow
|
||||
|
||||
### Starting a work session
|
||||
1. Open Claude Code in the project directory
|
||||
2. Claude greets you by name (reads identity.json)
|
||||
3. Tell Claude what you're working on — it auto-loads the right context
|
||||
4. Work normally — ask questions, make changes, run commands
|
||||
|
||||
### Ending a work session
|
||||
1. `/save` — creates the session log (DO THIS EVERY TIME)
|
||||
2. `/sync` — pushes everything to Gitea
|
||||
3. Close Claude Code
|
||||
|
||||
### When switching projects mid-session
|
||||
Just say "let's work on GuruRMM" or "switch to Dataforth" — Claude reads the relevant CONTEXT.md and picks up where the last session left off.
|
||||
|
||||
---
|
||||
|
||||
## Things to know
|
||||
|
||||
**Claude remembers across sessions** — via session logs and memory files, not magic. If you don't `/save`, the next session starts cold.
|
||||
|
||||
**Credentials are in the vault** — don't ask Mike for passwords; ask Claude. It decrypts from the vault.
|
||||
|
||||
**Git commits are attributed to YOU** — your name and email appear on every commit from your machine.
|
||||
|
||||
**Production deployments need care** — Claude will warn before destructive operations (git push --force, database drops, service restarts). Read the warnings.
|
||||
|
||||
**If Claude seems confused about a project** — say `/context` and ask it to search for recent work. Or read the project's CONTEXT.md yourself.
|
||||
|
||||
**If something breaks** — session logs have the full history. `git log` shows what changed and who changed it. Gitea keeps everything.
|
||||
|
||||
---
|
||||
|
||||
## Getting help
|
||||
|
||||
- Ask Claude: "What commands do I have?" or "How do I access credentials?"
|
||||
- Read `.claude/CLAUDE.md` for the full rulebook
|
||||
- Check `session-logs/` for recent work examples
|
||||
- Ask Mike
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-04-16*
|
||||
@@ -40,15 +40,6 @@ Please create a comprehensive git checkpoint with the following steps:
|
||||
- Confirm git commit succeeded by running `git log -1`
|
||||
- Report commit status to user
|
||||
|
||||
## Part 3: Refresh Directives (MANDATORY)
|
||||
|
||||
7. **Refresh directives** (MANDATORY):
|
||||
- After checkpoint completion, auto-invoke `/refresh-directives`
|
||||
- Re-read `directives.md` to prevent shortcut-taking
|
||||
- Perform self-assessment for any violations
|
||||
- Confirm commitment to agent coordination rules
|
||||
- Report directives refreshed to user
|
||||
|
||||
## Benefits of Git Checkpoint
|
||||
|
||||
**Git Checkpoint provides:**
|
||||
|
||||
132
.claude/commands/import.md
Normal file
132
.claude/commands/import.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# /import — Ingest a folder into ClaudeTools
|
||||
|
||||
Import any folder of data into the ClaudeTools structure. Claude analyzes each file's content, classifies it, proposes placement, sanitizes credentials, and organizes everything into the correct locations.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/import <path> Import a folder
|
||||
/import <path> --dry-run Show plan without executing
|
||||
/import <path> --client <name> Hint: this data belongs to a specific client
|
||||
/import <path> --project <name> Hint: this data belongs to a specific project
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
The first argument is a folder path to ingest. Everything inside (recursive) is scanned and classified.
|
||||
|
||||
## Process
|
||||
|
||||
Follow these steps IN ORDER. Do not skip any step.
|
||||
|
||||
### Step 1: Scan
|
||||
|
||||
Read the source folder recursively. For each file, note:
|
||||
- Filename + extension
|
||||
- Size
|
||||
- First ~200 lines of content (for text files)
|
||||
- Binary vs text detection
|
||||
|
||||
Skip files >50 MB (flag them for manual review).
|
||||
|
||||
### Step 2: Classify
|
||||
|
||||
For each file, determine its category based on content analysis:
|
||||
|
||||
| Category | Signals | Destination |
|
||||
|---|---|---|
|
||||
| **Session log** | Conversation transcript, dated entries, "accomplished", "session" | `session-logs/` or `projects/*/session-logs/` or `clients/*/session-logs/` |
|
||||
| **Client work** | Client name mentioned, ticket/case references, client-specific infra | `clients/<client>/` |
|
||||
| **Project code** | Source code, configs, build files, READMEs | `projects/<project>/` |
|
||||
| **Credentials** | Passwords, API keys, tokens, connection strings, SSH keys | `D:\vault\` (SOPS encrypted) |
|
||||
| **Infrastructure docs** | Server configs, network diagrams, IP lists, runbooks | `credentials.md` update or memory entry |
|
||||
| **Tool/script** | Standalone utility, automation script, helper | `tools/` or `projects/msp-tools/` |
|
||||
| **Documentation** | Guides, how-tos, notes, procedures | Project-specific docs or root docs |
|
||||
| **Unknown** | Can't classify | Flag for user decision |
|
||||
|
||||
If `--client` or `--project` was specified, weight classification toward that target.
|
||||
|
||||
### Step 3: Credential extraction
|
||||
|
||||
Before placing ANY file, scan for sensitive data:
|
||||
- Passwords (inline, in configs, in notes)
|
||||
- API keys / tokens (any string matching `[A-Za-z0-9_\-]{20,}` near words like key/token/secret)
|
||||
- Connection strings (jdbc:, postgres://, mysql://, mongodb://)
|
||||
- SSH private keys (`-----BEGIN`)
|
||||
- Certificate private keys
|
||||
|
||||
For each credential found:
|
||||
1. Show the user: "Found credential in `<file>`: `<context>` — move to vault?"
|
||||
2. If approved: create a vault SOPS entry, replace inline value with a vault reference
|
||||
3. If declined: leave as-is but warn
|
||||
|
||||
### Step 4: Present plan
|
||||
|
||||
Show a table:
|
||||
|
||||
```
|
||||
SOURCE → DESTINATION ACTION
|
||||
────────────────────────────────────────────────────────────────────────────────────
|
||||
notes/client-acme.md → clients/acme/notes.md copy
|
||||
scripts/backup-check.ps1 → tools/backup-check.ps1 copy
|
||||
creds.txt → D:\vault\clients\acme.sops.yaml vault + delete source
|
||||
session-2026-04-10.md → clients/acme/session-logs/2026-04-10.md copy
|
||||
my-tool/src/main.rs → projects/msp-tools/howard-tools/src/ copy (new project)
|
||||
random-binary.exe → (SKIP - 85 MB, too large) flag
|
||||
unknown-doc.pdf → (UNKNOWN - needs your input) ask
|
||||
```
|
||||
|
||||
Ask: "Does this plan look right? I can adjust any placement before executing."
|
||||
|
||||
### Step 5: Execute
|
||||
|
||||
After approval:
|
||||
1. Copy files to destinations (never move from source — source is the user's data)
|
||||
2. Create destination directories as needed
|
||||
3. Encrypt credential files via SOPS
|
||||
4. Update `MEMORY.md` if new knowledge was gained
|
||||
5. Update project `CONTEXT.md` files if project state changed
|
||||
6. Update `credentials.md` if infrastructure details were discovered
|
||||
|
||||
### Step 6: Report
|
||||
|
||||
Write a summary showing:
|
||||
- Files imported: N
|
||||
- Credentials vaulted: N
|
||||
- New directories created: list
|
||||
- Skipped files: list with reasons
|
||||
- Suggested follow-ups (e.g., "review clients/acme/ for completeness")
|
||||
|
||||
Commit the imported files with message: `import: ingested <N> files from <source_path>`
|
||||
|
||||
## Special cases
|
||||
|
||||
### Claude Code session data (~/.claude/projects/)
|
||||
|
||||
If the source folder IS a Claude Code projects directory (contains `.jsonl` files):
|
||||
- Use `tools/import-sessions.py` to extract summaries first
|
||||
- Then apply the standard classification to the summaries
|
||||
- Don't import raw JSONL (too large, mostly noise)
|
||||
|
||||
### Existing project detection
|
||||
|
||||
If imported code has a `Cargo.toml`, `package.json`, `pyproject.toml`, or similar:
|
||||
- Detect the project name from the manifest
|
||||
- Check if it already exists under `projects/`
|
||||
- If new: propose creating a new project directory
|
||||
- If existing: propose merging into the existing project
|
||||
|
||||
### Duplicate detection
|
||||
|
||||
Before copying, check if a file with the same name already exists at the destination:
|
||||
- If content is identical: skip (report as "already present")
|
||||
- If content differs: ask user which version to keep, or keep both with suffix
|
||||
|
||||
## File placement rules
|
||||
|
||||
Follow the conventions in `.claude/FILE_PLACEMENT_GUIDE.md`. Key rules:
|
||||
- Dataforth work → `projects/dataforth-dos/`
|
||||
- GuruRMM work → `projects/msp-tools/guru-rmm/`
|
||||
- Client work → `clients/<client-name>/`
|
||||
- General session logs → `session-logs/`
|
||||
- Credentials → SOPS vault at `D:\vault\`, NEVER in plaintext in the repo
|
||||
68
.claude/commands/mode.md
Normal file
68
.claude/commands/mode.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# /mode — Set or view the current work mode
|
||||
|
||||
Manually set the work mode, or let it auto-detect. Mode controls the terminal color and adjusts Claude's operational posture.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/mode Show current mode
|
||||
/mode client Switch to client mode (orange)
|
||||
/mode dev Switch to development mode (cyan)
|
||||
/mode infra Switch to infrastructure mode (red)
|
||||
/mode general Switch to general mode (blue)
|
||||
/mode remediation Switch to remediation/365 mode (purple)
|
||||
/mode auto Re-run auto-detection from current context
|
||||
```
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Color | Posture |
|
||||
|---|---|---|
|
||||
| **client** | orange | Working on/for a specific client. Extra care with data handling. Session logs go to `clients/<name>/session-logs/`. Credential access audited. Always identify the client in session logs. |
|
||||
| **dev** | cyan | Building features, writing code, testing. Delegate freely to Coding/Testing agents. Use Ollama for drafts when available. Less confirmation friction on non-destructive operations. |
|
||||
| **infra** | red | Infrastructure work — servers, firewalls, DNS, deployments, backups. Confirm before any destructive or hard-to-reverse operation. Backup-first mentality. Double-check IPs and hostnames. |
|
||||
| **general** | blue | Research, planning, documentation, email drafts, general questions. Default mode. Lightweight posture. |
|
||||
| **remediation** | purple | M365 tenant work, breach investigation, security remediation. Graph API focus. Compliance-grade language. Full audit trail. |
|
||||
|
||||
## When invoked
|
||||
|
||||
1. Set the mode in `.claude/identity.json` under a `"mode"` key
|
||||
2. Run the color change: invoke `/color <mode_color>`
|
||||
3. Confirm to user: "Mode: **<mode>** (<color>)"
|
||||
|
||||
## Auto-detection rules
|
||||
|
||||
When `/mode auto` is called, OR at session start, OR when the user shifts topics, determine mode from context:
|
||||
|
||||
**Priority order (first match wins):**
|
||||
|
||||
1. **remediation** — user said "remediation tool", "365", "breach", "tenant sweep", or `/remediation-tool` was invoked
|
||||
2. **client** — user mentions a client name (check `clients/` subdirectories for name matches), or current work is under `clients/`, or user said "for <client>"
|
||||
3. **infra** — user mentions servers by name/IP (AD2, Jupiter, Uranus, pfSense, 172.16.x.x), SSH commands, firewall rules, DNS changes, service restarts, or "deploy to production"
|
||||
4. **dev** — user mentions code, building, compiling, Rust/Python/Node, cargo, npm, GuruRMM development, writing features, testing, or current work is under `projects/`
|
||||
5. **general** — default if nothing else matches
|
||||
|
||||
**On mode change (auto or manual):**
|
||||
- Update `.claude/identity.json` with `"mode": "<mode>"`
|
||||
- **Tell the user to run `/color <color>`** — Claude cannot invoke `/color` programmatically (it's a built-in CLI command). Include the command inline so the user can copy-paste.
|
||||
- Log the transition: `[MODE] general -> infra (detected: SSH to 172.16.3.30) — run /color red`
|
||||
|
||||
**Silent auto-switching:** When auto-detection triggers a mode change mid-session, announce it as: `[MODE -> infra] /color red` — short, actionable, the user can run the color command or ignore it. Don't interrupt flow with a long explanation. If the detection seems wrong, the user can override with `/mode <correct_mode>`.
|
||||
|
||||
## Session log integration
|
||||
|
||||
Session logs should include the mode in the User section:
|
||||
|
||||
```markdown
|
||||
## User
|
||||
- **User:** Mike Swanson (mike)
|
||||
- **Machine:** DESKTOP-0O8A1RL
|
||||
- **Role:** admin
|
||||
- **Mode:** infra (red)
|
||||
```
|
||||
|
||||
If mode changed during the session, note the transitions:
|
||||
|
||||
```markdown
|
||||
- **Mode:** general → infra → dev (transitioned during session)
|
||||
```
|
||||
@@ -1,306 +0,0 @@
|
||||
# /refresh-directives Command
|
||||
|
||||
**Purpose:** Re-read and internalize operational directives to prevent shortcut-taking and ensure proper agent coordination.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
**Automatic triggers (I should invoke this):**
|
||||
- After conversation compaction/summarization
|
||||
- After completing a large task
|
||||
- When detecting directive violations (database queries, emoji use, etc.)
|
||||
- At start of new work session
|
||||
- After extended conversation (>100 exchanges)
|
||||
|
||||
**Manual invocation:**
|
||||
- User types: `/refresh-directives`
|
||||
- User says: "refresh your directives" or "read your rules again"
|
||||
|
||||
---
|
||||
|
||||
## What This Command Does
|
||||
|
||||
1. **Reads directives.md** - Full file from project root
|
||||
2. **Self-assessment** - Checks recent actions for violations
|
||||
3. **Commitment** - Explicitly commits to following directives
|
||||
4. **Reports to user** - Confirms directives internalized
|
||||
|
||||
---
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Read Directives File
|
||||
```
|
||||
Read tool → D:\ClaudeTools\directives.md
|
||||
```
|
||||
|
||||
**Must read entire file** - All sections are mandatory:
|
||||
- My Identity
|
||||
- Core Operating Principle
|
||||
- What I DO / DO NOT DO
|
||||
- Agent Coordination Rules
|
||||
- Coding Standards (NO EMOJIS)
|
||||
- Enforcement Checklist
|
||||
|
||||
### Step 2: Self-Assessment
|
||||
|
||||
**Check recent conversation for violations:**
|
||||
|
||||
**Database Operations:**
|
||||
- [ ] Did I query database directly? (Violation)
|
||||
- [ ] Did I use ssh/mysql/curl to ClaudeTools API? (Violation)
|
||||
- [ ] Did I delegate to Database Agent? (Correct)
|
||||
|
||||
**Code Generation:**
|
||||
- [ ] Did I write production code myself? (Violation)
|
||||
- [ ] Did I delegate to Coding Agent? (Correct)
|
||||
|
||||
**Emoji Usage:**
|
||||
- [ ] Did I use emojis in code/output? (Violation)
|
||||
- [ ] Did I use ASCII markers [OK]/[ERROR]? (Correct)
|
||||
|
||||
**Agent Coordination:**
|
||||
- [ ] Did I execute operations directly? (Violation)
|
||||
- [ ] Did I coordinate via agents? (Correct)
|
||||
|
||||
### Step 3: Commit to Directives
|
||||
|
||||
**Explicit commitment statement:**
|
||||
|
||||
"I have read and internalized directives.md. I commit to:
|
||||
- Coordinating via agents, not executing directly
|
||||
- Using Database Agent for ALL database operations
|
||||
- Using ASCII markers, NEVER emojis
|
||||
- Preserving my context by delegating
|
||||
- Following the enforcement checklist before every action"
|
||||
|
||||
### Step 4: Report to User
|
||||
|
||||
**Format:**
|
||||
```markdown
|
||||
## Directives Refreshed
|
||||
|
||||
I've re-read and internalized my operational directives from `directives.md`.
|
||||
|
||||
**Key commitments:**
|
||||
- [OK] Coordinate via agents (not execute directly)
|
||||
- [OK] Database Agent handles ALL database operations
|
||||
- [OK] ASCII markers only (no emojis: [OK], [ERROR], [WARNING])
|
||||
- [OK] Preserve context by delegating operations >500 tokens
|
||||
- [OK] Auto-invoke frontend-design skill for UI changes
|
||||
|
||||
**Self-assessment:** [Clean / X violations detected]
|
||||
|
||||
**Status:** Ready to coordinate effectively.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### With /checkpoint Command
|
||||
|
||||
**After git commit + database save:**
|
||||
```
|
||||
1. Execute checkpoint (git + database)
|
||||
2. Verify both succeeded
|
||||
3. Auto-invoke /refresh-directives
|
||||
4. Confirm directives refreshed
|
||||
```
|
||||
|
||||
### With /save Command
|
||||
|
||||
**After creating session log:**
|
||||
```
|
||||
1. Create/append session log
|
||||
2. Commit to repository
|
||||
3. Auto-invoke /refresh-directives
|
||||
4. Confirm directives refreshed
|
||||
```
|
||||
|
||||
### With Session Start
|
||||
|
||||
**When conversation begins:**
|
||||
```
|
||||
1. If directives.md exists → Read it immediately
|
||||
2. If starting new project → Create directives.md first
|
||||
3. Confirm directives internalized before proceeding
|
||||
```
|
||||
|
||||
### After Large Tasks
|
||||
|
||||
**When completing major work:**
|
||||
- Multi-agent coordination (3+ agents)
|
||||
- Complex problem-solving with Sequential Thinking
|
||||
- Database migrations or schema changes
|
||||
- Large code refactoring
|
||||
|
||||
**Trigger:** Auto-invoke /refresh-directives
|
||||
|
||||
---
|
||||
|
||||
## Violation Detection
|
||||
|
||||
**If I detect violations during self-assessment:**
|
||||
|
||||
1. **Acknowledge violations:**
|
||||
```
|
||||
[WARNING] Detected X directive violations in recent conversation:
|
||||
- Violation 1: Direct database query at [timestamp]
|
||||
- Violation 2: Emoji usage in output at [timestamp]
|
||||
```
|
||||
|
||||
2. **Commit to correction:**
|
||||
```
|
||||
[OK] Corrective actions:
|
||||
- Will use Database Agent for all future database operations
|
||||
- Will use ASCII markers [OK]/[ERROR] instead of emojis
|
||||
```
|
||||
|
||||
3. **Reset behavior:**
|
||||
```
|
||||
[SUCCESS] Directives re-internalized. Proceeding with proper coordination.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Usage
|
||||
|
||||
### User-Invoked
|
||||
```
|
||||
User: /refresh-directives
|
||||
|
||||
Claude:
|
||||
[Reads directives.md]
|
||||
[Performs self-assessment]
|
||||
[Commits to directives]
|
||||
|
||||
## Directives Refreshed
|
||||
|
||||
I've re-read my operational directives.
|
||||
|
||||
**Key commitments:**
|
||||
- [OK] Coordinate via agents, not execute
|
||||
- [OK] Database Agent for ALL data operations
|
||||
- [OK] ASCII markers only (no emojis)
|
||||
- [OK] Preserve context by delegating
|
||||
|
||||
**Self-assessment:** Clean - no violations detected
|
||||
|
||||
**Status:** Ready to coordinate effectively.
|
||||
```
|
||||
|
||||
### Auto-Invoked After Checkpoint
|
||||
```
|
||||
Claude: [Completes /checkpoint command]
|
||||
Claude: [Auto-invokes /refresh-directives]
|
||||
Claude: [Reads directives.md]
|
||||
Claude: [Confirms directives internalized]
|
||||
|
||||
Checkpoint complete. Directives refreshed. Ready for next task.
|
||||
```
|
||||
|
||||
### Auto-Invoked After Conversation Compaction
|
||||
```
|
||||
System: [Conversation compacted]
|
||||
Claude: [Detects compaction occurred]
|
||||
Claude: [Auto-invokes /refresh-directives]
|
||||
Claude: [Reads directives.md]
|
||||
Claude: [Confirms ready to proceed]
|
||||
|
||||
Context compacted. Directives re-internalized. Continuing coordination.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Hook Integration
|
||||
|
||||
**Create hook:** `.claude/hooks/refresh-directives`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Hook: Refresh Directives
|
||||
# Triggers: session-start, post-checkpoint, post-compaction
|
||||
|
||||
echo "[INFO] Triggering directives refresh..."
|
||||
echo "Reading: D:/ClaudeTools/directives.md"
|
||||
echo "[OK] Directives file available for refresh"
|
||||
```
|
||||
|
||||
### Command Recognition
|
||||
|
||||
**User input patterns:**
|
||||
- `/refresh-directives`
|
||||
- `/refresh`
|
||||
- "refresh your directives"
|
||||
- "read your rules again"
|
||||
- "re-read directives"
|
||||
|
||||
**Auto-trigger patterns:**
|
||||
- After `/checkpoint` success
|
||||
- After `/save` success
|
||||
- After conversation compaction (detect via system messages)
|
||||
- Every 50 tool uses (counter-based)
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
### Prevents Shortcut-Taking
|
||||
- Reminds me not to query database directly
|
||||
- Reinforces agent coordination model
|
||||
- Stops emoji usage before it happens
|
||||
|
||||
### Context Recovery
|
||||
- Restores operational mode after compaction
|
||||
- Ensures consistency across sessions
|
||||
- Maintains coordination principles
|
||||
|
||||
### Self-Correction
|
||||
- Detects violations automatically
|
||||
- Commits to corrective behavior
|
||||
- Provides accountability
|
||||
|
||||
### User Visibility
|
||||
- User sees when directives refreshed
|
||||
- Transparency in operational changes
|
||||
- Builds trust in coordination model
|
||||
|
||||
---
|
||||
|
||||
## Enforcement
|
||||
|
||||
**Mandatory refresh points:**
|
||||
1. [OK] Session start (if directives.md exists)
|
||||
2. [OK] After conversation compaction
|
||||
3. [OK] After /checkpoint command
|
||||
4. [OK] After /save command
|
||||
5. [OK] When user requests: /refresh-directives
|
||||
6. [OK] After completing large tasks (3+ agents)
|
||||
|
||||
**Optional refresh points:**
|
||||
- Every 50 tool uses (counter-based)
|
||||
- When detecting potential violations
|
||||
- Before critical operations (migrations, deployments)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**This command ensures I:**
|
||||
- Never forget my role as Coordinator
|
||||
- Always delegate to appropriate agents
|
||||
- Use ASCII markers, never emojis
|
||||
- Follow enforcement checklist
|
||||
- Maintain proper agent architecture
|
||||
|
||||
**Result:** Consistent, rule-following behavior across all sessions and contexts.
|
||||
|
||||
---
|
||||
|
||||
**Created:** 2026-01-19
|
||||
**Purpose:** Enforce directives.md compliance throughout session lifecycle
|
||||
**Status:** Active - auto-invoke at trigger points
|
||||
192
.claude/commands/remediation-tool.md
Normal file
192
.claude/commands/remediation-tool.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
description: M365 tenant investigation + remediation via the ComputerGuru tiered MSP app suite. Breach checks, tenant sweeps, consent URLs, and gated remediation actions.
|
||||
---
|
||||
|
||||
# /remediation-tool
|
||||
|
||||
M365 investigation and remediation using the **ComputerGuru tiered MSP app suite** — five multi-tenant apps covering read-only investigation, Exchange write operations, user lifecycle management, high-privilege tenant admin, and optional Defender ATP.
|
||||
|
||||
**Default posture: READ-ONLY.** Remediation actions require explicit `YES` confirmation in chat.
|
||||
|
||||
---
|
||||
|
||||
## App Tiers (quick reference)
|
||||
|
||||
| Tier flag | App | App ID | Use for |
|
||||
|---|---|---|---|
|
||||
| `investigator` | ComputerGuru Security Investigator | `bfbc12a4` | All read-only breach checks via Graph |
|
||||
| `investigator-exo` | ComputerGuru Security Investigator | `bfbc12a4` | Exchange read: Get-InboxRule (hidden), Get-Mailbox, permissions |
|
||||
| `exchange-op` | ComputerGuru Exchange Operator | `b43e7342` | Exchange write: Set-Mailbox, Remove-InboxRule, session revoke |
|
||||
| `user-manager` | ComputerGuru User Manager | `64fac46b` | User create/disable, license assign, MFA reset, password reset |
|
||||
| `tenant-admin` | ComputerGuru Tenant Admin | `709e6eed` | App role assignments, CA policy, high-privilege directory |
|
||||
| `defender` | ComputerGuru Defender Add-on | `dbf8ad1a` | Alerts, machine risk, vuln data — MDE-licensed tenants only |
|
||||
|
||||
Pass the tier flag to `get-token.sh`:
|
||||
```bash
|
||||
bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> <tier>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Form | What it does |
|
||||
|---|---|
|
||||
| `/remediation-tool check <upn>` | 10-point breach check on a single user |
|
||||
| `/remediation-tool sweep <domain>` | Tenant-wide signals (sign-ins, audits, risky users, guests) |
|
||||
| `/remediation-tool signins <domain> [--user upn] [--failed-only] [--days N]` | Ad-hoc sign-in query |
|
||||
| `/remediation-tool consent-url <domain> [--app <tier>]` | Emit admin consent URL for a tenant + app |
|
||||
| `/remediation-tool remediate <upn> <action>` | **GATED:** revoke-sessions, disable-forwarding, remove-inbox-rules, disable-account, password-reset |
|
||||
|
||||
`<domain>` accepts a tenant domain (`cascadestucson.com`), a UPN (`user@domain.com`), or a tenant GUID.
|
||||
|
||||
---
|
||||
|
||||
## Workflow Claude should follow
|
||||
|
||||
### 0. Parse invocation
|
||||
|
||||
- Extract subcommand, target, and any flags from `$ARGUMENTS`.
|
||||
- Normalize: UPN -> domain (split on `@`), domain -> look up tenant-id.
|
||||
- If the target is ambiguous or missing, ask the user once and proceed.
|
||||
|
||||
### 1. Resolve tenant ID
|
||||
|
||||
Run `bash .claude/skills/remediation-tool/scripts/resolve-tenant.sh <domain>` — returns tenant GUID via OpenID discovery. If it fails, the domain is not in Entra ID; surface the error and stop.
|
||||
|
||||
### 2. Acquire tokens (cached)
|
||||
|
||||
Use the minimum-privilege tier for the task. Most breach checks only need:
|
||||
```bash
|
||||
GT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> investigator)
|
||||
ET=$(bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> investigator-exo)
|
||||
```
|
||||
|
||||
Escalate to write tiers only for remediation:
|
||||
```bash
|
||||
# Exchange write (disable-forwarding, remove-inbox-rules)
|
||||
EXO_WRITE=$(bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> exchange-op)
|
||||
|
||||
# User write (revoke-sessions, disable-account, password-reset, MFA reset)
|
||||
UT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> user-manager)
|
||||
|
||||
# Defender (MDE tenants only)
|
||||
DT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh <tenant-id> defender)
|
||||
```
|
||||
|
||||
Tokens cache at `/tmp/remediation-tool/{tenant}/{tier}.jwt` with 55-minute TTL.
|
||||
|
||||
If a token returns 403/401 on first use, check `.claude/skills/remediation-tool/references/gotchas.md` for per-tenant prerequisites and emit the appropriate consent or role-assignment link.
|
||||
|
||||
### 3. Run the requested checks
|
||||
|
||||
- **`check <upn>`** -> `bash scripts/user-breach-check.sh <tenant> <upn>`. Runs all 10 checks and dumps raw JSON to `/tmp/remediation-tool/{tenant}/user-breach/<slug>/`. Interpret against `references/checklist.md` and write report.
|
||||
|
||||
- **`sweep <domain>`** -> `bash scripts/tenant-sweep.sh <tenant>`. Pulls tenant-wide failed sign-ins (30d), successful non-US sign-ins, directory audits filtered for consent/auth-method/service-principal changes, risky users, B2B guest invites. Claude summarizes priority findings.
|
||||
|
||||
- **`signins`** — build ad-hoc `curl` against Graph `/auditLogs/signIns` with the requested filter. Use `investigator` tier.
|
||||
|
||||
- **`consent-url <domain> [--app <tier>]`** — emit the appropriate admin consent URL (see below). Default to Security Investigator (`investigator`) unless `--app` specifies another tier.
|
||||
|
||||
- **`remediate`** — see Remediation section below.
|
||||
|
||||
### 4. Write the report
|
||||
|
||||
Location: `clients/{client-slug}/reports/YYYY-MM-DD-{action}.md` (UTC date). Derive client slug from domain:
|
||||
- `cascadestucson.com` -> `cascades-tucson`
|
||||
- `grabblaw.com` -> `grabblaw`
|
||||
- Use existing `clients/<slug>/` directory if present; if no match, ask the user for the slug.
|
||||
|
||||
Use `templates/breach-report.md` as skeleton. For single-user checks, fill per-check findings from raw JSON.
|
||||
|
||||
### 5. Summarize to the user
|
||||
|
||||
Short chat summary: top findings, blocked checks (with remediation links), next actions. Save raw JSON artifact paths in the report.
|
||||
|
||||
### 6. Auto-commit
|
||||
|
||||
After writing the report, delegate to the **Gitea Agent** to commit with `Remediation report: <action> for <target>`. Do not push unless the user asks.
|
||||
|
||||
---
|
||||
|
||||
## Admin Consent URLs
|
||||
|
||||
Each app must be individually consented in each customer tenant. Consent URL format:
|
||||
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={app-id}&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Security Investigator** (read-only — consent this first):
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id=bfbc12a4-f0dd-4e12-b06d-997e7271e10c&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Exchange Operator** (EXO write — consent when remediation needed):
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id=b43e7342-5b4b-492f-890f-bb5a4f7f40e9&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**User Manager** (user/license write):
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id=64fac46b-8b44-41ad-93ee-7da03927576c&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Tenant Admin** (high-privilege — use sparingly):
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Defender Add-on** (MDE-licensed tenants only):
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id=dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
The customer admin must sign in as Global Admin of that tenant and click Accept. Redirect lands on azcomputerguru.com — that is expected. Verify consent via `/servicePrincipals/{sp-id}/appRoleAssignments` (new grants should be timestamped today).
|
||||
|
||||
---
|
||||
|
||||
## Remediation (gated)
|
||||
|
||||
When the user runs `/remediation-tool remediate <upn> <action>`:
|
||||
|
||||
1. **Confirm read-only context first**: skill must have recently run `check <upn>` in this session (check `/tmp/remediation-tool/{tenant}/user-breach/<slug>/` exists). If not, tell the user to run the check first.
|
||||
2. **Display the exact action** (curl command, cmdlet name, parameters).
|
||||
3. **Require explicit `YES` in chat** — not a permission prompt. Anything else aborts.
|
||||
4. Execute via the appropriate app tier. Capture response to `/tmp/remediation-tool/{tenant}/remediation/<slug>-YYYY-MM-DDTHHMMSS.json`.
|
||||
5. Update the user's report with a `## Remediation Actions` section.
|
||||
|
||||
Allowed actions and which tier handles them:
|
||||
|
||||
| Action | App tier | API |
|
||||
|---|---|---|
|
||||
| `revoke-sessions` | `user-manager` | Graph `POST /users/{upn}/revokeSignInSessions` |
|
||||
| `disable-account` | `user-manager` | Graph `PATCH /users/{upn}` with `accountEnabled: false` |
|
||||
| `password-reset` | `user-manager` | Graph `PATCH /users/{upn}` with new `passwordProfile` |
|
||||
| `disable-forwarding` | `exchange-op` | Exchange REST `Set-Mailbox -ForwardingAddress $null -ForwardingSmtpAddress $null -DeliverToMailboxAndForward $false` |
|
||||
| `remove-inbox-rules` | `exchange-op` | Exchange REST `Remove-InboxRule` per non-default rule (ask which to keep first) |
|
||||
| `disable-smtp-auth` | `exchange-op` | Exchange REST `Set-CASMailbox -SmtpClientAuthenticationDisabled $true` |
|
||||
|
||||
---
|
||||
|
||||
## Arguments
|
||||
|
||||
`$ARGUMENTS` — the full invocation text. Parse freely; common forms:
|
||||
|
||||
- `check john.trozzi@cascadestucson.com`
|
||||
- `sweep cascadestucson.com`
|
||||
- `signins cascadestucson.com --user megan.hiatt@cascadestucson.com --failed-only --days 30`
|
||||
- `consent-url cascadestucson.com`
|
||||
- `consent-url grabblaw.com --app exchange-op`
|
||||
- `remediate megan.hiatt@cascadestucson.com revoke-sessions`
|
||||
|
||||
If the user's phrasing is loose ("check john's box at cascades", "who's being attacked"), infer intent from CONTEXT.md and session logs. Prefer asking one clarifying question to guessing.
|
||||
|
||||
---
|
||||
|
||||
## Scope and references
|
||||
|
||||
- Detailed check rubric: `.claude/skills/remediation-tool/references/checklist.md`
|
||||
- Permission/role gotchas + consent URLs: `.claude/skills/remediation-tool/references/gotchas.md`
|
||||
- Endpoint cheatsheet: `.claude/skills/remediation-tool/references/graph-endpoints.md`
|
||||
- Report template: `.claude/skills/remediation-tool/templates/breach-report.md`
|
||||
@@ -72,15 +72,32 @@ Format credentials as:
|
||||
|
||||
## After Saving
|
||||
|
||||
Before committing, emit a **Change Summary** block for the user to review:
|
||||
|
||||
```
|
||||
## Change Summary (this session)
|
||||
User: <full_name> (from .claude/identity.json)
|
||||
Machine: <HOSTNAME>
|
||||
|
||||
Files changed:
|
||||
<output of: git status --short>
|
||||
|
||||
Stats:
|
||||
<output of: git diff --stat HEAD>
|
||||
```
|
||||
|
||||
Then:
|
||||
1. Commit with message: "Session log: [brief description of work done]"
|
||||
2. Push to gitea remote (if configured)
|
||||
3. Confirm push was successful
|
||||
4. **Refresh directives** (MANDATORY):
|
||||
- Auto-invoke `/refresh-directives`
|
||||
- Re-read `directives.md` to prevent shortcut-taking
|
||||
- Perform self-assessment for violations
|
||||
- Confirm commitment to coordination rules
|
||||
- Report directives refreshed
|
||||
3. After push, emit a **Post-commit Summary**:
|
||||
- New commit SHA + message
|
||||
- Author (from `git log -1 --format='%an <%ae>'`)
|
||||
- Files in the commit (from `git show --stat HEAD`)
|
||||
4. Confirm push was successful
|
||||
|
||||
### Why the summary
|
||||
|
||||
In the multi-user setup, commits can land in `main` from either team member. Always attributing author + files makes it obvious who made what change when someone else pulls the repo. Saves re-reading diffs to figure out "wait, when did that happen?"
|
||||
|
||||
## Purpose
|
||||
|
||||
|
||||
@@ -32,6 +32,4 @@ Quick command to save session log, stage everything, and push to Gitea in one sh
|
||||
|
||||
## Important
|
||||
- This is a FAST command - no lengthy analysis, just save and ship
|
||||
- Do NOT invoke /refresh-directives afterward (unlike /sync)
|
||||
- Do NOT read behavioral guidelines beyond the role reaffirmation above
|
||||
- Just save, commit, push, reaffirm, report
|
||||
|
||||
169
.claude/commands/syncro.md
Normal file
169
.claude/commands/syncro.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# /syncro — Syncro PSA ticket management
|
||||
|
||||
Create, update, close, comment on, and bill tickets in Syncro PSA.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/syncro Show open tickets summary
|
||||
/syncro ticket <number> View ticket details + comments
|
||||
/syncro create <customer> <subject> Create new ticket
|
||||
/syncro update <number> <status> Update ticket status
|
||||
/syncro close <number> Close/resolve a ticket
|
||||
/syncro comment <number> <text> Add a comment to a ticket
|
||||
/syncro bill <number> Create invoice from ticket time entries
|
||||
/syncro search <query> Search tickets by subject/customer
|
||||
/syncro customers <query> Search customers
|
||||
```
|
||||
|
||||
## API Configuration
|
||||
|
||||
**Base URL:** `https://computerguru.syncromsp.com/api/v1`
|
||||
**API Key:** SOPS vault `msp-tools/syncro.sops.yaml` → `credentials.credential`
|
||||
**Rate limit:** 180 requests/minute per IP
|
||||
**Docs:** https://api-docs.syncromsp.com/
|
||||
|
||||
## Implementation
|
||||
|
||||
When invoked, use the Syncro REST API via `curl`. All requests include `?api_key=<key>` as query parameter (NOT in header — Syncro uses query param auth).
|
||||
|
||||
### Get API key
|
||||
|
||||
```bash
|
||||
API_KEY=$(bash D:/vault/scripts/vault.sh get-field msp-tools/syncro.sops.yaml credentials.credential)
|
||||
BASE="https://computerguru.syncromsp.com/api/v1"
|
||||
```
|
||||
|
||||
If `vault.sh get-field` fails (yq not installed), fall back to:
|
||||
```bash
|
||||
API_KEY=$(sops -d D:/vault/msp-tools/syncro.sops.yaml | python -c "import sys,yaml; print(yaml.safe_load(sys.stdin)['credentials']['credential'])")
|
||||
```
|
||||
|
||||
### Endpoints reference
|
||||
|
||||
#### Tickets
|
||||
|
||||
| Operation | Method | Endpoint | Body |
|
||||
|---|---|---|---|
|
||||
| List tickets | GET | `/tickets?status=<status>&per_page=25` | — |
|
||||
| Get ticket | GET | `/tickets/<id>` | — |
|
||||
| Create ticket | POST | `/tickets` | `{"customer_id": N, "subject": "...", "problem_type": "...", "status": "New"}` |
|
||||
| Update ticket | PUT | `/tickets/<id>` | `{"status": "In Progress", "priority": "..."}` |
|
||||
| Delete ticket | DELETE | `/tickets/<id>` | — |
|
||||
|
||||
**Ticket statuses:** `New`, `In Progress`, `Waiting on Customer`, `Waiting on Vendor`, `Scheduled`, `Resolved`, `Invoiced`, `Closed`
|
||||
|
||||
**Ticket fields (create/update):**
|
||||
- `customer_id` (required for create)
|
||||
- `subject` (required for create)
|
||||
- `problem_type` (string, free-form)
|
||||
- `status` (string, one of the statuses above)
|
||||
- `priority` (string)
|
||||
- `due_date` (ISO date)
|
||||
- `user_id` (assign to tech)
|
||||
- `contact_id` (customer contact)
|
||||
- `ticket_type_id` (ticket category)
|
||||
|
||||
#### Comments (with optional time entry)
|
||||
|
||||
| Operation | Method | Endpoint | Body |
|
||||
|---|---|---|---|
|
||||
| Add comment | POST | `/tickets/<id>/comment` | `{"subject": "Update", "body": "...", "hidden": false, "do_not_email": false}` |
|
||||
| Add comment + time | POST | `/tickets/<id>/comment` | Same as above, PLUS: `"product_id": N, "minutes_spent": 60, "bill_time_now": false` |
|
||||
|
||||
**Comment fields:**
|
||||
- `subject` — comment header (e.g., "Update", "Resolution", "Internal Note")
|
||||
- `body` — comment text (HTML supported)
|
||||
- `hidden` — if true, internal-only (customer can't see)
|
||||
- `do_not_email` — if true, don't email customer about this comment
|
||||
- `product_id` — labor product ID (see labor products table below). Adds billable time to the ticket.
|
||||
- `minutes_spent` — integer, minutes of work (60 = 1hr minimum in most cases)
|
||||
- `bill_time_now` — if true, immediately creates a charge (equivalent to "Charge now" checkbox in GUI)
|
||||
|
||||
**This is the primary way to log time.** Comment + time in one call mirrors the GUI workflow exactly. Timer entries (`/tickets/{id}/timer_entry`) exist but are rarely used.
|
||||
|
||||
#### Customers
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|---|---|---|
|
||||
| List/search | GET | `/customers?query=<search>&per_page=25` |
|
||||
| Get customer | GET | `/customers/<id>` |
|
||||
| Create customer | POST | `/customers` |
|
||||
|
||||
#### Timer Entries (add time to ticket)
|
||||
|
||||
| Operation | Method | Endpoint | Body |
|
||||
|---|---|---|---|
|
||||
| Add time | POST | `/tickets/<id>/timer_entry` | `{"start_at": "ISO8601", "end_at": "ISO8601", "notes": "...", "billable": true, "product_id": N}` |
|
||||
| List timers | GET | `/ticket_timers?ticket_id=<id>` |
|
||||
|
||||
**IMPORTANT:** `product_id` must be a **labor product**, not an invoice product. Common labor products:
|
||||
- `1190473` — Labor - Remote Business (standard remote work)
|
||||
- `26118` — Labor - Onsite Business
|
||||
- `26184` — Labor - Emergency or After Hours Business
|
||||
- `9269129` — Labor - Prepaid Project Labor
|
||||
- `9269124` — Labor - Internal Labor
|
||||
- `26117` — Fee - Travel Time
|
||||
- `68055` — Labor - Website Labor
|
||||
|
||||
#### Invoices
|
||||
|
||||
| Operation | Method | Endpoint | Body |
|
||||
|---|---|---|---|
|
||||
| List invoices | GET | `/invoices?per_page=25` |
|
||||
| Get invoice | GET | `/invoices/<id>` |
|
||||
| Create from ticket | POST | `/invoices` | `{"ticket_id": N, "customer_id": N, "category": "Standard"}` |
|
||||
| Delete invoice | DELETE | `/invoices/<id>` | — |
|
||||
|
||||
**"Make Invoice" flow:** Timer entries on the ticket become invoice line items when you POST `/invoices` with the ticket_id. This is the equivalent of clicking "Make Invoice" in the GUI.
|
||||
|
||||
#### Invoice Line Items
|
||||
|
||||
| Operation | Method | Endpoint | Body |
|
||||
|---|---|---|---|
|
||||
| Add line item | POST | `/invoices/<id>/line_items` | `{"item": "...", "quantity": 1, "price": 125.00, "product_id": N}` |
|
||||
|
||||
### Display formatting
|
||||
|
||||
When showing ticket lists, format as:
|
||||
|
||||
```
|
||||
#32164 New Jerry Burger Own cloud thing again
|
||||
#32163 New LeeAnn Parkinson Remote - Jim cant access his email
|
||||
#32162 Invoiced Len's Auto Brokerage Server upgrade
|
||||
```
|
||||
|
||||
When showing ticket detail, include:
|
||||
- Ticket number, subject, status, priority
|
||||
- Customer name + contact
|
||||
- Created date, due date, last updated
|
||||
- Assigned tech
|
||||
- Comments (most recent first, truncated to last 5)
|
||||
- Time entries if any
|
||||
- Billing status
|
||||
|
||||
### Billing workflow
|
||||
|
||||
When `/syncro bill <number>` is called:
|
||||
1. Get ticket details
|
||||
2. Ask: "How many minutes + labor type?" (default: 60 min, Labor - Remote Business)
|
||||
3. Add comment with time: `POST /tickets/{id}/comment` with `product_id`, `minutes_spent`, `bill_time_now: false`, and work notes as body
|
||||
4. Then create invoice: `POST /invoices` with `{"ticket_id": N, "customer_id": N, "category": "Standard"}`
|
||||
5. Update ticket status to "Invoiced"
|
||||
|
||||
**The flow mirrors the GUI: add comment with time attached → Make Invoice.**
|
||||
|
||||
When `/syncro comment <number> <text> --time 60 --labor remote` is called:
|
||||
- Post the comment with time in one API call
|
||||
- `--labor` maps to product IDs: `remote` → 1190473, `onsite` → 26118, `emergency` → 26184, `project` → 9269129, `internal` → 9269124, `travel` → 26117, `website` → 68055
|
||||
|
||||
### Error handling
|
||||
|
||||
- 401: API key invalid or expired
|
||||
- 404: ticket/customer/invoice not found
|
||||
- 422: validation error (show the error message from response body)
|
||||
- 429: rate limited (wait 60s and retry)
|
||||
|
||||
### Integration with session logs
|
||||
|
||||
When closing a ticket (`/syncro close`), offer to create a session log entry in `clients/<customer>/session-logs/` documenting what was resolved. Pull the ticket subject, comments, and resolution into a structured log.
|
||||
396
.claude/gururmm-tunnel-plan.md
Normal file
396
.claude/gururmm-tunnel-plan.md
Normal file
@@ -0,0 +1,396 @@
|
||||
# GuruRMM Real-Time Tunnel Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Transform GuruRMM agents from periodic check-in mode (30-second heartbeats) to persistent tunnel mode, enabling Claude Code on tech workstation to execute commands on remote machines through secure multiplexed channels.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
### Current State (Confirmed via exploration)
|
||||
- **Server:** Axum 0.7 @ 172.16.3.30:3001, WebSocket endpoint, AgentConnections HashMap
|
||||
- **Agent:** Tokio async, 30-second heartbeat confirmed, 3 concurrent tasks (metrics/network/heartbeat)
|
||||
- **Protocol:** Tagged JSON enums (ServerMessage/AgentMessage) with serde
|
||||
|
||||
### Key Architectural Decisions
|
||||
|
||||
1. **Tunnel Lifecycle:** Hybrid - WebSocket stays persistent, tunnel mode is operational state change
|
||||
- Agent modes: Heartbeat (default) ↔ Tunnel (active session)
|
||||
- One tunnel per agent, on-demand activation, instant mode switching
|
||||
|
||||
2. **Channel Multiplexing:** Unified protocol with channel_id routing
|
||||
- Single WebSocket, multiple logical channels
|
||||
- Enables concurrent operations (multiple terminals, simultaneous file transfers)
|
||||
- Channel types: Terminal, FileRead, FileWrite, FileList, Registry, Services
|
||||
|
||||
3. **Claude Integration:** Custom MCP server
|
||||
- Tools: `gururmm_run_command`, `gururmm_read_file`, `gururmm_write_file`, `gururmm_list_directory`, `gururmm_list_agents`
|
||||
- JWT authentication via environment variable
|
||||
- Auto-manages tunnel sessions (open on first use, keep-alive, close on idle)
|
||||
|
||||
4. **Security:** Three-layer model
|
||||
- Layer 1: JWT authentication (24h expiration)
|
||||
- Layer 2: Session authorization (tech_sessions table, 4h inactivity timeout)
|
||||
- Layer 3: Command validation (working directory allowlist, rate limiting 100/min, audit logging)
|
||||
|
||||
---
|
||||
|
||||
## Protocol Extensions
|
||||
|
||||
### New Message Types
|
||||
|
||||
```rust
|
||||
// Server → Agent
|
||||
enum ServerMessage {
|
||||
// ... existing ...
|
||||
TunnelOpen { session_id: String, tech_id: i32 },
|
||||
TunnelClose { session_id: String },
|
||||
TunnelData { channel_id: String, data: TunnelDataPayload },
|
||||
}
|
||||
|
||||
// Agent → Server
|
||||
enum AgentMessage {
|
||||
// ... existing ...
|
||||
TunnelReady { session_id: String },
|
||||
TunnelData { channel_id: String, data: TunnelDataPayload },
|
||||
TunnelError { channel_id: String, error: String },
|
||||
}
|
||||
|
||||
enum TunnelDataPayload {
|
||||
Terminal { command: String },
|
||||
TerminalOutput { stdout: String, stderr: String, exit_code: Option<i32> },
|
||||
FileRead { path: String },
|
||||
FileContent { content: Vec<u8>, mime_type: String },
|
||||
FileWrite { path: String, content: Vec<u8> },
|
||||
FileList { path: String },
|
||||
FileListResult { entries: Vec<FileEntry> },
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Mode State Machine
|
||||
|
||||
```rust
|
||||
enum AgentMode {
|
||||
Heartbeat, // Default: 30s heartbeats, metrics, network monitoring
|
||||
Tunnel {
|
||||
session_id: String,
|
||||
tech_id: i32,
|
||||
channels: HashMap<String, ChannelType>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Core Tunnel Infrastructure (Week 1)
|
||||
**Goal:** Establish tunnel mode switching and channel routing
|
||||
|
||||
**Server:**
|
||||
- Add TunnelOpen/TunnelClose/TunnelData to ServerMessage enum
|
||||
- Create tech_sessions table (id, session_id, tech_id, agent_id, opened_at, last_activity, status)
|
||||
- Implement endpoints: POST /api/v1/tunnel/open, POST /close, GET /status/:session_id
|
||||
- Add channel routing in WebSocket handler (route by channel_id)
|
||||
- Session validation middleware (JWT + ownership check)
|
||||
|
||||
**Agent:**
|
||||
- Add TunnelReady/TunnelData/TunnelError to AgentMessage enum
|
||||
- Implement AgentMode state machine
|
||||
- Add channel manager (HashMap<channel_id, ChannelHandler>)
|
||||
- Handle TunnelOpen → respond TunnelReady
|
||||
- Handle TunnelClose → cleanup channels, return to heartbeat mode
|
||||
|
||||
**Critical Files:**
|
||||
- `server/src/ws/mod.rs` - WebSocket handler, protocol definitions
|
||||
- `server/src/routes/tunnel.rs` - NEW: Tunnel API endpoints
|
||||
- `server/src/middleware/auth.rs` - Session validation
|
||||
- `agent/src/transport/websocket.rs` - WebSocket client, protocol handling
|
||||
- `agent/src/tunnel/mod.rs` - NEW: Tunnel mode manager
|
||||
- `migrations/XXX_create_tech_sessions.sql` - NEW: Database schema
|
||||
|
||||
### Phase 2: Terminal Channel (Week 2)
|
||||
**Goal:** Execute PowerShell/cmd/bash commands through tunnel
|
||||
|
||||
**Implementation:**
|
||||
- Create TerminalChannel handler on agent (spawn child process, capture streams)
|
||||
- Implement TunnelDataPayload::Terminal on server
|
||||
- Working directory validation on agent (configurable allowlist)
|
||||
- Command result streaming for long-running commands
|
||||
- Endpoint: POST /api/v1/tunnel/:session_id/command
|
||||
|
||||
**Critical Files:**
|
||||
- `agent/src/tunnel/terminal.rs` - NEW: Terminal channel handler
|
||||
- `server/src/routes/tunnel.rs` - Add command execution endpoint
|
||||
- `agent/config.toml` - Add allowed_paths configuration
|
||||
|
||||
### Phase 3: File Operations (Week 3)
|
||||
**Goal:** Read, write, list files through tunnel
|
||||
|
||||
**Implementation:**
|
||||
- Create FileChannel handler on agent
|
||||
- Chunked transfer for files > 1MB (transfer_id tracking)
|
||||
- Base64 encoding for binary data
|
||||
- MIME type detection (magic numbers)
|
||||
- Endpoints: GET /file, PUT /file, POST /file/list
|
||||
|
||||
**Critical Files:**
|
||||
- `agent/src/tunnel/file.rs` - NEW: File channel handler
|
||||
- `server/src/routes/tunnel.rs` - Add file operation endpoints
|
||||
- `common/src/transfer.rs` - NEW: Chunked transfer utilities
|
||||
|
||||
### Phase 4: MCP Server Integration (Week 4)
|
||||
**Goal:** Expose tunnel operations as MCP tools for Claude Code
|
||||
|
||||
**Implementation:**
|
||||
- Create new project: `gururmm-mcp-server` (Rust)
|
||||
- Use `mcp-server-rs` crate
|
||||
- Implement 5 core tools (run_command, read_file, write_file, list_dir, list_agents)
|
||||
- JWT token from environment variable (GURURMM_AUTH_TOKEN)
|
||||
- Auto-manage tunnel sessions (open on first tool use, 5min idle timeout)
|
||||
|
||||
**Critical Files:**
|
||||
- `mcp-server/src/main.rs` - NEW: MCP server entry point
|
||||
- `mcp-server/src/tools.rs` - NEW: Tool implementations
|
||||
- `mcp-server/src/session.rs` - NEW: Session manager
|
||||
- `mcp-server/Cargo.toml` - NEW: Dependencies
|
||||
|
||||
**MCP Config Example:**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"gururmm": {
|
||||
"command": "gururmm-mcp-server",
|
||||
"env": {
|
||||
"GURURMM_API_URL": "http://172.16.3.30:3001",
|
||||
"GURURMM_AUTH_TOKEN": "jwt-token-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Advanced Features (Week 5+)
|
||||
- Registry operations (Windows winreg crate)
|
||||
- Service management (sc.exe/WMI on Windows, systemctl on Linux)
|
||||
- Interactive terminal with PTY (stretch goal)
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE tech_sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(36) UNIQUE NOT NULL,
|
||||
tech_id INTEGER NOT NULL REFERENCES techs(id),
|
||||
agent_id INTEGER NOT NULL REFERENCES agents(id),
|
||||
opened_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
last_activity TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
closed_at TIMESTAMP,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
UNIQUE(tech_id, agent_id, status) WHERE status = 'active'
|
||||
);
|
||||
|
||||
CREATE TABLE tunnel_audit (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(36) NOT NULL REFERENCES tech_sessions(session_id),
|
||||
channel_id VARCHAR(36) NOT NULL,
|
||||
operation VARCHAR(50) NOT NULL,
|
||||
details JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tech_sessions_tech ON tech_sessions(tech_id);
|
||||
CREATE INDEX idx_tech_sessions_agent ON tech_sessions(agent_id);
|
||||
CREATE INDEX idx_tunnel_audit_session ON tunnel_audit(session_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints (New)
|
||||
|
||||
```
|
||||
POST /api/v1/tunnel/open
|
||||
Body: { "agent_id": 123 }
|
||||
Response: { "session_id": "uuid", "status": "active" }
|
||||
|
||||
POST /api/v1/tunnel/close
|
||||
Body: { "session_id": "uuid" }
|
||||
|
||||
GET /api/v1/tunnel/status/:session_id
|
||||
|
||||
POST /api/v1/tunnel/:session_id/command
|
||||
Body: { "command": "...", "shell": "powershell", "working_dir": "...", "timeout": 30000 }
|
||||
|
||||
GET /api/v1/tunnel/:session_id/file?path=...
|
||||
|
||||
PUT /api/v1/tunnel/:session_id/file?path=...
|
||||
|
||||
POST /api/v1/tunnel/:session_id/file/list?path=...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
```
|
||||
gururmm_run_command(agent_id, command, shell, working_dir, timeout)
|
||||
gururmm_read_file(agent_id, path)
|
||||
gururmm_write_file(agent_id, path, content)
|
||||
gururmm_list_directory(agent_id, path)
|
||||
gururmm_list_agents()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Implementation
|
||||
|
||||
### Working Directory Validation
|
||||
```toml
|
||||
# agent/config.toml
|
||||
[security]
|
||||
allowed_paths = ["C:\\Shares", "C:\\Temp"]
|
||||
```
|
||||
|
||||
Agent validates all file operations against allowlist, rejects path traversal (`..`).
|
||||
|
||||
### Rate Limiting
|
||||
- Server enforces: 100 commands per minute per tech per agent
|
||||
- Sliding window (in-memory or Redis)
|
||||
- 429 response on limit exceeded
|
||||
- Violations logged to tunnel_audit
|
||||
|
||||
### Command Injection Prevention
|
||||
- tokio::process::Command (no shell expansion)
|
||||
- PowerShell: `-NoProfile -NonInteractive -Command`
|
||||
- Input sanitization (escape quotes, reject backticks)
|
||||
- Timeout enforcement
|
||||
|
||||
### Session Security
|
||||
- JWT 24h expiration
|
||||
- Sessions auto-expire 4h inactivity
|
||||
- One tunnel per agent (prevents concurrent session conflicts)
|
||||
- Admin force-close endpoint
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Channel routing (correct channel receives message)
|
||||
- Session validation (JWT + ownership)
|
||||
- Command sanitization
|
||||
- Path validation (traversal prevention)
|
||||
|
||||
### Integration Tests
|
||||
- Full tunnel lifecycle (open → command → close)
|
||||
- Concurrent sessions to different agents
|
||||
- Session timeout enforcement
|
||||
- Rate limiting
|
||||
|
||||
### End-to-End Tests
|
||||
- Claude Code MCP integration
|
||||
- File upload via MCP, verify on agent
|
||||
- Multi-step workflow (read file → modify → write back)
|
||||
|
||||
---
|
||||
|
||||
## Rollout Plan
|
||||
|
||||
1. **Week 5:** Internal testing (2 agents: AD2, DESKTOP-0O8A1RL)
|
||||
2. **Week 6:** Beta release (3 power user techs)
|
||||
3. **Week 7:** General availability (all techs, documentation, training)
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
**Infrastructure (Phase 1-2):**
|
||||
- 95% tunnel open success rate
|
||||
- <500ms command response time
|
||||
- Zero session conflicts
|
||||
|
||||
**MCP Integration (Phase 3-4):**
|
||||
- 80% tech adoption within 2 weeks
|
||||
- >50 tunnel sessions/day
|
||||
- <5% command error rate
|
||||
|
||||
**Long-term:**
|
||||
- 20% reduction in RDP sessions
|
||||
- 90% tech satisfaction
|
||||
- <1% security incidents
|
||||
|
||||
---
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| Command injection | Critical | Input sanitization, no shell expansion, path allowlist |
|
||||
| Session hijacking | High | Short-lived JWT, session ownership validation, audit logging |
|
||||
| WebSocket instability | Medium | Auto-reconnect, session recovery |
|
||||
| Rate limiting too strict | Medium | Configurable per-tech limits, user feedback |
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. Registry operations scope (full access or specific hives only)?
|
||||
2. Interactive terminal priority (defer to Phase 6)?
|
||||
3. Multi-tech sessions for pair programming?
|
||||
4. MCP server credential manager integration (1Password)?
|
||||
5. Agent-side logging requirements (compliance)?
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Phase 1 Verification
|
||||
```bash
|
||||
# Tech opens tunnel session
|
||||
curl -X POST http://172.16.3.30:3001/api/v1/tunnel/open \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-d '{"agent_id": 1}'
|
||||
# Response: {"session_id": "uuid", "status": "active"}
|
||||
|
||||
# Check agent logs - should show: "Tunnel mode activated for session uuid"
|
||||
# Check database: SELECT * FROM tech_sessions WHERE session_id = 'uuid';
|
||||
```
|
||||
|
||||
### Phase 2 Verification
|
||||
```bash
|
||||
# Execute command via tunnel
|
||||
curl -X POST http://172.16.3.30:3001/api/v1/tunnel/$SESSION_ID/command \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-d '{"command": "Get-Date", "shell": "powershell"}'
|
||||
# Response: {"stdout": "Sunday, April 13, 2026...", "exit_code": 0}
|
||||
```
|
||||
|
||||
### Phase 4 Verification (MCP)
|
||||
```bash
|
||||
# Configure MCP server in Claude Code
|
||||
# Test tools appear in Claude's tool list
|
||||
# Execute: "List files in C:\Shares on agent ID 1"
|
||||
# Claude should call gururmm_list_directory tool
|
||||
# Verify output shows directory listing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps After Approval
|
||||
|
||||
1. Create feature branch: `feature/real-time-tunnel`
|
||||
2. Phase 1 database migrations (tech_sessions, tunnel_audit tables)
|
||||
3. Update protocol enums (ServerMessage/AgentMessage)
|
||||
4. Implement tunnel open/close endpoints
|
||||
5. Update agent WebSocket handler for tunnel mode
|
||||
6. Unit tests for session validation
|
||||
7. Deploy to test environment
|
||||
|
||||
**Estimated Timeline:** 5 weeks to MCP integration, 7 weeks to GA
|
||||
|
||||
---
|
||||
|
||||
**Detailed plan location:** `projects/msp-tools/guru-rmm/plans/real-time-tunnel-architecture.md`
|
||||
@@ -1,25 +1,39 @@
|
||||
# Memory Index
|
||||
|
||||
## Reference
|
||||
- [Community Forum (Flarum)](reference_community_forum.md) - Flarum forum at community.azcomputerguru.com, API access, database, posting workflow
|
||||
- [Radio Show Website](reference_radio_website.md) - Astro static site at radio.azcomputerguru.com on IX server
|
||||
- [IX Server SSH Access](reference_ix_server_ssh.md) - SSH access notes, no key auth from CachyOS workstation yet
|
||||
- [IX Access via Tailscale](reference_ix_access_tailscale.md) - IX server accessible with Tailscale on, no VPN needed
|
||||
- [Neptune Access via D2TESTNAS](reference_neptune_access_d2testnas.md) - Neptune must be routed through D2TESTNAS
|
||||
- [CachyOS Workstation Setup](reference_workstation_setup.md) - Dual NVMe, autostart apps, key fixes applied, old home location
|
||||
- [Matomo Analytics](reference_matomo_analytics.md) - Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites
|
||||
- [Dataforth Contact - AJ](reference_dataforth_contact.md) - AJ at Dataforth, dataforthgit@ email forwarding to him
|
||||
|
||||
## Feedback
|
||||
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) - Use root@192.168.0.9 with Paper123!@#, not sysadmin
|
||||
- [Bypass Permissions Setting](feedback_bypass_permissions_setting.md) - Set permissions.defaultMode to bypassPermissions in settings.json on all machines
|
||||
|
||||
## Machine
|
||||
- [Windows GURU-BEAST-ROG Setup](machine_windows_guru_setup_status.md) - Fully configured: Node.js, Ollama (qwen3:14b, nomic-embed-text), GrepAI, MCP servers. Pending: codestral:22b pull
|
||||
|
||||
## Project
|
||||
- [Audio Processor Architecture](project_audio_processor_architecture.md) - Segment-first pipeline: detect breaks before transcription for complete content capture
|
||||
- [Neptune Email Routing Issues](project_email_routing_neptune.md) - Multiple clients (devcon, Sorensen/rieussetcorp) have email not routing properly from Neptune
|
||||
- [Neptune SBR Email Routing Setup](project_neptune_sbr_email_routing.md) - Full SBR routing chain, config file locations, MailProtector integration, access methods
|
||||
- [Dataforth Test Datasheet Pipeline](project_datasheet_pipeline.md) - Full pipeline rebuilt 2026-03-27. Server-side generation replaces DFWDS/Uploader. Website upload still broken.
|
||||
- [Dataforth Security Incident](project_dataforth_incident_2026-03-27.md) - DF-JOEL2 compromised, MFA deployed, IC3 filed. CA policies enforce April 4.
|
||||
# Memory Index
|
||||
|
||||
## Reference
|
||||
- [Community Forum (Flarum)](reference_community_forum.md) - Flarum forum at community.azcomputerguru.com, API access, database, posting workflow
|
||||
- [Radio Show Website](reference_radio_website.md) - Astro static site at radio.azcomputerguru.com on IX server
|
||||
- [IX Server SSH Access](reference_ix_server_ssh.md) - SSH access notes, no key auth from CachyOS workstation yet
|
||||
- [IX Access via Tailscale](reference_ix_access_tailscale.md) - IX server accessible with Tailscale on, no VPN needed
|
||||
- [Neptune Access via D2TESTNAS](reference_neptune_access_d2testnas.md) - Neptune must be routed through D2TESTNAS
|
||||
- [ACG-5070 Workstation](reference_workstation_setup.md) - Windows 11, replaced CachyOS. SOPS vault, Ollama, all dev tools.
|
||||
- [Matomo Analytics](reference_matomo_analytics.md) - Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites
|
||||
- [Dataforth Contact - AJ](reference_dataforth_contact.md) - AJ at Dataforth, dataforthgit@ email forwarding to him
|
||||
- [TickTick Integration](reference_ticktick_integration.md) - OAuth API integration, MCP server, SOPS vault creds, project/task CRUD
|
||||
- [Client Docs Structure](reference_client_docs_structure.md) - clients/<name>/docs/ layout (overview, network, servers, cloud, security, rmm, issues). Template at clients/_client_template/.
|
||||
- [MSP Audit Scripts](reference_msp_audit_scripts.md) - server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/. ScreenConnect 80-char rule.
|
||||
- [GuruRMM Server Layout](reference_gururmm_server.md) - SSH as `guru`, repo at /home/guru/gururmm, deploy to /var/www/gururmm/dashboard/
|
||||
- [Pluto Build Server](reference_pluto_build_server.md) - General-purpose Windows build VM, 172.16.3.36, SSH as Administrator, MSVC toolchain — use for any EXE (utilities, Howard's tools, GuruRMM agent)
|
||||
|
||||
## Users
|
||||
- [Howard Enos](user_howard.md) — Mike's brother, technician, full trust/access. Known machine: ACG-TECH03L.
|
||||
|
||||
## Feedback
|
||||
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) - Use root@192.168.0.9 with Paper123!@#, not sysadmin
|
||||
- [Bypass Permissions Setting](feedback_bypass_permissions_setting.md) - Set permissions.defaultMode to bypassPermissions in settings.json on all machines
|
||||
- [365 Remediation Tool](feedback_365_remediation_tool.md) - Always means Graph API app fabb3421, not CIPP
|
||||
|
||||
## Machine
|
||||
- [ACG-5070 Workstation Setup](reference_workstation_setup.md) - Windows 11 Pro clean install 2026-03-30, replaced CachyOS. All tools installed.
|
||||
|
||||
## Pending Setup
|
||||
- [Mac gururmm setup pending](project_mac_gururmm_setup_pending.md) — ACTION REQUIRED: run `bash scripts/install-hooks.sh` in gururmm repo on Mikes-MacBook-Air before any RMM work
|
||||
|
||||
## Project
|
||||
- [Sync script bug — untracked files](project_sync_script_bug.md) — Flagged for Mike. `.claude/scripts/sync.sh` line 53 misses untracked-only changes; one-line fix included.
|
||||
- [MasterBooter Side Project](project_masterbooter.md) — Howard's Rust+Slint Windows deployment toolkit at C:\MasterBooter, separate from client work. Do not log to clients/.
|
||||
- [Audio Processor Architecture](project_audio_processor_architecture.md) - Segment-first pipeline: detect breaks before transcription for complete content capture
|
||||
- [Neptune Email Routing Issues](project_email_routing_neptune.md) - Multiple clients (devcon, Sorensen/rieussetcorp) have email not routing properly from Neptune
|
||||
- [Neptune SBR Email Routing Setup](project_neptune_sbr_email_routing.md) - Full SBR routing chain, config file locations, MailProtector integration, access methods
|
||||
- [Dataforth Test Datasheet Pipeline](project_datasheet_pipeline.md) - Full pipeline rebuilt 2026-03-27. Server-side generation replaces DFWDS/Uploader. Website upload still broken.
|
||||
- [Dataforth Security Incident](project_dataforth_incident_2026-03-27.md) - DF-JOEL2 compromised, MFA deployed, IC3 filed. CA policies enforce April 4.
|
||||
|
||||
32
.claude/memory/feedback_365_remediation_tool.md
Normal file
32
.claude/memory/feedback_365_remediation_tool.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: 365 Remediation Tool Reference
|
||||
description: "365 remediation tool" always means the Claude-MSP-Access Graph API app (fabb3421-8b34-484b-bc17-e46de9703418), not CIPP
|
||||
type: feedback
|
||||
---
|
||||
|
||||
When user says "365 remediation tool" or "remediation tool", they ALWAYS mean the Claude-MSP-Access Graph API application (App ID: fabb3421-8b34-484b-bc17-e46de9703418). This is NOT CIPP.
|
||||
|
||||
**Why:** User explicitly clarified this after I incorrectly navigated to CIPP. The remediation tool is direct Graph API access using client credentials flow against customer tenants.
|
||||
|
||||
**How to apply:** Authenticate directly via Graph API using the app's client secret from SOPS vault (`msp-tools/claude-msp-access-graph-api.sops.yaml`), get tenant ID from OpenID discovery for the target domain, and query Graph API endpoints directly. No browser/UI needed.
|
||||
|
||||
**Preferred invocation: use the `/remediation-tool` skill** (`.claude/skills/remediation-tool/`, also surfaces as a `/remediation-tool` command). It wraps tenant resolution, token caching, the 10-point user breach check, and tenant-wide sweep. Remediation actions are gated behind explicit `YES` confirmation. Reference docs at `references/gotchas.md`, `references/graph-endpoints.md`, `references/checklist.md`.
|
||||
|
||||
### Directory Role Requirements (discovered 2026-04-01)
|
||||
|
||||
Graph API permissions alone are NOT sufficient for privileged operations. The service principal also needs Entra directory roles assigned per-tenant:
|
||||
|
||||
| Operation | Required Directory Role |
|
||||
|-----------|----------------------|
|
||||
| Password reset | User Administrator |
|
||||
| Exchange transport rules, mailbox permissions | Exchange Administrator |
|
||||
|
||||
**Roles assigned so far:**
|
||||
- Valleywide Plastering (5c53ae9f...): User Administrator
|
||||
- Dataforth (7dfa3ce8...): User Administrator, Exchange Administrator
|
||||
|
||||
**For new tenants:** After admin consent, manually assign roles via Entra portal > Roles and administrators. The app cannot self-assign directory roles.
|
||||
|
||||
### Exchange Online REST API
|
||||
|
||||
For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`.
|
||||
@@ -1,20 +1,23 @@
|
||||
---
|
||||
name: Bypass Permissions Persistent Setting
|
||||
description: Configure settings.json on all machines to persist bypass permissions mode across context compressions
|
||||
name: Bypass Permissions and Act Autonomously
|
||||
description: Never ask for permission or confirmation -- just execute. User is an experienced engineer who wants autonomous operation.
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Always set `permissions.defaultMode` to `"bypassPermissions"` in `~/.claude/settings.json` on all workstations.
|
||||
NEVER ask "want me to proceed?", "should I?", "which option?", or any variation. Just do it.
|
||||
|
||||
**Why:** The `--dangerously-skip-permissions` CLI flag is not preserved when Claude Code compresses context or resumes sessions -- the new process starts without the flag, causing unexpected permission prompts mid-session. This is a known bug (GitHub issue #21974).
|
||||
**Why:** User has explicitly and repeatedly asked not to be prompted for permissions or confirmations. Every time Claude asks instead of acting, it wastes time and frustrates the user. The user is a senior engineer who trusts Claude to make reasonable decisions and execute.
|
||||
|
||||
**How to apply:** On any new machine setup, ensure `~/.claude/settings.json` includes:
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"defaultMode": "bypassPermissions"
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true
|
||||
}
|
||||
```
|
||||
This makes bypass mode the default without needing the CLI flag. Both keys are needed -- `defaultMode` sets the mode, `skipDangerousModePermissionPrompt` suppresses the warning dialog.
|
||||
**How to apply:**
|
||||
- When there are multiple valid approaches and no direction has been set, it's OK to ask which path. But once a path is chosen, execute without asking.
|
||||
- When a task has clear next steps, execute them immediately. Don't pause for approval.
|
||||
- Asking about unclear requirements or missing context is fine. Asking "should I do the thing you told me to do?" is not.
|
||||
- `permissions.defaultMode` must be `"bypassPermissions"` in `~/.claude/settings.json` on all machines. Both keys needed:
|
||||
```json
|
||||
{
|
||||
"permissions": { "defaultMode": "bypassPermissions" },
|
||||
"skipDangerousModePermissionPrompt": true
|
||||
}
|
||||
```
|
||||
- This applies to tool calls, git operations, file edits, agent launches -- everything.
|
||||
- The ONLY exception: truly destructive operations on shared/production systems (dropping databases, force-pushing main). Even then, a brief "doing X" is better than "should I do X?"
|
||||
|
||||
26
.claude/memory/project_mac_gururmm_setup_pending.md
Normal file
26
.claude/memory/project_mac_gururmm_setup_pending.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Mac gururmm hook setup pending
|
||||
description: Mikes-MacBook-Air needs install-hooks.sh run in gururmm repo — one-time setup to prevent sqlx migration drift
|
||||
type: project
|
||||
---
|
||||
|
||||
The gururmm repo on this Mac needs a one-time setup step that was applied to all other machines on 2026-04-19.
|
||||
|
||||
**Action required (do this now, before any gururmm work):**
|
||||
|
||||
```bash
|
||||
cd /Users/azcomputerguru/ClaudeTools/projects/msp-tools/guru-rmm
|
||||
git pull
|
||||
bash scripts/install-hooks.sh
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- Sets `core.hooksPath = scripts/hooks/` so the pre-commit CRLF check is active
|
||||
- Sets `core.autocrlf=false` and `core.eol=lf` locally and globally
|
||||
- Prevents sqlx migration checksum drift (root cause: CRLF vs LF sha384 mismatch)
|
||||
|
||||
**Why:** The gururmm build server refused to start after a rebuild because migration file hashes differed between what was stored in `_sqlx_migrations` and the current files. Root cause was CRLF line endings from Windows commits. Fixed with `.gitattributes` + per-machine git config. This command applies the git config side.
|
||||
|
||||
macOS defaults to LF, so this is low-risk — mainly sets the hooksPath so the pre-commit guard is active.
|
||||
|
||||
**After running:** Delete this memory file or mark it resolved.
|
||||
31
.claude/memory/project_masterbooter.md
Normal file
31
.claude/memory/project_masterbooter.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: MasterBooter Side Project
|
||||
description: Howard's personal side project at C:\MasterBooter — Windows deployment toolkit, separate from client/MSP work. Do not mix with clients/ content.
|
||||
type: project
|
||||
---
|
||||
|
||||
MasterBooter is Howard's personal Rust + Slint Windows deployment toolkit at `C:\MasterBooter`. Single-portable-EXE targeting IT/MSP/repair-shop techs. Four modes: Backup/Restore, Windows Deploy, WinPE Builder (WinRE-based), System Prep. Public GitHub repo: `Howweird/Masterbooter`.
|
||||
|
||||
**Why:** Side project separate from Arizona Computer Guru client work. Howard is learning Rust through it — code is heavily commented by design. Not a commercial product yet, no paying customers.
|
||||
|
||||
**How to apply:**
|
||||
- When Howard mentions MasterBooter, WinPE builder, winpe.rs, deploy.rs, etc., context is `C:\MasterBooter`, NOT the `clients/` folder in ClaudeTools.
|
||||
- Don't log MasterBooter sessions to `clients/` — they are personal project work, not customer engagements.
|
||||
- Project has its own `C:\MasterBooter\CLAUDE.md` with its own rules. Follow those when in that directory.
|
||||
- Heavy comments are intentional (learning), not tech debt.
|
||||
|
||||
**Key docs in C:\MasterBooter:**
|
||||
- `VISION.md` — goals, 4 modes
|
||||
- `REQUIREMENTS.md` — feature tracking (sections 1-10 complete, Section 11 added 2026-04-17 with F1-F25 planned)
|
||||
- `DECISIONS.md` — ADRs (ADR-001 through ADR-014, Rust/Slint switch is ADR-005)
|
||||
- `EXPANSION_PLAN.md` — full roadmap added 2026-04-17, phased execution plan
|
||||
- `TODO_CLEANUP.md` — refactor backlog from March 2026 code review
|
||||
- `CHANGELOG.md` — actively maintained
|
||||
|
||||
**Current status (2026-04-17):** v0.2.1 released. Phase 1 reliability work starting — logging, tempfile, DISM /English, quick-xml, tests, CLI, GHA. Then 26 new features (F1-F25) across 4 tiers + tool swaps.
|
||||
|
||||
**Reference programs Howard studies for ideas** (all in `C:\Users\howar\ClaudeSourceFiles\` or `C:\`):
|
||||
- AMPIPIT (C:\AMPIPIT) — primary Rust+Slint reference
|
||||
- GhostWin — Rust WIM/deploy reference
|
||||
- d7x (C:\Users\howar\ClaudeSourceFiles\d7x) — MSP tool catalog inspiration (55+ bundled tools)
|
||||
- Windows Setup Helper, Unattend Generator, SysprepPreparator, PhoenixPE
|
||||
23
.claude/memory/project_sync_script_bug.md
Normal file
23
.claude/memory/project_sync_script_bug.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Sync script bug — untracked files
|
||||
description: Flagged for Mike — .claude/scripts/sync.sh misses untracked-only changes
|
||||
type: project
|
||||
---
|
||||
|
||||
`.claude/scripts/sync.sh` line 53 uses `git diff-index --quiet HEAD --` to detect local changes. This only flags **tracked** files with modifications. Brand-new untracked files (a new report, new session log, new memory) will NOT be detected on their own — they only get swept up when a tracked file is also dirty (because `git add -A` then runs).
|
||||
|
||||
Symptom seen 2026-04-17 by Howard: added a single new report file, ran /sync, script said "No local changes to commit" and did nothing. Workaround was `git add <file>` first, then re-run.
|
||||
|
||||
**Why:** `git diff-index` ignores untracked files by design. Needs `git status --porcelain` (any output = changes) or equivalent.
|
||||
|
||||
**How to apply:** Mike — small one-line fix in `.claude/scripts/sync.sh`. Suggested replacement:
|
||||
|
||||
```bash
|
||||
# Before (line 53):
|
||||
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
||||
|
||||
# After:
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
```
|
||||
|
||||
Also applies to the Sync Summary's `git diff --stat $LOCAL_BEFORE..HEAD` — may need review to make sure the summary range still makes sense after the detection fix.
|
||||
33
.claude/memory/reference_client_docs_structure.md
Normal file
33
.claude/memory/reference_client_docs_structure.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Client Documentation Structure
|
||||
description: Howard's MSP client docs live under clients/<name>/docs/ with a standard subfolder layout (overview, network, servers, cloud, security, rmm, issues). Template at clients/_client_template/.
|
||||
type: reference
|
||||
---
|
||||
|
||||
Each active client has structured Markdown documentation under `clients/<client-name>/docs/`:
|
||||
|
||||
| File / Folder | Purpose |
|
||||
|---|---|
|
||||
| `overview.md` | Company info, contacts, environment summary, device counts |
|
||||
| `network/topology.md` | Switches, APs, cabling, interconnects |
|
||||
| `network/vlans.md` | VLAN table, subnets, inter-VLAN routing |
|
||||
| `network/dns.md` | DNS servers, zones, records, forwarders |
|
||||
| `network/dhcp.md` | Scopes, reservations, relay config |
|
||||
| `network/firewall.md` | Rules, NAT, VPN, interfaces |
|
||||
| `network/wifi.md` | SSIDs, security, AP assignments |
|
||||
| `servers/<name>.md` | Per-server docs (use `server_template.md`) |
|
||||
| `cloud/m365.md` | Tenant, licensing, Exchange, Entra ID |
|
||||
| `cloud/azure.md` | Subscriptions, VMs, networking |
|
||||
| `security/antivirus.md` | EDR/AV product, deployment status |
|
||||
| `security/backup.md` | Backup jobs, targets, DR plan |
|
||||
| `rmm/rmm.md` | RMM product, agent counts, patch policy |
|
||||
| `issues/log.md` | Historical incident log with root causes |
|
||||
| `billing-log.md` | Per-client billing / work log |
|
||||
|
||||
Clients currently documented (imported 2026-04-16 from Howard's `C:\Users\howar\Clients`):
|
||||
anaise, cascades-tucson, dataforth, instrumental-music-center, khalsa, kittle, lens-auto-brokerage.
|
||||
|
||||
Credentials NEVER go inline in these docs — reference SOPS vault instead:
|
||||
`clients/<name>/<system>.sops.yaml` field path.
|
||||
|
||||
The template at `clients/_client_template/` is the scaffold for new clients.
|
||||
14
.claude/memory/reference_gururmm_server.md
Normal file
14
.claude/memory/reference_gururmm_server.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: GuruRMM Server Layout
|
||||
description: SSH user, home directory, and deploy paths on 172.16.3.30
|
||||
type: reference
|
||||
---
|
||||
|
||||
SSH user is `guru`, NOT `mike`. Home directory is `/home/guru/`.
|
||||
|
||||
- Repo: `/home/guru/gururmm`
|
||||
- Dashboard build: `cd /home/guru/gururmm/dashboard && npm run build`
|
||||
- Deploy: `sudo cp -r dist/* /var/www/gururmm/dashboard/`
|
||||
- Other dirs under `/home/guru/`: `guru-connect`, `guruconnect-server`, `backups`
|
||||
|
||||
**Why:** First SSH session assumed `/home/mike/` — does not exist. Only users with home dirs are `guru` and `gitea-runner`.
|
||||
29
.claude/memory/reference_msp_audit_scripts.md
Normal file
29
.claude/memory/reference_msp_audit_scripts.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: MSP Audit Scripts
|
||||
description: server_audit.ps1 and workstation_audit.ps1 for on-demand auditing via ScreenConnect Toolbox. Also hosted on GitHub (Howweird/msp-audit-scripts) for remote fetch.
|
||||
type: reference
|
||||
---
|
||||
|
||||
Location in claudetools: `projects/msp-tools/msp-audit-scripts/`.
|
||||
|
||||
Scripts:
|
||||
- `server_audit.ps1` — Full server + AD + security audit, outputs JSON to `C:\Temp\`.
|
||||
- `workstation_audit.ps1` — Full workstation audit, outputs JSON to `C:\Temp\`.
|
||||
- `README.md` — Usage notes.
|
||||
|
||||
Remote fetch URL pattern (for ScreenConnect Toolbox):
|
||||
```
|
||||
https://raw.githubusercontent.com/Howweird/msp-audit-scripts/master/server_audit.ps1
|
||||
https://raw.githubusercontent.com/Howweird/msp-audit-scripts/master/workstation_audit.ps1
|
||||
```
|
||||
|
||||
ScreenConnect Toolbox PowerShell rules (IMPORTANT):
|
||||
- No line may exceed 80 chars — Toolbox silently truncates long lines
|
||||
- Store long URLs/paths in variables first
|
||||
- Use multi-line try/catch blocks, never single-line
|
||||
- Paste whole scripts as one command — no inline comments between blocks
|
||||
|
||||
Utility scripts also at `projects/msp-tools/utilities/`:
|
||||
- `clean_printer_ports.ps1`
|
||||
- `win11_upgrade.ps1`
|
||||
- `screenconnect-toolbox-commands.txt` (saved Toolbox one-liners)
|
||||
56
.claude/memory/reference_pluto_build_server.md
Normal file
56
.claude/memory/reference_pluto_build_server.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: Pluto Build Server
|
||||
description: General-purpose Windows build VM on Jupiter — for any EXE needing native Windows compilation (utilities, Howard's tools, GuruRMM agent, etc.)
|
||||
type: reference
|
||||
---
|
||||
|
||||
Pluto is a Windows Server VM on Jupiter. It is the **general-purpose Windows build machine** for any project needing a native Windows executable — not just GuruRMM.
|
||||
|
||||
- **Hostname:** PLUTO (VM on Jupiter)
|
||||
- **Static IP:** 172.16.3.36 (confirmed static 2026-04-19)
|
||||
- **SSH:** `ssh -i ~/.ssh/id_ed25519 Administrator@172.16.3.36` (key auth)
|
||||
- **Authorized key:** `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINXR2BOcFAlOPuB7OYOKfOZDNd3u1tCt/IINRH9beFyB guru@DESKTOP-0O8A1RL`
|
||||
|
||||
## Installed Toolchain
|
||||
|
||||
- **Rust:** stable-x86_64-pc-windows-msvc (rustup at `C:\Users\Administrator\.cargo\bin`)
|
||||
- **VS Build Tools:** Installed with `Microsoft.VisualStudio.Workload.VCTools` (MSVC linker, CRT, Windows SDK)
|
||||
- **Git:** v2.47.1.windows.2
|
||||
- **OpenSSH:** Win32-OpenSSH, sshd set to Automatic startup
|
||||
|
||||
## Use Cases
|
||||
|
||||
Use Pluto when you need a **native Windows MSVC build** — produces proper `.exe` files with no MinGW runtime dependency. Examples:
|
||||
- Utilities (internal tooling, one-off scripts compiled to EXE)
|
||||
- Howard's tech tools (MasterBooter, Slint GUI apps, etc.)
|
||||
- GuruRMM agent MSVC builds (when MSVC target is preferred over the automated MinGW build on the Linux server)
|
||||
- Anything using Windows-only APIs or needing code signing via signtool
|
||||
|
||||
**Note:** Routine GuruRMM agent builds are automated on the Linux server (172.16.3.30) via MinGW + jsign. Use Pluto for MSVC-specific builds or one-off tooling.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
- `C:\builds\` — general project builds (create a subdirectory per project)
|
||||
- `C:\gururmm\` — GuruRMM repo clone
|
||||
|
||||
## Typical Build Workflow
|
||||
|
||||
```bash
|
||||
# 1. SSH in
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@172.16.3.36
|
||||
|
||||
# 2. Clone or pull project
|
||||
git clone https://azcomputerguru:<token>@git.azcomputerguru.com/azcomputerguru/<repo>.git C:\builds\<project>
|
||||
|
||||
# 3. Build
|
||||
cd C:\builds\<project>
|
||||
cargo build --release
|
||||
|
||||
# 4. SCP output back
|
||||
# From workstation:
|
||||
scp -i ~/.ssh/id_ed25519 Administrator@172.16.3.36:"C:/builds/<project>/target/release/<name>.exe" ./
|
||||
```
|
||||
|
||||
## Not Neptune
|
||||
|
||||
Neptune is a separate existing server (email/web hosting). Pluto is only for builds.
|
||||
33
.claude/memory/reference_ticktick_integration.md
Normal file
33
.claude/memory/reference_ticktick_integration.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: TickTick Integration
|
||||
description: TickTick API integration for project/task management - OAuth credentials in SOPS vault, MCP server, API service
|
||||
type: reference
|
||||
---
|
||||
|
||||
## TickTick Integration (Built 2026-03-31)
|
||||
|
||||
**App Name:** ClaudeTools (registered at developer.ticktick.com)
|
||||
|
||||
### Credentials
|
||||
- SOPS vault: `services/ticktick.sops.yaml`
|
||||
- Fields: `credentials.client_id`, `credentials.client_secret`, `credentials.oauth_redirect_url`
|
||||
- OAuth tokens: `mcp-servers/ticktick/.tokens.json` (gitignored, auto-refreshed)
|
||||
|
||||
### Components
|
||||
- **MCP Server:** `mcp-servers/ticktick/ticktick_mcp.py` - 9 tools for Claude Code (registered in `.mcp.json`)
|
||||
- **OAuth Auth:** `mcp-servers/ticktick/ticktick_auth.py` - One-time browser auth flow (localhost:9876 callback)
|
||||
- **API Service:** `api/services/ticktick_service.py` - Async service, SOPS vault credentials, auto token refresh
|
||||
- **API Router:** `api/routers/ticktick.py` - REST at `/api/ticktick/`, JWT-protected
|
||||
|
||||
### TickTick API
|
||||
- Base URL: `https://api.ticktick.com/open/v1`
|
||||
- Auth: OAuth 2.0 Bearer tokens, scopes: `tasks:read tasks:write`
|
||||
- No webhooks (must poll), no search endpoint (filter client-side)
|
||||
- Priority values: 0=none, 1=low, 3=medium, 5=high (non-sequential)
|
||||
- Token endpoint requires `application/x-www-form-urlencoded` (not JSON)
|
||||
|
||||
### MCP Tools
|
||||
`ticktick_list_projects`, `ticktick_get_project`, `ticktick_create_project`, `ticktick_update_project`, `ticktick_delete_project`, `ticktick_create_task`, `ticktick_update_task`, `ticktick_complete_task`, `ticktick_delete_task`
|
||||
|
||||
### Re-auth
|
||||
If tokens expire completely, run: `python mcp-servers/ticktick/ticktick_auth.py` from bash (not PowerShell - needs vault access via bash).
|
||||
@@ -1,35 +1,32 @@
|
||||
---
|
||||
name: CachyOS Workstation Setup
|
||||
description: Current workstation config - CachyOS on ASUS laptop, dual NVMe, autostart apps, old home btrfs subvolume location
|
||||
name: ACG-5070 Workstation Setup
|
||||
description: Primary workstation ACG-5070 (Windows 11 Pro), clean install 2026-03-30. Replaced CachyOS.
|
||||
type: reference
|
||||
---
|
||||
|
||||
## Workstation: acg-guru-5070
|
||||
## Workstation: ACG-5070
|
||||
|
||||
- **OS:** CachyOS (Arch-based), kernel 6.19.x
|
||||
- **DE:** KDE Plasma 6 (Wayland)
|
||||
- **CPU/GPU:** Intel Arrow Lake-S + NVIDIA RTX 5070 Ti Mobile
|
||||
- **Tailscale IP:** 100.95.216.79
|
||||
- **OS:** Windows 11 Pro (clean install 2026-03-30)
|
||||
- **Previous OS:** CachyOS Linux (gone, replaced by Windows)
|
||||
- **Hardware:** ASUS laptop, Intel Arrow Lake-S + NVIDIA RTX 5070 Ti Mobile, dual NVMe
|
||||
|
||||
### Storage
|
||||
- **nvme0n1:** 954GB btrfs - CachyOS install (OS, root)
|
||||
- **nvme1n1:** 954GB ext4 - `/home` (formatted from old Windows drive)
|
||||
- **Old home:** btrfs `@home` subvolume on nvme0n1, mount with: `sudo mount -o subvol=@home UUID=8a8b1d34-99fb-470f-82ca-b5d08e43ec32 /mnt/old-home`
|
||||
### Installed Tools
|
||||
- Node.js v24.14.1, npm 11.11.0
|
||||
- Git 2.53.0, Python 3.14.3
|
||||
- 1Password CLI 2.33.1 (desktop app integration)
|
||||
- Ollama 0.18.3 (models on D:\OllamaModels: qwen3:14b, codestral:22b, nomic-embed-text)
|
||||
- Claude Code 2.1.87
|
||||
- sops 3.7.3, age 1.3.1, yq 4.52.5
|
||||
- jq, curl, Windows OpenSSH
|
||||
- Missing: gh (GitHub CLI)
|
||||
|
||||
### Autostart Apps (~/.config/autostart/)
|
||||
- `arch-update-tray.desktop` (pre-existing)
|
||||
- `cachyos-hello.desktop` (pre-existing)
|
||||
- `discord.desktop` (added, starts minimized)
|
||||
- `tailscale-systray.desktop` (added)
|
||||
- ScreenConnect: autostart removed (on-demand only via URI scheme handler from web UI)
|
||||
### SOPS Vault
|
||||
- age key: %APPDATA%\sops\age\keys.txt
|
||||
- Vault repo: D:\vault (git.azcomputerguru.com/azcomputerguru/vault)
|
||||
- 1Password backup: "age Key - ACG-5070 (Windows)" in Infrastructure vault
|
||||
|
||||
### Known Issues
|
||||
- **Warm reboot hangs:** Rebooting (e.g. for GPU issues) causes system to hang with spinning symbol — requires hard power-off. Observed multiple times. Likely NVIDIA driver not unloading cleanly during shutdown.
|
||||
|
||||
### Key Fixes Applied
|
||||
- **Tailscale:** `--accept-routes`, systemd-resolved + NetworkManager DNS config
|
||||
- **Brightness:** Hide nvidia_0 backlight via udev rule, KDE controls intel_backlight only
|
||||
- **ScreenConnect:** dpkg + full JRE + Wayland patch (GDK_BACKEND=x11)
|
||||
- **Sudo:** NOPASSWD for guru user
|
||||
### Other Machines
|
||||
- GURU-BEAST-ROG (Windows 11) -- needs vault setup (sops, age, yq, clone repo, generate age key, rotate)
|
||||
- Mikes-MacBook-Air (macOS) -- needs vault setup
|
||||
|
||||
**How to apply:** Reference when troubleshooting workstation issues or setting up additional services.
|
||||
|
||||
13
.claude/memory/user_howard.md
Normal file
13
.claude/memory/user_howard.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Howard Enos — team member
|
||||
description: Howard is Mike's brother and employee at AZ Computer Guru. Technician role with full trust and full access. Uses claudetools for MSP tracking and daily client work.
|
||||
type: user
|
||||
---
|
||||
|
||||
Howard Enos is a technician at Arizona Computer Guru LLC and Mike Swanson's brother. He has full access to all systems, credentials, and client data — same level as Mike. No permission gating.
|
||||
|
||||
Known machine: ACG-TECH03L (laptop). Desktop hostname TBD (will be registered on first sync).
|
||||
|
||||
When working with Howard, treat him exactly as you would Mike — same context loading, same credential access, same capabilities. He uses claudetools for MSP work tracking, client management, and daily IT operations.
|
||||
|
||||
His git commits should show `Howard Enos <howard@azcomputerguru.com>`.
|
||||
9
.claude/messages/for-howard.md
Normal file
9
.claude/messages/for-howard.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Messages for Howard
|
||||
|
||||
Check this file at sync. Delete items after you've addressed them.
|
||||
|
||||
---
|
||||
|
||||
## From Mike, 2026-04-19 — Cascades IdentityRiskyUser.Read.All — RESOLVED
|
||||
|
||||
App manifest updated with all risky Identity Protection APIs. Admin consent re-run on Cascades tenant (2026-04-19). Should be live — re-test the risky-user check when you get a chance.
|
||||
68
.claude/messages/for-mike.md
Normal file
68
.claude/messages/for-mike.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Note for Mike
|
||||
|
||||
## From Howard, 2026-04-19 - FOLLOW-UP (update after your approval)
|
||||
|
||||
You approved it (thank you), and you/I clicked the admin-consent URL on Cascades. Microsoft redirected to `login.microsoftonline.com/common/wrongplace` (their standard "consent succeeded but no app redirect configured" landing page).
|
||||
|
||||
**But it didn't actually grant the scope.** I re-ran the risky-user check and still got `Forbidden`. I decoded the JWT and confirmed the `IdentityRiskyUser.Read.All` role is not in the token's `roles` array.
|
||||
|
||||
**Why:** the scope isn't in the app manifest yet. Tenant-side consent can only grant permissions the app has declared it wants. The fix has to happen on OUR side, at the app registration in our home Azure tenant:
|
||||
|
||||
1. Azure Portal > Entra ID > App Registrations > **ComputerGuru - AI Remediation** (App ID `fabb3421-8b34-484b-bc17-e46de9703418`)
|
||||
2. API Permissions > Add a permission > Microsoft Graph > Application permissions
|
||||
3. Add `IdentityRiskyUser.Read.All`
|
||||
4. Grant admin consent in our home tenant (or skip — customer tenants will each re-consent)
|
||||
5. For each customer tenant we want it on, re-run the admin consent URL:
|
||||
`https://login.microsoftonline.com/{tenant}/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418`
|
||||
|
||||
For Cascades that URL is:
|
||||
```
|
||||
https://login.microsoftonline.com/207fa277-e9d8-4eb7-ada1-1064d2221498/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418
|
||||
```
|
||||
|
||||
(Same URL — just needs to be clicked AGAIN after the manifest is updated, because now it'll include the new permission in the consent prompt.)
|
||||
|
||||
Let me know when the manifest is updated and I'll re-test.
|
||||
|
||||
---
|
||||
|
||||
## From Howard, 2026-04-19 (original ask)
|
||||
|
||||
### Cascades of Tucson - M365 Remediation App - Identity Protection scope
|
||||
|
||||
During today's phishing investigation on Cascades of Tucson (crystal.rodriguez, et al.), the 10-point breach check returned `Forbidden` on `/identityProtection/riskyUsers` and `/identityProtection/riskDetections` because **Claude-MSP-Access (ComputerGuru - AI Remediation, App ID `fabb3421-8b34-484b-bc17-e46de9703418`) lacks admin consent for `IdentityRiskyUser.Read.All` on the Cascades tenant.**
|
||||
|
||||
**Asking before I grant:** should I go ahead and give this consent, or do you want to hold off?
|
||||
|
||||
#### What the scope does
|
||||
|
||||
- **Read-only.** Reads Entra ID Identity Protection signals: risky-user state (low/medium/high), and the underlying risk detections (impossible travel, anonymous IP, leaked credentials, malware-linked IP, etc.).
|
||||
- **No write capability** - not `ReadWrite.All`, just `Read.All`. The app cannot reset risk state, dismiss detections, or modify anything in Identity Protection.
|
||||
- **Tenant-scoped.** Consent applies only to the Cascades tenant; doesn't affect other clients.
|
||||
|
||||
#### Why I want it
|
||||
|
||||
- Closes a visibility gap in our standard breach-check workflow. Today I had to tell the report "this check skipped" for risky-user signals.
|
||||
- Saves us from logging into the Defender / Entra portal manually during IR to cross-check.
|
||||
- Cascades has Defender P1+ (based on targeted-user protection already configured), so risk data exists to read.
|
||||
|
||||
#### Why you might say no
|
||||
|
||||
- Every additional scope on the app = larger blast radius if the app's client secret/cert leaks.
|
||||
- Scope is persistent until revoked via the portal.
|
||||
- Identity Protection data can include sensitive info (IPs, geo, device hints). If our audit logging is weak, reading it leaves tracks we should be aware of.
|
||||
|
||||
#### My lean
|
||||
|
||||
**Allow it.** The scope is read-only, the app is narrowly controlled (only us), and we already have Mail.Read, User.Read.All, Exchange Admin, etc. — which are materially more sensitive than this. The inconsistency of "we can read full mailbox contents but not risky-user flags" doesn't match a risk-based model.
|
||||
|
||||
If you say yes, consent URL is:
|
||||
```
|
||||
https://login.microsoftonline.com/207fa277-e9d8-4eb7-ada1-1064d2221498/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418
|
||||
```
|
||||
|
||||
Takes ~30 seconds. Sign in as a GA on Cascades' tenant (sysadmin@ works), review the permission, click Accept.
|
||||
|
||||
Full investigation report: `clients/cascades-tucson/reports/2026-04-19-crystal-rodriguez-phish-investigation.md`
|
||||
|
||||
- Howard
|
||||
@@ -1,118 +1,173 @@
|
||||
#!/bin/bash
|
||||
# ClaudeTools Bidirectional Sync Script
|
||||
# Ensures proper pull BEFORE push on all machines
|
||||
# Prints incoming/outgoing change summary with author attribution
|
||||
|
||||
set -e # Exit on error
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Detect machine name
|
||||
# Machine + timestamp
|
||||
if [ -n "$COMPUTERNAME" ]; then
|
||||
MACHINE="$COMPUTERNAME"
|
||||
else
|
||||
MACHINE=$(hostname)
|
||||
fi
|
||||
|
||||
# Timestamp
|
||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
|
||||
echo -e "${GREEN}[OK]${NC} Starting ClaudeTools sync from $MACHINE at $TIMESTAMP"
|
||||
|
||||
# Navigate to ClaudeTools directory
|
||||
if [ -d "$HOME/ClaudeTools" ]; then
|
||||
cd "$HOME/ClaudeTools"
|
||||
elif [ -d "/d/ClaudeTools" ]; then
|
||||
cd "/d/ClaudeTools"
|
||||
elif [ -d "D:/ClaudeTools" ]; then
|
||||
cd "D:/ClaudeTools"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} ClaudeTools directory not found"
|
||||
# Navigate to ClaudeTools directory (check common locations)
|
||||
for candidate in "$HOME/ClaudeTools" "/d/ClaudeTools" "D:/ClaudeTools" "/d/claudetools" "D:/claudetools"; do
|
||||
if [ -d "$candidate" ]; then
|
||||
cd "$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${RED}[ERROR]${NC} Not in a git working tree"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[OK]${NC} Working directory: $(pwd)"
|
||||
|
||||
# Phase 1: Check and commit local changes
|
||||
echo ""
|
||||
echo "=== Phase 1: Local Changes ==="
|
||||
|
||||
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
||||
echo -e "${YELLOW}[INFO]${NC} Local changes detected"
|
||||
|
||||
# Show status
|
||||
git status --short
|
||||
|
||||
# Stage all changes
|
||||
echo -e "${GREEN}[OK]${NC} Staging all changes..."
|
||||
git add -A
|
||||
|
||||
# Commit with timestamp
|
||||
COMMIT_MSG="sync: Auto-sync from $MACHINE at $TIMESTAMP
|
||||
|
||||
Synced files:
|
||||
- Session logs updated
|
||||
- Latest context and credentials
|
||||
- Command/directive updates
|
||||
|
||||
Machine: $MACHINE
|
||||
Timestamp: $TIMESTAMP
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
|
||||
git commit -m "$COMMIT_MSG"
|
||||
echo -e "${GREEN}[OK]${NC} Changes committed"
|
||||
else
|
||||
echo -e "${GREEN}[OK]${NC} No local changes to commit"
|
||||
fi
|
||||
|
||||
# Phase 2: Sync with remote (CRITICAL: Pull BEFORE Push)
|
||||
echo ""
|
||||
echo "=== Phase 2: Remote Sync (Pull + Push) ==="
|
||||
|
||||
# Fetch to see what's available
|
||||
echo -e "${GREEN}[OK]${NC} Fetching from remote..."
|
||||
git fetch origin
|
||||
|
||||
# Check if remote has updates
|
||||
LOCAL=$(git rev-parse main)
|
||||
REMOTE=$(git rev-parse origin/main)
|
||||
|
||||
if [ "$LOCAL" != "$REMOTE" ]; then
|
||||
echo -e "${YELLOW}[INFO]${NC} Remote has updates, pulling..."
|
||||
|
||||
# Pull with rebase
|
||||
if git pull origin main --rebase; then
|
||||
echo -e "${GREEN}[OK]${NC} Successfully pulled remote changes"
|
||||
git log --oneline "$LOCAL..origin/main"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Pull failed - may have conflicts"
|
||||
echo -e "${YELLOW}[INFO]${NC} Resolve conflicts and run sync again"
|
||||
exit 1
|
||||
# Detect Python interpreter — verify it actually runs (Windows Store stub passes command -v but fails to execute)
|
||||
PYTHON=""
|
||||
for candidate in python3 python; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
if "$candidate" -c "import sys; sys.exit(0)" >/dev/null 2>&1; then
|
||||
PYTHON="$candidate"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}[OK]${NC} Already up to date with remote"
|
||||
fi
|
||||
|
||||
# Push local changes
|
||||
echo ""
|
||||
echo -e "${GREEN}[OK]${NC} Pushing local changes to remote..."
|
||||
if git push origin main; then
|
||||
echo -e "${GREEN}[OK]${NC} Successfully pushed to remote"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Push failed"
|
||||
done
|
||||
if [ -z "$PYTHON" ]; then
|
||||
echo -e "${RED}[ERROR]${NC} No Python interpreter found (need python or python3)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Phase 3: Report final status
|
||||
# Load user identity
|
||||
USER_DISPLAY="unknown"
|
||||
USER_GITEA=""
|
||||
if [ -f ".claude/identity.json" ]; then
|
||||
USER_DISPLAY=$($PYTHON -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('full_name', d.get('user','unknown')))" 2>/dev/null || echo "unknown")
|
||||
USER_GITEA=$($PYTHON -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('user',''))" 2>/dev/null || echo "")
|
||||
fi
|
||||
echo -e "${GREEN}[OK]${NC} Syncing as: $USER_DISPLAY (machine: $MACHINE)"
|
||||
|
||||
# Phase 1: Local changes
|
||||
echo ""
|
||||
echo "=== Sync Complete ==="
|
||||
echo -e "${GREEN}[OK]${NC} Local branch: $(git rev-parse --abbrev-ref HEAD)"
|
||||
echo -e "${GREEN}[OK]${NC} Current commit: $(git log -1 --oneline)"
|
||||
echo -e "${GREEN}[OK]${NC} Remote status: $(git status -sb | head -1)"
|
||||
echo "=== Phase 1: Local changes ==="
|
||||
|
||||
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
||||
echo -e "${YELLOW}[INFO]${NC} Local changes detected:"
|
||||
git status --short
|
||||
echo ""
|
||||
|
||||
echo -e "${GREEN}[OK]${NC} Staging all changes..."
|
||||
git add -A
|
||||
|
||||
# Commit message (Co-Authored-By uses local git user if configured)
|
||||
COMMIT_MSG="sync: auto-sync from $MACHINE at $TIMESTAMP
|
||||
|
||||
Author: $USER_DISPLAY
|
||||
Machine: $MACHINE
|
||||
Timestamp: $TIMESTAMP"
|
||||
|
||||
git commit -m "$COMMIT_MSG"
|
||||
echo -e "${GREEN}[OK]${NC} Committed."
|
||||
else
|
||||
echo -e "${GREEN}[OK]${NC} No local changes to commit."
|
||||
fi
|
||||
|
||||
# Phase 2: Remote sync
|
||||
echo ""
|
||||
echo "=== Phase 2: Fetch + inspect ==="
|
||||
|
||||
LOCAL_BEFORE=$(git rev-parse HEAD)
|
||||
|
||||
echo -e "${GREEN}[OK]${NC} Fetching from origin..."
|
||||
git fetch origin --quiet
|
||||
|
||||
LOCAL=$(git rev-parse HEAD)
|
||||
REMOTE=$(git rev-parse origin/main 2>/dev/null || git rev-parse origin/master 2>/dev/null || echo "$LOCAL")
|
||||
REMOTE_BRANCH="origin/main"
|
||||
if ! git rev-parse origin/main >/dev/null 2>&1; then
|
||||
REMOTE_BRANCH="origin/master"
|
||||
fi
|
||||
|
||||
# Count and show incoming
|
||||
INCOMING_COUNT=$(git rev-list --count HEAD..$REMOTE_BRANCH 2>/dev/null || echo 0)
|
||||
OUTGOING_COUNT=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$INCOMING_COUNT" -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${CYAN}--- Incoming: $INCOMING_COUNT commits from remote ---${NC}"
|
||||
git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' HEAD..$REMOTE_BRANCH | head -30
|
||||
echo ""
|
||||
echo -e "${CYAN}--- Files touched by incoming commits ---${NC}"
|
||||
git diff --stat HEAD..$REMOTE_BRANCH | tail -20
|
||||
else
|
||||
echo -e "${GREEN}[OK]${NC} No incoming changes."
|
||||
fi
|
||||
|
||||
if [ "$OUTGOING_COUNT" -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${CYAN}--- Outgoing: $OUTGOING_COUNT commits to remote ---${NC}"
|
||||
git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' $REMOTE_BRANCH..HEAD | head -30
|
||||
fi
|
||||
|
||||
# Phase 3: Pull (if needed)
|
||||
if [ "$INCOMING_COUNT" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "=== Phase 3: Pull (rebase) ==="
|
||||
if git pull origin main --rebase; then
|
||||
echo -e "${GREEN}[OK]${NC} Pulled successfully."
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Pull failed (likely conflicts). Resolve and re-run sync."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Phase 4: Push (if needed)
|
||||
OUTGOING_AFTER_PULL=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0)
|
||||
if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "=== Phase 4: Push ==="
|
||||
if git push origin main; then
|
||||
echo -e "${GREEN}[OK]${NC} Pushed successfully."
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Push failed. Check auth / network."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}[OK]${NC} Nothing to push."
|
||||
fi
|
||||
|
||||
# Phase 5: Summary
|
||||
echo ""
|
||||
echo "=== Sync Summary ==="
|
||||
|
||||
if [ "$INCOMING_COUNT" -gt 0 ]; then
|
||||
# Count commits by author
|
||||
INCOMING_AUTHORS=$(git log --format='%an' $LOCAL_BEFORE..HEAD 2>/dev/null | sort | uniq -c | sort -rn | awk '{printf "%s (%s), ", substr($0, index($0,$2)), $1}' | sed 's/, $//')
|
||||
echo -e "${CYAN}Pulled in:${NC} $INCOMING_COUNT commit(s) — authors: ${INCOMING_AUTHORS:-unknown}"
|
||||
fi
|
||||
if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then
|
||||
echo -e "${CYAN}Pushed out:${NC} $OUTGOING_AFTER_PULL commit(s) by $USER_DISPLAY"
|
||||
fi
|
||||
if [ "$INCOMING_COUNT" -eq 0 ] && [ "$OUTGOING_AFTER_PULL" -eq 0 ]; then
|
||||
echo -e "${GREEN}Already in sync — no commits moved in either direction.${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[OK]${NC} HEAD: $(git log -1 --oneline)"
|
||||
echo -e "${GREEN}[OK]${NC} Status: $(git status -sb | head -1)"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}[SUCCESS]${NC} All machines in sync. Ready to continue work."
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Sync complete."
|
||||
|
||||
9
.claude/settings.json
Normal file
9
.claude/settings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"defaultMode": "bypassPermissions"
|
||||
},
|
||||
"preferences": {
|
||||
"autoCompact": true,
|
||||
"verbose": false
|
||||
}
|
||||
}
|
||||
64
.claude/skills/remediation-tool/SKILL.md
Normal file
64
.claude/skills/remediation-tool/SKILL.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: remediation-tool
|
||||
description: |
|
||||
M365 tenant investigation and remediation using the ComputerGuru tiered MSP app suite (5 apps: Security Investigator, Exchange Operator, User Manager, Tenant Admin, Defender Add-on). Auto-invoke when the user says "remediation tool", "365 remediation", "check <user>'s mailbox/box", "credential stuffing" against an M365 user, "breach check" on an M365 tenant, or needs M365 admin API work that client-credentials Graph + Exchange REST can perform. NOT for CIPP — this is the direct Graph API app suite.
|
||||
|
||||
Also invoke when the user needs any of: inbox rule enumeration, mailbox forwarding check, delegate/SendAs audit, OAuth consent audit, sign-in log queries, risky user lookup, directory audit queries, B2B guest invite audit against M365.
|
||||
|
||||
Triggers: "365 remediation", "remediation tool", "check <user> box/mailbox/account for breach", "credential stuff*", "who's getting attacked", "foreign sign-in", "inbox rule", "mailbox forward*", "oauth consent" (in MSP context), "tenant sweep", "risky user", "hidden rule", Exchange Online admin API, "adminapi/beta/{tenant}/InvokeCommand".
|
||||
---
|
||||
|
||||
# 365 Remediation Tool
|
||||
|
||||
Read-only by default. All remediation actions require explicit `YES` confirmation in chat (not a permission prompt).
|
||||
|
||||
## App Architecture (Tiered)
|
||||
|
||||
Five multi-tenant apps cover distinct privilege tiers. Use only what the task requires.
|
||||
|
||||
| Tier | App display name | App ID | Vault file | Scope |
|
||||
|---|---|---|---|---|
|
||||
| `investigator` | ComputerGuru Security Investigator | `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` | `computerguru-security-investigator.sops.yaml` | Graph read-only |
|
||||
| `investigator-exo` | ComputerGuru Security Investigator | `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` | `computerguru-security-investigator.sops.yaml` | Exchange Online read |
|
||||
| `exchange-op` | ComputerGuru Exchange Operator | `b43e7342-5b4b-492f-890f-bb5a4f7f40e9` | `computerguru-exchange-operator.sops.yaml` | Exchange Online write |
|
||||
| `user-manager` | ComputerGuru User Manager | `64fac46b-8b44-41ad-93ee-7da03927576c` | `computerguru-user-manager.sops.yaml` | Graph user/group write |
|
||||
| `tenant-admin` | ComputerGuru Tenant Admin | `709e6eed-0711-4875-9c44-2d3518c47063` | `computerguru-tenant-admin.sops.yaml` | Graph high-privilege |
|
||||
| `defender` | ComputerGuru Defender Add-on | `dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b` | `computerguru-defender-addon.sops.yaml` | Defender ATP (MDE only) |
|
||||
|
||||
**Default for breach checks:** use `investigator` (Graph) + `investigator-exo` (Exchange read). Escalate to write tiers only when remediating.
|
||||
|
||||
## Auto-Invocation Behavior
|
||||
|
||||
When triggered automatically (vs. via `/remediation-tool`), follow the same workflow in `.claude/commands/remediation-tool.md`:
|
||||
|
||||
1. Parse the user's intent into a subcommand (check/sweep/signins/consent-url/remediate).
|
||||
2. Resolve tenant ID from domain.
|
||||
3. Acquire tokens via `get-token.sh <tenant> <tier>` — use lowest-privilege tier needed.
|
||||
4. Run checks via scripts in `scripts/`.
|
||||
5. Interpret findings using `references/checklist.md`.
|
||||
6. Write report to `clients/{slug}/reports/YYYY-MM-DD-{action}.md` using `templates/breach-report.md`.
|
||||
7. Chat summary + delegate commit to Gitea agent.
|
||||
|
||||
## Before calling any script, verify
|
||||
|
||||
- The SOPS vault is accessible: `test -f D:/vault/scripts/vault.sh` (Windows) or `test -f ~/vault/scripts/vault.sh` (other).
|
||||
- `jq`, `curl`, `bash` are available.
|
||||
- For Exchange REST checks: confirm the target tenant has **Exchange Administrator** role assigned to the **Security Investigator** SP (for reads) or **Exchange Operator** SP (for writes). If any Exchange REST call returns 403, emit the tenant-scoped Entra Roles link from `references/gotchas.md`.
|
||||
- For Identity Protection checks: `IdentityRiskyUser.Read.All` is in the Security Investigator manifest AND the tenant has consented to that app. If 403, emit the per-app consent URL from `references/gotchas.md`.
|
||||
- For Defender checks: confirm tenant has Microsoft Defender for Endpoint (MDE) license before using `defender` tier — it returns AADSTS650052 otherwise.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Target identifiers**: accept UPN, domain, or tenant GUID. Normalize to tenant GUID internally.
|
||||
- **Token tiers**: minimum necessary privilege. Never use `tenant-admin` for a read-only check.
|
||||
- **Token cache**: `/tmp/remediation-tool/{tenant-id}/{tier}.jwt`. TTL 55 minutes. Check `-mmin -55` before reuse.
|
||||
- **Raw JSON artifacts**: `/tmp/remediation-tool/{tenant-id}/{check}/` — keep so the user can re-analyze.
|
||||
- **Reports**: `clients/{slug}/reports/YYYY-MM-DD-{action}.md`. Derive slug from domain (strip TLD, hyphenate).
|
||||
- **UTC dates everywhere**.
|
||||
|
||||
## Scope boundaries
|
||||
|
||||
- **Not a replacement for CIPP.** Use CIPP for bulk baseline configuration, templates, standards alerting. Use this tool for focused investigation and point-in-time remediation.
|
||||
- **Not for creating/modifying Entra apps or Conditional Access policies.** Those are sensitive enough to stay manual in the portal.
|
||||
- **Not for Graph permissions the apps don't have.** If a call 403s and the scope isn't in the relevant app's manifest, stop and tell the user — don't try to work around it.
|
||||
- **Defender tier requires MDE license.** If the tenant doesn't have MDE, the token request succeeds but API calls return AADSTS650052. Check before using.
|
||||
48
.claude/skills/remediation-tool/references/checklist.md
Normal file
48
.claude/skills/remediation-tool/references/checklist.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Breach-Check Rubric
|
||||
|
||||
How to interpret the outputs from `user-breach-check.sh` and `tenant-sweep.sh`.
|
||||
|
||||
## Single-user check — the 10 points
|
||||
|
||||
| # | Check | What "clean" looks like | Red flags |
|
||||
|---|---|---|---|
|
||||
| 1 | Inbox rules (Graph) | Empty, or only benign filters | ForwardTo / RedirectTo / ForwardAsAttachmentTo set; DeleteMessage+MarkAsRead combos; rules filtered on "password", "bank", "invoice", "CEO name", "security"; rules with name like "." or " " (attacker hiding) |
|
||||
| 2 | Mailbox settings / auto-reply | Auto-reply disabled or legitimate | Auto-reply active with external audience + unfamiliar message body |
|
||||
| 3 | Exchange REST (hidden rules, delegates, SendAs, Get-Mailbox forwarding fields) | Only SELF in permissions; no forwarding | **Hidden** inbox rule moving to RSS/Notes/Conversation History; non-SELF FullAccess/SendAs; ForwardingAddress or ForwardingSmtpAddress set to external |
|
||||
| 4 | OAuth consents + app role assignments | Legitimate apps only (Teams, Outlook mobile, BlueMail, etc.); dates match user history | New consent in attack window; unknown app with `Mail.ReadWrite`, `Files.ReadWrite`, `offline_access`; publisher not verified |
|
||||
| 5 | Auth methods | All methods predate the attack window | New phone/Authenticator registered within hours of first suspicious sign-in; duplicate entries with the same device name but different createdDateTime |
|
||||
| 6 | Sign-ins 30d | Consistent US IPs, user's known geography | Any successful sign-in from a country the user never visits; IMAP/POP/Authenticated SMTP client apps (legacy auth); sign-ins from TOR exit nodes or known residential-proxy ranges |
|
||||
| 7 | Directory audits | Only legit admin/system actions | `Update user` by non-admin principal; password reset the user didn't initiate; auth method change from `Microsoft Substrate Management` is normal but repeated changes are not |
|
||||
| 8 | Risky users / risk detections | `riskLevel: none` | Any `medium` or `high`; `riskDetail: userPerformedSecuredPasswordChange` just means resolved — check the original detection |
|
||||
| 9 | Sent items (recent 25) | Normal business correspondence | Blast emails to random external recipients; forwards of internal financial/HR info externally; anything after-hours from an unusual client app |
|
||||
| 10 | Deleted items (recent 25) | Marketing/spam, routine notifications | Deleted security alerts, password-reset emails, MFA notifications, bounce notices the user wouldn't delete — all signs of attacker cleanup |
|
||||
|
||||
### Cross-check rule
|
||||
|
||||
If inbox rules and forwarding are clean **but** sign-ins show successful foreign access — attacker may have used OAuth-based access (check OAuth grants) or already extracted data and cleaned up. Pull sent items + deleted items aggressively and check `/auditLogs/signIns/beta` for non-interactive sign-ins.
|
||||
|
||||
## Tenant-wide sweep — priorities
|
||||
|
||||
| Priority | Signal | Action |
|
||||
|---|---|---|
|
||||
| P1 | User with ≥20 failed sign-ins from ≥2 foreign countries | Likely active credential-stuffing target. Reset password, disable SMTP AUTH, monitor. |
|
||||
| P1 | Successful sign-in from non-US | Verify with user immediately. If not them: force password reset + revoke sessions + full breach check. |
|
||||
| P2 | New OAuth consent to unfamiliar app in attack window | Review app publisher, scopes, and requesting user. Revoke if unknown. |
|
||||
| P2 | B2B guest invite to personal email domain (gmail.com, outlook.com, yahoo.com) | Confirm with inviter it's intentional. Guest invites are a known persistence mechanism. |
|
||||
| P3 | Transport rule created/modified by a non-admin | Transport rules can redirect mail tenant-wide. Review body/actions carefully. |
|
||||
| P3 | Service principal added by non-admin or by "PowerApps Service" unexpectedly | Usually benign, but worth noting. |
|
||||
| P4 | Isolated wrong-password attempt from foreign IP | Record and move on. Single attempts are noise unless repeated. |
|
||||
|
||||
## False positives to filter out
|
||||
|
||||
- `sysadmin@<tenant>` failures during onboarding (error 65001 against any **ComputerGuru** app — Security Investigator, Exchange Operator, User Manager, Tenant Admin, or Defender Add-on).
|
||||
- `Microsoft Substrate Management` and `Azure MFA StrongAuthenticationService` routinely update user records — those are not attacker activity.
|
||||
- Our own consent attempts show up as `Consent to application` in directory audits. Filter `sysadmin` + target matching any "ComputerGuru" app display name during the onboarding window.
|
||||
- `error 50140` "Keep me signed in interrupt" is a browser prompt, not a failed auth.
|
||||
|
||||
## When to escalate beyond this tool
|
||||
|
||||
- Data exfiltration suspected -> pull Unified Audit Log via Purview (this tool does not access UAL).
|
||||
- Tenant-wide phishing campaign -> enable Purview Content Search, quarantine messages.
|
||||
- Domain-joined workstation compromise -> GuruRMM + Bitdefender workflow (see `clients/ace-portables/reports/` for past example).
|
||||
- Attacker still active and exfiltrating -> consider disabling the user via the `remediate` subcommand and rotating the mailbox password at the same time.
|
||||
113
.claude/skills/remediation-tool/references/gotchas.md
Normal file
113
.claude/skills/remediation-tool/references/gotchas.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Gotchas — Permissions, Roles, Consent
|
||||
|
||||
## App Suite (tiered architecture)
|
||||
|
||||
Five multi-tenant apps replace the old single over-permissioned app. Use minimum necessary tier.
|
||||
|
||||
| Tier | Display name in customer tenant | App ID | Vault file |
|
||||
|---|---|---|---|
|
||||
| `investigator` / `investigator-exo` | ComputerGuru Security Investigator | `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` | `computerguru-security-investigator.sops.yaml` |
|
||||
| `exchange-op` | ComputerGuru Exchange Operator | `b43e7342-5b4b-492f-890f-bb5a4f7f40e9` | `computerguru-exchange-operator.sops.yaml` |
|
||||
| `user-manager` | ComputerGuru User Manager | `64fac46b-8b44-41ad-93ee-7da03927576c` | `computerguru-user-manager.sops.yaml` |
|
||||
| `tenant-admin` | ComputerGuru Tenant Admin | `709e6eed-0711-4875-9c44-2d3518c47063` | `computerguru-tenant-admin.sops.yaml` |
|
||||
| `defender` | ComputerGuru Defender Add-on | `dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b` | `computerguru-defender-addon.sops.yaml` |
|
||||
|
||||
**Deprecated (do not use):** ~~ComputerGuru - AI Remediation~~ (`fabb3421`) — old single-app with 159 permissions including Defender ATP. Broke consent on tenants without MDE license. Retire/delete from portal when confirmed no active tenants depend on it.
|
||||
|
||||
When searching customer admin portals for a service principal (role assignments, app role assignments, CA exclusions), search by the display name for that tier (e.g., "ComputerGuru Security Investigator").
|
||||
|
||||
## Per-tenant prerequisites
|
||||
|
||||
Graph API permissions alone are not enough. Most privileged operations require directory roles on the specific service principal *in that tenant*:
|
||||
|
||||
| Operation | App tier | Required directory role on that SP |
|
||||
|---|---|---|
|
||||
| Exchange REST read (Get-InboxRule, Get-Mailbox) | `investigator-exo` | Exchange Administrator |
|
||||
| Exchange REST write (Set-Mailbox, Remove-InboxRule) | `exchange-op` | Exchange Administrator |
|
||||
| Password reset, user property updates | `user-manager` | User Administrator |
|
||||
| MFA method reset | `user-manager` | Authentication Administrator |
|
||||
| Conditional Access reads/writes | `tenant-admin` | Conditional Access Administrator OR Security Administrator |
|
||||
| Teams policies | `tenant-admin` | Teams Administrator |
|
||||
|
||||
### How to assign a role to an SP in a customer tenant
|
||||
|
||||
1. Sign into the customer's Entra admin center as Global Admin:
|
||||
`https://entra.microsoft.com/#@{customer-domain}`
|
||||
2. Identity -> Roles & admins -> All roles -> select the role (e.g., Exchange Administrator)
|
||||
3. Add assignments -> search by the app display name (e.g., "ComputerGuru Security Investigator") -> Assign
|
||||
(Active, permanent — service principals cannot activate eligible assignments)
|
||||
|
||||
## Admin consent URLs
|
||||
|
||||
Each app must be individually consented in each customer tenant. Format:
|
||||
|
||||
```
|
||||
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={app-id}&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Security Investigator** (consent this first — needed for all breach checks):
|
||||
```
|
||||
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=bfbc12a4-f0dd-4e12-b06d-997e7271e10c&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Exchange Operator** (consent when remediation scope is needed):
|
||||
```
|
||||
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=b43e7342-5b4b-492f-890f-bb5a4f7f40e9&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**User Manager**:
|
||||
```
|
||||
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=64fac46b-8b44-41ad-93ee-7da03927576c&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Tenant Admin**:
|
||||
```
|
||||
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
**Defender Add-on** (MDE-licensed tenants only — AADSTS650052 if no MDE license):
|
||||
```
|
||||
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
The customer admin signs in as Global Admin, clicks Accept. Redirect lands on azcomputerguru.com — expected. Verify via `/servicePrincipals/{sp-id}/appRoleAssignments` (grants timestamped today confirm success).
|
||||
|
||||
## Diagnosing "required scopes are missing"
|
||||
|
||||
Token returned 403 with `"required scopes are missing in the token"`:
|
||||
|
||||
1. Decode the JWT payload (2nd segment, base64url) and check the `roles` claim.
|
||||
2. If the expected scope is missing from `roles`:
|
||||
- Confirm the scope is in the app manifest in the home tenant (saved, not just selected).
|
||||
- Grant admin consent in the home tenant.
|
||||
- Re-run the customer admin consent URL above for that specific app.
|
||||
3. If the scope IS in `roles` but you still get 403: check for a missing directory role (see table above).
|
||||
|
||||
## Diagnosing Exchange REST 403
|
||||
|
||||
- Wrong token scope: must request `https://outlook.office365.com/.default` (use `investigator-exo` or `exchange-op` tier, NOT `investigator`).
|
||||
- Missing Exchange Administrator role on the specific SP in that tenant.
|
||||
- Propagation delay: newly assigned role can take up to 15 minutes to reach Exchange Online. If just assigned, wait and retry.
|
||||
|
||||
## AADSTS650052 — service not licensed
|
||||
|
||||
If token request or API call returns AADSTS650052 referencing `WindowsDefenderATP` (`fc780465`): the tenant does not have an MDE license. Do not use the `defender` tier for this tenant. Security investigation proceeds with `investigator` + `investigator-exo` only.
|
||||
|
||||
## Common, benign "failures" in sign-in logs
|
||||
|
||||
- `error 50140` "Keep me signed in interrupt" — KMSI prompt, not a real failure.
|
||||
- `error 65001` "has not consented to use the application" — fires during onboarding and before consent granted. If `appDisplayName` matches any ComputerGuru app, those are our own consent attempts, not attacker activity.
|
||||
- `error 50126` from the sysadmin account during onboarding is typo/retry noise — check `ipAddress` against Mike's known IPs before flagging.
|
||||
|
||||
## Tenants where apps are consented (as of 2026-04-20)
|
||||
|
||||
| Tenant | Tenant ID | Security Investigator | Exchange Operator | User Manager | Tenant Admin | Defender | Directory roles | Notes |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| Valleywide Plastering | 5c53ae9f... | old app only | — | — | — | — | User Admin (old app) | Needs migration to new app suite |
|
||||
| Dataforth | 7dfa3ce8... | old app only | — | — | — | — | User Admin + Exchange Admin (old app) | Needs migration |
|
||||
| Cascades Tucson | 207fa277-e9d8-4eb7-ada1-1064d2221498 | old app only | — | — | — | — | User Admin + Exchange Admin (old app) | IdentityRiskyUser scope still not consented as of 2026-04-16 |
|
||||
| Grabblaw | 032b383e-96e4-491b-880d-3fd3295672c3 | YES (2026-04-20) | — | YES (2026-04-20) | — | — | none | No directory roles assigned yet |
|
||||
|
||||
**Migration note:** Valleywide, Dataforth, and Cascades still use the old deprecated app. Next visit: consent Security Investigator + assign Exchange Administrator role to new SP, then retire old app consent.
|
||||
|
||||
Keep this table updated when rolling out to new tenants or migrating existing ones.
|
||||
159
.claude/skills/remediation-tool/references/graph-endpoints.md
Normal file
159
.claude/skills/remediation-tool/references/graph-endpoints.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Graph + Exchange REST Cheatsheet
|
||||
|
||||
All examples assume:
|
||||
- `$GT` = Graph token (`investigator` tier)
|
||||
- `$EXO_R` = Exchange read token (`investigator-exo` tier) — Get-* cmdlets
|
||||
- `$EXO_W` = Exchange write token (`exchange-op` tier) — Set-*/Remove-* cmdlets
|
||||
- `$UT` = User Manager graph token (`user-manager` tier) — user write ops
|
||||
- `$TID` = tenant ID, `$UPN`/`$UID` = user identifiers
|
||||
|
||||
Acquire tokens:
|
||||
```bash
|
||||
GT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator)
|
||||
EXO_R=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator-exo)
|
||||
EXO_W=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID exchange-op) # remediation only
|
||||
UT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID user-manager) # remediation only
|
||||
```
|
||||
|
||||
## Graph API (`https://graph.microsoft.com/v1.0`)
|
||||
|
||||
### User lookup / status
|
||||
|
||||
```bash
|
||||
# By UPN
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/$UPN?\$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime,lastPasswordChangeDateTime"
|
||||
|
||||
# All users (filter, paged)
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users?\$top=999&\$filter=accountEnabled%20eq%20true"
|
||||
```
|
||||
|
||||
### Mailbox
|
||||
|
||||
```bash
|
||||
# Visible inbox rules (Graph v1.0 — does NOT return hidden rules)
|
||||
/users/$UPN/mailFolders/inbox/messageRules
|
||||
|
||||
# Mailbox settings (auto-reply, delegates meeting option, NOT forwarding flags)
|
||||
/users/$UPN/mailboxSettings
|
||||
|
||||
# Recent sent / deleted
|
||||
/users/$UPN/mailFolders/sentitems/messages?$top=25&$orderby=sentDateTime%20desc
|
||||
/users/$UPN/mailFolders/deleteditems/messages?$top=25&$orderby=receivedDateTime%20desc
|
||||
```
|
||||
|
||||
### Authentication methods
|
||||
|
||||
```bash
|
||||
/users/$UPN/authentication/methods
|
||||
# Watch for new methods added within the attack window
|
||||
```
|
||||
|
||||
### OAuth + app role assignments
|
||||
|
||||
```bash
|
||||
/users/$UPN/oauth2PermissionGrants # user-level consents
|
||||
/users/$UPN/appRoleAssignments # apps assigned to this user
|
||||
/servicePrincipals/$SP_ID/appRoleAssignments # what scopes a SP has
|
||||
```
|
||||
|
||||
### Sign-ins (needs Entra ID P1 or higher)
|
||||
|
||||
```bash
|
||||
# Interactive sign-ins v1.0 (does NOT include non-interactive/service-principal)
|
||||
/auditLogs/signIns?$filter=userId eq '$UID' and createdDateTime ge $FROM&$top=200
|
||||
|
||||
# All sign-in event types (beta endpoint)
|
||||
/beta/auditLogs/signIns?$filter=userId eq '$UID' and (signInEventTypes/any(t:t eq 'nonInteractiveUser'))
|
||||
|
||||
# Foreign successful sign-ins tenant-wide
|
||||
/auditLogs/signIns?$filter=(status/errorCode eq 0) and (location/countryOrRegion ne 'US')
|
||||
```
|
||||
|
||||
### Directory audits
|
||||
|
||||
```bash
|
||||
# Changes targeting a specific user
|
||||
/auditLogs/directoryAudits?$filter=targetResources/any(t:t/id eq '$UID')
|
||||
|
||||
# Tenant-wide consent / auth-method / role events
|
||||
/auditLogs/directoryAudits?$filter=activityDateTime ge $FROM
|
||||
# Then client-side filter by activityDisplayName ~ Consent|Authentication Method|Add service principal|Add member to role
|
||||
```
|
||||
|
||||
### Identity Protection (needs IdentityRiskyUser.Read.All)
|
||||
|
||||
```bash
|
||||
/identityProtection/riskyUsers
|
||||
/identityProtection/riskyUsers/$UID
|
||||
/identityProtection/riskDetections?$filter=userId eq '$UID'
|
||||
```
|
||||
|
||||
### B2B guests
|
||||
|
||||
```bash
|
||||
# Get guest by gmail/external address
|
||||
/users?$filter=startswith(userPrincipalName,'dunedolly21')
|
||||
|
||||
# Invite audits
|
||||
/auditLogs/directoryAudits?$filter=activityDisplayName eq 'Invite external user'
|
||||
```
|
||||
|
||||
## Exchange Online REST (`https://outlook.office365.com/adminapi/beta/{tenant-id}/InvokeCommand`)
|
||||
|
||||
POST with JSON body `{"CmdletInput":{"CmdletName":"<cmdlet>","Parameters":{...}}}`.
|
||||
- **Read ops** (Get-*): use `$EXO_R` — Security Investigator token (`investigator-exo` tier)
|
||||
- **Write ops** (Set-*, Remove-*): use `$EXO_W` — Exchange Operator token (`exchange-op` tier)
|
||||
|
||||
### Inbox rules (INCLUDING hidden)
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-InboxRule","Parameters":{"Mailbox":"user@domain.com","IncludeHidden":true}}}
|
||||
```
|
||||
|
||||
Why this matters: attackers commonly create hidden rules that Graph v1.0 cannot see.
|
||||
|
||||
### Mailbox forwarding / properties
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-Mailbox","Parameters":{"Identity":"user@domain.com"}}}
|
||||
```
|
||||
|
||||
Check: `ForwardingAddress`, `ForwardingSmtpAddress`, `DeliverToMailboxAndForward`, `GrantSendOnBehalfTo`, `HiddenFromAddressListsEnabled`.
|
||||
|
||||
### Mailbox permissions (delegates / FullAccess)
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-MailboxPermission","Parameters":{"Identity":"user@domain.com"}}}
|
||||
```
|
||||
|
||||
Filter out `NT AUTHORITY\\SELF` — anything else is a delegate.
|
||||
|
||||
### SendAs permissions
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-RecipientPermission","Parameters":{"Identity":"user@domain.com"}}}
|
||||
```
|
||||
|
||||
### Transport rules (tenant-wide mail flow)
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-TransportRule","Parameters":{}}}
|
||||
```
|
||||
|
||||
Check for rules that reroute, delete, or exfiltrate mail.
|
||||
|
||||
### SMTP AUTH
|
||||
|
||||
```json
|
||||
{"CmdletInput":{"CmdletName":"Get-CASMailbox","Parameters":{"Identity":"user@domain.com"}}}
|
||||
```
|
||||
|
||||
Check `SmtpClientAuthenticationDisabled`. To disable SMTP AUTH on a single mailbox (remediation): `Set-CASMailbox -SmtpClientAuthenticationDisabled $true`.
|
||||
|
||||
## Rate limits / pagination
|
||||
|
||||
- Graph signIns endpoints cap `$top` at 999. For >999 results, follow `@odata.nextLink`.
|
||||
- Exchange REST has undocumented throttling — if you hit 429, back off 30–60s.
|
||||
- Token is valid ~60 minutes. Script caches for 55 min.
|
||||
140
.claude/skills/remediation-tool/scripts/get-token.sh
Normal file
140
.claude/skills/remediation-tool/scripts/get-token.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# Acquire a client-credentials bearer token for a ComputerGuru MSP app tier.
|
||||
# Usage: get-token.sh <tenant-id-or-domain> <tier>
|
||||
#
|
||||
# Tiers and their app + resource scope:
|
||||
# investigator ComputerGuru Security Investigator -> Graph API (read-only breach checks)
|
||||
# investigator-exo ComputerGuru Security Investigator -> Exchange Online (EXO read: Get-InboxRule, Get-Mailbox)
|
||||
# exchange-op ComputerGuru Exchange Operator -> Exchange Online (EXO write: Set-Mailbox, Remove-InboxRule, revoke sessions)
|
||||
# user-manager ComputerGuru User Manager -> Graph API (user create/update/disable, license assign, MFA reset)
|
||||
# tenant-admin ComputerGuru Tenant Admin -> Graph API (app roles, CA policy, directory write — high privilege)
|
||||
# defender ComputerGuru Defender Add-on -> Defender ATP API (MDE-licensed tenants only)
|
||||
#
|
||||
# Output (stdout): bearer token. Exits 0 on success.
|
||||
# Cache: /tmp/remediation-tool/{tenant-id}/{tier}.jwt TTL 55 minutes.
|
||||
set -euo pipefail
|
||||
|
||||
TARGET="${1:?usage: get-token.sh <tenant-id|domain> <tier>}"
|
||||
TIER="${2:?usage: get-token.sh <tenant-id|domain> <tier>}"
|
||||
|
||||
# Resolve domain to tenant GUID if needed
|
||||
if [[ "$TARGET" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
|
||||
TENANT_ID="$TARGET"
|
||||
else
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TARGET")
|
||||
fi
|
||||
|
||||
# Map tier -> client_id, vault SOPS path, resource scope
|
||||
case "$TIER" in
|
||||
investigator)
|
||||
CLIENT_ID="bfbc12a4-f0dd-4e12-b06d-997e7271e10c"
|
||||
VAULT_PATH="msp-tools/computerguru-security-investigator.sops.yaml"
|
||||
SCOPE_URL="https://graph.microsoft.com/.default"
|
||||
;;
|
||||
investigator-exo)
|
||||
CLIENT_ID="bfbc12a4-f0dd-4e12-b06d-997e7271e10c"
|
||||
VAULT_PATH="msp-tools/computerguru-security-investigator.sops.yaml"
|
||||
SCOPE_URL="https://outlook.office365.com/.default"
|
||||
;;
|
||||
exchange-op)
|
||||
CLIENT_ID="b43e7342-5b4b-492f-890f-bb5a4f7f40e9"
|
||||
VAULT_PATH="msp-tools/computerguru-exchange-operator.sops.yaml"
|
||||
SCOPE_URL="https://outlook.office365.com/.default"
|
||||
;;
|
||||
user-manager)
|
||||
CLIENT_ID="64fac46b-8b44-41ad-93ee-7da03927576c"
|
||||
VAULT_PATH="msp-tools/computerguru-user-manager.sops.yaml"
|
||||
SCOPE_URL="https://graph.microsoft.com/.default"
|
||||
;;
|
||||
tenant-admin)
|
||||
CLIENT_ID="709e6eed-0711-4875-9c44-2d3518c47063"
|
||||
VAULT_PATH="msp-tools/computerguru-tenant-admin.sops.yaml"
|
||||
SCOPE_URL="https://graph.microsoft.com/.default"
|
||||
;;
|
||||
defender)
|
||||
CLIENT_ID="dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b"
|
||||
VAULT_PATH="msp-tools/computerguru-defender-addon.sops.yaml"
|
||||
SCOPE_URL="https://api.securitycenter.microsoft.com/.default"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown tier '$TIER'." >&2
|
||||
echo "Valid tiers: investigator | investigator-exo | exchange-op | user-manager | tenant-admin | defender" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
CACHE_DIR="/tmp/remediation-tool/$TENANT_ID"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
CACHE_FILE="$CACHE_DIR/${TIER}.jwt"
|
||||
|
||||
# Return cached token if < 55 minutes old
|
||||
if [[ -f "$CACHE_FILE" ]] && [[ $(find "$CACHE_FILE" -mmin -55 2>/dev/null) ]]; then
|
||||
cat "$CACHE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Locate vault repo
|
||||
VAULT_ROOT=""
|
||||
for candidate in "D:/vault" "$HOME/vault" "/d/vault"; do
|
||||
[[ -d "$candidate" ]] && VAULT_ROOT="$candidate" && break
|
||||
done
|
||||
[[ -z "$VAULT_ROOT" ]] && { echo "ERROR: SOPS vault not found (tried D:/vault ~/vault /d/vault)" >&2; exit 3; }
|
||||
|
||||
SOPS_FILE="$VAULT_ROOT/$VAULT_PATH"
|
||||
[[ ! -f "$SOPS_FILE" ]] && { echo "ERROR: vault file not found: $SOPS_FILE" >&2; exit 3; }
|
||||
|
||||
# Read client secret via vault.sh (fast path), falling back to raw sops+python
|
||||
CLIENT_SECRET=""
|
||||
if [[ -f "$VAULT_ROOT/scripts/vault.sh" ]]; then
|
||||
# Try both field names — new files use client_secret, legacy used credential
|
||||
CLIENT_SECRET=$(bash "$VAULT_ROOT/scripts/vault.sh" get-field "$VAULT_PATH" credentials.client_secret 2>/dev/null | tr -d '\r\n' || true)
|
||||
if [[ -z "$CLIENT_SECRET" ]]; then
|
||||
CLIENT_SECRET=$(bash "$VAULT_ROOT/scripts/vault.sh" get-field "$VAULT_PATH" credentials.credential 2>/dev/null | tr -d '\r\n' || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$CLIENT_SECRET" ]]; then
|
||||
PYTHON_BIN=""
|
||||
for p in python3 python py; do command -v "$p" >/dev/null 2>&1 && PYTHON_BIN="$p" && break; done
|
||||
[[ -z "$PYTHON_BIN" ]] && { echo "ERROR: vault.sh failed and python unavailable for SOPS fallback" >&2; exit 3; }
|
||||
command -v sops >/dev/null 2>&1 || { echo "ERROR: sops not on PATH (needed for fallback decrypt)" >&2; exit 3; }
|
||||
|
||||
CLIENT_SECRET=$(sops -d "$SOPS_FILE" 2>/dev/null | "$PYTHON_BIN" -c "
|
||||
import sys, re
|
||||
t = sys.stdin.read()
|
||||
m = re.search(r'^credentials:\s*\n((?:[ \t]+.*\n)+)', t, re.MULTILINE)
|
||||
if not m: sys.exit(1)
|
||||
for line in m.group(1).splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith('client_secret:') or line.startswith('credential:'):
|
||||
print(line.split(':', 1)[1].strip().strip('\"').strip(\"'\"))
|
||||
break
|
||||
" | tr -d '\r\n')
|
||||
fi
|
||||
|
||||
[[ -z "$CLIENT_SECRET" ]] && {
|
||||
echo "ERROR: could not read secret from $VAULT_PATH" >&2
|
||||
echo " Check field: credentials.client_secret (or credentials.credential for older entries)" >&2
|
||||
exit 4
|
||||
}
|
||||
|
||||
# Request token
|
||||
RESP=$(curl -s --max-time 15 -X POST \
|
||||
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
|
||||
--data-urlencode "client_id=${CLIENT_ID}" \
|
||||
--data-urlencode "client_secret=${CLIENT_SECRET}" \
|
||||
--data-urlencode "scope=${SCOPE_URL}" \
|
||||
--data-urlencode "grant_type=client_credentials")
|
||||
|
||||
TOKEN=$(echo "$RESP" | jq -r '.access_token // empty')
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "ERROR: token request failed (tenant=$TENANT_ID tier=$TIER)" >&2
|
||||
echo "$RESP" >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "$TOKEN" > "$CACHE_FILE"
|
||||
chmod 600 "$CACHE_FILE" 2>/dev/null || true
|
||||
echo "$TOKEN"
|
||||
37
.claude/skills/remediation-tool/scripts/resolve-tenant.sh
Normal file
37
.claude/skills/remediation-tool/scripts/resolve-tenant.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolve an M365 domain (or UPN) to a tenant GUID via OpenID discovery.
|
||||
# Usage: resolve-tenant.sh <domain-or-upn-or-tenantid>
|
||||
# Output (stdout): tenant GUID. Exit 0 on success, 1 on failure.
|
||||
set -euo pipefail
|
||||
|
||||
INPUT="${1:?usage: resolve-tenant.sh <domain|upn|tenant-id>}"
|
||||
|
||||
# If it looks like a GUID already, pass through.
|
||||
if [[ "$INPUT" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
|
||||
echo "$INPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If it's a UPN, strip to domain.
|
||||
DOMAIN="${INPUT#*@}"
|
||||
|
||||
# Lightweight cache keyed by domain.
|
||||
CACHE_DIR="/tmp/remediation-tool/_tenant-cache"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
CACHE_FILE="$CACHE_DIR/${DOMAIN}.txt"
|
||||
if [[ -f "$CACHE_FILE" ]] && [[ $(find "$CACHE_FILE" -mmin -1440 2>/dev/null) ]]; then
|
||||
cat "$CACHE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# OpenID discovery — parse issuer URL for tenant GUID.
|
||||
RESP=$(curl -s --max-time 10 "https://login.microsoftonline.com/${DOMAIN}/v2.0/.well-known/openid-configuration")
|
||||
TENANT_ID=$(echo "$RESP" | jq -r '.issuer // empty' | sed -E 's|^https://login\.microsoftonline\.com/||;s|/v2\.0/?$||' || true)
|
||||
|
||||
if [[ -z "$TENANT_ID" ]] || [[ ! "$TENANT_ID" =~ ^[0-9a-fA-F]{8}- ]]; then
|
||||
echo "ERROR: could not resolve tenant for domain: $DOMAIN" >&2
|
||||
echo "Response: $RESP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$TENANT_ID" | tee "$CACHE_FILE"
|
||||
82
.claude/skills/remediation-tool/scripts/tenant-sweep.sh
Normal file
82
.claude/skills/remediation-tool/scripts/tenant-sweep.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tenant-wide signals sweep: failed sign-ins, foreign successful sign-ins, directory audits,
|
||||
# risky users, B2B guest invites, per-user location profile.
|
||||
# Usage: tenant-sweep.sh <tenant-id-or-domain>
|
||||
# Writes raw JSON to /tmp/remediation-tool/{tenant-id}/sweep/
|
||||
# Prints a priority summary to stdout.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
TENANT_INPUT="${1:?usage: tenant-sweep.sh <tenant-id|domain>}"
|
||||
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TENANT_INPUT")
|
||||
GT=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" graph)
|
||||
|
||||
OUT="/tmp/remediation-tool/$TENANT_ID/sweep"
|
||||
mkdir -p "$OUT"
|
||||
FROM=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
echo "[info] tenant=$TENANT_ID window=30d from=$FROM"
|
||||
|
||||
# Enabled users list
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users?\$top=999&\$filter=accountEnabled%20eq%20true&\$select=id,displayName,userPrincipalName,accountEnabled,userType,externalUserState,lastPasswordChangeDateTime,createdDateTime" \
|
||||
> "$OUT/users.json" &
|
||||
|
||||
# Failed sign-ins tenant-wide
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=(createdDateTime%20ge%20${FROM})%20and%20(status/errorCode%20ne%200)&\$top=999" \
|
||||
> "$OUT/failed_signins.json" &
|
||||
|
||||
# Successful sign-ins tenant-wide (to find non-US)
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=(createdDateTime%20ge%20${FROM})%20and%20(status/errorCode%20eq%200)&\$top=999" \
|
||||
> "$OUT/success_signins.json" &
|
||||
|
||||
# Directory audits, filtered by risky activity names
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=activityDateTime%20ge%20${FROM}&\$top=999" \
|
||||
> "$OUT/dir_audits.json" &
|
||||
|
||||
# Risky users (may 403 if IdentityRiskyUser scope absent)
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?\$top=100" \
|
||||
> "$OUT/risky_users.json" &
|
||||
|
||||
# B2B guest invites
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=activityDateTime%20ge%20${FROM}%20and%20activityDisplayName%20eq%20'Invite%20external%20user'&\$top=100" \
|
||||
> "$OUT/guest_invites.json" &
|
||||
|
||||
wait
|
||||
|
||||
echo ""
|
||||
echo "=== Priority 1: accounts with foreign failed sign-ins (credential stuffing candidates) ==="
|
||||
jq '[.value[] | select(.location.countryOrRegion != "US" and .location.countryOrRegion != null) | {user: .userPrincipalName, ip: .ipAddress, country: .location.countryOrRegion, city: .location.city, t: .createdDateTime, err: .status.errorCode, fail: .status.failureReason}] | group_by(.user) | map({user: .[0].user, attempts: length, unique_ips: ([.[]|.ip]|unique|length), countries: ([.[]|.country]|unique), first: ([.[]|.t]|min), last: ([.[]|.t]|max)}) | sort_by(-.attempts)' "$OUT/failed_signins.json"
|
||||
|
||||
echo ""
|
||||
echo "=== Priority 2: successful sign-ins from non-US (suspicious) ==="
|
||||
jq '[.value[] | select(.location.countryOrRegion != "US" and .location.countryOrRegion != null) | {user: .userPrincipalName, ip: .ipAddress, country: .location.countryOrRegion, city: .location.city, t: .createdDateTime, app: .appDisplayName, clientApp: .clientAppUsed}] | sort_by(.t) | reverse | .[:30]' "$OUT/success_signins.json"
|
||||
|
||||
echo ""
|
||||
echo "=== Priority 3: B2B guest invites (30d) ==="
|
||||
jq '[.value[] | {t: .activityDateTime, by: (.initiatedBy.user.userPrincipalName // .initiatedBy.app.displayName), target: [.targetResources[]?|{name: .displayName, upn: .userPrincipalName}], result: .result}] | sort_by(.t) | reverse' "$OUT/guest_invites.json"
|
||||
|
||||
echo ""
|
||||
echo "=== Priority 4: directory audit - consent/role/auth-method changes ==="
|
||||
jq '[.value[] | select(.activityDisplayName | test("[Cc]onsent|[Aa]uthentication [Mm]ethod|Add service principal|Add delegated permission grant|Add app role|Add member to role"; "")) | {t: .activityDateTime, act: .activityDisplayName, by: (.initiatedBy.user.userPrincipalName // .initiatedBy.app.displayName // "system"), target: [.targetResources[]?|{type: .type, name: .displayName, upn: .userPrincipalName}], result: .result}] | sort_by(.t) | reverse | .[:50]' "$OUT/dir_audits.json"
|
||||
|
||||
echo ""
|
||||
echo "=== Risky users (if Identity Protection accessible) ==="
|
||||
if jq -e '.error' "$OUT/risky_users.json" >/dev/null 2>&1; then
|
||||
echo "BLOCKED: $(jq -r '.error.code // "?"' "$OUT/risky_users.json") — $(jq -r '.error.message // ""' "$OUT/risky_users.json")"
|
||||
echo "(Check references/gotchas.md for how to unblock IdentityRiskyUser scope)"
|
||||
else
|
||||
jq '[.value[] | {upn: .userPrincipalName, level: .riskLevel, state: .riskState, detail: .riskDetail, lastUpdated: .riskLastUpdatedDateTime}]' "$OUT/risky_users.json"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== User locations profile (successful sign-ins) ==="
|
||||
jq '[.value[] | {user: .userPrincipalName, country: .location.countryOrRegion, city: .location.city}] | unique | group_by(.user) | map({user: .[0].user, locations: [.[]|{country, city}]|unique})' "$OUT/success_signins.json"
|
||||
|
||||
echo ""
|
||||
echo "[info] Enabled users in tenant: $(jq '.value | length' "$OUT/users.json")"
|
||||
echo "[info] raw artifacts: $OUT"
|
||||
141
.claude/skills/remediation-tool/scripts/user-breach-check.sh
Normal file
141
.claude/skills/remediation-tool/scripts/user-breach-check.sh
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the 10-point breach check on a single user.
|
||||
# Usage: user-breach-check.sh <tenant-id-or-domain> <upn>
|
||||
# Writes raw JSON to /tmp/remediation-tool/{tenant-id}/user-breach/{user-slug}/
|
||||
# Prints a summary table to stdout.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
TENANT_INPUT="${1:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
|
||||
UPN="${2:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
|
||||
|
||||
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TENANT_INPUT")
|
||||
GT=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" graph)
|
||||
EXO=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" exchange) || EXO=""
|
||||
|
||||
USER_SLUG=$(echo "$UPN" | tr '@.' '__')
|
||||
OUT="/tmp/remediation-tool/$TENANT_ID/user-breach/$USER_SLUG"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
echo "[info] tenant=$TENANT_ID user=$UPN out=$OUT"
|
||||
|
||||
# --- 0. Resolve user object ID ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}?\$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime,lastPasswordChangeDateTime" \
|
||||
> "$OUT/00_user.json"
|
||||
UID_=$(jq -r '.id // empty' "$OUT/00_user.json")
|
||||
if [[ -z "$UID_" ]]; then
|
||||
echo "ERROR: user not found or Graph returned error" >&2
|
||||
cat "$OUT/00_user.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[info] object id: $UID_"
|
||||
|
||||
# --- 1. Inbox rules (Graph v1.0 — visible only) ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/inbox/messageRules" \
|
||||
> "$OUT/01_inbox_rules_graph.json" &
|
||||
|
||||
# --- 2. Mailbox settings (forwarding flags) ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/mailboxSettings" \
|
||||
> "$OUT/02_mailbox_settings.json" &
|
||||
|
||||
# --- 4. OAuth consents + app role assignments ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/oauth2PermissionGrants" \
|
||||
> "$OUT/04a_oauth_grants.json" &
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/appRoleAssignments" \
|
||||
> "$OUT/04b_app_role_assignments.json" &
|
||||
|
||||
# --- 5. Authentication methods ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/authentication/methods" \
|
||||
> "$OUT/05_auth_methods.json" &
|
||||
|
||||
wait
|
||||
|
||||
# --- 6. Sign-ins 30d (v1.0 — interactive only) ---
|
||||
FROM=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=userId%20eq%20'${UID_}'%20and%20createdDateTime%20ge%20${FROM}&\$top=200" \
|
||||
> "$OUT/06_signins.json" &
|
||||
|
||||
# --- 7. Directory audits (targetResources = user) 30d ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=activityDateTime%20ge%20${FROM}%20and%20targetResources/any(t:t/id%20eq%20'${UID_}')&\$top=200" \
|
||||
> "$OUT/07_dir_audits.json" &
|
||||
|
||||
# --- 8. Risky user + risk detections (403 if app lacks IdentityRiskyUser scope) ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers/${UID_}" \
|
||||
> "$OUT/08a_risky_user.json" &
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/identityProtection/riskDetections?\$filter=userId%20eq%20'${UID_}'&\$top=100" \
|
||||
> "$OUT/08b_risk_detections.json" &
|
||||
|
||||
# --- 9. Sent items (last 25) ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/sentitems/messages?\$top=25&\$orderby=sentDateTime%20desc&\$select=subject,toRecipients,sentDateTime,from" \
|
||||
> "$OUT/09_sent.json" &
|
||||
|
||||
# --- 10. Deleted items (last 25) ---
|
||||
curl -s -H "Authorization: Bearer $GT" \
|
||||
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/deleteditems/messages?\$top=25&\$orderby=receivedDateTime%20desc&\$select=subject,from,receivedDateTime" \
|
||||
> "$OUT/10_deleted.json" &
|
||||
|
||||
wait
|
||||
|
||||
# --- 3. Exchange REST (hidden rules + delegates + SendAs + Get-Mailbox) ---
|
||||
if [[ -n "$EXO" ]]; then
|
||||
EX_URL="https://outlook.office365.com/adminapi/beta/${TENANT_ID}/InvokeCommand"
|
||||
|
||||
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
|
||||
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-InboxRule\",\"Parameters\":{\"Mailbox\":\"${UPN}\",\"IncludeHidden\":true}}}" \
|
||||
> "$OUT/03a_InboxRule_hidden.json" &
|
||||
|
||||
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
|
||||
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-MailboxPermission\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
|
||||
> "$OUT/03b_MailboxPermission.json" &
|
||||
|
||||
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
|
||||
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-RecipientPermission\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
|
||||
> "$OUT/03c_RecipientPermission.json" &
|
||||
|
||||
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
|
||||
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-Mailbox\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
|
||||
> "$OUT/03d_Mailbox.json" &
|
||||
|
||||
wait
|
||||
else
|
||||
echo "[warn] no Exchange token; skipping check 3 (hidden rules/delegates/SendAs/mailbox forwarding flags)"
|
||||
fi
|
||||
|
||||
# --- Summary table ---
|
||||
echo ""
|
||||
echo "=== Summary: $UPN ==="
|
||||
jq -r '"account_enabled: \(.accountEnabled) lastPwChange: \(.lastPasswordChangeDateTime) created: \(.createdDateTime)"' "$OUT/00_user.json"
|
||||
echo "01 inbox_rules (Graph): $(jq '.value | length // "error"' "$OUT/01_inbox_rules_graph.json")"
|
||||
echo "02 forwarding: fwdSmtp=$(jq -r '.automaticRepliesSetting.status // "n/a"' "$OUT/02_mailbox_settings.json" 2>/dev/null) (see mailbox Get-Mailbox for forwarding fields)"
|
||||
echo "04a oauth_grants: $(jq '.value | length // "error"' "$OUT/04a_oauth_grants.json")"
|
||||
echo "04b app_role_assignments: $(jq '.value | length // "error"' "$OUT/04b_app_role_assignments.json")"
|
||||
echo "05 auth_methods: $(jq '.value | length // "error"' "$OUT/05_auth_methods.json")"
|
||||
echo "06 signins (30d, interactive): $(jq '.value | length // "error"' "$OUT/06_signins.json") non-US: $(jq '[.value[]?|select(.location.countryOrRegion != "US" and .location.countryOrRegion != null)] | length' "$OUT/06_signins.json" 2>/dev/null)"
|
||||
echo "07 dir_audits (30d): $(jq '.value | length // "error"' "$OUT/07_dir_audits.json")"
|
||||
echo "08 risky_user: $(jq -r '.riskLevel // .error.code // "none"' "$OUT/08a_risky_user.json" 2>/dev/null)"
|
||||
echo "08 risk_detections: $(jq '.value | length // (.error.code // "error")' "$OUT/08b_risk_detections.json")"
|
||||
echo "09 sent (recent 25): $(jq '.value | length // "error"' "$OUT/09_sent.json")"
|
||||
echo "10 deleted (recent 25): $(jq '.value | length // "error"' "$OUT/10_deleted.json")"
|
||||
if [[ -f "$OUT/03a_InboxRule_hidden.json" ]]; then
|
||||
HIDDEN=$(jq '.value | length // (.error.code // "?")' "$OUT/03a_InboxRule_hidden.json" 2>/dev/null || echo "?")
|
||||
echo "03a hidden_inbox_rules: $HIDDEN"
|
||||
echo "03b mailbox_permissions: $(jq '[.value[]? | select(.User != "NT AUTHORITY\\SELF")] | length // "?"' "$OUT/03b_MailboxPermission.json" 2>/dev/null) non-SELF"
|
||||
echo "03c send_as: $(jq '[.value[]? | select(.Trustee != "NT AUTHORITY\\SELF")] | length // "?"' "$OUT/03c_RecipientPermission.json" 2>/dev/null) non-SELF"
|
||||
echo "03d mailbox_forwarding: fwdAddr=$(jq -r '.value[0].ForwardingAddress // "null"' "$OUT/03d_Mailbox.json" 2>/dev/null) fwdSmtp=$(jq -r '.value[0].ForwardingSmtpAddress // "null"' "$OUT/03d_Mailbox.json" 2>/dev/null)"
|
||||
else
|
||||
echo "03 exchange_rest: SKIPPED (no exchange token — tenant likely needs Exchange Admin role assigned)"
|
||||
fi
|
||||
echo ""
|
||||
echo "[info] raw artifacts: $OUT"
|
||||
75
.claude/skills/remediation-tool/templates/breach-report.md
Normal file
75
.claude/skills/remediation-tool/templates/breach-report.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# {{TITLE}}
|
||||
|
||||
**Date:** {{YYYY-MM-DD}}
|
||||
**Tenant:** {{tenant-display-name}} ({{domain}}, {{tenant-id}})
|
||||
**Subject:** {{user-or-tenant}}
|
||||
**Tool:** Claude-MSP-Access / ComputerGuru - AI Remediation (App ID `fabb3421-8b34-484b-bc17-e46de9703418`)
|
||||
**Scope:** {{read-only | included remediation}}
|
||||
|
||||
## Summary
|
||||
|
||||
- {{3-5 bullets: breach indicators found? which categories? priority actions?}}
|
||||
|
||||
## Target details
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| UPN | |
|
||||
| Object ID | |
|
||||
| Account Enabled | |
|
||||
| Created | |
|
||||
| Last Password Change | |
|
||||
|
||||
## Per-check findings
|
||||
|
||||
### 1. Inbox rules (Graph)
|
||||
{{count, flagged items verbatim}}
|
||||
|
||||
### 2. Mailbox forwarding / settings
|
||||
{{forwarding flags, auto-reply status}}
|
||||
|
||||
### 3. Exchange REST (hidden rules, delegates, SendAs, Get-Mailbox)
|
||||
{{hidden rule count, non-SELF permissions, ForwardingAddress/ForwardingSmtpAddress}}
|
||||
|
||||
### 4. OAuth consents + app role assignments
|
||||
{{apps consented, when, scopes}}
|
||||
|
||||
### 5. Authentication methods
|
||||
{{methods, creation dates — flag any inside attack window}}
|
||||
|
||||
### 6. Sign-ins (30d)
|
||||
{{count, unique IPs, countries, failures — flag non-US and legacy client apps}}
|
||||
|
||||
### 7. Directory audits
|
||||
{{30d changes targeting user, by-whom analysis}}
|
||||
|
||||
### 8. Risky users / risk detections
|
||||
{{risk level, recent detections — or note if blocked by missing permission}}
|
||||
|
||||
### 9. Sent items (recent 25)
|
||||
{{sample of recipients/subjects — flag blast patterns or unusual externals}}
|
||||
|
||||
### 10. Deleted items (recent 25)
|
||||
{{sample — flag deleted security alerts or MFA notifications}}
|
||||
|
||||
## Suspicious items (pulled out of per-check findings)
|
||||
|
||||
{{bullets for anything abnormal — external forwards, hidden rules, unfamiliar consents, foreign-geo sign-ins, new auth methods within attack window}}
|
||||
|
||||
## Gaps — checks not completed
|
||||
|
||||
{{list any 403s or missing permissions with exact remediation link (see gotchas.md)}}
|
||||
|
||||
## Next actions
|
||||
|
||||
1. {{specific action + owner + deadline}}
|
||||
2. {{...}}
|
||||
|
||||
## Remediation actions (if any)
|
||||
|
||||
{{populated only when `/remediation-tool remediate` was executed — include cmdlet, parameters, response, timestamp}}
|
||||
|
||||
## Data artifacts
|
||||
|
||||
Raw JSON saved at `/tmp/remediation-tool/{{tenant-id}}/{{check-dir}}/` — files:
|
||||
- {{list filenames the scripts produced}}
|
||||
117
.claude/skills/skill-creator/SKILL.md
Normal file
117
.claude/skills/skill-creator/SKILL.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
name: skill-creator
|
||||
description: |
|
||||
Create new Claude Code custom skills and slash commands. Use when the user wants to create a new skill,
|
||||
add a slash command, build a custom command, or set up a new automation. Guides through the process of
|
||||
defining the skill's purpose, triggers, and implementation, then generates the proper file structure.
|
||||
---
|
||||
|
||||
# Skill Creator
|
||||
|
||||
You help the user create new Claude Code custom skills and slash commands.
|
||||
|
||||
## Two Types of Custom Extensions
|
||||
|
||||
### 1. Skills (`.claude/skills/{name}/SKILL.md`)
|
||||
- Rich, multi-purpose capabilities with automatic invocation triggers
|
||||
- Can include supporting files (scripts, references, checklists)
|
||||
- Best for: complex behaviors, design patterns, validation workflows, integrations
|
||||
|
||||
### 2. Slash Commands (`.claude/commands/{name}.md`)
|
||||
- Simple, user-invoked commands triggered by `/{name}`
|
||||
- Single markdown file with instructions
|
||||
- Best for: workflows the user explicitly triggers, task automation, shortcuts
|
||||
- Can accept arguments via `$ARGUMENTS`
|
||||
|
||||
## Creation Process
|
||||
|
||||
### Step 1: Gather Requirements
|
||||
|
||||
Ask the user:
|
||||
1. **What should this skill/command do?** (core purpose)
|
||||
2. **Skill or command?** Help them decide:
|
||||
- If it should run automatically in response to certain actions -> **Skill**
|
||||
- If the user will invoke it explicitly with `/{name}` -> **Command**
|
||||
- If unsure, recommend based on the use case
|
||||
3. **Name** - short, kebab-case identifier (e.g., `code-review`, `deploy-check`)
|
||||
4. **When should it trigger?** (for skills: automatic triggers; for commands: typical usage)
|
||||
|
||||
### Step 2: Generate the Files
|
||||
|
||||
#### For Skills
|
||||
|
||||
Create `.claude/skills/{name}/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: {name}
|
||||
description: |
|
||||
{Detailed description. This is used for discovery/matching, so be specific about
|
||||
when this skill should be invoked. Include trigger keywords and example scenarios.}
|
||||
---
|
||||
|
||||
# {Skill Title}
|
||||
|
||||
{Clear instructions for what Claude should do when this skill is invoked.}
|
||||
|
||||
## When to Invoke
|
||||
|
||||
{List specific triggers - file types, actions, keywords that should activate this skill.}
|
||||
|
||||
## Workflow
|
||||
|
||||
{Step-by-step process the skill follows.}
|
||||
|
||||
## Guidelines
|
||||
|
||||
{Rules, patterns, and best practices to follow.}
|
||||
```
|
||||
|
||||
#### For Commands
|
||||
|
||||
Create `.claude/commands/{name}.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: {One-line description shown in command list}
|
||||
---
|
||||
|
||||
# {Command Title}
|
||||
|
||||
{Instructions for what Claude should do when the user runs /{name}.}
|
||||
|
||||
## Arguments
|
||||
|
||||
If the command accepts arguments, reference them via `$ARGUMENTS`.
|
||||
|
||||
## Workflow
|
||||
|
||||
{Step-by-step process.}
|
||||
```
|
||||
|
||||
### Step 3: Register and Validate
|
||||
|
||||
After creating the files:
|
||||
1. Confirm the file was created in the correct location
|
||||
2. Tell the user they can invoke it:
|
||||
- Skills: Explain the automatic triggers or manual invocation via `/skill-name`
|
||||
- Commands: Tell them to use `/{name}` or `/{name} arguments`
|
||||
3. Remind them to update CLAUDE.md's Commands & Skills table if they want it documented there
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before finalizing, verify:
|
||||
- [ ] Description is detailed enough for Claude to match it to relevant situations
|
||||
- [ ] Instructions are clear and actionable (Claude will follow them literally)
|
||||
- [ ] The skill/command doesn't duplicate an existing one
|
||||
- [ ] File is in the correct location (`.claude/skills/` or `.claude/commands/`)
|
||||
- [ ] Name uses kebab-case and is concise
|
||||
- [ ] For skills with auto-triggers: triggers are specific enough to avoid false positives
|
||||
|
||||
## Tips for Good Skills/Commands
|
||||
|
||||
- **Be specific in descriptions** - vague descriptions lead to missed or false invocations
|
||||
- **Include examples** in the instructions so Claude understands edge cases
|
||||
- **Keep scope focused** - one skill per concern, don't create mega-skills
|
||||
- **Test after creation** - have the user try invoking it to verify behavior
|
||||
- **Reference existing patterns** - look at `.claude/skills/` and `.claude/commands/` for examples
|
||||
105
.claude/skills/stop-slop/SKILL.md
Normal file
105
.claude/skills/stop-slop/SKILL.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: stop-slop
|
||||
description: |
|
||||
Enforce high-quality, slop-free output in all Claude responses. MANDATORY AUTOMATIC INVOCATION:
|
||||
This skill is always active. It governs how Claude writes text, code comments, commit messages,
|
||||
documentation, and any other output. Detects and eliminates generic AI filler, hollow phrases,
|
||||
unnecessary verbosity, and performative enthusiasm. Applies to all output — conversation, code,
|
||||
docs, and generated content.
|
||||
---
|
||||
|
||||
# Stop Slop
|
||||
|
||||
You are a direct, competent engineer. Write like one. Every word must earn its place.
|
||||
|
||||
## Always-On Rules
|
||||
|
||||
These rules apply to ALL output at ALL times. No exceptions.
|
||||
|
||||
### Banned Patterns -- Never Write These
|
||||
|
||||
**Performative enthusiasm and filler openers:**
|
||||
- "Great question!", "Excellent point!", "That's a really interesting..."
|
||||
- "Certainly!", "Absolutely!", "Of course!", "Sure thing!"
|
||||
- "I'd be happy to help!", "Let me help you with that!"
|
||||
- "Good news!", "Here's the exciting part..."
|
||||
|
||||
**Hollow transitions and hedging:**
|
||||
- "It's worth noting that..." (just state it)
|
||||
- "It's important to remember..." (just state it)
|
||||
- "As you can see..." / "As we discussed..."
|
||||
- "Basically..." / "Essentially..." / "Fundamentally..."
|
||||
- "In order to..." (use "to")
|
||||
- "It should be noted that..." (just note it)
|
||||
- "At the end of the day..."
|
||||
- "Moving forward..."
|
||||
|
||||
**Unnecessary meta-commentary:**
|
||||
- "Let me explain..." (just explain)
|
||||
- "I'll now..." / "Next, I'll..." (just do it)
|
||||
- "Here's what I found..." (just show it)
|
||||
- "Let me break this down..." (just break it down)
|
||||
|
||||
**Trailing summaries and sign-offs:**
|
||||
- Restating what was just done at the end of a response
|
||||
- "Let me know if you have any questions!"
|
||||
- "Hope this helps!"
|
||||
- "Feel free to ask if you need anything else!"
|
||||
- "Happy coding!" / "Happy hacking!"
|
||||
- Any variation of "don't hesitate to reach out"
|
||||
|
||||
**Weasel words and padding:**
|
||||
- "Very", "really", "quite", "rather", "somewhat", "fairly"
|
||||
- "Just" (when used as filler, not as "only")
|
||||
- "Simply" (when the thing isn't simple, or as filler)
|
||||
- "Actually" (at start of sentences, as filler)
|
||||
- "Obviously" / "Clearly" (if it were obvious, you wouldn't say it)
|
||||
|
||||
**Sycophantic agreement:**
|
||||
- "You're absolutely right that..."
|
||||
- "That's a great approach!"
|
||||
- "What a thoughtful question!"
|
||||
- Praising the user's code/ideas before giving feedback
|
||||
|
||||
### Writing Standards
|
||||
|
||||
**Lead with the answer.** Don't build up to it. State the conclusion, then support it if needed.
|
||||
|
||||
**One sentence beats three.** If you can say it shorter, do. Compress ruthlessly.
|
||||
|
||||
**No preamble.** Start with the substance. Drop throat-clearing intros.
|
||||
|
||||
**No postamble.** End when the content ends. Don't summarize what you just said. Don't offer further help.
|
||||
|
||||
**Be specific.** "This fails because X" not "There might be some issues with this approach."
|
||||
|
||||
**Code comments: only when non-obvious.** Don't add comments that restate what the code does. Comment the *why*, not the *what*. Most code needs zero comments.
|
||||
|
||||
**Commit messages: state the change.** Not "This commit updates the..." -- just "Update X to handle Y."
|
||||
|
||||
**Error messages: state what went wrong and what to do.** Not "Oops! It looks like something went wrong."
|
||||
|
||||
### Calibration Examples
|
||||
|
||||
**Slop:**
|
||||
> Great question! Let me help you with that. So basically, what's happening here is that the function is essentially trying to parse the input string. It's worth noting that this can sometimes fail if the input isn't valid JSON. I'd recommend wrapping it in a try-catch block to handle any potential errors that might occur. Let me know if you have any questions!
|
||||
|
||||
**Clean:**
|
||||
> The function fails on invalid JSON. Wrap it in try-catch:
|
||||
> ```js
|
||||
> try { return JSON.parse(input); } catch { return null; }
|
||||
> ```
|
||||
|
||||
**Slop:**
|
||||
> I've successfully updated the configuration file to include the new database connection settings. The changes include adding the host, port, username, and password fields as requested. Everything should be working correctly now. Feel free to test it out and let me know if you run into any issues!
|
||||
|
||||
**Clean:**
|
||||
> Updated the database config with the new connection settings.
|
||||
|
||||
### What This Skill Does NOT Do
|
||||
|
||||
- It does not make responses terse to the point of being unhelpful
|
||||
- It does not remove necessary technical explanation
|
||||
- It does not prevent friendly, human tone -- just fake enthusiasm
|
||||
- It does not restrict response length when length is warranted by complexity
|
||||
- Thoroughness is good. Fluff is not. Know the difference.
|
||||
202
.claude/skills/theme-factory/LICENSE.txt
Normal file
202
.claude/skills/theme-factory/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
59
.claude/skills/theme-factory/SKILL.md
Normal file
59
.claude/skills/theme-factory/SKILL.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: theme-factory
|
||||
description: Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
|
||||
# Theme Factory Skill
|
||||
|
||||
This skill provides a curated collection of professional font and color themes themes, each with carefully selected color palettes and font pairings. Once a theme is chosen, it can be applied to any artifact.
|
||||
|
||||
## Purpose
|
||||
|
||||
To apply consistent, professional styling to presentation slide decks, use this skill. Each theme includes:
|
||||
- A cohesive color palette with hex codes
|
||||
- Complementary font pairings for headers and body text
|
||||
- A distinct visual identity suitable for different contexts and audiences
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
To apply styling to a slide deck or other artifact:
|
||||
|
||||
1. **Show the theme showcase**: Display the `theme-showcase.pdf` file to allow users to see all available themes visually. Do not make any modifications to it; simply show the file for viewing.
|
||||
2. **Ask for their choice**: Ask which theme to apply to the deck
|
||||
3. **Wait for selection**: Get explicit confirmation about the chosen theme
|
||||
4. **Apply the theme**: Once a theme has been chosen, apply the selected theme's colors and fonts to the deck/artifact
|
||||
|
||||
## Themes Available
|
||||
|
||||
The following 10 themes are available, each showcased in `theme-showcase.pdf`:
|
||||
|
||||
1. **Ocean Depths** - Professional and calming maritime theme
|
||||
2. **Sunset Boulevard** - Warm and vibrant sunset colors
|
||||
3. **Forest Canopy** - Natural and grounded earth tones
|
||||
4. **Modern Minimalist** - Clean and contemporary grayscale
|
||||
5. **Golden Hour** - Rich and warm autumnal palette
|
||||
6. **Arctic Frost** - Cool and crisp winter-inspired theme
|
||||
7. **Desert Rose** - Soft and sophisticated dusty tones
|
||||
8. **Tech Innovation** - Bold and modern tech aesthetic
|
||||
9. **Botanical Garden** - Fresh and organic garden colors
|
||||
10. **Midnight Galaxy** - Dramatic and cosmic deep tones
|
||||
|
||||
## Theme Details
|
||||
|
||||
Each theme is defined in the `themes/` directory with complete specifications including:
|
||||
- Cohesive color palette with hex codes
|
||||
- Complementary font pairings for headers and body text
|
||||
- Distinct visual identity suitable for different contexts and audiences
|
||||
|
||||
## Application Process
|
||||
|
||||
After a preferred theme is selected:
|
||||
1. Read the corresponding theme file from the `themes/` directory
|
||||
2. Apply the specified colors and fonts consistently throughout the deck
|
||||
3. Ensure proper contrast and readability
|
||||
4. Maintain the theme's visual identity across all slides
|
||||
|
||||
## Create your Own Theme
|
||||
To handle cases where none of the existing themes work for an artifact, create a custom theme. Based on provided inputs, generate a new theme similar to the ones above. Give the theme a similar name describing what the font/color combinations represent. Use any basic description provided to choose appropriate colors/fonts. After generating the theme, show it for review and verification. Following that, apply the theme as described above.
|
||||
BIN
.claude/skills/theme-factory/theme-showcase.pdf
Normal file
BIN
.claude/skills/theme-factory/theme-showcase.pdf
Normal file
Binary file not shown.
19
.claude/skills/theme-factory/themes/arctic-frost.md
Normal file
19
.claude/skills/theme-factory/themes/arctic-frost.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Arctic Frost
|
||||
|
||||
A cool and crisp winter-inspired theme that conveys clarity, precision, and professionalism.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Ice Blue**: `#d4e4f7` - Light backgrounds and highlights
|
||||
- **Steel Blue**: `#4a6fa5` - Primary accent color
|
||||
- **Silver**: `#c0c0c0` - Metallic accent elements
|
||||
- **Crisp White**: `#fafafa` - Clean backgrounds and text
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Sans Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Healthcare presentations, technology solutions, winter sports, clean tech, pharmaceutical content.
|
||||
19
.claude/skills/theme-factory/themes/botanical-garden.md
Normal file
19
.claude/skills/theme-factory/themes/botanical-garden.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Botanical Garden
|
||||
|
||||
A fresh and organic theme featuring vibrant garden-inspired colors for lively presentations.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Fern Green**: `#4a7c59` - Rich natural green
|
||||
- **Marigold**: `#f9a620` - Bright floral accent
|
||||
- **Terracotta**: `#b7472a` - Earthy warm tone
|
||||
- **Cream**: `#f5f3ed` - Soft neutral backgrounds
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Serif Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Garden centers, food presentations, farm-to-table content, botanical brands, natural products.
|
||||
19
.claude/skills/theme-factory/themes/desert-rose.md
Normal file
19
.claude/skills/theme-factory/themes/desert-rose.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Desert Rose
|
||||
|
||||
A soft and sophisticated theme with dusty, muted tones perfect for elegant presentations.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Dusty Rose**: `#d4a5a5` - Soft primary color
|
||||
- **Clay**: `#b87d6d` - Earthy accent
|
||||
- **Sand**: `#e8d5c4` - Warm neutral backgrounds
|
||||
- **Deep Burgundy**: `#5d2e46` - Rich dark contrast
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: FreeSans Bold
|
||||
- **Body Text**: FreeSans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Fashion presentations, beauty brands, wedding planning, interior design, boutique businesses.
|
||||
19
.claude/skills/theme-factory/themes/forest-canopy.md
Normal file
19
.claude/skills/theme-factory/themes/forest-canopy.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Forest Canopy
|
||||
|
||||
A natural and grounded theme featuring earth tones inspired by dense forest environments.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Forest Green**: `#2d4a2b` - Primary dark green
|
||||
- **Sage**: `#7d8471` - Muted green accent
|
||||
- **Olive**: `#a4ac86` - Light accent color
|
||||
- **Ivory**: `#faf9f6` - Backgrounds and text
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: FreeSerif Bold
|
||||
- **Body Text**: FreeSans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Environmental presentations, sustainability reports, outdoor brands, wellness content, organic products.
|
||||
19
.claude/skills/theme-factory/themes/golden-hour.md
Normal file
19
.claude/skills/theme-factory/themes/golden-hour.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Golden Hour
|
||||
|
||||
A rich and warm autumnal palette that creates an inviting and sophisticated atmosphere.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Mustard Yellow**: `#f4a900` - Bold primary accent
|
||||
- **Terracotta**: `#c1666b` - Warm secondary color
|
||||
- **Warm Beige**: `#d4b896` - Neutral backgrounds
|
||||
- **Chocolate Brown**: `#4a403a` - Dark text and anchors
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: FreeSans Bold
|
||||
- **Body Text**: FreeSans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Restaurant presentations, hospitality brands, fall campaigns, cozy lifestyle content, artisan products.
|
||||
19
.claude/skills/theme-factory/themes/midnight-galaxy.md
Normal file
19
.claude/skills/theme-factory/themes/midnight-galaxy.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Midnight Galaxy
|
||||
|
||||
A dramatic and cosmic theme with deep purples and mystical tones for impactful presentations.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Deep Purple**: `#2b1e3e` - Rich dark base
|
||||
- **Cosmic Blue**: `#4a4e8f` - Mystical mid-tone
|
||||
- **Lavender**: `#a490c2` - Soft accent color
|
||||
- **Silver**: `#e6e6fa` - Light highlights and text
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: FreeSans Bold
|
||||
- **Body Text**: FreeSans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Entertainment industry, gaming presentations, nightlife venues, luxury brands, creative agencies.
|
||||
19
.claude/skills/theme-factory/themes/modern-minimalist.md
Normal file
19
.claude/skills/theme-factory/themes/modern-minimalist.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Modern Minimalist
|
||||
|
||||
A clean and contemporary theme with a sophisticated grayscale palette for maximum versatility.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Charcoal**: `#36454f` - Primary dark color
|
||||
- **Slate Gray**: `#708090` - Medium gray for accents
|
||||
- **Light Gray**: `#d3d3d3` - Backgrounds and dividers
|
||||
- **White**: `#ffffff` - Text and clean backgrounds
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Sans Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Tech presentations, architecture portfolios, design showcases, modern business proposals, data visualization.
|
||||
19
.claude/skills/theme-factory/themes/ocean-depths.md
Normal file
19
.claude/skills/theme-factory/themes/ocean-depths.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Ocean Depths
|
||||
|
||||
A professional and calming maritime theme that evokes the serenity of deep ocean waters.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Deep Navy**: `#1a2332` - Primary background color
|
||||
- **Teal**: `#2d8b8b` - Accent color for highlights and emphasis
|
||||
- **Seafoam**: `#a8dadc` - Secondary accent for lighter elements
|
||||
- **Cream**: `#f1faee` - Text and light backgrounds
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Sans Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Corporate presentations, financial reports, professional consulting decks, trust-building content.
|
||||
19
.claude/skills/theme-factory/themes/sunset-boulevard.md
Normal file
19
.claude/skills/theme-factory/themes/sunset-boulevard.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Sunset Boulevard
|
||||
|
||||
A warm and vibrant theme inspired by golden hour sunsets, perfect for energetic and creative presentations.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Burnt Orange**: `#e76f51` - Primary accent color
|
||||
- **Coral**: `#f4a261` - Secondary warm accent
|
||||
- **Warm Sand**: `#e9c46a` - Highlighting and backgrounds
|
||||
- **Deep Purple**: `#264653` - Dark contrast and text
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Serif Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Creative pitches, marketing presentations, lifestyle brands, event promotions, inspirational content.
|
||||
19
.claude/skills/theme-factory/themes/tech-innovation.md
Normal file
19
.claude/skills/theme-factory/themes/tech-innovation.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Tech Innovation
|
||||
|
||||
A bold and modern theme with high-contrast colors perfect for cutting-edge technology presentations.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- **Electric Blue**: `#0066ff` - Vibrant primary accent
|
||||
- **Neon Cyan**: `#00ffff` - Bright highlight color
|
||||
- **Dark Gray**: `#1e1e1e` - Deep backgrounds
|
||||
- **White**: `#ffffff` - Clean text and contrast
|
||||
|
||||
## Typography
|
||||
|
||||
- **Headers**: DejaVu Sans Bold
|
||||
- **Body Text**: DejaVu Sans
|
||||
|
||||
## Best Used For
|
||||
|
||||
Tech startups, software launches, innovation showcases, AI/ML presentations, digital transformation content.
|
||||
31
.claude/users.json
Normal file
31
.claude/users.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"users": {
|
||||
"mike": {
|
||||
"full_name": "Mike Swanson",
|
||||
"email": "mike@azcomputerguru.com",
|
||||
"role": "admin",
|
||||
"title": "President",
|
||||
"known_machines": ["DESKTOP-0O8A1RL", "Mikes-MacBook-Air"],
|
||||
"git_name": "Mike Swanson",
|
||||
"git_email": "mike@azcomputerguru.com",
|
||||
"notes": "Owner. Full access to everything."
|
||||
},
|
||||
"howard": {
|
||||
"full_name": "Howard Enos",
|
||||
"email": "howard@azcomputerguru.com",
|
||||
"role": "tech",
|
||||
"title": "Technician",
|
||||
"known_machines": ["ACG-TECH03L"],
|
||||
"git_name": "Howard Enos",
|
||||
"git_email": "howard@azcomputerguru.com",
|
||||
"gitea_username": "howard",
|
||||
"gitea_initial_password": "ACG-Tech2026!",
|
||||
"gitea_must_change_password": true,
|
||||
"notes": "Employee, Mike's brother. Full trust. Same access as Mike for MSP tracking and daily work. Has own Gitea account (howard) with admin access to all repos."
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"admin": "Full access to all systems, credentials, deployments, and infrastructure.",
|
||||
"tech": "Full access to all systems, credentials, and client work. Same as admin for this organization."
|
||||
}
|
||||
}
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -3,12 +3,20 @@ backups/
|
||||
|
||||
# Local settings (machine-specific)
|
||||
.claude/settings.local.json
|
||||
.claude/identity.json
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
*.bak
|
||||
|
||||
# Live secrets / tokens — never commit
|
||||
.token
|
||||
.token_*
|
||||
*.jwt
|
||||
token.txt
|
||||
.token.txt
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -53,6 +61,7 @@ build/
|
||||
*.sqlite
|
||||
logs/
|
||||
.claude/tokens.json
|
||||
**/.tokens.json
|
||||
.claude/context-recall-config.env
|
||||
.claude/context-recall-config.env.backup
|
||||
.claude/context-cache/
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "projects/msp-tools/guru-rmm"]
|
||||
path = projects/msp-tools/guru-rmm
|
||||
url = https://git.azcomputerguru.com/azcomputerguru/gururmm.git
|
||||
264
CONTEXT.md
Normal file
264
CONTEXT.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# ClaudeTools - Project Context
|
||||
|
||||
**Last Updated:** 2026-04-14
|
||||
**Status:** Active - Production Stable
|
||||
|
||||
## Quick Start - Infrastructure Overview
|
||||
|
||||
| Component | Location | Access | Notes |
|
||||
|-----------|----------|--------|-------|
|
||||
| **Production API** | http://172.16.3.30:8001 | Public access | ClaudeTools work tracking API |
|
||||
| **Production DB** | MariaDB @ 172.16.3.30:3306/claudetools | Vault credentials | 38 tables, AES-256-GCM encryption |
|
||||
| **Vault (SOPS)** | D:\vault\ | age-encrypted YAML | Primary credential store |
|
||||
| **1Password** | Service account | Fallback | op://Projects/ClaudeTools * |
|
||||
| **Gitea Repo** | git.azcomputerguru.com/azcomputerguru/claudetools | Active development | Main codebase |
|
||||
|
||||
**Get DB credentials:**
|
||||
```bash
|
||||
bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password
|
||||
```
|
||||
|
||||
## Current State (READ THIS FIRST)
|
||||
|
||||
### Project Status
|
||||
- **API:** 95+ endpoints, production-stable
|
||||
- **Database:** 38 tables, fully encrypted sensitive fields
|
||||
- **Authentication:** JWT-based, AES-256-GCM for API keys
|
||||
- **Deployment:** Auto-deploy via Gitea webhooks (planned)
|
||||
|
||||
### Active Subprojects
|
||||
1. **GuruRMM** - Remote monitoring system (see projects/msp-tools/guru-rmm/CONTEXT.md)
|
||||
2. **Dataforth DOS** - Test datasheet pipeline (see projects/dataforth-dos/CONTEXT.md)
|
||||
|
||||
### Session Logs
|
||||
- **Project-specific:** projects/*/session-logs/
|
||||
- **Client work:** clients/*/session-logs/
|
||||
- **General:** session-logs/ (root)
|
||||
|
||||
## Anti-Patterns (DON'T DO THIS)
|
||||
|
||||
❌ **DO NOT query database directly** - Use Database Agent for ALL operations
|
||||
|
||||
❌ **DO NOT write production code yourself** - Delegate to Coding Agent, coordinate as needed
|
||||
|
||||
❌ **DO NOT use emojis** - ASCII markers only: [OK], [ERROR], [WARNING], [SUCCESS], [INFO]
|
||||
|
||||
❌ **DO NOT hardcode credentials** - Always use SOPS vault (primary) or 1Password (fallback)
|
||||
|
||||
❌ **DO NOT skip Code Review Agent** - MANDATORY after any code changes
|
||||
|
||||
❌ **DO NOT execute >500 token operations directly** - Delegate to appropriate agent
|
||||
|
||||
## Where to Find Things
|
||||
|
||||
### Repository Structure
|
||||
```
|
||||
ClaudeTools/
|
||||
├── .claude/
|
||||
│ ├── CLAUDE.md # Project instructions (directives)
|
||||
│ ├── REFERENCE.md # Technical reference
|
||||
│ ├── CODING_GUIDELINES.md # Code standards
|
||||
│ ├── FILE_PLACEMENT_GUIDE.md # Where to save files
|
||||
│ ├── agents/ # Agent definitions
|
||||
│ └── memory/ # Persistent facts (syncs via Git)
|
||||
├── credentials.md # Infrastructure reference (migrating to vault)
|
||||
├── session-logs/ # General session logs
|
||||
├── projects/
|
||||
│ ├── msp-tools/guru-rmm/ # GuruRMM (CONTEXT.md there)
|
||||
│ ├── dataforth-dos/ # Dataforth (CONTEXT.md there)
|
||||
│ └── claudetools-api/ # API codebase (legacy)
|
||||
├── clients/
|
||||
│ └── [client-name]/ # Client-specific work
|
||||
└── D:\vault\ # SOPS encrypted credentials (separate repo)
|
||||
```
|
||||
|
||||
### Credential Locations
|
||||
|
||||
**Primary: SOPS Vault (D:\vault\)**
|
||||
```bash
|
||||
# Search for keywords (no decryption needed)
|
||||
bash D:/vault/scripts/vault.sh search "claudetools"
|
||||
|
||||
# Get specific field
|
||||
bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password
|
||||
|
||||
# List all entries
|
||||
bash D:/vault/scripts/vault.sh list
|
||||
```
|
||||
|
||||
**Structure:**
|
||||
- infrastructure/ - Servers, network gear
|
||||
- clients/ - Client-specific credentials
|
||||
- services/ - External services (GitHub, APIs)
|
||||
- projects/ - Project databases, APIs
|
||||
- msp-tools/ - MSP application credentials
|
||||
|
||||
**Fallback: 1Password**
|
||||
```bash
|
||||
# ClaudeTools database credentials
|
||||
op read "op://Projects/ClaudeTools Database/password"
|
||||
|
||||
# ClaudeTools API auth
|
||||
op read "op://Projects/ClaudeTools API Auth/credential"
|
||||
```
|
||||
|
||||
## Common Operations
|
||||
|
||||
### Start Work on Subproject
|
||||
```
|
||||
User: "Let's work on GuruRMM tunnel Phase 2"
|
||||
|
||||
Claude should:
|
||||
1. Read projects/msp-tools/guru-rmm/CONTEXT.md (this file)
|
||||
2. Check recent session logs referenced in CONTEXT.md
|
||||
3. Understand current state, infrastructure, anti-patterns
|
||||
4. Proceed without asking user for context
|
||||
|
||||
DO NOT:
|
||||
- Ask user "what's the server IP?"
|
||||
- Ask user "where is the database?"
|
||||
- Ask user "what credentials should I use?"
|
||||
All of this is in CONTEXT.md
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
```bash
|
||||
# WRONG: Direct query
|
||||
ssh user@172.16.3.30 "mysql -u claudetools -p claudetools -e 'SELECT * FROM ...'"
|
||||
|
||||
# RIGHT: Delegate to Database Agent
|
||||
"Use Database Agent to query work_logs table for entries from last 7 days"
|
||||
```
|
||||
|
||||
### Deploy Code Changes
|
||||
```bash
|
||||
# WRONG: Deploy yourself
|
||||
scp file.js user@172.16.3.30:/path/
|
||||
|
||||
# RIGHT: Follow project deployment process
|
||||
# (See project-specific CONTEXT.md for deployment steps)
|
||||
```
|
||||
|
||||
## Project-Specific Context Files
|
||||
|
||||
**When user says "work on [project]", read that project's CONTEXT.md FIRST:**
|
||||
|
||||
| Project | CONTEXT.md Location | What It Contains |
|
||||
|---------|---------------------|------------------|
|
||||
| GuruRMM | projects/msp-tools/guru-rmm/CONTEXT.md | Server IPs, deployment, tunnel architecture, agent status |
|
||||
| Dataforth DOS | projects/dataforth-dos/CONTEXT.md | AD2/AD1 infrastructure, testdatadb service, log formats |
|
||||
| ClaudeTools API | (This file) | Main project overview, credential locations |
|
||||
|
||||
## Coordination Rules (My Role)
|
||||
|
||||
I am a **Coordinator**, NOT an executor:
|
||||
|
||||
| Operation | Delegate To |
|
||||
|-----------|-------------|
|
||||
| Database queries/updates | Database Agent |
|
||||
| Production code generation | Coding Agent |
|
||||
| Code review (MANDATORY) | Code Review Agent |
|
||||
| Test execution | Testing Agent |
|
||||
| Git commit/push | Gitea Agent |
|
||||
| Backups/restore | Backup Agent |
|
||||
| File exploration | Explore Agent |
|
||||
| Semantic code search | deep-explore Agent (GrepAI) |
|
||||
| Complex reasoning | General-purpose + Sequential Thinking |
|
||||
|
||||
**I do myself:** Simple responses, reading 1-2 files, presenting results, planning, decisions
|
||||
|
||||
**Rule:** >500 tokens of work = delegate
|
||||
|
||||
## Memory System
|
||||
|
||||
**Location:** `.claude/memory/` (syncs across machines via Git)
|
||||
|
||||
**Structure:**
|
||||
- MEMORY.md - Index of all facts
|
||||
- [topic]-context.md - Topic-specific persistent facts
|
||||
|
||||
**IMPORTANT:** Always write to `.claude/memory/` (repo-relative), NOT `~/.claude/projects/*/memory/`
|
||||
|
||||
## Session Log Locations
|
||||
|
||||
**Follow these rules:**
|
||||
|
||||
| Work Type | Save To |
|
||||
|-----------|---------|
|
||||
| Dataforth DOS work | projects/dataforth-dos/session-logs/ |
|
||||
| ClaudeTools API code | projects/claudetools-api/session-logs/ |
|
||||
| GuruRMM work | projects/msp-tools/guru-rmm/session-logs/ |
|
||||
| Client work | clients/[client-name]/session-logs/ |
|
||||
| General/mixed work | session-logs/ (root) |
|
||||
|
||||
**See:** .claude/FILE_PLACEMENT_GUIDE.md for full rules
|
||||
|
||||
## Auto-Invoke Skills
|
||||
|
||||
**Frontend Design:** Auto-invoke `/frontend-design` skill after ANY UI change (HTML/CSS/JSX/styling)
|
||||
|
||||
**Sequential Thinking:** Use for genuine complexity only:
|
||||
- Rejection loops (3+ failed attempts)
|
||||
- Critical architectural decisions
|
||||
- Multi-step debugging with unclear root cause
|
||||
- NOT for every task
|
||||
|
||||
## Key Commands
|
||||
|
||||
| Command | Purpose | When |
|
||||
|---------|---------|------|
|
||||
| `/checkpoint` | Git commit + database context save | After code changes |
|
||||
| `/save` | Comprehensive session log | End of session |
|
||||
| `/context` | Search session logs + credentials | User references previous work |
|
||||
| `/1password` | 1Password operations | Manage secrets |
|
||||
| `/sync` | Sync from Gitea | Update local config |
|
||||
| `/refresh-directives` | Re-read CLAUDE.md | After summarization, large tasks |
|
||||
|
||||
## Reference Documents (Read On-Demand)
|
||||
|
||||
- **Project instructions:** .claude/CLAUDE.md (my role, agent delegation rules)
|
||||
- **Technical reference:** .claude/REFERENCE.md (endpoints, database, workflows)
|
||||
- **Coding standards:** .claude/CODING_GUIDELINES.md (agents read, not every session)
|
||||
- **File placement:** .claude/FILE_PLACEMENT_GUIDE.md (where to save files)
|
||||
- **MCP servers:** MCP_SERVERS.md (available integrations)
|
||||
- **Agent definitions:** .claude/agents/*.md (agent capabilities)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "I don't know the database password"
|
||||
- **Check:** D:\vault\ (SOPS encrypted)
|
||||
- **Command:** `bash D:/vault/scripts/vault.sh get-field projects/claudetools/database.sops.yaml credentials.password`
|
||||
- **Fallback:** credentials.md (has 1Password references)
|
||||
|
||||
### "Where is the GuruRMM server?"
|
||||
- **Check:** projects/msp-tools/guru-rmm/CONTEXT.md
|
||||
- **Answer:** 172.16.3.30 (listed in first table)
|
||||
|
||||
### "How do I deploy Dataforth changes?"
|
||||
- **Check:** projects/dataforth-dos/CONTEXT.md
|
||||
- **Section:** "Common Operations → Deploy Code to AD2"
|
||||
|
||||
### "I forgot my role as Coordinator"
|
||||
- **Read:** .claude/CLAUDE.md
|
||||
- **Command:** `/refresh-directives`
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **Credentials (vault):** D:\vault\ (SOPS encrypted YAML)
|
||||
- **Credentials (legacy):** credentials.md (migrating to vault)
|
||||
- **Gitea:** http://172.16.3.20:3000/azcomputerguru/claudetools
|
||||
- **API Docs:** http://172.16.3.30:8001/api/docs
|
||||
|
||||
---
|
||||
|
||||
**When user says "work on [project]":**
|
||||
1. Read [project]/CONTEXT.md FIRST (don't ask user for context)
|
||||
2. Check recent session logs mentioned in CONTEXT.md
|
||||
3. Understand infrastructure, anti-patterns, current state
|
||||
4. Proceed with work
|
||||
|
||||
**This eliminates:**
|
||||
- "What's the server IP?" → In CONTEXT.md
|
||||
- "Where's the database?" → In CONTEXT.md
|
||||
- "What credentials?" → In CONTEXT.md
|
||||
- "What did we do last time?" → In session logs referenced by CONTEXT.md
|
||||
@@ -1,68 +0,0 @@
|
||||
# Stage TXT Import Task
|
||||
# Date: 2026-03-28
|
||||
# Context: CTONWTXT.BAT now uploads C:\STAGE\*.TXT from DOS machines to T:\STAGE\%MACHINE%\
|
||||
|
||||
## What happened
|
||||
|
||||
1. CTONWTXT.BAT was never being called -- fixed, now called from CTONW.BAT on every boot
|
||||
2. Destination changed from broken X: (Novell serve.sys check) to T:\STAGE\%MACHINE%\
|
||||
3. DOS 6.22 can't MD on existing dirs without error, so dirs are pre-created on NAS
|
||||
4. All TS-* machine folders pre-created under /data/test/STAGE/ on D2TESTNAS
|
||||
|
||||
## What needs to run
|
||||
|
||||
Save the script below as C:\Shares\testdatadb\import-all-stage.js and run it:
|
||||
|
||||
cd C:\Shares\testdatadb
|
||||
node import-all-stage.js
|
||||
|
||||
## What it does
|
||||
|
||||
- Scans \\D2TESTNAS\test\STAGE\TS-*\*.TXT (~8,100 files across 10 machines)
|
||||
- Parses each TXT datasheet (Date, Model, SN)
|
||||
- Decodes hex-prefix serial numbers for 8.3 filename encoding:
|
||||
- Letter prefix = hex digit: A=10, B=11, C=12, ..., H=17, etc.
|
||||
- Example: H8236-12.TXT has SN: 178236-12 inside the file
|
||||
- Example: A819-1.TXT has SN: A819-1 inside -> decoded to 10819-1
|
||||
- The SN line inside H-prefix files already has the full numeric serial
|
||||
- The SN line inside A-prefix files still has the encoded serial
|
||||
- Cross-references against testdata.db by (serial_number, model_number)
|
||||
- Inserts MISSING records as log_type='SHT' with test_station from folder name
|
||||
- Copies ALL files to X:\For_Web\{decoded_serial}.TXT (the web share)
|
||||
|
||||
## Machines with data
|
||||
|
||||
TS-4L: 3,082 files (largest)
|
||||
TS-4R: 2,741 files
|
||||
TS-1R: 509 files
|
||||
TS-8R: 478 files
|
||||
TS-3R: 435 files
|
||||
TS-11R: 325 files
|
||||
TS-8L: 285 files
|
||||
TS-11L: 248 files
|
||||
TS-27: 10 files (already imported this session)
|
||||
TS-1L: 1 file
|
||||
|
||||
## Serial number encoding (8.3 filename scheme)
|
||||
|
||||
The QuickBASIC ATE software encodes long serial numbers to fit DOS 8.3 filenames.
|
||||
The first two digits get replaced with a hex letter if the serial is too long:
|
||||
|
||||
178236-12 -> H8236-12.TXT (17 -> H, which is char code 72, 72-55=17)
|
||||
10819-1 -> A819-1.TXT (10 -> A, which is char code 65, 65-55=10)
|
||||
|
||||
Decode: letter.charCodeAt(0) - 55 = numeric prefix
|
||||
Only applies if filename starts with [A-Z] followed by digits.
|
||||
|
||||
## TS-27 already done
|
||||
|
||||
10 files from TS-27 were already imported earlier this session into the DB as SHT records.
|
||||
The import script uses INSERT OR REPLACE so re-running is safe.
|
||||
|
||||
## Previous CTONWTXT.BAT issues (resolved)
|
||||
|
||||
- v1.0: Never called, checked for Novell serve.sys, used X: drive parameter
|
||||
- v2.0: Called from CTONW, but used mixed-case "Stage" path -> failed on DOS
|
||||
- v2.1: All uppercase STAGE, but had MD commands that fail on existing dirs
|
||||
- v2.2: Same issue
|
||||
- v2.3: Removed MD entirely, dirs pre-created on NAS. CURRENT VERSION.
|
||||
@@ -1,80 +0,0 @@
|
||||
Subject: Test Datasheets - Weekend Update: All 73 Quatronix Sheets Generated, Work Order Search Live
|
||||
|
||||
John, Ken,
|
||||
|
||||
Quick update on progress since Friday's email. The pipeline is significantly further along.
|
||||
|
||||
## Quatronix Customer Issue - RESOLVED
|
||||
|
||||
All 73 requested datasheets have been generated (TXT + PDF). The last holdout was SCM5B49-05 (SN 177000-15) — the 5B49DATA.DAT spec file was empty, but John pointed us to 5B49_2.DAT which had the data. All 73 files are ready to send to Peter/Ginger.
|
||||
|
||||
## Model Spec Coverage Expanded
|
||||
|
||||
We went from 751 model specs to 1,470+ by loading additional spec databases:
|
||||
|
||||
| Spec File | Family | Models |
|
||||
|-----------|--------|--------|
|
||||
| 5BMAIN.DAT | SCM5B | 481 |
|
||||
| 5B45DATA.DAT | SCM5B (freq/counter) | 56 |
|
||||
| DB5B48.DAT | SCM5B (multi-bandwidth) | 3 |
|
||||
| 5B49_2.DAT | SCM5B (sample & hold) | 15 |
|
||||
| 8BMAIN.DAT | 8B | 148 |
|
||||
| DSCOUT.DAT | DSCA (output) | 23 |
|
||||
| DSCMAIN4.DAT | DSCA (input) | 391 |
|
||||
| SCTMAIN.DAT | DSCT | 103 |
|
||||
| 7BMAIN.DAT | SCM7B | 276 |
|
||||
|
||||
If there are additional spec files we're missing, let me know the paths and we'll add them.
|
||||
|
||||
## SCM7B Support Added
|
||||
|
||||
The 7B product family is now fully supported in the datasheet generator:
|
||||
- 31 test parameters (vs 20 for SCM5B)
|
||||
- Correct header ("SCM" prefix prepended to model name)
|
||||
- 120VAC Withstand / Hi-Pot (skipped for 7BPT models)
|
||||
- "Packing Check List" with blank fields (vs pre-marked checkboxes on 5B/8B)
|
||||
- "Tested by" and "QC" signature lines
|
||||
- Note: The 7B DAT format (single CSV line) doesn't include individual accuracy test points, so the accuracy table is omitted. Only the Final Test Results section is generated from DAT data.
|
||||
|
||||
## Work Order Search & Linking
|
||||
|
||||
Imported all 33,745 work order status reports from the test station Reports folders:
|
||||
- 63,263 individual test lines parsed (serial number, model, pass/fail, date/time, station)
|
||||
- 2.27 million test records linked to their work orders
|
||||
|
||||
In the web app (http://192.168.0.6:3000):
|
||||
- New "Work Order #" search field — enter a WO number to find all associated test records
|
||||
- Click the WO number in any record's detail view to see the full work order:
|
||||
- All serial numbers tested under that WO
|
||||
- Pass/fail status for each (including retests)
|
||||
- Test program and version used
|
||||
- Test station and timestamps
|
||||
- New work order reports are automatically imported when synced from the NAS
|
||||
|
||||
## Datasheet Formatting Refined
|
||||
|
||||
Compared generated datasheets against originals from the DFWDS archive and fixed column alignment to match the QuickBASIC output:
|
||||
- TAB positions match exactly (parameter names, measured values, spec limits, pass/fail)
|
||||
- Number formatting matches QB PRINT USING (right-justified, correct decimal places)
|
||||
- STR$() behavior replicated (leading space for positive numbers, dropped leading zeros)
|
||||
- Spec limit formatting matches (e.g., "+/- .03 %" not "+/- 0.03 %")
|
||||
|
||||
## View Button Updated
|
||||
|
||||
The "SHEET" button in the web app now shows a styled HTML page that matches the PDF/TXT layout — white page, monospace font, same column alignment. Includes Print and Download PDF buttons.
|
||||
|
||||
## Infrastructure
|
||||
|
||||
- Created domain service account (INTRANET\svc_testdatadb) for the TestDataDB Windows service — resolves the file permission issues we were hitting
|
||||
- Added STAGE folder sync to the NAS sync script — TXT datasheets from DOS machines will now be pulled to AD2 automatically
|
||||
- Work order report import added to sync script — new reports are ingested automatically every 15 minutes
|
||||
|
||||
## Still Open
|
||||
|
||||
1. **Website upload** — The old Uploader.aspx endpoints are dead. Need to determine the new upload mechanism for dataforth.com.
|
||||
2. **STAGE backlog** — ~8,100 TXT files on the NAS from DOS machines need to be processed (script ready, haven't run it yet).
|
||||
3. **Pending ForWeb export** — ~845K records in the database don't have TXT files in For_Web yet (mostly 7B and older records). Can batch-export as needed.
|
||||
|
||||
Let me know if you need anything else.
|
||||
|
||||
Mike
|
||||
45
WORKITEMS.md
Normal file
45
WORKITEMS.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Shared Work Items
|
||||
|
||||
Tag yourself to claim. Check off when done. Add new items at the bottom of the relevant section.
|
||||
|
||||
**Syntax:** `- [ ] Description — @mike/@howard/@unassigned | added YYYY-MM-DD`
|
||||
|
||||
---
|
||||
|
||||
## Active
|
||||
|
||||
- [ ] Deploy session manager to SAGE-SQL (IIS app, Windows Auth) — files ready at `clients/dataforth/session-manager/` — @mike | added 2026-04-17
|
||||
- [x] Cascades Synology (cascadesds) — get admin creds, add to vault — @howard | done 2026-04-17 (vault: `clients/cascades-tucson/synology-cascadesds.sops.yaml`)
|
||||
- [ ] Cascades — second Life Enrichment machine: end-to-end folder redirection test (tomorrow). See `clients/cascades-tucson/session-logs/2026-04-17-howard-cascades-onboarding-and-folder-redirection.md` — @howard | added 2026-04-17
|
||||
- [ ] Cascades GPO — add Desktop/Pictures/Music/Videos/Favorites once 2nd machine validates the pattern, and retire the DLTAGOI Desktop reg hack — @howard | added 2026-04-17
|
||||
- [ ] Cascades — build matching folder-redirection GPOs for every other department (Nursing, Admin, Maintenance, etc.) once Life Enrichment is proven — @howard | added 2026-04-17
|
||||
- [ ] Cascades — design OneDrive-to-server migration plan (machines with Documents/Desktop already in OneDrive KFM need data-migration + unlink BEFORE the GPO applies) — @unassigned | added 2026-04-17
|
||||
- [ ] Cascades HIPAA hardening — `Set-SmbShare -Name homes -EncryptData $true`, enable file-access auditing on D:\Homes, verify BitLocker on CS-SERVER D: — @unassigned | added 2026-04-17
|
||||
- [ ] GuruRMM bug — agent command executor can wedge after a user-context PS command hangs; doesn't recover on reboot. File + fix. — @mike | added 2026-04-17
|
||||
- [ ] Howard Gitea account — create via web UI at git.azcomputerguru.com — @mike | added 2026-04-16
|
||||
- [ ] desertrat.com — add DMARC p=reject + harden SPF on Route 53 (need AWS access) — @unassigned | added 2026-04-17
|
||||
- [ ] desertrat.com — long-term migration from WebSvr to IX + MailProtector — @unassigned | added 2026-04-17
|
||||
- [ ] MVAN other domains — only mvaninc.com has DMARC; client has other domains needing protection — @unassigned | added 2026-04-17
|
||||
- [ ] Glaztech Syncro ticket #32165 — timer entry billed wrong (should be comment+time); fix in Syncro GUI — @mike | added 2026-04-17
|
||||
- [ ] jparkinsonaz.com certbot — retry autodiscover cert once A record TTL expires — @unassigned | added 2026-04-17
|
||||
- [ ] Neptune jparkinson password — set to jP$48504850, verify mail working — @unassigned | added 2026-04-17
|
||||
- [ ] Len's Auto Brokerage — deploy GuruRMM v0.6.1 to 10 Windows endpoints — @mike | added 2026-04-16
|
||||
- [ ] GuruRMM server migration 5 — sqlx checksum drift blocks new server build — @mike | added 2026-04-16
|
||||
- [ ] Jupiter Windows VM — Server 2022 build worker for MSI CI — @unassigned | added 2026-04-16
|
||||
- [ ] Cloudflare SXG — disable via dashboard (API tokens lack scope), auto-removes June 23 — @unassigned | added 2026-04-17
|
||||
- [ ] GrepAI index — run `grepai watch` to build semantic search index — @unassigned | added 2026-04-16
|
||||
- [ ] Change LAN subnet for ACG-DC16/NEPTUNE on Dataforth network — current 172.16.x.x collides with ACG network (172.16.x.x/22) — @unassigned | added 2026-04-18
|
||||
|
||||
## Completed
|
||||
|
||||
_Move items here when done. Keep for 30 days then delete._
|
||||
|
||||
---
|
||||
|
||||
## How to use
|
||||
|
||||
- **Claim:** change `@unassigned` to your name
|
||||
- **Add:** append to Active section with today's date
|
||||
- **Complete:** move to Completed with date: `- [x] Description — @mike | done 2026-04-18`
|
||||
- **Claude:** say "show work items" or "add work item: ..." and Claude reads/updates this file
|
||||
- **Sync:** items sync via `/sync` like everything else
|
||||
@@ -35,6 +35,7 @@ from api.routers import (
|
||||
version,
|
||||
quotes,
|
||||
admin_quotes,
|
||||
ticktick,
|
||||
)
|
||||
|
||||
# Import middleware
|
||||
@@ -130,6 +131,9 @@ app.include_router(bulk_import.router, prefix="/api/bulk-import", tags=["Bulk Im
|
||||
app.include_router(quotes.router, prefix="/api/quotes", tags=["Quotes"])
|
||||
app.include_router(admin_quotes.router, prefix="/api/admin/quotes", tags=["Admin Quotes"])
|
||||
|
||||
# External integrations
|
||||
app.include_router(ticktick.router, prefix="/api/ticktick", tags=["TickTick"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
333
api/routers/ticktick.py
Normal file
333
api/routers/ticktick.py
Normal file
@@ -0,0 +1,333 @@
|
||||
"""
|
||||
TickTick API router for ClaudeTools.
|
||||
|
||||
This module defines REST API endpoints for managing TickTick projects and tasks,
|
||||
proxying requests through the TickTickService with automatic token management.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from api.middleware.auth import get_current_user
|
||||
from api.services.ticktick_service import TickTickResult, get_ticktick_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Pydantic request/response schemas
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ProjectCreate(BaseModel):
|
||||
"""Schema for creating a new TickTick project."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=200, description="Project name")
|
||||
color: Optional[str] = Field(
|
||||
None, description="Hex color string (e.g., '#FF6347')"
|
||||
)
|
||||
view_mode: Optional[str] = Field(
|
||||
None, description="View mode: 'list', 'kanban', or 'timeline'"
|
||||
)
|
||||
kind: Optional[str] = Field(
|
||||
None, description="Project kind: 'TASK' or 'NOTE'"
|
||||
)
|
||||
|
||||
|
||||
class ProjectUpdate(BaseModel):
|
||||
"""Schema for updating an existing TickTick project."""
|
||||
|
||||
name: Optional[str] = Field(
|
||||
None, min_length=1, max_length=200, description="New project name"
|
||||
)
|
||||
color: Optional[str] = Field(None, description="New hex color string")
|
||||
view_mode: Optional[str] = Field(None, description="New view mode")
|
||||
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
"""Schema for creating a new task in a TickTick project."""
|
||||
|
||||
title: str = Field(..., min_length=1, max_length=500, description="Task title")
|
||||
content: Optional[str] = Field(None, description="Task description/content")
|
||||
priority: Optional[int] = Field(
|
||||
None,
|
||||
ge=0,
|
||||
le=5,
|
||||
description="Priority: 0=none, 1=low, 3=medium, 5=high",
|
||||
)
|
||||
due_date: Optional[str] = Field(
|
||||
None, description="Due date in ISO 8601 format"
|
||||
)
|
||||
tags: Optional[list[str]] = Field(None, description="List of tag strings")
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
"""Schema for updating an existing task."""
|
||||
|
||||
title: Optional[str] = Field(
|
||||
None, min_length=1, max_length=500, description="New task title"
|
||||
)
|
||||
content: Optional[str] = Field(None, description="New task content")
|
||||
priority: Optional[int] = Field(
|
||||
None, ge=0, le=5, description="New priority level"
|
||||
)
|
||||
due_date: Optional[str] = Field(None, description="New due date in ISO 8601 format")
|
||||
tags: Optional[list[str]] = Field(None, description="New list of tags")
|
||||
|
||||
|
||||
class TickTickResponse(BaseModel):
|
||||
"""Standard response wrapper for all TickTick endpoints."""
|
||||
|
||||
success: bool
|
||||
data: Optional[dict] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _to_response(result: TickTickResult, status_code_on_error: int = 500) -> dict:
|
||||
"""
|
||||
Convert a TickTickResult to a JSON-serializable response dict.
|
||||
|
||||
Raises an HTTPException when the result indicates failure.
|
||||
|
||||
Args:
|
||||
result: The service result to convert.
|
||||
status_code_on_error: HTTP status code for error responses.
|
||||
|
||||
Returns:
|
||||
Dict matching the TickTickResponse schema.
|
||||
"""
|
||||
if not result.success:
|
||||
raise HTTPException(
|
||||
status_code=status_code_on_error,
|
||||
detail={
|
||||
"success": False,
|
||||
"data": None,
|
||||
"error": result.error or "Unknown error",
|
||||
},
|
||||
)
|
||||
return {"success": True, "data": result.data, "error": None}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Project endpoints
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=TickTickResponse,
|
||||
summary="List all TickTick projects",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def list_projects(current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Retrieve all projects (lists) from the authenticated TickTick account.
|
||||
|
||||
**Example Request:**
|
||||
```
|
||||
GET /api/ticktick
|
||||
```
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.list_projects()
|
||||
return _to_response(result)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{project_id}",
|
||||
response_model=TickTickResponse,
|
||||
summary="Get a TickTick project with tasks",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def get_project(project_id: str, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Retrieve a single project and its associated task data.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.get_project(project_id)
|
||||
return _to_response(result, status_code_on_error=404)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=TickTickResponse,
|
||||
summary="Create a new TickTick project",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_project(body: ProjectCreate, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Create a new project (list) in TickTick.
|
||||
|
||||
**Request Body:**
|
||||
- **name** (required): Project name.
|
||||
- **color**: Hex color string.
|
||||
- **view_mode**: View mode ('list', 'kanban', 'timeline').
|
||||
- **kind**: Project kind ('TASK' or 'NOTE').
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.create_project(
|
||||
name=body.name,
|
||||
color=body.color,
|
||||
view_mode=body.view_mode,
|
||||
kind=body.kind,
|
||||
)
|
||||
return _to_response(result, status_code_on_error=400)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{project_id}",
|
||||
response_model=TickTickResponse,
|
||||
summary="Update a TickTick project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def update_project(project_id: str, body: ProjectUpdate, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Update an existing project's properties.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID to update.
|
||||
|
||||
**Request Body:**
|
||||
At least one field must be provided.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.update_project(
|
||||
project_id=project_id,
|
||||
name=body.name,
|
||||
color=body.color,
|
||||
view_mode=body.view_mode,
|
||||
)
|
||||
return _to_response(result, status_code_on_error=400)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{project_id}",
|
||||
response_model=TickTickResponse,
|
||||
summary="Delete a TickTick project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def delete_project(project_id: str, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Delete a project from TickTick.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID to delete.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.delete_project(project_id)
|
||||
return _to_response(result, status_code_on_error=404)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Task endpoints
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/tasks",
|
||||
response_model=TickTickResponse,
|
||||
summary="Create a task in a TickTick project",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_task(project_id: str, body: TaskCreate, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Create a new task within the specified project.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID.
|
||||
|
||||
**Request Body:**
|
||||
- **title** (required): Task title.
|
||||
- **content**: Task description.
|
||||
- **priority**: 0=none, 1=low, 3=medium, 5=high.
|
||||
- **due_date**: ISO 8601 date string.
|
||||
- **tags**: List of tag strings.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.create_task(
|
||||
title=body.title,
|
||||
project_id=project_id,
|
||||
content=body.content,
|
||||
priority=body.priority,
|
||||
due_date=body.due_date,
|
||||
tags=body.tags,
|
||||
)
|
||||
return _to_response(result, status_code_on_error=400)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{project_id}/tasks/{task_id}",
|
||||
response_model=TickTickResponse,
|
||||
summary="Update a task in a TickTick project",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def update_task(project_id: str, task_id: str, body: TaskUpdate, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Update an existing task's properties.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID.
|
||||
- **task_id**: The task ID to update.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.update_task(
|
||||
task_id=task_id,
|
||||
project_id=project_id,
|
||||
title=body.title,
|
||||
content=body.content,
|
||||
priority=body.priority,
|
||||
due_date=body.due_date,
|
||||
tags=body.tags,
|
||||
)
|
||||
return _to_response(result, status_code_on_error=400)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/tasks/{task_id}/complete",
|
||||
response_model=TickTickResponse,
|
||||
summary="Complete a task",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def complete_task(project_id: str, task_id: str, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Mark a task as complete in TickTick.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID.
|
||||
- **task_id**: The task ID to mark complete.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.complete_task(task_id=task_id, project_id=project_id)
|
||||
return _to_response(result, status_code_on_error=400)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{project_id}/tasks/{task_id}",
|
||||
response_model=TickTickResponse,
|
||||
summary="Delete a task",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def delete_task(project_id: str, task_id: str, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
Delete a task from a TickTick project.
|
||||
|
||||
**Path Parameters:**
|
||||
- **project_id**: The TickTick project ID.
|
||||
- **task_id**: The task ID to delete.
|
||||
"""
|
||||
service = get_ticktick_service()
|
||||
result = await service.delete_task(task_id=task_id, project_id=project_id)
|
||||
return _to_response(result, status_code_on_error=404)
|
||||
596
api/services/ticktick_service.py
Normal file
596
api/services/ticktick_service.py
Normal file
@@ -0,0 +1,596 @@
|
||||
"""
|
||||
TickTick API integration service for ClaudeTools.
|
||||
|
||||
This module handles all interactions with the TickTick Open API for project
|
||||
and task management. Tokens are managed via a local JSON file with automatic
|
||||
refresh on 401 responses.
|
||||
|
||||
API Documentation: https://developer.ticktick.com/api
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TICKTICK_API_BASE_URL = "https://api.ticktick.com/open/v1"
|
||||
TICKTICK_TOKEN_URL = "https://ticktick.com/oauth/token"
|
||||
TICKTICK_TOKEN_FILE = Path(__file__).resolve().parents[2] / "mcp-servers" / "ticktick" / ".tokens.json"
|
||||
|
||||
VAULT_SCRIPT = "D:/vault/scripts/vault.sh"
|
||||
VAULT_ENTRY = "services/ticktick.sops.yaml"
|
||||
|
||||
TICKTICK_TIMEOUT_SECONDS = 30.0
|
||||
TICKTICK_CONNECT_TIMEOUT_SECONDS = 10.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class TickTickResult:
|
||||
"""Result wrapper for all TickTick API operations."""
|
||||
|
||||
success: bool
|
||||
data: Optional[dict] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
def _vault_get_field(field: str) -> str:
|
||||
"""Retrieve a single field from the SOPS vault entry."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["bash", VAULT_SCRIPT, "get-field", VAULT_ENTRY, field],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip()
|
||||
logger.error("[ERROR] Vault returned empty or error for %s", field)
|
||||
return ""
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
||||
logger.error("[ERROR] Vault retrieval failed for %s: %s", field, exc)
|
||||
return ""
|
||||
|
||||
|
||||
class TickTickService:
|
||||
"""
|
||||
Service for interacting with the TickTick Open API.
|
||||
|
||||
Handles project and task CRUD operations with automatic OAuth token
|
||||
refresh when the access token expires. Credentials are retrieved from
|
||||
the SOPS vault.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_base_url: str = TICKTICK_API_BASE_URL,
|
||||
token_file: Path = TICKTICK_TOKEN_FILE,
|
||||
timeout: float = TICKTICK_TIMEOUT_SECONDS,
|
||||
connect_timeout: float = TICKTICK_CONNECT_TIMEOUT_SECONDS,
|
||||
):
|
||||
self.api_base_url = api_base_url.rstrip("/")
|
||||
self.token_file = token_file
|
||||
self.timeout = httpx.Timeout(timeout, connect=connect_timeout)
|
||||
self._access_token: Optional[str] = None
|
||||
self._refresh_token: Optional[str] = None
|
||||
self._client_id: Optional[str] = None
|
||||
self._client_secret: Optional[str] = None
|
||||
self._load_tokens()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Token management
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _load_tokens(self) -> None:
|
||||
"""Load access and refresh tokens from the local token file."""
|
||||
if not self.token_file.exists():
|
||||
logger.warning(
|
||||
"[WARNING] TickTick token file not found at %s", self.token_file
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
data = json.loads(self.token_file.read_text(encoding="utf-8"))
|
||||
self._access_token = data.get("access_token")
|
||||
self._refresh_token = data.get("refresh_token")
|
||||
logger.info("[OK] TickTick tokens loaded from %s", self.token_file)
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
logger.error(
|
||||
"[ERROR] Failed to read TickTick token file: %s", exc
|
||||
)
|
||||
|
||||
def _save_tokens(self) -> None:
|
||||
"""Persist current tokens back to the token file."""
|
||||
try:
|
||||
existing: dict = {}
|
||||
if self.token_file.exists():
|
||||
try:
|
||||
existing = json.loads(
|
||||
self.token_file.read_text(encoding="utf-8")
|
||||
)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
existing = {}
|
||||
|
||||
existing["access_token"] = self._access_token
|
||||
existing["refresh_token"] = self._refresh_token
|
||||
|
||||
self.token_file.write_text(
|
||||
json.dumps(existing, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
logger.info("[OK] TickTick tokens saved to %s", self.token_file)
|
||||
except OSError as exc:
|
||||
logger.error(
|
||||
"[ERROR] Failed to write TickTick token file: %s", exc
|
||||
)
|
||||
|
||||
async def _refresh_access_token(self) -> bool:
|
||||
"""
|
||||
Refresh the OAuth access token using the stored refresh token.
|
||||
|
||||
Returns:
|
||||
True if the token was refreshed successfully, False otherwise.
|
||||
"""
|
||||
if not self._refresh_token:
|
||||
logger.error("[ERROR] No refresh token available for TickTick")
|
||||
return False
|
||||
|
||||
# Lazy-load vault credentials for refresh
|
||||
if not self._client_id:
|
||||
self._client_id = _vault_get_field("credentials.client_id")
|
||||
if not self._client_secret:
|
||||
self._client_secret = _vault_get_field("credentials.client_secret")
|
||||
|
||||
if not self._client_id or not self._client_secret:
|
||||
logger.error(
|
||||
"[ERROR] Could not retrieve TickTick client credentials from SOPS vault"
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info("[INFO] Refreshing TickTick access token")
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
TICKTICK_TOKEN_URL,
|
||||
headers={
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": self._refresh_token,
|
||||
"client_id": self._client_id,
|
||||
"client_secret": self._client_secret,
|
||||
},
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(
|
||||
"[ERROR] TickTick token refresh failed with status %d: %s",
|
||||
response.status_code,
|
||||
response.text,
|
||||
)
|
||||
return False
|
||||
|
||||
token_data = response.json()
|
||||
self._access_token = token_data.get("access_token")
|
||||
if "refresh_token" in token_data:
|
||||
self._refresh_token = token_data["refresh_token"]
|
||||
|
||||
self._save_tokens()
|
||||
logger.info("[OK] TickTick access token refreshed successfully")
|
||||
return True
|
||||
|
||||
except httpx.HTTPError as exc:
|
||||
logger.error(
|
||||
"[ERROR] TickTick token refresh request failed: %s", exc
|
||||
)
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# HTTP helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _get_client(self) -> httpx.AsyncClient:
|
||||
"""
|
||||
Create an async HTTP client with configured settings.
|
||||
|
||||
Returns:
|
||||
Configured httpx.AsyncClient for TickTick API calls.
|
||||
"""
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if self._access_token:
|
||||
headers["Authorization"] = f"Bearer {self._access_token}"
|
||||
|
||||
return httpx.AsyncClient(timeout=self.timeout, headers=headers)
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
json_body: Optional[dict] = None,
|
||||
retry_on_401: bool = True,
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Execute an API request with automatic 401 retry after token refresh.
|
||||
|
||||
Args:
|
||||
method: HTTP method (GET, POST, PUT, DELETE).
|
||||
endpoint: API path relative to the base URL (e.g., '/project').
|
||||
json_body: Optional JSON payload for POST/PUT requests.
|
||||
retry_on_401: Whether to attempt a token refresh on 401.
|
||||
|
||||
Returns:
|
||||
TickTickResult with success status and response data or error.
|
||||
"""
|
||||
url = f"{self.api_base_url}{endpoint}"
|
||||
|
||||
try:
|
||||
async with self._get_client() as client:
|
||||
response = await client.request(
|
||||
method, url, json=json_body
|
||||
)
|
||||
|
||||
if response.status_code == 401 and retry_on_401:
|
||||
logger.info(
|
||||
"[INFO] TickTick API returned 401, attempting token refresh"
|
||||
)
|
||||
refreshed = await self._refresh_access_token()
|
||||
if refreshed:
|
||||
return await self._request(
|
||||
method, endpoint, json_body, retry_on_401=False
|
||||
)
|
||||
return TickTickResult(
|
||||
success=False,
|
||||
error="Authentication failed and token refresh was unsuccessful",
|
||||
)
|
||||
|
||||
if response.status_code == 204:
|
||||
return TickTickResult(success=True, data={})
|
||||
|
||||
if response.status_code >= 400:
|
||||
error_text = response.text
|
||||
logger.error(
|
||||
"[ERROR] TickTick API %s %s returned %d: %s",
|
||||
method,
|
||||
endpoint,
|
||||
response.status_code,
|
||||
error_text,
|
||||
)
|
||||
return TickTickResult(
|
||||
success=False,
|
||||
error=f"API returned {response.status_code}: {error_text}",
|
||||
)
|
||||
|
||||
# Some responses may have empty bodies (e.g., 200 with no content)
|
||||
if not response.text.strip():
|
||||
return TickTickResult(success=True, data={})
|
||||
|
||||
return TickTickResult(success=True, data=response.json())
|
||||
|
||||
except httpx.HTTPError as exc:
|
||||
logger.error(
|
||||
"[ERROR] TickTick API request failed (%s %s): %s",
|
||||
method,
|
||||
endpoint,
|
||||
exc,
|
||||
)
|
||||
return TickTickResult(
|
||||
success=False, error=f"Request failed: {exc}"
|
||||
)
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.error(
|
||||
"[ERROR] TickTick API returned invalid JSON (%s %s): %s",
|
||||
method,
|
||||
endpoint,
|
||||
exc,
|
||||
)
|
||||
return TickTickResult(
|
||||
success=False, error=f"Invalid JSON in response: {exc}"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Project operations
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_projects(self) -> TickTickResult:
|
||||
"""
|
||||
List all projects (lists) in the TickTick account.
|
||||
|
||||
Returns:
|
||||
TickTickResult with data containing a list of project dicts.
|
||||
"""
|
||||
logger.info("[INFO] Fetching TickTick project list")
|
||||
result = await self._request("GET", "/project")
|
||||
if result.success and isinstance(result.data, list):
|
||||
result.data = {"projects": result.data}
|
||||
return result
|
||||
|
||||
async def get_project(self, project_id: str) -> TickTickResult:
|
||||
"""
|
||||
Get a single project with its task data.
|
||||
|
||||
Args:
|
||||
project_id: The TickTick project ID.
|
||||
|
||||
Returns:
|
||||
TickTickResult with the project data including tasks.
|
||||
"""
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
logger.info("[INFO] Fetching TickTick project %s", project_id)
|
||||
result = await self._request("GET", f"/project/{project_id}/data")
|
||||
return result
|
||||
|
||||
async def create_project(
|
||||
self,
|
||||
name: str,
|
||||
color: Optional[str] = None,
|
||||
view_mode: Optional[str] = None,
|
||||
kind: Optional[str] = None,
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Create a new project (list) in TickTick.
|
||||
|
||||
Args:
|
||||
name: Project name.
|
||||
color: Optional hex color string (e.g., '#FF6347').
|
||||
view_mode: Optional view mode ('list', 'kanban', 'timeline').
|
||||
kind: Optional project kind ('TASK' or 'NOTE').
|
||||
|
||||
Returns:
|
||||
TickTickResult with the created project data.
|
||||
"""
|
||||
if not name:
|
||||
return TickTickResult(success=False, error="name is required")
|
||||
|
||||
body: dict = {"name": name}
|
||||
if color is not None:
|
||||
body["color"] = color
|
||||
if view_mode is not None:
|
||||
body["viewMode"] = view_mode
|
||||
if kind is not None:
|
||||
body["kind"] = kind
|
||||
|
||||
logger.info("[INFO] Creating TickTick project: %s", name)
|
||||
return await self._request("POST", "/project", json_body=body)
|
||||
|
||||
async def update_project(
|
||||
self,
|
||||
project_id: str,
|
||||
name: Optional[str] = None,
|
||||
color: Optional[str] = None,
|
||||
view_mode: Optional[str] = None,
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Update an existing project in TickTick.
|
||||
|
||||
Args:
|
||||
project_id: The TickTick project ID to update.
|
||||
name: Optional new project name.
|
||||
color: Optional new hex color string.
|
||||
view_mode: Optional new view mode.
|
||||
|
||||
Returns:
|
||||
TickTickResult with the updated project data.
|
||||
"""
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
body: dict = {}
|
||||
if name is not None:
|
||||
body["name"] = name
|
||||
if color is not None:
|
||||
body["color"] = color
|
||||
if view_mode is not None:
|
||||
body["viewMode"] = view_mode
|
||||
|
||||
if not body:
|
||||
return TickTickResult(
|
||||
success=False, error="At least one field to update is required"
|
||||
)
|
||||
|
||||
logger.info("[INFO] Updating TickTick project %s", project_id)
|
||||
return await self._request(
|
||||
"POST", f"/project/{project_id}", json_body=body
|
||||
)
|
||||
|
||||
async def delete_project(self, project_id: str) -> TickTickResult:
|
||||
"""
|
||||
Delete a project from TickTick.
|
||||
|
||||
Args:
|
||||
project_id: The TickTick project ID to delete.
|
||||
|
||||
Returns:
|
||||
TickTickResult with success status.
|
||||
"""
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
logger.info("[INFO] Deleting TickTick project %s", project_id)
|
||||
return await self._request("DELETE", f"/project/{project_id}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Task operations
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def create_task(
|
||||
self,
|
||||
title: str,
|
||||
project_id: str,
|
||||
content: Optional[str] = None,
|
||||
priority: Optional[int] = None,
|
||||
due_date: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Create a new task in a TickTick project.
|
||||
|
||||
Args:
|
||||
title: Task title.
|
||||
project_id: ID of the project to create the task in.
|
||||
content: Optional task description/content.
|
||||
priority: Optional priority (0=none, 1=low, 3=medium, 5=high).
|
||||
due_date: Optional due date in ISO 8601 format.
|
||||
tags: Optional list of tag strings.
|
||||
|
||||
Returns:
|
||||
TickTickResult with the created task data.
|
||||
"""
|
||||
if not title:
|
||||
return TickTickResult(success=False, error="title is required")
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
body: dict = {"title": title, "projectId": project_id}
|
||||
if content is not None:
|
||||
body["content"] = content
|
||||
if priority is not None:
|
||||
body["priority"] = priority
|
||||
if due_date is not None:
|
||||
body["dueDate"] = due_date
|
||||
if tags is not None:
|
||||
body["tags"] = tags
|
||||
|
||||
logger.info(
|
||||
"[INFO] Creating TickTick task '%s' in project %s",
|
||||
title,
|
||||
project_id,
|
||||
)
|
||||
return await self._request("POST", "/task", json_body=body)
|
||||
|
||||
async def update_task(
|
||||
self,
|
||||
task_id: str,
|
||||
project_id: str,
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
priority: Optional[int] = None,
|
||||
due_date: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Update an existing task in TickTick.
|
||||
|
||||
Args:
|
||||
task_id: The task ID to update.
|
||||
project_id: The project ID containing the task.
|
||||
title: Optional new task title.
|
||||
content: Optional new task content.
|
||||
priority: Optional new priority level.
|
||||
due_date: Optional new due date in ISO 8601 format.
|
||||
tags: Optional new list of tags.
|
||||
|
||||
Returns:
|
||||
TickTickResult with the updated task data.
|
||||
"""
|
||||
if not task_id:
|
||||
return TickTickResult(success=False, error="task_id is required")
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
body: dict = {"id": task_id, "projectId": project_id}
|
||||
if title is not None:
|
||||
body["title"] = title
|
||||
if content is not None:
|
||||
body["content"] = content
|
||||
if priority is not None:
|
||||
body["priority"] = priority
|
||||
if due_date is not None:
|
||||
body["dueDate"] = due_date
|
||||
if tags is not None:
|
||||
body["tags"] = tags
|
||||
|
||||
logger.info(
|
||||
"[INFO] Updating TickTick task %s in project %s",
|
||||
task_id,
|
||||
project_id,
|
||||
)
|
||||
return await self._request(
|
||||
"POST", f"/task/{task_id}", json_body=body
|
||||
)
|
||||
|
||||
async def complete_task(
|
||||
self, task_id: str, project_id: str
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Mark a task as complete in TickTick.
|
||||
|
||||
Args:
|
||||
task_id: The task ID to complete.
|
||||
project_id: The project ID containing the task.
|
||||
|
||||
Returns:
|
||||
TickTickResult with success status.
|
||||
"""
|
||||
if not task_id:
|
||||
return TickTickResult(success=False, error="task_id is required")
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
logger.info(
|
||||
"[INFO] Completing TickTick task %s in project %s",
|
||||
task_id,
|
||||
project_id,
|
||||
)
|
||||
return await self._request(
|
||||
"POST", f"/project/{project_id}/task/{task_id}/complete"
|
||||
)
|
||||
|
||||
async def delete_task(
|
||||
self, task_id: str, project_id: str
|
||||
) -> TickTickResult:
|
||||
"""
|
||||
Delete a task from TickTick.
|
||||
|
||||
Args:
|
||||
task_id: The task ID to delete.
|
||||
project_id: The project ID containing the task.
|
||||
|
||||
Returns:
|
||||
TickTickResult with success status.
|
||||
"""
|
||||
if not task_id:
|
||||
return TickTickResult(success=False, error="task_id is required")
|
||||
if not project_id:
|
||||
return TickTickResult(success=False, error="project_id is required")
|
||||
|
||||
logger.info(
|
||||
"[INFO] Deleting TickTick task %s from project %s",
|
||||
task_id,
|
||||
project_id,
|
||||
)
|
||||
return await self._request(
|
||||
"DELETE", f"/project/{project_id}/task/{task_id}"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Singleton accessor
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
_ticktick_service: Optional[TickTickService] = None
|
||||
|
||||
|
||||
def get_ticktick_service() -> TickTickService:
|
||||
"""
|
||||
Return a singleton TickTickService instance.
|
||||
|
||||
Creates the service on first call, reuses it thereafter.
|
||||
|
||||
Returns:
|
||||
The shared TickTickService instance.
|
||||
"""
|
||||
global _ticktick_service
|
||||
if _ticktick_service is None:
|
||||
_ticktick_service = TickTickService()
|
||||
return _ticktick_service
|
||||
28
clients/_client_template/cloud/azure.md
Normal file
28
clients/_client_template/cloud/azure.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Azure / Cloud Services
|
||||
|
||||
## Azure Subscription
|
||||
- Subscription Name:
|
||||
- Subscription ID:
|
||||
- Resource Group(s):
|
||||
- Region:
|
||||
- Monthly Spend (approx):
|
||||
|
||||
## Virtual Machines
|
||||
| VM Name | Size | OS | IP | Purpose |
|
||||
|---------------|------------|------------|------------|-----------------|
|
||||
| | | | | |
|
||||
|
||||
## Networking
|
||||
- Virtual Network:
|
||||
- Address Space:
|
||||
- Subnets:
|
||||
- VPN Gateway to On-Prem: Yes/No
|
||||
- ExpressRoute: Yes/No
|
||||
|
||||
## Other Cloud Services
|
||||
<!-- AWS, Google Workspace, third-party SaaS -->
|
||||
| Service | Purpose | Admin URL | Notes |
|
||||
|-----------------|------------------|------------------|-----------------|
|
||||
| | | | |
|
||||
|
||||
## Notes
|
||||
52
clients/_client_template/cloud/m365.md
Normal file
52
clients/_client_template/cloud/m365.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Microsoft 365
|
||||
|
||||
## Tenant Info
|
||||
- Tenant Name:
|
||||
- Tenant ID:
|
||||
- Primary Domain:
|
||||
- Admin Portal URL: https://admin.microsoft.com
|
||||
|
||||
## Licensing
|
||||
| License Type | Quantity | Assigned | Available |
|
||||
|--------------------------|----------|----------|-----------|
|
||||
| Microsoft 365 Business Basic | | | |
|
||||
| Microsoft 365 Business Standard | | | |
|
||||
| Microsoft 365 Business Premium | | | |
|
||||
| Exchange Online Plan 1/2 | | | |
|
||||
| Other | | | |
|
||||
|
||||
## Exchange Online
|
||||
- Mail Domain(s):
|
||||
- MX Record Points To:
|
||||
- SPF Record:
|
||||
- DKIM Enabled: Yes/No
|
||||
- DMARC Policy:
|
||||
- Shared Mailboxes:
|
||||
- Distribution Groups:
|
||||
- Mail Flow Rules: Yes/No (describe below)
|
||||
|
||||
## SharePoint / OneDrive
|
||||
- SharePoint Sites:
|
||||
- External Sharing: Enabled/Disabled
|
||||
- OneDrive Storage Limit:
|
||||
|
||||
## Teams
|
||||
- Teams Phone System: Yes/No
|
||||
- Calling Plan / Direct Routing:
|
||||
- Auto Attendant:
|
||||
|
||||
## Entra ID (Azure AD)
|
||||
- Hybrid Joined: Yes/No
|
||||
- Azure AD Connect Server:
|
||||
- Sync Schedule:
|
||||
- Password Hash Sync: Yes/No
|
||||
- MFA Enforced: Yes/No
|
||||
- Conditional Access Policies:
|
||||
|
||||
## Security
|
||||
- Defender for Office 365: Yes/No
|
||||
- Safe Links: Yes/No
|
||||
- Safe Attachments: Yes/No
|
||||
- Audit Log Retention:
|
||||
|
||||
## Notes
|
||||
19
clients/_client_template/issues/log.md
Normal file
19
clients/_client_template/issues/log.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Issue Log
|
||||
|
||||
Record past issues and their resolutions here. This helps the AI learn from historical
|
||||
troubleshooting and avoid repeating failed approaches.
|
||||
|
||||
## Template
|
||||
|
||||
### [DATE] - [Brief Description]
|
||||
- **Reported By:**
|
||||
- **Severity:** Low / Medium / High / Critical
|
||||
- **Symptoms:**
|
||||
- **Root Cause:**
|
||||
- **Resolution:**
|
||||
- **Time to Resolve:**
|
||||
- **Lessons Learned:**
|
||||
|
||||
---
|
||||
|
||||
<!-- Add new issues above this line, newest first -->
|
||||
31
clients/_client_template/network/dhcp.md
Normal file
31
clients/_client_template/network/dhcp.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# DHCP Configuration
|
||||
|
||||
## DHCP Server
|
||||
- Server Name:
|
||||
- Server IP:
|
||||
- Failover Partner:
|
||||
|
||||
## Scopes
|
||||
|
||||
### Scope - [VLAN Name]
|
||||
- Subnet:
|
||||
- Range Start:
|
||||
- Range End:
|
||||
- Subnet Mask:
|
||||
- Default Gateway:
|
||||
- DNS Servers:
|
||||
- Lease Duration:
|
||||
- Exclusions:
|
||||
|
||||
<!-- Copy the block above for each DHCP scope -->
|
||||
|
||||
## Reservations
|
||||
| Device Name | MAC Address | IP Address | Scope | Notes |
|
||||
|-----------------|-------------------|-----------------|---------------|---------------|
|
||||
| | | | | |
|
||||
|
||||
## DHCP Relay
|
||||
- Relay agents configured on:
|
||||
- Helper address:
|
||||
|
||||
## Notes
|
||||
33
clients/_client_template/network/dns.md
Normal file
33
clients/_client_template/network/dns.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# DNS Configuration
|
||||
|
||||
## Internal DNS Servers
|
||||
| Server Name | IP Address | Role |
|
||||
|-------------|-----------|-------------------|
|
||||
| | | Primary |
|
||||
| | | Secondary |
|
||||
|
||||
## DNS Forwarders
|
||||
- Forwarder 1:
|
||||
- Forwarder 2:
|
||||
|
||||
## Conditional Forwarders
|
||||
| Domain | Forward To | Purpose |
|
||||
|----------------------|-----------------|-------------------|
|
||||
| | | |
|
||||
|
||||
## Key DNS Records
|
||||
| Record Type | Name | Value | Notes |
|
||||
|-------------|------------------|------------------|------------------|
|
||||
| A | | | |
|
||||
| CNAME | | | |
|
||||
| MX | | | |
|
||||
| TXT | | | |
|
||||
|
||||
## External DNS
|
||||
- Registrar:
|
||||
- Hosted At:
|
||||
- Primary Domain:
|
||||
- Management URL:
|
||||
|
||||
## Notes
|
||||
<!-- Split-brain DNS, special zones, etc. -->
|
||||
47
clients/_client_template/network/firewall.md
Normal file
47
clients/_client_template/network/firewall.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Firewall Configuration
|
||||
|
||||
## Device Info
|
||||
- Vendor/Model:
|
||||
- Firmware Version:
|
||||
- Management IP:
|
||||
- Management URL:
|
||||
- HA Pair: Yes/No
|
||||
- License Expiry:
|
||||
|
||||
## Interfaces
|
||||
| Interface | Zone | IP Address | VLAN | Description |
|
||||
|-----------|-----------|-----------------|------|-------------------|
|
||||
| WAN1 | WAN | | | Primary Internet |
|
||||
| WAN2 | WAN | | | Backup Internet |
|
||||
| LAN | LAN | | | |
|
||||
| DMZ | DMZ | | | |
|
||||
|
||||
## NAT Rules
|
||||
| Name | Source | Destination | Port(s) | NAT To |
|
||||
|-------------------|---------------|----------------|-------------|-----------------|
|
||||
| | | | | |
|
||||
|
||||
## Key Firewall Policies
|
||||
| Name | Source Zone | Dest Zone | Service | Action | Notes |
|
||||
|-------------------|--------------|---------------|-------------|--------|--------|
|
||||
| | | | | | |
|
||||
|
||||
## VPN
|
||||
### Site-to-Site VPNs
|
||||
| Peer Name | Peer IP | Local Subnet | Remote Subnet | Status |
|
||||
|-------------------|--------------|----------------|---------------|--------|
|
||||
| | | | | |
|
||||
|
||||
### SSL/Client VPN
|
||||
- Enabled: Yes/No
|
||||
- Portal URL:
|
||||
- Auth Method:
|
||||
- IP Pool:
|
||||
- Split Tunnel: Yes/No
|
||||
|
||||
## Content Filtering
|
||||
- Web Filter Profile:
|
||||
- App Control Profile:
|
||||
- DNS Filter:
|
||||
|
||||
## Notes
|
||||
43
clients/_client_template/network/topology.md
Normal file
43
clients/_client_template/network/topology.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Network Topology
|
||||
|
||||
## Internet Connection
|
||||
- ISP:
|
||||
- Circuit Type:
|
||||
- Speed (Down/Up):
|
||||
- Public IP:
|
||||
- Gateway:
|
||||
- Modem Model:
|
||||
|
||||
## Core Switch
|
||||
- Model:
|
||||
- IP Address:
|
||||
- Management URL:
|
||||
- Firmware Version:
|
||||
- Location:
|
||||
|
||||
## Additional Switches
|
||||
<!-- Copy this block for each switch -->
|
||||
### Switch - [Name/Location]
|
||||
- Model:
|
||||
- IP Address:
|
||||
- Port Count:
|
||||
- PoE: Yes/No
|
||||
- Uplink To:
|
||||
|
||||
## Wireless
|
||||
- Controller Model:
|
||||
- Controller IP:
|
||||
- Number of APs:
|
||||
- AP Model(s):
|
||||
|
||||
### Access Points
|
||||
<!-- Copy for each AP -->
|
||||
- AP Name:
|
||||
- Location:
|
||||
- IP Address:
|
||||
- Connected Switch/Port:
|
||||
|
||||
## WAN / SD-WAN
|
||||
- SD-WAN Vendor:
|
||||
- Number of Sites:
|
||||
- Hub Site:
|
||||
21
clients/_client_template/network/vlans.md
Normal file
21
clients/_client_template/network/vlans.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# VLANs
|
||||
|
||||
## VLAN Table
|
||||
|
||||
| VLAN ID | Name | Subnet | Gateway | DHCP Scope | Purpose |
|
||||
|---------|---------------|-----------------|-----------------|------------------|------------------------|
|
||||
| 1 | Default | | | | |
|
||||
| 10 | Management | | | | Network devices |
|
||||
| 20 | Servers | | | | Server infrastructure |
|
||||
| 30 | Workstations | | | | End user devices |
|
||||
| 40 | VoIP | | | | Phone system |
|
||||
| 50 | WiFi-Corp | | | | Corporate wireless |
|
||||
| 60 | WiFi-Guest | | | | Guest wireless |
|
||||
| 100 | Security | | | | Cameras / access ctrl |
|
||||
|
||||
## Inter-VLAN Routing
|
||||
- Performed by:
|
||||
- Routing device IP:
|
||||
|
||||
## VLAN Notes
|
||||
<!-- Any special considerations, trunk ports, tagged/untagged config -->
|
||||
31
clients/_client_template/overview.md
Normal file
31
clients/_client_template/overview.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Client Overview
|
||||
|
||||
## Company Name
|
||||
<!-- Replace with company name -->
|
||||
|
||||
## Primary Contact
|
||||
- Name:
|
||||
- Phone:
|
||||
- Email:
|
||||
|
||||
## IT Contact
|
||||
- Name:
|
||||
- Phone:
|
||||
- Email:
|
||||
|
||||
## Contract Details
|
||||
- Service Level:
|
||||
- Hours Covered:
|
||||
- Contract Renewal Date:
|
||||
|
||||
## Environment Summary
|
||||
- Total Users:
|
||||
- Total Locations:
|
||||
- Domain Name:
|
||||
- Primary Site Address:
|
||||
- RMM Agent Count:
|
||||
- Workstation Count:
|
||||
- Server Count:
|
||||
|
||||
## Notes
|
||||
<!-- General notes about this client -->
|
||||
34
clients/_client_template/rmm/rmm.md
Normal file
34
clients/_client_template/rmm/rmm.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# RMM / Monitoring
|
||||
|
||||
## RMM Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- Agent Version:
|
||||
|
||||
## Agent Deployment
|
||||
- Total Devices:
|
||||
- Servers Monitored:
|
||||
- Workstations Monitored:
|
||||
- Network Devices Monitored:
|
||||
|
||||
## Monitoring Policies
|
||||
| Policy Name | Applies To | Alert Condition | Action |
|
||||
|-------------------|----------------|-------------------------|---------------|
|
||||
| Disk Space | All Servers | < 10% free | Alert + Ticket|
|
||||
| CPU | All Servers | > 90% for 15 min | Alert |
|
||||
| Service Monitor | All Servers | | |
|
||||
| Backup Monitor | | | |
|
||||
| Offline Alert | All Agents | Offline > 30 min | Alert |
|
||||
|
||||
## Patch Management
|
||||
- Patch Policy:
|
||||
- Patch Window:
|
||||
- Auto-approve: Yes/No
|
||||
- Exclusions:
|
||||
|
||||
## Scripting / Automation
|
||||
| Script Name | Schedule | Purpose |
|
||||
|---------------------|-------------|--------------------------|
|
||||
| | | |
|
||||
|
||||
## Notes
|
||||
26
clients/_client_template/security/antivirus.md
Normal file
26
clients/_client_template/security/antivirus.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Endpoint Security / Antivirus
|
||||
|
||||
## Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- License Count:
|
||||
- License Expiry:
|
||||
- Managed By:
|
||||
|
||||
## Policy
|
||||
- Real-time Protection: Yes/No
|
||||
- Scheduled Scans: (frequency)
|
||||
- Exclusions:
|
||||
|
||||
## Deployment Status
|
||||
- Total Endpoints:
|
||||
- Protected:
|
||||
- Missing Agent:
|
||||
- Out of Date:
|
||||
|
||||
## EDR / XDR
|
||||
- EDR Enabled: Yes/No
|
||||
- Product:
|
||||
- Console URL:
|
||||
|
||||
## Notes
|
||||
34
clients/_client_template/security/backup.md
Normal file
34
clients/_client_template/security/backup.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Backup and Disaster Recovery
|
||||
|
||||
## Backup Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- License/Subscription:
|
||||
|
||||
## Backup Targets
|
||||
| Target Name | Type | Location | Capacity | Encrypted |
|
||||
|----------------|----------------|-----------------|--------------|-----------|
|
||||
| | Local NAS | | | Yes/No |
|
||||
| | Cloud | | | Yes/No |
|
||||
| | Offsite | | | Yes/No |
|
||||
|
||||
## Backup Jobs
|
||||
| Job Name | Source | Target | Schedule | Retention | Status |
|
||||
|-----------------|-------------------|------------|---------------|-------------|--------|
|
||||
| | | | | | |
|
||||
|
||||
## M365 Backup
|
||||
- M365 Backup Product:
|
||||
- Exchange Backed Up: Yes/No
|
||||
- SharePoint Backed Up: Yes/No
|
||||
- OneDrive Backed Up: Yes/No
|
||||
- Teams Backed Up: Yes/No
|
||||
|
||||
## Disaster Recovery Plan
|
||||
- RTO Target:
|
||||
- RPO Target:
|
||||
- DR Site:
|
||||
- Last DR Test Date:
|
||||
- DR Test Result:
|
||||
|
||||
## Notes
|
||||
49
clients/_client_template/servers/server_template.md
Normal file
49
clients/_client_template/servers/server_template.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Server: [SERVER NAME]
|
||||
|
||||
## General Info
|
||||
- Hostname:
|
||||
- IP Address:
|
||||
- OS:
|
||||
- OS Version:
|
||||
- Physical / Virtual:
|
||||
- Host (if virtual):
|
||||
- Location:
|
||||
- Last Patched:
|
||||
|
||||
## Hardware (if physical)
|
||||
- Make/Model:
|
||||
- CPU:
|
||||
- RAM:
|
||||
- Storage:
|
||||
- Warranty Expiry:
|
||||
|
||||
## Roles and Services
|
||||
<!-- List all roles this server performs -->
|
||||
- [ ] Domain Controller
|
||||
- [ ] DNS Server
|
||||
- [ ] DHCP Server
|
||||
- [ ] File Server
|
||||
- [ ] Print Server
|
||||
- [ ] Application Server
|
||||
- [ ] Database Server
|
||||
- [ ] Backup Target
|
||||
- [ ] RDS / Terminal Server
|
||||
- [ ] Hyper-V Host
|
||||
|
||||
## Shares (if file server)
|
||||
| Share Name | Path | Permissions Group | Notes |
|
||||
|---------------|-------------------|---------------------|----------------|
|
||||
| | | | |
|
||||
|
||||
## Applications Installed
|
||||
| Application | Version | Purpose | License |
|
||||
|-------------------|------------|----------------------|---------------|
|
||||
| | | | |
|
||||
|
||||
## Backup
|
||||
- Backup Method:
|
||||
- Backup Schedule:
|
||||
- Backup Target:
|
||||
- Last Verified Restore:
|
||||
|
||||
## Notes
|
||||
17
clients/ace-portables/PROJECT_STATE.md
Normal file
17
clients/ace-portables/PROJECT_STATE.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Ace Portables — Project State
|
||||
|
||||
> Last updated: 2026-04-20
|
||||
|
||||
**Status:** MAINTENANCE
|
||||
**Last Activity:** Unknown (reports directory only)
|
||||
|
||||
Minimal client presence — directory contains only a `reports/` subdirectory. No session logs, no infrastructure details, no active projects.
|
||||
|
||||
## What Was Done
|
||||
|
||||
- Reports directory created
|
||||
|
||||
## If Resuming
|
||||
|
||||
- Check `clients/ace-portables/reports/` for any generated reports
|
||||
- No infrastructure or contact details recorded — gather before starting any work
|
||||
@@ -0,0 +1,559 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Security Incident Report - Ace Portables - 31 March 2026</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--primary: #1a1a2e;
|
||||
--accent: #e87a1e;
|
||||
--accent-light: #f5a623;
|
||||
--text: #2c2c2c;
|
||||
--text-light: #666;
|
||||
--border: #e0e0e0;
|
||||
--bg-light: #f8f9fa;
|
||||
--bg-green: #e8f5e9;
|
||||
--green: #2e7d32;
|
||||
--red: #c62828;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
padding: 35px 50px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.header-left img {
|
||||
height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.header-left .report-type {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2.5px;
|
||||
color: var(--accent);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.header-right strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Status Banner */
|
||||
.status-banner {
|
||||
background: var(--bg-green);
|
||||
border-left: 5px solid var(--green);
|
||||
padding: 20px 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--green);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-icon svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.status-text h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--green);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.status-text p {
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
padding: 40px 50px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: var(--accent);
|
||||
border-bottom: 2px solid var(--accent);
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* Info Grid */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.info-item:nth-child(even) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.info-item:nth-last-child(-n+2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-value.mono {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
grid-column: 1 / -1;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* Machine Status Table */
|
||||
.machine-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.machine-table thead {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.machine-table th {
|
||||
padding: 12px 18px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.machine-table td {
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.machine-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.machine-table tr:nth-child(even) {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-clean {
|
||||
background: var(--bg-green);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.badge-managed {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.badge-deleted {
|
||||
background: #fce4ec;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
width: 2px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.timeline-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -26px;
|
||||
top: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 0 0 2px var(--accent);
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.timeline-text {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--primary);
|
||||
color: #ccc;
|
||||
padding: 30px 50px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.footer-left h4 {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.footer-left h4 span {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.footer-left p {
|
||||
font-size: 13px;
|
||||
color: #aaa;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.footer-right p {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 25px 0;
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-before: always;
|
||||
break-before: page;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body { background: #fff; }
|
||||
.page { max-width: 100%; }
|
||||
.header, .footer { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.section { break-inside: avoid; }
|
||||
.timeline { break-inside: avoid; }
|
||||
.info-grid { break-inside: avoid; }
|
||||
.machine-table { break-inside: avoid; }
|
||||
.page-break { page-break-before: always; break-before: page; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<img src="logo-light.png" alt="Arizona ComputerGuru">
|
||||
<div class="report-type">Security Incident Report</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<strong>Report Reference:</strong> ACE-SEC-2026-0331<br>
|
||||
<strong>Date:</strong> 31 March 2026<br>
|
||||
<strong>Prepared for:</strong> Ace Portables
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Banner -->
|
||||
<div class="status-banner">
|
||||
<div class="status-icon">
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</div>
|
||||
<div class="status-text">
|
||||
<h3>ALL SYSTEMS VERIFIED CLEAN</h3>
|
||||
<p>Both workstations have been scanned, verified, and are actively protected by enterprise-grade endpoint security. No active threats detected.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="content">
|
||||
|
||||
<!-- Executive Summary -->
|
||||
<div class="section">
|
||||
<div class="section-title">Executive Summary</div>
|
||||
<p>
|
||||
Ace Portables contacted AZ Computer Guru LLC after their financial institution requested verification that company workstations were free of malware. Upon investigation, we determined that the previously installed antivirus software (McAfee) had silently expired, leaving the machines unprotected.
|
||||
</p>
|
||||
<p>
|
||||
We removed the expired McAfee installation and deployed <strong>Bitdefender GravityZone</strong>, an enterprise-grade Endpoint Detection and Response (EDR) platform, across both company workstations. During the initial security scan, Bitdefender detected and automatically deleted a malicious browser extension containing a Trojan on one machine. Both machines have been fully scanned and are confirmed clean with no active threats.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Incident Timeline -->
|
||||
<div class="section">
|
||||
<div class="section-title">Incident Timeline</div>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">Prior to Engagement</div>
|
||||
<div class="timeline-text">McAfee antivirus subscription silently expired, leaving workstations without active endpoint protection.</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">Engagement Initiated</div>
|
||||
<div class="timeline-text">Ace Portables contacted AZ Computer Guru LLC at the request of their bank to verify workstation security.</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">Remediation</div>
|
||||
<div class="timeline-text">Expired McAfee software removed. Bitdefender GravityZone EDR deployed on both workstations (DESKTOP-DV7I10S, DESKTOP-U317856).</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">25 March 2026, 11:15</div>
|
||||
<div class="timeline-text">Bitdefender detected and automatically deleted a Trojan (Trojan.GenericKD.77292516) within a malicious Microsoft Edge browser extension on one workstation.</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">31 March 2026</div>
|
||||
<div class="timeline-text">Full scans completed on both machines. Both verified clean. This report issued.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Threat Details -->
|
||||
<div class="section page-break">
|
||||
<div class="section-title">Threat Details</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Threat Classification</div>
|
||||
<div class="info-value">Trojan.GenericKD.77292516</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Threat Type</div>
|
||||
<div class="info-value">Malware (Trojan)</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Detection Date</div>
|
||||
<div class="info-value">25 March 2026, 11:15</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Action Taken</div>
|
||||
<div class="info-value"><span class="badge badge-deleted">Automatically Deleted</span></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Affected Component</div>
|
||||
<div class="info-value">Microsoft Edge Browser Extension (background.js)</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Extension ID</div>
|
||||
<div class="info-value mono">cfacibcmkcdppnkgennk...blmp</div>
|
||||
</div>
|
||||
<div class="info-item full-width">
|
||||
<div class="info-label">File SHA-256 Hash</div>
|
||||
<div class="info-value mono">B3F83B5EC4CFED5D93561B86B5A124FA88D2EA35491011D32CCDA3E385C036E1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Machines Scanned -->
|
||||
<div class="section">
|
||||
<div class="section-title">Workstation Scan Results</div>
|
||||
<p>Both Ace Portables workstations were enrolled in Bitdefender GravityZone and scanned. Current status as of 31 March 2026:</p>
|
||||
<br>
|
||||
<table class="machine-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Machine Name</th>
|
||||
<th>Type</th>
|
||||
<th>Management</th>
|
||||
<th>Security Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>DESKTOP-DV7I10S</strong></td>
|
||||
<td>Physical Machine</td>
|
||||
<td><span class="badge badge-managed">Managed</span></td>
|
||||
<td><span class="badge badge-clean">No Issues</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>DESKTOP-U317856</strong></td>
|
||||
<td>Physical Machine</td>
|
||||
<td><span class="badge badge-managed">Managed</span></td>
|
||||
<td><span class="badge badge-clean">No Issues</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Remediation Steps -->
|
||||
<div class="section">
|
||||
<div class="section-title">Remediation Actions Taken</div>
|
||||
<ul>
|
||||
<li><strong>Removed expired antivirus software</strong> — McAfee, which had silently expired, was fully uninstalled from both workstations.</li>
|
||||
<li><strong>Deployed enterprise endpoint protection</strong> — Bitdefender GravityZone EDR was installed and configured on both machines, providing real-time threat monitoring, behavioral analysis, and automated response.</li>
|
||||
<li><strong>Malicious extension deleted</strong> — The Trojan-infected browser extension was automatically detected and removed by Bitdefender during the initial scan.</li>
|
||||
<li><strong>Extension blocked globally</strong> — The malicious extension has been added to our managed blocklist, preventing it from being installed on any endpoint under our management.</li>
|
||||
<li><strong>Full system scans completed</strong> — Comprehensive antimalware scans were run on both workstations. Both returned clean results with no further threats detected.</li>
|
||||
<li><strong>Password reset recommended</strong> — The affected user was advised to change passwords for all accounts accessed via the browser, prioritising financial and email accounts.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Ongoing Protection -->
|
||||
<div class="section">
|
||||
<div class="section-title">Ongoing Protection</div>
|
||||
<p>Both Ace Portables workstations are now continuously protected by Bitdefender GravityZone, which provides:</p>
|
||||
<ul>
|
||||
<li><strong>Real-time file system protection</strong> — On-access scanning of all files as they are opened, created, or modified.</li>
|
||||
<li><strong>Advanced Threat Control</strong> — Behavioral monitoring that detects suspicious process activity in real time.</li>
|
||||
<li><strong>Network Attack Defense</strong> — Protection against network-based exploits and lateral movement attempts.</li>
|
||||
<li><strong>Web Threat Protection</strong> — Blocks access to known malicious, phishing, and fraudulent websites.</li>
|
||||
<li><strong>Anti-Exploit Technology</strong> — Detects and prevents exploitation of software vulnerabilities.</li>
|
||||
<li><strong>Centralised Management</strong> — All endpoints are monitored and managed through the GravityZone console by AZ Computer Guru LLC, ensuring policies and definitions remain current.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Conclusion -->
|
||||
<div class="section">
|
||||
<p>
|
||||
Both Ace Portables workstations have been verified clean and are now actively protected by enterprise-grade endpoint security. The previously unprotected state caused by the expired McAfee subscription has been fully resolved. The detected Trojan was automatically removed before any confirmed data exfiltration occurred, and preventative measures are in place to block future threats.
|
||||
</p>
|
||||
<p>
|
||||
Should the bank require any additional information, technical logs, or further clarification, please do not hesitate to contact us using the details below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<div class="footer-left">
|
||||
<h4>Arizona <span>Computer</span>Guru LLC</h4>
|
||||
<p>7437 E. 22nd St, Tucson, AZ 85710</p>
|
||||
<p>Phone: (520) 304-8300</p>
|
||||
<p>Web: azcomputerguru.com</p>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<p>This report is confidential and intended solely for the use of Ace Portables and their financial institution.</p>
|
||||
<br>
|
||||
<p>Report Ref: ACE-SEC-2026-0331</p>
|
||||
<p>Date Issued: 31 March 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,106 @@
|
||||
# Security Incident Report - Malware Detection and Remediation
|
||||
|
||||
**Prepared by:** AZ Computer Guru LLC
|
||||
**Prepared for:** Ace Portables
|
||||
**Date:** 31 March 2026
|
||||
**Report Reference:** ACE-SEC-2026-0331
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
On 25 March 2026, our endpoint protection platform detected and automatically removed a malicious browser extension from a workstation belonging to Ace Portables. The threat was identified, quarantined, and deleted without user intervention. Additional preventative measures have been implemented across the managed environment to prevent recurrence.
|
||||
|
||||
---
|
||||
|
||||
## Incident Details
|
||||
|
||||
| Field | Detail |
|
||||
|-------|--------|
|
||||
| **Date of Detection** | 25 March 2026, 11:15 |
|
||||
| **Affected Machine User** | John |
|
||||
| **Threat Classification** | Trojan.GenericKD.77292516 |
|
||||
| **Threat Type** | Malware (Trojan) |
|
||||
| **Affected File** | `background.js` (browser extension component) |
|
||||
| **File Location** | Microsoft Edge browser extension directory |
|
||||
| **Extension ID** | cfacibcmkcdppnkgennkfaepplpkblmp |
|
||||
| **File SHA256 Hash** | B3F83B5EC4CFED5D93561B86B5A124FA88D2EA35491011D32CCDA3E385C036E1 |
|
||||
|
||||
---
|
||||
|
||||
## Detection and Response
|
||||
|
||||
### Detection
|
||||
|
||||
The threat was identified by **Bitdefender GravityZone**, our enterprise endpoint detection and response (EDR) platform, during a scheduled on-demand scan task. The malicious file was a JavaScript component (`background.js`) operating within a Microsoft Edge browser extension.
|
||||
|
||||
### Automated Response
|
||||
|
||||
Bitdefender GravityZone automatically took the following action upon detection:
|
||||
|
||||
- **Action Taken:** File deleted
|
||||
- **Detection Module:** Antimalware (On-Demand Scan)
|
||||
- **Result:** Threat successfully removed from the system
|
||||
|
||||
### Additional Remediation Steps
|
||||
|
||||
The following manual remediation steps were performed by AZ Computer Guru LLC:
|
||||
|
||||
1. **Extension removal verified** - Confirmed the malicious browser extension was fully removed from Microsoft Edge, including all associated files and registry entries.
|
||||
2. **Extension blocked at policy level** - The malicious extension (ID: `cfacibcmkcdppnkgennkfaepplpkblmp`) has been added to the GravityZone extension blocklist, preventing installation across all managed endpoints company-wide.
|
||||
3. **Full system scan completed** - A comprehensive antimalware scan was conducted on the affected workstation to confirm no additional threats or residual malicious components remain.
|
||||
4. **Browser data review** - Edge browser settings were reviewed and restored to safe defaults where necessary.
|
||||
5. **Password reset recommended** - The affected user was advised to change passwords for all accounts accessed via the browser as a precautionary measure, with priority given to financial and email accounts.
|
||||
|
||||
---
|
||||
|
||||
## Current System Status
|
||||
|
||||
**The affected workstation is confirmed CLEAN and free of malware.** Bitdefender GravityZone endpoint protection continues to actively monitor the system in real time with:
|
||||
|
||||
- Real-time file system protection (on-access scanning)
|
||||
- Network attack defense
|
||||
- Web threat protection
|
||||
- Advanced anti-exploit technology
|
||||
- Behavioral monitoring (Advanced Threat Control)
|
||||
|
||||
The GravityZone management console shows **no active threats** on the affected machine or any other Ace Portables endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Preventative Measures Implemented
|
||||
|
||||
| Measure | Scope | Status |
|
||||
|---------|-------|--------|
|
||||
| Malicious extension added to blocklist | All managed client endpoints | Complete |
|
||||
| Full system scan on affected workstation | Affected machine | Complete - Clean |
|
||||
| User advised to reset browser passwords | Affected user | Advised |
|
||||
| Ongoing real-time endpoint monitoring | All Ace Portables endpoints | Active |
|
||||
|
||||
---
|
||||
|
||||
## About Our Security Platform
|
||||
|
||||
AZ Computer Guru LLC utilises **Bitdefender GravityZone**, an enterprise-grade endpoint protection platform that provides:
|
||||
|
||||
- Multi-layered malware detection (signature, heuristic, behavioural, and machine learning)
|
||||
- Real-time threat monitoring and automated response
|
||||
- Centralised management and policy enforcement
|
||||
- Regular definition updates and cloud-based threat intelligence
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The malicious browser extension was detected promptly by our automated security systems, removed before any confirmed data exfiltration occurred, and blocked from future installation. The affected workstation has been verified clean and continues to be actively protected. No further action is required at this time.
|
||||
|
||||
Should the bank require any additional information, technical logs, or clarification, please do not hesitate to contact us.
|
||||
|
||||
---
|
||||
|
||||
**AZ Computer Guru LLC**
|
||||
Managed IT Services Provider
|
||||
|
||||
---
|
||||
|
||||
*This report is confidential and intended solely for the use of Ace Portables and their financial institution.*
|
||||
BIN
clients/ace-portables/reports/logo-light.png
Normal file
BIN
clients/ace-portables/reports/logo-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
52
clients/anaise/PROJECT_STATE.md
Normal file
52
clients/anaise/PROJECT_STATE.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Anaise — Project State
|
||||
|
||||
> READ THIS before starting work on this client.
|
||||
> UPDATE THIS when you begin work (claim a lock) and when you finish (release lock + log changes).
|
||||
> Last updated: 2026-04-20
|
||||
|
||||
---
|
||||
|
||||
## Active Session Locks
|
||||
|
||||
| Session | Working On | Status | Started |
|
||||
|---------|-----------|--------|---------|
|
||||
| _(none active)_ | | | |
|
||||
|
||||
**How to claim a lock:** Add a row before starting work. Remove it when done. Locks older than 2 hours with no update are considered stale.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Status:** ONBOARDING
|
||||
**Last Activity:** 2026-04-16
|
||||
|
||||
New client. Standard directory template applied 2026-04-16. Onboarding not yet complete. Directory contains only a `docs/` subfolder — minimal information captured.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure / Access
|
||||
|
||||
No infrastructure details recorded yet. Check `clients/anaise/docs/` for any notes captured during initial onboarding.
|
||||
|
||||
---
|
||||
|
||||
## Pending / Next Up
|
||||
|
||||
- [ ] Complete onboarding — capture infrastructure details, contacts, credentials to vault
|
||||
- [ ] Populate `docs/` with client overview, network diagram, server inventory
|
||||
|
||||
---
|
||||
|
||||
## Recent Changes
|
||||
|
||||
| Date | By | Change | Status |
|
||||
|------|-----|--------|--------|
|
||||
| 2026-04-16 | Howard | Standard client directory structure applied | IN PROGRESS |
|
||||
|
||||
---
|
||||
|
||||
## How to Update
|
||||
|
||||
**When starting:** Add your session to Active Session Locks.
|
||||
**When finishing:** Remove your lock row, add entries to Recent Changes, update Current State if needed.
|
||||
28
clients/anaise/docs/cloud/azure.md
Normal file
28
clients/anaise/docs/cloud/azure.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Azure / Cloud Services
|
||||
|
||||
## Azure Subscription
|
||||
- Subscription Name:
|
||||
- Subscription ID:
|
||||
- Resource Group(s):
|
||||
- Region:
|
||||
- Monthly Spend (approx):
|
||||
|
||||
## Virtual Machines
|
||||
| VM Name | Size | OS | IP | Purpose |
|
||||
|---------------|------------|------------|------------|-----------------|
|
||||
| | | | | |
|
||||
|
||||
## Networking
|
||||
- Virtual Network:
|
||||
- Address Space:
|
||||
- Subnets:
|
||||
- VPN Gateway to On-Prem: Yes/No
|
||||
- ExpressRoute: Yes/No
|
||||
|
||||
## Other Cloud Services
|
||||
<!-- AWS, Google Workspace, third-party SaaS -->
|
||||
| Service | Purpose | Admin URL | Notes |
|
||||
|-----------------|------------------|------------------|-----------------|
|
||||
| | | | |
|
||||
|
||||
## Notes
|
||||
52
clients/anaise/docs/cloud/m365.md
Normal file
52
clients/anaise/docs/cloud/m365.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Microsoft 365
|
||||
|
||||
## Tenant Info
|
||||
- Tenant Name:
|
||||
- Tenant ID:
|
||||
- Primary Domain:
|
||||
- Admin Portal URL: https://admin.microsoft.com
|
||||
|
||||
## Licensing
|
||||
| License Type | Quantity | Assigned | Available |
|
||||
|--------------------------|----------|----------|-----------|
|
||||
| Microsoft 365 Business Basic | | | |
|
||||
| Microsoft 365 Business Standard | | | |
|
||||
| Microsoft 365 Business Premium | | | |
|
||||
| Exchange Online Plan 1/2 | | | |
|
||||
| Other | | | |
|
||||
|
||||
## Exchange Online
|
||||
- Mail Domain(s):
|
||||
- MX Record Points To:
|
||||
- SPF Record:
|
||||
- DKIM Enabled: Yes/No
|
||||
- DMARC Policy:
|
||||
- Shared Mailboxes:
|
||||
- Distribution Groups:
|
||||
- Mail Flow Rules: Yes/No (describe below)
|
||||
|
||||
## SharePoint / OneDrive
|
||||
- SharePoint Sites:
|
||||
- External Sharing: Enabled/Disabled
|
||||
- OneDrive Storage Limit:
|
||||
|
||||
## Teams
|
||||
- Teams Phone System: Yes/No
|
||||
- Calling Plan / Direct Routing:
|
||||
- Auto Attendant:
|
||||
|
||||
## Entra ID (Azure AD)
|
||||
- Hybrid Joined: Yes/No
|
||||
- Azure AD Connect Server:
|
||||
- Sync Schedule:
|
||||
- Password Hash Sync: Yes/No
|
||||
- MFA Enforced: Yes/No
|
||||
- Conditional Access Policies:
|
||||
|
||||
## Security
|
||||
- Defender for Office 365: Yes/No
|
||||
- Safe Links: Yes/No
|
||||
- Safe Attachments: Yes/No
|
||||
- Audit Log Retention:
|
||||
|
||||
## Notes
|
||||
19
clients/anaise/docs/issues/log.md
Normal file
19
clients/anaise/docs/issues/log.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Issue Log
|
||||
|
||||
Record past issues and their resolutions here. This helps the AI learn from historical
|
||||
troubleshooting and avoid repeating failed approaches.
|
||||
|
||||
## Template
|
||||
|
||||
### [DATE] - [Brief Description]
|
||||
- **Reported By:**
|
||||
- **Severity:** Low / Medium / High / Critical
|
||||
- **Symptoms:**
|
||||
- **Root Cause:**
|
||||
- **Resolution:**
|
||||
- **Time to Resolve:**
|
||||
- **Lessons Learned:**
|
||||
|
||||
---
|
||||
|
||||
<!-- Add new issues above this line, newest first -->
|
||||
31
clients/anaise/docs/network/dhcp.md
Normal file
31
clients/anaise/docs/network/dhcp.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# DHCP Configuration
|
||||
|
||||
## DHCP Server
|
||||
- Server Name:
|
||||
- Server IP:
|
||||
- Failover Partner:
|
||||
|
||||
## Scopes
|
||||
|
||||
### Scope - [VLAN Name]
|
||||
- Subnet:
|
||||
- Range Start:
|
||||
- Range End:
|
||||
- Subnet Mask:
|
||||
- Default Gateway:
|
||||
- DNS Servers:
|
||||
- Lease Duration:
|
||||
- Exclusions:
|
||||
|
||||
<!-- Copy the block above for each DHCP scope -->
|
||||
|
||||
## Reservations
|
||||
| Device Name | MAC Address | IP Address | Scope | Notes |
|
||||
|-----------------|-------------------|-----------------|---------------|---------------|
|
||||
| | | | | |
|
||||
|
||||
## DHCP Relay
|
||||
- Relay agents configured on:
|
||||
- Helper address:
|
||||
|
||||
## Notes
|
||||
33
clients/anaise/docs/network/dns.md
Normal file
33
clients/anaise/docs/network/dns.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# DNS Configuration
|
||||
|
||||
## Internal DNS Servers
|
||||
| Server Name | IP Address | Role |
|
||||
|-------------|-----------|-------------------|
|
||||
| | | Primary |
|
||||
| | | Secondary |
|
||||
|
||||
## DNS Forwarders
|
||||
- Forwarder 1:
|
||||
- Forwarder 2:
|
||||
|
||||
## Conditional Forwarders
|
||||
| Domain | Forward To | Purpose |
|
||||
|----------------------|-----------------|-------------------|
|
||||
| | | |
|
||||
|
||||
## Key DNS Records
|
||||
| Record Type | Name | Value | Notes |
|
||||
|-------------|------------------|------------------|------------------|
|
||||
| A | | | |
|
||||
| CNAME | | | |
|
||||
| MX | | | |
|
||||
| TXT | | | |
|
||||
|
||||
## External DNS
|
||||
- Registrar:
|
||||
- Hosted At:
|
||||
- Primary Domain:
|
||||
- Management URL:
|
||||
|
||||
## Notes
|
||||
<!-- Split-brain DNS, special zones, etc. -->
|
||||
47
clients/anaise/docs/network/firewall.md
Normal file
47
clients/anaise/docs/network/firewall.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Firewall Configuration
|
||||
|
||||
## Device Info
|
||||
- Vendor/Model:
|
||||
- Firmware Version:
|
||||
- Management IP:
|
||||
- Management URL:
|
||||
- HA Pair: Yes/No
|
||||
- License Expiry:
|
||||
|
||||
## Interfaces
|
||||
| Interface | Zone | IP Address | VLAN | Description |
|
||||
|-----------|-----------|-----------------|------|-------------------|
|
||||
| WAN1 | WAN | | | Primary Internet |
|
||||
| WAN2 | WAN | | | Backup Internet |
|
||||
| LAN | LAN | | | |
|
||||
| DMZ | DMZ | | | |
|
||||
|
||||
## NAT Rules
|
||||
| Name | Source | Destination | Port(s) | NAT To |
|
||||
|-------------------|---------------|----------------|-------------|-----------------|
|
||||
| | | | | |
|
||||
|
||||
## Key Firewall Policies
|
||||
| Name | Source Zone | Dest Zone | Service | Action | Notes |
|
||||
|-------------------|--------------|---------------|-------------|--------|--------|
|
||||
| | | | | | |
|
||||
|
||||
## VPN
|
||||
### Site-to-Site VPNs
|
||||
| Peer Name | Peer IP | Local Subnet | Remote Subnet | Status |
|
||||
|-------------------|--------------|----------------|---------------|--------|
|
||||
| | | | | |
|
||||
|
||||
### SSL/Client VPN
|
||||
- Enabled: Yes/No
|
||||
- Portal URL:
|
||||
- Auth Method:
|
||||
- IP Pool:
|
||||
- Split Tunnel: Yes/No
|
||||
|
||||
## Content Filtering
|
||||
- Web Filter Profile:
|
||||
- App Control Profile:
|
||||
- DNS Filter:
|
||||
|
||||
## Notes
|
||||
43
clients/anaise/docs/network/topology.md
Normal file
43
clients/anaise/docs/network/topology.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Network Topology
|
||||
|
||||
## Internet Connection
|
||||
- ISP:
|
||||
- Circuit Type:
|
||||
- Speed (Down/Up):
|
||||
- Public IP:
|
||||
- Gateway:
|
||||
- Modem Model:
|
||||
|
||||
## Core Switch
|
||||
- Model:
|
||||
- IP Address:
|
||||
- Management URL:
|
||||
- Firmware Version:
|
||||
- Location:
|
||||
|
||||
## Additional Switches
|
||||
<!-- Copy this block for each switch -->
|
||||
### Switch - [Name/Location]
|
||||
- Model:
|
||||
- IP Address:
|
||||
- Port Count:
|
||||
- PoE: Yes/No
|
||||
- Uplink To:
|
||||
|
||||
## Wireless
|
||||
- Controller Model:
|
||||
- Controller IP:
|
||||
- Number of APs:
|
||||
- AP Model(s):
|
||||
|
||||
### Access Points
|
||||
<!-- Copy for each AP -->
|
||||
- AP Name:
|
||||
- Location:
|
||||
- IP Address:
|
||||
- Connected Switch/Port:
|
||||
|
||||
## WAN / SD-WAN
|
||||
- SD-WAN Vendor:
|
||||
- Number of Sites:
|
||||
- Hub Site:
|
||||
21
clients/anaise/docs/network/vlans.md
Normal file
21
clients/anaise/docs/network/vlans.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# VLANs
|
||||
|
||||
## VLAN Table
|
||||
|
||||
| VLAN ID | Name | Subnet | Gateway | DHCP Scope | Purpose |
|
||||
|---------|---------------|-----------------|-----------------|------------------|------------------------|
|
||||
| 1 | Default | | | | |
|
||||
| 10 | Management | | | | Network devices |
|
||||
| 20 | Servers | | | | Server infrastructure |
|
||||
| 30 | Workstations | | | | End user devices |
|
||||
| 40 | VoIP | | | | Phone system |
|
||||
| 50 | WiFi-Corp | | | | Corporate wireless |
|
||||
| 60 | WiFi-Guest | | | | Guest wireless |
|
||||
| 100 | Security | | | | Cameras / access ctrl |
|
||||
|
||||
## Inter-VLAN Routing
|
||||
- Performed by:
|
||||
- Routing device IP:
|
||||
|
||||
## VLAN Notes
|
||||
<!-- Any special considerations, trunk ports, tagged/untagged config -->
|
||||
26
clients/anaise/docs/overview.md
Normal file
26
clients/anaise/docs/overview.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Client Overview
|
||||
|
||||
## Company Name
|
||||
Anaise
|
||||
|
||||
## Primary Contact
|
||||
- Name: David
|
||||
- Email: anaisedavid.office@gmail.com
|
||||
|
||||
## Workstations
|
||||
|
||||
| Machine | Username | OS | Notes |
|
||||
|---------|----------|-----|-------|
|
||||
| DESKTOP-O8GF4SD | david | | |
|
||||
|
||||
## Credentials
|
||||
|
||||
Stored in SOPS vault. Do not put plaintext passwords in this file.
|
||||
|
||||
| Machine | Username | Vault Reference |
|
||||
|---------|----------|-----------------|
|
||||
| DESKTOP-O8GF4SD | david | `clients/anaise/desktop-o8gf4sd.sops.yaml` → `credentials.password` |
|
||||
|
||||
Retrieve with: `bash C:/vault/scripts/vault.sh get-field clients/anaise/desktop-o8gf4sd.sops.yaml credentials.password`
|
||||
|
||||
## Notes
|
||||
34
clients/anaise/docs/rmm/rmm.md
Normal file
34
clients/anaise/docs/rmm/rmm.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# RMM / Monitoring
|
||||
|
||||
## RMM Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- Agent Version:
|
||||
|
||||
## Agent Deployment
|
||||
- Total Devices:
|
||||
- Servers Monitored:
|
||||
- Workstations Monitored:
|
||||
- Network Devices Monitored:
|
||||
|
||||
## Monitoring Policies
|
||||
| Policy Name | Applies To | Alert Condition | Action |
|
||||
|-------------------|----------------|-------------------------|---------------|
|
||||
| Disk Space | All Servers | < 10% free | Alert + Ticket|
|
||||
| CPU | All Servers | > 90% for 15 min | Alert |
|
||||
| Service Monitor | All Servers | | |
|
||||
| Backup Monitor | | | |
|
||||
| Offline Alert | All Agents | Offline > 30 min | Alert |
|
||||
|
||||
## Patch Management
|
||||
- Patch Policy:
|
||||
- Patch Window:
|
||||
- Auto-approve: Yes/No
|
||||
- Exclusions:
|
||||
|
||||
## Scripting / Automation
|
||||
| Script Name | Schedule | Purpose |
|
||||
|---------------------|-------------|--------------------------|
|
||||
| | | |
|
||||
|
||||
## Notes
|
||||
26
clients/anaise/docs/security/antivirus.md
Normal file
26
clients/anaise/docs/security/antivirus.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Endpoint Security / Antivirus
|
||||
|
||||
## Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- License Count:
|
||||
- License Expiry:
|
||||
- Managed By:
|
||||
|
||||
## Policy
|
||||
- Real-time Protection: Yes/No
|
||||
- Scheduled Scans: (frequency)
|
||||
- Exclusions:
|
||||
|
||||
## Deployment Status
|
||||
- Total Endpoints:
|
||||
- Protected:
|
||||
- Missing Agent:
|
||||
- Out of Date:
|
||||
|
||||
## EDR / XDR
|
||||
- EDR Enabled: Yes/No
|
||||
- Product:
|
||||
- Console URL:
|
||||
|
||||
## Notes
|
||||
34
clients/anaise/docs/security/backup.md
Normal file
34
clients/anaise/docs/security/backup.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Backup and Disaster Recovery
|
||||
|
||||
## Backup Solution
|
||||
- Product:
|
||||
- Console URL:
|
||||
- License/Subscription:
|
||||
|
||||
## Backup Targets
|
||||
| Target Name | Type | Location | Capacity | Encrypted |
|
||||
|----------------|----------------|-----------------|--------------|-----------|
|
||||
| | Local NAS | | | Yes/No |
|
||||
| | Cloud | | | Yes/No |
|
||||
| | Offsite | | | Yes/No |
|
||||
|
||||
## Backup Jobs
|
||||
| Job Name | Source | Target | Schedule | Retention | Status |
|
||||
|-----------------|-------------------|------------|---------------|-------------|--------|
|
||||
| | | | | | |
|
||||
|
||||
## M365 Backup
|
||||
- M365 Backup Product:
|
||||
- Exchange Backed Up: Yes/No
|
||||
- SharePoint Backed Up: Yes/No
|
||||
- OneDrive Backed Up: Yes/No
|
||||
- Teams Backed Up: Yes/No
|
||||
|
||||
## Disaster Recovery Plan
|
||||
- RTO Target:
|
||||
- RPO Target:
|
||||
- DR Site:
|
||||
- Last DR Test Date:
|
||||
- DR Test Result:
|
||||
|
||||
## Notes
|
||||
49
clients/anaise/docs/servers/server_template.md
Normal file
49
clients/anaise/docs/servers/server_template.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Server: [SERVER NAME]
|
||||
|
||||
## General Info
|
||||
- Hostname:
|
||||
- IP Address:
|
||||
- OS:
|
||||
- OS Version:
|
||||
- Physical / Virtual:
|
||||
- Host (if virtual):
|
||||
- Location:
|
||||
- Last Patched:
|
||||
|
||||
## Hardware (if physical)
|
||||
- Make/Model:
|
||||
- CPU:
|
||||
- RAM:
|
||||
- Storage:
|
||||
- Warranty Expiry:
|
||||
|
||||
## Roles and Services
|
||||
<!-- List all roles this server performs -->
|
||||
- [ ] Domain Controller
|
||||
- [ ] DNS Server
|
||||
- [ ] DHCP Server
|
||||
- [ ] File Server
|
||||
- [ ] Print Server
|
||||
- [ ] Application Server
|
||||
- [ ] Database Server
|
||||
- [ ] Backup Target
|
||||
- [ ] RDS / Terminal Server
|
||||
- [ ] Hyper-V Host
|
||||
|
||||
## Shares (if file server)
|
||||
| Share Name | Path | Permissions Group | Notes |
|
||||
|---------------|-------------------|---------------------|----------------|
|
||||
| | | | |
|
||||
|
||||
## Applications Installed
|
||||
| Application | Version | Purpose | License |
|
||||
|-------------------|------------|----------------------|---------------|
|
||||
| | | | |
|
||||
|
||||
## Backup
|
||||
- Backup Method:
|
||||
- Backup Schedule:
|
||||
- Backup Target:
|
||||
- Last Verified Restore:
|
||||
|
||||
## Notes
|
||||
56
clients/at-trebesch/PROJECT_STATE.md
Normal file
56
clients/at-trebesch/PROJECT_STATE.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# AT Trebesch — Project State
|
||||
|
||||
> READ THIS before starting work on this client.
|
||||
> UPDATE THIS when you begin work (claim a lock) and when you finish (release lock + log changes).
|
||||
> Last updated: 2026-04-20
|
||||
|
||||
---
|
||||
|
||||
## Active Session Locks
|
||||
|
||||
| Session | Working On | Status | Started |
|
||||
|---------|-----------|--------|---------|
|
||||
| _(none active)_ | | | |
|
||||
|
||||
**How to claim a lock:** Add a row before starting work. Remove it when done. Locks older than 2 hours with no update are considered stale.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Status:** ONBOARDING
|
||||
**Last Activity:** 2026-04-17
|
||||
|
||||
New client. Standard directory structure applied 2026-04-17. Onboarding is in progress — initial files (overview.md, workstations.md) exist but onboarding tasks are not yet confirmed complete.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure / Access
|
||||
|
||||
See `clients/at-trebesch/overview.md` for client overview.
|
||||
See `clients/at-trebesch/workstations.md` for workstation inventory.
|
||||
|
||||
Other directories: `cloud/`, `issues/`, `network/`, `reports/`, `rmm/`, `security/`, `servers/`.
|
||||
|
||||
---
|
||||
|
||||
## Pending / Next Up
|
||||
|
||||
- [ ] Complete onboarding — review `overview.md` and `workstations.md` for any gaps
|
||||
- [ ] Confirm infrastructure details (servers, network, credentials in vault)
|
||||
- [ ] Enroll in GuruRMM if applicable
|
||||
|
||||
---
|
||||
|
||||
## Recent Changes
|
||||
|
||||
| Date | By | Change | Status |
|
||||
|------|-----|--------|--------|
|
||||
| 2026-04-17 | Howard | Standard client directory structure created; overview.md and workstations.md populated | IN PROGRESS |
|
||||
|
||||
---
|
||||
|
||||
## How to Update
|
||||
|
||||
**When starting:** Add your session to Active Session Locks.
|
||||
**When finishing:** Remove your lock row, add entries to Recent Changes, update Current State if needed.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user