diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index abfa4f3..7dc0846 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -1,101 +1,103 @@ -# Memory Index - -## Reference -- [ACG resource map](reference_resource_map.md) — **READ THIS FIRST** when a task references a server/service/tenant/API. What we have access to, how to connect from this machine, per-machine exceptions, gotchas. Points at the detail files below. -- [GURU-5070 Rust toolchain](reference_guru5070_rust_toolchain.md) — GURU-5070 now has cargo + MSVC + protoc; build/clippy/test guru-connect LOCALLY (set PROTOC to the winget path) instead of the build host. CI only clippy-checks the Linux server, not the Windows agent. -- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage. -- [Power Failure Runbook](../POWER_FAILURE_RUNBOOK.md) — Recovery order after a power event: Tailscale routes, libvirt/VMs, Seafile, NPM/DNS. -- [Syncro API — Invoice Verification Pattern](syncro_invoice_verification_pattern.md) — /invoices?customer_id=X returns no ticket linkage; query /invoices/{number} for ticket_id. Compare by ticket ID, not number. -- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval; features→roadmap, bugs→bug list. -- [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 Access](reference_ix_server_access.md) — `ix.azcomputerguru.com` / 172.16.3.10. Reachable when Tailscale is on (no VPN). SSH currently uses sshpass with root password; key auth from GURU-5070 not configured yet (was CachyOS, now Win11 — verify). -- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites. -- [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//docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/. -- [MSP Audit Scripts](reference_msp_audit_scripts.md) — server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/. -- [Pluto Build Server](reference_pluto_build_server.md) — Windows build VM: hostname PLUTO = Unraid VM "Claude-Builder" = 172.16.3.36 (all the same box). MSVC + WiX + Azure Trusted Signing. Drive via /rmm (agent enrolls as PLUTO) when SSH key isn't authorized. -- [Coord /messages API shape](reference_coord_messages_api_shape.md) — GET /api/coord/messages returns {total,skip,limit,messages[]} NOT a bare array; parse .messages[], strip control chars, read flag may be null. -- [Gitea API credential](reference_gitea_api_credential.md) — Gitea API (PRs/merges) as howard uses services/gitea-howard.sops.yaml password on internal http://172.16.3.20:3000; NOT the gururmm-server SSH password. -- [Gitea Internal API Access](reference_gitea_internal.md) — git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips). -- [Gitea git-op latency](reference_gitea_git_op_latency.md) — SSH (.20:2222) is SLOWEST (~1.5s); internal HTTP+token ~0.55s; SOPS lookup only ~0.33s. Don't switch to SSH for speed. Gitea SSH is .20:2222 (API ssh_url .21 is wrong). -- [GuruRMM technical reference](reference_gururmm.md) — Server (172.16.3.30) layout + downloads dir `/var/www/gururmm/downloads` + `.channel` sidecar rollout control (stable/beta) + privileged server access via the server's OWN root RMM agent (hostname `gururmm`, no SSH needed; plink fallback) + API + `context=user_session` (WTS impersonation) + build-pipeline vendoring at `deploy/build-pipeline/` + Linux agent systemd sandbox trap. -- [Trebesch DESKTOP-QNP3ON5 shell replacement](reference_trebesch_qnp3on5.md) — AT Trebesch box runs an Explorer shell replacement; explorer.exe owner check returns blank — use Win32_ComputerSystem.UserName. GuruRMM SWIFT-LION-2892. - -## Users -- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json). -- [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI. - -## Feedback -- [Bot alerts need a ticket link](feedback_bot_alert_ticket_link.md) — Syncro ticket bot-alerts MUST include a clickable link: https://computerguru.syncromsp.com/tickets/ (internal id, not ticket number). post-bot-alert.sh posts raw text; put the URL in the message. -- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks. -- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh. -- [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) — "remediation tool" = tiered ComputerGuru app suite via /remediation-tool; NOT CIPP, NOT the deprecated fabb3421. -- [CA managed programmatically (with discipline)](feedback_ca_programmatic_management.md) — Conditional Access CAN be written via Tenant Admin app; ALWAYS report-only first + exclude break-glass + confirm before enforcing. Overrides old "CA manual" rule. -- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) — Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional. -- [/save writes narrative directly](feedback_save_no_ollama.md) — No Ollama for /save; write all sections inline — too slow. -- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines). -- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows. -- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message. -- [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl. -- [Windows bash command mapping](feedback_windows_bash_mapping.md) — `bash` often resolves to WSL stub instead of Git/MSYS bash required by the harness. Fix by prepending `C:\Program Files\Git\bin` (and usr\bin) to PATH, or source `.claude/scripts/ensure-git-bash.ps1`. Profile has the logic; use plain `bash .claude/scripts/...` after remap. See the helper and this memory file for details. -- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall. -- [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\\*` directly. -- [Graph CA policy reads are eventually consistent](feedback_graph_ca_policy_eventual_consistency.md) — After PATCHing a CA policy (204), wait ~5s before GET-verifying; immediate reads can be stale. -- [Graph password reset needs a privileged role](feedback_graph_password_reset_requires_role.md) — PATCH passwordProfile on an existing user 403s without a directory role; User.ReadWrite.All alone only sets a password at CREATE. -- [Vault writes — do the full sequence yourself](feedback_complete_vault_operations_end_to_end.md) — A vault entry = write plaintext → sops -e -i → git add/commit/push, all of it; don't stop at "encrypted on disk." -- [Syncro is the default PSA; Autotask is opt-in](feedback_psa_default_syncro.md) — Ticketing/billing/customers default to Syncro (/syncro). Only use /autotask on an explicit "in Autotask" request. /autotask kept local/undistributed. -- [Paste-safe command formatting (Howard)](feedback_command_formatting.md) — Two clauses, one root cause: (a) multi-line scripts not semicolon one-liners (wrap breaks paste), (b) all code at column 0 inside fences (indentation breaks PowerShell paste). -- [Autonomous infra/build setup](feedback_autonomous_infra_setup.md) — During infra/build/CI/dev setup, just install prerequisites and push through routine steps; reserve check-ins for genuine decisions (forks, destructive/outward, client/prod). -- [Check patterns before asking](feedback_check_patterns_before_asking.md) — Before asking how to do something repeat-style (sync, save, sweep, billing), study existing artifacts and workflow docs first; reach for similar past artifacts as the template. -- [Pricing verification — no guessing](policy_pricing_verification.md) — ANY cost presented to the team or a client MUST be verified via live web lookup (WebFetch/WebSearch, fallback to headless Chrome). Never estimate from training data. Cite source + date inline. If unreachable, say so — do NOT substitute a guess. -- [Client communication tone](feedback_client_tone.md) — How to write client-facing Syncro comments — expert partner, not intake questionnaire. -- [Add Mike as owner on all Entra apps](feedback_entra_app_owner.md) — Apps created via management SP have no user owner — must add Mike manually or publisher verification fails. -- [No TOML/config file approach for endpoints](feedback_no_toml_config_endpoints.md) — User explicitly prohibits TOML or config-file-based endpoint configuration — this will never be approved. -- [Python on Windows — use py launcher](feedback_python_windows.md) — Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL. -- [Memory tooling may delete now — additive-only constraint dropped](feedback_memory_sync_destructive_ok.md) — As of 2026-06-02, memory-dream and sync-memory.sh are sanctioned to perform destructive ops (apply proposed merges/dedups, propagate repo deletions back to harness profile stores). Onboarding-phase safety net now fights deliberate consolidation (e.g. 2026-06-01's 39 deletions resurrected on the next sync). Script updates pending. -- [Unsaved sessions are recoverable from transcripts](feedback_session_recovery.md) — Crashed/closed-before-save sessions live in `~/.claude/projects//*.jsonl`; the detector auto-recovers orphans, `/recover ` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`. - -### Syncro -- [Syncro API plumbing](feedback_syncro_api.md) — Content-Type required on all POST/PUT; NO idempotency anywhere — always GET before retrying; response wrappers (`.ticket.id`, `.comment.id`); add_line_item shape (internal ID, flat response, required fields); HTML uses `
` not `
    /
  • `; timer_entry response is FLAT but SUPERSEDED (use add_line_item). -- [Syncro billing rules](feedback_syncro_billing.md) — Bill with `add_line_item` directly (not timers); fetch rates LIVE; never invent labor names (real product names only); match labor type to delivery channel (never "Prepaid project labor"); labor `taxable:false` (AZ); warranty `1049360` (never patch price); emergency `26184` ×1.5 once, branch by `prepay_hours`; corrections preserve original tech's user_id; estimate hardware `32252`. -- [Syncro workflow rules](feedback_syncro_workflow.md) — ALWAYS preview comments before posting (no exceptions); verify appointment day-of-week ("Saturday 2026-05-23") before creating; ASK who the appointment owner is; leave `contact_id` BLANK by default for ALL customers (ignore Syncro's contact-picker auto-default). -- [Syncro lessons / incident archive](feedback_syncro_history.md) — Detail behind the three rule files: tickets (#32332, #32312, #32225, #32253, #32203, #32185, #32142, #32304, #32333), verbatim Mike/Howard/Winter quotes, dates, tech user_id table (Mike 1735 / Howard 1750 / Winter 1737 / Rob 1760), labor product table, and superseded-rule history. - -### GuruRMM -- [GuruRMM operational rules](feedback_gururmm.md) — Six rules: (1) RMM dev = Mike, never Howard (368/0 commits); GuruScan is Howard's. (2) Agent parity Win+Linux+macOS in same change. (3) Builds via Gitea webhook pipeline only, never SSH. (4) #bot-alerts only for client/ticket impact, skip internal infra/dev. (5) Identify agents by IP, not by reconning candidates. (6) UNC paths in user_session need [char]92 — literals get halved. -- [Build channel default = beta](feedback_gururmm_build_channel_default.md) — New agent builds must be tagged BETA by default (stable = explicit promote re-tag); distinct from agents defaulting to the stable CHANNEL (correct). Fixed build-windows/linux.sh 2026-06-01; macOS already correct. Enables beta-first canary. -- [Dashboard beta-first deploy](feedback_dashboard_beta_first.md) — Dashboard auto-builds to rmm-beta.azcomputerguru.com on push; prod (rmm.azcomputerguru.com) is explicit promote-only via promote-dashboard.sh --confirm. Never hand-rsync prod. One artifact, nginx sub_filter BETA banner. Stood up 2026-06-02. - -### Cascades -- [Cascades operational rules](feedback_cascades.md) — Two active rules: (1) folder redirection (fdeploy) needs subfolders PRE-CREATED before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1. (2) ALWAYS ask which security group(s) a new user goes into — never auto-derive from OU. - -## Machine -- [GURU-5070 Workstation Setup](reference_workstation_setup.md) — Mike's primary (owner confirmed 2026-05-26). Windows 11 Pro. Renamed from OC-5070 → ACG-5070/acg-guru-5070 → GURU-5070; all the same box, all Mike's. -- [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers. - -## Project -- [Automate memory consolidation/lint (phased)](project_memory_consolidation_automation.md) — Eventually auto-run /memory-dream; lint+additive fixes can automate early, merges/deletes stay human-approved. Engine: .claude/skills/memory-dream/ + .claude/scripts/sync-memory.sh. -- [Trebesch PST consolidation (staged)](project_trebesch_pst_consolidation.md) — Address-book CSV from 24 PSTs on DESKTOP-QNP3ON5; scripts staged at .claude/tmp/treb-*.ps1, WAITING for Howard's 6pm-MST 2026-06-01 go signal (attended run). See [[reference_trebesch_qnp3on5]]. -- [GuruRMM project state](project_gururmm.md) — Dev principles (every feature full-stack: backend+API+UI+docs+scalability; product works without AI; FEATURE_ROADMAP update is part of definition-of-done; mirrors guru-rmm/docs/DESIGN.md). Webhook docs-only build guard (SPEC-020 Phase 0; webhook-handler.py repo copy is STALE — don't redeploy). Mac install-hooks.sh setup STILL PENDING on Mikes-MacBook-Air. -- [GuruConnect](project_guruconnect.md) — v2 direction (native-first full key fidelity Win+R/Ctrl+Alt+Del + bidirectional file cut/paste/drag; WebRTC fallback only; standalone-first + RMM contract; tenancy-ready schema; Mike willing to scrap v1). Manual deploy procedure to 172.16.3.30 (build-on-server in login shell; sqlx runtime queries; NPM `CONNECT_TRUSTED_PROXIES=172.16.3.20` gotcha). v2 live since 2026-05-30. -- [Apple MDM + Developer certs (GuruRMM mobile)](project_apple_mdm_certs.md) — ACG holds Apple Developer+signing and Apple MDM Push certs (acquired 2026-05-29) for SPEC-017. MDM push cert RENEWS ANNUALLY on the same Apple ID or all enrolled iOS devices break. -- [Only RMM & GC are versionable products](project_versionable_products.md) — GuruRMM + GuruConnect are the only products with own repos/submodules; everything else stays in the claudetools monorepo. Split only for independent pipeline OR versioned external consumer. -- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed. -- [Cascades](project_cascades.md) — Active state: Syncro ticket #110680053 + plan file (machine-specific path on Howard's box), admin accounts (sysadmin@=Howard, admin@=Mike — daily-driver, NOT break-glass), Phase-B caregiver CA pilot (SG-Caregivers-Pilot, group-scoped never tenant-wide), prepaid block ~37.5h (rate TBD), pilot cleanup checklist. -- [Cascades history](project_cascades_history.md) — fdeploy 502/ACL root cause (Flags=1211→187 fix), 2026-04-29 CA-rescoping decision (Howard pulled the brakes on tenant-wide), 2026-05-14 per-user-security-group decision rationale. -- [Sync script bug — untracked files (RESOLVED)](project_sync_script_bug.md) — FIXED 2026-05-21: sync.sh now uses `git status --porcelain` for change detection (repo + vault). -- [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 SBR Email Routing Setup](project_neptune_sbr_email_routing.md) — Full SBR routing chain, config file locations, MailProtector integration, access methods. Treat routing breakage as systemic (devcon, Sorensen/rieussetcorp), not per-client. -- [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](project_dataforth.md) — M365 email (Graph API; tenant in vault at clients/dataforth/m365.sops.yaml); neptune.acghosting.com is ACG's, NOT Dataforth's. MFA enforced 2026-04-04 (3 CA policies). AJ needs dataforthgit@ forwarding. -- [Dataforth history (2026-03-27 incident)](project_dataforth_history.md) — DF-JOEL2 compromise via ScreenConnect social-engineering, attacker C2 IPs + IC3 case + remediation log + MFA rollout origin story + Joel Lohr retirement. RESOLVED 2026-04-04. -- [Radio show co-host — Tara, not Tom](radio_show_no_cohost_named_tom.md) — Co-host in 2014-s6e19 and 2016-s8e43 is Tara. "Tom" was hallucinated; rename complete. -- [Proposal: centralize config in identity.json](proposal_identity_centralization.md) — Rationale for the identity.json machine-config centralization (claudetools_root, ollama/python); now implemented. -- [ACG MSP tool stack](reference_acg_msp_stack.md) — ScreenConnect/CW Control, Splashtop, Syncro, Datto RMM, Datto EDR/AV, GuruRMM are ACG's OWN tools; do not flag as foreign/threat on managed machines (Defender-off is expected when Datto AV is active). -- [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel. -- [jq on Windows emits CRLF](feedback_jq_crlf_windows.md) — winget jq outputs CRLF; trailing \r silently breaks `for x in $(jq ...)` loops + read-from-@tsv. Override `jq(){ command jq "$@"|tr -d '\r'; }`. Windows-build-specific (passes on Mac/Linux). -- [ScreenConnect RESTful API auth](reference_screenconnect_api.md) — CTRLAuthHeader = raw api_secret (no Basic/b64) + Origin header; only method is GetSessionsByName; matches blank-for-agents Name field so it cannot enumerate full inventory. +# Memory Index + +## Reference +- [ACG resource map](reference_resource_map.md) — **READ THIS FIRST** when a task references a server/service/tenant/API. What we have access to, how to connect from this machine, per-machine exceptions, gotchas. Points at the detail files below. +- [GURU-5070 Rust toolchain](reference_guru5070_rust_toolchain.md) — GURU-5070 now has cargo + MSVC + protoc; build/clippy/test guru-connect LOCALLY (set PROTOC to the winget path) instead of the build host. CI only clippy-checks the Linux server, not the Windows agent. +- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage. +- [Power Failure Runbook](../POWER_FAILURE_RUNBOOK.md) — Recovery order after a power event: Tailscale routes, libvirt/VMs, Seafile, NPM/DNS. +- [Syncro API — Invoice Verification Pattern](syncro_invoice_verification_pattern.md) — /invoices?customer_id=X returns no ticket linkage; query /invoices/{number} for ticket_id. Compare by ticket ID, not number. +- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval; features→roadmap, bugs→bug list. +- [CDP Chrome driver](reference_cdp_chrome_driver.md) — Drive Chrome via DevTools Protocol (.claude/scripts/cdp.py): visible window + screenshots-to-disk so Gemini/Grok can SEE the live site. Use localhost not 127.0.0.1; dedicated profile. Antigravity-style. +- [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 Access](reference_ix_server_access.md) — `ix.azcomputerguru.com` / 172.16.3.10. Reachable when Tailscale is on (no VPN). SSH currently uses sshpass with root password; key auth from GURU-5070 not configured yet (was CachyOS, now Win11 — verify). +- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites. +- [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//docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/. +- [MSP Audit Scripts](reference_msp_audit_scripts.md) — server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/. +- [Pluto Build Server](reference_pluto_build_server.md) — Windows build VM: hostname PLUTO = Unraid VM "Claude-Builder" = 172.16.3.36 (all the same box). MSVC + WiX + Azure Trusted Signing. Drive via /rmm (agent enrolls as PLUTO) when SSH key isn't authorized. +- [Coord /messages API shape](reference_coord_messages_api_shape.md) — GET /api/coord/messages returns {total,skip,limit,messages[]} NOT a bare array; parse .messages[], strip control chars, read flag may be null. +- [Gitea API credential](reference_gitea_api_credential.md) — Gitea API (PRs/merges) as howard uses services/gitea-howard.sops.yaml password on internal http://172.16.3.20:3000; NOT the gururmm-server SSH password. +- [Gitea Internal API Access](reference_gitea_internal.md) — git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips). +- [Gitea git-op latency](reference_gitea_git_op_latency.md) — SSH (.20:2222) is SLOWEST (~1.5s); internal HTTP+token ~0.55s; SOPS lookup only ~0.33s. Don't switch to SSH for speed. Gitea SSH is .20:2222 (API ssh_url .21 is wrong). +- [GuruRMM technical reference](reference_gururmm.md) — Server (172.16.3.30) layout + downloads dir `/var/www/gururmm/downloads` + `.channel` sidecar rollout control (stable/beta) + privileged server access via the server's OWN root RMM agent (hostname `gururmm`, no SSH needed; plink fallback) + API + `context=user_session` (WTS impersonation) + build-pipeline vendoring at `deploy/build-pipeline/` + Linux agent systemd sandbox trap. +- [Trebesch DESKTOP-QNP3ON5 shell replacement](reference_trebesch_qnp3on5.md) — AT Trebesch box runs an Explorer shell replacement; explorer.exe owner check returns blank — use Win32_ComputerSystem.UserName. GuruRMM SWIFT-LION-2892. + +## Users +- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json). +- [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI. + +## Feedback +- [Bot alerts need a ticket link](feedback_bot_alert_ticket_link.md) — Syncro ticket bot-alerts MUST include a clickable link: https://computerguru.syncromsp.com/tickets/ (internal id, not ticket number). post-bot-alert.sh posts raw text; put the URL in the message. +- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks. +- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh. +- [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) — "remediation tool" = tiered ComputerGuru app suite via /remediation-tool; NOT CIPP, NOT the deprecated fabb3421. +- [CA managed programmatically (with discipline)](feedback_ca_programmatic_management.md) — Conditional Access CAN be written via Tenant Admin app; ALWAYS report-only first + exclude break-glass + confirm before enforcing. Overrides old "CA manual" rule. +- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) — Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional. +- [/save writes narrative directly](feedback_save_no_ollama.md) — No Ollama for /save; write all sections inline — too slow. +- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines). +- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows. +- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message. +- [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl. +- [Windows bash command mapping](feedback_windows_bash_mapping.md) — `bash` often resolves to WSL stub instead of Git/MSYS bash required by the harness. Fix by prepending `C:\Program Files\Git\bin` (and usr\bin) to PATH, or source `.claude/scripts/ensure-git-bash.ps1`. Profile has the logic; use plain `bash .claude/scripts/...` after remap. See the helper and this memory file for details. +- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall. +- [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\\*` directly. +- [Graph CA policy reads are eventually consistent](feedback_graph_ca_policy_eventual_consistency.md) — After PATCHing a CA policy (204), wait ~5s before GET-verifying; immediate reads can be stale. +- [Graph password reset needs a privileged role](feedback_graph_password_reset_requires_role.md) — PATCH passwordProfile on an existing user 403s without a directory role; User.ReadWrite.All alone only sets a password at CREATE. +- [Vault writes — do the full sequence yourself](feedback_complete_vault_operations_end_to_end.md) — A vault entry = write plaintext → sops -e -i → git add/commit/push, all of it; don't stop at "encrypted on disk." +- [Syncro is the default PSA; Autotask is opt-in](feedback_psa_default_syncro.md) — Ticketing/billing/customers default to Syncro (/syncro). Only use /autotask on an explicit "in Autotask" request. /autotask kept local/undistributed. +- [Paste-safe command formatting (Howard)](feedback_command_formatting.md) — Two clauses, one root cause: (a) multi-line scripts not semicolon one-liners (wrap breaks paste), (b) all code at column 0 inside fences (indentation breaks PowerShell paste). +- [Autonomous infra/build setup](feedback_autonomous_infra_setup.md) — During infra/build/CI/dev setup, just install prerequisites and push through routine steps; reserve check-ins for genuine decisions (forks, destructive/outward, client/prod). +- [Check patterns before asking](feedback_check_patterns_before_asking.md) — Before asking how to do something repeat-style (sync, save, sweep, billing), study existing artifacts and workflow docs first; reach for similar past artifacts as the template. +- [Pricing verification — no guessing](policy_pricing_verification.md) — ANY cost presented to the team or a client MUST be verified via live web lookup (WebFetch/WebSearch, fallback to headless Chrome). Never estimate from training data. Cite source + date inline. If unreachable, say so — do NOT substitute a guess. +- [Client communication tone](feedback_client_tone.md) — How to write client-facing Syncro comments — expert partner, not intake questionnaire. +- [Add Mike as owner on all Entra apps](feedback_entra_app_owner.md) — Apps created via management SP have no user owner — must add Mike manually or publisher verification fails. +- [No TOML/config file approach for endpoints](feedback_no_toml_config_endpoints.md) — User explicitly prohibits TOML or config-file-based endpoint configuration — this will never be approved. +- [Python on Windows — use py launcher](feedback_python_windows.md) — Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL. +- [Memory tooling may delete now — additive-only constraint dropped](feedback_memory_sync_destructive_ok.md) — As of 2026-06-02, memory-dream and sync-memory.sh are sanctioned to perform destructive ops (apply proposed merges/dedups, propagate repo deletions back to harness profile stores). Onboarding-phase safety net now fights deliberate consolidation (e.g. 2026-06-01's 39 deletions resurrected on the next sync). Script updates pending. +- [Unsaved sessions are recoverable from transcripts](feedback_session_recovery.md) — Crashed/closed-before-save sessions live in `~/.claude/projects//*.jsonl`; the detector auto-recovers orphans, `/recover ` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`. + +### Syncro +- [Syncro API plumbing](feedback_syncro_api.md) — Content-Type required on all POST/PUT; NO idempotency anywhere — always GET before retrying; response wrappers (`.ticket.id`, `.comment.id`); add_line_item shape (internal ID, flat response, required fields); HTML uses `
    ` not `
      /
    • `; timer_entry response is FLAT but SUPERSEDED (use add_line_item). +- [Syncro billing rules](feedback_syncro_billing.md) — Bill with `add_line_item` directly (not timers); fetch rates LIVE; never invent labor names (real product names only); match labor type to delivery channel (never "Prepaid project labor"); labor `taxable:false` (AZ); warranty `1049360` (never patch price); emergency `26184` ×1.5 once, branch by `prepay_hours`; corrections preserve original tech's user_id; estimate hardware `32252`. +- [Syncro workflow rules](feedback_syncro_workflow.md) — ALWAYS preview comments before posting (no exceptions); verify appointment day-of-week ("Saturday 2026-05-23") before creating; ASK who the appointment owner is; leave `contact_id` BLANK by default for ALL customers (ignore Syncro's contact-picker auto-default). +- [Syncro lessons / incident archive](feedback_syncro_history.md) — Detail behind the three rule files: tickets (#32332, #32312, #32225, #32253, #32203, #32185, #32142, #32304, #32333), verbatim Mike/Howard/Winter quotes, dates, tech user_id table (Mike 1735 / Howard 1750 / Winter 1737 / Rob 1760), labor product table, and superseded-rule history. + +### GuruRMM +- [GuruRMM operational rules](feedback_gururmm.md) — Six rules: (1) RMM dev = Mike, never Howard (368/0 commits); GuruScan is Howard's. (2) Agent parity Win+Linux+macOS in same change. (3) Builds via Gitea webhook pipeline only, never SSH. (4) #bot-alerts only for client/ticket impact, skip internal infra/dev. (5) Identify agents by IP, not by reconning candidates. (6) UNC paths in user_session need [char]92 — literals get halved. +- [Build channel default = beta](feedback_gururmm_build_channel_default.md) — New agent builds must be tagged BETA by default (stable = explicit promote re-tag); distinct from agents defaulting to the stable CHANNEL (correct). Fixed build-windows/linux.sh 2026-06-01; macOS already correct. Enables beta-first canary. +- [Dashboard beta-first deploy](feedback_dashboard_beta_first.md) — Dashboard auto-builds to rmm-beta.azcomputerguru.com on push; prod (rmm.azcomputerguru.com) is explicit promote-only via promote-dashboard.sh --confirm. Never hand-rsync prod. One artifact, nginx sub_filter BETA banner. Stood up 2026-06-02. + +### Cascades +- [Cascades operational rules](feedback_cascades.md) — Two active rules: (1) folder redirection (fdeploy) needs subfolders PRE-CREATED before first logon or it caches a failure forever; recovery via fix-shell-redirect.ps1. (2) ALWAYS ask which security group(s) a new user goes into — never auto-derive from OU. + +## Machine +- [GURU-5070 Workstation Setup](reference_workstation_setup.md) — Mike's primary (owner confirmed 2026-05-26). Windows 11 Pro. Renamed from OC-5070 → ACG-5070/acg-guru-5070 → GURU-5070; all the same box, all Mike's. +- [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers. + +## Project +- [Automate memory consolidation/lint (phased)](project_memory_consolidation_automation.md) — Eventually auto-run /memory-dream; lint+additive fixes can automate early, merges/deletes stay human-approved. Engine: .claude/skills/memory-dream/ + .claude/scripts/sync-memory.sh. +- [Trebesch PST consolidation (staged)](project_trebesch_pst_consolidation.md) — Address-book CSV from 24 PSTs on DESKTOP-QNP3ON5; scripts staged at .claude/tmp/treb-*.ps1, WAITING for Howard's 6pm-MST 2026-06-01 go signal (attended run). See [[reference_trebesch_qnp3on5]]. +- [GuruRMM project state](project_gururmm.md) — Dev principles (every feature full-stack: backend+API+UI+docs+scalability; product works without AI; FEATURE_ROADMAP update is part of definition-of-done; mirrors guru-rmm/docs/DESIGN.md). Webhook docs-only build guard (SPEC-020 Phase 0; webhook-handler.py repo copy is STALE — don't redeploy). Mac install-hooks.sh setup STILL PENDING on Mikes-MacBook-Air. +- [GuruConnect](project_guruconnect.md) — v2 direction (native-first full key fidelity Win+R/Ctrl+Alt+Del + bidirectional file cut/paste/drag; WebRTC fallback only; standalone-first + RMM contract; tenancy-ready schema; Mike willing to scrap v1). Manual deploy procedure to 172.16.3.30 (build-on-server in login shell; sqlx runtime queries; NPM `CONNECT_TRUSTED_PROXIES=172.16.3.20` gotcha). v2 live since 2026-05-30. +- [Apple MDM + Developer certs (GuruRMM mobile)](project_apple_mdm_certs.md) — ACG holds Apple Developer+signing and Apple MDM Push certs (acquired 2026-05-29) for SPEC-017. MDM push cert RENEWS ANNUALLY on the same Apple ID or all enrolled iOS devices break. +- [Only RMM & GC are versionable products](project_versionable_products.md) — GuruRMM + GuruConnect are the only products with own repos/submodules; everything else stays in the claudetools monorepo. Split only for independent pipeline OR versioned external consumer. +- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed. +- [Cascades](project_cascades.md) — Active state: Syncro ticket #110680053 + plan file (machine-specific path on Howard's box), admin accounts (sysadmin@=Howard, admin@=Mike — daily-driver, NOT break-glass), Phase-B caregiver CA pilot (SG-Caregivers-Pilot, group-scoped never tenant-wide), prepaid block ~37.5h (rate TBD), pilot cleanup checklist. +- [Cascades history](project_cascades_history.md) — fdeploy 502/ACL root cause (Flags=1211→187 fix), 2026-04-29 CA-rescoping decision (Howard pulled the brakes on tenant-wide), 2026-05-14 per-user-security-group decision rationale. +- [Sync script bug — untracked files (RESOLVED)](project_sync_script_bug.md) — FIXED 2026-05-21: sync.sh now uses `git status --porcelain` for change detection (repo + vault). +- [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 SBR Email Routing Setup](project_neptune_sbr_email_routing.md) — Full SBR routing chain, config file locations, MailProtector integration, access methods. Treat routing breakage as systemic (devcon, Sorensen/rieussetcorp), not per-client. +- [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](project_dataforth.md) — M365 email (Graph API; tenant in vault at clients/dataforth/m365.sops.yaml); neptune.acghosting.com is ACG's, NOT Dataforth's. MFA enforced 2026-04-04 (3 CA policies). AJ needs dataforthgit@ forwarding. +- [Dataforth history (2026-03-27 incident)](project_dataforth_history.md) — DF-JOEL2 compromise via ScreenConnect social-engineering, attacker C2 IPs + IC3 case + remediation log + MFA rollout origin story + Joel Lohr retirement. RESOLVED 2026-04-04. +- [Radio show co-host — Tara, not Tom](radio_show_no_cohost_named_tom.md) — Co-host in 2014-s6e19 and 2016-s8e43 is Tara. "Tom" was hallucinated; rename complete. +- [Proposal: centralize config in identity.json](proposal_identity_centralization.md) — Rationale for the identity.json machine-config centralization (claudetools_root, ollama/python); now implemented. +- [ACG MSP tool stack](reference_acg_msp_stack.md) — ScreenConnect/CW Control, Splashtop, Syncro, Datto RMM, Datto EDR/AV, GuruRMM are ACG's OWN tools; do not flag as foreign/threat on managed machines (Defender-off is expected when Datto AV is active). +- [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel. +- [jq on Windows emits CRLF](feedback_jq_crlf_windows.md) — winget jq outputs CRLF; trailing \r silently breaks `for x in $(jq ...)` loops + read-from-@tsv. Override `jq(){ command jq "$@"|tr -d '\r'; }`. Windows-build-specific (passes on Mac/Linux). +- [ScreenConnect RESTful API auth](reference_screenconnect_api.md) — CTRLAuthHeader = raw api_secret (no Basic/b64) + Origin header; only method is GetSessionsByName; matches blank-for-agents Name field so it cannot enumerate full inventory. +- [No manufactured guardrails on our products](feedback_no_manufactured_guardrails.md) — At Mikes request on GuruRMM/GuruConnect/ClaudeTools, just execute; stop only for genuinely irreversible/destructive ops (with a heads-up). Read the actual code/state before claiming something is disallowed or a security hole. diff --git a/.claude/memory/feedback_no_manufactured_guardrails.md b/.claude/memory/feedback_no_manufactured_guardrails.md new file mode 100644 index 0000000..7debbb8 --- /dev/null +++ b/.claude/memory/feedback_no_manufactured_guardrails.md @@ -0,0 +1,19 @@ +--- +name: feedback-no-manufactured-guardrails +description: On OUR products (GuruRMM/GuruConnect/ClaudeTools etc.) at Mike's request, execute without manufactured guardrails; only stop for genuinely dangerous (irreversible/destructive) actions, with a clear heads-up. +metadata: + type: feedback +--- + +For products we build together (GuruRMM, GuruConnect, ClaudeTools, the MSP tooling), Mike is the owner/admin and his authority over them is real: **at his request, just execute** — do not invent "prohibited" categories, do not refuse routine admin/dev/provisioning work, do not lecture. Judge by the action's ACTUAL consequence, not by which generic rule-of-thumb bucket it falls in. + +But "just execute" does NOT mean blind compliance. Three standing expectations: +1. **See around corners (proactive).** He counts on foresight — flag downstream/second-order consequences, design coupling, "works now but bites later" risks BEFORE they land. Bringing judgment forward is part of the job, not optional. +2. **Some actions I MUST perform because no interface exists for him** (e.g. creating an RMM user when there's no admin UI). When the tooling gap means I'm the only one who CAN do it, deflecting "go do it yourself" is just unhelpful, not safety. Do it. +3. **Negative ramifications → tell, then do.** If a request has real downsides, lay them out clearly and let HIM decide; inform is NOT block. The decision is his. + +Stop him ONLY when something is genuinely dangerous — **irreversible and destructive to production/client data, fleet-breaking, or harmful to third parties** (e.g. dropping client data, mass cross-tenant deletes). Even then it's: stop, state plainly why, wait for explicit confirmation — never "refuse for a made-up reason." That confirm is a partner check he WANTS, not a gate. + +**Why:** 2026-06-05 — I refused to create a routine test user in GuruRMM (his own product) citing a generic "don't create accounts" rule, then falsely alarmed that an endpoint was an "ungated security hole" after reading the route table but NOT the handler (it was bootstrap-only — not a vuln). Both were manufactured friction on his own system, and he was right to be frustrated: "you're actively making it so I use different products." + +**How to apply:** Default to action on our products. Before claiming something is disallowed or a security problem, READ THE ACTUAL CODE/STATE first. Reserve "stop and confirm" for truly irreversible/destructive ops. Related: [[feedback-no-toml-config-endpoints]]. diff --git a/.claude/memory/reference_cdp_chrome_driver.md b/.claude/memory/reference_cdp_chrome_driver.md new file mode 100644 index 0000000..42ad1b1 --- /dev/null +++ b/.claude/memory/reference_cdp_chrome_driver.md @@ -0,0 +1,38 @@ +--- +name: reference_cdp_chrome_driver +description: Drive Chrome via CDP (debugger) with on-disk screenshots; how Gemini/Grok "see" the live site +metadata: + type: reference +--- + +`.claude/scripts/cdp.py` drives Chrome over the **Chrome DevTools Protocol** (same approach +Antigravity uses) — fixing two problems the claude-in-chrome MCP extension had: invisible windows, +and screenshots that never landed on disk. + +**Why it matters:** CDP `Page.captureScreenshot` returns the PNG bytes, so cdp.py writes a **real +PNG file** → which can be fed to `agy image-analyze` (Gemini) or Grok. That is how Gemini/Grok +"look at the live site" (verified 2026-06-05: Gemini correctly read a CDP screenshot of the GuruRMM +login). The MCP extension's `save_to_disk` never produced a findable file. + +**Setup (one-time per session):** +- `py -m pip install websocket-client` (uses stdlib `urllib` + `websocket-client`; no Playwright/Node). +- `py .claude/scripts/cdp.py launch [url]` — opens a **visible** Chrome on a **dedicated profile** + (`~/.claude/cdp-chrome-profile`) with `--remote-debugging-port=9222`. Dedicated profile = NOT logged + in; the user signs into authenticated apps once (Claude still must NOT type passwords — that rule + holds regardless of CDP). + +**Gotchas:** +- Chrome's DNS-rebinding guard rejects `Host: 127.0.0.1` on the debug endpoint → **use `localhost`** + (cdp.py BASE is `http://localhost:9222`). Launch also passes `--remote-allow-origins=*`. +- Launching `chrome.exe` while Chrome runs on the SAME profile just opens a tab in the existing + instance (flags ignored). The dedicated `--user-data-dir` forces a real new instance with the port. + +**Commands:** `launch [url]` · `status` · `nav [tabid]` · `shot [tabid]` · +`click ` · `type ` · `key ` · `eval `. Stateless (new WS per command). + +**Letting Gemini/Grok DRIVE (not just see):** cdp.py is a plain CLI, so Grok's `run_terminal_command` +(or any agent with shell access) could call it to navigate/click. **Security caveat:** a debug Chrome +on :9222 is controllable by any local process, and if it holds authenticated sessions (M365, Syncro, +RMM) those are driveable by whatever drives it — including external-vendor CLIs. Safer model: **Claude +drives cdp.py; Gemini/Grok receive the on-disk screenshots.** Only expose direct driving to an +external CLI deliberately. See [[reference_gururmm]]. diff --git a/.claude/scripts/cdp.py b/.claude/scripts/cdp.py new file mode 100644 index 0000000..47e86f1 --- /dev/null +++ b/.claude/scripts/cdp.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +""" +cdp.py - drive Chrome over the DevTools Protocol (CDP), like Antigravity does. + +Launches (or attaches to) a Chrome started with --remote-debugging-port and drives +it: navigate, screenshot-to-disk, click, type, key, eval. Screenshots are written +as real PNG files (so they can be fed to Gemini/Grok image tools). + +Usage: + py cdp.py launch [url] # start a visible debug Chrome (dedicated profile) + py cdp.py status # /json/version + list page targets + py cdp.py nav [tabid] # navigate (active page if tabid omitted) + py cdp.py shot [tabid] # screenshot the page to a PNG file + py cdp.py click [tabid] # left-click at viewport coords + py cdp.py type [tabid] # insert text into the focused element + py cdp.py key [tabid] # press a key (Enter/Tab/Escape/...) + py cdp.py eval [tabid] # Runtime.evaluate, prints JSON result + +Env: CDP_PORT (default 9222), CDP_PROFILE (default %USERPROFILE%\\.claude\\cdp-chrome-profile) +""" +import sys, os, json, time, base64, subprocess, urllib.request + +PORT = int(os.environ.get("CDP_PORT", "9222")) +BASE = f"http://localhost:{PORT}" +PROFILE = os.environ.get("CDP_PROFILE", os.path.join(os.path.expanduser("~"), ".claude", "cdp-chrome-profile")) +CHROME = next((p for p in [ + r"C:\Program Files\Google\Chrome\Application\chrome.exe", + r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", + os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"), +] if os.path.isfile(p)), None) + +import websocket # websocket-client + + +def http_get(path): + with urllib.request.urlopen(BASE + path, timeout=5) as r: + return json.loads(r.read().decode()) + + +def page_targets(): + return [t for t in http_get("/json") if t.get("type") == "page"] + + +def pick_target(tabid=None): + targets = page_targets() + if not targets: + raise SystemExit("[cdp] no page targets. Run: py cdp.py launch") + if tabid: + for t in targets: + if t["id"] == tabid: + return t + raise SystemExit(f"[cdp] tabid {tabid} not found") + # prefer a non-devtools, non-blank page + for t in targets: + if not t["url"].startswith("devtools://"): + return t + return targets[0] + + +def send(ws, _id, method, params=None): + ws.send(json.dumps({"id": _id, "method": method, "params": params or {}})) + while True: + msg = json.loads(ws.recv()) + if msg.get("id") == _id: + if "error" in msg: + raise SystemExit(f"[cdp] {method} error: {msg['error']}") + return msg.get("result", {}) + # ignore events with no matching id + + +def with_ws(tabid, fn): + t = pick_target(tabid) + ws = websocket.create_connection(t["webSocketDebuggerUrl"], max_size=64 * 1024 * 1024) + try: + return fn(ws) + finally: + ws.close() + + +def cmd_launch(args): + if not CHROME: + raise SystemExit("[cdp] chrome.exe not found") + os.makedirs(PROFILE, exist_ok=True) + url = args[0] if args else "about:blank" + subprocess.Popen([ + CHROME, + f"--remote-debugging-port={PORT}", + f"--user-data-dir={PROFILE}", + "--no-first-run", "--no-default-browser-check", + "--remote-allow-origins=*", + url, + ], close_fds=True) + for _ in range(40): + try: + v = http_get("/json/version") + print(f"[cdp] launched: {v.get('Browser')} ws={v.get('webSocketDebuggerUrl','')[:40]}...") + print(f"[cdp] profile: {PROFILE}") + return + except Exception: + time.sleep(0.25) + raise SystemExit("[cdp] chrome started but debug port never opened") + + +def cmd_status(args): + v = http_get("/json/version") + print(f"Browser: {v.get('Browser')}") + for t in page_targets(): + print(f" [{t['id'][:8]}] {t['title'][:40]!r} {t['url'][:70]}") + + +def cmd_nav(args): + url = args[0] + if "://" not in url: + url = "https://" + url + tabid = args[1] if len(args) > 1 else None + def fn(ws): + send(ws, 1, "Page.enable") + send(ws, 2, "Page.navigate", {"url": url}) + # wait for load event (best-effort) + deadline = time.time() + 20 + ws.settimeout(20) + while time.time() < deadline: + try: + m = json.loads(ws.recv()) + except Exception: + break + if m.get("method") == "Page.loadEventFired": + break + return "ok" + with_ws(tabid, fn) + time.sleep(1.0) + print(f"[cdp] navigated -> {url}") + + +def cmd_shot(args): + out = os.path.abspath(args[0]) + tabid = args[1] if len(args) > 1 else None + def fn(ws): + return send(ws, 1, "Page.captureScreenshot", {"format": "png", "captureBeyondViewport": False}) + res = with_ws(tabid, fn) + with open(out, "wb") as f: + f.write(base64.b64decode(res["data"])) + print(f"[cdp] screenshot -> {out} ({os.path.getsize(out)} bytes)") + + +def cmd_click(args): + x, y = float(args[0]), float(args[1]) + tabid = args[2] if len(args) > 2 else None + def fn(ws): + for typ in ("mousePressed", "mouseReleased"): + send(ws, 1, "Input.dispatchMouseEvent", + {"type": typ, "x": x, "y": y, "button": "left", "clickCount": 1}) + return "ok" + with_ws(tabid, fn) + print(f"[cdp] click ({x},{y})") + + +def cmd_type(args): + text = args[0] + tabid = args[1] if len(args) > 1 else None + with_ws(tabid, lambda ws: send(ws, 1, "Input.insertText", {"text": text})) + print(f"[cdp] typed {len(text)} chars") + + +KEYMAP = {"Enter": 13, "Return": 13, "Tab": 9, "Escape": 27, "Backspace": 8} +def cmd_key(args): + key = args[0] + tabid = args[1] if len(args) > 1 else None + code = KEYMAP.get(key) + def fn(ws): + base = {"key": key, "windowsVirtualKeyCode": code} if code else {"key": key} + send(ws, 1, "Input.dispatchKeyEvent", {"type": "keyDown", **base}) + send(ws, 2, "Input.dispatchKeyEvent", {"type": "keyUp", **base}) + return "ok" + with_ws(tabid, fn) + print(f"[cdp] key {key}") + + +def cmd_eval(args): + js = args[0] + tabid = args[1] if len(args) > 1 else None + res = with_ws(tabid, lambda ws: send(ws, 1, "Runtime.evaluate", + {"expression": js, "returnByValue": True})) + print(json.dumps(res.get("result", {}).get("value"), indent=2, default=str)) + + +CMDS = {"launch": cmd_launch, "status": cmd_status, "nav": cmd_nav, "shot": cmd_shot, + "click": cmd_click, "type": cmd_type, "key": cmd_key, "eval": cmd_eval} + +if __name__ == "__main__": + if len(sys.argv) < 2 or sys.argv[1] not in CMDS: + print(__doc__) + raise SystemExit(1) + CMDS[sys.argv[1]](sys.argv[2:]) diff --git a/clients/dataforth/migration-gap-diff-RESUME.md b/clients/dataforth/migration-gap-diff-RESUME.md index bd2f3d0..ca34cae 100644 --- a/clients/dataforth/migration-gap-diff-RESUME.md +++ b/clients/dataforth/migration-gap-diff-RESUME.md @@ -46,7 +46,8 @@ HGHAUBNER/server `user_session` can write to existing GPO-mapped drives (e.g. Q: ## Other Dataforth items PARKED 2026-06-04 (not started) -### 1. AD1 Files backup — command READY, awaiting "run AD1" +### 1. AD1 Files backup — [DONE] created 2026-06-05 +**Created 2026-06-05 ~18:40 UTC** via GuruRMM remote command (cmd id `a12d59a3-117d-497a-a080-75823b8db08d`) on AD1 (agent `bf7bc5ee…`); `cbb.exe` returned "Backup plan is created." (180-day retention, fast-NTFS, compress, NBF, synthetic full, daily 2:00 AM). Initial run NOT triggered (first run at 2:00 AM, per Mike). AD1 now has image (`Image2025`) + file (`Files`), parallel to AD2. Original prep notes below for reference: AD1's shared data folders need a Files plan matching AD2's (NBF, daily 2 AM, 180-day retention, `ACG-Dataforth`). Shares: `Engineering`→`C:\Engineering`, `ITSvc`→`C:\Shares\ITSvc`. AD1 currently has only the `Image2025` image plan. Verified AD2 `Files` plan config: `SerializationSupportRetentionTime=180 days`, GFS off, synthetic full, compression, fast-NTFS. Run on AD1 via RMM (agent `bf7bc5ee…`): ``` cbb.exe addBackupPlan -n "Files" -a "ACG-Dataforth" -nbf -syntheticFull yes -d "C:\Engineering" -d "C:\Shares\ITSvc" -c yes -fastNtfs yes -ntfs yes -every day -at "2:00 AM" -purge "180d" -notification errorOnly -dr yes diff --git a/projects/msp-tools/guru-rmm b/projects/msp-tools/guru-rmm index 226ba9f..3cef6ba 160000 --- a/projects/msp-tools/guru-rmm +++ b/projects/msp-tools/guru-rmm @@ -1 +1 @@ -Subproject commit 226ba9f77c0e262a2410e70f939ddace7dcd0dc3 +Subproject commit 3cef6bacac80093d78035166bc4c3c39af00c739 diff --git a/session-logs/2026-06-05-rmm-dashboard-redesign-cdp.md b/session-logs/2026-06-05-rmm-dashboard-redesign-cdp.md new file mode 100644 index 0000000..98f0cd7 --- /dev/null +++ b/session-logs/2026-06-05-rmm-dashboard-redesign-cdp.md @@ -0,0 +1,51 @@ +## User +- **User:** Mike Swanson (mike) +- **Machine:** GURU-5070 +- **Role:** admin + +# GuruRMM dashboard redesign (Gemini live review) + CDP Chrome driver + +## Session Summary +Ran an interactive, Gemini-driven redesign review of the LIVE GuruRMM dashboard (Claude as eyes/hands +over 5 rounds), producing a final proposal + a round-by-round session log in the gururmm submodule +docs/. Then solved the underlying "can't drive/see Chrome" problem by building a CDP (Chrome DevTools +Protocol) driver, so screenshots now hit disk and can be fed to Gemini/Grok image tools. + +## Key Decisions +- Gemini drove the redesign (design brain w/ full docs incl. 2302-line roadmap + source); Claude + transcribed live views + ran non-destructive interaction tests. No app code changed; no live-site + changes. Proposal: context-spine IA, unified omnibox, triage inbox, AgentDetail vertical sub-nav, + reuse existing primitives (tri-state inheritance, vertical sub-nav, RBAC banners, dark terminal). +- CDP over the MCP extension: launch visible Chrome with --remote-debugging-port on a DEDICATED + profile; drive via .claude/scripts/cdp.py (websocket-client + urllib). Fixes invisible-window + + screenshots-not-on-disk. Use localhost (not 127.0.0.1) for the debug endpoint. +- Password rule unchanged: Claude does NOT type passwords into fields even with CDP / even when asked. + +## Problems Encountered +- MCP browser windows opened on a Chrome instance Mike couldn't see; save_to_disk screenshots never + landed on a findable path -> blocked feeding images to Gemini. Solved with CDP (captureScreenshot + returns bytes -> real PNG file -> Gemini image-analyze verified reading it). +- CDP /json 404 on 127.0.0.1 (DNS-rebinding guard) -> switched to localhost. + +## Live UX bugs found during testing (candidates for UI_GAPS.md) +1. "Search everything" bar dead on Dashboard (per-route local filter bound to nothing). 2. Ctrl+K + palette collides with Chrome's native shortcut + no on-screen trigger. 3. Alerts: no grouping/dedup, + no bulk actions, no remediation link from an alert. 4. AgentDetail Overview leads with Maintenance + Mode, burying live metrics. 5. App defaults to light despite "dark is native" (theme is per-profile + localStorage). + +## Configuration Changes +- NEW .claude/scripts/cdp.py (CDP driver). NEW .claude/memory/reference_cdp_chrome_driver.md (+ index). +- gururmm submodule (local commit 3cef6ba): docs/dashboard-redesign-{brief,proposal-gemini,session}.md +- pip: websocket-client 1.9.0 installed (py 3.14). + +## Pending / Next +- Decide: push gururmm submodule docs to the gururmm repo (may hit build webhook) — held local for now. +- Optional: re-run the redesign loop feeding Gemini REAL CDP screenshots (now possible); file bugs 1-5 + into UI_GAPS.md; triangulate with Grok; start Phase 1. +- Decide whether to grant Gemini/Grok direct cdp.py driving (security caveat: drives authed sessions). + +## Reference +- CDP driver: .claude/scripts/cdp.py (launch/status/nav/shot/click/type/key/eval). Port 9222, profile + ~/.claude/cdp-chrome-profile. Screenshots: .claude/tmp/cdp/. +- Redesign docs: projects/msp-tools/guru-rmm/docs/dashboard-redesign-*.md