Compare commits

...

311 Commits

Author SHA1 Message Date
Winter Williams
7db991b037 sync: auto-sync from GURU-BEAST-ROG at 2026-07-16 11:01:29
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-16 11:01:29
2026-07-16 11:02:18 -07:00
Winter Williams
e4639b419f sync: auto-sync from GURU-BEAST-ROG at 2026-07-16 10:22:46
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-16 10:22:46
2026-07-16 10:23:35 -07:00
Winter Williams
eba471a61a sync: auto-sync from GURU-BEAST-ROG at 2026-07-16 10:11:10
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-16 10:11:10
2026-07-16 10:13:18 -07:00
7e1525711a wiki: compile cascades-tucson (full) 2026-07-15 14:24:19 -07:00
05f115307c sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-15 14:10:38
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-15 14:10:38
2026-07-15 14:11:48 -07:00
4e3cf71851 sync: auto-sync from HOWARD-HOME at 2026-07-15 13:59:09
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 13:59:09
2026-07-15 13:59:40 -07:00
3bab0de9c3 sync: auto-sync from HOWARD-HOME at 2026-07-15 12:52:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 12:52:13
2026-07-15 12:52:42 -07:00
fd72c36f1e sync: auto-sync from HOWARD-HOME at 2026-07-15 12:26:02
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 12:26:02
2026-07-15 12:26:30 -07:00
0e244a610f sync: auto-sync from HOWARD-HOME at 2026-07-15 12:21:30
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 12:21:30
2026-07-15 12:22:01 -07:00
5aeabbcfb6 sync: auto-sync from HOWARD-HOME at 2026-07-15 12:11:07
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 12:11:07
2026-07-15 12:11:37 -07:00
3846c35b81 sync: auto-sync from GURU-5070 at 2026-07-15 12:09:07
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-15 12:09:07
2026-07-15 12:09:38 -07:00
dff86c7d05 sync: auto-sync from GURU-5070 at 2026-07-15 12:04:36
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-15 12:04:36
2026-07-15 12:05:08 -07:00
Winter Williams
fbec6a0480 sync: auto-sync from GURU-BEAST-ROG at 2026-07-15 11:58:24
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-15 11:58:24
2026-07-15 11:59:20 -07:00
9bd3dd796b sync: auto-sync from GURU-5070 at 2026-07-15 11:38:26
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-15 11:38:26
2026-07-15 11:43:25 -07:00
71aa0da646 sync: auto-sync from HOWARD-HOME at 2026-07-15 11:39:11
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 11:39:11
2026-07-15 11:39:45 -07:00
aae897abfe sync: auto-sync from HOWARD-HOME at 2026-07-15 07:08:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-15 07:08:08
2026-07-15 07:08:36 -07:00
9dd241626f sync: auto-sync from HOWARD-HOME at 2026-07-14 20:16:29
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-14 20:16:29
2026-07-14 20:17:04 -07:00
941de499c2 sync: auto-sync from HOWARD-HOME at 2026-07-14 19:13:30
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-14 19:13:30
2026-07-14 19:14:01 -07:00
ec2fe6060b sync: auto-sync from HOWARD-HOME at 2026-07-14 17:41:40
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-14 17:41:40
2026-07-14 17:42:14 -07:00
a6d8d8173f sync: auto-sync from HOWARD-HOME at 2026-07-14 17:02:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-14 17:02:24
2026-07-14 17:02:57 -07:00
Winter Williams
a9a17f426f sync: auto-sync from GURU-BEAST-ROG at 2026-07-14 15:28:42
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-14 15:28:42
2026-07-14 15:29:37 -07:00
088ffb7645 sync: auto-sync from ACG-TECH03L at 2026-07-14 14:43:38
Author: Howard Enos
Machine: ACG-TECH03L
Timestamp: 2026-07-14 14:43:38
2026-07-14 14:47:06 -07:00
548ebb8a20 sync: auto-sync from GURU-BEAST-ROG at 2026-07-14 13:01:57
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-14 13:01:57
2026-07-14 13:02:49 -07:00
Winter Williams
2f1af438d8 sync: auto-sync from GURU-BEAST-ROG at 2026-07-14 11:17:28
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-14 11:17:28
2026-07-14 11:18:21 -07:00
22503149ce sync: auto-sync from HOWARD-HOME at 2026-07-14 11:14:46
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-14 11:14:46
2026-07-14 11:16:53 -07:00
4ab8137daa report: sign-in investigation for mike@azcomputerguru.com (new-location alert)
No compromise found; alert traced to geo-IP mislocation of Tucson FirstDigital IP.
Documents ongoing password-spray against Azure CLI app (0 successes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-14 11:16:52 -07:00
8b55e9645a sync: auto-sync from GURU-BEAST-ROG at 2026-07-14 11:15:27
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-14 11:15:27
2026-07-14 11:16:14 -07:00
0daecf471a sync: auto-sync from GURU-BEAST-ROG at 2026-07-14 10:31:06
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-14 10:31:06
2026-07-14 10:31:54 -07:00
f617c507c4 sync: auto-sync from HOWARD-HOME at 2026-07-13 17:32:04
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-13 17:32:04
2026-07-13 17:32:32 -07:00
4a246ff097 sync: auto-sync from HOWARD-HOME at 2026-07-13 16:10:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-13 16:10:13
2026-07-13 16:10:39 -07:00
cda32b53bb sync: auto-sync from HOWARD-HOME at 2026-07-13 11:13:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-13 11:13:45
2026-07-13 11:14:18 -07:00
f2f0fe0f70 sync: auto-sync from HOWARD-HOME at 2026-07-13 11:11:22
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-13 11:11:22
2026-07-13 11:11:54 -07:00
7291c5e691 sync: auto-sync from HOWARD-HOME at 2026-07-13 10:20:57
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-13 10:20:57
2026-07-13 10:21:31 -07:00
1ead30b6f1 radio: advance radio-show -> efccc61 (7/11 compiled show notes: fold in unaired 7/4 walk-through)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 09:09:26 -07:00
794c46bff1 sync: auto-sync from GURU-5070 at 2026-07-11 08:45:16
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-11 08:45:16
2026-07-11 08:45:54 -07:00
3770d52777 guru-scan: bump submodule -- RKill disabled by default + expanded whitelist
Points the guru-scan submodule at 11b5d85: RKill now default_disabled (opt in
via -IncludeRKill/-Scanners) so background scans don't kill the interactive
user, and the scanner whitelist now also covers Tailscale, ownCloud, Datto
Workplace, Seafile, and OpenVPN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 08:07:23 -07:00
384f5967e9 sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 18:44:07
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 18:44:07
2026-07-10 18:44:56 -07:00
285687d48d sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 17:45:57
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 17:45:57
2026-07-10 17:48:58 -07:00
c72048d4d7 wiki: goldstein — DALLAS MSA->local account via ProfWiz fixed RDP (Mike) 2026-07-10 17:43:34 -07:00
d8941c56df wiki: compile goldstein (update) - fold OpenVPN/Site-Magic session log, refresh Active Work vs Syncro (#32384) 2026-07-10 17:27:49 -07:00
fe91b4ecfc sync: auto-sync from HOWARD-HOME at 2026-07-10 17:21:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-10 17:21:24
2026-07-10 17:21:55 -07:00
273eb150c8 sync: auto-sync from HOWARD-HOME at 2026-07-10 17:20:37
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-10 17:20:37
2026-07-10 17:21:06 -07:00
d1da1e5857 wiki: compile the-prairie-schooner (update) — migration paused state folded 2026-07-10 17:20:20 -07:00
8afd7ec9b7 syncro: record sandbox-cleanup findings (DELETE shapes, customer soft-delete, agent uninstall via msiexec)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:20:08 -07:00
269449d27c syncro: record that the public API has NO remote device-control/exec endpoint (GUI-only; execution goes through GuruRMM)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:13:43 -07:00
ae2ee346b5 syncro: Phase-2 deploy-agent + asset/RMM verified on a real device
deploy-agent proven end-to-end on RMM-TEST-MACHINE: vaulted Howard Test MSI URL -> GuruRMM
push (msiexec /qn, SYSTEM) -> Syncro service Running -> auto-enrolled as asset 12676769 under
the client + the policy folder embedded in the installer token (5092561) in ~1 min.
installed_applications populates fast; patches lag. POST /policy_folders {policy_folder}
requires parent_id (null->422); move-asset flip-restore verified on the real asset; DELETE
policy_folder works. Shapes + findings recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:09:47 -07:00
1fa1a42092 syncro: Phase-1b breadth findings (leads/vendors/products/PO/worksheets)
Live-tested the remaining write resources on Howard Test. Recorded:
- wrapped response shapes (.lead.id/.vendor.id/.product.id/.purchase_order.id/
  .po_line_item.id/.worksheet_result.id) added to Verified Response Shapes.
- create_po_line_item requires product maintain_stock:true.
- worksheet templates live in GET /tickets/settings (.worksheet_templates[]), no standalone
  endpoint; need a valid worksheet_template_id.
- PUT /products requires name+description; leads/vendors/products/POs have NO DELETE (global
  test records persist -> GUI cleanup).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:46:47 -07:00
555e1ffd5a syncro: finalize deploy-agent (installer URL = per-client vaulted secret, MSI)
Decoded the Syncro RMM installer URL from 4 client samples:
base64(v1-{customer_id}-{A}-48753-{policy_folder_id}). 48753 = our account id (constant);
customer_id + policy_folder_id are API-readable; A is an unguessable per-client security token
NOT exposed anywhere in the API -> the URL is NOT constructible and must be harvested per client
and vaulted. Installer is an MSI -> deploy uses msiexec /qn /norestart.

deploy-agent now reads clients/<slug>/syncro-agent-installer (credentials.installer_url) from
the vault (cascades-tucson, grabb-durando, reliant, howard-test seeded) and pushes via GuruRMM.
Findings recorded in test-findings.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:46:47 -07:00
12bb9f1544 syncro: capture Phase-1 live-test findings (email-delivery trap + response shapes)
Test campaign on Howard Test sandbox (customer 36118743) surfaced traps the skill must not
re-learn:
- invoice/estimate email endpoints return 200 "Email sent" even with NO recipient
  (customer.email null) -> never report delivery from the 200; verify customer.email first.
- customer.email is UNIQUE tenant-wide; PUT /customers failure returns
  {success:false,message:[...]} not {customer:{...}} -> check .success.
- POST/PUT /contacts return a FLAT object (.id), not {contact:{...}}; contact recipient
  flags (primary/receives_invoices) are ignored via API.
- POST /contracts write succeeds but body doesn't echo the object; /contracts?customer_id
  doesn't filter server-side -> GET-verify + client-filter.

New living log .claude/standards/syncro/test-findings.md; critical items folded into
syncro.md Hard Rules + Verified Response Shapes. Correction logged to errorlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:46:46 -07:00
996041406c sync: auto-sync from GURU-5070 at 2026-07-10 15:57:23
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-10 15:57:23
2026-07-10 15:57:55 -07:00
1f56098652 syncro: expand skill to full Syncro API surface (assets/RMM/deploy) + verified capability model
Add comprehensive Syncro coverage beyond PSA core:
- New .claude/standards/syncro/api-reference.md: complete verified inventory of ~180
  endpoints across 38 resource types (generated from live OpenAPI 3.0 spec + tenant
  probe 2026-07-10), with worked GET/POST/PUT/DELETE templates and token-capability matrix.
- /syncro: asset read intelligence (patches, installed_applications), asset create/update,
  policy-folder move (move-asset), RMM alerts, and deploy-agent (hybrid installer push via
  GuruRMM using SyncroSetup --console --allow-force-reboot).
- move-asset ships a capability preflight (GET /policy_folders?customer_id -> 401 = missing
  Assets-Policy-Change) + mandatory post-write verify, because an under-scoped token returns
  HTTP 200 and silently no-ops the move.

Correct the "Syncro RMM is API-impossible" belief: it was a token-scope gap, not an API
limit. Live-verified the asset move (flip-and-restore 692253->692278->692253). Token scope
today: Howard + Winter full; Mike (vaulted ...ebbeb3) still 401 pending re-vault.

Corrects memory reference-syncro-rmm-api-gui-only; correction logged to errorlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 15:45:26 -07:00
46d2ae7a5b sync: auto-sync from HOWARD-HOME at 2026-07-10 15:11:39
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-10 15:11:39
2026-07-10 15:12:08 -07:00
830a86ea95 wiki: goldstein - full network + remote-access topology (UniFi gateways, Site Magic, OpenVPN, roaming playbook) (#32384) 2026-07-10 15:03:13 -07:00
a0690793bc guru-scan: bump gitlink to 3ca5ba7 (RKill whitelist + scan-log protection)
Point the superproject at the tested guru-scan commit: RKill -w process
whitelist (spares the PowerShell host + running GuruRMM/Syncro/Datto EDR/
ScreenConnect/Bitdefender/MSP360/Splashtop agents), Datto EDR folders added to
the HitmanPro/Emsisoft exclusion list, and cleanup guard so scan logs/archives
are never deleted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:47:29 -07:00
0fdeabde3d sync: auto-sync from HOWARD-HOME at 2026-07-10 14:44:39
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-10 14:44:39
2026-07-10 14:45:46 -07:00
Winter Williams
df46f56e59 sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 14:25:09
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 14:25:09
2026-07-10 14:25:55 -07:00
Winter Williams
299c053795 sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 14:03:06
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 14:03:06
2026-07-10 14:03:53 -07:00
Winter Williams
8350a4aeaf sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 13:15:01
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 13:15:01
2026-07-10 13:16:44 -07:00
1041db8587 sync: auto-sync from GURU-5070 at 2026-07-10 12:36:41
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-10 12:36:41
2026-07-10 12:37:18 -07:00
ebeb0ec1f6 wiki: compile instrumental-music-center (update) — refresh prepaid hours (10.0), fold 2026-07-08 log source, bump last_compiled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:20:28 -07:00
ecc0c8cd82 sync: auto-sync from HOWARD-HOME at 2026-07-10 11:12:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-10 11:12:18
2026-07-10 11:12:54 -07:00
Winter Williams
07404759f3 sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 10:06:22
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 10:06:22
2026-07-10 10:07:09 -07:00
Winter Williams
3286a47090 sync: auto-sync from GURU-BEAST-ROG at 2026-07-10 09:58:26
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-10 09:58:26
2026-07-10 09:59:18 -07:00
f95809cd9d sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-10 09:04:22
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-10 09:04:22
2026-07-10 09:04:29 -07:00
e78152a706 sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-10 08:30:31
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-10 08:30:31
2026-07-10 08:30:39 -07:00
017dcdf04c voip: VWP park extension 107 (Payroll) - shared mailbox
Extension 107 (Payroll) marked as PARKED pending decision on shared mailbox setup.
Removed from CSV bulk import (11 -> 10 users).
Updated all documentation to reflect new counts: 13 matched, 1 shared, 4 parked.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:59:00 -07:00
2f35163866 harness: read Syncro keys from vault, stop hardcoding the repo root
Two defects found while running /wiki-compile, both from tooling assuming
a path instead of resolving it.

1. Plaintext Syncro API keys. Mike's and Howard's live PSA keys were
   copy-pasted into three command files, a script, and two catalog docs --
   despite both already being vaulted at msp-tools/syncro and
   msp-tools/syncro-howard. Replaced with reads from the SOPS vault via a
   new sourced helper, .claude/scripts/syncro-env.sh. Write paths fail
   closed; read-only paths degrade to skipped enrichment rather than a
   wrong key. Per-user mapping is unchanged, so Syncro attribution is too.

2. Hardcoded repo root. wiki-compile/wiki-lint/inject-standards and
   gen_b64.py hardcoded D:/claudetools; this machine is C:/claudetools.
   syncro.md also read ~/.claude/identity.json before the repo copy -- the
   same bug that made remediation-tool's consent-audit report a fully
   consented tenant as RED. Root now resolves from the script's own
   location, with identity.json claudetools_root as the override.

get-identity.sh had a chicken-and-egg bug: it read ${CLAUDETOOLS_ROOT:-.}
but never set it, so every caller had to already know the root. It now
self-resolves and exports CLAUDETOOLS_ROOT + VAULT_ROOT.

Verified: all three command setup blocks authenticate against live Syncro;
get-identity.sh works from any cwd and honors a pre-set root; gps-rmm
autoenroll resolves its key from the vault. security-review: no findings.

NOTE: both keys remain valid in git history. Rotation in the Syncro portal
is the required follow-up -- this commit does not resolve that exposure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 07:56:33 -07:00
c34cdb3a39 voip: VWP note phone MAC assignments for Kayla and Jesse III
Extension 109 (Kayla Guerrero) - Phone MAC last 4: 73b7
Extension 117 (Jesse III) - Phone MAC last 4: 6cf5

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:55:32 -07:00
ec1cb40b62 voip: VWP note phone MAC assignment for Chris
Extension 113 (Chris Guerrero) - Phone MAC last 4: 755b

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:49:42 -07:00
f9b44cfb68 voip: VWP note phone MAC assignment for Tammy
Extension 106 (Tammy) - Phone MAC last 4: 6a36

Documented for device assignment workflow after second CSV batch.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:49:14 -07:00
1ca456b03f voip: VWP note phone MAC assignment for Rose
Extension 103 (Rose Guerrero) - Phone MAC last 4: 5217

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:43:26 -07:00
250eb8ecfe voip: VWP note phone MAC assignment for Ron
Extension 110 (Ron Winger) - Phone MAC last 4: 6509

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:42:42 -07:00
5641532429 voip: VWP note phone MAC assignment for J.R.
Extension 105 (J.R. Guerrero) - Phone MAC last 4: 7441

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:41:52 -07:00
1b0eeb5595 voip: VWP note phone MAC assignment for Ty
Extension 114 (Ty Fetters) - Phone MAC last 4: 6d01

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:41:21 -07:00
b4972497a7 voip: VWP note phone MAC assignment for Kitchen shared phone
Extension 111 (Kitchen) - Phone MAC last 4: 958e
Shared phone extension (no M365 account required)
Updated counts: 3 extensions parked (down from 4)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:40:43 -07:00
a70ec28bdc voip: VWP note phone MAC assignment for Shelly
Extension 104 (Shelly) - Phone MAC last 4: 96b8

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:39:02 -07:00
7f8fb5d9c5 voip: VWP note phone MAC assignment for Toni
Extension 115 (Toni) - Phone MAC last 4: 6cf9

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:38:25 -07:00
d477970ab8 voip: VWP note phone MAC assignments for Jesse Sr and Bart
Extension 102 (Jesse Sr) - Phone MAC last 4: 7559
Extension 116 (Bart) - Phone MAC last 4: a890

Documented for device assignment workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:37:01 -07:00
522594dedb wiki: compile cascades-tucson (update)
Fold 4 session logs since last_compiled=2026-07-01:
- 2026-07-02 caregiver phone login / PSO-Caregivers FGPP
- 2026-07-02 Shelby Trozzi Company Web Docs share + mapped drive
- 2026-07-09 Charity Menle caregiver rename
- 2026-07-09 Chris Knight accounting mailbox delegation

Surgical update mode (not --full): 735 of 739 lines left byte-for-byte
intact. Adds Company Web Docs share record, Shelby vault path (path only),
4 History rows; refreshes Syncro hours/tickets (37.5 hrs, 0 open).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 07:36:49 -07:00
06ed9ba562 voip: VWP clarify ext 106 (Tammy) and park ext 108 (Shannon)
Extension 106 (Tammy) matched to acctpay@valleywideplastering.com
Extension 108 (Shannon) parked pending clarification
Updated counts: 14 matched, 4 unmatched/parked

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:13:58 -07:00
25af453f61 feat(voip): Extension 117 clarification - Jesse III external email
CLARIFIED (2026-07-10):
- Extension 117: Jesse III
- Email: jesse@nescoap.com (external domain)
- Similar to ext 114 (Ty@CASARICA.NET - external domain)
- Status: Ready to add to second CSV batch

UPDATED:
- Matched users: 12 -> 13 (Jesse III now matched)
- Unmatched users: 6 -> 5 (removed Jesse III from unmatched list)
- Will be included in second CSV import with other clarified/shared extensions

FILES MODIFIED:
- extension-mapping.md: Updated ext 117 to show jesse@nescoap.com
- PROVISIONING-STATUS.md: Moved ext 117 to clarified section
- README.md: Updated user counts and user summary

CURRENT STATUS:
- 11 users in CSV ready for bulk import (102-116 minus gaps)
- 2 users for separate provisioning (101 Natalya, 117 Jesse III)
- 5 users still need clarification (106, 108, 111, 112, 118)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:09:35 -07:00
736d43f482 feat(voip): Extension 101 clarification - Natalya is receptionist
CLARIFIED (2026-07-10):
- Extension 101: Natalya (Receptionist)
- M365 Account: Customer Service (customerservice@valleywideplastering.com)
- Phone: Test phone from yesterday (MAC 805e0cdd71b1 / Last 4: 71b1)
- Status: Phone already registered, ready for user provisioning

UPDATED:
- Matched users: 11 -> 12 (Natalya now matched)
- Unmatched users: 7 -> 6 (removed Natalya from unmatched list)
- Created provision-natalya-101.md with step-by-step commands

FILES MODIFIED:
- extension-mapping.md: Updated ext 101 to show customerservice@ match
- PROVISIONING-STATUS.md: Added clarified section for ext 101
- README.md: Updated user counts and added provision-natalya-101.md link
- provision-natalya-101.md: NEW - Complete provisioning guide for receptionist

NEXT ACTIONS:
1. Upload 11-user CSV to PacketDial (unchanged)
2. Provision extension 101 separately (commands documented)
3. Remaining 6 unmatched extensions need clarification

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-10 07:09:35 -07:00
033019df18 sync: auto-sync from GURU-5070 at 2026-07-10 06:59:57
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-10 06:59:57
2026-07-10 07:00:29 -07:00
bf95a10685 sync: auto-sync from HOWARD-HOME at 2026-07-09 20:26:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-09 20:26:12
2026-07-09 20:26:40 -07:00
2fba62b7df sync: auto-sync from HOWARD-HOME at 2026-07-09 20:23:39
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-09 20:23:39
2026-07-09 20:24:08 -07:00
db10206aea feat(voip): VWP complete provisioning prep + setup documentation
Critical RPS provisioning URL fix + CSV bulk import ready + complete VoIP setup guide

CRITICAL FIX:
- RPS server URL corrected: http://ndp.ucaasnetwork.com/cfg (was ftp://p.packetdials.net)
- Discovered ACG has TWO whitelabel accounts (WLC vs OIT/PacketDial)
- Test phone (805e0cdd71b1) now registered immediately after fix (Status 1)

USER PROVISIONING:
- M365 account matching: 11 matched users, 7 unmatched (need clarification)
- CSV bulk import prepared (vwp-users-import.csv)
- Strategy: users WITHOUT devices initially (add phone extension: no)
- Device assignment workflow: incremental as phones distributed

COMPLETE VOIP SETUP DOCUMENTED:
- DID (phone number) management + routing options
- Call queues (ring groups) - recommended queues for VWP
- Auto-attendants (IVR menus) - main menu design
- Time frames (business hours routing)
- Additional features (hunt groups, voicemail-only, fax-to-email)
- All components with API commands + examples

FILES CREATED (7):
- README.md: Quick navigation hub + domain info
- PROVISIONING-STATUS.md: Current status + next steps
- SESSION-SUMMARY-2026-07-09.md: Complete session documentation (18 pages)
- COMPLETE-CLIENT-SETUP.md: Full VoIP setup guide (15 pages)
- DEVICE-ASSIGNMENT-COMMANDS.md: Copy-paste command reference
- extension-mapping.md: Extension to M365 account mapping
- vwp-users-import.csv: Bulk import file (READY for upload)

IMPLEMENTATION PHASES:
Phase 1 (In Progress): Basic connectivity - CSV ready for upload
Phase 2 (Planned): Main line setup - queue + auto-attendant + DID
Phase 3 (Planned): Business hours routing - time-based call handling
Phase 4 (Optional): Additional features per client needs

NEXT ACTIONS:
- Upload CSV to PacketDial web UI (manual step)
- Get client clarification on 7 unmatched users
- Assign devices as phones distributed (per-phone workflow documented)

Domain: vwp.91912.service
Main Number: 480-705-9500
E911: a-6a395c03d4cfe (301 N 56TH ST, CHANDLER AZ)
YMCS Site: 1e7578a6fe0e41cfb5a3e8b40933ffee

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-09 20:21:57 -07:00
eb04287396 sync: auto-sync from HOWARD-HOME at 2026-07-09 20:14:58
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-09 20:14:58
2026-07-09 20:16:45 -07:00
ab987bf9e1 wiki(sif-oidak): close the onmicrosoft.com UPN rename as not actionable
Removing onmicrosoft.com from M365 usernames needs sifoidak.com verified as a custom
Entra domain, which needs a DNS TXT record at GoDaddy. Mike confirmed ACG has no
registrar access to that account, so UPNs stay on sifoidak.onmicrosoft.com and new
users follow the same convention. Recorded as decided rather than blocked so it is
not re-proposed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:16:05 -07:00
d9f877661e wiki(sif-oidak): correct identity model, add printer + onboarding runbook
Sif-oidak was documented as a hybrid AD/M365 environment. It is not: there is no
Entra Connect on either DC, no AD object carries msDS-ConsistencyGuid, and every
M365 user has onPremisesSyncEnabled=null. AD and M365 are disjoint, and email is a
third system entirely (sifoidak.com at GoDaddy; the O365_BUSINESS SKU carries the
EXCHANGE_S_FOUNDATION no-mailbox stub).

That mismatch caused a real failure: Dwayne Ortega had a cloud account only, so he
could not log in to a domain-joined workstation. Created his AD account and recorded
the two-account onboarding rule.

Also documented: printer error 740 (Point-and-Print driver install needs admin,
UAC prompt never surfaces), a GPO pushing the MX-6240N queue from the wrong server,
SIF-SERVER2 confirmed as a backup DC, and refreshed stale laptop agent UUIDs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:16:04 -07:00
5a463ae133 sync: auto-sync from GURU-5070 at 2026-07-09 15:56:52
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-09 15:56:52
2026-07-09 15:57:31 -07:00
6942827391 sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-09 14:52:07
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-09 14:52:07
2026-07-09 14:52:17 -07:00
Winter Williams
07268cd741 sync: auto-sync from GURU-BEAST-ROG at 2026-07-09 13:39:12
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-09 13:39:12
2026-07-09 13:40:02 -07:00
57c23fc8d7 sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-09 12:31:21
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-09 12:31:21
2026-07-09 12:31:30 -07:00
2e92a2d38d sync: auto-sync from GURU-5070 at 2026-07-09 11:06:29
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-09 11:06:29
2026-07-09 11:12:33 -07:00
9926e5f2cc sync: auto-sync from HOWARD-HOME at 2026-07-09 11:00:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-09 11:00:18
2026-07-09 11:01:24 -07:00
Winter Williams
252d22048b sync: auto-sync from GURU-BEAST-ROG at 2026-07-09 10:09:06
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-09 10:09:06
2026-07-09 10:12:27 -07:00
b3ea44a8ff sync: auto-sync from HOWARD-HOME at 2026-07-08 18:20:05
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 18:20:05
2026-07-08 18:20:34 -07:00
e765c9115d sync: auto-sync from HOWARD-HOME at 2026-07-08 18:04:09
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 18:04:09
2026-07-08 18:04:40 -07:00
cac5eadaee sync: auto-sync from HOWARD-HOME at 2026-07-08 17:06:40
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 17:06:40
2026-07-08 17:07:11 -07:00
9c998a95d0 sync: auto-sync from HOWARD-HOME at 2026-07-08 17:03:04
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 17:03:04
2026-07-08 17:03:59 -07:00
447b18d62b sync: auto-sync from HOWARD-HOME at 2026-07-08 17:00:33
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 17:00:33
2026-07-08 17:01:23 -07:00
a7b2fae468 sync: auto-sync from HOWARD-HOME at 2026-07-08 17:00:10
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 17:00:10
2026-07-08 17:00:39 -07:00
cc336243e3 sync: auto-sync from HOWARD-HOME at 2026-07-08 13:09:22
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 13:09:22
2026-07-08 13:09:52 -07:00
fa97bcc49b sync: auto-sync from HOWARD-HOME at 2026-07-08 13:01:22
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 13:01:22
2026-07-08 13:01:51 -07:00
d037d82f5e sync: auto-sync from HOWARD-HOME at 2026-07-08 12:39:04
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 12:39:04
2026-07-08 12:39:32 -07:00
c0d304485a wiki: compile desert-rv-center (seed) 2026-07-08 12:38:25 -07:00
25ca9306e1 sync: auto-sync from HOWARD-HOME at 2026-07-08 12:15:56
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 12:15:56
2026-07-08 12:16:23 -07:00
fcf43044d4 sync: auto-sync from HOWARD-HOME at 2026-07-08 09:00:22
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 09:00:22
2026-07-08 09:00:51 -07:00
2a1ea6c9f7 sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-08 06:49:28
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-08 06:49:28
2026-07-08 06:50:46 -07:00
fc12c596f1 sync: auto-sync from HOWARD-HOME at 2026-07-08 06:09:58
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-08 06:09:58
2026-07-08 06:10:25 -07:00
eee469ce86 sync: auto-sync from HOWARD-HOME at 2026-07-07 21:17:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 21:17:44
2026-07-07 21:18:15 -07:00
4e148b6dc5 sync: auto-sync from HOWARD-HOME at 2026-07-07 21:05:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 21:05:08
2026-07-07 21:05:37 -07:00
19edfc45c0 wiki(IMC): SQL max-memory caps APPLIED 2026-07-07 (12GB/256/512) + probable-cause watch flag
- Confirmed caps were never applied (all uncapped); applied + verified via GuruRMM
- SQLEXPRESS/AIMSQL required IMC\guru impersonation (SYSTEM not sysadmin there)
- WATCH flag through ~2026-07-11: if AIM/perf issues reported, suspect this first; rollback = uncap
- Active Work item marked done

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:03:49 -07:00
e548f63605 fix(ps-encoded.sh): Python fallback when iconv.exe absent (Git-for-Windows ships libiconv DLL only, no pacman)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:51:17 -07:00
840b0ce9d6 wiki(IMC): 2026-07-07 fleet reconciliation — Datto EDR rollout, AV vendor, backup/billing gap, retire list
- AV/EDR is Datto (not Bitdefender); REPAIRADMIN BD->EDR swap in progress
- EDR deployed to BigMom + DESKTOP-NFU17AJ; queued LUIS/EdServices1/REPAIRADMIN
- DESKTOP-NFU17AJ enrolled in RMM (17->18); 4 more queued via SC
- MSP360 backup live-but-unbilled on IMC1; AV billed 20 seats
- 12 Syncro assets to archive (>40d offline; archive=UI-only)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:45:53 -07:00
09ab3de106 wiki: Prairie Schooner server migration state (paused, resume after UniFi swap) 2026-07-07 20:45:00 -07:00
361478dd66 sync: auto-sync from HOWARD-HOME at 2026-07-07 20:39:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 20:39:12
2026-07-07 20:39:41 -07:00
0a438929d0 ask-forum: harden per high-effort review + document capability limits
Fixes from the code-review workflow (7 findings): page the poll cursor past the
100-msg window (no false timeout behind bot/overflow messages); honor 429
retry_after and bail fast on permanent Discord errors (10003/50001/...) instead
of burning the timeout; status-check overflow follow-up chunks (warn, don't
silently truncate); clamp --read limit safely (guard >64-bit overflow); add
--wait --after for precise resumption; DRY the newline-trim; set LC_ALL=C.UTF-8
for codepoint-safe slicing. SKILL.md gains the tested bot capability/limit map
(can: post/read/edit-own/react; cannot without a grant: delete/archive/unlock/pin)
and a robustness section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:37:46 -07:00
Winter Williams
f379a828e9 bot: deploy #ct-forum guard on BEAST (submodule -> 56938bb) + log restart
Requested by Mike via #ct-forum thread; restart fires via one-shot
scheduled task so the bot survives posting its own confirmation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-07 20:32:39 -07:00
a2a277ab67 sync: auto-sync from HOWARD-HOME at 2026-07-07 20:31:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 20:31:16
2026-07-07 20:31:47 -07:00
9340cdec17 ask-forum: promote to a full skill + wire into skill-first routing
Add .claude/skills/ask-forum/SKILL.md (usage contract, correlation model,
forum-only scope, access/permissions) and route to it from CLAUDE.md
(skill-first covered domains) + SKILL_ROUTING.md, so sessions invoke the
skill instead of hand-rolling the Discord API — the footgun that produced a
broken background wait. Capture that footgun as memory
feedback_background_task_no_ampersand (run_in_background, never a shell &).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:12:18 -07:00
8def189a17 ask-forum: human-in-the-loop question flow via #ct-forum
Add ask-forum.sh + /ask-forum command: a live Claude session posts a
question to the private #ct-forum forum and blocks (server-side, one tool
call) until a human replies, then acts on the answer. Same session throughout
- a three-way between the user, the session, and the teammate. No DMs, no
second bot. Bumps the discord-bot submodule to the matching #ct-forum guard.

Approved by Mike via #ct-forum (a-go, forum-only, /ask-forum yes), 2026-07-08.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:27:45 -07:00
29725a3851 wiki: IMC MOO location address (6300 E El Dorado Cir) 2026-07-07 17:58:43 -07:00
81398663ac wiki: IMC two-location split (Main + MOO sites, agent placement, SC marking) 2026-07-07 17:53:42 -07:00
2fd646218d sync: auto-sync from HOWARD-HOME at 2026-07-07 17:33:19
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 17:33:19
2026-07-07 17:33:56 -07:00
Winter Williams
30c20df2b9 sync: auto-sync from GURU-BEAST-ROG at 2026-07-07 14:18:48
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-07 14:18:48
2026-07-07 14:19:54 -07:00
5edab36df6 sync: auto-sync from GURU-BEAST-ROG at 2026-07-07 14:11:51
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-07 14:11:51
2026-07-07 14:13:04 -07:00
1952c36675 wiki: compile gururmm (update) — Feature 6 security-detection-alerts spec'd
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:02:10 -07:00
8b016edb9d sync: auto-sync from HOWARD-HOME at 2026-07-07 13:55:03
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 13:55:03
2026-07-07 13:55:32 -07:00
5dfaaaa33b sync: auto-sync from HOWARD-HOME at 2026-07-07 12:47:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 12:47:44
2026-07-07 12:48:10 -07:00
2259f4c172 sync: auto-sync from HOWARD-HOME at 2026-07-07 11:48:31
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 11:48:31
2026-07-07 11:49:07 -07:00
a507678254 sync: auto-sync from HOWARD-HOME at 2026-07-07 11:45:40
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 11:45:40
2026-07-07 11:46:46 -07:00
Winter Williams
1e1bae772d sync: auto-sync from GURU-BEAST-ROG at 2026-07-07 11:44:27
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-07 11:44:27
2026-07-07 11:45:34 -07:00
3d28f9ea5d sync: auto-sync from HOWARD-HOME at 2026-07-07 11:15:40
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 11:15:40
2026-07-07 11:16:14 -07:00
Winter Williams
a0a86742bc sync: auto-sync from GURU-BEAST-ROG at 2026-07-07 11:01:13
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-07 11:01:13
2026-07-07 11:02:25 -07:00
27595d6475 sync: auto-sync from HOWARD-HOME at 2026-07-07 11:01:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 11:01:18
2026-07-07 11:01:49 -07:00
766c7fa54d sync: auto-sync from GURU-5070 at 2026-07-07 10:26:45
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-07 10:26:45
2026-07-07 10:27:22 -07:00
ff6546bee8 wiki: compile gps-rmm-audit (full) — Phase 4 backup verification, master-status list, 20 client wikis, onboardings, onsite installer kit 2026-07-07 10:15:38 -07:00
33b6140236 sync: auto-sync from HOWARD-HOME at 2026-07-07 10:07:55
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 10:07:55
2026-07-07 10:08:25 -07:00
efca405669 sync: auto-sync from HOWARD-HOME at 2026-07-07 09:08:42
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 09:08:42
2026-07-07 09:09:11 -07:00
f30413cffc sync: auto-sync from HOWARD-HOME at 2026-07-07 09:02:48
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 09:02:48
2026-07-07 09:03:16 -07:00
5d53452c6d sync: auto-sync from HOWARD-HOME at 2026-07-07 08:58:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 08:58:14
2026-07-07 08:58:46 -07:00
03d02128d7 sync: auto-sync from HOWARD-HOME at 2026-07-07 08:51:03
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 08:51:03
2026-07-07 08:51:30 -07:00
2e262c619e sync: auto-sync from HOWARD-HOME at 2026-07-07 08:48:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 08:48:12
2026-07-07 08:48:40 -07:00
8efc351e25 sync: auto-sync from HOWARD-HOME at 2026-07-07 08:22:41
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 08:22:41
2026-07-07 08:23:08 -07:00
ead30520e5 sync: auto-sync from HOWARD-HOME at 2026-07-07 07:23:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 07:23:12
2026-07-07 07:23:39 -07:00
0daf06263f sync: auto-sync from HOWARD-HOME at 2026-07-07 07:13:10
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 07:13:10
2026-07-07 07:13:39 -07:00
8854a584f3 sync: auto-sync from HOWARD-HOME at 2026-07-07 06:53:34
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 06:53:34
2026-07-07 06:54:02 -07:00
5968ee9231 sync: auto-sync from HOWARD-HOME at 2026-07-07 06:47:06
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 06:47:06
2026-07-07 06:48:26 -07:00
da2aa60990 sync: auto-sync from GURU-BEAST-ROG at 2026-07-07 06:38:06
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-07 06:38:06
2026-07-07 06:41:00 -07:00
b9635cafd9 wiki: compile gururmm (update) — ContextTree collapse-during-search fix
Fold today's dashboard bug fix (commit 5bf7896) into the GuruRMM article as a
dated Recent Work entry + two sources. Incremental update, not a full rebuild:
the article was already fully compiled today (last_compiled 2026-07-06) and the
delta is a single minor UI fix, so a Sonnet re-synthesis was unwarranted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:05:44 -07:00
45e1072dcb sync: auto-sync from HOWARD-HOME at 2026-07-06 17:39:50
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 17:39:50
2026-07-06 17:40:18 -07:00
7264840da6 sync: auto-sync from HOWARD-HOME at 2026-07-06 16:51:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 16:51:24
2026-07-06 16:51:53 -07:00
43101f4597 sync: auto-sync from HOWARD-HOME at 2026-07-06 16:48:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 16:48:18
2026-07-06 16:48:45 -07:00
e790d7c7e8 wiki: compile goldstein (seed) 2026-07-06 16:43:49 -07:00
12c3d83f75 sync: auto-sync from HOWARD-HOME at 2026-07-06 16:41:20
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 16:41:20
2026-07-06 16:41:51 -07:00
Winter Williams
419de64af5 sync: auto-sync from GURU-BEAST-ROG at 2026-07-06 16:23:09
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-06 16:23:09
2026-07-06 16:24:18 -07:00
ba2403b798 sync: auto-sync from GURU-BEAST-ROG at 2026-07-06 16:14:04
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-06 16:14:04
2026-07-06 16:15:18 -07:00
7dc519827c sync: auto-sync from HOWARD-HOME at 2026-07-06 16:10:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 16:10:24
2026-07-06 16:10:52 -07:00
8f4984c5a6 sync: auto-sync from HOWARD-HOME at 2026-07-06 15:56:41
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 15:56:41
2026-07-06 15:57:08 -07:00
0201e7422c sync: auto-sync from HOWARD-HOME at 2026-07-06 15:49:07
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 15:49:07
2026-07-06 15:49:33 -07:00
0b11a400ed sync: auto-sync from HOWARD-HOME at 2026-07-06 15:48:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 15:48:08
2026-07-06 15:48:36 -07:00
4b535a1a81 sync: auto-sync from HOWARD-HOME at 2026-07-06 15:10:00
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 15:10:00
2026-07-06 15:10:27 -07:00
c9ea7e8f01 wiki: client Wi-Fi inventory structure (vault-backed) + memory; ignore .ocu scratch
- wiki/reference/client-wifi.md: readable index (no passwords), /wiki-compile-exempt
- reference_client_wifi_inventory.md: convention (vault clients/<slug>/wifi.sops.yaml)
- wiki/index.md: new Reference section
- .gitignore: .ocu.* scratch artifacts
2026-07-06 13:47:22 -07:00
27dc83f927 sync: auto-sync from HOWARD-HOME at 2026-07-06 13:46:28
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 13:46:28
2026-07-06 13:46:58 -07:00
b10f527cda sync: auto-sync from HOWARD-HOME at 2026-07-06 13:44:28
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 13:44:28
2026-07-06 13:46:04 -07:00
bd247191e6 sync: auto-sync from HOWARD-HOME at 2026-07-06 13:43:09
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 13:43:09
2026-07-06 13:46:03 -07:00
b20985d241 sync: auto-sync from GURU-5070 at 2026-07-06 13:43:08
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-06 13:43:08
2026-07-06 13:43:38 -07:00
3cc9b22594 sync: auto-sync from GURU-5070 at 2026-07-06 13:11:54
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-06 13:11:54
2026-07-06 13:12:28 -07:00
49d5a535a4 wiki(index): refresh lonestar summary (0 hrs, Unraid VM-loss root cause + live IP)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:48:39 -07:00
37a6598ef7 wiki: compile lonestar-electrical (full) — Unraid VM-loss root cause (/boot/config/go hijack), IP fix 172.16.1.188->192.168.120.177, hours 0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:47:20 -07:00
a2a9356a02 sync: auto-sync from GURU-5070 at 2026-07-06 12:39:35
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-06 12:39:35
2026-07-06 12:39:59 -07:00
c3ad79d9fe promotion-candidates: zip GURU-5070 .claude/tmp scratch (21) for Beast review
21 graduation candidates from GURU-5070's local .claude/tmp/ (gitignored/per-machine)
zipped for GURU-BEAST-ROG to review + graduate keepers per TEMP_GRADUATION.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:05:41 -07:00
dfc237e6d4 sync: auto-sync from GURU-5070 at 2026-07-06 11:01:42
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-06 11:01:42
2026-07-06 11:02:17 -07:00
14ac542a00 sync: auto-sync from GURU-BEAST-ROG at 2026-07-06 10:55:23
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-06 10:55:23
2026-07-06 10:58:07 -07:00
c1706393b9 sync: auto-sync from HOWARD-HOME at 2026-07-06 09:41:46
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 09:41:46
2026-07-06 09:42:14 -07:00
e08f6c534e sync: auto-sync from HOWARD-HOME at 2026-07-06 08:31:47
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 08:31:47
2026-07-06 08:32:15 -07:00
f88c7d1739 tailscale skill (admin add/delete) + verified Windows edition-upgrade procedure
- New `tailscale` skill + `/tailscale` command: Tailscale API v2 admin ops
  (devices/keys list, delete-device, authorize, create-key, delete-key).
  Vault-first auth (OAuth preferred, token fallback), every write gated
  --confirm, name->id resolution stops on ambiguous match, [TAILSCALE] bot
  alerts. Live add/delete verification pending an API credential.
- reference_windows_edition_upgrade_rmm memory: verified Home->Pro/PfW path
  (changepk not DISM; Restart-Computer not shutdown /t; PfW-MAK auto-upgrade)
  from the ACG-Tech03L upgrade.
- errorlog: quote-stripping + shutdown-drop friction, PfW-MAK label correction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 08:30:22 -07:00
d5f7eadf82 sync: auto-sync from HOWARD-HOME at 2026-07-06 08:29:31
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-06 08:29:31
2026-07-06 08:30:01 -07:00
c289ba3370 sync: auto-sync from ACG-TECH03L at 2026-07-05 23:45:36
Author: Howard Enos
Machine: ACG-TECH03L
Timestamp: 2026-07-05 23:45:36
2026-07-05 23:46:12 -07:00
60b307316c sync: auto-sync from ACG-TECH03L at 2026-07-05 23:22:43
Author: Howard Enos
Machine: ACG-TECH03L
Timestamp: 2026-07-05 23:22:43
2026-07-05 23:23:21 -07:00
355c5f1e3f wiki: compile gururmm (fold policy-ui Fleet-Policy Console + WS-looper comms-durability; agent 0.6.79 beta / server 0.3.103, mig 068) 2026-07-05 23:13:26 -07:00
d70cdd88e4 submodule: advance guru-rmm -> 7058195 (session log: WS-looper comms-durability) 2026-07-05 21:26:14 -07:00
faf796386a sync: auto-sync from HOWARD-HOME at 2026-07-05 21:24:32
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 21:24:32
2026-07-05 21:25:01 -07:00
8b08ec64c2 seafile: route provisioning/admin writes to the skill in SKILL_ROUTING
Add a doing-skill line for Seafile (SeaCloud) admin writes -- create/
deactivate/delete account, set quota, create/transfer/delete library,
share/unshare -- pointing at the `seafile` skill (preview by default,
--confirm to apply). Complements the existing read/inventory line.

The write-ops implementation itself (seafile.py subcommands, client
_write via shared http_json form=, quota decimal-GB round-trip, gated
--confirm, SKILL.md docs) landed earlier via auto-sync in a8e76ba.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:54:57 -07:00
da6567ffe0 owncloud: extend skill from read-only audit to full occ management
Layer gated admin writes onto the read-only GPS-audit skill, driven through
occ over SSH (same paramiko/vault/filecache plumbing):

- user lifecycle (add/delete/enable/disable/reset-password/modify), quota
  get/set/reset, groups (create/delete/members), read-only share listing
  (from oc_share; occ has no share create/list), apps, system config,
  maintenance mode, files scan, ownership transfer, trashbin/versions cleanup.
- All writes gated behind --confirm (refuse -> rc 3); hard-fail guards (rc 2)
  for invalid uid, missing password, and all-users cleanup with no target.
- --all-users trashbin/versions purge now ALSO requires --force-all-users in
  addition to --confirm, so a single stray --confirm cannot trigger an
  irreversible fleet-wide purge.

Built against the verified live occ surface (occ list / occ help), not guessed
flags. Fixes from code-review + security-review: Python-side share filtering
(no MySQL SQL-injection via '' -doubling), occ reads no longer mask a failed
read as healthy (maintenance/quota/config), guarded json.loads, clean --json
output, and '--' end-of-options guards so dash-leading uids can't be parsed as
occ flags. dry_test.sh added as a non-destructive regression harness (43 checks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:52:25 -07:00
3d8aa2be59 submodule: advance guru-rmm -> c81eeb5 (WS-looper comms-durability: write-timeout + heartbeat-count diagnostics + AIMD keepalive; agent 0.6.79 beta, server 0.3.102 deployed; BUG-024 linux 0.6.79 re-quarantined)
Merge 07307e3 (fix/ws-looper-writetimeout-hbdiag-aimd) + CI version bump c81eeb5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:29:44 -07:00
a8e76ba215 sync: auto-sync from HOWARD-HOME at 2026-07-05 20:23:39
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 20:23:39
2026-07-05 20:24:08 -07:00
976bfe7957 sync: auto-sync from HOWARD-HOME at 2026-07-05 19:59:52
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 19:59:52
2026-07-05 20:00:20 -07:00
b0ebd44c9d sync: auto-sync from HOWARD-HOME at 2026-07-05 19:34:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 19:34:16
2026-07-05 19:34:44 -07:00
f274eed8fa dataforth-dos: advance pointer to #32489 toolchain + telemetry commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:39:02 -07:00
5e682c9293 sync: auto-sync from GURU-5070 at 2026-07-05 16:35:57
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-05 16:35:57
2026-07-05 16:36:27 -07:00
bee140c020 sync: auto-sync from GURU-5070 at 2026-07-05 16:34:34
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-05 16:34:34
2026-07-05 16:35:02 -07:00
a737c4ab24 wiki: compile gururmm (full) — fold 07-04/05 batches (v0.3.97-99, agent 0.6.78), policy-core deployed (mig 067), BUG-003 closed, BUG-023 fixed, BUG-024 linux glibc OPEN/quarantined
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 15:42:55 -07:00
fa4867580f sync: auto-sync from HOWARD-HOME at 2026-07-05 15:29:59
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 15:29:59
2026-07-05 15:30:28 -07:00
a5b8f6e7b1 sync: auto-sync from HOWARD-HOME at 2026-07-05 13:42:15
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 13:42:15
2026-07-05 13:43:35 -07:00
6ad608c3bf rmm: bump guru-rmm -> policy-core deployed to prod (migration 067)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 13:08:47 -07:00
ea84b7bcd2 submodule: advance guru-rmm -> 120854a (v0.3.99 batch-4: command TTL + status reconciler + update supersede; BUG-024 linux glibc incident quarantined + ix/Jupiter revived; orphan dupes purged)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:58:54 -07:00
b835f350d6 submodule: advance guru-rmm (batch-3 session log)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:57:25 -07:00
5e1ebf4789 sync: auto-sync from HOWARD-HOME at 2026-07-05 09:56:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 09:56:12
2026-07-05 09:56:42 -07:00
656101a7b4 submodule: advance guru-rmm -> d85d6be (v0.3.98: visible_agents view+trigger, --migrate-only deploy gate, BUG-003 pipeline hardening live, BUG-023 channel-race fixed, 0.6.78 promoted stable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:48:43 -07:00
7c0f467cdf rmm: bump guru-rmm -> 4b0a28b (policy-core shape spec)
docs/specs/policy-core/ — folders+inheritance gate spec for the policy redesign (#7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 07:52:50 -07:00
26b1cf400f sync: auto-sync from HOWARD-HOME at 2026-07-05 01:00:55
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 01:00:55
2026-07-05 01:01:23 -07:00
e359702837 gps-rmm-audit: CP-QB WMI-repaired+enrolled (112/189); IMC-PRINTSERVER agent installed, blocked by box-wide outbound TLS drop
CP-QB: corrupt WMI class store (Win32_Processor invalid class) killed the universal
installer's arch check; mofcomp cimwin32.mof fixed it; enrolled + reassigned to
Curtis Plumbing. IMC-PRINTSERVER: agent+watchdog installed manually over LAN HTTP
from IMC1 (PowerShell dead on box, SMB hangs, SC extension eats chains/multiline);
box drops ALL outbound TLS (incl google) so agent cannot connect — fix is at IMC
UniFi gateway or local filter; no reinstall needed. Session log + tracker updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 01:00:48 -07:00
67822d5ca8 sync: auto-sync from HOWARD-HOME at 2026-07-05 00:45:20
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 00:45:20
2026-07-05 00:45:47 -07:00
346832b469 sync: auto-sync from HOWARD-HOME at 2026-07-05 00:23:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 00:23:45
2026-07-05 00:24:15 -07:00
a7174a894e sync: auto-sync from HOWARD-HOME at 2026-07-04 23:42:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 23:42:44
2026-07-04 23:43:13 -07:00
3600df76cf wiki: compile gururmm (full rebuild — v0.3.96, easy-bugs batch, dedup, legacy disable, VSS/tray/integrations)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:42:31 -07:00
9efa6eaf4f sync: auto-sync from HOWARD-HOME at 2026-07-04 23:29:28
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 23:29:28
2026-07-04 23:29:57 -07:00
9542de2b0a sync: auto-sync from HOWARD-HOME at 2026-07-04 23:15:20
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 23:15:20
2026-07-04 23:15:49 -07:00
a0998fa255 sync: auto-sync from HOWARD-HOME at 2026-07-04 22:38:31
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 22:38:31
2026-07-04 22:39:06 -07:00
c8036d7e67 sync: auto-sync from HOWARD-HOME at 2026-07-04 20:47:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 20:47:45
2026-07-04 20:48:11 -07:00
567fd8dd8c sync: auto-sync from HOWARD-HOME at 2026-07-04 20:44:56
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 20:44:56
2026-07-04 20:45:25 -07:00
66211a5652 sync: auto-sync from HOWARD-HOME at 2026-07-04 19:35:27
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 19:35:27
2026-07-04 19:35:53 -07:00
46705d48ff sync: auto-sync from HOWARD-HOME at 2026-07-04 19:34:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 19:34:24
2026-07-04 19:34:54 -07:00
e6dae3dba2 sync: auto-sync from HOWARD-HOME at 2026-07-04 19:33:17
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 19:33:17
2026-07-04 19:33:49 -07:00
46d4900229 sync: auto-sync from HOWARD-HOME at 2026-07-04 19:27:24
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 19:27:24
2026-07-04 19:27:52 -07:00
65b6043cde gps-rmm-audit: JANC (2248945) != Janet Altschuler (457710) — confirmed separate clients
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:18:11 -07:00
7a6fbcfc29 wiki: compile gps-rmm-audit (seed) — GPS->GuruRMM coverage audit project article
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:53:54 -07:00
371fce1104 sync: auto-sync from HOWARD-HOME at 2026-07-04 18:44:32
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 18:44:32
2026-07-04 18:45:07 -07:00
42d43a5bcc sync: auto-sync from HOWARD-HOME at 2026-07-04 18:39:34
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 18:39:34
2026-07-04 18:40:02 -07:00
a270f405a6 guru-rmm: align submodule pointer to deployed main (c4f24de, dedup fix live)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:38:06 -07:00
a6a6a477d5 sync: auto-sync from HOWARD-HOME at 2026-07-04 18:34:08
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 18:34:08
2026-07-04 18:34:36 -07:00
b8bba3cd8f sync: auto-sync from HOWARD-HOME at 2026-07-04 18:08:15
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 18:08:15
2026-07-04 18:08:41 -07:00
011ee7350d sync: auto-sync from HOWARD-HOME at 2026-07-04 18:00:11
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 18:00:11
2026-07-04 18:00:38 -07:00
21b198f1ee sync: auto-sync from HOWARD-HOME at 2026-07-04 17:49:31
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 17:49:31
2026-07-04 17:49:58 -07:00
6d36f7151c sync: auto-sync from HOWARD-HOME at 2026-07-04 17:09:47
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 17:09:47
2026-07-04 17:10:14 -07:00
edd4bd39e7 sync: auto-sync from HOWARD-HOME at 2026-07-04 17:05:53
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 17:05:53
2026-07-04 17:06:23 -07:00
ddd6bb06c2 sync: auto-sync from GURU-5070 at 2026-07-04 16:52:21
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-04 16:52:21
2026-07-04 16:52:59 -07:00
b97223f69e sync: auto-sync from HOWARD-HOME at 2026-07-04 16:36:49
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 16:36:49
2026-07-04 16:37:15 -07:00
f64418d5e1 sync: auto-sync from HOWARD-HOME at 2026-07-04 16:34:34
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 16:34:34
2026-07-04 16:34:59 -07:00
2aed9d93c9 sync: auto-sync from HOWARD-HOME at 2026-07-04 16:20:30
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 16:20:30
2026-07-04 16:20:55 -07:00
cb99daa6cb sync: auto-sync from HOWARD-HOME at 2026-07-04 16:09:13
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 16:09:13
2026-07-04 16:09:41 -07:00
d7bc2b34ac guru-rmm: bump submodule — dedup reuses record without re-homing (moves persist)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 15:38:12 -07:00
734a7b201d guru-rmm: bump submodule — cross-site agent dedup fix
Points to cdc87d2 (server: fix cross-site agent duplication on re-enrollment).
Needs build + deploy by Mike/Howard before the dedup deletes are run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 15:31:59 -07:00
d4a1bbbc5b sync: auto-sync from HOWARD-HOME at 2026-07-04 15:30:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 15:30:44
2026-07-04 15:31:14 -07:00
a733db1029 sync: auto-sync from HOWARD-HOME at 2026-07-04 14:18:41
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 14:18:41
2026-07-04 14:19:11 -07:00
d5020a6415 sync: auto-sync from GURU-5070 at 2026-07-04 13:52:09
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-04 13:52:09
2026-07-04 13:52:35 -07:00
0da31cbb65 memory: GuruRMM legacy 1.77 build disabled + advance guru-rmm gitlink (tray feature deployed, legacy frozen at 0.6.76)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:30:06 -07:00
ff7025d912 sync: auto-sync from GURU-5070 at 2026-07-04 13:07:09
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-04 13:07:09
2026-07-04 13:10:12 -07:00
ad6a529ba4 sync: auto-sync from HOWARD-HOME at 2026-07-04 12:42:50
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 12:42:50
2026-07-04 12:43:27 -07:00
60a0a5728f sync: auto-sync from GURU-5070 at 2026-07-04 12:23:18
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-04 12:23:18
2026-07-04 12:27:48 -07:00
10b1ea7528 sync: auto-sync from GURU-5070 at 2026-07-04 07:30:32
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-04 07:30:32
2026-07-04 12:26:42 -07:00
85c8149495 sync: auto-sync from HOWARD-HOME at 2026-07-04 12:00:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 12:00:16
2026-07-04 12:00:44 -07:00
a8ed995979 sync: auto-sync from HOWARD-HOME at 2026-07-04 11:47:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 11:47:14
2026-07-04 11:47:40 -07:00
ac05230cd2 sync: auto-sync from HOWARD-HOME at 2026-07-04 10:45:23
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 10:45:23
2026-07-04 10:45:49 -07:00
a1f0a3e5e8 sync: auto-sync from HOWARD-HOME at 2026-07-04 09:24:45
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 09:24:45
2026-07-04 09:25:14 -07:00
4e739abe5f sync: auto-sync from HOWARD-HOME at 2026-07-04 08:43:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 08:43:14
2026-07-04 08:43:40 -07:00
1ab0e88e67 sync: auto-sync from HOWARD-HOME at 2026-07-04 08:35:27
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 08:35:27
2026-07-04 08:35:53 -07:00
0ca8e384d8 sync: auto-sync from HOWARD-HOME at 2026-07-04 08:25:49
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-04 08:25:49
2026-07-04 08:26:19 -07:00
fad05480a5 sync: auto-sync from HOWARD-HOME at 2026-07-03 23:31:28
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 23:31:28
2026-07-03 23:31:53 -07:00
e71a486db2 sync: auto-sync from HOWARD-HOME at 2026-07-03 23:02:54
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 23:02:54
2026-07-03 23:03:20 -07:00
2e167c97fa sync: auto-sync from HOWARD-HOME at 2026-07-03 22:36:52
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 22:36:52
2026-07-03 22:37:24 -07:00
e2d902a337 sync: auto-sync from HOWARD-HOME at 2026-07-03 22:01:21
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 22:01:21
2026-07-03 22:01:53 -07:00
c18e74ed1c sync: auto-sync from HOWARD-HOME at 2026-07-03 21:44:00
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 21:44:00
2026-07-03 21:44:28 -07:00
fceb7c6075 memory + rmm: agy-headless correction, gemini errorlog, bump guru-rmm (Syncro recon + spec plan)
- memory: agy.exe NOW works headless (v1.0.6+ --print/-p, --add-dir) — corrected
  reference_antigravity_agy_not_headless + MEMORY.md index; supersedes the old "DOA headless" note.
- errorlog: gemini-cli OAuth eligibility failure on this machine (agy/ask-gemini).
- bump projects/msp-tools/guru-rmm -> a7ecb9f (RMM_THOUGHTS Feature 11 OS Updates + RMM Fixes batch;
  docs/recon + docs/specs: Syncro policy/device recon + policy-redesign spec plan).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:24:36 -07:00
442f3cb1c7 sync: auto-sync from HOWARD-HOME at 2026-07-03 20:52:38
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 20:52:38
2026-07-03 20:53:05 -07:00
f61088dac4 sync: auto-sync from HOWARD-HOME at 2026-07-03 19:57:06
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 19:57:06
2026-07-03 19:57:32 -07:00
63e1eb743b sync: auto-sync from HOWARD-HOME at 2026-07-03 19:30:59
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 19:30:59
2026-07-03 19:31:29 -07:00
c82c1c76bb sync: auto-sync from HOWARD-HOME at 2026-07-03 17:22:21
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 17:22:21
2026-07-03 17:22:47 -07:00
e0e3dd0d82 sync: auto-sync from HOWARD-HOME at 2026-07-03 17:00:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 17:00:12
2026-07-03 17:01:38 -07:00
78f794a924 sync: auto-sync from GURU-5070 at 2026-07-03 13:18:27
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-03 13:18:27
2026-07-03 13:19:22 -07:00
41c12a934f agy: rewire skill from dead gemini npm CLI to Antigravity 'agy' binary
The old Google gemini CLI stopped working on this account (throwIneligibleOrProjectIdError -
needs a GOOGLE_CLOUD_PROJECT the personal account can't supply). Replace it with the
Antigravity CLI (agy, native Go binary, own auth, no project ID).

- New scripts/ask-agy.sh: plain-text output (no JSON), -p prompt-last, --model friendly
  names (Gemini 3.1 Pro (High) default for verify/review*/vision/search), --add-dir for
  file/vision reads, --dangerously-skip-permissions to avoid print-mode approval hangs.
  All modes smoke-tested (text/verify/review/search).
- scripts/ask-gemini.sh: deprecated shim -> exec ask-agy.sh (back-compat).
- SKILL.md: rewired header/flags/models/auth/availability/reference to agy.
- ask-grok.sh: xsearch fallback now execs ask-agy.sh (was the dead gemini).
- identity.json (local, gitignored): agy block added, gemini marked retired.

Authoritative flags sourced from 'agy --help' (web docs are JS SPAs, unreadable by
fetch tools; grok fetch/search timed out).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:19:21 -07:00
727a0757f6 wiki: compile peaceful-spirit (full) — Syncro refresh, VSS + address, root-log provenance
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 12:30:08 -07:00
6cb90c5b09 sync: auto-sync from HOWARD-HOME at 2026-07-03 09:25:34
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-03 09:25:34
2026-07-03 09:26:05 -07:00
8ecb406571 sync: auto-sync from GURU-BEAST-ROG at 2026-07-03 06:23:58
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-03 06:23:58
2026-07-03 06:25:13 -07:00
5e4b2cf385 sync: auto-sync from GURU-5070 at 2026-07-02 19:14:01
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 19:14:01
2026-07-02 19:14:50 -07:00
d745c7d40f sync: auto-sync from HOWARD-HOME at 2026-07-02 18:33:52
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 18:33:52
2026-07-02 18:35:56 -07:00
7300f57a47 sync: auto-sync from HOWARD-HOME at 2026-07-02 18:32:04
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 18:32:04
2026-07-02 18:33:52 -07:00
101b95e610 sync: auto-sync from GURU-5070 at 2026-07-02 18:25:06
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 18:25:06
2026-07-02 18:25:55 -07:00
27b9966dfa sync: auto-sync from GURU-5070 at 2026-07-02 17:44:44
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 17:44:44
2026-07-02 17:45:34 -07:00
c54ca5ec22 sync: auto-sync from GURU-5070 at 2026-07-02 17:34:37
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 17:34:37
2026-07-02 17:35:26 -07:00
75f60df6a6 sync: auto-sync from GURU-5070 at 2026-07-02 17:30:07
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 17:30:07
2026-07-02 17:30:57 -07:00
59b5f1f5f2 wiki: update PST (deletion-report location) + add fast wiki-compile 'update' mode
- peaceful-spirit: record the standing 'Mara audit log' (daily PST Deletion Report task,
  SACL 4660/4663 on G:\Shares\Scanned) and its new output location under the legal/
  partner-review folder (moved 2026-07-02). Surgical update, no full recompile.
- wiki-compile: add an incremental UPDATE mode (now the no-flag default) that folds only
  session logs newer than last_compiled via targeted section edits — no Sonnet subagent,
  no full-article regeneration. --full is now the explicit REBUILD; --syncro is the
  instant Syncro-only refresh. Addresses the slow-rebuild complaint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:27:23 -07:00
7ff092f7bb sync: auto-sync from GURU-5070 at 2026-07-02 16:01:04
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 16:01:04
2026-07-02 16:01:59 -07:00
b01105d1f9 cascades: caretaker roster + phone-login remediation updates
- Document forced-change-at-logon gotcha + fleet-wide fix (shared-phone err 50126)
- Record PSO-Caregivers (never-expire FGPP on SG-Caregivers)
- Log hard-delete of 7 offboarded leavers; Juan Andrade offboard scheduled 2026-07-11

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:23:20 -07:00
8152476ee4 remediation-tool: document the 365 app suite + build consent-audit
Root-caused the recurring '365 suite isn't documented' pain: the apps are fine (tiered by
privilege) but per-tenant consent is NOT uniform and there was no way to see a tenant's
actual grant state. VWP had the Tenant Admin app but no SharePoint app-only role -> silent
401s until this session.

- references/app-suite.md: authoritative, live-verified map of every app, App ID, and
  actually-granted permission per tier; the consent-drift problem + both fix methods
  (adminconsent URL, direct appRoleAssignment grant).
- scripts/consent-audit.sh: audits a tenant (or --all) vs the baseline, grades
  GREEN/AMBER/RED, prints the exact fix per gap. Extends the assign-exchange-role --verify
  pattern to Graph scopes + SharePoint role + EXO role. Verified: BirthBio GREEN, VWP/Cascades
  AMBER (caught real drift - both missing grants).
- SKILL.md: run consent-audit FIRST on any tenant task. Memory + errorlog correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:15:08 -07:00
42da3cfcca sync: auto-sync from HOWARD-HOME at 2026-07-02 14:48:25
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 14:48:25
2026-07-02 14:49:01 -07:00
5008ad79dc wiki: compile scileppi-law (full) - UniFi layer, Cox WAN RCA, Active Work, rose user 2026-07-02 13:44:54 -07:00
b0cf2a31ba sync: auto-sync from HOWARD-HOME at 2026-07-02 12:05:22
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 12:05:22
2026-07-02 12:05:55 -07:00
552760e7cd sync: auto-sync from HOWARD-HOME at 2026-07-02 11:20:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 11:20:14
2026-07-02 11:20:45 -07:00
ccfa4f7b21 sync: auto-sync from HOWARD-HOME at 2026-07-02 11:16:12
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 11:16:12
2026-07-02 11:16:42 -07:00
Winter Williams
ac23f17e23 sync: auto-sync from GURU-BEAST-ROG at 2026-07-02 10:55:41
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-02 10:55:41
2026-07-02 10:57:33 -07:00
26f47fdd10 sync: auto-sync from HOWARD-HOME at 2026-07-02 09:08:36
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-02 09:08:36
2026-07-02 09:10:02 -07:00
3e6f946377 sync: auto-sync from GURU-5070 at 2026-07-02 06:23:34
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-02 06:23:34
2026-07-02 06:25:18 -07:00
30c7b26af2 sync: auto-sync from GURU-BEAST-ROG at 2026-07-01 20:15:51
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-07-01 20:15:51
2026-07-01 20:16:08 -07:00
1264cbda7b chore: advance discord-bot gitlink to a35bd1bf (default model -> claude-fable-5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 20:08:28 -07:00
b07613c127 sync: auto-sync from GURU-5070 at 2026-07-01 20:07:01
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 20:07:01
2026-07-01 20:07:42 -07:00
2937b00ebf sync: auto-sync from GURU-5070 at 2026-07-01 15:49:56
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 15:49:56
2026-07-01 15:50:54 -07:00
1775571abb wiki: compile cascades-tucson (full) -- 7/1 caretaker roster update (35 in SG-Caregivers) + phone-login CA cutover integrated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 15:47:03 -07:00
282c4af8cc sync: auto-sync from HOWARD-HOME at 2026-07-01 15:12:14
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-01 15:12:14
2026-07-01 15:12:48 -07:00
c3aeef60fb sync: auto-sync from GURU-5070 at 2026-07-01 15:06:42
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 15:06:42
2026-07-01 15:07:39 -07:00
875048f9ad sync: auto-sync from HOWARD-HOME at 2026-07-01 14:43:57
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-01 14:43:57
2026-07-01 14:44:29 -07:00
142afd7e98 sync: auto-sync from HOWARD-HOME at 2026-07-01 13:50:18
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-01 13:50:18
2026-07-01 13:50:49 -07:00
486b72ec71 wiki: compile cascades-tucson (full) — VLAN 20 migration live-reconciled (22 machines moved, printer shares 4/15), ALIS SSO login model, Syncro 37.5h/0 tickets/29 assets 2026-07-01 13:32:24 -07:00
9e78a153f3 sync: auto-sync from HOWARD-HOME at 2026-07-01 13:22:23
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-01 13:22:23
2026-07-01 13:24:58 -07:00
7f897ce93f sync: auto-sync from GURU-5070 at 2026-07-01 13:09:08
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 13:09:08
2026-07-01 13:10:01 -07:00
af8a3de00e sync: auto-sync from GURU-5070 at 2026-07-01 13:06:10
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 13:06:10
2026-07-01 13:07:50 -07:00
29355584bf wiki: compile scileppi-law (full) -- NAS/mount root-cause, no-sleep fix, ScreenConnect, corrected billing + Mac identity 2026-07-01 12:28:32 -07:00
e527b89999 sync: auto-sync from HOWARD-HOME at 2026-07-01 11:39:58
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-01 11:39:58
2026-07-01 11:41:17 -07:00
6f7f939a62 sync: auto-sync from GURU-5070 at 2026-07-01 09:32:17
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 09:32:17
2026-07-01 09:33:09 -07:00
e583bf43a5 wiki: compile peaceful-spirit (full) — two-DC DFS, deletion investigation, Admin1/Admin2 ACL hardening 2026-07-01 09:09:40 -07:00
02845878d6 wiki: compile birth-biologic (full) — Datto->SharePoint migration complete, Quality sync done, MX cut, #32187 rename scheduled 2026-07-01 09:08:57 -07:00
6f676672a8 sync: auto-sync from GURU-5070 at 2026-07-01 08:54:46
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 08:54:46
2026-07-01 08:55:39 -07:00
6251044baf sync: auto-sync from GURU-5070 at 2026-07-01 05:15:39
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 05:15:39
2026-07-01 05:16:24 -07:00
9951e88f69 wiki: compile cascades-tucson (full) -- caregiver phone SSO licensing + billing 37.5h + 6/25-6/30 work 2026-06-30 17:43:11 -07:00
be9574a480 sync: auto-sync from HOWARD-HOME at 2026-06-30 17:28:16
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-30 17:28:16
2026-06-30 17:30:25 -07:00
ab640dfe77 sync: auto-sync from HOWARD-HOME at 2026-06-30 17:28:00
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-30 17:28:00
2026-06-30 17:28:36 -07:00
01613697c6 sync: auto-sync from GURU-5070 at 2026-06-30 17:21:06
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-30 17:21:06
2026-06-30 17:21:47 -07:00
1b0b313896 Birth Biologic: Merge + save Quality sync continuation state 2026-06-30 15:28:44 -07:00
152513b15d Birth Biologic: Save Quality sync state + working upload script
- Current state: 3,249/3,768 files uploaded, 519 remaining
- Active RMM command: 9e0fcfe8 (running on ACG-DWP-X-BB)
- Working upload script with drive ID concatenation fix
- Comprehensive continuation instructions
- All verification scripts

Client very angry - this was promised yesterday
Issue: PowerShell escaping ! in drive ID (b! -> b\!)
Solution: String concatenation at runtime
2026-06-30 15:27:43 -07:00
510 changed files with 199778 additions and 2591 deletions

1
.agents.json Normal file

File diff suppressed because one or more lines are too long

36
.analyze-glaztech.py Normal file
View File

@@ -0,0 +1,36 @@
import json
from collections import defaultdict
with open('/Users/azcomputerguru/ClaudeTools/.glaztech-sessions.json') as f:
data = json.load(f)
print(f'Total machines: {len(data)}')
# Extract IPs and current sites
subnet_info = defaultdict(lambda: {'count': 0, 'machines': [], 'current_sites': set()})
for session in data:
ip = session.get('GuestInfo', {}).get('PrivateNetworkAddress', '')
name = session.get('Name', 'Unknown')
current_site = session.get('CustomProperties', {}).get('CustomProperty2', '')
if ip and ip != '':
# Extract /24 subnet
subnet = '.'.join(ip.split('.')[:3]) + '.0/24'
subnet_info[subnet]['count'] += 1
subnet_info[subnet]['machines'].append({'name': name, 'ip': ip})
if current_site:
subnet_info[subnet]['current_sites'].add(current_site)
# Sort by count descending
sorted_subnets = sorted(subnet_info.items(), key=lambda x: x[1]['count'], reverse=True)
print('\n=== Subnet Distribution ===')
for subnet, info in sorted_subnets:
sites_str = ', '.join(sorted(info['current_sites'])) if info['current_sites'] else 'No site tag'
print(f'{subnet:20s} {info["count"]:3d} machines Current tags: {sites_str}')
# Show first 3 machines as examples
for m in info['machines'][:3]:
print(f' - {m["name"]:30s} {m["ip"]}')
if len(info['machines']) > 3:
print(f' ... and {len(info["machines"]) - 3} more')

346
.audit/safe_to_delete.csv Normal file
View File

@@ -0,0 +1,346 @@
class,company,hostname,endpoint_id,reason,evidence
DECOMMISSIONED,AT Trebesch,AT,5dd6d0e660e456225596d973,managed stale lastSeen 282d | Windows 7 Home Premium,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,ACG-HC,66ad7a8194d8d96dbbd317ff,managed stale lastSeen 706d | Windows Server 2012 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,ACGVDS,675f08a6261f375674d73033,managed stale lastSeen 549d | Windows Server 2012 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,DESKTOP-0O8A1RL,69ce91b1f6eea97ee1016e70,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,DESKTOP-BCRENMO,5d2e7459f3509009dd6b0bc4,unmanaged mtype=1 | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,GURU-BEAST-I9,66ad772128980e6e026398a5,managed stale lastSeen 612d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,GURU-LEGION,60dca8aa04c8e42270fa4604,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,GURU-TEST,5e476ba49d5dfb49a5d227cd,managed stale lastSeen 2200d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,GURUBOX2,5c42aa4287606605f6978872,managed stale lastSeen 2668d | Windows 10 Enterprise,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,LENOVHA,5eb9d18c1b250679c5717336,managed stale lastSeen 1961d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Arizona Computer Guru - Michael Swanson,VIP,5cb95f67b5e538748bb3c708,unmanaged mtype=1 | Windows Server 2016 Datacenter,RMM absent; Datto no; no-live-BD
DUPLICATE,Arizona Computer Guru - Michael Swanson,ACGVDS,66c4d5b00b0f0ee09c418016,dup; keep 675f08a6261f375674d73033 (mgd) | this mgd Windows Server 2012 R2 Standard,RMM absent; Datto no; no-live-BD
DUPLICATE,Arizona Computer Guru - Michael Swanson,ACGVDS,66b15faff22e8a0befe670df,dup; keep 675f08a6261f375674d73033 (mgd) | this mgd Windows Server 2012 R2 Standard,RMM absent; Datto no; no-live-BD
MIGRATED,Arizona Medical Transit - Bertie Lozano,SERVER,5cb9075cf2c564744116f7c7,unmanaged mtype=2 | Windows Server 2008 R2 Enterprise,RMM 0d; Datto yes; BD-live-agent
DECOMMISSIONED,Associated Realty of the Americas - Carl Bosse,LAPTOP-PSIC3D3T,66cb8a029489d746d72c99d0,managed stale lastSeen 645d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Baboquivari District - Idella Stanley,BAQ-DESKTOP0103,66670c297b424e8512864f8e,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Curtis Plumbing - Ken Curtis,CURTIS-003,5f7c92e504b0062a4b09d536,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Curtis Plumbing - Ken Curtis,CURTIS-005-W7,5da75781302bc04bc2d3c082,unmanaged mtype=1 | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DUPLICATE,Curtis Plumbing - Ken Curtis,CURTIS-001,66fab92596da30df7ef0d9b1,dup; keep 5dbc5f7bc6da513b2df125e3 (unmgd) | this unmgd Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Curtis Plumbing - Ken Curtis,CURTIS-001,5dbc5f7bc6da513b2df125e3,unmanaged mtype=1 | Windows 7 Professional,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,David Anaise,DROBO-OLD,5dc5ac37f4c40339b97ccbfe,unmanaged mtype=1 | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBCSR1,66731c113a4c2e615a0628b6,managed stale lastSeen 163d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBCUTTINGTABLE,659b2925ebbb1c432fac2426,managed stale lastSeen 910d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBMAINTENANCE,5fd129b686f0e37c182bc9b6,managed stale lastSeen 93d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBO14,614dcea1290cb4b9145b0432,managed stale lastSeen 1544d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBO7,5dcde10e8cf293433d6dd5cf,managed stale lastSeen 2430d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBPERFECTCUT23,6944188314e9c7f6cb765f0d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALBSVR1,69657cbcf5b8e864c49c0213,unmanaged mtype=1 | Windows Server 2008 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ALHOME,68ddbd34cfc93f575816fcd5,managed stale lastSeen 162d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOICSR1,6665cf997740d8e5de318fdc,managed stale lastSeen 381d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOIFAB,66731c00c7a4f567685c5f1a,managed stale lastSeen 140d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOIFURNANCE,601c5d1c1084082cf07b0fda,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOILEIA,6665cf9a15b4e6468bae5541,managed stale lastSeen 646d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOINTAYLOR,6944187f7e28b2b991431268,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOISESVR,6941b72c6254f011aa26a5b3,unmanaged mtype=1 | Windows Server 2022 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOISHIPPING,601c5d2ce421af6446a70f87,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOISHIPPING1,694418818c399d0cb3038728,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BOITRACER,694418824eb00e080ed15b75,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BRLCSRMORGAN,6944188380301a004e83ee25,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BRLMAINT,6944187e12b7fd777f9d1eb2,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BRLOMROXY,694418836bbd28939f3ede85,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BRLS3,601c5e15a01f6763f2ecf316,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,BRLSHIPPING,6944187f0a7faeceb913bef7,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,CHRISHOME,69fbb7087cc0382fe4551e11,managed stale lastSeen 65d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,CSMITHAR,69441881377e86115ebd7825,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,CUTTINGSANTABLE,69441882d69f3e9d75792826,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,DENS3,5cb783929be88a7320f60ae4,managed stale lastSeen 1355d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,DENWATERJET32,69441881a977c1e2255ed329,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,DESKTOP-CPMCF00,5ceeb1ab4159e84a4021dd3e,managed stale lastSeen 305d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,DESKTOP-GOTG6ET,5cbb49aed1fef636029f48b1,managed stale lastSeen 1411d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPCSR1,66731c16e89e6c61808777a1,managed stale lastSeen 254d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPO10,5cb4fa5a4442de68067d5fde,managed stale lastSeen 1912d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPO7,5cb4fa5a4442de68067d5fe1,managed stale lastSeen 1935d | Windows 7 Home Premium,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPO8,6665cfacd84664e67332f51b,managed stale lastSeen 591d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPS6,5cb4fa5a4442de68067d5fe3,managed stale lastSeen 268d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPS9,6665cf8e6293cbce8b9ee8f5,managed stale lastSeen 267d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPSVR,6665cf2c1490b1ebecbd7e1a,unmanaged mtype=1 | Windows Server 2008 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ELPSVR1,601c62e35c31a26c7e0ef518,managed stale lastSeen 1982d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GLAZ-SEASTMAN,5cb740e71f1b8c2d5fe037d8,managed stale lastSeen 1067d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GLAZLAPTOP,69de9d0886ce7e6e2682d093,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GLAZOFFICE10,60c14365d46a268f48dea372,managed stale lastSeen 1724d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GLAZOFFICE11,5cb90e16ea51de212ed1fc84,managed stale lastSeen 1270d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTI-INV-SQL12,62c3f0b041b01242b7191d3c,managed stale lastSeen 289d | Windows Server 2008 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBALBERT,66731c0a843694782b1223ae,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBANTONIO,6665cf8cbe52690e9ebc8f38,managed stale lastSeen 186d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBCSR1,6944188301e388a90b2c2af1,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBDEIDRA,6256db34133dc52457e3da9d,managed stale lastSeen 177d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBFAB,5cd47bed16f09c67b7faf1b3,managed stale lastSeen 1766d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBIAN,6665cf4dcdcec4b6a85ae3ec,managed stale lastSeen 175d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBO3,5cb3e76b4442de680673fb58,managed stale lastSeen 1736d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBS1,601194f6219d9a5bc2249f42,managed stale lastSeen 462d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBS7,60119506e74d6237ca1af38f,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIALBSIG,66731c11e8dc689013583b22,managed stale lastSeen 149d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBLRS1,601c5e4393b85d4e46af5823,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBLT2,5cb88c0223b35836c23199c0,managed stale lastSeen 1958d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBO10,5cb52cf79be88a7320e4c3b2,managed stale lastSeen 1752d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBO13,6665cf4c696ccfb94f656063,managed stale lastSeen 458d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBO4,5cb52cf79be88a7320e4c3b4,managed stale lastSeen 1780d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBOICSR2,69949c0b1d4e9a8356d29fa6,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBOIJAMIE,6944188310aa6562af960bad,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBOIJEFF,694418838d981de163ebd577,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBOIMAINTENAN,6944188363e2ce57977e4cb3,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBOIPM,694418836a7d56f6c53c743c,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLCSR,6665cfa0b560096618f4de69,managed stale lastSeen 345d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLCSR1,5cbde2de8eb04d361fb2c675,managed stale lastSeen 1375d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLCSR2,6667730cbf64d65ef9b15559,managed stale lastSeen 315d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLFAB,5cbf68c7ea51de212efbd281,managed stale lastSeen 268d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLFAB3,69441883a11bce397c3a8f94,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLKUMASI,68783432a5e3e4122c8f902e,managed stale lastSeen 141d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLLTO2,6666ca7af079fa08a59beb46,managed stale lastSeen 277d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLO7,6665cf9ca888096dc3fa03c6,managed stale lastSeen 571d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLO8,66731c13436606b5dd4e94b8,managed stale lastSeen 473d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLS10,61cdbfa2c946c45788577e91,managed stale lastSeen 1047d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBRLS7,614c90c6931713b3125c0fff,managed stale lastSeen 1751d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIBS5,66ab98a8b474c9b783683e69,managed stale lastSeen 82d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDEN3,5cb783929be88a7320f60ae5,managed stale lastSeen 1424d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENCSR1,6665cf911920e79808854261,managed stale lastSeen 235d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENCSR2,66731c11bf53b9ec4841c88d,managed stale lastSeen 239d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENCSR5,69441882dd21243a43131eb2,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENGM,66731c0d4d99eaeee06e2f72,managed stale lastSeen 220d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENO14,6476636fded92a5875e23511,managed stale lastSeen 192d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENO4,5cb783929be88a7320f60ae6,managed stale lastSeen 1905d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENO7,5cb783929be88a7320f60ae8,managed stale lastSeen 400d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENO8,5cb888394c838874dba3e25a,managed stale lastSeen 1822d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENS11,601c5f0a658fc163f8e48c86,managed stale lastSeen 1439d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENS5,619e8c1e2df7ff18c88b97f5,managed stale lastSeen 1052d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENS6,6944188166ed6a6b5b1b3edf,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENS7,67902ba30e4c39831f2525fa,managed stale lastSeen 144d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENS9,665e1ac1967c66b2a677d880,managed stale lastSeen 193d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIDENSD1,69441883b86a8415a554d74b,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIOFFICE35,5cdcb723724f211df04d366a,managed stale lastSeen 1864d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXCSR1,66731c168e5a816a47d45f42,managed stale lastSeen 486d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXCSR2,60be73181b8455e6cae91984,managed stale lastSeen 1513d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXCSR3,66731c100b40cb69483106bd,managed stale lastSeen 232d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXCSR4,66731c11373a33f2c06dd51c,managed stale lastSeen 213d | Windows 10 Pro Education,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXFAB,6944188347940e4b1e01ef16,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXO10,6665cf4d9168569359cfa97b,managed stale lastSeen 722d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXO13,5cb5d66aba316028d97460e1,managed stale lastSeen 1428d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXS12,69441881993486f154d87546,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXS4,5cb5d66aba316028d97460e5,managed stale lastSeen 1404d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXS9,6632bed0f6b4d54268e6b28f,managed stale lastSeen 534d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXSALES,66db5bec159494576db1facf,managed stale lastSeen 530d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTIPHXSHIPPING,601c625589e6cc68d4dac52d,managed stale lastSeen 715d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISANS10,66590f0b47afb2d3d86a2023,managed stale lastSeen 728d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISHOP2324,636acbdea0a011e1aaa9f286,managed stale lastSeen 905d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISHVSPACER,6944188414e9c7f6cb765f0f,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLBLT02,5cb5fbea3debea680183bba4,managed stale lastSeen 1408d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLC03,6665cf9a3477817a0309ef7e,managed stale lastSeen 189d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCCSR1,6665cf8cb30e91d541a0428e,managed stale lastSeen 641d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCCSR2,6944187ffe062c8c9daae191,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCCSR3,6665cf80cc2503a6dc657a73,managed stale lastSeen 556d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCINVENTORY,601c628a785e2823e1163523,managed stale lastSeen 199d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCS3,66731c03b514cb20134b0bfd,managed stale lastSeen 189d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLCS4,66731c0910a0d1ae9eef5ff5,managed stale lastSeen 199d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLO4,5ffcae213871092538a93704,unmanaged mtype=1 | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISLO5,6665cf7bf27c80d4b0f7e754,managed stale lastSeen 640d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTCESAR,6665cf7c9c6bae29043837d4,managed stale lastSeen 254d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTCSRO1,5cb4fa5a4442de68067d5fe7,managed stale lastSeen 1638d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTCSRO2,66731c113f2529d8a7898362,managed stale lastSeen 252d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTCSRO3,6665cf934c48966d46a2d3c8,managed stale lastSeen 567d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTCSRO4,6665cf9268d2b063e5091c0c,managed stale lastSeen 473d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTISTIG,66731c43a36fdd0e2cac471f,managed stale lastSeen 246d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTITUC50-CSR,5cb8d026c1f9903613b4e959,managed stale lastSeen 1171d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTITUCO1,5cb8ae50ea51de212ecff733,managed stale lastSeen 1864d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTITUCS10,5ce6d7b4b67a6365228af334,managed stale lastSeen 1775d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,GTITUCSPACER,5cb5d66aba316028d97460e3,managed stale lastSeen 184d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,HRALBLPT1,5e46fe2fe2363443dd73209f,managed stale lastSeen 1408d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,HRCLERK,6765bae08b5be0ec074434dc,managed stale lastSeen 520d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,HRSLCLPT,659b138f5ffd78adf5f25b9e,managed stale lastSeen 861d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ITSUPPORT,63222d8cf1005b58dd9de1eb,managed stale lastSeen 93d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ITTZELHR,66732ee96844ef8b32cb92e8,managed stale lastSeen 639d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,LOGISTICS,6679f1b3047a9744d355d413,managed stale lastSeen 556d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,MAS90SVR,63877eb52676f7765bb43de5,managed stale lastSeen 1318d | Windows Server 2012 R2 Datacenter,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,NICKS-LAPTOP,5f99ba775abc1d06245692d7,unmanaged mtype=1 | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,OFFICETUCSTEVE,6665cf75a5d80f290bd0e42f,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PERFPHXBILLCO,6944187f009fcff23feb2651,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PETES_LAPTOP,5d5dc8fdca42a426bac5a433,managed stale lastSeen 2398d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PFAZLOLLAHHOME,60d904f5f4c25dc9f7206db2,managed stale lastSeen 1319d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXDELL2,5cb5d59e2205cd67732df5c7,managed stale lastSeen 1838d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXLT2,5f3daf67311be85e8892ae99,managed stale lastSeen 1278d | Windows 8.1,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXMAINT,66731c136fac40716e2bb832,managed stale lastSeen 232d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXO10,5cb5d66aba316028d97460e7,managed stale lastSeen 1816d | Windows 7 Home Premium,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXO9,5cb5ee45f16b7467c7fef774,managed stale lastSeen 1709d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,PHXSALES,69442580ddfb7991d995fcda,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,ROBERTHOME,694711fe47019a9d7f83609d,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SANCUTTINGTABLE,5ea1b115f2594a6edad0dfcc,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SBS,6665cf9f1f9b3337f75866e3,unmanaged mtype=1 | Windows Server 2008 R2 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHOP11,6665cf83343ce1b15aacf4de,managed stale lastSeen 462d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHOPGTITUC,649db49395c1a87f34183c23,managed stale lastSeen 534d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHVFURNACE,69441883f8992ba2c41a79e1,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHVPERFECTCUT,69441883469466a26d03bf21,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHVPM,694418838002ffd7fb9cf1c7,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SHVSHIPPING,69441883c88c68c22c653105,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,SLCLT1,5e78f8aee87b84574d4f90b8,managed stale lastSeen 1204d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,STCSR1,66c657db363dc2e6c1688c72,managed stale lastSeen 149d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,STEVEGHADMIN,69441880004ece09488c59d4,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,STSHIPPING,668c67d663266a84b59651a4,managed stale lastSeen 268d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TGDC1,69441880eec0b7b4b0cade0c,unmanaged mtype=1 | Windows Server 2016 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TOMAKK02-PC,694418a5d9c42ec04a0e7ec9,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TOMAKK05-PC,6944188272d1cac82a52ead2,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TOMLAPTOP,6027f54644c65b740f26f306,managed stale lastSeen 101d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TOMSKK06-PC,694418833619705af64388c2,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TOM_OFFICE,66d4dae359eade85d92a5310,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUC-PM,6665cf932394521a53fedf6c,managed stale lastSeen 184d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUC-SALES,69441880f7df3360924365a7,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCKRISTY,66731c07a19b8d3ec973c825,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCS2,601c63ad4a174a4ab2cbf6d8,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCS25,601c63c2f4cc952c713dc834,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCSHIPPING,69ab421351c9104a5ed050f5,managed stale lastSeen 71d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCTEMPERING,601c63e279ae996c77715e6c,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,TUCWATERJETGA,694429d91792c630324ca393,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,WIN-MLNNOHI46VA,6666f7956fb28de144bf01ba,managed stale lastSeen 241d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Glaz-Tech Industries - Steve Eastman,WIN11PRO16GI767,6973b42e16f15a4c834ef731,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DUPLICATE,Glaz-Tech Industries - Steve Eastman,BOIFAB,69a0e9f5d2e9a5f0b0499b75,dup; keep 66731c00c7a4f567685c5f1a (mgd) | this unmgd Windows,RMM absent; Datto no; no-live-BD
MIGRATED,Glaz-Tech Industries - Steve Eastman,WWW,66731c10fe374dc282a4f0b0,unmanaged mtype=1 | Windows Server 2019 Standard,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,Grabb & Durando Law Office - Jeff Williams,GND-JEANNETTE,5d8b6481151c720930e32e83,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Grabb & Durando Law Office - Jeff Williams,GND-L-2,5e8217fe4ec7f03c794d6a13,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Grabb & Durando Law Office - Jeff Williams,GND-L-4,5f5acefbe8b45260ffd55098,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Grabb & Durando Law Office - Jeff Williams,GND-MARTHA,5d8b67355a8a566479b76998,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Grabb & Durando Law Office - Jeff Williams,GND-MELODY,5d8b673cb567a51b2104c370,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Grabb & Durando Law Office - Jeff Williams,GND-BOB-PC,66f6e0a0cb4cf07be5f86c0e,managed stale lastSeen no-posture | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Grabb & Durando Law Office - Jeff Williams,GND-JEFF-2,6a517f7bd7d0c0b92f858041,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,Horseshoe Management - Bill Young,HSM-DONNA,5d676a255a34a14bfa927358,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Horseshoe Management - Bill Young,HSM-FRANK,5d676a5bc00bc54bf3108b8d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Horseshoe Management - Bill Young,HSM-RANDI,666388621490b1ebecbd7d28,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Horseshoe Management - Bill Young,HSM-SURFACE,66fcdceecb79455ee9cd7296,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Horseshoe Management - Bill Young,HSM-BILL,66f6e02a85d9f362d35420ea,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Horseshoe Management - Bill Young,HSM-FRANK02,66635af3e7e9e7d2105acecc,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Horseshoe Management - Bill Young,HSM-NEWSERVER,627af44cab5f6082fea31005,unmanaged mtype=1 | Windows Server 2019 Essentials,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,Instrumental Music Center - Leslie Faltin,IMC-M-EDSERVICE,6a4be5c9487e29403f73c193,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,C2B,6a487f5677b615646b11307d,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,DESKTOP-44L80C0,6a487f682dd91ce4845bb6c2,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,DESKTOP-GHG12G3,6a487f55220ff2f93ab7ccc1,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,DESKTOP-MR3ALTK,6a487f541933628dc5488870,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-L1-STATION9,6a487f55c88178e3f4aaf1cb,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-LESSONS,6a487f55ddc985cabdceae6a,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-LUIS,6a4bc78a4f8e1d54db50f73f,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-MINI,6a487f5640ffe040cccce0e6,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-STATION1,6a487f56b1c1d8ff52c05d29,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-STATION2,6a487f561abbaadb83f548a9,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC-SVCSTR,6a487f55ab4696625a449034,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,IMC1,681290f6ad2aebf7b838bbde,unmanaged mtype=1 | Windows Server 2016 Standard,RMM 0d; Datto yes; no-live-BD
MIGRATED,Instrumental Music Center - Leslie Faltin,LAPTOP-DCHQ3F92,6a487f5634e7c7cb1256fd05,unmanaged mtype=1 | Windows 10 Home,RMM 0d; Datto yes; no-live-BD
MIGRATED,Jimmy Company - Jimmy Hughes,BLASTER2,666600421ea6cb6a5dac25ad,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,AD-JANICE-D,669813f875fcea56c7c3d3c5,managed stale lastSeen 529d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,CAM-STAFFROOM-D,667321e4098c23fb2a620f19,managed stale lastSeen 414d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,DESKTOP-9LT4ICC,666c55d14912423a2c8a56d1,managed stale lastSeen 342d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,DESKTOP-RKKR9KN,67c8b53c940a0a5ca7d052f6,managed stale lastSeen 468d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,LAPTOP-NIBQP9LG,667eb0f1667aab6a5401d2dc,managed stale lastSeen 631d | Windows 11 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,PRIME-RTG-PC,6665cf3b8348a5f6336e7c85,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,SALMON,667ec0638cad39fd5dd85cd5,unmanaged mtype=1 | Windows Server 2016 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Khalsa Montessori School - Michael Herrera,TROUT,667edc6da782682d14f64cbb,unmanaged mtype=1 | Windows Server 2016 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Len Swanson,LEN-PC,5c8d5c698baa2522897ee01d,managed stale lastSeen 2557d | Windows 7 Home Premium,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Len's Auto Brokerage - Becky Kahn,LAB-005246,65848253d3aea6ddcb23792a,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Len's Auto Brokerage - Becky Kahn,LAB-005248,5e45a44e7b367c215ddc3eea,managed stale lastSeen 1838d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Len's Auto Brokerage - Becky Kahn,LAB-005251,5e45a4d63d248c5d381da8b6,managed stale lastSeen 1500d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Len's Auto Brokerage - Becky Kahn,LAB-005252,6463d32980fc8b5788251e32,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Len's Auto Brokerage - Becky Kahn,LAB-BECKY,6463d329bbc719578210eb8d,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,MVAN Enterprises Inc - June Bradford,Junes MacBook Pro,654d6731d926169444537a02,unmanaged mtype=1 | macOS Sequoia 15.3.0,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,MVAN Enterprises Inc - June Bradford,MITCH-LAPTOP,6665e1b4d855cb5af2fa96d3,managed stale lastSeen 761d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",BCW-20BQK97,666b3fc3d714274b452e9500,unmanaged mtype=1 | Windows 11 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",BWA-IK12323,666b40617a4642e103a8c8dd,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",DISC-GBT6BP1,666b41ca9a6a4778698da00b,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",DMS-5X8GVD3,666b3eacad64464053d8e6f9,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",HWM-56V84Z2,666b40938a5a8bf55bf3c521,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",JMH-FLSDXQ1,666b3f757cf38ef20042a7a7,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",JPL-B1W39Y1,666b3f89c3928c83d26534d6,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",KCP-FG3G842,666b3f17d77745c974f4bec0,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",KSB-JFLTB82,666b3f1602063d43bb4e6366,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",LIM-614MVD3,666b3f94475a8113dc7e47f4,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",LNW-7GLTB82,666b3f60d77745c974f4bec3,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",MACH-GM2Y8P1,666b424da9ba473659422e54,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",NJN-2W73T12,666b405c436606b5dd4e8b4d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",PRO-FMM7GZ1,666b3f8eeea213874724bc52,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",PRW-62C7X04,666b3f8f51d3ad8d8d3b53b5,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",RW-DH83T12,666b3f734400652fc3935158,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",SHIP-9PJPWP3,666b3f369f201a1660c99145,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",SLW-183CQW1,666b527c436606b5dd4e8b68,managed stale lastSeen 452d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",TDM-DKK5KS1,666b40a7d26e9e2fc97338b6,managed stale lastSeen 567d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",TDM-FG3G842,66959b178d9a59668d563074,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"Midway Industries, Inc. - Bryan Wilson",TWB-8H73T12,666b408148250c6c4dc18bda,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Mineralogical Record - Tom Gressman,DESKTOP-C84J8OS,691619a537206be5436830a1,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,"PUTT LAND SURVEYING, INC. - David Putt",PLS-FOUR,6690122ea2701cd0495cb42d,managed stale lastSeen 416d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"PUTT LAND SURVEYING, INC. - David Putt",PLS-FRONT,67003607182f0f03a7eee78c,managed stale lastSeen 199d | Windows 11 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"PUTT LAND SURVEYING, INC. - David Putt",PLS-LAPTOP,669013c42f46c7e0dbdfad80,managed stale lastSeen 528d | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,"PUTT LAND SURVEYING, INC. - David Putt",PLS-ONE,6690264d056706a244f11dd8,managed stale lastSeen 567d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Quantum Wealth Management - Sheila Peress,QUANTUMSERVER,6665cf20dd4cd55b318c511a,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto yes; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,DESKTOP-09H7T66,67228367cbe6b4c2bc288d69,managed stale lastSeen 199d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,DESKTOP-V2247T9,672262a52162eaefe712e706,managed stale lastSeen 380d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,FWSERVER,5c6f14666850d34c5aba5913,managed stale lastSeen 1704d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,FWW-ERIKALAPTOP,62dab5eeea4692a14a255e7d,unmanaged mtype=1 | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,LAPTOP-EJL9PFOU,67f825d6a2cf678ddb7fe2b6,managed stale lastSeen 372d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,LAPTOP-FR4QN9KF,67f825b9adbda4a403c2d692,managed stale lastSeen 372d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,LAPTOP-JQMU8N9K,67f825c1954153156bfb1c68,managed stale lastSeen 372d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,RELIANT-L01,68923903dfab0192d9ca8cb7,managed stale lastSeen 299d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,RELIANT-L02,689239143ef4423d63b163d8,managed stale lastSeen 339d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,RELIANT-L03,689238d65268b36f707e1443,managed stale lastSeen 339d | Windows 11 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,RWD-ALANLAPTOP,67226e3116029df6801d1a85,managed stale lastSeen 133d | Windows 11 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Reliant Well Drilling and Pump Corporate - Channa V,WILCOXADMIN,67101a81c3d6dd4666e09551,managed stale lastSeen 596d | Windows 8.1,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Russo Law Firm - Shannon Trionfo,RRSAD,6294eca9bc14cc060b47df4c,unmanaged mtype=1 | Windows Server 2019 Standard,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Russo Law Firm - Shannon Trionfo,RUSSOADMIN,60f639b001f0c2896cbc023b,unmanaged mtype=1 | Windows Server 2008 R2 Standard,RMM absent; Datto no; no-live-BD
DUPLICATE,Russo Law Firm - Shannon Trionfo,STRIONFO,660c5e1bad9b30fdbc120012,dup; keep 67290a8e932f963c6b82d29a (mgd) | this unmgd Windows 10 Pro,RMM 0d; Datto yes; BD-live-agent
DECOMMISSIONED,Safesite LLC - Tim Story,1122-SUZANNE-DE,6932e9ac8765ee735b0d8b8d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Safesite LLC - Tim Story,1123DELL3540-2,69323aaccf244c5062a29338,unmanaged mtype=1 | Windows 11 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0122-DENIM-DELL,6932e975b71a6a52a89e0043,unmanaged mtype=1 | Windows 11 Home,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0524-DELLG16,6932ee6332646df3c0243aaf,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0525-ASUSFX707Z,693253fd9b6c8af9f094e8aa,unmanaged mtype=1 | Windows 11 Home,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0621-DELL3502,6932d7adf3bfca192e2f2b0d,unmanaged mtype=1 | Windows 11 Pro for Workstations,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0624DELL3550,6932dfe12b64a9bfd3c83a6a,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,0923-DELL616,6932f26ce03c1570408e6da7,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto yes; no-live-BD
MIGRATED,Safesite LLC - Tim Story,SAFESITE-LT04,693342ee84f072623b49ebec,unmanaged mtype=1 | Windows 11 Home,RMM 2d; Datto yes; no-live-BD
DECOMMISSIONED,Saguaro Conveyor Equipment - Rick Ostrop,DESKTOP-DGM4C1T,66fabad76751f1db6125803b,managed stale lastSeen 303d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Saguaro Conveyor Equipment - Rick Ostrop,DESKTOP-L0U6QUN,67290b3b9516187d08294c2d,managed stale lastSeen 303d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Saguaro Conveyor Equipment - Rick Ostrop,RICKOSTROP-PC,6704d83114f1c3dd2c7983b7,managed stale lastSeen 303d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,DESKTOP-9204DI5,69dd41be340d40b4c8073064,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-60JJ,6144e71bf2cfcb1a4a2cef14,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-868J,6145022429c59ab2a1a5a586,unmanaged mtype=1 | Windows 10 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-BGZH,6144e72a815369c338c13dd1,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-BGZS,6144e7336518d2e3c8fdc2c8,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-BWY2,69dd41be340d40b4c8073061,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-GCX1,69dd41be340d40b4c8073065,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-LHKQ,69dd41be340d40b4c8073063,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-DT-XN22,69dd41be340d40b4c8073062,unmanaged mtype=1 | Windows,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-LT-4WMN,6144e7585bcc1d3b54a9282d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-LT-9JPN,6146664f598f9470676baf5d,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SD-LT-CWPN,6146378f11163bd471d87b35,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SDO-CHAIRPERSON,5d55723a0b2c750908be3f9b,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sells District - Tohono O'odham Nation - Delmarie Pan,SELLS-SHELLSTOR,6144e77053b3b5c433dc0c87,unmanaged mtype=1 | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-FJUAN,61b21623275b90362889bbd3,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-01,62b0f15d0c41724c800f5cf4,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-02,61b1625cfcaf19929d5617b5,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-03,65f0db94af36077f61bee8bf,managed stale lastSeen 716d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-13,6584c7c3f5717670a9970390,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-14,62b634e734e19a5493f9e307,managed stale lastSeen 722d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-15,615e51e47d5a6346e1504427,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-18,65f0ce2b50d2181f62a600c5,managed stale lastSeen 722d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-19,632a40031c5fbf2393bde427,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-L-9,62b3bf68a1574dc358791717,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-RWOOD,615f0bf046e7be8b63e5febd,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-TREASURER-D,615e4da8023769c17b584783,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Stamback Septic - Joe Schmuker,SSS-SPARE,6665cf3cc910b9822d07395e,managed stale lastSeen 393d | Windows 11 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,SunDanzer Refrigeration - David Bergeron,ENGR-COMPUTER,67bf5439db2464cade8b4e97,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,The Prairie Schooner - Tina Coalter,DESKTOP-SRUOH4R,66fab14a3164148712d02dcf,managed stale lastSeen 316d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,The Prairie Schooner - Tina Coalter,JAYMINEWDELL,5d9639845c1950227c339213,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
MIGRATED,The Prairie Schooner - Tina Coalter,TPS-SERVER,665900e22a5978810642f297,unmanaged mtype=1 | Windows Server 2016 Essentials,RMM 0d; Datto yes; no-live-BD
DUPLICATE,Tom Sorensen,TOM-WIN7,5ebd66174c004c2ddc701ebd,dup; keep 66fabc590627e2af49646a95 (mgd) | this unmgd Windows 10 Pro,RMM 0d; Datto yes; BD-live-agent
DECOMMISSIONED,Tucson Mountain Motors - Pete Jacoby,TMM-FRONT,5c92c1d797a6682e089c71e0,unmanaged mtype=1 | Windows 7 Home Premium,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,DESKTOP-AJT3O8I,5e19c4bef6686c3db8a55478,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,POS1-PC,5e190c90cd272c67f77f93fd,managed stale lastSeen 2212d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TSAM-DRIVER2,670da21f48088defd39eafdb,managed stale lastSeen 284d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TSAM-JERRY,68acabd09b5e43255082a6d5,unmanaged mtype=1 | Windows 10 Pro for Workstations,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TSAM-KAYTIE,68acabcfc3b0e1613a13fa4e,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TSAM-SERVER,6665cf2762e4cb63e90285cf,unmanaged mtype=1 | Windows Server 2016 Essentials,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TSM-FRONTDESK,670c204cd12823c4ad1aa76b,managed stale lastSeen 408d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Tucson Safety and Medical - Gerald Long,TUCSONSAFETY-LT,5e1243aa1f985c21c3d81aa4,managed stale lastSeen 2097d | Windows 7 Professional,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Universal Cryogenics - Richard Schickling,AARDMAN,67361977fa2612fcc78649df,managed stale lastSeen 513d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Universal Cryogenics - Richard Schickling,GARFIELD,666a0e29eadf86115042969e,managed stale lastSeen 231d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Universal Cryogenics - Richard Schickling,SCOOBY,66673da0a711ab8d2ebd39e5,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Universal Cryogenics - Richard Schickling,SNOOP,63594bf032d88ee20e0f5ca4,managed stale lastSeen 1352d | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Yvonne Tedards,BILL,5d20fe4c32551b7ecbd20b21,unmanaged mtype=1 | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Yvonne Tedards,DESKTOP-SUFJR0J,5d20ffe95967865bb2c7f194,unmanaged mtype=1 | Windows 10 Home,RMM absent; Datto no; no-live-BD
DECOMMISSIONED,Yvonne Tedards,TEDARDSLAPTOP,5e7a5ac10d4d5e5dc8e47742,unmanaged mtype=1 | Windows 10 Pro,RMM absent; Datto no; no-live-BD
DUPLICATE,Zeus Nestora - Cindy White,CINDYW,6665cf3e963d87e89ca1f311,dup; keep 5e63f85708f6f833d2be52e0 (unmgd) | this unmgd Windows 10 Home,RMM 0d; Datto yes; no-live-BD
MIGRATED,Zeus Nestora - Cindy White,CINDYW,5e63f85708f6f833d2be52e0,unmanaged mtype=1 | Windows 7 Home Premium,RMM 0d; Datto yes; no-live-BD
MIGRATED,Zeus Nestora - Cindy White,SERVER,66425bba4bda5fb351ea0091,unmanaged mtype=1 | Windows Server 2016 Essentials,RMM 0d; Datto yes; BD-live-agent
1 class company hostname endpoint_id reason evidence
2 DECOMMISSIONED AT Trebesch AT 5dd6d0e660e456225596d973 managed stale lastSeen 282d | Windows 7 Home Premium RMM absent; Datto no; no-live-BD
3 DECOMMISSIONED Arizona Computer Guru - Michael Swanson ACG-HC 66ad7a8194d8d96dbbd317ff managed stale lastSeen 706d | Windows Server 2012 R2 Standard RMM absent; Datto no; no-live-BD
4 DECOMMISSIONED Arizona Computer Guru - Michael Swanson ACGVDS 675f08a6261f375674d73033 managed stale lastSeen 549d | Windows Server 2012 R2 Standard RMM absent; Datto no; no-live-BD
5 DECOMMISSIONED Arizona Computer Guru - Michael Swanson DESKTOP-0O8A1RL 69ce91b1f6eea97ee1016e70 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
6 DECOMMISSIONED Arizona Computer Guru - Michael Swanson DESKTOP-BCRENMO 5d2e7459f3509009dd6b0bc4 unmanaged mtype=1 | Windows 10 Home RMM absent; Datto no; no-live-BD
7 DECOMMISSIONED Arizona Computer Guru - Michael Swanson GURU-BEAST-I9 66ad772128980e6e026398a5 managed stale lastSeen 612d | Windows 11 Pro RMM absent; Datto no; no-live-BD
8 DECOMMISSIONED Arizona Computer Guru - Michael Swanson GURU-LEGION 60dca8aa04c8e42270fa4604 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
9 DECOMMISSIONED Arizona Computer Guru - Michael Swanson GURU-TEST 5e476ba49d5dfb49a5d227cd managed stale lastSeen 2200d | Windows 10 Home RMM absent; Datto no; no-live-BD
10 DECOMMISSIONED Arizona Computer Guru - Michael Swanson GURUBOX2 5c42aa4287606605f6978872 managed stale lastSeen 2668d | Windows 10 Enterprise RMM absent; Datto no; no-live-BD
11 DECOMMISSIONED Arizona Computer Guru - Michael Swanson LENOVHA 5eb9d18c1b250679c5717336 managed stale lastSeen 1961d | Windows 10 Home RMM absent; Datto no; no-live-BD
12 DECOMMISSIONED Arizona Computer Guru - Michael Swanson VIP 5cb95f67b5e538748bb3c708 unmanaged mtype=1 | Windows Server 2016 Datacenter RMM absent; Datto no; no-live-BD
13 DUPLICATE Arizona Computer Guru - Michael Swanson ACGVDS 66c4d5b00b0f0ee09c418016 dup; keep 675f08a6261f375674d73033 (mgd) | this mgd Windows Server 2012 R2 Standard RMM absent; Datto no; no-live-BD
14 DUPLICATE Arizona Computer Guru - Michael Swanson ACGVDS 66b15faff22e8a0befe670df dup; keep 675f08a6261f375674d73033 (mgd) | this mgd Windows Server 2012 R2 Standard RMM absent; Datto no; no-live-BD
15 MIGRATED Arizona Medical Transit - Bertie Lozano SERVER 5cb9075cf2c564744116f7c7 unmanaged mtype=2 | Windows Server 2008 R2 Enterprise RMM 0d; Datto yes; BD-live-agent
16 DECOMMISSIONED Associated Realty of the Americas - Carl Bosse LAPTOP-PSIC3D3T 66cb8a029489d746d72c99d0 managed stale lastSeen 645d | Windows 10 Home RMM absent; Datto no; no-live-BD
17 DECOMMISSIONED Baboquivari District - Idella Stanley BAQ-DESKTOP0103 66670c297b424e8512864f8e unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
18 DECOMMISSIONED Curtis Plumbing - Ken Curtis CURTIS-003 5f7c92e504b0062a4b09d536 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
19 DECOMMISSIONED Curtis Plumbing - Ken Curtis CURTIS-005-W7 5da75781302bc04bc2d3c082 unmanaged mtype=1 | Windows 7 Professional RMM absent; Datto no; no-live-BD
20 DUPLICATE Curtis Plumbing - Ken Curtis CURTIS-001 66fab92596da30df7ef0d9b1 dup; keep 5dbc5f7bc6da513b2df125e3 (unmgd) | this unmgd Windows 10 Pro RMM 0d; Datto yes; no-live-BD
21 MIGRATED Curtis Plumbing - Ken Curtis CURTIS-001 5dbc5f7bc6da513b2df125e3 unmanaged mtype=1 | Windows 7 Professional RMM 0d; Datto yes; no-live-BD
22 DECOMMISSIONED David Anaise DROBO-OLD 5dc5ac37f4c40339b97ccbfe unmanaged mtype=1 | Windows 7 Professional RMM absent; Datto no; no-live-BD
23 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBCSR1 66731c113a4c2e615a0628b6 managed stale lastSeen 163d | Windows 10 Pro RMM absent; Datto no; no-live-BD
24 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBCUTTINGTABLE 659b2925ebbb1c432fac2426 managed stale lastSeen 910d | Windows 10 Pro RMM absent; Datto no; no-live-BD
25 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBMAINTENANCE 5fd129b686f0e37c182bc9b6 managed stale lastSeen 93d | Windows 10 Pro RMM absent; Datto no; no-live-BD
26 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBO14 614dcea1290cb4b9145b0432 managed stale lastSeen 1544d | Windows 10 Pro RMM absent; Datto no; no-live-BD
27 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBO7 5dcde10e8cf293433d6dd5cf managed stale lastSeen 2430d | Windows 7 Professional RMM absent; Datto no; no-live-BD
28 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBPERFECTCUT23 6944188314e9c7f6cb765f0d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
29 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALBSVR1 69657cbcf5b8e864c49c0213 unmanaged mtype=1 | Windows Server 2008 R2 Standard RMM absent; Datto no; no-live-BD
30 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ALHOME 68ddbd34cfc93f575816fcd5 managed stale lastSeen 162d | Windows 11 Home RMM absent; Datto no; no-live-BD
31 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOICSR1 6665cf997740d8e5de318fdc managed stale lastSeen 381d | Windows 10 Pro RMM absent; Datto no; no-live-BD
32 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOIFAB 66731c00c7a4f567685c5f1a managed stale lastSeen 140d | Windows 10 Pro RMM absent; Datto no; no-live-BD
33 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOIFURNANCE 601c5d1c1084082cf07b0fda unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
34 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOILEIA 6665cf9a15b4e6468bae5541 managed stale lastSeen 646d | Windows 10 Pro RMM absent; Datto no; no-live-BD
35 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOINTAYLOR 6944187f7e28b2b991431268 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
36 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOISESVR 6941b72c6254f011aa26a5b3 unmanaged mtype=1 | Windows Server 2022 Standard RMM absent; Datto no; no-live-BD
37 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOISHIPPING 601c5d2ce421af6446a70f87 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
38 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOISHIPPING1 694418818c399d0cb3038728 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
39 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BOITRACER 694418824eb00e080ed15b75 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
40 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BRLCSRMORGAN 6944188380301a004e83ee25 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
41 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BRLMAINT 6944187e12b7fd777f9d1eb2 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
42 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BRLOMROXY 694418836bbd28939f3ede85 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
43 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BRLS3 601c5e15a01f6763f2ecf316 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
44 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman BRLSHIPPING 6944187f0a7faeceb913bef7 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
45 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman CHRISHOME 69fbb7087cc0382fe4551e11 managed stale lastSeen 65d | Windows 10 Pro RMM absent; Datto no; no-live-BD
46 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman CSMITHAR 69441881377e86115ebd7825 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
47 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman CUTTINGSANTABLE 69441882d69f3e9d75792826 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
48 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman DENS3 5cb783929be88a7320f60ae4 managed stale lastSeen 1355d | Windows 10 Pro RMM absent; Datto no; no-live-BD
49 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman DENWATERJET32 69441881a977c1e2255ed329 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
50 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman DESKTOP-CPMCF00 5ceeb1ab4159e84a4021dd3e managed stale lastSeen 305d | Windows 10 Home RMM absent; Datto no; no-live-BD
51 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman DESKTOP-GOTG6ET 5cbb49aed1fef636029f48b1 managed stale lastSeen 1411d | Windows 10 Home RMM absent; Datto no; no-live-BD
52 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPCSR1 66731c16e89e6c61808777a1 managed stale lastSeen 254d | Windows 10 Pro RMM absent; Datto no; no-live-BD
53 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPO10 5cb4fa5a4442de68067d5fde managed stale lastSeen 1912d | Windows 7 Professional RMM absent; Datto no; no-live-BD
54 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPO7 5cb4fa5a4442de68067d5fe1 managed stale lastSeen 1935d | Windows 7 Home Premium RMM absent; Datto no; no-live-BD
55 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPO8 6665cfacd84664e67332f51b managed stale lastSeen 591d | Windows 10 Pro RMM absent; Datto no; no-live-BD
56 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPS6 5cb4fa5a4442de68067d5fe3 managed stale lastSeen 268d | Windows 10 Pro RMM absent; Datto no; no-live-BD
57 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPS9 6665cf8e6293cbce8b9ee8f5 managed stale lastSeen 267d | Windows 10 Pro RMM absent; Datto no; no-live-BD
58 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPSVR 6665cf2c1490b1ebecbd7e1a unmanaged mtype=1 | Windows Server 2008 R2 Standard RMM absent; Datto no; no-live-BD
59 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ELPSVR1 601c62e35c31a26c7e0ef518 managed stale lastSeen 1982d | Windows 7 Professional RMM absent; Datto no; no-live-BD
60 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GLAZ-SEASTMAN 5cb740e71f1b8c2d5fe037d8 managed stale lastSeen 1067d | Windows 10 Pro RMM absent; Datto no; no-live-BD
61 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GLAZLAPTOP 69de9d0886ce7e6e2682d093 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
62 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GLAZOFFICE10 60c14365d46a268f48dea372 managed stale lastSeen 1724d | Windows 10 Pro RMM absent; Datto no; no-live-BD
63 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GLAZOFFICE11 5cb90e16ea51de212ed1fc84 managed stale lastSeen 1270d | Windows 10 Pro RMM absent; Datto no; no-live-BD
64 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTI-INV-SQL12 62c3f0b041b01242b7191d3c managed stale lastSeen 289d | Windows Server 2008 R2 Standard RMM absent; Datto no; no-live-BD
65 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBALBERT 66731c0a843694782b1223ae unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
66 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBANTONIO 6665cf8cbe52690e9ebc8f38 managed stale lastSeen 186d | Windows 10 Pro RMM absent; Datto no; no-live-BD
67 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBCSR1 6944188301e388a90b2c2af1 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
68 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBDEIDRA 6256db34133dc52457e3da9d managed stale lastSeen 177d | Windows 11 Pro RMM absent; Datto no; no-live-BD
69 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBFAB 5cd47bed16f09c67b7faf1b3 managed stale lastSeen 1766d | Windows 7 Professional RMM absent; Datto no; no-live-BD
70 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBIAN 6665cf4dcdcec4b6a85ae3ec managed stale lastSeen 175d | Windows 10 Pro RMM absent; Datto no; no-live-BD
71 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBO3 5cb3e76b4442de680673fb58 managed stale lastSeen 1736d | Windows 10 Pro RMM absent; Datto no; no-live-BD
72 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBS1 601194f6219d9a5bc2249f42 managed stale lastSeen 462d | Windows 10 Pro RMM absent; Datto no; no-live-BD
73 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBS7 60119506e74d6237ca1af38f unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
74 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIALBSIG 66731c11e8dc689013583b22 managed stale lastSeen 149d | Windows 10 Pro RMM absent; Datto no; no-live-BD
75 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBLRS1 601c5e4393b85d4e46af5823 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
76 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBLT2 5cb88c0223b35836c23199c0 managed stale lastSeen 1958d | Windows 10 Home RMM absent; Datto no; no-live-BD
77 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBO10 5cb52cf79be88a7320e4c3b2 managed stale lastSeen 1752d | Windows 10 Pro RMM absent; Datto no; no-live-BD
78 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBO13 6665cf4c696ccfb94f656063 managed stale lastSeen 458d | Windows 10 Pro RMM absent; Datto no; no-live-BD
79 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBO4 5cb52cf79be88a7320e4c3b4 managed stale lastSeen 1780d | Windows 7 Professional RMM absent; Datto no; no-live-BD
80 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBOICSR2 69949c0b1d4e9a8356d29fa6 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
81 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBOIJAMIE 6944188310aa6562af960bad unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
82 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBOIJEFF 694418838d981de163ebd577 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
83 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBOIMAINTENAN 6944188363e2ce57977e4cb3 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
84 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBOIPM 694418836a7d56f6c53c743c unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
85 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLCSR 6665cfa0b560096618f4de69 managed stale lastSeen 345d | Windows 10 Pro RMM absent; Datto no; no-live-BD
86 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLCSR1 5cbde2de8eb04d361fb2c675 managed stale lastSeen 1375d | Windows 10 Pro RMM absent; Datto no; no-live-BD
87 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLCSR2 6667730cbf64d65ef9b15559 managed stale lastSeen 315d | Windows 10 Pro RMM absent; Datto no; no-live-BD
88 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLFAB 5cbf68c7ea51de212efbd281 managed stale lastSeen 268d | Windows 10 Pro RMM absent; Datto no; no-live-BD
89 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLFAB3 69441883a11bce397c3a8f94 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
90 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLKUMASI 68783432a5e3e4122c8f902e managed stale lastSeen 141d | Windows 11 Pro RMM absent; Datto no; no-live-BD
91 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLLTO2 6666ca7af079fa08a59beb46 managed stale lastSeen 277d | Windows 10 Pro RMM absent; Datto no; no-live-BD
92 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLO7 6665cf9ca888096dc3fa03c6 managed stale lastSeen 571d | Windows 10 Pro RMM absent; Datto no; no-live-BD
93 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLO8 66731c13436606b5dd4e94b8 managed stale lastSeen 473d | Windows 10 Pro RMM absent; Datto no; no-live-BD
94 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLS10 61cdbfa2c946c45788577e91 managed stale lastSeen 1047d | Windows 10 Pro RMM absent; Datto no; no-live-BD
95 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBRLS7 614c90c6931713b3125c0fff managed stale lastSeen 1751d | Windows 10 Pro RMM absent; Datto no; no-live-BD
96 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIBS5 66ab98a8b474c9b783683e69 managed stale lastSeen 82d | Windows 10 Pro RMM absent; Datto no; no-live-BD
97 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDEN3 5cb783929be88a7320f60ae5 managed stale lastSeen 1424d | Windows 10 Pro RMM absent; Datto no; no-live-BD
98 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENCSR1 6665cf911920e79808854261 managed stale lastSeen 235d | Windows 10 Pro RMM absent; Datto no; no-live-BD
99 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENCSR2 66731c11bf53b9ec4841c88d managed stale lastSeen 239d | Windows 10 Pro RMM absent; Datto no; no-live-BD
100 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENCSR5 69441882dd21243a43131eb2 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
101 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENGM 66731c0d4d99eaeee06e2f72 managed stale lastSeen 220d | Windows 10 Pro RMM absent; Datto no; no-live-BD
102 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENO14 6476636fded92a5875e23511 managed stale lastSeen 192d | Windows 10 Pro RMM absent; Datto no; no-live-BD
103 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENO4 5cb783929be88a7320f60ae6 managed stale lastSeen 1905d | Windows 10 Pro RMM absent; Datto no; no-live-BD
104 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENO7 5cb783929be88a7320f60ae8 managed stale lastSeen 400d | Windows 7 Professional RMM absent; Datto no; no-live-BD
105 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENO8 5cb888394c838874dba3e25a managed stale lastSeen 1822d | Windows 7 Professional RMM absent; Datto no; no-live-BD
106 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENS11 601c5f0a658fc163f8e48c86 managed stale lastSeen 1439d | Windows 10 Pro RMM absent; Datto no; no-live-BD
107 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENS5 619e8c1e2df7ff18c88b97f5 managed stale lastSeen 1052d | Windows 10 Pro RMM absent; Datto no; no-live-BD
108 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENS6 6944188166ed6a6b5b1b3edf unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
109 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENS7 67902ba30e4c39831f2525fa managed stale lastSeen 144d | Windows 10 Pro RMM absent; Datto no; no-live-BD
110 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENS9 665e1ac1967c66b2a677d880 managed stale lastSeen 193d | Windows 10 Pro RMM absent; Datto no; no-live-BD
111 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIDENSD1 69441883b86a8415a554d74b unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
112 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIOFFICE35 5cdcb723724f211df04d366a managed stale lastSeen 1864d | Windows 10 Pro RMM absent; Datto no; no-live-BD
113 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXCSR1 66731c168e5a816a47d45f42 managed stale lastSeen 486d | Windows 10 Pro RMM absent; Datto no; no-live-BD
114 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXCSR2 60be73181b8455e6cae91984 managed stale lastSeen 1513d | Windows 10 Pro RMM absent; Datto no; no-live-BD
115 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXCSR3 66731c100b40cb69483106bd managed stale lastSeen 232d | Windows 10 Pro RMM absent; Datto no; no-live-BD
116 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXCSR4 66731c11373a33f2c06dd51c managed stale lastSeen 213d | Windows 10 Pro Education RMM absent; Datto no; no-live-BD
117 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXFAB 6944188347940e4b1e01ef16 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
118 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXO10 6665cf4d9168569359cfa97b managed stale lastSeen 722d | Windows 10 Pro RMM absent; Datto no; no-live-BD
119 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXO13 5cb5d66aba316028d97460e1 managed stale lastSeen 1428d | Windows 10 Pro RMM absent; Datto no; no-live-BD
120 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXS12 69441881993486f154d87546 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
121 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXS4 5cb5d66aba316028d97460e5 managed stale lastSeen 1404d | Windows 7 Professional RMM absent; Datto no; no-live-BD
122 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXS9 6632bed0f6b4d54268e6b28f managed stale lastSeen 534d | Windows 10 Pro RMM absent; Datto no; no-live-BD
123 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXSALES 66db5bec159494576db1facf managed stale lastSeen 530d | Windows 10 Pro RMM absent; Datto no; no-live-BD
124 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTIPHXSHIPPING 601c625589e6cc68d4dac52d managed stale lastSeen 715d | Windows 10 Pro RMM absent; Datto no; no-live-BD
125 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISANS10 66590f0b47afb2d3d86a2023 managed stale lastSeen 728d | Windows 10 Pro RMM absent; Datto no; no-live-BD
126 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISHOP2324 636acbdea0a011e1aaa9f286 managed stale lastSeen 905d | Windows 10 Pro RMM absent; Datto no; no-live-BD
127 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISHVSPACER 6944188414e9c7f6cb765f0f unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
128 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLBLT02 5cb5fbea3debea680183bba4 managed stale lastSeen 1408d | Windows 10 Pro RMM absent; Datto no; no-live-BD
129 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLC03 6665cf9a3477817a0309ef7e managed stale lastSeen 189d | Windows 10 Pro RMM absent; Datto no; no-live-BD
130 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCCSR1 6665cf8cb30e91d541a0428e managed stale lastSeen 641d | Windows 10 Pro RMM absent; Datto no; no-live-BD
131 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCCSR2 6944187ffe062c8c9daae191 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
132 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCCSR3 6665cf80cc2503a6dc657a73 managed stale lastSeen 556d | Windows 10 Pro RMM absent; Datto no; no-live-BD
133 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCINVENTORY 601c628a785e2823e1163523 managed stale lastSeen 199d | Windows 10 Pro RMM absent; Datto no; no-live-BD
134 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCS3 66731c03b514cb20134b0bfd managed stale lastSeen 189d | Windows 10 Pro RMM absent; Datto no; no-live-BD
135 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLCS4 66731c0910a0d1ae9eef5ff5 managed stale lastSeen 199d | Windows 10 Pro RMM absent; Datto no; no-live-BD
136 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLO4 5ffcae213871092538a93704 unmanaged mtype=1 | Windows 7 Professional RMM absent; Datto no; no-live-BD
137 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISLO5 6665cf7bf27c80d4b0f7e754 managed stale lastSeen 640d | Windows 10 Pro RMM absent; Datto no; no-live-BD
138 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTCESAR 6665cf7c9c6bae29043837d4 managed stale lastSeen 254d | Windows 10 Pro RMM absent; Datto no; no-live-BD
139 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTCSRO1 5cb4fa5a4442de68067d5fe7 managed stale lastSeen 1638d | Windows 10 Pro RMM absent; Datto no; no-live-BD
140 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTCSRO2 66731c113f2529d8a7898362 managed stale lastSeen 252d | Windows 10 Pro RMM absent; Datto no; no-live-BD
141 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTCSRO3 6665cf934c48966d46a2d3c8 managed stale lastSeen 567d | Windows 10 Pro RMM absent; Datto no; no-live-BD
142 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTCSRO4 6665cf9268d2b063e5091c0c managed stale lastSeen 473d | Windows 10 Pro RMM absent; Datto no; no-live-BD
143 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTISTIG 66731c43a36fdd0e2cac471f managed stale lastSeen 246d | Windows 10 Pro RMM absent; Datto no; no-live-BD
144 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTITUC50-CSR 5cb8d026c1f9903613b4e959 managed stale lastSeen 1171d | Windows 10 Pro RMM absent; Datto no; no-live-BD
145 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTITUCO1 5cb8ae50ea51de212ecff733 managed stale lastSeen 1864d | Windows 10 Home RMM absent; Datto no; no-live-BD
146 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTITUCS10 5ce6d7b4b67a6365228af334 managed stale lastSeen 1775d | Windows 10 Pro RMM absent; Datto no; no-live-BD
147 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman GTITUCSPACER 5cb5d66aba316028d97460e3 managed stale lastSeen 184d | Windows 10 Pro RMM absent; Datto no; no-live-BD
148 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman HRALBLPT1 5e46fe2fe2363443dd73209f managed stale lastSeen 1408d | Windows 10 Pro RMM absent; Datto no; no-live-BD
149 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman HRCLERK 6765bae08b5be0ec074434dc managed stale lastSeen 520d | Windows 10 Pro RMM absent; Datto no; no-live-BD
150 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman HRSLCLPT 659b138f5ffd78adf5f25b9e managed stale lastSeen 861d | Windows 10 Pro RMM absent; Datto no; no-live-BD
151 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ITSUPPORT 63222d8cf1005b58dd9de1eb managed stale lastSeen 93d | Windows 10 Pro RMM absent; Datto no; no-live-BD
152 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ITTZELHR 66732ee96844ef8b32cb92e8 managed stale lastSeen 639d | Windows 10 Pro RMM absent; Datto no; no-live-BD
153 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman LOGISTICS 6679f1b3047a9744d355d413 managed stale lastSeen 556d | Windows 10 Pro RMM absent; Datto no; no-live-BD
154 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman MAS90SVR 63877eb52676f7765bb43de5 managed stale lastSeen 1318d | Windows Server 2012 R2 Datacenter RMM absent; Datto no; no-live-BD
155 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman NICKS-LAPTOP 5f99ba775abc1d06245692d7 unmanaged mtype=1 | Windows 10 Home RMM absent; Datto no; no-live-BD
156 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman OFFICETUCSTEVE 6665cf75a5d80f290bd0e42f unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
157 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PERFPHXBILLCO 6944187f009fcff23feb2651 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
158 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PETES_LAPTOP 5d5dc8fdca42a426bac5a433 managed stale lastSeen 2398d | Windows 10 Home RMM absent; Datto no; no-live-BD
159 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PFAZLOLLAHHOME 60d904f5f4c25dc9f7206db2 managed stale lastSeen 1319d | Windows 10 Home RMM absent; Datto no; no-live-BD
160 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXDELL2 5cb5d59e2205cd67732df5c7 managed stale lastSeen 1838d | Windows 7 Professional RMM absent; Datto no; no-live-BD
161 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXLT2 5f3daf67311be85e8892ae99 managed stale lastSeen 1278d | Windows 8.1 RMM absent; Datto no; no-live-BD
162 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXMAINT 66731c136fac40716e2bb832 managed stale lastSeen 232d | Windows 10 Pro RMM absent; Datto no; no-live-BD
163 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXO10 5cb5d66aba316028d97460e7 managed stale lastSeen 1816d | Windows 7 Home Premium RMM absent; Datto no; no-live-BD
164 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXO9 5cb5ee45f16b7467c7fef774 managed stale lastSeen 1709d | Windows 7 Professional RMM absent; Datto no; no-live-BD
165 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman PHXSALES 69442580ddfb7991d995fcda unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
166 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman ROBERTHOME 694711fe47019a9d7f83609d unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
167 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SANCUTTINGTABLE 5ea1b115f2594a6edad0dfcc unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
168 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SBS 6665cf9f1f9b3337f75866e3 unmanaged mtype=1 | Windows Server 2008 R2 Standard RMM absent; Datto no; no-live-BD
169 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHOP11 6665cf83343ce1b15aacf4de managed stale lastSeen 462d | Windows 10 Pro RMM absent; Datto no; no-live-BD
170 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHOPGTITUC 649db49395c1a87f34183c23 managed stale lastSeen 534d | Windows 10 Pro RMM absent; Datto no; no-live-BD
171 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHVFURNACE 69441883f8992ba2c41a79e1 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
172 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHVPERFECTCUT 69441883469466a26d03bf21 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
173 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHVPM 694418838002ffd7fb9cf1c7 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
174 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SHVSHIPPING 69441883c88c68c22c653105 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
175 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman SLCLT1 5e78f8aee87b84574d4f90b8 managed stale lastSeen 1204d | Windows 10 Home RMM absent; Datto no; no-live-BD
176 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman STCSR1 66c657db363dc2e6c1688c72 managed stale lastSeen 149d | Windows 11 Pro RMM absent; Datto no; no-live-BD
177 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman STEVEGHADMIN 69441880004ece09488c59d4 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
178 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman STSHIPPING 668c67d663266a84b59651a4 managed stale lastSeen 268d | Windows 10 Pro RMM absent; Datto no; no-live-BD
179 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TGDC1 69441880eec0b7b4b0cade0c unmanaged mtype=1 | Windows Server 2016 Standard RMM absent; Datto no; no-live-BD
180 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TOMAKK02-PC 694418a5d9c42ec04a0e7ec9 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
181 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TOMAKK05-PC 6944188272d1cac82a52ead2 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
182 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TOMLAPTOP 6027f54644c65b740f26f306 managed stale lastSeen 101d | Windows 7 Professional RMM absent; Datto no; no-live-BD
183 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TOMSKK06-PC 694418833619705af64388c2 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
184 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TOM_OFFICE 66d4dae359eade85d92a5310 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
185 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUC-PM 6665cf932394521a53fedf6c managed stale lastSeen 184d | Windows 10 Pro RMM absent; Datto no; no-live-BD
186 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUC-SALES 69441880f7df3360924365a7 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
187 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCKRISTY 66731c07a19b8d3ec973c825 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
188 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCS2 601c63ad4a174a4ab2cbf6d8 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
189 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCS25 601c63c2f4cc952c713dc834 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
190 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCSHIPPING 69ab421351c9104a5ed050f5 managed stale lastSeen 71d | Windows 10 Pro RMM absent; Datto no; no-live-BD
191 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCTEMPERING 601c63e279ae996c77715e6c unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
192 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman TUCWATERJETGA 694429d91792c630324ca393 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
193 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman WIN-MLNNOHI46VA 6666f7956fb28de144bf01ba managed stale lastSeen 241d | Windows 10 Pro RMM absent; Datto no; no-live-BD
194 DECOMMISSIONED Glaz-Tech Industries - Steve Eastman WIN11PRO16GI767 6973b42e16f15a4c834ef731 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
195 DUPLICATE Glaz-Tech Industries - Steve Eastman BOIFAB 69a0e9f5d2e9a5f0b0499b75 dup; keep 66731c00c7a4f567685c5f1a (mgd) | this unmgd Windows RMM absent; Datto no; no-live-BD
196 MIGRATED Glaz-Tech Industries - Steve Eastman WWW 66731c10fe374dc282a4f0b0 unmanaged mtype=1 | Windows Server 2019 Standard RMM 0d; Datto yes; no-live-BD
197 DECOMMISSIONED Grabb & Durando Law Office - Jeff Williams GND-JEANNETTE 5d8b6481151c720930e32e83 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
198 DECOMMISSIONED Grabb & Durando Law Office - Jeff Williams GND-L-2 5e8217fe4ec7f03c794d6a13 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
199 DECOMMISSIONED Grabb & Durando Law Office - Jeff Williams GND-L-4 5f5acefbe8b45260ffd55098 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
200 DECOMMISSIONED Grabb & Durando Law Office - Jeff Williams GND-MARTHA 5d8b67355a8a566479b76998 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
201 DECOMMISSIONED Grabb & Durando Law Office - Jeff Williams GND-MELODY 5d8b673cb567a51b2104c370 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
202 MIGRATED Grabb & Durando Law Office - Jeff Williams GND-BOB-PC 66f6e0a0cb4cf07be5f86c0e managed stale lastSeen no-posture | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
203 MIGRATED Grabb & Durando Law Office - Jeff Williams GND-JEFF-2 6a517f7bd7d0c0b92f858041 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
204 DECOMMISSIONED Horseshoe Management - Bill Young HSM-DONNA 5d676a255a34a14bfa927358 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
205 DECOMMISSIONED Horseshoe Management - Bill Young HSM-FRANK 5d676a5bc00bc54bf3108b8d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
206 DECOMMISSIONED Horseshoe Management - Bill Young HSM-RANDI 666388621490b1ebecbd7d28 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
207 DECOMMISSIONED Horseshoe Management - Bill Young HSM-SURFACE 66fcdceecb79455ee9cd7296 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
208 MIGRATED Horseshoe Management - Bill Young HSM-BILL 66f6e02a85d9f362d35420ea unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
209 MIGRATED Horseshoe Management - Bill Young HSM-FRANK02 66635af3e7e9e7d2105acecc unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
210 MIGRATED Horseshoe Management - Bill Young HSM-NEWSERVER 627af44cab5f6082fea31005 unmanaged mtype=1 | Windows Server 2019 Essentials RMM 0d; Datto yes; no-live-BD
211 DECOMMISSIONED Instrumental Music Center - Leslie Faltin IMC-M-EDSERVICE 6a4be5c9487e29403f73c193 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
212 MIGRATED Instrumental Music Center - Leslie Faltin C2B 6a487f5677b615646b11307d unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
213 MIGRATED Instrumental Music Center - Leslie Faltin DESKTOP-44L80C0 6a487f682dd91ce4845bb6c2 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
214 MIGRATED Instrumental Music Center - Leslie Faltin DESKTOP-GHG12G3 6a487f55220ff2f93ab7ccc1 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
215 MIGRATED Instrumental Music Center - Leslie Faltin DESKTOP-MR3ALTK 6a487f541933628dc5488870 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
216 MIGRATED Instrumental Music Center - Leslie Faltin IMC-L1-STATION9 6a487f55c88178e3f4aaf1cb unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
217 MIGRATED Instrumental Music Center - Leslie Faltin IMC-LESSONS 6a487f55ddc985cabdceae6a unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
218 MIGRATED Instrumental Music Center - Leslie Faltin IMC-LUIS 6a4bc78a4f8e1d54db50f73f unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
219 MIGRATED Instrumental Music Center - Leslie Faltin IMC-MINI 6a487f5640ffe040cccce0e6 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
220 MIGRATED Instrumental Music Center - Leslie Faltin IMC-STATION1 6a487f56b1c1d8ff52c05d29 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
221 MIGRATED Instrumental Music Center - Leslie Faltin IMC-STATION2 6a487f561abbaadb83f548a9 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
222 MIGRATED Instrumental Music Center - Leslie Faltin IMC-SVCSTR 6a487f55ab4696625a449034 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
223 MIGRATED Instrumental Music Center - Leslie Faltin IMC1 681290f6ad2aebf7b838bbde unmanaged mtype=1 | Windows Server 2016 Standard RMM 0d; Datto yes; no-live-BD
224 MIGRATED Instrumental Music Center - Leslie Faltin LAPTOP-DCHQ3F92 6a487f5634e7c7cb1256fd05 unmanaged mtype=1 | Windows 10 Home RMM 0d; Datto yes; no-live-BD
225 MIGRATED Jimmy Company - Jimmy Hughes BLASTER2 666600421ea6cb6a5dac25ad unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
226 DECOMMISSIONED Khalsa Montessori School - Michael Herrera AD-JANICE-D 669813f875fcea56c7c3d3c5 managed stale lastSeen 529d | Windows 10 Pro RMM absent; Datto no; no-live-BD
227 DECOMMISSIONED Khalsa Montessori School - Michael Herrera CAM-STAFFROOM-D 667321e4098c23fb2a620f19 managed stale lastSeen 414d | Windows 10 Pro RMM absent; Datto no; no-live-BD
228 DECOMMISSIONED Khalsa Montessori School - Michael Herrera DESKTOP-9LT4ICC 666c55d14912423a2c8a56d1 managed stale lastSeen 342d | Windows 10 Pro RMM absent; Datto no; no-live-BD
229 DECOMMISSIONED Khalsa Montessori School - Michael Herrera DESKTOP-RKKR9KN 67c8b53c940a0a5ca7d052f6 managed stale lastSeen 468d | Windows 11 Pro RMM absent; Datto no; no-live-BD
230 DECOMMISSIONED Khalsa Montessori School - Michael Herrera LAPTOP-NIBQP9LG 667eb0f1667aab6a5401d2dc managed stale lastSeen 631d | Windows 11 Pro for Workstations RMM absent; Datto no; no-live-BD
231 DECOMMISSIONED Khalsa Montessori School - Michael Herrera PRIME-RTG-PC 6665cf3b8348a5f6336e7c85 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
232 DECOMMISSIONED Khalsa Montessori School - Michael Herrera SALMON 667ec0638cad39fd5dd85cd5 unmanaged mtype=1 | Windows Server 2016 Standard RMM absent; Datto no; no-live-BD
233 DECOMMISSIONED Khalsa Montessori School - Michael Herrera TROUT 667edc6da782682d14f64cbb unmanaged mtype=1 | Windows Server 2016 Standard RMM absent; Datto no; no-live-BD
234 DECOMMISSIONED Len Swanson LEN-PC 5c8d5c698baa2522897ee01d managed stale lastSeen 2557d | Windows 7 Home Premium RMM absent; Datto no; no-live-BD
235 DECOMMISSIONED Len's Auto Brokerage - Becky Kahn LAB-005246 65848253d3aea6ddcb23792a unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
236 DECOMMISSIONED Len's Auto Brokerage - Becky Kahn LAB-005248 5e45a44e7b367c215ddc3eea managed stale lastSeen 1838d | Windows 10 Pro RMM absent; Datto no; no-live-BD
237 DECOMMISSIONED Len's Auto Brokerage - Becky Kahn LAB-005251 5e45a4d63d248c5d381da8b6 managed stale lastSeen 1500d | Windows 7 Professional RMM absent; Datto no; no-live-BD
238 DECOMMISSIONED Len's Auto Brokerage - Becky Kahn LAB-005252 6463d32980fc8b5788251e32 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
239 MIGRATED Len's Auto Brokerage - Becky Kahn LAB-BECKY 6463d329bbc719578210eb8d unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
240 DECOMMISSIONED MVAN Enterprises Inc - June Bradford June’s MacBook Pro 654d6731d926169444537a02 unmanaged mtype=1 | macOS Sequoia 15.3.0 RMM absent; Datto no; no-live-BD
241 DECOMMISSIONED MVAN Enterprises Inc - June Bradford MITCH-LAPTOP 6665e1b4d855cb5af2fa96d3 managed stale lastSeen 761d | Windows 11 Pro RMM absent; Datto no; no-live-BD
242 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson BCW-20BQK97 666b3fc3d714274b452e9500 unmanaged mtype=1 | Windows 11 Pro for Workstations RMM absent; Datto no; no-live-BD
243 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson BWA-IK12323 666b40617a4642e103a8c8dd unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
244 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson DISC-GBT6BP1 666b41ca9a6a4778698da00b unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
245 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson DMS-5X8GVD3 666b3eacad64464053d8e6f9 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
246 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson HWM-56V84Z2 666b40938a5a8bf55bf3c521 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
247 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson JMH-FLSDXQ1 666b3f757cf38ef20042a7a7 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
248 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson JPL-B1W39Y1 666b3f89c3928c83d26534d6 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
249 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson KCP-FG3G842 666b3f17d77745c974f4bec0 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
250 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson KSB-JFLTB82 666b3f1602063d43bb4e6366 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
251 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson LIM-614MVD3 666b3f94475a8113dc7e47f4 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
252 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson LNW-7GLTB82 666b3f60d77745c974f4bec3 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
253 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson MACH-GM2Y8P1 666b424da9ba473659422e54 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
254 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson NJN-2W73T12 666b405c436606b5dd4e8b4d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
255 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson PRO-FMM7GZ1 666b3f8eeea213874724bc52 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
256 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson PRW-62C7X04 666b3f8f51d3ad8d8d3b53b5 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
257 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson RW-DH83T12 666b3f734400652fc3935158 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
258 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson SHIP-9PJPWP3 666b3f369f201a1660c99145 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
259 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson SLW-183CQW1 666b527c436606b5dd4e8b68 managed stale lastSeen 452d | Windows 10 Pro RMM absent; Datto no; no-live-BD
260 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson TDM-DKK5KS1 666b40a7d26e9e2fc97338b6 managed stale lastSeen 567d | Windows 10 Pro RMM absent; Datto no; no-live-BD
261 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson TDM-FG3G842 66959b178d9a59668d563074 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
262 DECOMMISSIONED Midway Industries, Inc. - Bryan Wilson TWB-8H73T12 666b408148250c6c4dc18bda unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
263 MIGRATED Mineralogical Record - Tom Gressman DESKTOP-C84J8OS 691619a537206be5436830a1 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
264 DECOMMISSIONED PUTT LAND SURVEYING, INC. - David Putt PLS-FOUR 6690122ea2701cd0495cb42d managed stale lastSeen 416d | Windows 10 Home RMM absent; Datto no; no-live-BD
265 DECOMMISSIONED PUTT LAND SURVEYING, INC. - David Putt PLS-FRONT 67003607182f0f03a7eee78c managed stale lastSeen 199d | Windows 11 Pro for Workstations RMM absent; Datto no; no-live-BD
266 DECOMMISSIONED PUTT LAND SURVEYING, INC. - David Putt PLS-LAPTOP 669013c42f46c7e0dbdfad80 managed stale lastSeen 528d | Windows 10 Home RMM absent; Datto no; no-live-BD
267 DECOMMISSIONED PUTT LAND SURVEYING, INC. - David Putt PLS-ONE 6690264d056706a244f11dd8 managed stale lastSeen 567d | Windows 10 Pro RMM absent; Datto no; no-live-BD
268 MIGRATED Quantum Wealth Management - Sheila Peress QUANTUMSERVER 6665cf20dd4cd55b318c511a unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto yes; no-live-BD
269 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V DESKTOP-09H7T66 67228367cbe6b4c2bc288d69 managed stale lastSeen 199d | Windows 11 Home RMM absent; Datto no; no-live-BD
270 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V DESKTOP-V2247T9 672262a52162eaefe712e706 managed stale lastSeen 380d | Windows 11 Home RMM absent; Datto no; no-live-BD
271 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V FWSERVER 5c6f14666850d34c5aba5913 managed stale lastSeen 1704d | Windows 7 Professional RMM absent; Datto no; no-live-BD
272 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V FWW-ERIKALAPTOP 62dab5eeea4692a14a255e7d unmanaged mtype=1 | Windows 10 Home RMM absent; Datto no; no-live-BD
273 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V LAPTOP-EJL9PFOU 67f825d6a2cf678ddb7fe2b6 managed stale lastSeen 372d | Windows 11 Home RMM absent; Datto no; no-live-BD
274 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V LAPTOP-FR4QN9KF 67f825b9adbda4a403c2d692 managed stale lastSeen 372d | Windows 11 Home RMM absent; Datto no; no-live-BD
275 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V LAPTOP-JQMU8N9K 67f825c1954153156bfb1c68 managed stale lastSeen 372d | Windows 11 Home RMM absent; Datto no; no-live-BD
276 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V RELIANT-L01 68923903dfab0192d9ca8cb7 managed stale lastSeen 299d | Windows 11 Pro RMM absent; Datto no; no-live-BD
277 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V RELIANT-L02 689239143ef4423d63b163d8 managed stale lastSeen 339d | Windows 11 Pro RMM absent; Datto no; no-live-BD
278 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V RELIANT-L03 689238d65268b36f707e1443 managed stale lastSeen 339d | Windows 11 Pro RMM absent; Datto no; no-live-BD
279 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V RWD-ALANLAPTOP 67226e3116029df6801d1a85 managed stale lastSeen 133d | Windows 11 Home RMM absent; Datto no; no-live-BD
280 DECOMMISSIONED Reliant Well Drilling and Pump Corporate - Channa V WILCOXADMIN 67101a81c3d6dd4666e09551 managed stale lastSeen 596d | Windows 8.1 RMM absent; Datto no; no-live-BD
281 DECOMMISSIONED Russo Law Firm - Shannon Trionfo RRSAD 6294eca9bc14cc060b47df4c unmanaged mtype=1 | Windows Server 2019 Standard RMM absent; Datto no; no-live-BD
282 DECOMMISSIONED Russo Law Firm - Shannon Trionfo RUSSOADMIN 60f639b001f0c2896cbc023b unmanaged mtype=1 | Windows Server 2008 R2 Standard RMM absent; Datto no; no-live-BD
283 DUPLICATE Russo Law Firm - Shannon Trionfo STRIONFO 660c5e1bad9b30fdbc120012 dup; keep 67290a8e932f963c6b82d29a (mgd) | this unmgd Windows 10 Pro RMM 0d; Datto yes; BD-live-agent
284 DECOMMISSIONED Safesite LLC - Tim Story 1122-SUZANNE-DE 6932e9ac8765ee735b0d8b8d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
285 DECOMMISSIONED Safesite LLC - Tim Story 1123DELL3540-2 69323aaccf244c5062a29338 unmanaged mtype=1 | Windows 11 Pro RMM absent; Datto no; no-live-BD
286 MIGRATED Safesite LLC - Tim Story 0122-DENIM-DELL 6932e975b71a6a52a89e0043 unmanaged mtype=1 | Windows 11 Home RMM 0d; Datto yes; no-live-BD
287 MIGRATED Safesite LLC - Tim Story 0524-DELLG16 6932ee6332646df3c0243aaf unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
288 MIGRATED Safesite LLC - Tim Story 0525-ASUSFX707Z 693253fd9b6c8af9f094e8aa unmanaged mtype=1 | Windows 11 Home RMM 0d; Datto yes; no-live-BD
289 MIGRATED Safesite LLC - Tim Story 0621-DELL3502 6932d7adf3bfca192e2f2b0d unmanaged mtype=1 | Windows 11 Pro for Workstations RMM 0d; Datto yes; no-live-BD
290 MIGRATED Safesite LLC - Tim Story 0624DELL3550 6932dfe12b64a9bfd3c83a6a unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
291 MIGRATED Safesite LLC - Tim Story 0923-DELL616 6932f26ce03c1570408e6da7 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto yes; no-live-BD
292 MIGRATED Safesite LLC - Tim Story SAFESITE-LT04 693342ee84f072623b49ebec unmanaged mtype=1 | Windows 11 Home RMM 2d; Datto yes; no-live-BD
293 DECOMMISSIONED Saguaro Conveyor Equipment - Rick Ostrop DESKTOP-DGM4C1T 66fabad76751f1db6125803b managed stale lastSeen 303d | Windows 10 Pro RMM absent; Datto no; no-live-BD
294 DECOMMISSIONED Saguaro Conveyor Equipment - Rick Ostrop DESKTOP-L0U6QUN 67290b3b9516187d08294c2d managed stale lastSeen 303d | Windows 10 Pro RMM absent; Datto no; no-live-BD
295 DECOMMISSIONED Saguaro Conveyor Equipment - Rick Ostrop RICKOSTROP-PC 6704d83114f1c3dd2c7983b7 managed stale lastSeen 303d | Windows 10 Pro RMM absent; Datto no; no-live-BD
296 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan DESKTOP-9204DI5 69dd41be340d40b4c8073064 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
297 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-60JJ 6144e71bf2cfcb1a4a2cef14 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
298 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-868J 6145022429c59ab2a1a5a586 unmanaged mtype=1 | Windows 10 Pro for Workstations RMM absent; Datto no; no-live-BD
299 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-BGZH 6144e72a815369c338c13dd1 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
300 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-BGZS 6144e7336518d2e3c8fdc2c8 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
301 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-BWY2 69dd41be340d40b4c8073061 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
302 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-GCX1 69dd41be340d40b4c8073065 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
303 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-LHKQ 69dd41be340d40b4c8073063 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
304 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-DT-XN22 69dd41be340d40b4c8073062 unmanaged mtype=1 | Windows RMM absent; Datto no; no-live-BD
305 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-LT-4WMN 6144e7585bcc1d3b54a9282d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
306 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-LT-9JPN 6146664f598f9470676baf5d unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
307 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SD-LT-CWPN 6146378f11163bd471d87b35 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
308 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SDO-CHAIRPERSON 5d55723a0b2c750908be3f9b unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
309 DECOMMISSIONED Sells District - Tohono O'odham Nation - Delmarie Pan SELLS-SHELLSTOR 6144e77053b3b5c433dc0c87 unmanaged mtype=1 | Windows 7 Professional RMM absent; Datto no; no-live-BD
310 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-FJUAN 61b21623275b90362889bbd3 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
311 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-01 62b0f15d0c41724c800f5cf4 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
312 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-02 61b1625cfcaf19929d5617b5 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
313 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-03 65f0db94af36077f61bee8bf managed stale lastSeen 716d | Windows 10 Pro RMM absent; Datto no; no-live-BD
314 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-13 6584c7c3f5717670a9970390 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
315 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-14 62b634e734e19a5493f9e307 managed stale lastSeen 722d | Windows 10 Pro RMM absent; Datto no; no-live-BD
316 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-15 615e51e47d5a6346e1504427 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
317 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-18 65f0ce2b50d2181f62a600c5 managed stale lastSeen 722d | Windows 10 Pro RMM absent; Datto no; no-live-BD
318 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-19 632a40031c5fbf2393bde427 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
319 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-L-9 62b3bf68a1574dc358791717 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
320 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-RWOOD 615f0bf046e7be8b63e5febd unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
321 DECOMMISSIONED Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-TREASURER-D 615e4da8023769c17b584783 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
322 DECOMMISSIONED Stamback Septic - Joe Schmuker SSS-SPARE 6665cf3cc910b9822d07395e managed stale lastSeen 393d | Windows 11 Pro for Workstations RMM absent; Datto no; no-live-BD
323 DECOMMISSIONED SunDanzer Refrigeration - David Bergeron ENGR-COMPUTER 67bf5439db2464cade8b4e97 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
324 DECOMMISSIONED The Prairie Schooner - Tina Coalter DESKTOP-SRUOH4R 66fab14a3164148712d02dcf managed stale lastSeen 316d | Windows 10 Pro RMM absent; Datto no; no-live-BD
325 DECOMMISSIONED The Prairie Schooner - Tina Coalter JAYMINEWDELL 5d9639845c1950227c339213 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
326 MIGRATED The Prairie Schooner - Tina Coalter TPS-SERVER 665900e22a5978810642f297 unmanaged mtype=1 | Windows Server 2016 Essentials RMM 0d; Datto yes; no-live-BD
327 DUPLICATE Tom Sorensen TOM-WIN7 5ebd66174c004c2ddc701ebd dup; keep 66fabc590627e2af49646a95 (mgd) | this unmgd Windows 10 Pro RMM 0d; Datto yes; BD-live-agent
328 DECOMMISSIONED Tucson Mountain Motors - Pete Jacoby TMM-FRONT 5c92c1d797a6682e089c71e0 unmanaged mtype=1 | Windows 7 Home Premium RMM absent; Datto no; no-live-BD
329 DECOMMISSIONED Tucson Safety and Medical - Gerald Long DESKTOP-AJT3O8I 5e19c4bef6686c3db8a55478 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
330 DECOMMISSIONED Tucson Safety and Medical - Gerald Long POS1-PC 5e190c90cd272c67f77f93fd managed stale lastSeen 2212d | Windows 10 Pro RMM absent; Datto no; no-live-BD
331 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TSAM-DRIVER2 670da21f48088defd39eafdb managed stale lastSeen 284d | Windows 10 Pro RMM absent; Datto no; no-live-BD
332 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TSAM-JERRY 68acabd09b5e43255082a6d5 unmanaged mtype=1 | Windows 10 Pro for Workstations RMM absent; Datto no; no-live-BD
333 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TSAM-KAYTIE 68acabcfc3b0e1613a13fa4e unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
334 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TSAM-SERVER 6665cf2762e4cb63e90285cf unmanaged mtype=1 | Windows Server 2016 Essentials RMM absent; Datto no; no-live-BD
335 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TSM-FRONTDESK 670c204cd12823c4ad1aa76b managed stale lastSeen 408d | Windows 10 Pro RMM absent; Datto no; no-live-BD
336 DECOMMISSIONED Tucson Safety and Medical - Gerald Long TUCSONSAFETY-LT 5e1243aa1f985c21c3d81aa4 managed stale lastSeen 2097d | Windows 7 Professional RMM absent; Datto no; no-live-BD
337 DECOMMISSIONED Universal Cryogenics - Richard Schickling AARDMAN 67361977fa2612fcc78649df managed stale lastSeen 513d | Windows 10 Pro RMM absent; Datto no; no-live-BD
338 DECOMMISSIONED Universal Cryogenics - Richard Schickling GARFIELD 666a0e29eadf86115042969e managed stale lastSeen 231d | Windows 10 Pro RMM absent; Datto no; no-live-BD
339 DECOMMISSIONED Universal Cryogenics - Richard Schickling SCOOBY 66673da0a711ab8d2ebd39e5 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
340 DECOMMISSIONED Universal Cryogenics - Richard Schickling SNOOP 63594bf032d88ee20e0f5ca4 managed stale lastSeen 1352d | Windows 10 Pro RMM absent; Datto no; no-live-BD
341 DECOMMISSIONED Yvonne Tedards BILL 5d20fe4c32551b7ecbd20b21 unmanaged mtype=1 | Windows 10 Home RMM absent; Datto no; no-live-BD
342 DECOMMISSIONED Yvonne Tedards DESKTOP-SUFJR0J 5d20ffe95967865bb2c7f194 unmanaged mtype=1 | Windows 10 Home RMM absent; Datto no; no-live-BD
343 DECOMMISSIONED Yvonne Tedards TEDARDSLAPTOP 5e7a5ac10d4d5e5dc8e47742 unmanaged mtype=1 | Windows 10 Pro RMM absent; Datto no; no-live-BD
344 DUPLICATE Zeus Nestora - Cindy White CINDYW 6665cf3e963d87e89ca1f311 dup; keep 5e63f85708f6f833d2be52e0 (unmgd) | this unmgd Windows 10 Home RMM 0d; Datto yes; no-live-BD
345 MIGRATED Zeus Nestora - Cindy White CINDYW 5e63f85708f6f833d2be52e0 unmanaged mtype=1 | Windows 7 Home Premium RMM 0d; Datto yes; no-live-BD
346 MIGRATED Zeus Nestora - Cindy White SERVER 66425bba4bda5fb351ea0091 unmanaged mtype=1 | Windows Server 2016 Essentials RMM 0d; Datto yes; BD-live-agent

4
.audit/uncertain.csv Normal file
View File

@@ -0,0 +1,4 @@
company,hostname,endpoint_id,reason,evidence
Safesite LLC - Tim Story,0124-DELL3540,6936d62a85cfdaad1acd6b5a,managed stale lastSeen 80d | Windows 11 Pro,RMM absent; Datto yes; no-live-BD
Safesite LLC - Tim Story,0323-LENOVO-NIC,6932f160a6b469cdd086c074,unmanaged mtype=1 | Windows 11 Pro,RMM 17d; Datto yes; no-live-BD
Tucson Safety and Medical - Gerald Long,OFFICE1,5e137b0ad161a869f1785dab,unmanaged mtype=1 | Windows 7 Professional,RMM absent; Datto yes; no-live-BD
1 company hostname endpoint_id reason evidence
2 Safesite LLC - Tim Story 0124-DELL3540 6936d62a85cfdaad1acd6b5a managed stale lastSeen 80d | Windows 11 Pro RMM absent; Datto yes; no-live-BD
3 Safesite LLC - Tim Story 0323-LENOVO-NIC 6932f160a6b469cdd086c074 unmanaged mtype=1 | Windows 11 Pro RMM 17d; Datto yes; no-live-BD
4 Tucson Safety and Medical - Gerald Long OFFICE1 5e137b0ad161a869f1785dab unmanaged mtype=1 | Windows 7 Professional RMM absent; Datto yes; no-live-BD

View File

@@ -0,0 +1,47 @@
company,hostname,endpoint_id,reason,evidence
Curtis Plumbing - Ken Curtis,DESKTOP-1ORLAQ1,66fac36cbe6e24d604885121,unmanaged mtype=1 | Windows 11 Pro for Workstations,RMM 0d; Datto no; no-live-BD
Curtis Plumbing - Ken Curtis,SRV02,5dcdd5bb2d95236828a0c659,unmanaged mtype=1 | Windows Server 2008 R2 Standard,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,ALBFURNANCE,69441880c8c72a20a1641704,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,ALBSHIPPING,69441880bd56e52a331456fb,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,BOIPATRICK,66731c114a0c063dec1791f4,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,BRLKUMASI,69441890fe062c8c9daae194,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,DENPERFECTCUT,69441888dd21243a43131eb4,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTI-FINANCESVR,646811189bf31557bf3bf807,unmanaged mtype=1 | Windows Server 2022 Standard Evaluation,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTI-TUC-VM,66731c111bf6653667c2f968,unmanaged mtype=1 | Windows Server 2012 R2 Standard,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTIALBLINDAOM,6944187f009463b1c08dcc86,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTIBRLIG,69441883ee5579c8fb6ad1ac,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTIDENSDOOR,66731c06cc38d43d4ef92760,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTIGLAZSTEVE,6944188018a4e7959792e084,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTIPHXSM,69441883f8b8209b6c253774,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,GTISLCALUM,694418842801b3e87a5f1ba4,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,LEIAGMBOI,6944187dc7c91272245cacdf,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,LOGISTICS1,6944419b87fec01d63fd162d,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,OFFICEGLAZPRO,69441883b9633c9729c41b1d,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,PEBBLES,694418819c69d194cdcbfefe,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,PERFECTCUTBRL,69441882b6b2d1b702f66049,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,PHXCSR1,69441880a977c1e2255ed327,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,PHXDIANA,69441884ee5579c8fb6ad1ae,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,PHXTRACER,6944187e42a684e559b80653,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SANCSR1,69441880ea7287f8e67b7736,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SANFURNACE2,69441884ae9615383a448af2,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SANSHOP1,6944188290517a16a4b97d34,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SANSHOWERD,6944187f6c4b6de750744514,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SHVERIN,69441883bb339ec4331fd732,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SHVWASHER,69441883abb02940255fd416,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,STEVEHOMEPC,6944187f61d497d416fce5e3,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,STEVEMLAPTOP,69443b5adc4c18389626274c,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,SUEHOMEW11,6944187e64f9a23fd1cdffec,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,TOMHOME1,694422debfa97ea38078b3ea,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,TUCCLERK1,69441882cf55df39f96452b2,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,TUCPERFECTTABLE,6944188280bc46b1234c0685,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,TUCTRACER,694426d1c2567b39fe785c15,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Glaz-Tech Industries - Steve Eastman,W11I78716G21225,699f676581bc6dd53e315a78,unmanaged mtype=1 | Windows 11 Pro,RMM 0d; Datto no; no-live-BD
Grabb & Durando Law Office - Jeff Williams,GND-L-1,6a517f7990ebd4fe7c130c69,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Len's Auto Brokerage - Becky Kahn,LAB-MADONNA,6463d325c392e3c8ea4ebc60,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Sif-oidak District - Tohono O'odham Nation - Alex Cr,SIF-SERVER2,615e4de15567dc4ca397a8a2,unmanaged mtype=1 | Windows Server 2016 Standard,RMM 0d; Datto no; no-live-BD
Universal Cryogenics - Richard Schickling,DESKTOP-PMML1JC,666619bb98bc7f4740c92164,unmanaged mtype=1 | Windows 10 Pro,RMM 7d; Datto no; no-live-BD
Universal Cryogenics - Richard Schickling,GROMIT,6665cf3df2bcdb4df67cc655,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Universal Cryogenics - Richard Schickling,HOBBES,6665cf3c83ff4404e082bf5e,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Universal Cryogenics - Richard Schickling,PLUTO,63596b68616a033cea3bddea,managed stale lastSeen 1289d | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
Universal Cryogenics - Richard Schickling,UC2-SERVER,5df93532bff7d24b1911a3d1,unmanaged mtype=2 | Windows Server 2012 R2 Essentials,RMM 4d; Datto no; no-live-BD
Zeus Nestora - Cindy White,ZEUS-ACCOUNTING,6667178102b261593a4569ab,unmanaged mtype=1 | Windows 10 Pro,RMM 0d; Datto no; no-live-BD
1 company hostname endpoint_id reason evidence
2 Curtis Plumbing - Ken Curtis DESKTOP-1ORLAQ1 66fac36cbe6e24d604885121 unmanaged mtype=1 | Windows 11 Pro for Workstations RMM 0d; Datto no; no-live-BD
3 Curtis Plumbing - Ken Curtis SRV02 5dcdd5bb2d95236828a0c659 unmanaged mtype=1 | Windows Server 2008 R2 Standard RMM 0d; Datto no; no-live-BD
4 Glaz-Tech Industries - Steve Eastman ALBFURNANCE 69441880c8c72a20a1641704 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
5 Glaz-Tech Industries - Steve Eastman ALBSHIPPING 69441880bd56e52a331456fb unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
6 Glaz-Tech Industries - Steve Eastman BOIPATRICK 66731c114a0c063dec1791f4 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
7 Glaz-Tech Industries - Steve Eastman BRLKUMASI 69441890fe062c8c9daae194 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
8 Glaz-Tech Industries - Steve Eastman DENPERFECTCUT 69441888dd21243a43131eb4 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
9 Glaz-Tech Industries - Steve Eastman GTI-FINANCESVR 646811189bf31557bf3bf807 unmanaged mtype=1 | Windows Server 2022 Standard Evaluation RMM 0d; Datto no; no-live-BD
10 Glaz-Tech Industries - Steve Eastman GTI-TUC-VM 66731c111bf6653667c2f968 unmanaged mtype=1 | Windows Server 2012 R2 Standard RMM 0d; Datto no; no-live-BD
11 Glaz-Tech Industries - Steve Eastman GTIALBLINDAOM 6944187f009463b1c08dcc86 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
12 Glaz-Tech Industries - Steve Eastman GTIBRLIG 69441883ee5579c8fb6ad1ac unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
13 Glaz-Tech Industries - Steve Eastman GTIDENSDOOR 66731c06cc38d43d4ef92760 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
14 Glaz-Tech Industries - Steve Eastman GTIGLAZSTEVE 6944188018a4e7959792e084 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
15 Glaz-Tech Industries - Steve Eastman GTIPHXSM 69441883f8b8209b6c253774 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
16 Glaz-Tech Industries - Steve Eastman GTISLCALUM 694418842801b3e87a5f1ba4 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
17 Glaz-Tech Industries - Steve Eastman LEIAGMBOI 6944187dc7c91272245cacdf unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
18 Glaz-Tech Industries - Steve Eastman LOGISTICS1 6944419b87fec01d63fd162d unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
19 Glaz-Tech Industries - Steve Eastman OFFICEGLAZPRO 69441883b9633c9729c41b1d unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
20 Glaz-Tech Industries - Steve Eastman PEBBLES 694418819c69d194cdcbfefe unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
21 Glaz-Tech Industries - Steve Eastman PERFECTCUTBRL 69441882b6b2d1b702f66049 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
22 Glaz-Tech Industries - Steve Eastman PHXCSR1 69441880a977c1e2255ed327 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
23 Glaz-Tech Industries - Steve Eastman PHXDIANA 69441884ee5579c8fb6ad1ae unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
24 Glaz-Tech Industries - Steve Eastman PHXTRACER 6944187e42a684e559b80653 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
25 Glaz-Tech Industries - Steve Eastman SANCSR1 69441880ea7287f8e67b7736 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
26 Glaz-Tech Industries - Steve Eastman SANFURNACE2 69441884ae9615383a448af2 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
27 Glaz-Tech Industries - Steve Eastman SANSHOP1 6944188290517a16a4b97d34 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
28 Glaz-Tech Industries - Steve Eastman SANSHOWERD 6944187f6c4b6de750744514 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
29 Glaz-Tech Industries - Steve Eastman SHVERIN 69441883bb339ec4331fd732 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
30 Glaz-Tech Industries - Steve Eastman SHVWASHER 69441883abb02940255fd416 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
31 Glaz-Tech Industries - Steve Eastman STEVEHOMEPC 6944187f61d497d416fce5e3 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
32 Glaz-Tech Industries - Steve Eastman STEVEMLAPTOP 69443b5adc4c18389626274c unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
33 Glaz-Tech Industries - Steve Eastman SUEHOMEW11 6944187e64f9a23fd1cdffec unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
34 Glaz-Tech Industries - Steve Eastman TOMHOME1 694422debfa97ea38078b3ea unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
35 Glaz-Tech Industries - Steve Eastman TUCCLERK1 69441882cf55df39f96452b2 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
36 Glaz-Tech Industries - Steve Eastman TUCPERFECTTABLE 6944188280bc46b1234c0685 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
37 Glaz-Tech Industries - Steve Eastman TUCTRACER 694426d1c2567b39fe785c15 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
38 Glaz-Tech Industries - Steve Eastman W11I78716G21225 699f676581bc6dd53e315a78 unmanaged mtype=1 | Windows 11 Pro RMM 0d; Datto no; no-live-BD
39 Grabb & Durando Law Office - Jeff Williams GND-L-1 6a517f7990ebd4fe7c130c69 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
40 Len's Auto Brokerage - Becky Kahn LAB-MADONNA 6463d325c392e3c8ea4ebc60 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
41 Sif-oidak District - Tohono O'odham Nation - Alex Cr SIF-SERVER2 615e4de15567dc4ca397a8a2 unmanaged mtype=1 | Windows Server 2016 Standard RMM 0d; Datto no; no-live-BD
42 Universal Cryogenics - Richard Schickling DESKTOP-PMML1JC 666619bb98bc7f4740c92164 unmanaged mtype=1 | Windows 10 Pro RMM 7d; Datto no; no-live-BD
43 Universal Cryogenics - Richard Schickling GROMIT 6665cf3df2bcdb4df67cc655 unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
44 Universal Cryogenics - Richard Schickling HOBBES 6665cf3c83ff4404e082bf5e unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD
45 Universal Cryogenics - Richard Schickling PLUTO 63596b68616a033cea3bddea managed stale lastSeen 1289d | Windows 10 Pro RMM 0d; Datto no; no-live-BD
46 Universal Cryogenics - Richard Schickling UC2-SERVER 5df93532bff7d24b1911a3d1 unmanaged mtype=2 | Windows Server 2012 R2 Essentials RMM 4d; Datto no; no-live-BD
47 Zeus Nestora - Cindy White ZEUS-ACCOUNTING 6667178102b261593a4569ab unmanaged mtype=1 | Windows 10 Pro RMM 0d; Datto no; no-live-BD

2310
.bb-sp-changes.json Normal file

File diff suppressed because it is too large Load Diff

1
.bb-tok Normal file
View File

@@ -0,0 +1 @@
eyJ0eXAiOiJKV1QiLCJub25jZSI6IlNzRUhBcVJSVzVzOFYwTzBaVVBUdnZGZjRnVXl2YmRvNjFiVklhYjJNbDgiLCJhbGciOiJSUzI1NiIsIng1dCI6ImFGa21LVkZjLTRXVjZzWENCdk5aa1hJNTA1WSIsImtpZCI6ImFGa21LVkZjLTRXVjZzWENCdk5aa1hJNTA1WSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xOWE1NjhlOC05ZTg4LTQxM2ItOTM0MS1jYmMyMjRiMzkxNDUvIiwiaWF0IjoxNzgzMDQ4NDQwLCJuYmYiOjE3ODMwNDg0NDAsImV4cCI6MTc4MzA1MjM0MCwiYWNycyI6WyJwZmRyIl0sImFpbyI6ImsyRmdZQWhtcnMxdW5yZlo2TmVTNUxmbDNzNkZNNXA0YnNoN1RuNGhmc1hJZkhWYjkzMEEiLCJhcHBfZGlzcGxheW5hbWUiOiJDb21wdXRlckd1cnUgLSBUZW5hbnQgQWRtaW4iLCJhcHBpZCI6IjcwOWU2ZWVkLTA3MTEtNDg3NS05YzQ0LTJkMzUxOGM0NzA2MyIsImFwcGlkYWNyIjoiMiIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzE5YTU2OGU4LTllODgtNDEzYi05MzQxLWNiYzIyNGIzOTE0NS8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6IjdhMTk5YjExLTk3ZmItNGU2NS05MTdkLWY4ZDI5YTUzYmE0OSIsInJoIjoiMS5BVVlBNkdpbEdZaWVPMEdUUWN2Q0pMT1JSUU1BQUFBQUFBQUF3QUFBQUFBQUFBQUFBQUJHQUEuIiwicm9sZXMiOlsiUG9saWN5LlJlYWRXcml0ZS5Db25kaXRpb25hbEFjY2VzcyIsIlVzZXIuUmVhZFdyaXRlLkFsbCIsIlNlY3VyaXR5RXZlbnRzLlJlYWQuQWxsIiwiVXNlckF1dGhlbnRpY2F0aW9uTWV0aG9kLlJlYWRXcml0ZS5BbGwiLCJBcHBsaWNhdGlvbi5SZWFkV3JpdGUuQWxsIiwiRGlyZWN0b3J5LlJlYWRXcml0ZS5BbGwiLCJTaXRlcy5SZWFkV3JpdGUuQWxsIiwiQXBwUm9sZUFzc2lnbm1lbnQuUmVhZFdyaXRlLkFsbCIsIlJvbGVNYW5hZ2VtZW50LlJlYWRXcml0ZS5EaXJlY3RvcnkiLCJQb2xpY3kuUmVhZC5BbGwiLCJTaXRlcy5GdWxsQ29udHJvbC5BbGwiXSwic3ViIjoiN2ExOTliMTEtOTdmYi00ZTY1LTkxN2QtZjhkMjlhNTNiYTQ5IiwidGVuYW50X3JlZ2lvbl9zY29wZSI6Ik5BIiwidGlkIjoiMTlhNTY4ZTgtOWU4OC00MTNiLTkzNDEtY2JjMjI0YjM5MTQ1IiwidXRpIjoiRFE2UE4wWlRqMEdSMDhWeEhhUWJBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiYjFiZTFjM2UtYjY1ZC00ZjE5LTg0MjctZjZmYTBkOTdmZWI5IiwiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19hY2QiOjE3NzY3MDg0MDEsInhtc19hY3RfZmN0IjoiOSAzIiwieG1zX2Z0ZCI6IjZuYTJJRXF0MWliaVd5emFrbzdzM0VHbEdnbm1JRlBUSjdFWUZTcEZPcjBCZFhOemIzVjBhQzFrYzIxeiIsInhtc19pZHJlbCI6IjEwIDciLCJ4bXNfcGZ0ZXhwIjoxNzgzMTM4NzQwLCJ4bXNfcmQiOiIwLjQyTGxZQkppZEJNUzRXQVZFcEJ6U3p5M29MSEZaN2RYdGFuWExOTmNJUkVPVGlHQnRJOWVVdy1lZXVrNVFldzVjLU4ybHM5Q0lod2NRZ0xNREJCd0FFb0xpWEJ3Q3dsc2VTRDdOZHpmN3VRaVNha1duNWF0QWdBIiwieG1zX3N1Yl9mY3QiOiIzIDkiLCJ4bXNfdGNkdCI6MTU4MTAyMjkyNywieG1zX3RudF9mY3QiOiIzIDE0In0.Rfz02nCkcm5iJ19EQVEFhZEcBf_xdd6kXTck-paz5UVgRhSWE8TDPfF9eliB38OZ59bRJdkY6TdtUNrlAAHhJxHs8Hxq6vyYJwKAhzfD-XYntD00Mas1yEe1pobc4vjLPrNYl5ie2KIZvSpmvUc97Lvf5juRueRbvUh9k9QgVwKfGBCHVXirBv9Bekhx4XUraskE-UvROmDvf5mi1DtkskMIC4-Xutz6gFTbJDj-uwTtI2vZa9dQMsVJfMpmq6pWD5lNrIm1tk6LMMIBVA00h3ue5hIt7v5IggvFFqk2JlacMnElQPNkgpwaMJTdcwrVEuonVZuKpv3iYZJU8MtCcg

774
.bill.json Normal file
View File

@@ -0,0 +1,774 @@
{
"CurrentSpaceUsed": 88433429895985,
"AverageSpaceUsed": 88104088032015,
"TotalRestore": 93051040061,
"StatisticBilling": [
{
"UserId": "73de908b-6f7a-4bab-9787-fd30549ba71e",
"Email": "AD1-7C7BE300650F",
"FirstName": "AD1",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 2971611409680,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 13.837643,
"RestoreCost": 0.0,
"TotalCost": 138.376439
},
{
"UserId": "0e6c3190-191b-4c61-8535-f6b694621af8",
"Email": "ad2-7C7BE36FD6EC",
"FirstName": "AD2",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 1136175720928,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 5.29073,
"RestoreCost": 0.0,
"TotalCost": 52.907304
},
{
"UserId": "af7b687b-4528-4a30-8205-fca4abf98dc9",
"Email": "AD-3-7C7BE3F37268",
"FirstName": "",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "f1885cea-f62b-4645-9c27-38b51e6e0bf3",
"Email": "admin@azcomputerguru.com",
"FirstName": "Mike",
"LastName": "Swanson",
"CompanyName": null,
"AverageSpace": 15214262052617,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 70.846922,
"RestoreCost": 0.0,
"TotalCost": 757.469281
},
{
"UserId": "27cae1c8-b253-45e1-aafc-cc94801b6a0c",
"Email": "anaisedavid.office@gmail.com",
"FirstName": "David",
"LastName": "Anaise",
"CompanyName": "Anaise, David",
"AverageSpace": 1557150766422,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 7.251048,
"RestoreCost": 0.0,
"TotalCost": 72.510483
},
{
"UserId": "ffa09772-0856-4b63-9a25-b2827d3a86ba",
"Email": "becky@desertrvcenter.com",
"FirstName": "Becky",
"LastName": "Kahn",
"CompanyName": "Desert RV",
"AverageSpace": 44244877358,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 0.206031,
"RestoreCost": 0.0,
"TotalCost": 51.060312
},
{
"UserId": "20e00bf6-56ce-479d-a26c-ca7131df8b3e",
"Email": "beckykahn",
"FirstName": "Becky",
"LastName": "Kahn",
"CompanyName": "Len's Auto",
"AverageSpace": 41654221392,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.193967,
"RestoreCost": 0.0,
"TotalCost": 1.939675
},
{
"UserId": "1a591c90-be53-403d-bc88-17289ab2f7b1",
"Email": "bertie@amtransit.com",
"FirstName": "Bertie",
"LastName": "Lozano",
"CompanyName": "Arizona Medical Transit",
"AverageSpace": 148078878816,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 0.689544,
"RestoreCost": 0.0,
"TotalCost": 55.895458
},
{
"UserId": "e2b8868f-05bf-4531-9f02-5e5787d7d6b4",
"Email": "billie.rose@rinconvistavet.com",
"FirstName": "Auburn",
"LastName": "Heist",
"CompanyName": "Rincon Vista Veterinary",
"AverageSpace": 1867243027446,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 8.695027,
"RestoreCost": 0.0,
"TotalCost": 135.950278
},
{
"UserId": "c1dfddd4-f845-43a3-8e40-d5e30c0450e8",
"Email": "CP-QB-7E3C66D32920",
"FirstName": "",
"LastName": "",
"CompanyName": "Curtis Plumbing",
"AverageSpace": 31120333065,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.144915,
"RestoreCost": 0.0,
"TotalCost": 1.449153
},
{
"UserId": "5897c22d-e119-44d6-bede-c9428d5925ba",
"Email": "cs@curtisplumbing.net",
"FirstName": "Ken",
"LastName": "Curtis",
"CompanyName": "Curtis Plumbing",
"AverageSpace": 92408986181,
"TotalVolumeRestore": 0,
"PlanCost": 12.0,
"StorageCost": 0.430312,
"RestoreCost": 0.0,
"TotalCost": 16.303128
},
{
"UserId": "08d55d69-bf76-40b5-ae45-b1bbe0064c14",
"Email": "CS-SERVER-9163AEDE7357",
"FirstName": "",
"LastName": "",
"CompanyName": "Cascades of Tucson",
"AverageSpace": 322775715004,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 1.503041,
"RestoreCost": 0.0,
"TotalCost": 15.030415
},
{
"UserId": "e7121651-8fac-456e-b666-6069eb4e33c2",
"Email": "D1-ENGI-010-7C7BE39C36E9",
"FirstName": "D1-ENGI",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 110050375049,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.512461,
"RestoreCost": 0.0,
"TotalCost": 5.124619
},
{
"UserId": "69f65bfa-3bdc-49e0-96a3-e59241a95cef",
"Email": "DESKTOP-9T0UAON-64BD1EB86A99",
"FirstName": "DESKTOP-9T0UAON",
"LastName": "",
"CompanyName": "Desert RV",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "06109a61-2ba2-4547-8294-f88d5e1799ba",
"Email": "DESKTOP-BMBTQLI-B16CD561D2DF",
"FirstName": "DESKTOP-BMBTQLI",
"LastName": "",
"CompanyName": "Len's Auto",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "ba5547d4-b5f3-411b-91f8-372ecff9703e",
"Email": "DESKTOP-P36LUUN-EC48473C3133",
"FirstName": "",
"LastName": "",
"CompanyName": "Tucson Coin and Autograph",
"AverageSpace": 65406995238,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.304575,
"RestoreCost": 0.0,
"TotalCost": 3.04575
},
{
"UserId": "4b8fed90-f5a8-4b67-a802-d213f1bea399",
"Email": "DF-HYPERV-B-7C7BE33DF814",
"FirstName": "DF-HYPERV-B",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 330077132393,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 1.537041,
"RestoreCost": 0.0,
"TotalCost": 15.370414
},
{
"UserId": "651e4c79-306c-4fc9-908f-3a9538e08db6",
"Email": "diegobuilder300@gmail.com",
"FirstName": "Diego",
"LastName": "Sosa",
"CompanyName": "Air Pros",
"AverageSpace": 449563391715,
"TotalVolumeRestore": 0,
"PlanCost": 12.0,
"StorageCost": 2.093442,
"RestoreCost": 0.0,
"TotalCost": 32.934426
},
{
"UserId": "0e0acb74-319a-40ff-adf3-e1047fc6cc7e",
"Email": "FILES-D1-7C7BE310B197",
"FirstName": "FILES-D1",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 366553235490,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 1.706896,
"RestoreCost": 0.0,
"TotalCost": 17.068965
},
{
"UserId": "ca27f550-6928-411b-8b5c-2f1754a99107",
"Email": "Gary A Hartman",
"FirstName": "Gary",
"LastName": "Hartman",
"CompanyName": "Hartman",
"AverageSpace": 616800023582,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 2.872198,
"RestoreCost": 0.0,
"TotalCost": 28.721989
},
{
"UserId": "9e377102-a4c4-45e3-b6b3-e6adeaa93db4",
"Email": "GTI-FINANCESVR-5C7487F5EF6B",
"FirstName": "GTI-FINANCESVR",
"LastName": "",
"CompanyName": "Glaztech Industries",
"AverageSpace": 905726974074,
"TotalVolumeRestore": 0,
"PlanCost": 8.0,
"StorageCost": 4.217619,
"RestoreCost": 0.0,
"TotalCost": 50.176198
},
{
"UserId": "019ea151-f10e-4bfb-9a78-71fd38bc469b",
"Email": "GTI-INV-VMHOST-5C7487D16BD2",
"FirstName": "GTI-INV-VMHOST",
"LastName": "",
"CompanyName": "Glaztech Industries",
"AverageSpace": 850669408054,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 3.961238,
"RestoreCost": 0.0,
"TotalCost": 39.612381
},
{
"UserId": "23b9805e-a479-49f4-aa68-0e1c1a424c19",
"Email": "HSM-NewServer-496E31DB4EA2",
"FirstName": "",
"LastName": "",
"CompanyName": "Horseshoe Management",
"AverageSpace": 163048404869,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.759253,
"RestoreCost": 0.0,
"TotalCost": 7.592533
},
{
"UserId": "56f80e2d-6ece-4407-8971-92635547ce1d",
"Email": "imcbackups",
"FirstName": "Leslie",
"LastName": "Faltin",
"CompanyName": "Instrumental Music",
"AverageSpace": 5013481114411,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 23.34584,
"RestoreCost": 0.0,
"TotalCost": 282.458406
},
{
"UserId": "54a647a3-b92a-4c7b-8a02-8a8759fd36ff",
"Email": "info@bestmassageintucson.com",
"FirstName": "Mara",
"LastName": "Concordia",
"CompanyName": "Peaceful Spirit",
"AverageSpace": 2827135401516,
"TotalVolumeRestore": 89345985316,
"PlanCost": 12.0,
"StorageCost": 13.164873,
"RestoreCost": 0.832099,
"TotalCost": 144.480849
},
{
"UserId": "5f09e9d3-82ad-42c4-8d68-f208d8e7645d",
"Email": "jgoodman@ridgetopgroup.com",
"FirstName": "RidgeTop",
"LastName": "Group",
"CompanyName": "Ridgetop Group",
"AverageSpace": 2283709312470,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 10.634349,
"RestoreCost": 0.0,
"TotalCost": 155.343501
},
{
"UserId": "d94b286b-d22e-49c3-8bb1-415848f448f4",
"Email": "jimmyco333@gmail.com",
"FirstName": "Jimmy",
"LastName": "Hughes",
"CompanyName": "Jimmy Company",
"AverageSpace": 302018992454,
"TotalVolumeRestore": 0,
"PlanCost": 12.0,
"StorageCost": 1.406385,
"RestoreCost": 0.0,
"TotalCost": 26.063855
},
{
"UserId": "a15b177b-941f-48ab-b502-1240558c0965",
"Email": "jwilliams@grabblaw.com",
"FirstName": "Jeff",
"LastName": "Williams",
"CompanyName": "Grabb and Durando",
"AverageSpace": 6894302077729,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 32.104095,
"RestoreCost": 0.0,
"TotalCost": 370.040958
},
{
"UserId": "004b8770-f635-4ba2-9ff9-b44b2f781428",
"Email": "KAT1-65F649951294",
"FirstName": "",
"LastName": "",
"CompanyName": "Business Services of Tucson",
"AverageSpace": 687746218542,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 3.202567,
"RestoreCost": 0.0,
"TotalCost": 32.025678
},
{
"UserId": "1c527291-fedf-454e-a8cd-a9a1aeb18819",
"Email": "kaytie@tucsonsafety.com",
"FirstName": "Jerry",
"LastName": "Long",
"CompanyName": "Tucson Safety and Medical",
"AverageSpace": 294437644433,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 1.371081,
"RestoreCost": 0.0,
"TotalCost": 62.71082
},
{
"UserId": "76803ea9-d35a-402a-b518-230ed04ed27d",
"Email": "LAB-SVR-B16CD5A3C6AE",
"FirstName": "LAB-SVR",
"LastName": "",
"CompanyName": "Len's Auto",
"AverageSpace": 84230185552,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.392227,
"RestoreCost": 0.0,
"TotalCost": 3.922273
},
{
"UserId": "554b8232-1647-480b-99d1-cdefa299343f",
"Email": "lahc",
"FirstName": "Mark",
"LastName": "Fillerup",
"CompanyName": "LaHC",
"AverageSpace": 3399164368765,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 15.828592,
"RestoreCost": 0.0,
"TotalCost": 158.285925
},
{
"UserId": "f20eb24d-fcb1-4754-8d87-4073b83e6a51",
"Email": "LeeAnn",
"FirstName": "LeeAnn",
"LastName": "Parkinson",
"CompanyName": null,
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "0e1048a1-c068-4f34-98b2-c46daad0c603",
"Email": "lloydhudson@financialsafeguards.us",
"FirstName": "Lloyd",
"LastName": "Hudson",
"CompanyName": "Financial Safeguards",
"AverageSpace": 3913079945904,
"TotalVolumeRestore": 0,
"PlanCost": 12.0,
"StorageCost": 18.221698,
"RestoreCost": 0.0,
"TotalCost": 194.216984
},
{
"UserId": "3f80c65f-ea43-4e40-bd38-e4a5854d2e1d",
"Email": "lulu.camacho@khalsaschools.org",
"FirstName": "Lulu",
"LastName": "Camacho",
"CompanyName": "Khalsa Montessori School",
"AverageSpace": 251015835161,
"TotalVolumeRestore": 0,
"PlanCost": 129.0,
"StorageCost": 1.168883,
"RestoreCost": 0.0,
"TotalCost": 140.688834
},
{
"UserId": "22dd7062-b7d5-4a07-9711-5d910f4edda1",
"Email": "martell@gainusa.com",
"FirstName": "John",
"LastName": "Martell",
"CompanyName": "Martell and Associates",
"AverageSpace": 2185882852434,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 10.17881,
"RestoreCost": 0.0,
"TotalCost": 150.788102
},
{
"UserId": "45225389-9392-4add-9421-f8981e3bd0af",
"Email": "mdiamos@gmail.com",
"FirstName": "Monica",
"LastName": "Diamos",
"CompanyName": null,
"AverageSpace": 60761796781,
"TotalVolumeRestore": 0,
"PlanCost": 12.0,
"StorageCost": 0.282944,
"RestoreCost": 0.0,
"TotalCost": 14.829441
},
{
"UserId": "f6a8e59d-a070-4473-9a53-ec42921dd876",
"Email": "mel.albert@iceinc.us.com",
"FirstName": "ICE",
"LastName": "inc",
"CompanyName": null,
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "85b4355a-7577-449e-afe4-c7995ded66c1",
"Email": "mike@azcomputerguru.com",
"FirstName": "Michael",
"LastName": "Swanson",
"CompanyName": null,
"AverageSpace": 428191317224,
"TotalVolumeRestore": 0,
"PlanCost": 8.0,
"StorageCost": 1.99392,
"RestoreCost": 0.0,
"TotalCost": 27.93921
},
{
"UserId": "20c09d4b-ef43-44f9-92df-e1b752729bf7",
"Email": "NEPTUNE-15E6653AF349",
"FirstName": null,
"LastName": null,
"CompanyName": "ACG-Internal",
"AverageSpace": 364369247509,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 1.696726,
"RestoreCost": 0.0,
"TotalCost": 16.967265
},
{
"UserId": "d4acd3ae-014c-4f3f-9109-8efee1ca8eda",
"Email": "pbx.intranet.dataforth.com-7C7BE3FE5EBA",
"FirstName": "pbx.intranet",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 11587187443,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.053957,
"RestoreCost": 0.0,
"TotalCost": 0.53957
},
{
"UserId": "43279277-f004-4eb0-ae7c-e2268f160746",
"Email": "rednourcarrievirt-21979DC124A8",
"FirstName": "",
"LastName": "",
"CompanyName": "Rednour Law",
"AverageSpace": 94770838344,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.441311,
"RestoreCost": 0.0,
"TotalCost": 4.413111
},
{
"UserId": "14e8071e-2990-4a29-baae-d09f4c386d28",
"Email": "richard@ucryo.com",
"FirstName": "Richard",
"LastName": "Schickling",
"CompanyName": "Universal Cryogenics",
"AverageSpace": 1390383116718,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 6.474475,
"RestoreCost": 0.0,
"TotalCost": 113.744759
},
{
"UserId": "a39b0d4c-51d8-43c9-84db-1f67ac6cf8ad",
"Email": "rohrbach",
"FirstName": "Mike",
"LastName": "Rohrbach",
"CompanyName": "Rohrbach",
"AverageSpace": 14296926585002,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 66.575251,
"RestoreCost": 0.0,
"TotalCost": 714.752523
},
{
"UserId": "26c4d430-9fbd-453d-b4e3-efd4c0f046ff",
"Email": "SAGE-SQL-7C7BE3224F9A",
"FirstName": "SAGE-SQL",
"LastName": "",
"CompanyName": "Dataforth",
"AverageSpace": 618020252591,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 2.877881,
"RestoreCost": 0.0,
"TotalCost": 28.77881
},
{
"UserId": "c064d061-f0ca-45d7-97f4-39ab8b9cdc12",
"Email": "seastman@glaztech.com",
"FirstName": "Steve",
"LastName": "Eastman",
"CompanyName": "Glaztech Industries",
"AverageSpace": 1981984799718,
"TotalVolumeRestore": 0,
"PlanCost": 49.0,
"StorageCost": 9.229335,
"RestoreCost": 0.0,
"TotalCost": 141.293358
},
{
"UserId": "975e31eb-ccfb-474a-8610-1d60e1716638",
"Email": "SERVER-6FB2906AA1E0",
"FirstName": "",
"LastName": "",
"CompanyName": "Sorensen",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "2a556356-417d-4434-954c-07d5b361aab0",
"Email": "SERVER-9CA8E3FACB16",
"FirstName": "",
"LastName": "",
"CompanyName": "Martell and Associates",
"AverageSpace": 1088831075045,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "46797735-e206-4f87-872d-955224f8a39f",
"Email": "Server-C061FB5C05FD",
"FirstName": "",
"LastName": "",
"CompanyName": "Brett Interiors",
"AverageSpace": 162180926669,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.755213,
"RestoreCost": 0.0,
"TotalCost": 7.552137
},
{
"UserId": "e356b1f5-f17b-4a7d-ba0f-bb269cf74366",
"Email": "Seth-PC-EC484706D26F",
"FirstName": "",
"LastName": "",
"CompanyName": "Tucson Coin and Autograph",
"AverageSpace": 63303717705,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.29478,
"RestoreCost": 0.0,
"TotalCost": 2.947809
},
{
"UserId": "914908da-7508-4c1e-ae3f-2c2db064e2b4",
"Email": "shannont@rrs-law.com",
"FirstName": "Russo Law",
"LastName": "Firm",
"CompanyName": "Russo Law Firm",
"AverageSpace": 6829452664717,
"TotalVolumeRestore": 0,
"PlanCost": 0.005,
"StorageCost": 31.802117,
"RestoreCost": 0.0,
"TotalCost": 318.026171
},
{
"UserId": "f664f30f-900b-43fd-92f8-c76afe6bd010",
"Email": "sorensen",
"FirstName": "Tom",
"LastName": "Sorensen",
"CompanyName": "Sorensen",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "ccb14528-ec07-431b-8cee-17dbaa17d6d7",
"Email": "Stamback",
"FirstName": "",
"LastName": "",
"CompanyName": "Stamback Services",
"AverageSpace": 2996811786965,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
},
{
"UserId": "f057d04d-c0e9-4ae7-a328-a75c20d5dc3b",
"Email": "susan-think-63AE5E1B7FB2",
"FirstName": "",
"LastName": "",
"CompanyName": "Rohrbach",
"AverageSpace": 345550097052,
"TotalVolumeRestore": 3705054745,
"PlanCost": 0.0,
"StorageCost": 1.609093,
"RestoreCost": 0.034506,
"TotalCost": 16.125436
},
{
"UserId": "38905235-0bb7-48a9-8728-de469d544fed",
"Email": "vwp",
"FirstName": "",
"LastName": "",
"CompanyName": "Valleywide Plastering",
"AverageSpace": 931579945707,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 4.338007,
"RestoreCost": 0.0,
"TotalCost": 43.380071
},
{
"UserId": "634e86db-b3e5-4f4e-ae9b-74d83eeb21de",
"Email": "VWP-FILES-BDC660BB0EB7",
"FirstName": "",
"LastName": "",
"CompanyName": "Valleywide Plastering",
"AverageSpace": 63795608336,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.297071,
"RestoreCost": 0.0,
"TotalCost": 2.970714
},
{
"UserId": "5ec66e3f-c90e-4cad-9c74-9c6fb27f09f4",
"Email": "VWP-QBS-BDC660670F99",
"FirstName": "",
"LastName": "",
"CompanyName": "Valleywide Plastering",
"AverageSpace": 16006972692,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.074538,
"RestoreCost": 0.0,
"TotalCost": 0.745382
},
{
"UserId": "30d7edce-1d60-4657-abc1-58ad30210e81",
"Email": "WINFileSvr-BDC660835B68",
"FirstName": "",
"LastName": "",
"CompanyName": "Valleywide Plastering",
"AverageSpace": 938754217053,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 4.371414,
"RestoreCost": 0.0,
"TotalCost": 43.714149
},
{
"UserId": "b07ed172-c783-41a6-9506-556b22084335",
"Email": "y226@tedards.net",
"FirstName": "Bill",
"LastName": "Tedards",
"CompanyName": "Tedards",
"AverageSpace": 0,
"TotalVolumeRestore": 0,
"PlanCost": 0.0,
"StorageCost": 0.0,
"RestoreCost": 0.0,
"TotalCost": 0.0
}
]
}

10
.bk.ps1 Normal file
View File

@@ -0,0 +1,10 @@
$b='C:\ProgramData\acg-backup'
'--- COMPLETE.txt ---'
if (Test-Path (Join-Path $b 'COMPLETE.txt')) { Get-Content (Join-Path $b 'COMPLETE.txt') } else { 'absent' }
'--- dir listing ---'
Get-ChildItem $b | Select-Object Name,Length,LastWriteTime | Format-Table -AutoSize | Out-String
'--- rclone.log tail ---'
Get-Content (Join-Path $b 'rclone.log') -Tail 25
'--- rclone process ---'
$p = Get-Process rclone -ErrorAction SilentlyContinue
if ($p) { 'RUNNING pid=' + $p.Id } else { 'not running' }

7
.chk.ps1 Normal file
View File

@@ -0,0 +1,7 @@
Import-Module BitsTransfer
$j = Get-BitsTransfer -Name 'Win11ARM64ISO' -AllUsers -ErrorAction SilentlyContinue
if ($j) { 'state=' + $j.JobState + ' bytes=' + $j.BytesTransferred + '/' + $j.BytesTotal }
else { 'NO JOB' }
$f = Get-Item 'C:\Users\howar\Desktop\Win11_25H2_English_Arm64_v2.iso' -ErrorAction SilentlyContinue
if ($f) { 'file present size=' + $f.Length }
'uptime-min: ' + [math]::Round(((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime).TotalMinutes,0)

8
.chk2.ps1 Normal file
View File

@@ -0,0 +1,8 @@
Import-Module BitsTransfer
'--- all BITS jobs (AllUsers) ---'
Get-BitsTransfer -AllUsers -ErrorAction SilentlyContinue | Select-Object DisplayName,JobState,BytesTransferred,BytesTotal,OwnerAccount | Format-Table -AutoSize | Out-String
'--- desktop files ---'
Get-ChildItem 'C:\Users\howar\Desktop' -Filter '*.iso*' -Force -ErrorAction SilentlyContinue | Select-Object Name,Length,LastWriteTime | Format-Table -AutoSize | Out-String
'--- tmp BITS file? ---'
Get-ChildItem 'C:\Users\howar\Desktop' -Filter 'BIT*' -Force -Hidden -ErrorAction SilentlyContinue | Select-Object Name,Length | Format-Table -AutoSize | Out-String
'uptime-min: ' + [math]::Round(((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime).TotalMinutes,0)

View File

@@ -30,6 +30,12 @@ failures, production risk). Bump one tier for: security, auth, credential, migra
production, data-loss. Detail: EXTENDED + `.claude/OLLAMA.md`. production, data-loss. Detail: EXTENDED + `.claude/OLLAMA.md`.
## Key rules (always) ## Key rules (always)
- **Simplest solution FIRST.** Start with the least complex thing that could answer the
problem, confirm it works, and only THEN graduate to more advanced/complicated approaches
as the simple one proves insufficient. Don't reach for SSH/plink/multi-hop tooling when a
one-shot query (a single DNS lookup, one API call, one command) would answer it. E.g. "what
IP does the UDM think X is?" = `nslookup X <udm-ip>`, not shelling into the device. Escalate
deliberately, not by default.
- **NO EMOJIS.** Use ASCII markers: `[OK]` `[ERROR]` `[WARNING]` `[INFO]` `[CRITICAL]`. - **NO EMOJIS.** Use ASCII markers: `[OK]` `[ERROR]` `[WARNING]` `[INFO]` `[CRITICAL]`.
- **Skill-first — if a skill/command covers the task, USE IT; never hand-roll the API.** - **Skill-first — if a skill/command covers the task, USE IT; never hand-roll the API.**
When a request maps to an installed skill or slash-command, INVOKE THAT SKILL instead of When a request maps to an installed skill or slash-command, INVOKE THAT SKILL instead of
@@ -38,7 +44,9 @@ production, data-loss. Detail: EXTENDED + `.claude/OLLAMA.md`.
records (e.g. Syncro tickets/invoices) reach a human for cleanup. **Syncro billing/invoicing records (e.g. Syncro tickets/invoices) reach a human for cleanup. **Syncro billing/invoicing
ALWAYS runs through `/syncro` (or `/syncro-emergency-billing`) — no exceptions.** Same for ALWAYS runs through `/syncro` (or `/syncro-emergency-billing`) — no exceptions.** Same for
other covered domains: credentials → `vault`, RMM actions → `/rmm` (+ `rmm-search` to find a other covered domains: credentials → `vault`, RMM actions → `/rmm` (+ `rmm-search` to find a
host), M365 → `remediation-tool`, etc. Knowing the API is NOT a reason to bypass the skill — host), M365 → `remediation-tool`, asking a human a question/decision in Discord (get their
reply back in-session) → `ask-forum` (never hand-roll the Discord API — that footgun cost us
a broken background wait), etc. Knowing the API is NOT a reason to bypass the skill —
the memory rules (e.g. [[feedback_syncro_billing]]) describe what the SKILL does, not a license the memory rules (e.g. [[feedback_syncro_billing]]) describe what the SKILL does, not a license
to free-hand it. Reach for raw API ONLY when no skill fits or the skill genuinely cannot do it to free-hand it. Reach for raw API ONLY when no skill fits or the skill genuinely cannot do it
— and say so explicitly when you do. Mistakes here go to `errorlog.md` (`--correction`). — and say so explicitly when you do. Mistakes here go to `errorlog.md` (`--correction`).

1
.claude/MEMORY.md Normal file
View File

@@ -0,0 +1 @@
- [PS5.1 -Headers $H empty-auth quirk](.claude/memory/ps51_invoke_restmethod_headers_variable.md) - build Graph auth headers inline per call under RMM powershell -File; capture Graph error bodies

View File

@@ -23,7 +23,7 @@
| Run a command / investigate / script on an RMM agent | `/rmm` (find the host first with `rmm-search`) | | Run a command / investigate / script on an RMM agent | `/rmm` (find the host first with `rmm-search`) |
| M365 investigation/remediation, breach check, mailbox/inbox-rule/forwarding audit, tenant sweep | `remediation-tool` | | M365 investigation/remediation, breach check, mailbox/inbox-rule/forwarding audit, tenant sweep | `remediation-tool` |
| Onboard a new M365 tenant to the remediation app suite | `onboard365` | | Onboard a new M365 tenant to the remediation app suite | `onboard365` |
| Backblaze / B2 storage, buckets, backup storage cost | `b2` | | Anything about a customer's **BACKUP** — is X backing up, who backs up where, backup status/failures, or set up / provision a backup | **Backups → wiki-first** (see the "Backups" block right below this table) |
| Bitdefender / GravityZone AV — endpoints, sweeps, policies, quarantine, EDR | `bitdefender` | | Bitdefender / GravityZone AV — endpoints, sweeps, policies, quarantine, EDR | `bitdefender` |
| Datto EDR / Datto AV — detections, isolate, scan, agent deploy | `datto-edr` | | Datto EDR / Datto AV — detections, isolate, scan, agent deploy | `datto-edr` |
| VoIP — PacketDial / OITVOIP / NetSapiens domains, users, DIDs, queues, CDRs | `packetdial` | | VoIP — PacketDial / OITVOIP / NetSapiens domains, users, DIDs, queues, CDRs | `packetdial` |
@@ -34,6 +34,7 @@
| UniFi WiFi tuning / RF / channel analysis | `unifi-wifi` | | UniFi WiFi tuning / RF / channel analysis | `unifi-wifi` |
| Map/repoint a Windows network drive on a remote endpoint | `drive-map` | | Map/repoint a Windows network drive on a remote endpoint | `drive-map` |
| Send someone a Discord DM / copy-paste-friendly link or command | `discord-dm` | | Send someone a Discord DM / copy-paste-friendly link or command | `discord-dm` |
| Ask a teammate a question/decision/sign-off and get their human answer back in-session | `ask-forum` (run the `--wait` as a proper background task — NO shell `&`) |
| Inter-session messaging, fleet todos, resource locks, coord status | `coord` | | Inter-session messaging, fleet todos, resource locks, coord status | `coord` |
| Git / Gitea / submodules / sync health | `gitea` (or `/sync`, `/scc`, `/save` for session ops) | | Git / Gitea / submodules / sync health | `gitea` (or `/sync`, `/scc`, `/save` for session ops) |
| Build/verify GuruRMM agent/server/dashboard, pre-merge check | `gururmm-build` | | Build/verify GuruRMM agent/server/dashboard, pre-merge check | `gururmm-build` |
@@ -46,6 +47,31 @@
> Not listed? If no skill fits, do it directly — and say so. New recurring need → `skill-creator`. > Not listed? If no skill fits, do it directly — and say so. New recurring need → `skill-creator`.
### Backups (wiki-first routing)
A customer can back up through **any** of several backends, so a backup request is a
two-step route: **first find out which backend THIS customer uses, then use that skill.**
Never assume B2/MSP360 — several clients are on Seafile, ownCloud, or Datto Workplace.
1. **Identify the backend for this customer (don't guess).** Read the client wiki
(`wiki/clients/<slug>.md`) and the backup map (`projects/gps-rmm-audit/backup-map.md`) —
both record each customer's real destination. If it isn't recorded, confirm with
`msp360 monitoring --company <name>` plus the per-backend inventory skills, then write the
answer back to the client wiki.
2. **Route to that backend's skill:**
| Need | Skill |
|---|---|
| Is X backing up? status / last run / failures / stale plans (MSP360 + B2 Cloud plans) | `msp360`**authoritative**, check here not bucket contents |
| Who backs up to Seafile / ownCloud / Datto Workplace (inventory + endpoint detection) | `seafile` / `owncloud` / `datto-workplace` |
| Storage / bucket / cost on the destination side | `b2` (Backblaze) |
| **Set up / provision a backup** — create account/user, quota, library, license, destination | the **target backend's** skill: `msp360` (MBS user/company/license), `seafile`, `owncloud` (all writes gated `--confirm`); `b2` for a new bucket/key |
- **Which machine gets backed up is a per-client decision** — report mismatches (billed vs
running), don't provision jobs off a billing count. See [[feedback_backup_targets_never_guessed]].
- Full per-customer destination + health map: `projects/gps-rmm-audit/backup-map.md`;
how backup-status works: memory `[[reference_msp360_backup_monitoring]]`.
--- ---
## Check-skills (work-type → the gate run BEFORE "done") ## Check-skills (work-type → the gate run BEFORE "done")

View File

@@ -0,0 +1,46 @@
---
name: ask-forum
description: Ask a teammate (e.g. Mike) a question in the private #ct-forum Discord forum and BLOCK until a human answers, then act on the reply — a live three-way between the user, this same Claude session, and the teammate. No second bot, no DMs, one tool call (the wait runs server-side in bash).
---
# /ask-forum — human-in-the-loop question via #ct-forum
Entry point to the `ask-forum` skill — full contract in `.claude/skills/ask-forum/SKILL.md`.
Engine: `.claude/scripts/ask-forum.sh`. Lets THIS session put a question
to a human in the private #ct-forum forum and get their answer back in-session, so it
can keep going. The forum post's thread id is the correlation key — the session reads
only the thread it created, so an answer is never confused with another question's.
Forum-only by design (Mike's call, 2026-07-08): DM replies are intentionally not used,
so answers stay visible to the team. The BEAST bot ignores #ct-forum, so the asking
session is the only agent that consumes replies there.
## Usage
```bash
# Ask and block until a human replies (default timeout 600s, poll 8s):
bash .claude/scripts/ask-forum.sh "your question" --tag @264814939619721216
# Re-read a thread you already asked in (one-shot, no waiting):
bash .claude/scripts/ask-forum.sh --read <thread_id>
# Resume blocking on a thread already posted (e.g. after a timeout):
bash .claude/scripts/ask-forum.sh --wait <thread_id> --timeout 1800 --poll 10
```
Flags: `--title "short title"` (forum post name; defaults to first 60 chars of the
question), `--timeout SEC`, `--poll SEC`, `--tag @<discord_id>` (repeatable — pings
that person; ids in `.claude/users.json`: mike 264814939619721216,
howard 624667664501178379, winter 624666486362996755, rob 261978810713505792).
## How to run it well
- **Long waits → background it** with `run_in_background: true` (NO shell `&` — that
forks the wait off and exits early). You get the answer when the human replies.
- Only **non-bot** replies count as answers, so bot chatter can never be mistaken for
the human's answer.
- Exit codes: 0 answered · 2 no token · 3 Discord API error · 4 timeout (thread stays
open — resume with `--wait <thread_id>`).
Channel: #ct-forum (`1522960388432465950`), private — @everyone denied; Howard, Mike,
and the bot allowed. Add someone via a channel permission overwrite if they need access.

View File

@@ -23,7 +23,7 @@ Follow these steps exactly when /inject-standards is invoked:
### Step 2 — Auto-select relevant standards ### Step 2 — Auto-select relevant standards
1. Read `D:/claudetools/.claude/standards/index.yml`. 1. Read `.claude/standards/index.yml`.
2. Review the descriptions for all entries. 2. Review the descriptions for all entries.
3. Select the 25 standards most relevant to either: 3. Select the 25 standards most relevant to either:
- The task description in $ARGUMENTS, or - The task description in $ARGUMENTS, or
@@ -35,7 +35,7 @@ Follow these steps exactly when /inject-standards is invoked:
For each selected standard (in order of relevance): For each selected standard (in order of relevance):
1. Read the file at `D:/claudetools/.claude/standards/<slug>.md`. 1. Read the file at `.claude/standards/<slug>.md`.
2. Display a header: 2. Display a header:
``` ```
=== STANDARD: <slug> === === STANDARD: <slug> ===
@@ -87,8 +87,8 @@ Reads the recent conversation, infers the task type, selects 25 most relevant
## Standards index location ## Standards index location
`D:/claudetools/.claude/standards/index.yml` `.claude/standards/index.yml`
## Standards files location ## Standards files location
`D:/claudetools/.claude/standards/<folder>/<name>.md` `.claude/standards/<folder>/<name>.md`

View File

@@ -159,6 +159,24 @@ Use `python` only when explicitly writing a Python script. Use `script` for save
**VALID `command_type` values ONLY: `shell`, `powershell`, `python`, `script`, `claude_task` (plus alias `cmd` → shell = cmd.exe).** The agent deserializes `command_type` into a Rust enum; an UNKNOWN value (e.g. a made-up type) fails the agent's whole-message JSON parse and the command is **silently dropped — no ack, no result, no error** — which is indistinguishable from a network black-hole and has caused a long mis-diagnosis. On Windows: `powershell` runs powershell.exe (UTF-8 output fixed in-agent); `shell` or `cmd` runs cmd.exe. If a dispatched command sits un-acked forever, FIRST suspect an invalid `command_type` before chasing the network. (Newer agents NAK an unparseable command so it fails fast with a clear stderr instead of black-holing.) **VALID `command_type` values ONLY: `shell`, `powershell`, `python`, `script`, `claude_task` (plus alias `cmd` → shell = cmd.exe).** The agent deserializes `command_type` into a Rust enum; an UNKNOWN value (e.g. a made-up type) fails the agent's whole-message JSON parse and the command is **silently dropped — no ack, no result, no error** — which is indistinguishable from a network black-hole and has caused a long mis-diagnosis. On Windows: `powershell` runs powershell.exe (UTF-8 output fixed in-agent); `shell` or `cmd` runs cmd.exe. If a dispatched command sits un-acked forever, FIRST suspect an invalid `command_type` before chasing the network. (Newer agents NAK an unparseable command so it fails fast with a clear stderr instead of black-holing.)
### Quote-safe dispatch for real scripts (PREFERRED for anything with quotes/UNC/$)
Any PowerShell payload containing embedded double-quotes, UNC `\\` paths, or `$`
that must survive literally should NOT be inlined into the JSON dispatch — the
RMM->cmd.exe layer strips/mangles them (see memory `feedback_windows_quote_stripping`).
Write the script to a file (with the Write tool — bash heredocs collapse `\\`),
then dispatch it byte-exact via `-EncodedCommand`:
```bash
bash .claude/scripts/ps-encoded.sh rmm "$AGENT_ID" script.ps1 --timeout 120 [--user-session]
# or print a paste-safe one-liner for ScreenConnect / plink:
bash .claude/scripts/ps-encoded.sh encode script.ps1
```
Size limit: the agent fails on ~7KB command bodies and encoding inflates ~2.67x,
so keep scripts under ~2KB raw (the helper warns at 4KB encoded, refuses at 6KB).
Inline dispatch below remains fine for simple quote-free commands.
### Basic dispatch ### Basic dispatch
```bash ```bash

View File

@@ -18,6 +18,12 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
/syncro estimate <customer> <subject> Create ticket + linked estimate with line items and private purchase notes /syncro estimate <customer> <subject> Create ticket + linked estimate with line items and private purchase notes
/syncro schedules <customer> List recurring invoice schedules for a customer /syncro schedules <customer> List recurring invoice schedules for a customer
/syncro schedule <id> View a schedule's template and line items /syncro schedule <id> View a schedule's template and line items
/syncro assets <customer> List a customer's RMM assets (read)
/syncro asset <id> Asset detail + patches + installed applications
/syncro asset-update <id> Rename / fix serial / edit an asset (preview-gated)
/syncro move-asset <id> <folder> Move asset to a policy folder (needs token scope - see below)
/syncro rmm-alerts [<customer>] List live RMM alerts; mute/clear by id
/syncro deploy-agent <customer> <host> Push the Syncro RMM agent to a machine via GuruRMM
``` ```
## API Configuration ## API Configuration
@@ -26,6 +32,10 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
**API Key:** per-user tokens in SOPS vault — see "Get API key" below **API Key:** per-user tokens in SOPS vault — see "Get API key" below
**Rate limit:** 180 requests/minute per IP **Rate limit:** 180 requests/minute per IP
**Docs:** https://api-docs.syncromsp.com/ **Docs:** https://api-docs.syncromsp.com/
**Full endpoint catalog:** `.claude/standards/syncro/api-reference.md` — every Syncro call
(~180 endpoints, 38 resource types), verified against the live spec + tenant 2026-07-10.
This file documents the core PSA + asset/RMM/agent-deploy workflows; the reference covers
the rest (contacts, contracts, leads, vendors, POs, products, payments, wiki, policy folders).
## Hard Rules (violations have occurred — no exceptions) ## Hard Rules (violations have occurred — no exceptions)
@@ -82,6 +92,21 @@ $PYTHON -c "import datetime; d = datetime.date(YYYY, M, D); print(d.strftime('%A
If any check fails, complete the missing step before reporting done. This rule fires on initial estimate creation AND on every subsequent "add X to the estimate" request. Incident: 2026-05-22, UPS added to estimate #7189 without a ticket note — caught by Winter. If any check fails, complete the missing step before reporting done. This rule fires on initial estimate creation AND on every subsequent "add X to the estimate" request. Incident: 2026-05-22, UPS added to estimate #7189 without a ticket note — caught by Winter.
**Emailing an invoice/estimate returns 200 "Email sent" even when it reached NOBODY — never
report delivery from the 200 alone.** `POST /invoices/{id}/email` and `/estimates/{id}/email`
take no recipient param; Syncro picks it from the record. If `customer.email` is null (and no
recipient contact is flagged), the send is a silent no-op that still returns HTTP 200. Before
emailing, confirm the customer has a valid `email`. Note `customer.email` is **UNIQUE
tenant-wide** — reusing one returns `{"success":false,"message":["Email has already been
taken"]}`; a `PUT /customers` that fails returns that error shape (not `{"customer":{...}}`),
so check `.success` on customer writes instead of assuming a silent no-op.
**Living test-findings log:** `.claude/standards/syncro/test-findings.md` records verified
behaviors/gotchas discovered while exercising the skill on the Howard Test sandbox
(customer 36118743). Consult + append to it as new API quirks surface — it is how the skill
avoids re-learning the same trap. Critical entries are folded into these Hard Rules and the
Verified Response Shapes table.
## Implementation ## 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). 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).
@@ -96,53 +121,39 @@ Every Syncro API call is attributed to the **owner of the API key**. Comments, l
- **Correcting mis-billed labor** (a debug action) must keep the ORIGINAL tech's `user_id` (their commission). `update_line_item` preserves the existing `user_id`; a remove+add defaults the new line to the API-key owner — so set `user_id` to the original tech on `add_line_item`, or PUT it afterward. Determine the original tech from `.ticket.user_id` and the line's `.user_id`. Don't take a tech's commission just because the math was fixed by someone else. - **Correcting mis-billed labor** (a debug action) must keep the ORIGINAL tech's `user_id` (their commission). `update_line_item` preserves the existing `user_id`; a remove+add defaults the new line to the API-key owner — so set `user_id` to the original tech on `add_line_item`, or PUT it afterward. Determine the original tech from `.ticket.user_id` and the line's `.user_id`. Don't take a tech's commission just because the math was fixed by someone else.
- **Ticket ownership:** adding notes/labor or changing status does NOT change the ticket owner. Multiple techs routinely work one ticket. Only PUT a ticket's `user_id` (reassign owner) when explicitly asked; status PUTs send only `status`. - **Ticket ownership:** adding notes/labor or changing status does NOT change the ticket owner. Multiple techs routinely work one ticket. Only PUT a ticket's `user_id` (reassign owner) when explicitly asked; status PUTs send only `status`.
| identity.json user | Syncro user | user_id | | identity.json user | Syncro user | user_id | vault path |
|---|---|---| |---|---|---|---|
| `mike` | Michael Swanson | 1735 | | `mike` | Michael Swanson | 1735 | `msp-tools/syncro` |
| `howard` | Howard Enos | 1750 | | `howard` | Howard Enos | 1750 | `msp-tools/syncro-howard` |
| `winter` | Winter Williams | 1737 | `msp-tools/syncro-winter` |
Keys are baked into the skill below. To add a new user: generate a token in Syncro → Admin → API Tokens, add a case to the key-select block, and store a backup copy in the vault at `msp-tools/syncro-<user>.sops.yaml`. Keys live in the SOPS vault and are resolved by `.claude/scripts/syncro-env.sh` (source it — never hardcode a key). When `CLAUDETOOLS_REQUESTER_USER` is set (Discord bot threads), syncro-env.sh prefers the REQUESTER's key if one is vaulted, so bot-driven Syncro actions attribute to the person who asked; it falls back to the identity.json user otherwise. To add a new user: generate a token in Syncro → Admin → API Tokens, vault it at `msp-tools/syncro-<user>.sops.yaml`, and add a case to `_syncro_key_path()` in syncro-env.sh.
### Get API key ### Get API key
```bash ```bash
BASE="https://computerguru.syncromsp.com/api/v1" # Resolves CLAUDETOOLS_ROOT + VAULT_ROOT from identity.json and reads the caller's
# per-user Syncro key from the SOPS vault. Never hardcode a repo path or an API key.
#
# The key is per-user on purpose: every action in Syncro is attributed to the key
# owner, so using someone else's key misattributes tickets, time, and invoices.
#
# NOTE: identity.json lives in the REPO (.claude/identity.json), not ~/.claude/.
# Reading ~/.claude/identity.json first is a known bug — it silently picks up a
# stale or absent file. syncro-env.sh resolves the repo copy from its own location.
source "$(git rev-parse --show-toplevel)/.claude/scripts/syncro-env.sh" || {
echo "[ERROR] Cannot resolve Syncro credentials — run onboarding / check the vault" >&2
exit 1
}
# Get repo root from identity.json (set during machine onboarding) BASE="$SYNCRO_BASE"
# Fallback to dynamic detection for legacy machines that haven't updated identity.json yet API_KEY="$SYNCRO_API_KEY"
IDENTITY_PATH="${HOME}/.claude/identity.json" REPO_ROOT="$CLAUDETOOLS_ROOT"
if [ ! -f "$IDENTITY_PATH" ]; then
# Try in-repo identity.json (gitignored, machine-specific)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
if [ -n "$REPO_ROOT" ]; then
IDENTITY_PATH="$REPO_ROOT/.claude/identity.json"
fi
fi
if [ ! -f "$IDENTITY_PATH" ]; then if [ -z "$API_KEY" ]; then
echo "[ERROR] Cannot locate identity.json - run onboarding first" >&2 echo "[ERROR] No Syncro API key for user '${SYNCRO_USER:-unknown}'" >&2
exit 1 exit 1
fi fi
REPO_ROOT=$(jq -r '.claudetools_root // empty' "$IDENTITY_PATH")
if [ -z "$REPO_ROOT" ]; then
# Legacy fallback for machines without claudetools_root in identity.json
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
if [ -z "$REPO_ROOT" ]; then
echo "[ERROR] claudetools_root not set in identity.json and not in a git directory" >&2
echo "[ERROR] Add 'claudetools_root' field to $IDENTITY_PATH" >&2
exit 1
fi
echo "[WARNING] Using git-detected repo root. Add 'claudetools_root' to identity.json to avoid this." >&2
fi
# Per-user keys — actions in Syncro are attributed to the key owner
USER_ID=$(jq -r '.user // empty' "$IDENTITY_PATH")
case "$USER_ID" in
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;;
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;;
*) echo "[ERROR] Unknown user '$USER_ID' in identity.json — cannot select Syncro API key" >&2; exit 1 ;;
esac
``` ```
### Ollama drafting ### Ollama drafting
@@ -572,6 +583,21 @@ Every endpoint's response shape, verified against the live API. Parse exactly as
| Add schedule line | POST `/schedules/{id}/line_items` | `{"schedule_line_item": {...}}` | `.schedule_line_item.id` | | Add schedule line | POST `/schedules/{id}/line_items` | `{"schedule_line_item": {...}}` | `.schedule_line_item.id` |
| Update schedule line | PUT `/schedules/{id}/line_items/{li_id}` | `{"schedule_line_item": {...}}` | `.schedule_line_item.quantity`, `.schedule_line_item.price_retail` | | Update schedule line | PUT `/schedules/{id}/line_items/{li_id}` | `{"schedule_line_item": {...}}` | `.schedule_line_item.quantity`, `.schedule_line_item.price_retail` |
| Delete schedule line | DELETE `/schedules/{id}/line_items/{li_id}` | `{"success": true}` | — | | Delete schedule line | DELETE `/schedules/{id}/line_items/{li_id}` | `{"success": true}` | — |
| Create customer | POST `/customers` | `{"customer": {...}}` | `.customer.id` |
| Update customer | PUT `/customers/{id}` | **success** `{"customer": {...}}` / **error** `{"success": false, "message": [...]}` | check `.success` — email is unique tenant-wide |
| Create contact | POST `/contacts` | **FLAT** `{"id": N, "email": ...}` (NOT wrapped) | `.id` — GET-verify; null-parse ≠ failure |
| Update contact | PUT `/contacts/{id}` | **FLAT** `{"id": N, ...}` | `.id` — `primary`/`receives_invoices` flags are ignored via API |
| Create contract | POST `/contracts` | write OK but body does NOT echo the object | GET `/contracts` + match; never retry on null |
| Email invoice | POST `/invoices/{id}/email` | `{"message": "Email sent."}` | **200 even with NO recipient** — verify `customer.email` first |
| Email estimate | POST `/estimates/{id}/email` | `{"message": "Email sent"}` | **200 even with NO recipient** — verify `customer.email` first |
| Create lead | POST `/leads` | `{"lead": {...}}` | `.lead.id` |
| Create vendor | POST `/vendors` | `{"vendor": {...}}` | `.vendor.id` — no DELETE (persists) |
| Create/Update product | POST/PUT `/products/{id}` | `{"product": {...}}` | `.product.id` — `name`+`description` required; no DELETE |
| Create PO | POST `/purchase_orders` | `{"purchase_order": {...}}` | `.purchase_order.id` — no DELETE |
| Add PO line | POST `/purchase_orders/{id}/create_po_line_item` | `{"po_line_item": {...}}` | needs product `maintain_stock:true` |
| Create worksheet | POST `/tickets/{id}/worksheet_results` | `{"worksheet_result": {...}}` | `.worksheet_result.id` — template ids in `GET /tickets/settings` |
| Create policy folder | POST `/policy_folders` | `{"policy_folder": {...}}` | `.policy_folder.id` — `parent_id` REQUIRED (null → 422); full-scope token |
| Delete policy folder | DELETE `/policy_folders/{id}` | empty body / 200 | full-scope token only |
**Invoice GET line_items field names differ from ticket line_items:** `item` = product name, `name` = description, `price` = unit rate. Do not use `price_retail` when reading invoice line items. **Invoice GET line_items field names differ from ticket line_items:** `item` = product name, `name` = description, `price` = unit rate. Do not use `price_retail` when reading invoice line items.
@@ -613,6 +639,162 @@ COMMENT_ID=$(echo "$COMMENT_RESP" | jq -r '.comment.id')
- Do NOT wrap the payload in `{"comment": {...}}` — returns 422. - Do NOT wrap the payload in `{"comment": {...}}` — returns 422.
- **If `COMMENT_ID` is null:** GET `/tickets/{id}` and check `.ticket.comments[]` by subject before doing anything else. Comments cannot be deleted via API — duplicates require manual GUI removal. - **If `COMMENT_ID` is null:** GET `/tickets/{id}` and check `.ticket.comments[]` by subject before doing anything else. Comments cannot be deleted via API — duplicates require manual GUI removal.
#### Customer Assets (RMM) — read intelligence + write management
Assets are Syncro's RMM device records. Reads are free; writes follow the standard
preview+confirm gate. Response shape is `{"asset": {...}}` (single) / `{"assets": [...]}`
(list). RMM telemetry lives under `.asset.properties.kabuto_information`.
**Reads (verified with per-user token 2026-07-10):**
```bash
# List / search a customer's assets (100/page, .assets[])
curl -s "${BASE}/customer_assets?customer_id=${CUST_ID}&per_page=100&api_key=${API_KEY}" | \
tr -d '\000-\037' | jq -r '.assets[] | "\(.id)\t\(.asset_type_name)\t\(.name)"'
# Asset detail
curl -s "${BASE}/customer_assets/${ASSET_ID}?api_key=${API_KEY}" | tr -d '\000-\037' | \
jq '{id:.asset.id, name:.asset.name, customer_id:.asset.customer_id, policy_folder_id:.asset.policy_folder_id, serial:.asset.asset_serial}'
# Windows patch posture -> {available_patches, available_patches_meta, installed_patches}
curl -s "${BASE}/customer_assets/${ASSET_ID}/patches?api_key=${API_KEY}" | tr -d '\000-\037' | \
jq '{available: (.available_patches|length), installed: (.installed_patches|length)}'
# Installed software inventory (paginated) -> {installed_applications, meta}
# Use this to VERIFY an install (e.g. after /syncro deploy-agent) — see Agent Deployment.
curl -s "${BASE}/customer_assets/${ASSET_ID}/installed_applications?per_page=100&api_key=${API_KEY}" | \
tr -d '\000-\037' | jq -r '.installed_applications[]?.name' | sort -u
```
`updated_at` is NOT a reliable "last online" (bulk account touches move it) — use the
ScreenConnect `GuestInfoUpdateTime` or kabuto data for real last-seen.
**Writes — `POST /customer_assets` (create), `PUT /customer_assets/{id}` (update).**
`name` is **required** on both (422 without it). Accepted body fields: `name`,
`asset_serial`, `asset_type_id`, `asset_type_name`, `customer_id`, `properties`. Preview the
full payload and confirm before writing; post a bot alert after. Response `{"asset": {...}}`.
```bash
# Update (rename / fix serial). Always resend name (required) even when only changing serial.
curl -s -X PUT "${BASE}/customer_assets/${ASSET_ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" --data-binary @- <<JSON
{"name": "${NEW_NAME}", "asset_serial": "${NEW_SERIAL}"}
JSON
```
**Move to a policy folder — `PUT /customer_assets/{id}` with `policy_folder_id` — WORKS, but
is TOKEN-SCOPE-GATED and MUST be capability-checked + verified.** Live-proven 2026-07-10 via
flip-and-restore on ACG-internal asset `12335235` (`692253 → 692278 → 692253`) with Howard's
full-scope token. The spec: *"Updating only `policy_folder_id` requires **Assets - Policy
Change**; with other fields requires **Assets - Edit** AND **Assets - Policy Change**. Nil,
nonexistent, or cross-customer folder IDs return 422."* `name` is required on the PUT, so a
move always sends other fields → needs BOTH scopes.
**The failure mode is SILENT, so never trust the 200.** A token WITHOUT Policy-Change returns
`HTTP 200` and simply does not change the folder (this is the symptom once misread as "RMM is
GUI-only"). `/me` reports coarse `asset:{write:true}` even without Policy-Change, so it cannot
detect this — you must probe and verify:
```bash
move_asset() { # $1=asset_id $2=target_folder_id (needs $BASE $API_KEY)
local AID="$1" TARGET="$2" NAME CUST CUR
local A; A=$(curl -s "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" | tr -d '\000-\037')
NAME=$(echo "$A" | jq -r '.asset.name'); CUST=$(echo "$A" | jq -r '.asset.customer_id')
CUR=$(echo "$A" | jq -r '.asset.policy_folder_id')
# 1) CAPABILITY PREFLIGHT — 200 = token can move; 401 = lacks Assets - Policy Change
local PC; PC=$(curl -s -o /dev/null -w '%{http_code}' "${BASE}/policy_folders?customer_id=${CUST}&api_key=${API_KEY}")
if [ "$PC" = "401" ]; then
echo "[ERROR] This token lacks 'Assets - Policy Change' — cannot move policy folders."
echo " Options: move it in the Syncro web console, OR have a user whose token has"
echo " the scope run it (do NOT silently swap keys — writes attribute to the owner)."
return 1
fi
# 2) PUT (name required), then 3) VERIFY the value actually changed — a 200 no-op = failure
curl -s -X PUT "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" -H "Content-Type: application/json" \
--data-binary "$(jq -nc --arg n "$NAME" --argjson pf "$TARGET" '{name:$n, policy_folder_id:$pf}')" >/dev/null
local NEW; NEW=$(curl -s "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" | tr -d '\000-\037' | jq -r '.asset.policy_folder_id')
if [ "$NEW" = "$TARGET" ]; then echo "[OK] asset ${AID} moved ${CUR} -> ${NEW}"; else
echo "[ERROR] move NOT applied (still ${NEW}) — token likely under-scoped; do it in the console."; return 1; fi
}
```
**Policy-folder route quirks (verified 2026-07-10):** `GET /policy_folders` (bare) → **404**;
`GET /policy_folders?customer_id=N` → **200** (list a customer's folders — how you enumerate
folder IDs); `GET /policy_folders/{id}` → **401** even with full scope. Find valid same-customer
folder IDs from the `?customer_id=N` list or from other assets' `policy_folder_id` values.
**Fleet posture:** Howard's per-user token now has full scope (verified). Other per-user tokens
may not — the preflight above handles that per-run. Recommend granting every per-user Syncro
token the Policy-Change scope so `move-asset` behaves the same regardless of who runs it (Mike's
call — least-privilege alternative is to leave the preflight to degrade gracefully). Corrects
memory `reference-syncro-rmm-api-gui-only`; full detail in `.claude/standards/syncro/api-reference.md`.
**Archive (retire) is still GUI-only — no REST API.** Verified 2026-07-08: archiving is web
UI only (asset Details → **Actions → Archive**, or Assets table → **Bulk Actions →
Archive**). There is no archive endpoint and no `archived` field on the PUT. Do NOT substitute
`DELETE /customer_assets/{id}` — delete is destructive/irreversible, loses history, and breaks
ticket linkages (Archive keeps the asset on its tickets/alerts). To retire: hand the user the
asset list + the Bulk-Actions-→-Archive steps.
#### RMM Alerts
Live RMM alert feed (`GET /rmm_alerts` -> `{rmm_alerts, meta}`, verified 2026-07-10). Reads
free; `POST /rmm_alerts/{id}/mute` and `DELETE /rmm_alerts/{id}` (clear) are gated writes.
```bash
curl -s "${BASE}/rmm_alerts?per_page=50&api_key=${API_KEY}" | tr -d '\000-\037' | \
jq -r '.rmm_alerts[] | "\(.id)\t\(.created_at)\t\(.description // .kind)"'
# Mute: POST /rmm_alerts/${ALERT_ID}/mute | Clear: DELETE /rmm_alerts/${ALERT_ID}
```
#### Agent Deployment (`/syncro deploy-agent`) — hybrid via GuruRMM
Push the Syncro RMM agent to a machine. Syncro's API has **no** installer-download endpoint
(verified: 0 of 116 paths), so this is a **hybrid**: the per-policy-folder installer URL is
harvested once from the Syncro GUI and **vaulted**; **GuruRMM (`/rmm`)** does the push (runs as
SYSTEM). `/rmm diagnose` already *detects* a Syncro agent — this is the deploy half.
**The installer URL is a per-client SECRET and is NOT constructible.** Decoded (2026-07-10):
`https://rmm.syncromsp.com/dl/msi/<b64>` where `<b64>` = base64 of
`v1-{customer_id}-{A}-48753-{policy_folder_id}`. `48753` is OUR account id (constant across all
clients); `customer_id` and `policy_folder_id` are API-readable — but **`A` is an unguessable
~1.8-billion per-client security token that appears nowhere in the API** (checked customer +
policy-folder objects). So you cannot build the URL; it must be harvested per client. Note the
link is an **MSI** (`/dl/msi/`) → silent install is `msiexec /qn`, not the `.exe --console` path.
**Prerequisites (one-time per client):**
1. In Syncro GUI: the client's Policy Folder → **Downloads** → copy the `.../dl/msi/<token>` URL.
2. Vault it (it embeds the secret `A`): `bash .claude/skills/vault/scripts/vault-helper.sh new
clients/<slug>/syncro-agent-installer --kind generic --name "Syncro RMM Installer - <Client>"
--set installer_url=<URL> --set customer_id=<N> --set policy_folder_id=<N>`. Already vaulted:
`cascades-tucson`, `grabb-durando`, `reliant`, `howard-test`.
**Deploy workflow:**
1. Read the URL: `bash .claude/scripts/vault.sh get-field clients/<slug>/syncro-agent-installer
credentials.installer_url`. If the client has no entry, STOP and have the URL harvested first.
2. Resolve the target host in GuruRMM (`/rmm` — hostname → UUID; confirm `os_type` windows +
`is_connected`). Confirm the client↔host mapping with the user.
3. Preview the exact command + target; get explicit confirmation. **deploy-agent is
outward-facing (installs software, may reboot) — always confirm first.**
4. Dispatch via GuruRMM (`command_type: powershell`, SYSTEM). Download the MSI and install silent:
```powershell
$url = '<vaulted installer_url>'
$msi = "$env:TEMP\SyncroSetup.msi"
Invoke-WebRequest -Uri $url -OutFile $msi -UseBasicParsing
Start-Process msiexec.exe -ArgumentList '/i',"`"$msi`"",'/qn','/norestart' -Wait
```
(`.exe` variant, if a client's link is `/dl/exe/`: `Start-Process $exe -ArgumentList
'--console','--allow-force-reboot' -Wait`.)
5. **Verify enrollment:** after ~2-3 min, confirm the new asset appears —
`GET /customer_assets?customer_id=N&query=<host>` — and/or "Syncro" shows in the endpoint's
`installed_applications`.
6. Bot alert (RMM write → `[RMM]`/`[DEPLOY]` routes to #dev-alerts).
**[VERIFY before first production use]** Confirm the `msiexec /qn /norestart` silent behavior on
RMM-TEST-MACHINE (Howard Test) before rolling to a customer. Full detail:
`.claude/standards/syncro/test-findings.md`.
#### Customers #### Customers
```bash ```bash

View File

@@ -0,0 +1,55 @@
---
name: tailscale
description: Manage an ACG-administered Tailscale tailnet via the REST API v2 - list/inspect devices, delete a node, authorize a device, create/list/revoke tagged pre-auth keys. Reads free; writes gated --confirm.
---
# /tailscale — Tailscale tailnet management (REST API v2)
Thin entry point to the `tailscale` skill. Engine:
`.claude/skills/tailscale/scripts/tailscale-api.sh`. Full reference:
`.claude/skills/tailscale/SKILL.md`. Doctrine (per-client tailnets, tagged pre-auth
keys, offboarding): `wiki/patterns/tailscale-client-management.md`.
Read-only by default; every device delete/authorize and auth-key create/delete is
**gated behind `--confirm`** and previews first. ADMIN rights: add + delete.
## Usage
```bash
TS="bash .claude/skills/tailscale/scripts/tailscale-api.sh"
# Reads (no confirm)
$TS status # auth check + tailnet + device count
$TS devices [--json] # list devices
$TS device <name|id|100.x> [--json] # device detail
$TS keys [--json] # list auth keys
# Writes (GATED — preview without --confirm, act with it)
$TS create-key --tag tag:<client> --reusable --preauth [--ephemeral] \
[--expiry-days N] [--desc "..."] --confirm
$TS delete-key <keyId> --confirm # revoke an auth key
$TS authorize <name|id|100.x> --confirm # approve a device
$TS delete-device <name|id|100.x> --confirm # remove a node
# Point at a specific per-client tailnet's vault entry
$TS devices --vault tailscale/roberts.sops.yaml
```
## Rules
1. **Vault-first auth.** Credentials come from `tailscale/api-access.sops.yaml` (OAuth
client preferred, `credentials.api_key` token fallback); tailnet defaults to `-`. Never
hardcode a token. Provisioning commands are in the skill doc.
2. **Read before write.** Device ops resolve a name/100.x to the stable id first; an
**ambiguous match STOPS** (lists candidates, does nothing) — pass a specific id.
3. **Gated writes.** No `--confirm` = preview + exit, no change. Deleting a node drops it
off the tailnet; revoking a key blocks future enrollments with it.
4. **Per-client isolation.** One tailnet per client, one vault entry per tailnet. Confirm
the `--vault`/tailnet before any write.
5. **Key secrets → vault, never chat/commit.** `create-key` prints the secret once; store
it via `/vault` immediately.
6. **Bot alert after every write.** Successful writes auto-post a `[TAILSCALE] ...` line to
Discord (soft-fail); reads do not.
Use this to *drive* the tailnet; use `/rmm` to push the tagged pre-auth key onto client
machines (`tailscale-client-enroll.ps1`) once a key is minted.

View File

@@ -7,34 +7,47 @@ Seed new wiki articles or refresh existing ones from session logs, client docume
## Usage ## Usage
``` ```
/wiki-compile client:<slug> Seed or refresh a client wiki article /wiki-compile client:<slug> UPDATE an existing article (fast, incremental) or seed a new one
/wiki-compile client:<slug> --full Force full recompile of existing article (Sonnet synthesis) /wiki-compile client:<slug> --full REBUILD: full re-synthesis from ALL sources (Sonnet, slow)
/wiki-compile client:<slug> --syncro Syncro dynamic fields only (hours/tickets) — instant, no LLM
/wiki-compile project:<slug> Compile a project wiki article (no Syncro) /wiki-compile project:<slug> Compile a project wiki article (no Syncro)
/wiki-compile system:<slug> Compile a system wiki article (no Syncro) /wiki-compile system:<slug> Compile a system wiki article (no Syncro)
/wiki-compile all Process all missing + stale articles /wiki-compile all Process all missing + stale articles
``` ```
**Mode auto-detection:** **Mode auto-detection:**
- If `wiki/clients/<slug>.md` **does not exist****Seed mode** (full synthesis, Sonnet subagent) - If `wiki/clients/<slug>.md` **does not exist****Seed mode** (full synthesis, Sonnet subagent).
- If `wiki/clients/<slug>.md` **exists** and no `--full` flag → **Refresh mode** (surgical update of dynamic fields only, no subagent) - If it **exists** and no flag → **Update mode** (fast, incremental — the default; see below).
- `--full` flag → **Full recompile** (Sonnet synthesis, preserves existing Patterns/History) - `--full` **Rebuild** (full Sonnet re-synthesis from ALL sources; preserves Patterns/History).
- `--syncro`**Syncro-only refresh** (dynamic fields only; instant, no LLM).
**Update vs Rebuild — why update is fast (this is the point).** A **Rebuild** (`--full`) reads
*every* session log for the client and regenerates the *entire* article with a Sonnet subagent —
correct, but slow and expensive, and wasteful when only one thing changed. **Update** reads ONLY
the session logs dated after the article's `last_compiled` (usually 13 files, often zero) plus the
current article, and applies a few **surgical section edits** — new History rows, and targeted
edits to any section a new log actually changes — leaving the rest of the article byte-for-byte
untouched. Small input + small output + no full-article Sonnet pass = typically many times faster.
Reach for `--full` only when the article structure has drifted, sections are stale/wrong, or you
want a periodic clean rebuild. For "I just did some work, capture it," use plain update.
--- ---
## Phase 0 — Setup ## Phase 0 — Setup
```bash ```bash
CLAUDETOOLS_ROOT="D:/claudetools" # Resolves CLAUDETOOLS_ROOT + VAULT_ROOT from identity.json and reads the caller's
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" # per-user Syncro key from the SOPS vault. Never hardcode a repo path or an API key.
# Exports: CLAUDETOOLS_ROOT, VAULT_ROOT, SYNCRO_USER, SYNCRO_BASE, SYNCRO_API_KEY.
source "$(git rev-parse --show-toplevel)/.claude/scripts/syncro-env.sh" || true
# Syncro auth (read-only operations only — GET requests ONLY in this skill) VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
BASE="https://computerguru.syncromsp.com/api/v1" BASE="$SYNCRO_BASE" # read-only: GET requests ONLY in this skill
USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json") API_KEY="$SYNCRO_API_KEY" # empty if no key vaulted for this user -> skip enrichment
case "$USER_ID" in
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;; if [ -z "$API_KEY" ]; then
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;; echo "[WARNING] No Syncro key for user '${SYNCRO_USER:-unknown}' — Syncro enrichment skipped"
*) echo "[WARNING] Unknown user — Syncro enrichment skipped" ; API_KEY="" ;; fi
esac
``` ```
**Synthesis engine:** seed/full article drafting is done by a **Sonnet subagent** (Agent tool, `model: "sonnet"`), not Ollama. The main agent gathers sources + Syncro data, delegates the draft, then reviews it before writing. No Ollama dependency. **Synthesis engine:** seed/full article drafting is done by a **Sonnet subagent** (Agent tool, `model: "sonnet"`), not Ollama. The main agent gathers sources + Syncro data, delegates the draft, then reviews it before writing. No Ollama dependency.
@@ -65,12 +78,21 @@ esac
if [ ! -f "$CLAUDETOOLS_ROOT/$ARTICLE_PATH" ]; then if [ ! -f "$CLAUDETOOLS_ROOT/$ARTICLE_PATH" ]; then
MODE="seed" MODE="seed"
elif [ "$FULL_FLAG" = "--full" ]; then elif [ "$FULL_FLAG" = "--full" ]; then
MODE="full" MODE="full" # rebuild — full Sonnet re-synthesis
elif [ "$FULL_FLAG" = "--syncro" ]; then
MODE="syncro" # Syncro dynamic fields only
else else
MODE="refresh" MODE="update" # default: fast incremental knowledge merge
fi fi
echo "[INFO] Mode: $MODE | Target: $TARGET_TYPE:$SLUG" echo "[INFO] Mode: $MODE | Target: $TARGET_TYPE:$SLUG"
# For update mode, read the article's last_compiled so Phase 3 can select only newer logs.
LAST_COMPILED=""
if [ "$MODE" = "update" ]; then
LAST_COMPILED=$(sed -n 's/^last_compiled:[[:space:]]*//p' "$CLAUDETOOLS_ROOT/$ARTICLE_PATH" | head -1)
echo "[INFO] Update since last_compiled=${LAST_COMPILED:-unknown}"
fi
``` ```
--- ---
@@ -244,6 +266,28 @@ SOURCE_COUNT=$(echo "$ALL_SOURCES" | grep -c '^' || echo 0)
echo "[INFO] Found $SOURCE_COUNT source files" echo "[INFO] Found $SOURCE_COUNT source files"
``` ```
**Update mode — narrow to NEW sources only (this is the speedup).** In update mode, do NOT read
the full source set. Select only the logs the article has not yet incorporated: a source is "new"
if its filename date is **after** `LAST_COMPILED`, **or** it is not already listed in the article's
frontmatter `sources:`. Read only those.
```bash
if [ "$MODE" = "update" ]; then
# existing sources already folded into the article
EXISTING_SRC=$(awk '/^sources:/{f=1;next} /^[^ -]/{f=0} f&&/^[[:space:]]*-/{sub(/^[[:space:]]*-[[:space:]]*/,"");print}' "$CLAUDETOOLS_ROOT/$ARTICLE_PATH")
NEW_SOURCES=$(echo "$ALL_SOURCES" | while read -r f; do
[ -z "$f" ] && continue
# (a) not yet in the article's sources list?
if ! grep -qxF "$f" <<<"$EXISTING_SRC"; then echo "$f"; continue; fi
# (b) filename carries a YYYY-MM-DD newer than last_compiled?
d=$(echo "$f" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1)
if [ -n "$d" ] && [ -n "$LAST_COMPILED" ] && [ "$d" \> "$LAST_COMPILED" ]; then echo "$f"; fi
done | sort -u | grep -v '^$')
NEW_COUNT=$(echo "$NEW_SOURCES" | grep -c '^' || echo 0)
echo "[INFO] Update: $NEW_COUNT new source(s) since ${LAST_COMPILED:-unknown}"
fi
```
If `SOURCE_COUNT == 0` and no Syncro data: warn and stop. If `SOURCE_COUNT == 0` and no Syncro data: warn and stop.
``` ```
[ERROR] No session logs and no Syncro data found for '${SLUG}'. Cannot compile. [ERROR] No session logs and no Syncro data found for '${SLUG}'. Cannot compile.
@@ -254,7 +298,40 @@ If `SOURCE_COUNT == 0` and no Syncro data: warn and stop.
## Phase 4 — Article Generation ## Phase 4 — Article Generation
### Refresh Mode (existing article, no --full) ### Update Mode (existing article, default) — fast incremental
Fold only what changed since the last compile. **Do NOT re-synthesize the whole article and do
NOT spawn a Sonnet subagent.** Two parts:
**Part A — Syncro dynamic fields** (the three surgical edits documented under *Syncro-only Refresh*
below): hours remaining, Active Work ticket list, and frontmatter (`last_compiled`, `compiled_by`).
**Part B — Incremental knowledge merge** — run ONLY if `NEW_COUNT > 0` (new logs from Phase 3):
1. Read the full text of the `NEW_SOURCES` logs **and the current article**. Do NOT read the full
historical log set — that is what makes this fast.
2. Apply **targeted edits** for what those new logs actually establish:
- **Always:** add one dated row per material change to **History Highlights** (chronological).
- **Infrastructure** — add/adjust a row for any new or removed host, IP, service, or key path.
- **Access** — add any new vault path or access route (vault path only, never the secret).
- **Patterns & Known Issues** — add a genuinely new recurring issue, or mark an existing one
resolved if a new log shows it fixed.
- Touch **only** the sections a new log changes; leave every other byte of the article intact.
3. The delta is small, so **the main agent applies these edits directly** (Edit tool), or delegates
only the prose wording to Ollama Tier-0 / `haiku` and reviews it. Follow the same Hard Rules
(Syncro authoritative for billing; never inline secrets; never invent vault paths).
4. Append the `NEW_SOURCES` paths to frontmatter `sources:` (dedup).
If `NEW_COUNT == 0`: there is nothing new to fold — Part A (Syncro refresh) is the whole update.
Emit:
```
[OK] Update complete for wiki/clients/<slug>.md
- New logs folded: <NEW_COUNT> (since <LAST_COMPILED>)
- Sections touched: History[, Infrastructure, Access, Patterns] | none (Syncro-only)
- Syncro: hours <PREPAY_HOURS>, tickets <TICKET_COUNT>
```
### Syncro-only Refresh (`--syncro`) — instant, no LLM
Perform surgical updates only. No Ollama call. Three edits: Perform surgical updates only. No Ollama call. Three edits:
@@ -298,7 +375,7 @@ After edits, emit:
- Sources: ${SOURCE_COUNT} files tracked - Sources: ${SOURCE_COUNT} files tracked
``` ```
### Seed Mode / Full Recompile — Claude Synthesis (Sonnet subagent) ### Seed Mode / Rebuild (`--full`) — Claude Synthesis (Sonnet subagent)
Prepare the synthesis context by reading the most relevant source files. For session logs, read the full content of client-specific logs and the first 200 lines of root session logs (to avoid overwhelming the prompt). For full recompile, also read the existing article. Prepare the synthesis context by reading the most relevant source files. For session logs, read the full content of client-specific logs and the first 200 lines of root session logs (to avoid overwhelming the prompt). For full recompile, also read the existing article.
@@ -447,4 +524,7 @@ When invoked as `/wiki-compile all`:
- **Never invent vault paths.** If a credential is not mentioned in session logs, write "(verify)" in the Access section. - **Never invent vault paths.** If a credential is not mentioned in session logs, write "(verify)" in the Access section.
- **Never populate Infrastructure tables with placeholder rows.** Only include servers/services that appear in session logs or Syncro assets. - **Never populate Infrastructure tables with placeholder rows.** Only include servers/services that appear in session logs or Syncro assets.
- **Syncro contacts are ground truth for the Profile section.** Do not override with session log guesses if the contact name differs. - **Syncro contacts are ground truth for the Profile section.** Do not override with session log guesses if the contact name differs.
- **Refresh mode never touches Patterns or History.** Those sections require human review or `--full`. - **Syncro-only refresh (`--syncro`) never touches Patterns or History.** It edits dynamic fields only.
- **Update mode may ADD to History (always) and may add/adjust Infrastructure, Access, and Patterns**
strictly from the NEW logs — it never rewrites or removes existing prose. Wholesale re-synthesis
(rewriting existing sections, reconciling contradictions across the full history) is `--full` only.

View File

@@ -10,7 +10,8 @@ Scan for clients and projects that have session logs but no wiki article.
```bash ```bash
# List all client slugs that have session-logs but no wiki article # List all client slugs that have session-logs but no wiki article
cd D:/claudetools # Repo root is machine-specific (C:/ on some, D:/ on others) — never hardcode it.
cd "$(git rev-parse --show-toplevel)"
for dir in clients/*/session-logs; do for dir in clients/*/session-logs; do
slug=$(echo "$dir" | sed 's|clients/||;s|/session-logs||') slug=$(echo "$dir" | sed 's|clients/||;s|/session-logs||')
wiki="wiki/clients/$slug.md" wiki="wiki/clients/$slug.md"
@@ -96,13 +97,17 @@ For every client wiki article that contains a `Syncro customer ID` line, pull li
### Setup ### Setup
```bash ```bash
BASE="https://computerguru.syncromsp.com/api/v1" # Resolves CLAUDETOOLS_ROOT + VAULT_ROOT from identity.json and reads the caller's
USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json") # per-user Syncro key from the SOPS vault. Never hardcode a repo path or an API key.
case "$USER_ID" in source "$(git rev-parse --show-toplevel)/.claude/scripts/syncro-env.sh" || true
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;;
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;; BASE="$SYNCRO_BASE"
*) echo "[SYNCRO] No API key for user '$USER_ID' — skipping Step 6" ; exit 0 ;; API_KEY="$SYNCRO_API_KEY"
esac
if [ -z "$API_KEY" ]; then
echo "[SYNCRO] No API key for user '${SYNCRO_USER:-unknown}' — skipping Step 6"
exit 0
fi
``` ```
### For Each Client Article ### For Each Client Article

55
.claude/hooks/block-tmp-path.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# PreToolUse(Bash) hook: block bash commands that WRITE files under /tmp on
# Windows (Git Bash / MSYS).
#
# Why: MSYS bash and the harness's other tools (Write, Python via py launcher)
# resolve /tmp to DIFFERENT real directories on Windows, so a file curl/tee
# writes to /tmp cannot be read back by the next tool call — the single most
# repeated tmp-path friction in errorlog.md (ref=feedback_tmp_path_windows,
# 6+ hits; also breaks run_in_background shells where TMPDIR is unset).
#
# Windows-only: exits 0 immediately on macOS/Linux where /tmp is one real dir.
# Only WRITE patterns are blocked (redirects, tee, -o/--output, cp/mv/mktemp
# targets). Reads (ls/cat/rm /tmp/...) pass.
#
# Dual-driver like block-backslash-winpath.sh: handles Claude (tool_input) and
# Grok (toolInput) event shapes; emits Grok decision JSON when denying there.
case "$(uname -s 2>/dev/null)" in
MINGW*|MSYS*|CYGWIN*) ;; # Windows Git-bash: the mismatch exists — check
*) exit 0 ;; # real /tmp elsewhere: nothing to protect
esac
input=$(cat)
cmd=$(echo "$input" | jq -r '(.toolInput // .tool_input // {}) | .command // ""' 2>/dev/null || python -c "
import sys, json
try:
d = json.load(sys.stdin)
ti = d.get('toolInput') or d.get('tool_input') or {}
print(ti.get('command', ''))
except:
print('')
" 2>/dev/null || echo '')
is_grok=$(echo "$input" | jq -r 'if has("hookEventName") or has("toolInput") then "1" else "0" end' 2>/dev/null || echo '0')
# Strip quoted substrings so a /tmp mention inside a string (commit message,
# grep pattern) does not false-trigger; real write targets sit outside quotes.
bare=$(printf '%s' "$cmd" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g")
if printf '%s' "$bare" | grep -qE '(>>?[[:space:]]*/tmp/|[[:space:]]tee[[:space:]]+(-a[[:space:]]+)?/tmp/|(^|[[:space:]])-o[[:space:]]*/tmp/|--output(=|[[:space:]]+)/tmp/|(^|[[:space:]]|;|\|)(cp|mv)[[:space:]][^|;&>]*[[:space:]]/tmp/|mktemp[[:space:]]+(-d[[:space:]]+)?/tmp/)'; then
reason="Blocked write to /tmp in bash on Windows: MSYS /tmp and the Write/Python tools' /tmp are DIFFERENT directories — the file cannot be read back by the next tool call."
echo "BLOCKED: do not write files under /tmp on Windows (Git Bash)."
echo ""
echo "MSYS bash and the harness's other tools resolve /tmp to different real"
echo "directories, so the next tool call cannot read what you just wrote"
echo "(ref: feedback_tmp_path_windows). Use one of these instead:"
echo " - repo-relative scratch: curl -o ./.x.json ... (gitignored .tmp-* also works)"
echo " - the session scratchpad dir (absolute path, shared by all tools)"
echo " - pipe directly: curl ... | jq ... (no intermediate file)"
if [ "$is_grok" = "1" ]; then
printf '{"decision":"deny","reason":"%s"}\n' "$reason"
fi
exit 2
fi
exit 0

View File

@@ -2,18 +2,22 @@
## Reference ## 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. - [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.
- [MSP360 backup monitoring](reference_msp360_backup_monitoring.md) — Authoritative "is client X actually backing up / last run" source for the whole fleet (NOT B2 bucket contents). MSP360 MBS API `/api/Monitoring`, vault `msp-tools/msp360-api.sops.yaml`. CompanyName also IDs B2 buckets (ACG-PST=Peaceful Spirit, ACG-TCA=Tucson Coin).
- [Backup checks only for billed clients](feedback_backup_check_only_billed.md) — Don't scan backup status for clients with no billed Data Backup line (Syncro schedule qty=0); verify billed clients only. Non-billed-but-backing-up = revenue-leak question for Mike/Winter, not a scan target.
- [ALIS (Medtelligent)](reference_alis_medtelligent.md) — Cascades assisted-living EHR. API host api.alisonline.com, community 622; username must be tenant-qualified (howard.enos@cascadestucson). Staff are READ-ONLY via API — create/change staff via web-UI Staff Import .xls. Use the `alis` skill. - [ALIS (Medtelligent)](reference_alis_medtelligent.md) — Cascades assisted-living EHR. API host api.alisonline.com, community 622; username must be tenant-qualified (howard.enos@cascadestucson). Staff are READ-ONLY via API — create/change staff via web-UI Staff Import .xls. Use the `alis` skill.
- [GuruRMM User Manager](reference_gururmm_user_manager.md) — GuruRMM has a built-in per-agent User Manager tab (reset_password/enable/disable/groups for local+domain+AAD endpoint users; domain users only on a DC via `is_dc`). Use it, NOT raw Set-ADAccountPassword via /rmm. Endpoints: /api/agents/{id}/users + /users/action. - [GuruRMM User Manager](reference_gururmm_user_manager.md) — GuruRMM has a built-in per-agent User Manager tab (reset_password/enable/disable/groups for local+domain+AAD endpoint users; domain users only on a DC via `is_dc`). Use it, NOT raw Set-ADAccountPassword via /rmm. Endpoints: /api/agents/{id}/users + /users/action.
- [RMM map network drive (err67 double-hop)](reference_rmm_map_network_drive.md) — Pushing a persistent mapped drive to a remote share via /rmm user_session fails with err67/1702 (impersonated token = no network cred/double-hop). Plant HKCU:\Network\<drv> keys + cmdkey; mounts at next interactive logon. Immediate visibility needs the live session (ScreenConnect).
- [exchange-op = all-access Exchange tier](feedback_exchange_op_all_access.md) — STOP claiming "no tier can write mail." Exchange Operator app = Exchange Admin role + full_access_as_app + Exchange.ManageAsApp = full all-access (move mail, rules, config, EWS). Default to `exchange-op` for any Exchange write. - [exchange-op = all-access Exchange tier](feedback_exchange_op_all_access.md) — STOP claiming "no tier can write mail." Exchange Operator app = Exchange Admin role + full_access_as_app + Exchange.ManageAsApp = full all-access (move mail, rules, config, EWS). Default to `exchange-op` for any Exchange write.
- [Tedards tenant facts](reference_tedards_tenant_facts.md) — Bill Tedards law office; tenant `4fcbb1f4…`; bt@/y226@ mailboxes; matter-number filing; UAL ingestion OFF; 9 synced devices; botched-import DUPLICATE folder. - [Tedards tenant facts](reference_tedards_tenant_facts.md) — Bill Tedards law office; tenant `4fcbb1f4…`; bt@/y226@ mailboxes; matter-number filing; UAL ingestion OFF; 9 synced devices; botched-import DUPLICATE folder.
- [Investigator EXO ManageAsApp gap](reference_investigator_exo_manageasapp_gap.md) — Security Investigator app lacks `Exchange.ManageAsApp` (only `full_access_as_app`) so `investigator-exo` 401s on EXO adminapi; use `exchange-op` tier for InvokeCommand. - [Investigator EXO ManageAsApp gap](reference_investigator_exo_manageasapp_gap.md) — Security Investigator app lacks `Exchange.ManageAsApp` (only `full_access_as_app`) so `investigator-exo` 401s on EXO adminapi; use `exchange-op` tier for InvokeCommand.
- [Windows edition upgrade via RMM](reference_windows_edition_upgrade_rmm.md) — Verified Home->Pro/Pro-for-Workstations fleet procedure: `changepk.exe /ProductKey <generic Pro>` (NOT DISM Set-Edition — Error 50) -> reboot with `Restart-Computer -Force` (`shutdown /r /t N` is silently dropped in SYSTEM session) -> `slmgr /ipk <MAK>` + /ato. The vault MAK is a Pro-for-WORKSTATIONS key (auto-upgrades edition past plain Pro).
- [Tailscale subnet-route key expiry](reference_tailscale_subnet_key_expiry.md) — "internet OK but all of 172.16.3.x (Gitea .20, RMM/coord .30) dead" = Tailscale infra-node KEY EXPIRY (pfSense subnet router advertises 172.16.0.0/22), NOT a LAN outage; expiry now disabled on infra nodes (2026-06-25). Fallback: gururmm-server direct at tailnet 100.86.12.15:3001. - [Tailscale subnet-route key expiry](reference_tailscale_subnet_key_expiry.md) — "internet OK but all of 172.16.3.x (Gitea .20, RMM/coord .30) dead" = Tailscale infra-node KEY EXPIRY (pfSense subnet router advertises 172.16.0.0/22), NOT a LAN outage; expiry now disabled on infra nodes (2026-06-25). Fallback: gururmm-server direct at tailnet 100.86.12.15:3001.
- [GravityZone support center](reference_gravityzone_support.md) — Authoritative Bitdefender GravityZone product + Public API docs; use to confirm UNVERIFIED `bitdefender` skill methods/param shapes (push setPushEventSettings, assignPolicy, report/account writes, maintenancewindows/integrations names). - [GravityZone support center](reference_gravityzone_support.md) — Authoritative Bitdefender GravityZone product + Public API docs; use to confirm UNVERIFIED `bitdefender` skill methods/param shapes (push setPushEventSettings, assignPolicy, report/account writes, maintenancewindows/integrations names).
- [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. - [GURU-5070 Rust toolchain](reference_guru5070_rust_toolchain.md) — toolchain installed BUT a WDAC/Smart App Control policy (since 2026-07-04) BLOCKS running rustc/cargo/sops/openssl locally (os error 4551) — build the Windows agent on Beast, not here. Toolchain paths + CI gates still valid for reference.
- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage. - [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. - [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. - [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.
- [Syncro RMM policies = API-impossible](reference_syncro_rmm_api_gui_only.md) — policy create/assign/folder-move is GUI-ONLY; `policy_folder_id` is read-only on PUT (live-proven), policy endpoints 404, /policy_folders 401 scope-gated. Don't build /syncro move-asset; use `bitdefender` for API policy work. - [Syncro asset/RMM IS API-drivable](reference_syncro_rmm_api_gui_only.md) — CORRECTED 2026-07-10: asset read/create/update, patches, installed_apps, rmm_alerts all work via API; ONLY policy folders + `policy_folder_id` move need the `Assets - Policy Change` token scope (401 today). Archive stays GUI-only. Full catalog: `.claude/standards/syncro/api-reference.md`.
- [Datto EDR detection behavior](reference_datto_edr_detection_behavior.md) — alert `sourceType`: `av`=Datto AV signature, `rule`=EDR reputation detection (both via `edr.py detections`). EDR is reputation-based not structural (wire known-bad file as autostart exe to trip it; loose files aren't surveyed). AV is tamper-protected (console-only disable); disabling Datto AV uninstalls it + Defender auto-reactivates (AMSI blocks scripts with literal EICAR → build from char codes). Verified live on RMM-TEST-MACHINE. - [Datto EDR detection behavior](reference_datto_edr_detection_behavior.md) — alert `sourceType`: `av`=Datto AV signature, `rule`=EDR reputation detection (both via `edr.py detections`). EDR is reputation-based not structural (wire known-bad file as autostart exe to trip it; loose files aren't surveyed). AV is tamper-protected (console-only disable); disabling Datto AV uninstalls it + Defender auto-reactivates (AMSI blocks scripts with literal EICAR → build from char codes). Verified live on RMM-TEST-MACHINE.
- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval for architecture/features; Howard can handle merges/deploys himself (2026-06-21); bugs→bug list. - [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval for architecture/features; Howard can handle merges/deploys himself (2026-06-21); 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. - [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.
@@ -25,6 +29,9 @@
- [Matomo Analytics](reference_matomo_analytics.md) — Self-hosted analytics at analytics.azcomputerguru.com, site IDs, tracking for all 3 sites. - [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. - [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). Template: clients/_client_template/. - [Client Docs Structure](reference_client_docs_structure.md) — clients/<name>/docs/ layout (overview, network, servers, cloud, security, rmm). Template: clients/_client_template/.
- [Client Wi-Fi Inventory](reference_client_wifi_inventory.md) — Building a fleet Wi-Fi list to connect onsite without asking. Passwords → vault `clients/<slug>/wifi.sops.yaml` (`credentials.<key>_ssid/_password`); readable index → `wiki/reference/client-wifi.md`. `/wifi` importer deferred (structure-only 2026-07-06).
- [Git Bash TZ ignored](feedback_gitbash_tz_ignored.md) — `TZ=America/Phoenix date` in Git Bash silently prints UTC (no tzdata) — confidently-wrong "current time". Use PowerShell `Get-Date` (machine already AZ) or plain `date` with no TZ override for now-time on Windows.
- [Discord read replies](feedback_discord_read_replies.md) — Discord DM replies don't come through coord/repo sync. Use `discord-dm.sh read <user>` to see if mike/winter/rob actually answered before assuming silence.
- [MSP Audit Scripts](reference_msp_audit_scripts.md) — server_audit.ps1 / workstation_audit.ps1 at projects/msp-tools/msp-audit-scripts/. - [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. - [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. - [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.
@@ -32,6 +39,9 @@
- [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 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). - [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. - [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.
- [GuruRMM command timeout_seconds](reference_gururmm_command_timeout_seconds.md) — agent command dispatch honors `timeout_seconds`, NOT `timeout`; long jobs die ~300s / go zombie (`running`, empty stdout) otherwise. Cost Birth Biologic a full day.
- [SharePoint Graph large-file upload](reference_sharepoint_graph_large_file_upload.md) — <4MB simple PUT, >=4MB MUST use chunked upload session (Content-Range); `\\?\` long paths; idempotent size-check; verify counts via /root/delta; single stream ~40Mbps (SPO throttle).
- [RMM-spawn headless Claude](reference_rmm_spawn_headless_claude.md) — run `claude -p` on any RMM-managed Windows box with Claude Code (reaches coord-isolated sites like AD2); use `context:user_session`, UNSET the stale machine `ANTHROPIC_API_KEY` (shadows OAuth → "Invalid API key"), detach + poll a DONE marker. Validated on AD2 2026-07-01.
- [RMM agent update model](rmm-agent-update-model.md) — Agent updates are server-PUSH on heartbeat (no self-poll); available versions = filesystem scan needing a `.sha256`; promote flips `.channel` sidecars beta→stable globally. Two stranders: beta-first freezes stable until an explicit promote; agents older than ~0.6.50 re-enroll with a NEW device_id/agent row when updated. - [RMM agent update model](rmm-agent-update-model.md) — Agent updates are server-PUSH on heartbeat (no self-poll); available versions = filesystem scan needing a `.sha256`; promote flips `.channel` sidecars beta→stable globally. Two stranders: beta-first freezes stable until an explicit promote; agents older than ~0.6.50 re-enroll with a NEW device_id/agent row when updated.
- [GuruRMM physical server storage](gururmm-physical-server-storage.md) — New box 172.16.1.231 (temp IP→will be .30), Ubuntu 26.04, ssh key `gururmm-physical`/alias `gururmm-new`. SSD (915G root) = HOT (PG default tablespace + WAL + builds); HDD ext4 at `/data` = COLD (`gururmm_cold` PG tablespace for aged `agent_logs` partitions + downloads + backups + archive). The #3 retention answer. - [GuruRMM physical server storage](gururmm-physical-server-storage.md) — New box 172.16.1.231 (temp IP→will be .30), Ubuntu 26.04, ssh key `gururmm-physical`/alias `gururmm-new`. SSD (915G root) = HOT (PG default tablespace + WAL + builds); HDD ext4 at `/data` = COLD (`gururmm_cold` PG tablespace for aged `agent_logs` partitions + downloads + backups + archive). The #3 retention answer.
- [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. - [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.
@@ -58,6 +68,8 @@
- [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI. - [Mike — font preference](user_font_preference.md) — Mike prefers Lucida Console for monospace UI.
## Feedback ## Feedback
- [Simplest solution first](feedback_simplest_solution_first.md) — Start with the least-complex thing that could answer the problem (one DNS lookup / one API call / one command), confirm it works, THEN graduate to advanced/complicated as needed. Don't reach for SSH/plink/multi-hop when a one-shot query answers it. Now a CLAUDE.md core rule.
- [Report times in Arizona time](feedback_timezone_arizona_reporting.md) — All user-facing times in America/Phoenix (MST, no DST), labeled AZ; convert from UTC. Set by Winter 2026-07-02.
- [RMM dashboard: beta before main](rmm-dashboard-beta-before-main.md) — GuruRMM website/dashboard changes land on beta (rmm-beta.azcomputerguru.com) and get tested BEFORE pushing/merging to main, unless told otherwise. Pipeline auto-builds beta FROM main, so don't merge to get on beta — build the feature branch's dashboard and rsync to /var/www/gururmm/dashboard-beta via a git worktree. Promote beta→prod with promote-dashboard.sh --confirm. - [RMM dashboard: beta before main](rmm-dashboard-beta-before-main.md) — GuruRMM website/dashboard changes land on beta (rmm-beta.azcomputerguru.com) and get tested BEFORE pushing/merging to main, unless told otherwise. Pipeline auto-builds beta FROM main, so don't merge to get on beta — build the feature branch's dashboard and rsync to /var/www/gururmm/dashboard-beta via a git worktree. Promote beta→prod with promote-dashboard.sh --confirm.
- [Prefer SSH over RMM](feedback_prefer_ssh_over_rmm.md) — When a target has SSH (key auth) and the task is easier over it, default to `scp script + ssh run` (system OpenSSH); RMM runs as SYSTEM + hits the server-side timeout reaper. Reserve RMM as fallback when SSH/VPN is down. - [Prefer SSH over RMM](feedback_prefer_ssh_over_rmm.md) — When a target has SSH (key auth) and the task is easier over it, default to `scp script + ssh run` (system OpenSSH); RMM runs as SYSTEM + hits the server-side timeout reaper. Reserve RMM as fallback when SSH/VPN is down.
- [Re-clone submodule creds](reclone-submodule-creds.md) — Re-cloning the restructured claudetools (projects now submodules): set `credential.helper=store` GLOBALLY before `git submodule update --init --recursive` or every Gitea submodule fails "could not read Username". Steps in RECLONE.md. - [Re-clone submodule creds](reclone-submodule-creds.md) — Re-cloning the restructured claudetools (projects now submodules): set `credential.helper=store` GLOBALLY before `git submodule update --init --recursive` or every Gitea submodule fails "could not read Username". Steps in RECLONE.md.
@@ -79,13 +91,13 @@
- [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. - [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. - [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. - [/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 strips embedded double-quotes](feedback_windows_quote_stripping.md) — Embedded `"` in an arg gets eaten twice over: PowerShell->curl.exe (CommandLineToArgvW) AND RMM->cmd.exe. Use single-quoted heredoc `<<'JSON'` + `--data-binary @-` for bodies; build `"` from `[char]34`; or drop the quoted part (e.g. `shutdown /c`). - [Windows strips embedded double-quotes](feedback_windows_quote_stripping.md) — Embedded `"` in an arg gets eaten twice over: PowerShell->curl.exe (CommandLineToArgvW) AND RMM->cmd.exe. MECHANICAL FIX: deliver PS scripts via `.claude/scripts/ps-encoded.sh` (-EncodedCommand, byte-exact; author the file with the Write tool). Inline one-offs: single-quoted heredoc `<<'JSON'` + `--data-binary @-`; build `"` from `[char]34`.
- [Interview the AI / read its docs before probing](feedback_interview_ai_read_docs.md) — To learn an external AI/CLI's syntax or capabilities, READ its bundled docs (Grok: `~/.grok/docs/user-guide/`, `README.md`, `grok inspect`/`models`/`--help`) or interview the model; don't guess flags or run slow trial-and-error. One run to confirm a doc-derived hypothesis, not a dozen to discover. - [Interview the AI / read its docs before probing](feedback_interview_ai_read_docs.md) — To learn an external AI/CLI's syntax or capabilities, READ its bundled docs (Grok: `~/.grok/docs/user-guide/`, `README.md`, `grok inspect`/`models`/`--help`) or interview the model; don't guess flags or run slow trial-and-error. One run to confirm a doc-derived hypothesis, not a dozen to discover.
- [Web search over blind probing](feedback_web_search_over_probing.md) — For external API/capability discovery, LEAD with web search (grok/gemini) + vendor docs; live endpoint-probing only CONFIRMS a hypothesis, never the primary discovery method (it mostly 404s, "highly suspect"). Reading a system's OWN config is fine; guessing unknown PATHS is not. Web-search bots being flaky is a must-fix (CT_THOUGHTS Thought 2). - [Web search over blind probing](feedback_web_search_over_probing.md) — For external API/capability discovery, LEAD with web search (grok/gemini) + vendor docs; live endpoint-probing only CONFIRMS a hypothesis, never the primary discovery method (it mostly 404s, "highly suspect"). Reading a system's OWN config is fine; guessing unknown PATHS is not. Web-search bots being flaky is a must-fix (CT_THOUGHTS Thought 2).
- [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. - [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.
- [Git must authenticate non-interactively](feedback_git_noninteractive_auth.md) — Mike's gripe with Git for Windows is the constant password prompts (GCM) that hang automation, NOT the tool itself. D:\ClaudeTools is set to `credential.helper=store` primed with the azcomputerguru Gitea API token (host 172.16.3.20:3000); always set `GIT_TERMINAL_PROMPT=0`. Any never-prompts solution is acceptable. - [Git must authenticate non-interactively](feedback_git_noninteractive_auth.md) — Mike's gripe with Git for Windows is the constant password prompts (GCM) that hang automation, NOT the tool itself. D:\ClaudeTools is set to `credential.helper=store` primed with the azcomputerguru Gitea API token (host 172.16.3.20:3000); always set `GIT_TERMINAL_PROMPT=0`. Any never-prompts solution is acceptable.
- [Vault git auth — GCM shadows store token](feedback_vault_gcm_shadow_auth.md) — vault sync "Failed to authenticate user" on git.azcomputerguru.com: GCM is first in the helper chain and shadows the valid store token. Fix (machine-local): store-only credential.helper reset + pin `azcomputerguru@` in the vault remote URL so store returns the durable PAT (not the volatile OAUTH_USER JWT). Applied GURU-5070 2026-06-07. - [Vault git auth — GCM shadows store token](feedback_vault_gcm_shadow_auth.md) — vault sync "Failed to authenticate user" on git.azcomputerguru.com: GCM is first in the helper chain and shadows the valid store token. Fix (machine-local): store-only credential.helper reset + pin `azcomputerguru@` in the vault remote URL so store returns the durable PAT (not the volatile OAUTH_USER JWT). Applied GURU-5070 2026-06-07.
- [Antigravity agy.exe is not a headless CLI](reference_antigravity_agy_not_headless.md) — the `agy` skill's real backend is `@google/gemini-cli`, not the Antigravity `agy.exe` (IDE agent, no stdout, hangs). Don't reinstall agy.exe expecting headless output. Mike has a paid Gemini account, so stay on gemini-cli past the June 18 free-tier sunset (prefer `GEMINI_API_KEY`). - [agy.exe NOW works headless (2026-07-03)](reference_antigravity_agy_not_headless.md) — Antigravity `agy.exe` v1.0.6+ has a working `-p/--print` headless mode (clean stdout, `--add-dir` to read files); use it as the Gemini/Antigravity second-model path, esp. when gemini-cli OAuth is ineligible. Call by full path until PATH refresh. (Supersedes the old "agy is DOA headless" note.)
- [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. - [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.
- [RMM password setting limitation](feedback_rmm_password_limitation.md) — `net user <user> <password>` via GuruRMM fails silently (exit 0 but password doesn't set). Tested PowerShell AND CMD - both fail. ScreenConnect CMD works (also as SYSTEM). GuruRMM agent bug in process spawning. Use ScreenConnect for password ops. HIGH priority to fix. - [RMM password setting limitation](feedback_rmm_password_limitation.md) — `net user <user> <password>` via GuruRMM fails silently (exit 0 but password doesn't set). Tested PowerShell AND CMD - both fail. ScreenConnect CMD works (also as SYSTEM). GuruRMM agent bug in process spawning. Use ScreenConnect for password ops. HIGH priority to fix.
- [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\<SID>\*` directly. - [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\<SID>\*` directly.
@@ -109,6 +121,7 @@
- [Python on Windows — use py launcher](feedback_python_windows.md) — Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL. - [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. - [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/<slug>/*.jsonl`; the detector auto-recovers orphans, `/recover <uuid>` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`. - [Unsaved sessions are recoverable from transcripts](feedback_session_recovery.md) — Crashed/closed-before-save sessions live in `~/.claude/projects/<slug>/*.jsonl`; the detector auto-recovers orphans, `/recover <uuid>` does it manually. Ollama prose + Python verbatim. See `.claude/RECOVERY.md`.
- [Scheduled tasks must not flash a console](feedback_scheduled_task_no_console_flash.md) — A task action running bash.exe/py.exe/python.exe with Hidden=False draws a console window every fire (the recurring "command prompt opening/closing"). Wrap bash via a wscript VBS (style 0), use pythonw.exe for python, always add `-Hidden`. Fixed installers: register-edr-watcher.ps1, register-orphan-detector.ps1.
- [agy review is not read-only](feedback_agy_review_not_readonly.md) — agy review/review-files CAN write files + run npm despite docs claiming plan-mode; always git diff after and treat Gemini's output as a proposal to validate, not trusted/finished work. - [agy review is not read-only](feedback_agy_review_not_readonly.md) — agy review/review-files CAN write files + run npm despite docs claiming plan-mode; always git diff after and treat Gemini's output as a proposal to validate, not trusted/finished work.
- [Don't present inferred topology as fact](feedback_no_inferred_topology_as_fact.md) — Private-IP overlap (172.16.x on both sides) is NOT proof of a site-to-site link; I fabricated a VWP<->office VPN. State observations vs inferences; a failed reachability test disproves a link, don't explain it away; test "can reach RMM" against the EXTERNAL endpoint, not internal 172.16.3.30. - [Don't present inferred topology as fact](feedback_no_inferred_topology_as_fact.md) — Private-IP overlap (172.16.x on both sides) is NOT proof of a site-to-site link; I fabricated a VWP<->office VPN. State observations vs inferences; a failed reachability test disproves a link, don't explain it away; test "can reach RMM" against the EXTERNAL endpoint, not internal 172.16.3.30.
@@ -139,6 +152,8 @@
- [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers. - [GURU-BEAST-ROG Setup Status](machine_windows_guru_setup_status.md) — Windows workstation fully configured except SSH key deployment to servers.
## Project ## Project
- [VWP-ROSE QB updater crash-loop](project_vwp_rose_qb_updater.md) — RESOLVED 2026-07-13 via remote QB repair install; billed 0.5h to VWP prepay block, ticket #32545 Invoiced. Open side findings: NETLOGON 5719 at boot + Syncro agent service crashes on VWP-ROSE.
- [GuruRMM software-removal PR #58](project_gururmm_software_removal_pr58.md) — SPEC-030 hardening (driver exclusion in removal UI, lingering-uninstaller sweep, job list + stale reaper) on PR #58, UNMERGED; merge = beta deploy; live testing planned 2026-07-06 on DESKTOP-MS42HNC.
- [Cascades network + CS-SERVER SMB instability](project_cascades_network_segments.md) — NAS->CS-SERVER migration. NOT a CSC-ENT-vs-CSCNet issue (corrected): fresh SMB to `\\cs-server\*` fails err 67 from BOTH Wi-Fis; only persistent admin-mapped drives work, intermittently → CS-SERVER-side SMB instability (multichannel advertises .248/.254/IPv6 ULAs; Get-SmbServerConfiguration errors). Blockers: CSCNet=WPA3 (old adapters can't join); workstations store domain-admin cred. Repoint tool [[drive-map]]. - [Cascades network + CS-SERVER SMB instability](project_cascades_network_segments.md) — NAS->CS-SERVER migration. NOT a CSC-ENT-vs-CSCNet issue (corrected): fresh SMB to `\\cs-server\*` fails err 67 from BOTH Wi-Fis; only persistent admin-mapped drives work, intermittently → CS-SERVER-side SMB instability (multichannel advertises .248/.254/IPv6 ULAs; Get-SmbServerConfiguration errors). Blockers: CSCNet=WPA3 (old adapters can't join); workstations store domain-admin cred. Repoint tool [[drive-map]].
- [CyndyOffice physical HP lockups](cyndyoffice-physical-hp-lockups.md) — RMM "Howard-VM" site agent CyndyOffice is a PHYSICAL HP Pavilion TP01 (not a VM); ~20 hard freezes/6wk = Kernel-Power 41 bugcheck-0, no dump/WHEA = hardware (RAM/PSU/BIOS), SSD healthy. UUID re-enrolls. - [CyndyOffice physical HP lockups](cyndyoffice-physical-hp-lockups.md) — RMM "Howard-VM" site agent CyndyOffice is a PHYSICAL HP Pavilion TP01 (not a VM); ~20 hard freezes/6wk = Kernel-Power 41 bugcheck-0, no dump/WHEA = hardware (RAM/PSU/BIOS), SSD healthy. UUID re-enrolls.
- [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. - [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.
@@ -148,6 +163,7 @@
- [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. - [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. - [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. - [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.
- [GuruRMM legacy 1.77 build disabled](project_gururmm_legacy_disabled.md) — legacy (2008R2/Win7) Rust 1.77 variant DISABLED 2026-07-04 (`LEGACY_ENABLED=false`) after an edition2024 dep (chacha20/rand_core 0.10.1) broke the unpinned 1.77 re-resolve and fail-closed the whole Windows build; existing legacy artifacts preserved at 0.6.76. Do NOT re-enable blindly — 2008R2 support returns via a C++/.NET or clean-rewrite legacy agent (Mike, required).
- [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. - [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.
- [Howard-Home LAN shadow (RESOLVED)](howard-home-lan-shadow.md) — Howard-Home renumbered 2026-06-16 to **10.137.42.0/24** (gw 10.137.42.1, UniFi — NOT pfSense), off the old 192.168.0.0/24 that shadowed Cascades pfSense .0.x over the VPN. Cascades .0.x should now route via the tunnel; this machine is 10.137.42.x now (not 192.168.0.x). - [Howard-Home LAN shadow (RESOLVED)](howard-home-lan-shadow.md) — Howard-Home renumbered 2026-06-16 to **10.137.42.0/24** (gw 10.137.42.1, UniFi — NOT pfSense), off the old 192.168.0.0/24 that shadowed Cascades pfSense .0.x over the VPN. Cascades .0.x should now route via the tunnel; this machine is 10.137.42.x now (not 192.168.0.x).
- [Cascades CARF tech plan](project_cascades_carf_tech_plan.md) — Ashley's "technology plan" is a CARF accreditation deliverable (Aging Services Technology and System Plan standard); must use CARF action-plan structure (owner/cost/target+completion dates per area), persons-served assistive-tech lens, annual-review sign-off; it's Cascades' leadership-adopted plan, ACG supplies content. - [Cascades CARF tech plan](project_cascades_carf_tech_plan.md) — Ashley's "technology plan" is a CARF accreditation deliverable (Aging Services Technology and System Plan standard); must use CARF action-plan structure (owner/cost/target+completion dates per area), persons-served assistive-tech lens, annual-review sign-off; it's Cascades' leadership-adopted plan, ACG supplies content.
@@ -166,6 +182,7 @@
- [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. - [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. - [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. - [Proposal: centralize config in identity.json](proposal_identity_centralization.md) — Rationale for the identity.json machine-config centralization (claudetools_root, ollama/python); now implemented.
- [Defender: exempt all Claude activity](feedback_defender_claude_exclusions.md) — Mike wants NOTHING Claude/ClaudeTools issues flagged by Defender; ClickFix threat IDs 2147939088/2147945138 (RMM curl dispatch AMSI FPs) set to Allow + broad process/path exclusions. ThreatID-Allow is the lever; exclusions alone don't stop AMSI CmdLine detections.
- [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 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).
- [VoIP vendor stack: PacketDial / OIT / NetSapiens / YMCS](reference_packetdial_oit_netsapiens.md) — PacketDial = ACG's VoIP-dept brand (pbx.packetdial.com, the `packetdial` skill); NetSapiens = the PBX platform (API v2); OIT/OITVOIP = white-label wholesaler running NetSapiens (api.ucaasnetwork.com); reseller `91912.service`. YMCS (Yealink) = phone device-mgmt, pairs with the PBX. - [VoIP vendor stack: PacketDial / OIT / NetSapiens / YMCS](reference_packetdial_oit_netsapiens.md) — PacketDial = ACG's VoIP-dept brand (pbx.packetdial.com, the `packetdial` skill); NetSapiens = the PBX platform (API v2); OIT/OITVOIP = white-label wholesaler running NetSapiens (api.ucaasnetwork.com); reseller `91912.service`. YMCS (Yealink) = phone device-mgmt, pairs with the PBX.
- [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel. - [ACG Website Hosting](project_azcomputerguru_hosting.md) — azcomputerguru.com is hosted on IX Web Hosting via cPanel.
@@ -198,3 +215,20 @@
- [GuruScan verification IN TEST / paused](project_guruscan_in_test_paused.md) — multi-engine scanner verify on DESKTOP-MS42HNC paused 2026-06-22 (VM rebooted mid-Emsisoft run); HitmanPro done (36 removed), Emsisoft full-scan unverified; resume `guruscan-agent-test.sh DESKTOP-MS42HNC scan-one Emsisoft`; Defender RTP/Tamper still off on VM - [GuruScan verification IN TEST / paused](project_guruscan_in_test_paused.md) — multi-engine scanner verify on DESKTOP-MS42HNC paused 2026-06-22 (VM rebooted mid-Emsisoft run); HitmanPro done (36 removed), Emsisoft full-scan unverified; resume `guruscan-agent-test.sh DESKTOP-MS42HNC scan-one Emsisoft`; Defender RTP/Tamper still off on VM
- [GuruRMM fleet dispatch-hang fix](project_gururmm_dispatch_hang_fix.md) — blocking send_to on a full bounded channel to one black-holed agent wedged ALL command dispatch; fixed with try_send (9dae20c, deployed); proper black-hole eviction still missing (was reverted in 80df458) — finish it if it recurs - [GuruRMM fleet dispatch-hang fix](project_gururmm_dispatch_hang_fix.md) — blocking send_to on a full bounded channel to one black-holed agent wedged ALL command dispatch; fixed with try_send (9dae20c, deployed); proper black-hole eviction still missing (was reverted in 80df458) — finish it if it recurs
- [Windows won't-boot / offline DISM repair playbook](windows-offline-dism-repair-gotchas.md) — Automatic Repair loop = boot-critical fault (disk/registry/wedged update), NOT shell/appx store corruption (that's a symptom); `FaultyPackageInProgress` + 100s of Install/Uninstall-Pending packages = wedged CU -> RevertPendingActions or clean install. Offline DISM rejects `wim:` source (0x800f082e) -> MOUNT the wim, source `\Windows`. Ventoy breaks WIM mount (0xc1420134) -> use Rufus. 25H2(26200)=24H2(26100)+enablement, so match 26100 media. First hit: Four Paws AvImark #32447. - [Windows won't-boot / offline DISM repair playbook](windows-offline-dism-repair-gotchas.md) — Automatic Repair loop = boot-critical fault (disk/registry/wedged update), NOT shell/appx store corruption (that's a symptom); `FaultyPackageInProgress` + 100s of Install/Uninstall-Pending packages = wedged CU -> RevertPendingActions or clean install. Offline DISM rejects `wim:` source (0x800f082e) -> MOUNT the wim, source `\Windows`. Ventoy breaks WIM mount (0xc1420134) -> use Rufus. 25H2(26200)=24H2(26100)+enablement, so match 26100 media. First hit: Four Paws AvImark #32447.
- [365 app suite — authoritative map + consent-drift fix](reference_365_app_suite.md) — full map in `.claude/skills/remediation-tool/references/app-suite.md`; per-tenant consent is NOT uniform (VWP had the app but no SharePoint role). Run `consent-audit.sh <tenant|--all>` to detect gaps; fix via adminconsent URL or direct appRoleAssignment grant.
- [Remediation-tool has full M365 access (incl. SharePoint)](reference_remediation_tool_365_access.md) — the app suite covers Graph/EXO/Defender/SharePoint; don't declare "no access" on an accessDenied. SharePoint app-only needs a CERT (secret = "Unsupported app only token"); use get-token.sh `sharepoint`/`sharepoint-admin` tiers + CSOM admin API (Graph /admin/sharepoint/settings scope not held). Full map: skill references/app-permissions-and-sharepoint.md.
- [AV migration: Bitdefender -> Datto EDR](project_av_migration_bitdefender_to_edr.md) — retire Bitdefender fleet-wide, ONLY exception Glaztech (Dataforth migrates); end-state per machine = GuruRMM + Datto EDR
- [RMM deploy via ScreenConnect](reference_rmm_deploy_via_screenconnect.md) — push GuruRMM agent to client workstations via SC send-command (SYSTEM), not DC remote-exec (DCOM/schtasks blocked on Win11 clients)
- [ScreenConnect custom-property slots](reference_screenconnect_custom_property_slots.md) — CP1=Company CP2=Site CP3=Department CP4=Device Type CP8=Tag (API hides labels; UpdateSessionCustomProperties replaces the whole array)
- [ScreenConnect cleanup uses wiki as source](feedback_screenconnect_cleanup_wiki_source.md) — per-client SC/RMM metadata cleanup pulls machine->dept/location from the client wiki; enrich the wiki when missing
- [TECH03L systemprofile shortcut corruption](project_tech03l_systemprofile_shortcut_corruption.md) — Auto-Claude "opens then closes" = .lnk pointing at nonexistent systemprofile path; repoint, do not debug the app
- [Claude tui fullscreen crash](feedback_claude_tui_fullscreen_crash.md) — "flicker-free" prompt writes tui=fullscreen; instant-exit crash loop on some Windows terminals; set tui=default (reinstall does NOT fix)
- [Backup targets never guessed](feedback_backup_targets_never_guessed.md) — billed-vs-running mismatches are billing questions for Mike/Winter; never infer/deploy backup targets
- [Fire #dev-alerts for client/RMM work](feedback_fire_dev_alerts_for_client_work.md) — Howard+Mike watch #dev-alerts Discord for live visibility into what techs/sessions touch. Fire post-bot-alert.sh ([DEV]/[RMM]/[DEPLOY]/[GURURMM] prefix -> #dev-alerts) at the START of any RMM/Dev or active-client-machine action. No CLAUDE.md rule yet — this memory is the rule.
- [M365 app: SharePoint needs CERT](reference_m365_app_sharepoint_rest_vs_graph.md) — SP app-only works via the CERT (get-token.sh <tenant> sharepoint); the secret gives "Unsupported app only token". Graph app-only uses the secret.
- [EDR auto-isolates GuruRMM PowerShell](project_edr_rmm_autoisolation_fp.md) — Datto "Exfiltration Over HTTP" rule auto-isolates benign GuruRMM scripts fleet-wide (vwp-qbs 4h outage); watcher + narrow suppression deployed, policy fix pending Mike
- [Background tasks — no shell `&`](feedback_background_task_no_ampersand.md) — with run_in_background:true, run the command in the foreground of the shell; adding `&`/`disown` forks + exits 0 instantly, orphaning a blocking wait so it never delivers (hit twice building ask-forum)
- [ask-forum — human-in-the-loop via #ct-forum](../skills/ask-forum/SKILL.md) — ask a teammate a question in the private ct-forum Discord forum, get their human answer back in-session; forum-only, any human can answer, run the --wait as a proper background task
- [Claude Code Bun crash -> pin 2.1.110](reference_claude_code_bun_crash_pin.md) - v2.1.114+ segfaults on Win11 at new-session start; npm pin 2.1.110 + DISABLE_AUTOUPDATER=1; unpin on #55219 fix
- [GuruRMM build-server SSH key](reference_gururmm_build_server_ssh.md) — guru@172.16.3.30 key is ~/.ssh/gururmm-physical (not id_*); password fallback vault infrastructure/gururmm-server
- [LAB-SVR retired](feedback_labsvr_retired.md) — Len's Auto LAB-SVR replaced by LAB-SERVER; never chase LAB-SVR

View File

@@ -0,0 +1,26 @@
---
name: Background tasks — run_in_background only, never add a shell `&`
description: When launching a long-running command as a background task (run_in_background:true), do NOT also append a shell `&`/`disown`/`nohup`. The shell forks the command and exits 0 immediately, orphaning it — a blocking wait then never delivers its result. Run it in the FOREGROUND of that shell; the harness does the backgrounding.
type: feedback
---
When you want a long-running command (e.g. `ask-forum.sh --wait`, a poll loop, a blocking
watcher) to run in the background, use the Bash tool's **`run_in_background: true`** and run
the command in the **foreground of that shell** — a bare `bash script.sh ...` with **no
trailing `&`, no `disown`, no `nohup`**.
**Why:** appending `&` forks the command off inside the shell; the shell then reaches the
end and exits **0 immediately**. The harness sees exit 0 and reports the task "completed"
right away, while the forked process is orphaned (and often killed). A blocking `--wait`
that was supposed to sit for minutes and deliver an answer instead returns nothing — its
result is never captured or notified.
**How to apply:**
- Background wait, CORRECT: `Bash(command="bash .claude/scripts/ask-forum.sh --wait <id> --timeout 1800", run_in_background=true)` — the wait truly blocks server-side, costs zero tokens while pending, and the harness pings you the instant it completes.
- WRONG: `Bash(command="bash ... --wait ... &", run_in_background=true)` and `... & disown` — orphans the wait, "completes" instantly, no answer.
- The two mechanisms are redundant: `run_in_background` IS the backgrounding. Never stack a shell `&` on top of it.
**Incident:** hit TWICE in one session (2026-07-08) building the [[ask-forum]] flow — each
time the "background wait" for a Discord reply exited 0 instantly and never delivered the
answer, which looked like the reply was lost and forced the user to manually prompt "did
they answer?". Logged as `--friction` in `errorlog.md`. Related: [[ask-forum]].

View File

@@ -0,0 +1,20 @@
---
name: feedback_backup_check_only_billed
description: Only verify backup status for clients that actually bill a backup line - don't scan non-billed clients
metadata:
type: feedback
---
Backup verification is scoped to clients **marked for backups** — i.e. those with a billed
Data Backup line (Syncro recurring-schedule line qty > 0). If a client is NOT billed for
backup, do NOT scan their machines / MSP360 / backends for backup status. (Howard,
2026-07-07, GPS audit — Marty Ryan has Svc = A E only, no backup line, so stop checking.)
**Why:** "is X backing up" only matters where the client pays for it; scanning non-billed
clients burns time/endpoint calls and produces findings nobody asked for.
**How to apply:** determine billed-backup status first (Syncro backup line qty — see the
`/syncro schedules`/`schedule` pull, authoritative), then only verify the ones with a seat.
Non-billed clients that happen to be backing up are a separate "revenue leak" question for
Mike/Winter, not a per-machine scan target. Related: [[feedback_backup_targets_never_guessed]],
[[reference_msp360_backup_monitoring]].

View File

@@ -0,0 +1,21 @@
---
name: feedback_backup_targets_never_guessed
description: Backup targets are a deliberate per-client decision - never infer which machine needs backup or deploy backup jobs from billing counts
metadata:
type: feedback
---
When reconciling billed backup lines (e.g. Syncro "Service - Data Backup" qty) against
machines actually backing up (MSP360/B2), a mismatch is a BILLING question for Mike/Winter,
never a deployment instruction. (Howard, 2026-07-05, GPS->RMM audit backup verification.)
**Why:** which machine gets backed up is a deliberate choice made per client (usually the
server or a specific data machine) — billed qty does not mean "N machines each need a
backup job." Guessing targets and adding backup jobs would burn storage and misrepresent
what the client agreed to.
**How to apply:** report mismatches (billed > running, running > billed, dead buckets) as
findings with evidence; route the decision to Mike/Winter/client. Only configure a backup
job when the target machine is explicitly named by the user/client. Related:
[[project_av_migration_bitdefender_to_edr]] (the AV migration IS deploy-everywhere, unlike
backup — don't conflate the two postures).

View File

@@ -0,0 +1,22 @@
---
name: claude-tui-fullscreen-crash
description: Claude Code "flicker-free rendering" prompt writes tui=fullscreen to settings.json and can crash-loop the CLI on Windows; fix = delete the tui key
metadata:
type: feedback
---
On ACG-TECH03L (2026-07-04, Claude Code 2.1.201), accepting the startup prompt asking to
enable **flicker-free rendering** wrote `"tui": "fullscreen"` into
`~/.claude/settings.json`. On that machine's terminal the fullscreen/alternate-screen
mode crashed the CLI instantly on every subsequent launch ("opens and then closes") —
from any launch path, since all read the same settings file.
**Why:** the setting persists, so one bad answer to the prompt turns into a permanent
crash loop; reinstalling Claude Code does NOT fix it because settings survive reinstall.
**How to apply:** if a fleet machine's Claude Code exits immediately at launch, check
`~/.claude/settings.json` for `"tui": "fullscreen"` and set it to `"tui": "default"`
(surgically — keep hooks/plugins; explicit "default" also suppresses re-prompting,
which deleting the key does not). Emergency override without touching config:
`CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1`. Decline the flicker-free prompt on machines
with older terminals/conhost. Related: [[tech03l-systemprofile-shortcut-corruption]].

View File

@@ -0,0 +1,18 @@
---
name: feedback_defender_claude_exclusions
description: Mike wants NOTHING Claude/ClaudeTools issues to be flagged by Windows Defender; keep broad exclusions + allow the ClickFix threat IDs that fire on RMM curl dispatch.
metadata:
type: feedback
---
On his workstation (GURU-*), Mike wants **nothing Claude issues to be affected by Defender AV** — it's a constant irritation. The recurring hits are `Trojan:Win32/ClickFix.DBD!MTB` (ThreatID 2147939088) and `Trojan:Win32/ClickFix.ZF` (ThreatID 2147945138), fired by Defender's AMSI **command-line** scan on the base64-PowerShell payloads that `curl.exe` POSTs to the GuruRMM coordination API (`172.16.3.30:3001/api/agents/.../command`).
**Why:** These are false positives on legitimate ClaudeTools/GuruRMM command dispatch. He's the admin/owner and made an informed call to allow the family.
**How to apply:** Process/path exclusions alone do NOT stop these — AMSI CmdLine/behavioral detections ignore `ExclusionProcess`/`ExclusionPath`. The lever that works is `Add-MpPreference -ThreatIDDefaultAction_Ids <id> -ThreatIDDefaultAction_Actions Allow` (Allow = action 6) for both IDs. Also maintained (elevated PowerShell):
- ExclusionProcess: bash.exe, curl.exe, git.exe, node.exe, claude.exe
- ExclusionPath: `C:\Program Files\Git` (+ mingw64\bin, usr\bin), `C:\Program Files\nodejs`, `C:\Users\<u>\.claude`, `C:\Users\<u>\.local\bin`, `C:\Users\<u>\AppData\Roaming\npm`, `C:\ClaudeTools`, `D:\ClaudeTools`.
**ACTIVE (2026-07-01):** Mike opted for the fully-blanket lever — `Set-MpPreference -DisableScriptScanning $true` is SET on this box, disabling Defender AMSI script scanning machine-wide (his call: "I'm not likely to fall for bogus scripts"). This alone stops the CmdLine detections regardless of variant ID; the ThreatID-Allows + exclusions remain as belt-and-suspenders. If ever re-enabling, `Set-MpPreference -DisableScriptScanning $false`.
**Fleet application (2026-07-01):** `DisableScriptScanning` is **Tamper-Protection-gated** — it silently stays `False` if TP is on, even from SYSTEM. This workstation's TP is OFF (toggle worked); **GURU-BEAST-ROG's TP is ON**, so on Beast only the exclusions + ClickFix ThreatID-Allows applied via RMM (those aren't tamper-gated and DO cover the recurring detections) — the blanket script-scanning kill there needs a manual Windows Security UI toggle (TP can't be disabled by script). Beast (GURU-BEAST-ROG, AZ Computer Guru/Mike's House, RMM id 5233d75b-...) is "treated like this machine." Howard was OFFERED the same via Discord DM — his choice on his own box; do NOT push to Howard's machine without his ok. Related: [[reference_acg_msp_stack]] (ACG's own tools shouldn't be flagged as threats), [[feedback_windows_quote_stripping]].

View File

@@ -0,0 +1,26 @@
---
name: feedback_discord_read_replies
description: Discord DM replies are invisible to coord/repo sync — use `discord-dm.sh read <user>` to see if mike/winter/rob actually answered.
metadata:
type: feedback
---
When we DM someone via the `discord-dm` skill and wait on an answer, their reply does NOT
come through coord messages or repo sync — those channels never carry Discord replies. Before
telling the user "no reply yet / they didn't answer," READ the DM channel:
```bash
bash .claude/scripts/discord-dm.sh read mike 10 # last 10 in the mike DM
bash .claude/scripts/discord-dm.sh read #dev-alerts # a channel
```
Output is oldest-first; lines marked `>>>` are from THEM (replies), indented lines are us.
**Why:** the bot participates in every DM channel it opened, so `GET /channels/<id>/messages`
returns full content — the Message Content privileged intent only gates gateway events, not this
REST fetch. Before adding `read` (2026-07-07, Howard flagged it) the wrapper was send-only, so
replies from mike/winter were silently missed.
**How to apply:** after DMing a question to mike/winter/rob, `read` that user to check for their
answer instead of assuming silence. Related: [[reference_community_forum]] is a different channel;
this is Discord DMs/channels via [[discord-dm]] (`.claude/scripts/discord-dm.sh`).

View File

@@ -0,0 +1,29 @@
---
name: feedback_fire_dev_alerts_for_client_work
description: Fire a #dev-alerts Discord post (post-bot-alert.sh with an [RMM]/[DEV]/[DEPLOY]/[BUILD]/[GURURMM] prefix) at the START of any RMM/Dev or active-client-machine action — Howard+Mike watch that channel for live visibility into what techs/sessions are touching
metadata:
type: feedback
---
Howard and Mike watch the **#dev-alerts** Discord channel to see, in real time, what the other techs
(and Claude sessions) are working on. Any substantive action on **RMM/Dev** or an **ACTIVE CLIENT
machine** MUST post a #dev-alerts heads-up — ideally at the START of the work, not just after.
**What counts:** SSH into a client server, restarting/reconfiguring services, editing boot/config on a
live box, RMM remote commands, agent/server/dashboard deploys, channel promotions, anything with real
blast radius on production/client infra.
**Why:** without it a session can be hand-editing a live client box (e.g. Lonestar Electrical's Unraid
`/boot/config/go`, cycling libvirt) with ZERO visibility to the team — exactly what happened
2026-07-06, which Mike flagged: "all of the actions you've taken today should have fired the discord
alerts."
**How to apply:**
`bash .claude/scripts/post-bot-alert.sh "[DEV] <client/box> — <what + why>. — Claude @ <machine> (<user>)"`
Prefixes `[RMM] [DEPLOY] [DEV] [BUILD] [GURURMM] [SMARTBADGE-WATCH]` auto-route to the PRIVATE
#dev-alerts (Howard+Mike only, channel 1509998508198068484); everything else → #bot-alerts (whole
team). It soft-fails (best-effort), so it never blocks the work. Channel IDs + routing live in
`.claude/scripts/post-bot-alert.sh`.
As of 2026-07-06 there is NO CLAUDE.md rule enforcing this — treat this memory AS the rule until a core
rule/hook lands (worth proposing one). Related: [[discord-dm]] skill for direct DMs.

View File

@@ -0,0 +1,22 @@
# Git Bash `TZ=` is silently ignored on Windows — never use it for current-time reporting
**Date:** 2026-07-09
**Source:** Winter correction, Len's Auto browser-hijack thread (Discord bot session)
## What happened
Reported "current time" to Winter as `4:40 PM AZ` using
`TZ="America/Phoenix" date '+... AZ'` in Git Bash on BEAST. MSYS/Git-Bash has no
tzdata for that zone name, so `TZ` was silently ignored and the command printed
**UTC** (16:40) — which the format string then labeled "AZ". Real local time was
9:40 AM AZ. Winter caught it; caused a confusing side-quest about whether BEAST's
clock was a month off (it wasn't — clock + NTP + timezone all correct).
## Rule
- For current date/time on a Windows machine: use **PowerShell `Get-Date`**
(system local time — BEAST is already set to Arizona), or `date` in bash with
NO TZ override (MSYS date honors the Windows local zone), or an external HTTP
`Date:` header for authoritative UTC.
- Never trust `TZ=<IANA zone>` in Git Bash — unknown zones fall back to UTC with
no error, and a hardcoded zone label in the format string turns that into a
confidently-wrong answer.
- Calendar math (`date -d 2026-06-05 +%A`) is unaffected — that stays fine.

View File

@@ -9,3 +9,5 @@ When the system-reminder context claims `userEmail = mike@azcomputerguru.com` bu
**Why:** On 2026-04-23 I addressed Howard as "Mike" because the claudeMd/userEmail context said so. Howard corrected me. The CLAUDE.md onboarding flow explicitly defines identity.json as the authoritative local identity. **Why:** On 2026-04-23 I addressed Howard as "Mike" because the claudeMd/userEmail context said so. Howard corrected me. The CLAUDE.md onboarding flow explicitly defines identity.json as the authoritative local identity.
**How to apply:** At every session start, read `.claude/identity.json` FIRST (as CLAUDE.md step 1 requires) and greet from that file's `full_name`. Ignore the `# userEmail` context block for greeting purposes. If identity.json is missing, follow the first-machine bootstrap flow in CLAUDE.md — don't fall back to userEmail. **How to apply:** At every session start, read `.claude/identity.json` FIRST (as CLAUDE.md step 1 requires) and greet from that file's `full_name`. Ignore the `# userEmail` context block for greeting purposes. If identity.json is missing, follow the first-machine bootstrap flow in CLAUDE.md — don't fall back to userEmail.
This also covers **pronoun resolution**: "me"/"I"/"my" in any request (e.g. "send the list to me and Winter", "DM me the link") resolves to the identity.json user, NOT the userEmail account. On 2026-07-14 Howard asked to send a backup report "to me and Winter" and I DM'd Mike instead — Howard never got it. Any send/notify/assign target of "me" = the person in identity.json.

View File

@@ -0,0 +1,16 @@
---
name: LAB-SVR is retired — replaced by LAB-SERVER
description: Len's Auto machine LAB-SVR was decommissioned and replaced by LAB-SERVER; never chase LAB-SVR again
type: feedback
---
Len's Auto Brokerage's **LAB-SVR was replaced by LAB-SERVER**. Do NOT investigate, probe,
queue commands on, or report LAB-SVR as a stale/offline machine — it is retired.
**Why:** Howard has corrected this repeatedly ("for the last time... STOP LOOKING FOR THAT
MACHINE", 2026-07-15). LAB-SVR still appears in stale data (GuruRMM offline agent record,
old MSP360 plan rows), which keeps baiting audits into flagging it.
**How to apply:** In any backup/RMM/fleet audit, treat LAB-SVR rows as retired-machine
residue. Check LAB-SERVER for Len's Auto server health instead. Cleanup of the stale
LAB-SVR records (MSP360 plans/user side, RMM agent record) is the actual actionable item.

View File

@@ -0,0 +1,29 @@
---
name: feedback_scheduled_task_no_console_flash
description: Windows scheduled tasks must launch console apps windowless (wscript VBS for bash, pythonw + -Hidden for python) or they flash a console window on the desktop every run
metadata:
type: feedback
---
A Windows scheduled task whose action runs a **console-subsystem** program — `bash.exe`,
`py.exe`, `python.exe`, `cmd.exe` — with `LogonType=Interactive` and `Settings.Hidden=False`
draws a visible console window on the desktop **every time it fires**. On short repetition
intervals (the EDR watcher fires every 10 min) this reads to the user as a command prompt
"opening and closing" constantly. It is the single most reported harness annoyance and it
keeps recurring because the *installer scripts* recreate the flashing task.
**Why:** wscript.exe is a GUI-subsystem host and pythonw.exe is the windowless Python host;
neither allocates a console. bash.exe / py.exe / python.exe do.
**How to apply:** whenever you register (or find) a ClaudeTools scheduled task:
- **bash target** -> point the action at `C:\Windows\System32\wscript.exe` with a one-line VBS
wrapper that does `CreateObject("WScript.Shell").Run "...bash.exe -lc ...", 0, False`
(window style `0` = hidden). Pattern files: `gps-rmm-progress-hidden.vbs`,
`edr-isolation-watch-hidden.vbs`.
- **python target** -> use `pythonw.exe` (next to the active interpreter), never `py.exe`/`python.exe`.
- **Always** add `-Hidden` to `New-ScheduledTaskSettingsSet` as belt-and-suspenders.
- Verify: `Start-ScheduledTask`, then confirm no visible bash/wscript/conhost MainWindow.
Fixed installers: `register-edr-watcher.ps1`, `register-orphan-detector.ps1`. The scheduled
tasks themselves are per-machine (not in the repo) — re-run the fixed installer, or repoint
the existing task's action, on every box that runs it. Related: [[feedback_session_recovery]].

View File

@@ -0,0 +1,23 @@
---
name: feedback_screenconnect_cleanup_wiki_source
description: ScreenConnect/RMM machine-metadata cleanup uses the client wiki as source of truth; enrich the wiki when info is missing
metadata:
type: feedback
---
For the ScreenConnect session-hygiene + RMM-site cleanup (per client: normalize Company,
set Site/Department/Device Type/Tag, fix RMM sites, dedup) — **the client wiki is the primary
source of truth for machine -> department / person / location.** (Howard, 2026-07-03.)
**Why:** the wiki already carries person->machine->department maps (e.g. Cascades: Ashley
Jensen->Accounting on DESKTOP-U2DHAP0, Shelby Trozzi->MemCare Director on MDIRECTOR-PC), so
department/site can be derived from it rather than guessed.
**How to apply, per client:**
1. Pull the machine metadata from the client wiki FIRST (`wiki/clients/<slug>.md` + its source docs), then hostname role tokens, then UniFi switch/AP building/area names.
2. Where a machine's department/location is NOT in the wiki, **learn it (ask the user / investigate) and UPDATE the wiki** with the finding — so the wiki becomes the durable record and the next pass is easier.
3. Slot mapping = [[reference_screenconnect_custom_property_slots]] (CP1 Company, CP2 Site, CP3 Department, CP4 Device Type, CP8 Tag). Match each client's EXISTING vocabulary (e.g. Cascades uses Device Type "Desktop"/"Laptop"/"Server", not "Workstation"; Dataforth uses "Workstation").
4. UniFi placement: Dataforth = cloud UDM via Site Manager connector; Cascades = UOS controller. AP/switch names are building/area-coded. Related: [[project_av_migration_bitdefender_to_edr]], GPS->RMM audit `projects/gps-rmm-audit/tracker.md`.
Done so far: Dataforth (D1/D2 sites split + tags), Cascades (single-site + departments). Remaining
per-client unknowns get filled from the wiki as we walk each machine; wiki gets updated when it doesn't have it.

View File

@@ -0,0 +1,20 @@
---
name: feedback_simplest_solution_first
description: Always try the simplest solution to a problem first; escalate to complex only as needed
metadata:
type: feedback
---
Always START with the simplest solution that could solve the problem. Confirm it works, then
graduate to more advanced/complicated approaches only as the simple one proves insufficient.
**Why:** Mike watched a "ask the UDM what IP it thinks VWP-QBS is" turn balloon into vault
reads, plink host-key pinning, and password sanity checks — when the clean answer was a single
`nslookup vwp-qbs.vwp.us 172.16.9.1` from a machine already on the VPN. The heavy path burned
tokens and his patience ("incapable of doing things cleanly like a week ago"). Now a standing
CLAUDE.md core rule.
**How to apply:** Before picking tooling, ask "what is the least-complex thing that answers
this?" — one DNS lookup, one API call, one command. Do that first and observe the result.
Only reach for SSH/plink/double-hops/multi-step orchestration when the simple probe genuinely
can't answer. Escalate deliberately, not by default. See [[feedback_verify_live_before_acting]].

View File

@@ -0,0 +1,10 @@
# Feedback: Report times in Arizona time
**Source:** Winter, via Discord bot thread, 2026-07-02
**Rule:** All times reported to users (Discord replies, reports, logs, tickets) are in
Arizona time — `America/Phoenix`, MST (UTC-7) year-round, no DST. Convert API/log UTC
timestamps before presenting and label them "AZ". Include UTC only when needed for
cross-referencing raw logs.
Applied to `projects/discord-bot/DISCORD_CLAUDE.md` (formatting rules + rolling-log
timestamp format changed from PT to AZ) same day.

View File

@@ -5,6 +5,16 @@ metadata:
type: feedback type: feedback
--- ---
**MECHANICAL FIX FIRST (2026-07-01): for any PowerShell payload crossing a
mangling layer (RMM dispatch, ScreenConnect command box, plink, curl.exe args),
use `.claude/scripts/ps-encoded.sh`** — it UTF-16LE-base64 encodes a script file
and delivers it via `powershell -EncodedCommand` (`encode` prints the paste-safe
one-liner; `rmm <agent-uuid> <file>` dispatches + polls via GuruRMM). Base64 has
no quotes/backslashes/`$` to strip, so the script arrives byte-exact (verified:
UNC `\\` survives). Author the script with the **Write tool**, not a bash heredoc
(Git-bash heredocs collapse `\\` even single-quoted). The manual rules below
remain for one-off inline args only.
On Windows, **embedded double-quotes inside a command argument get silently On Windows, **embedded double-quotes inside a command argument get silently
stripped or mangled** at two separate layers we hit repeatedly. The body of the stripped or mangled** at two separate layers we hit repeatedly. The body of the
arg survives; the `"` characters vanish, so the receiving program sees broken arg survives; the `"` characters vanish, so the receiving program sees broken

View File

@@ -0,0 +1,32 @@
---
name: project_av_migration_bitdefender_to_edr
description: AV strategy — migrate all clients from Bitdefender to Datto EDR; ONLY exception is Glaztech
metadata:
type: project
---
Standing AV direction (Howard 2026-07-03, scope narrowed 2026-07-04): ACG is moving
endpoint AV/security from **Bitdefender GravityZone -> Datto EDR** for **all clients
EXCEPT Glaz-Tech Industries (glaztech)** — the ONLY client staying on Bitdefender.
**Dataforth migrates fully** (originally excepted; Howard removed the exception
2026-07-04 — Dataforth already runs 51 EDR agents with only 5 BD endpoints left:
D1-ENGI-006, DESKTOP-L2LE31M, DATAFORTH-PC, SURFACEOPS, MING-HP).
**Why:** consolidate on Datto EDR as the security plane; Bitdefender is being retired
fleet-wide. Glaztech keeps its large established Bitdefender footprint (~242 BD
endpoint records, vs 159 GPS-billed — count includes stale ghosts).
**How to apply:** whenever setting up or reconciling a client's endpoints (e.g. the
GPS->GuruRMM coverage audit), the target end-state per machine is: GuruRMM agent +
Datto EDR agent (AV on), and Bitdefender **removed**. Do NOT deploy new Bitdefender
coverage. Use existing Bitdefender inventory only as a discovery source for which
machines exist (its company names carry the Syncro CID `_NNNNN` suffix — exact join
key to Syncro customers). Deploy Datto EDR via `[[datto-edr]]` (create-group ->
mint-key -> deploy-cmd, pushed through `/rmm`).
Migration scope quantified 2026-07-04 (tracker Phase 4): 27 clients / 141 BD
endpoints + Dataforth's 5. Datto EDR "Default RMM Org" holds ~35 unassigned agents
that belong to real clients (IMC x7, Russo x2, Len's, Rednour, Reliant, etc.) —
attribute them to proper orgs as part of the migration.
Related: GPS->RMM audit tracker `projects/gps-rmm-audit/tracker.md`.

View File

@@ -8,7 +8,18 @@ metadata:
Cascades is migrating staff machines + printers off the flat old LAN (192.168.0.0/22, Cascades is migrating staff machines + printers off the flat old LAN (192.168.0.0/22,
"CSC ENT") onto the isolated **Staff VLAN 20 (10.0.20.0/24, gw 10.0.20.1)** ("CSCNET"). "CSC ENT") onto the isolated **Staff VLAN 20 (10.0.20.0/24, gw 10.0.20.1)** ("CSCNET").
Printers are being re-IP'd to 10.0.20.x (static) and re-shared on the CS-SERVER print Printers are being re-IP'd to 10.0.20.x (static) and re-shared on the CS-SERVER print
server. Key operational facts learned 2026-06-30 (Howard, front-desk ET-5800 + Life server.
**PROGRESS (live-reconciled 2026-07-01):** MACHINES are essentially done — 22 online hosts on
VLAN 20; only CS-SERVER (stays by design) + ~6 stragglers (ASSISTMAN-PC, CascadesProxess,
Laptop2, NurseAssist, 2 roaming laptops) remain on 192.168.x. PRINTERS lag — only 4 of 15
CS-SERVER shares repointed to 10.0.20.x (FrontDesk .221, BusinessOffice .220, LifeEnrichment
.94, MCReception .78); 11 shares still on old-LAN IPs (MCMedTech still stale 192.168.2.53 though
target 10.0.20.74 is live+reachable). GPO still NOT fleet-live (silent new-driver-install gap
open: reboot vs pre-stage drivers). MEMCARE-STATION rename staged but not yet applied. Full
live inventory (all shares/ports/machines): clients/cascades-tucson/docs/printer-gpo-map.md.
Key operational facts learned 2026-06-30 (Howard, front-desk ET-5800 + Life
Enrichment Canon MF741CDW): Enrichment Canon MF741CDW):
**pfSense gotcha (the big one):** CS-SERVER (on the old LAN) could not reach ANY VLAN 20 **pfSense gotcha (the big one):** CS-SERVER (on the old LAN) could not reach ANY VLAN 20

View File

@@ -0,0 +1,28 @@
---
name: project_edr_rmm_autoisolation_fp
description: Datto EDR "Exfiltration Over HTTP" rule auto-isolates benign GuruRMM PowerShell fleet-wide; watcher + narrow suppression deployed, policy fix pending Mike
metadata:
type: project
---
Incident 2026-07-07: **vwp-qbs** (Valley Wide Plastering QuickBooks server) was auto-isolated by Datto EDR — Mike lost ~4h before realizing EDR did it. No notification fired (EDR alert delivery was never wired to any channel).
**Root cause:** Datto EDR rule **"Exfiltration Over HTTP Protocol" (MITRE T1041, HIGH)** carries an automated response of **`kill-process` + `isolate-host`**. It fires on *signed* PowerShell doing an outbound HTTPS POST — which our **GuruRMM automation** does routinely. Process tree on the alerts: `gururmm-agent.exe -> cmd.exe -> powershell.exe`. Confirmed benign both times (vwp-qbs = a vSphere VM-power-on script; tps-tina = a network/connectivity diagnostic).
**This is SYSTEMIC, not a one-off.** The same rule fired + attempted auto-isolate on **tps-tina** (The Prairie Schooner) the same day — different GuruRMM script, different client. Per-command-line suppression is whack-a-mole. Note: the isolate-host response only actually cuts a host off where its endpoint policy has isolation enabled (vwp-qbs got isolated; tps-tina fired the same alert but `extensionSuccess=None`, stayed online).
**FLEET-WIDE FIX LIVE (2026-07-07):** Datto EDR suppression rule **`3365e79a`** "GuruRMM origin - Exfil-over-HTTP (fleet-wide FP)" — matches Rule Name = Exfiltration Over HTTP Protocol + Grand Parent = `gururmm-agent.exe`, org/loc empty = ALL clients. Suppresses this one rule for any GuruRMM-launched process fleet-wide (present + future scripts); every other rule still fires on RMM-origin. Created in the CONSOLE (by Howard) after the API create path proved unsafe. The two earlier narrow per-command-line rules (`e4dd55bf`, `7ea2a577`) are now redundant subsets — harmless, left in place.
**API FOOTGUN (do NOT create suppressions via `raw POST` on this tenant):** `POST /SuppressionRules` forces `active:true` and auto-creates an EMPTY match-everything version → a live "suppress all" window (I hit this with `7c36b89f`, deleted within ~2 min). `GET`/`PATCH /SuppressionRules/{id}` 400 ("undefined is not valid JSON"); `DELETE /{id}` works. Create suppressions in the CONSOLE until an atomic API create is verified. Details in `.claude/skills/datto-edr/references/api-reference.md`.
**What's deployed (done):**
- Narrow suppression rule (Datto EDR, id `e4dd55bf`) for VWP's exact script: matches Process Command Line + Grand Parent = `gururmm-agent.exe`. Does NOT blind other PowerShell. API schema for suppression is now in `.claude/skills/datto-edr/references/api-reference.md`.
- **EDR isolation watcher**: `.claude/scripts/edr-isolation-watch.sh` + `register-edr-watcher.ps1`, registered as Windows Scheduled Task **"ClaudeTools - EDR Isolation Watcher"** on Howard-Home (runs every 10 min, posts new auto-isolations to #dev-alerts — see [[feedback_fire_dev_alerts_for_client_work]]). Plain script, zero tokens. State file `.edr-watch-state.json` (gitignored) dedupes; seeded with the vwp-qbs + tps-tina event ids so it stays silent for the already-triaged ones. Retire when GuruRMM EDR webhook (Feature 6) lands.
**Pending Mike decisions (do NOT act without him):**
1. Auto-isolate vs **alert-only** on behavioral rules — the real fix (the isolation, not the alert, caused the outage).
2. Whether to allow a small curated "GuruRMM-origin + this-rule OK" exception set — but NOT a blanket `gururmm-agent.exe` whitelist (RMM = top MSP attack path; blinding EDR there is dangerous).
3. **Fix the RMM scripts** to stop looking like exfil (drop `-EncodedCommand`, least-priv accounts).
4. Hygiene: VWP's script had **ESXi root creds hardcoded** (base64, cert-validation disabled) — already in vault `clients/vwp/esxi.sops.yaml`; move to a least-priv service account + vault injection.
See [[reference_datto_edr_detection_behavior]] (behavioral rules DO fire on signed PowerShell, unlike reputation rules).

View File

@@ -0,0 +1,27 @@
---
name: project_gururmm_legacy_disabled
description: GuruRMM's legacy Rust 1.77 (Server 2008R2 / Win7) build variant is DISABLED as of 2026-07-04 — do not just re-enable it; 2008R2 support returns via a separate legacy agent (C++/.NET or clean rewrite)
metadata:
type: project
---
The GuruRMM Windows build's **legacy Rust 1.77 variant is disabled** (2026-07-04) via
`LEGACY_ENABLED=false` in `deploy/build-pipeline/build-windows.sh`.
**Why:** the legacy build moves `Cargo.lock` aside and re-resolves deps unpinned; crates.io now
serves edition2024 versions of transitive deps (`chacha20`/`rand_core` 0.10.1) that Rust 1.77
cannot parse ("feature `edition2024` is required"). This fail-closed the ENTIRE Windows build and
blocked modern (amd64/x86/tray) deploys. It is a **BUG-021 recurrence** and is NOT caused by any
agent code change — it reproduces at any base commit right now. This is at least the 3rd edition2024
break of the 1.77 build (the `agent/Cargo.toml` transitive-pin list is a monument to it).
**What the disable does:** skips the legacy wave/copy/deploy/symlink and EXCLUDES legacy from the
artifact cleanup, so the last-built legacy binaries (**0.6.76**) are PRESERVED and existing Server
2008R2 / Win7 endpoints stay supported *frozen at 0.6.76*. Modern agents advance normally.
**Mike's directive (2026-07-04):** "Kill the legacy version for now, but we will come back to it" —
2008R2 support is REQUIRED (a number are still in the wild) and will return via a **different
architecture (C++/.NET) or a clean rewrite for legacy**, NOT by flipping `LEGACY_ENABLED` back on
without first solving the 1.77 edition2024 pin problem. Do NOT re-enable it blindly. Tracked in
`projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md`. Related: the policy-controlled tray feature that
was shipping when this surfaced ([[project_guruconnect]] is a different project).

View File

@@ -0,0 +1,31 @@
---
name: project_gururmm_software_removal_pr58
description: SPEC-030 software-removal hardening (driver exclusion, lingering-process sweep, job list/reaper) is on PR #58 awaiting merge; live testing planned 2026-07-06 on DESKTOP-MS42HNC.
metadata:
type: project
---
GuruRMM SPEC-030 software removal (Assets -> Inventory tab): the 2026-06-24 session's open
requests were implemented 2026-07-05 and sit on branch `feat/software-removal-hardening`,
**PR #58** (https://git.azcomputerguru.com/azcomputerguru/gururmm/pulls/58), NOT merged —
merge deploys to beta. Live testing planned 2026-07-06 on the test box DESKTOP-MS42HNC
(agent 0de89b88-b21d-4647-ab64-96157ba87cc5).
What PR #58 contains:
- Engine `-List` emits `is_driver` (heuristic: driver/chipset/firmware name words, or hw-vendor
publisher + device words; junkware like "Driver Booster" deliberately excluded). Dashboard
hides drivers by default, keeps them out of select-all, deselects on re-hide.
- End-of-run lingering-uninstaller sweep in uninstall-engine.ps1: 45s budget-aware grace, then
taskkill of started trees (StartTime PID-reuse guard, same-session only, null CreationDate
excluded) + late re-verify that flips async-finisher false-failures to success. This is the
chosen resolution of the Launchy/AIMP "failed / no usable result" open decision.
- `GET /agents/:id/software/uninstall/jobs` (list, results omitted), stale-running-job reaper
(900s no-progress -> failed), `finish_job` guarded WHERE status='running', UI 100-target cap.
Verified pre-merge: engine live tests on Howard-Home (driver flags, sweep kill/no-kill,
orphaned/refused paths), heuristic unit cases, `verify.sh server --check` + `dashboard` PASS,
high-effort workflow code review with all confirmed findings fixed.
Remaining after this: recipe catalog (P3, [[project_gururmm_av_removal_recipes]] if created),
dashboard UI for the new job-list endpoint (client binding `softwareApi.uninstallJobs` exists,
unused), and TeamViewer retry-verify still not re-validated live.

View File

@@ -0,0 +1,23 @@
---
name: tech03l-systemprofile-shortcut-corruption
description: ACG-TECH03L shortcuts got rewritten to nonexistent systemprofile paths (Auto-Claude "opens then closes"); repointed via RMM 2026-07-04
metadata:
type: project
---
On ACG-TECH03L (Howard's laptop, "tech-03"), the desktop + Start Menu **Auto-Claude**
shortcuts and the **UltraSearch** shortcut were found pointing at
`C:\Windows\system32\config\systemprofile\...` paths that do not exist — symptom was
"ClaudeTools opens and then closes" when Howard double-clicked the launcher. The Claude
Code CLI itself was healthy (2.1.160, node v24.16.0, repo + identity.json intact).
**Why:** something (likely an installer/updater running under SYSTEM context, e.g. via
RMM) resolved `%LOCALAPPDATA%`/`%USERPROFILE%` to the SYSTEM profile when writing the
shortcuts. If a Claude-adjacent launcher "flashes and closes" on a fleet machine, check
the .lnk TargetPath for `systemprofile` FIRST before debugging the app.
**How to apply:** repoint the .lnk to the real per-user install
(`C:\Users\<user>\AppData\Local\Programs\auto-claude-ui\Auto-Claude.exe`) via
WScript.Shell as SYSTEM — fixed 2026-07-04 (RMM cmd 0c9180df). UltraSearch was left
broken: no user-profile copy exists on disk; needs reinstall if Howard wants it.
Avoid running per-user app installers/updaters from SYSTEM context.

View File

@@ -0,0 +1,12 @@
---
name: project-vwp-rose-qb-updater
description: VWP-ROSE QuickBooks updater crash-loop - RESOLVED 2026-07-13 (repair install); open side findings NETLOGON 5719 + Syncro agent crashes
metadata:
type: project
---
**RESOLVED 2026-07-13:** QuickBooks Desktop 2024 updater crash loop on VWP-ROSE (Valley Wide Plastering) - qbupdate/FCS heap corruption (0xc0000374) surviving reboot. Fixed via remote session: repair on the QuickBooks installation; update now completes. Billed 0.5 hr remote against the VWP prepay block (16.25 hrs remaining), invoice #68041, ticket #32545 Invoiced (https://computerguru.syncromsp.com/tickets/113779446). Rose's login vaulted at `clients/vwp/vwp-rose-workstation`.
**Open side findings on VWP-ROSE** (not yet addressed, from 7/13 event logs):
- NETLOGON 5719 + DNS registration failures at boot - machine can't reach a VWP domain controller at startup; may cause auth/mapped-drive issues.
- Syncro agent services (Syncro.Service.Runner / SyncroLive.Service.Runner) crashed twice on the morning of 2026-07-13 (KERNELBASE .NET exceptions).

View File

@@ -0,0 +1,25 @@
---
name: ps51-invoke-restmethod-headers-variable
description: PS5.1 via RMM powershell -File - Invoke-RestMethod -Headers $H (script-scope hashtable) sent an EMPTY Authorization header; build headers inline per call instead
metadata:
type: reference
---
On ACG-DWP-X-BB (WS2019, PowerShell 5.1, script executed via GuruRMM agent as
`powershell -NoProfile -File script.ps1`), passing a script-scope hashtable variable to
`Invoke-RestMethod -Headers $H` (where `$H=@{Authorization="Bearer $tok"}`) resulted in
Graph rejecting every call with `InvalidAuthenticationToken / "Access token is empty"`
even though the token variable was verifiably populated (fingerprint printed correctly)
and the same token + same URL worked with an inline-built header from the same machine.
**Fix that works:** build the header at each call site — e.g.
`function Hdr { return @{Authorization=("Bearer " + $script:tok)} }` then
`Invoke-RestMethod -Uri $u -Headers (Hdr)`. Inline `@{Authorization=('Bearer '+$t)}` also works.
Diagnosed 2026-07-02 during the BirthBio Datto-vs-SharePoint reconciliation after two
full failed runs (masqueraded first as token expiry, then as clock skew — the real tell
was the Graph error body "Access token is empty", captured only after adding response-body
extraction to the retry helper). Always capture the Graph error BODY, not just the
exception message: "(401) Unauthorized" alone cost three debugging cycles.
Related: [[gururmm-command-timeout-seconds]], [[sharepoint-graph-large-file-upload]]

View File

@@ -0,0 +1,32 @@
---
name: reference_365_app_suite
description: Authoritative map of the ComputerGuru M365 app suite (apps, App IDs, live-verified permissions per tier) and — the recurring failure — per-tenant consent is NOT uniform; how to audit + fix partial consent.
metadata:
type: reference
---
The ComputerGuru M365 app suite is fully documented in the remediation-tool skill:
`.claude/skills/remediation-tool/references/app-suite.md` (authoritative; live-verified
2026-07-02). Read it before concluding "the tool can't do X on tenant Y".
**The recurring failure it fixes:** per-tenant consent is NOT uniform. A tenant can have an
app's service principal but only a PARTIAL/OLD permission grant. Example: VWP
(valleywideplastering.com, 5c53ae9f-…) had the Tenant Admin app but NO SharePoint
`Sites.FullControl.All` — SharePoint calls 401'd with a valid-looking token whose `roles`
claim was empty. The suite "having" a capability (baseline design) ≠ a given tenant having it
(actual consent).
**Always AUDIT before giving up:** decode each tier's token `roles` on the target tenant and
compare to the baseline in app-suite.md. Empty roles on a correct `aud` = present-but-not-granted.
**Fix partial consent — two methods:**
- A: re-consent the whole manifest — `https://login.microsoftonline.com/<tenant>/adminconsent?client_id=<app-id>` (reliably grants Graph; the SharePoint app-only role often does NOT attach from consent — verify + use B for the leftover).
- B: grant the specific missing app role directly via `POST /servicePrincipals/{recipientSP}/appRoleAssignments` using a `tenant-admin` token (holds AppRoleAssignment.ReadWrite.All). This is how VWP's SharePoint role was granted 2026-07-02; propagates to a fresh token in seconds. Only to complete an intent the customer already consented to.
- EXO role gap: `assign-exchange-role.sh <domain>` (audit fleet: `--all --verify`).
Apps: Security Investigator bfbc12a4 (Graph read + EXO read), Exchange Operator b43e7342
(EXO all-access + `exchange-op-graph` Graph Mail.ReadWrite), User Manager 64fac46b (Graph
user/group write), Tenant Admin 709e6eed (Graph high-priv + SharePoint Sites.FullControl.All
via CERT), Defender dbf8ad1a (MDE), Intune 46986910, Mailbox 1873b1b0 (ACG-internal only).
SharePoint app-only REQUIRES cert (not secret). See [[reference_remediation_tool_365_access]],
[[feedback_exchange_role_recurring_gap]], [[feedback_exchange_op_all_access]].

View File

@@ -1,12 +1,24 @@
--- ---
name: reference_antigravity_agy_not_headless name: reference_antigravity_agy_not_headless
description: Antigravity CLI agy.exe is the IDE embedded agent (no stdout, SQLite store) — NOT a headless CLI. The agy skill uses @google/gemini-cli, not agy.exe. Don't reinstall agy.exe expecting a headless tool. description: UPDATE 2026-07-03 — agy.exe NOW works headless (v1.0.6+ --print/-p returns clean stdout). Use it as the Antigravity/Gemini second-model path. Older claim (DOA headless) is obsolete.
metadata: metadata:
type: reference type: reference
--- ---
The `agy.exe` installed by Google's Antigravity CLI (`%LOCALAPPDATA%\agy\bin\agy.exe`, installer `https://antigravity.google/cli/install.ps1`) is the IDE's embedded agent, **NOT a usable headless CLI** on this fleet. Even v1.0.6's advertised `-p/--print` produces ZERO stdout and hangs when invoked non-interactively from the Bash/PowerShell tool harness — it writes only to a SQLite conversation store. First found 2026-06-05 (`session-logs/2026-06-05-mike-gururmm-platform-day.md` line 35); **re-confirmed 2026-06-06** after the GURU-5070 reinstall (reinstalled agy.exe and walked straight back into the same no-output/hang symptom). **CORRECTION 2026-07-03 (GURU-BEAST-ROG):** `agy.exe` (Google Antigravity CLI,
`%LOCALAPPDATA%\agy\bin\agy.exe`) **now has a working headless print mode.** `agy -p "<prompt>"
--dangerously-skip-permissions` returns clean stdout and exits 0 (smoke-tested: returned "READY";
also drove a full multi-file spec critique reading files via `--add-dir <dir>`). Flags: `-p/--print`
(single non-interactive prompt), `--print-timeout` (default 5m), `--add-dir` (add a workspace dir so
it can read files), `--model`, `--dangerously-skip-permissions` (auto-approve tools). Subcommands:
`models`, `install`, `update`, `plugin`. Re-wire came from GURU-5070 fleet sync + a newer agy build.
So agy IS now a usable Antigravity/**Gemini second-model** path, especially when `gemini-cli` OAuth
fails (e.g. BEAST-ROG's `throwIneligibleOrProjectIdError`). NOTE: after install the binary is added to
User PATH registry but NOT the active shell PATH until terminal restart — call it by full path
`/c/Users/guru/AppData/Local/agy/bin/agy.exe` from the Bash tool.
The `agy` SKILL (despite the name) routes to the official **`@google/gemini-cli`** (`gemini`, npm global) — that IS the real headless second-opinion tool (Google OAuth, no API key), resolved via `identity.json .gemini.binary`. Grok (`ask-grok.sh`) is the other working second model. Both were verified returning `OK` on 2026-06-06. **Historical (pre-2026-07-03, now OBSOLETE):** earlier agy.exe builds (~June 2026) produced ZERO
stdout in `-p` mode and hung when driven headless (wrote only to a SQLite conversation store), so the
**June 18 sunset — likely a non-issue for ACG.** Google is sunsetting gemini-cli's free/unpaid OAuth quota on **2026-06-18**, but Mike has a **paid Gemini account**, so the plan is to **stay on gemini-cli** (do NOT migrate to Antigravity). The bulletproof form is to auth gemini-cli with a paid **Gemini API key** (`GEMINI_API_KEY`) rather than the free OAuth quota — that path is unaffected by the OAuth-CLI sunset regardless of how the consumer tiers shake out, and is more stable for headless use. (Sources disagree on whether paid Pro/Ultra OAuth is also cut, so the API-key path is the safe bet.) **Do NOT reinstall agy.exe expecting it to work headless.** Related: [[feedback_agy_review_not_readonly]]. `agy` SKILL was pointed at `@google/gemini-cli` instead. The gemini-cli path still exists
(`ask-gemini.sh`) but on machines where its OAuth is ineligible, prefer **agy -p** now. Related:
[[reference_cdp_chrome_driver]], [[feedback_agy_review_not_readonly]].

View File

@@ -0,0 +1,26 @@
---
name: reference_claude_code_bun_crash_pin
description: Claude Code v2.1.114+ (Bun-based builds) segfault at startup on Windows 11 — fix is pin to 2.1.110 + DISABLE_AUTOUPDATER; unpin when upstream fix ships
type: reference
---
Claude Code v2.1.114+ ships as a Bun-compiled binary; on some Windows 11 builds (seen on
Howard-Home, Win11 build 26100, CC v2.1.207, 2026-07-13) NEW interactive sessions crash at
TUI init with `panic: Segmentation fault at address 0x3C` / "oh no: Bun has crashed".
Already-running sessions keep working (binary loaded in memory); only fresh launches die.
Tracked: anthropics/claude-code #55219 (+ matching upstream Bun issue). `--version`/`--help`
still work. It is a runtime bug inside the shipped binary — no config/cache/reinstall of the
same version helps.
**Fix (verified working 2026-07-13, Howard-Home):**
1. `npm install -g @anthropic-ai/claude-code@2.1.110` (last pure-Node build; installs fine
even while an older session is running — the EPERM "cleanup" warning is just npm failing
to delete the locked running exe, the install itself succeeds).
2. Pin it — in `~/.claude/settings.json` add:
`"env": { "DISABLE_AUTOUPDATER": "1" }`
NOTE: a field named `autoUpdate` does NOT exist in the settings schema (validation
rejects it); the env-var route is the correct kill switch.
3. **Unpin when fixed:** watch issue #55219; then `npm i -g @anthropic-ai/claude-code@latest`
and remove the env entry.
Do NOT switch to the native installer as a workaround — it is also Bun-based (same crash).

View File

@@ -0,0 +1,32 @@
---
name: reference_client_wifi_inventory
description: Where client Wi-Fi networks are recorded so techs connect onsite without asking — vault holds passwords, wiki holds the readable index.
metadata:
type: reference
---
We are building a fleet-wide client Wi-Fi list so anyone onsite can connect without asking
for the SSID/password again. Established 2026-07-06 (Howard).
**Split (security-driven):**
- **Passwords/PSKs → SOPS vault**, `clients/<slug>/wifi.sops.yaml`, one file per client.
Per network use `credentials.<key>_ssid`, `credentials.<key>_password`, optional
`credentials.<key>_auth` (`<key>` = `staff`/`guest`/`voice`/`warehouse`...). Everything under
`credentials:` is encrypted at rest, so the entry is self-contained for an importer. Write it
with the `vault` skill's `vault-helper.sh new`/`set` (NEVER paste a Wi-Fi password into chat,
a ticket, a session log, or the wiki).
- **Readable index → wiki**, `wiki/reference/client-wifi.md` (registered in `wiki/index.md`
under a new **Reference** section). Client / SSID / band / location / vault-field — NO passwords.
It lives under `wiki/reference/` so `/wiki-compile` never clobbers it (compile only touches
clients/projects/systems slugs).
**Capture flow onsite:** ask "Wi-Fi name + password? staff vs guest? which should our machines
use?" → store in vault via vault-helper → add a row to the wiki inventory → `/sync`.
**Importer is DEFERRED** (Howard chose "structure only for now" 2026-07-06). Planned end state:
a `/wifi import <client>` that reads the vault file, builds a Windows WLAN profile per network
(`netsh wlan add profile`), and auto-connects. Interim: read the password with
`vault.sh get-field clients/<slug>/wifi credentials.<key>_password` and connect manually.
Build the importer/`/wifi` skill once a few real networks exist.
Keep `<slug>` identical to the client's `wiki/clients/<slug>.md` slug so vault + wiki + article line up.

View File

@@ -14,6 +14,8 @@ Verified live 2026-06-25/26 on RMM-TEST-MACHINE (EDR agent `b98b3ba0-...`, group
**Datto EDR is reputation-based, NOT structural.** A synthetic "looks suspicious" artifact (Run-key/scheduled task launching hidden `-EncodedCommand` powershell) is collected by the forensic scan but scored BENIGN → no alert (powershell.exe is signed/clean). To get an EDR detection you need a real reputation hit: wire a known-bad file as the **executable of an autostart** (Run-key/scheduled task) so the survey collects + hashes it. EICAR-as-autostart works → high-sev `rule` alert. A loose file on disk is NOT scanned by the EDR forensic survey (it only walks execution/persistence artifacts). **Datto EDR is reputation-based, NOT structural.** A synthetic "looks suspicious" artifact (Run-key/scheduled task launching hidden `-EncodedCommand` powershell) is collected by the forensic scan but scored BENIGN → no alert (powershell.exe is signed/clean). To get an EDR detection you need a real reputation hit: wire a known-bad file as the **executable of an autostart** (Run-key/scheduled task) so the survey collects + hashes it. EICAR-as-autostart works → high-sev `rule` alert. A loose file on disk is NOT scanned by the EDR forensic survey (it only walks execution/persistence artifacts).
**BUT behavioral rules DO fire on signed PowerShell** (unlike the reputation scoring above). Rules like **"Exfiltration Over HTTP Protocol" (T1041)** key on *runtime behavior* (outbound HTTP from powershell), not file reputation, so they alert HIGH on clean signed `powershell.exe` — and can carry an automated `isolate-host`/`kill-process` response. This routinely false-positives on GuruRMM automation (`gururmm-agent.exe -> cmd.exe -> powershell`). See [[project_edr_rmm_autoisolation_fp]].
**AV-suppression gotchas (to isolate EDR on an endpoint):** **AV-suppression gotchas (to isolate EDR on an endpoint):**
- Datto AV is **tamper-protected**: `Stop-Service EndpointProtectionService2 -Force` is refused ("cannot be stopped"); can't disable from the endpoint. Supported path = console policy (AV disabled / path-exclusion) — console-only, like policy assignment. - Datto AV is **tamper-protected**: `Stop-Service EndpointProtectionService2 -Force` is refused ("cannot be stopped"); can't disable from the endpoint. Supported path = console policy (AV disabled / path-exclusion) — console-only, like policy assignment.
- Disabling Datto AV in the policy **uninstalls** the AV component on the box (services `EndpointProtectionService`/`...2` go absent; `HUNTAgent` EDR stays). Platform `dattoAvEnabled` flips to False at the console first; the on-box apply lags a few minutes. - Disabling Datto AV in the policy **uninstalls** the AV component on the box (services `EndpointProtectionService`/`...2` go absent; `HUNTAgent` EDR stays). Platform `dattoAvEnabled` flips to False at the console first; the on-box apply lags a few minutes.

View File

@@ -1,12 +1,24 @@
--- ---
name: reference_guru5070_rust_toolchain name: reference_guru5070_rust_toolchain
description: GURU-5070 has the full local Rust toolchain (cargo + MSVC + protoc) — build/clippy/test the guru-connect workspace LOCALLY instead of the build host; set PROTOC first description: GURU-5070 has a full Rust toolchain installed BUT a WDAC/Smart App Control policy now blocks running rustc/cargo locally (os error 4551) — build the Windows agent on Beast, not here; toolchain paths + CI gates still valid for reference
metadata: metadata:
type: reference type: reference
--- ---
As of 2026-05-30, GURU-5070 has the full Rust dev toolchain installed, so GuruConnect can be **BLOCKED AS OF 2026-07-04 — do NOT rely on local Rust builds on GURU-5070.** A Windows
built/linted/tested locally — **no more build-host (172.16.3.30) round-trips just for `cargo fmt`/clippy.** Application Control (WDAC / Smart App Control) policy now blocks execution of `rustc.exe`/`cargo.exe`
(and `sops.exe`, `openssl`, `gawk`, Python `cryptography`'s native DLL) — attempts fail with
`An Application Control policy has blocked this file. (os error 4551)`. This is the same policy behind
the "Windows can't confirm who published …" blocks on unsigned binaries. Until the policy is adjusted
to trust the toolchain, **compile Windows agent/GuruConnect changes on Beast** (`guru@100.101.122.4`,
Tailscale; sccache at `C:\sccache`) via a throwaway `git worktree` + `cargo check` — see
[[gururmm-beast-windows-build-host]]. The paths/gates below remain accurate for reference and for when
the policy is relaxed. WDAC impact also breaks the `vault`/`sops` path here (use `age`+Node fallback per
[[feedback_vault_gcm_shadow_auth]]) and silently swallowed error logging (`CLAUDETOOLS_ROOT=D:/claudetools`
wrong-case → `log-skill-error.sh` can't write; real repo is `D:/ClaudeTools`).
As of 2026-05-30, GURU-5070 has the full Rust dev toolchain installed (able to build locally *until the
2026-07-04 WDAC block above*):
- **cargo/rustc/clippy/rustfmt:** `C:\Users\guru\.cargo\bin\` (rustup; cargo 1.96, rustfmt 1.9, clippy 0.1.96). - **cargo/rustc/clippy/rustfmt:** `C:\Users\guru\.cargo\bin\` (rustup; cargo 1.96, rustfmt 1.9, clippy 0.1.96).
- **MSVC C++ Build Tools:** VS2022 BuildTools (VCTools workload) — provides the `x86_64-pc-windows-msvc` linker. - **MSVC C++ Build Tools:** VS2022 BuildTools (VCTools workload) — provides the `x86_64-pc-windows-msvc` linker.

View File

@@ -0,0 +1,12 @@
---
name: GuruRMM build server SSH access
description: How to SSH guru@172.16.3.30 (key file name is non-obvious); saves re-discovery during rmm-audit pipeline passes
type: reference
---
SSH to the GuruRMM build/prod server as `guru@172.16.3.30` works with the key
`~/.ssh/gururmm-physical` (at least on Howard-Home) — there is no id_rsa/id_ed25519,
so tools that assume default key names report "no key / Permission denied (publickey)".
Use: `C:/Windows/System32/OpenSSH/ssh.exe -i ~/.ssh/gururmm-physical -o BatchMode=yes guru@172.16.3.30`.
Password fallback lives in vault `infrastructure/gururmm-server` (credentials.password; sudo same).
The 2026-07-13 rmm-audit Agent E failed on exactly this before the key was found.

View File

@@ -0,0 +1,20 @@
---
name: gururmm-command-timeout-seconds
description: GuruRMM agent command dispatch honors timeout_seconds, NOT timeout — long jobs die at ~300s otherwise
metadata:
type: reference
---
When dispatching a command to a GuruRMM agent (`POST {RMM}/api/agents/{id}/command`), the
execution timeout is controlled by **`timeout_seconds`**, not `timeout`. Passing only
`timeout` leaves the agent at its default cap (~300s): long-running commands get killed and
often surface as a zombie `status: running` with empty stdout that never terminates.
For multi-minute/multi-hour work (large uploads, big enumerations) set `timeout_seconds` high
(e.g. 10800) — send both fields to be safe. Poll `GET {RMM}/api/commands/{command_id}` for
`status` in {completed, failed, cancelled}; cancel a stuck one with
`POST {RMM}/api/commands/{id}/cancel`.
Cost the fleet a full day of failed Birth Biologic SharePoint uploads (Mac session) before
this was spotted. See [[reference_sharepoint_graph_large_file_upload]]. Auth helper:
`.claude/scripts/rmm-auth.sh` (internal `172.16.3.30:3001`).

View File

@@ -0,0 +1,38 @@
---
name: reference_m365_app_sharepoint_rest_vs_graph
description: ComputerGuru M365 app suite — SharePoint app-only WORKS but requires the CERT (client_assertion), not the secret. Secret => "Unsupported app only token". Use get-token.sh <tenant> sharepoint.
metadata:
type: reference
---
The ACG **Tenant Admin app** (`709e6eed-0711-4875-9c44-2d3518c47063`) has **full SharePoint app-only
access** — `Sites.FullControl.All` on the SharePoint resource — but it is gated by an auth-method rule
that keeps biting:
- **SharePoint app-only REQUIRES a CERTIFICATE (client_assertion). A `client_secret` token is rejected**
with **`Unsupported app only token`** on EVERY SharePoint endpoint (REST `/_api/...` and CSOM
`/_vti_bin/client.svc/ProcessQuery`). This is a SharePoint platform rule, not a missing grant.
- **Graph** app-only accepts the secret fine — so Graph SharePoint calls (driveItem `createdBy`/
`lastModifiedBy`/`retentionLabel`, item `/permissions` + inheritance, `/groups` + members) work with
the secret and are the right tool for *investigation*.
**Do NOT hand-roll a SharePoint token from the secret. Use the remediation-tool cert tiers:**
```bash
cd .claude/skills/remediation-tool
bash scripts/get-token.sh <tenant> sharepoint # content resource <name>.sharepoint.com (cert)
bash scripts/get-token.sh <tenant> sharepoint-admin # admin resource <name>-admin.sharepoint.com (cert)
```
`get-token.sh` forces cert for the `sharepoint*` tiers automatically; the minted token's `roles` =
`["Sites.FullControl.All"]`. The cert is vaulted in `msp-tools/computerguru-tenant-admin.sops.yaml`
(`cert_thumbprint_b64url` + `cert_private_key_pem_b64`). Tenant arg = the domain (`birthbiologic.com`).
**Use the CERT (SP REST/CSOM) for:** list settings (`ForceCheckout`, `EnableModeration`/content approval,
versioning), per-item `CheckoutUserId`/checkout state, re-stamping `Author`/`Editor` (Created By /
Modified By), site lock, tenant settings. Graph can't do these.
Reference (authoritative): `.claude/skills/remediation-tool/references/app-permissions-and-sharepoint.md`
and `app-suite.md`. **Before ever telling the user "the tool can't do X" on SharePoint, use the cert
tier and verify** — the "Unsupported app only token" wall means *wrong auth method (secret)*, NOT no access.
See [[birth-biologic]] (migrated content owned by "SharePoint App" => greyed-out Move; fix = re-stamp
Author/Editor via cert CSOM `SystemUpdate`). Open question (Mike, 2026-07-06): standardize ALL M365
app-only auth on cert (cert is resource-agnostic + more secure) to kill this secret-vs-cert friction.

View File

@@ -0,0 +1,30 @@
---
name: reference_msp360_backup_monitoring
description: MSP360 MBS /api/Monitoring is the authoritative "is this client backing up" source for the whole ACG fleet
metadata:
type: reference
---
For any "is client X actually backing up / when did it last run" question, the authoritative
source is the **MSP360 Managed Backup Service API**, not B2 bucket contents (buckets are just
where Cloud plans land; a bucket with files does not prove a recent backup ran).
- Creds: vault `msp-tools/msp360-api.sops.yaml` (API login+password, generated in MSP360
console Settings>General — not the console login).
- Auth: `POST https://api.mspbackups.com/api/Provider/Login` `{UserName, Password}` -> Bearer
`access_token`.
- `GET /api/Monitoring` -> every backup plan across all companies: `CompanyName`,
`ComputerName`, `PlanName`, `StorageType`, `LastStart`, `Status` (0/1=Success, 2=Warning,
3=Failed, 4=Running, 6=Interrupted, 7=completed-with-warnings), `DataCopied`, `TotalData`,
`ErrorMessage`. Also `GET /api/Companies` (37, with Destinations) and `GET /api/Users` (59).
`/api/Computers` is 404.
- Used 2026-07-05 for the GPS backup map (`projects/gps-rmm-audit/backup-map.md`): resolved 11
of 12 "no destination found" clients, surfaced 4 not-backing-up findings (AMT + T&C Sorensen
= 0 plans, Grabb GND-SERVER failing, Bill Tedards ~12mo stale). MSP360 CompanyName also IDs
B2 buckets: `ACG-PST`=Peaceful Spirit, `ACG-TCA`=Tucson Coin and Autograph.
- Backup targets are a per-client decision — report mismatches, don't deploy jobs. See
[[feedback_backup_targets_never_guessed]]. Related: [[reference_backblaze_storage_rate]].
- **Use the `msp360` skill** (`.claude/skills/msp360/`) — built 2026-07-05, full (not
read-only): reads (`status`/`monitoring --failed|--company|--stale`/`companies`/`users`/
`licenses`/`billing`) plus `--confirm`-gated writes (create/delete user+company,
grant/release/revoke license) and a `raw` passthrough for the rest of the MBS API.

View File

@@ -0,0 +1,32 @@
---
name: reference_remediation_tool_365_access
description: The remediation-tool app suite has full M365 access (incl. SharePoint via cert); don't declare "no access" on an accessDenied
metadata:
type: reference
---
The ComputerGuru remediation-tool apps collectively have **broad, working access across ALL of
M365** — Graph, Exchange Online, Defender, AND SharePoint Online. When a call fails it's almost
always wrong-tier / wrong-endpoint / not-consented / the SharePoint cert gotcha — **not** a real
lack of access. Do NOT tell the user "the tool can't do X" without checking the live permission
map first (decode the token `roles` claim).
Key facts:
- **SharePoint app-only requires a CERTIFICATE.** A `client_secret` token is rejected on every
SharePoint endpoint (REST `/_api` and CSOM `/_vti_bin/client.svc/ProcessQuery`) with
`"Unsupported app only token"`. The Tenant Admin app has a cert in the vault and holds
SharePoint-resource `Sites.FullControl.All`.
- `get-token.sh` now has **`sharepoint`** (content) and **`sharepoint-admin`** (tenant admin)
tiers — cert-forced, tenant resource auto-resolved from Graph `/sites/root`
(override `SP_RESOURCE_ENV`). Added 2026-07-01.
- Graph `GET /admin/sharepoint/settings` needs `SharePointTenantSettings.Read.All`, which NO app
holds → that route 403s. Read/write SharePoint tenant settings via the **CSOM admin API**
(`sharepoint-admin` tier) instead. Tenant settings live on the Tenant object
(TypeId `{268004ae-ef6b-4e9b-8425-127220d84719}`) — e.g. `SelfServiceSiteCreationDisabled`.
- Restricting employee SharePoint site creation = `SelfServiceSiteCreationDisabled=true` (CSOM)
AND restrict M365 Group creation (Entra `Group.Unified` directory setting via `user-manager`);
neither affects edit rights on existing sites.
Full detail (live per-tier permission map + CSOM examples):
`.claude/skills/remediation-tool/references/app-permissions-and-sharepoint.md`. Surfaced by
Syncro #32492 (Birth Biologic). See also [[feedback_syncro_billing]].

View File

@@ -0,0 +1,30 @@
---
name: reference_rmm_deploy_via_screenconnect
description: Best channel to mass-deploy the GuruRMM agent to client workstations = ScreenConnect send-command (not DC remote-exec)
metadata:
type: reference
---
**To push the GuruRMM agent onto a client's existing machines, the reliable channel is
ScreenConnect `send-command` (Backstage), NOT remote-exec from the domain controller.**
Discovered 2026-07-03 deploying to Instrumental Music Center (see
`projects/gps-rmm-audit/tracker.md`). Remote-exec FROM the DC to Win10/11 workstations
fails on default client settings:
- WMI/`Win32_Process` over **DCOM** -> "RPC server unavailable" (DCOM firewalled on clients)
- `schtasks /S` -> connects over SMB but Win11 **rejects the task definition** from an older
(Server 2016) DC ("The request is not supported")
- `New-PSDrive`/`sc.exe` admin-share from SYSTEM context -> access/parse failures
- WinRM is off by default on workstations
**What works cleanly:** ScreenConnect. ACG endpoints already run the SC agent, and
`send-command` runs on the guest **as SYSTEM** — no credentials, no firewall fight, no
DA-password-in-logs concern. Pattern (via the `[[screenconnect]]` skill):
1. `GetSessionsByName` with the EXACT hostname -> sessionID (no list-all method; must query by exact name).
2. Build the site installer one-liner: `irm 'https://rmm.azcomputerguru.com/install/<SITE-CODE>/windows'|iex` (site code from the client's vault `gururmm-site-*.sops.yaml`).
3. Encode it: `powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand <base64-UTF16LE>` (avoids all quoting; no /TR length limit like schtasks).
4. `send-command --session <id> --command "<that>" --confirm`. Online guests install + enroll in ~1-3 min; offline guests QUEUE in SC and install on reconnect.
5. Verify via GuruRMM `/api/agents` (hostname appears under the client, status online).
Note `iconv` is absent in this Git-Bash — compute the base64 with `py -c "import base64; ..."`.
Related: `[[project_av_migration_bitdefender_to_edr]]` (same channel can push the Datto EDR agent + remove Bitdefender once RMM is on).

View File

@@ -0,0 +1,19 @@
---
name: reference_rmm_map_network_drive
description: How to push a persistent mapped network drive to a machine via GuruRMM when net use fails with error 67 (double-hop)
metadata:
type: reference
---
Pushing a **persistent mapped drive** to an endpoint via the GuruRMM agent (`/rmm`) fails when the target share is on a *remote* server:
- Running `net use` in `context: user_session` impersonates the logged-on user, but that WTS-impersonated token has **no network credential** to make the second hop to the file server. Result: `System error 67 (network name cannot be found)` on `net use` and `System error 1702 (binding handle is invalid)` on `net view` — even with explicit `/user:.. <pw>`. This is the "SMB error 67 = RMM artifact" documented in `wiki/clients/cascades-tucson.md` (server + share are healthy; access works in a real interactive session).
**Reliable workaround — plant the map so it mounts at the user's next real logon:**
1. `cmdkey /add:<SERVER> /user:<DOMAIN\user> /pass:<pw>` in `user_session` — this is a *local* write to the user's Credential Manager and DOES succeed.
2. Write the persistent-map registry keys into the user's hive `HKCU:\Network\<DriveLetter>`: `RemotePath` (REG_SZ, `\\SERVER\Share`), `UserName` (REG_SZ, `DOMAIN\user`), `ProviderName` (`Microsoft Windows Network`), `ProviderType` (DWord `131072`), `ConnectionType` (DWord `1`), `DeferFlags` (DWord `4`).
3. At the user's **next interactive logon / reboot**, Windows reconnects the drive silently using the cmdkey credential. It will NOT appear in an already-open session — for immediate visibility, run `net use <D>: "\\SERVER\Share"` in the *live* interactive session (ScreenConnect), not through the RMM agent.
Non-domain-joined (workgroup) endpoints authenticate with `DOMAIN\user` + password saved via cmdkey — the domain account only needs to exist and be reachable, the client PC does not need to be joined.
PowerShell-in-RMM gotcha hit while doing this: a double-quoted string ending in a backslash (`"W:\"`, `"W:\\"`) breaks the parser — use bare path tokens (`Test-Path W:\`) or single quotes. See [[feedback_windows_quote_stripping]].

View File

@@ -0,0 +1,33 @@
---
name: rmm-spawn-headless-claude
description: Spawn a headless `claude -p` on any RMM-managed Windows box that has Claude Code installed — reaches isolated sites (AD2) the coord API can't
metadata:
type: reference
---
Any RMM-managed Windows endpoint with Claude Code installed can run an autonomous headless
Claude, dispatched via a GuruRMM command — even a site that's isolated from the ACG coord API.
The RMM agent phones home outbound, so this works where [[ad2-comms-via-sync-only]] says coord
can't reach (coord `:8001` blocked ≠ RMM `:3001` blocked). Validated 2026-07-01 on AD2
(Dataforth DC, agent `cfa93bb6-...`, claude v2.1.181 at `C:\Users\sysadmin\.local\bin\claude.exe`).
Recipe:
- Dispatch with **`"context":"user_session"`** — needs an interactive logged-on user (check
`quser`); an admin session comes back elevated. `claude` is a per-user install, not on the
SYSTEM PATH, so SYSTEM context won't find it.
- **GOTCHA: unset `ANTHROPIC_API_KEY` first.** A stale machine-level `ANTHROPIC_API_KEY` (108-char)
shadows the good OAuth creds and makes `claude -p` fail with `Invalid API key · Fix external API
key`. `Remove-Item Env:\ANTHROPIC_API_KEY` (+ `$env:ANTHROPIC_API_KEY=$null`) before invoking →
falls back to `~\.claude\.credentials.json` OAuth and authenticates.
- **Detach + poll.** A real audit run takes many minutes; RMM caps command lifetime (see
[[gururmm-command-timeout-seconds]] — use `timeout_seconds`). Launch detached
(`Start-Process powershell -File runner.ps1 -WindowStyle Hidden`), have the runner write the
deliverable to a file + a `DONE.txt` marker, and poll the marker via short RMM commands.
- Run headless as: `claude -p <brief> --permission-mode bypassPermissions --output-format text`.
For an audit, give an ironclad READ-ONLY brief (no writes/git/state changes) since
bypassPermissions lets it run any tool. Pass the brief via a base64'd file to dodge quoting.
- Windows/Git-Bash: the mingw `curl` intermittently hits `Permission denied` (AV lock) —
use `/c/Windows/System32/curl.exe` for the dispatch. See [[feedback_windows_quote_stripping]].
Use for: live audits/data-gathering on isolated or hard-to-reach managed boxes without the async
sync-handoff. Keep it read-only on production (AD2 is a domain controller).

View File

@@ -0,0 +1,28 @@
---
name: reference_screenconnect_custom_property_slots
description: ACG ScreenConnect custom-property slot -> label mapping (CP1..CP8) for session tagging
metadata:
type: reference
---
ACG's ConnectWise ScreenConnect instance (`computerguru.screenconnect.com`) custom-property
slot order, reverse-engineered 2026-07-03 from a labeled test session (HOWARD-HOME, values
"howard company / howards site / Howard department / howard type / ... / howard tag"):
- **CP1** (index 0) = **Company**
- **CP2** (index 1) = **Site**
- **CP3** (index 2) = **Department**
- **CP4** (index 3) = **Device Type**
- CP5 (index 4) = unused/other
- CP6 (index 5) = unused/other
- CP7 (index 6) = unused/other
- **CP8** (index 7) = **Tag**
The API does NOT expose the property LABELS (only values), so this mapping is the reference.
`UpdateSessionCustomProperties [sessionID, [cp1..cp8]]` REPLACES the whole 8-element array — always
read the session's current values first, modify only the slots you intend, write the full array back.
Set via the `[[screenconnect]]` skill: `sc.py set-properties --session <id> --props-json '[...8...]' --confirm`.
Read via `GetSessionsByName` -> `.CustomPropertyValues`.
Used by the GPS->RMM audit ScreenConnect cleanup (`projects/gps-rmm-audit/`): normalize Company,
set Site/Department/Device Type/Tag per machine, dedupe sessions.

View File

@@ -0,0 +1,27 @@
---
name: sharepoint-graph-large-file-upload
description: Uploading files to SharePoint via Graph — simple PUT <4MB, chunked upload session >=4MB; verify counts via delta
metadata:
type: reference
---
Pushing a folder tree into a SharePoint doc library via Microsoft Graph (app-only):
- **<4MB:** simple `PUT /drives/{drive}/root:/{path}:/content`.
- **>=4MB:** MUST use an **upload session**`POST .../root:/{path}:/createUploadSession`
then `PUT` the file in chunks (multiple of 320 KiB; 10 MB works) with a
`Content-Range: bytes {start}-{end}/{total}` header. In PowerShell 5.1
`Invoke-RestMethod -Headers @{ 'Content-Range'=... }` handles this fine. A naive script that
only does <4MB PUTs will silently skip every large file and never reach the target count.
- **Long Windows paths (>260):** prefix the local path with `\\?\` for `[IO.File]` reads.
- **Idempotent sync:** existence-check each file (`GET root:/{path}?$select=size`) and skip if
size matches — this also catches/repairs partial-upload residue from earlier failed runs.
- **Throughput:** a single sequential upload stream to SharePoint Online plateaus ~40 Mbps
regardless of link speed (per-session SPO throttle + PS5.1 HTTP stack + Expect100Continue).
For speed use parallel file streams + larger chunks + `Expect100Continue=$false`.
- **Verify total file count** with the Graph **delta** endpoint
(`/drives/{drive}/root/delta`) — whole-drive enumeration in a few paged calls, far cheaper
than recursive `/children`.
Proven end-to-end on Birth Biologic Quality Systems (3,768 files, 301 >=4MB, ~29.7 GB;
largest 3.94 GB). Dispatched via GuruRMM — see [[gururmm-command-timeout-seconds]].

View File

@@ -1,15 +1,41 @@
--- ---
name: reference-syncro-rmm-api-gui-only name: reference-syncro-rmm-api-gui-only
description: Syncro's public API cannot manage RMM policies/folders — creation, assignment, and asset moves are GUI-only (live-verified 2026-06-25) description: Syncro asset/RMM IS API-drivable (reads, create/update, patches, installed_apps, rmm_alerts) — only policy-folder ops need a token scope our per-user keys currently lack (re-verified 2026-07-10)
metadata: metadata:
type: reference type: reference
--- ---
**Syncro RMM policy management is GUI-only — the public REST API does NOT expose it.** Live-verified against the ACG production tenant (computerguru.syncromsp.com) on 2026-06-25: **CORRECTION (2026-07-10): most of Syncro's asset/RMM surface IS API-drivable — the earlier
"RMM is GUI-only" conclusion was a token-scope symptom, not an API limitation.** Live-probed
the ACG production tenant with a per-user token (howard) on 2026-07-10 and confirmed against
the OpenAPI 3.0 spec (`api-docs.syncromsp.com/swagger.json`):
- `GET /customer_assets` objects carry a read-only **`policy_folder_id`** field (which policy folder the machine sits in). **`PUT /customer_assets/{id}` with `policy_folder_id` is silently ignored** — returns HTTP 200 but the value never changes. Proven by a flip-and-restore test on ACG-internal asset 12335235 (DESKTOP-0O8A1RL): value stayed at folder 692253. **You CANNOT move a machine between policy folders via the API.** - **Works with our current per-user token (HTTP 200):** `GET/POST /customer_assets`,
- `/policies`, `/policy_builders`, `/rmm_policies`, `/asset_policies` all return **404** — no policy-CRUD endpoints exist. Policy Builder (the `/policy_builders` GUI page) is web-console only. `PUT /customer_assets/{id}` (name/serial/type/customer/properties — `name` required),
- `/policy_folders` (collection and specific-ID) returns **401** — the route exists but our API token lacks RMM/policy scope. A re-issued token *might* read folders, but since assets can't be moved anyway, it's moot for the move use case. `GET /customer_assets/{id}/patches`, `.../installed_applications`, `/rmm_alerts` (list +
- Syncro docs (docs.syncrosecure.com / docs.syncromsp.com "Work with Policies") confirm: policies are created in Policy Builder, assigned via an Organization's "Assets & Policies" subtab "Update Assigned Policy" dropdown, or "Bulk Assign Top-Level Policy" — all GUI, **no API mention**. `{id}/mute` + DELETE clear), plus contacts, contracts, leads, vendors, purchase_orders,
products, payments, wiki_pages, `/me`, `/search`. The `/syncro` skill now documents the
asset-read/asset-write/rmm-alert/agent-deploy workflows; the full inventory is in
`.claude/standards/syncro/api-reference.md`.
- **Policy-folder move: VERIFIED WORKING with a full-scope token (2026-07-10).** Howard's
token was upgraded to full scope; flip-and-restore on ACG-internal asset `12335235`
(`692253 → 692278 → 692253`) succeeded, each confirmed by GET. `PUT /customer_assets/{id}`
with `policy_folder_id` (+ required `name`) moves the asset. The spec: *"only
policy_folder_id requires **Assets - Policy Change**; with other fields requires **Assets -
Edit** AND **Assets - Policy Change**; nil/nonexistent/cross-customer folder → 422."*
- **Route quirks:** `GET /policy_folders` (bare) → 404; `GET /policy_folders?customer_id=N`
200 (enumerate a customer's folders); `GET /policy_folders/{id}` → 401 even at full scope.
- **Per-user scope differs — capability-check every scope-gated write.** A token WITHOUT
Policy-Change returns HTTP 200 and silently no-ops the move (the exact 2026-06-25 symptom
that produced the original "GUI-only" belief); `/me` shows coarse `asset:{write:true}` and
cannot detect it. The `/syncro move-asset` helper preflights `GET /policy_folders?customer_id=N`
(401 = no scope) and verifies the folder actually changed after the PUT. Only Howard's token
is confirmed full-scope so far; grant the scope to each per-user token for consistent behavior.
- **Archive (retire) IS genuinely GUI-only** — no archive endpoint, no `archived` field, and
DELETE is destructive. That part of the old note stands.
**How to apply:** Do NOT attempt to build `/syncro move-asset` or any Syncro RMM policy/folder/group capability — it's not buildable on the public API. Don't re-probe these endpoints. The only API-drivable policy surface in the fleet is the `bitdefender` skill (GravityZone: create/assign policies, custom groups, move endpoints). For Syncro RMM policy work, direct the user to the Syncro web console. The `/syncro` skill stays PSA-only (tickets/billing/customers/scheduling/estimates + read-only asset lookup). See [[feedback-psa-default-syncro]]. **How to apply:** Build/asset-manage freely via API for everything EXCEPT policy folders. To
unlock `/syncro move-asset` + policy CRUD, add **Assets - Policy Change** + policy-folder
read/manage to the Syncro API token (Admin → API Tokens), re-vault, then flip-and-restore
verify on ACG-internal asset `12335235` (folder `692253`). Do NOT re-assert "can't do assets
via API" — that was wrong. See [[feedback-psa-default-syncro]] and the api-reference doc.

View File

@@ -0,0 +1,54 @@
---
name: reference_windows_edition_upgrade_rmm
description: Verified fleet procedure to upgrade a Windows machine Home->Pro/Pro-for-Workstations via RMM, with the three gotchas (DISM error 50, shutdown /t drop, PfW MAK auto-upgrade).
metadata:
type: reference
---
Verified on ACG-Tech03L 2026-07-06 (Home 26200 -> Win 11 Pro for Workstations, permanently activated).
**Procedure (all steps as SYSTEM via `/rmm`, byte-exact via -EncodedCommand):**
1. Probe: EditionID/LicenseStatus (`.rmm-key-probe.ps1` pattern), confirm no logged-in user
(`query user`) so a reboot is safe.
2. Flip edition: `changepk.exe /ProductKey VK7JG-NPHTM-C97JM-9MPGT-3V66T` (generic Pro key).
Flips EditionID Core->Professional **live** (registry) + sets CBS RebootPending. exit 0.
3. Reboot to finalize (see gotcha #2).
4. Install MAK + activate: `slmgr /ipk <MAK>` then `slmgr /ato`; verify `slmgr /xpr` =
"permanently activated" and WMI SoftwareLicensingProduct LicenseStatus=1.
**Gotcha 1 — DISM is a dead end for online edition change.** `DISM /online /Set-Edition:Professional`
returns **Error 50 "Setting an edition is not supported with online images"** even though
`/Get-TargetEditions` lists Professional. Use `changepk.exe`, not DISM, for online Home->Pro.
**Gotcha 2 — `shutdown /r /t N` is silently DROPPED in the SYSTEM service session.** The command
returns exit 0 but the box never reboots (verified twice on tech03l). Also `shutdown /c "comment"`
fails outright because the RMM->cmd.exe layer strips the quotes ([[feedback_windows_quote_stripping]]).
Reboot reliably with `Restart-Computer -Force` (encoded PS) or `shutdown /r /t 0 /f` (no comment).
NOTE: on a fast SSD the edition-upgrade reboot can be <30s of downtime — a 30s-interval last_seen
monitor can miss the gap entirely; confirm the reboot by `LastBootUpTime` advancing, not by catching
the offline window. `is_connected` is null fleet-wide — track `last_seen` age, never that flag.
**Gotcha 3 — the ACG MAK `infrastructure/windows-pro-mak` is a Pro-for-WORKSTATIONS MAK** (mislabeled
"Pro"). `slmgr /ipk` of it on a Professional machine AUTO-UPGRADES the edition to ProfessionalWorkstation
(a strict superset of Pro), live, **no extra reboot** (verified DALLAS 2026-07-07). If a client genuinely
needs *plain* Pro, this key will over-shoot to PfW — there is no plain-Pro MAK in the vault. Billing:
each activation consumes one MAK count = $99 to the customer (ACG-internal machines: no invoice, still
burns a count).
**Gotcha 4 — `changepk.exe` TIMES OUT under SYSTEM but STILL flips the edition. Do NOT treat the timeout
as failure — VERIFY EditionID.** Run as SYSTEM via `/rmm`, `changepk.exe /ProductKey <key>` does not return
cleanly (the RMM command comes back `failed` / exit -1 `Command timeout`, or your Start-Process/`&` wrapper
hangs to the agent timeout), yet the registry `EditionID` flip to `Professional` DID happen. Always confirm
with a one-liner (`Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' EditionID`) before
concluding anything — if it reads `Professional`, proceed to the finalize reboot; the running OS still
reports Home in `Win32_OperatingSystem.Caption` until that reboot. (Verified DALLAS 2026-07-07 — the
`changepk` command timed out but EditionID was already `Professional`.)
**Gotcha 5 — Modern Standby laptops (S0 Low Power Idle) drop the RMM/SC agent when idle/lid-closed, which
mimics an endless reboot.** On a Core Ultra / S0-only machine, idle or a closed lid drops Wi-Fi -> agent
offline for long stretches (looked like a 25-min "slow reboot"; the box had actually been up 25 days and
never rebooted). Before a reboot-driven upgrade on a laptop, pin it awake first:
`powercfg /change standby-timeout-ac 0; powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS LIDACTION 0;
powercfg /setactive SCHEME_CURRENT` (AC only — safe; DON'T lid-nothing on DC/battery). A command dispatched
to an already-offline agent goes `pending` and fires (uncontrolled) on next wake — cancel stale ones with
`/rmm cancel`. NOTE: an RDP *target* laptop must STAY awake to be reachable, so keep the AC no-sleep in place.

View File

@@ -0,0 +1,247 @@
#!/usr/bin/env bash
# ask-forum.sh — ask a human a question in the #ct-forum Discord forum and BLOCK
# until a person answers, then print their answer and exit. Built for a live
# three-way: the user in the terminal, THIS Claude session, and a teammate (e.g.
# Mike) in the forum — all the same session. The wait loop runs here in bash, so
# the calling model pays for ONE tool call, not a polling loop.
#
# Usage:
# ask-forum.sh "question text" [--title "short title"] [--timeout SEC] [--poll SEC] [--tag @id ...]
# echo "question" | ask-forum.sh [flags]
# ask-forum.sh --read <thread_id> [limit] # one-shot read, no wait
# ask-forum.sh --wait <thread_id> [--timeout SEC] [--poll SEC] [--after <msg_id>]
#
# Flow (ask): POST a forum post (thread) with the question, then poll that thread
# for the first NON-BOT reply (human answer), print it, exit 0.
#
# Correlation is automatic: we create the thread and read only that thread.
# Exit codes: 0 answered; 1 usage; 2 token missing; 3 Discord API error; 4 timeout.
set -u
export LC_ALL="${LC_ALL:-C.UTF-8}" # count code points, not bytes, when slicing $CONTENT
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
API="https://discord.com/api/v10"
UA="ClaudeToolsBot (claudetools, 1.0)"
FORUM_ID="1522960388432465950" # #ct-forum (private forum; Howard + bot + Mike)
LIMIT_CHARS=1900 # Discord caps message content at 2000
TIMEOUT=600
POLL=8
TITLE=""
TAGS=()
MSG=""
# --- token resolver (shared by read + wait + ask) ---
resolve_token() {
local t
t="$(bash "$ROOT/.claude/scripts/vault.sh" get-field \
projects/discord-bot/bot-token.sops.yaml credentials.bot_token 2>/dev/null)"
if [ -z "$t" ] || [ "$t" = "null" ]; then
local env_file="$ROOT/projects/discord-bot/.env"
[ -f "$env_file" ] && t="$(grep -iE '^[[:space:]]*DISCORD_TOKEN[[:space:]]*=' "$env_file" | head -1 | sed -E 's/^[^=]*=[[:space:]]*//; s/^["'"'"']//; s/["'"'"'][[:space:]]*$//')"
fi
printf '%s' "$t"
}
# trim a string to the last newline within it (keeps split chunks on line boundaries)
trim_at_newline() {
local s="$1" nl
nl="${s%$'\n'*}"
if [ "${#nl}" -lt "${#s}" ] && [ "${#nl}" -gt 0 ]; then printf '%s' "$nl"; else printf '%s' "$s"; fi
}
# clamp a read limit to Discord's valid 1..100 (guard huge values that overflow bash math)
clamp_limit() {
local n="$1"
printf '%s' "$n" | grep -qE '^[0-9]+$' || { printf '25'; return; }
[ "${#n}" -ge 4 ] && { printf '100'; return; } # >=1000 always exceeds 100
[ "$n" -lt 1 ] && n=1; [ "$n" -gt 100 ] && n=100
printf '%s' "$n"
}
# Given a non-array poll response body, decide what to do. Echoes: "retry" (transient/
# rate-limited — sleeps any retry_after first) or "bail:<reason>" (permanent Discord error).
poll_disposition() {
local body="$1" ra code
ra="$(printf '%s' "$body" | jq -r '.retry_after // empty' 2>/dev/null)"
if [ -n "$ra" ]; then
# rate limited: honor Discord's back-off (ceil), then retry
sleep "$(printf '%s\n' "$ra" | awk '{printf "%d", ($1==int($1)?$1:int($1)+1)}')" 2>/dev/null || sleep 2
printf 'retry'; return
fi
code="$(printf '%s' "$body" | jq -r '.code // empty' 2>/dev/null)"
if [ -n "$code" ]; then printf 'bail:%s %s' "$code" "$(printf '%s' "$body" | jq -r '.message // ""')"; return; fi
printf 'retry' # unrecognized/transient (network blip, empty body)
}
# emit the human (non-bot) replies from a messages array as ">>> user: text" lines
emit_human() {
printf '%s' "$1" | jq -r 'sort_by(.id)[] | select(.author.bot|not) | ">>> " + .author.username + ": " + ((.content//"")|gsub("\n";" "))'
}
# newest message id in an array (string/lexical max — snowflakes are monotonic; avoids jq number precision loss)
newest_id() { printf '%s' "$1" | jq -r 'sort_by(.id)|.[-1].id // empty'; }
# ---------------------------------------------------------------------------
# --read: one-shot fetch of a thread's messages, no posting
# ---------------------------------------------------------------------------
if [ "${1:-}" = "--read" ]; then
THREAD="${2:-}"
[ -z "$THREAD" ] && { echo "[ERROR] usage: ask-forum.sh --read <thread_id> [limit]" >&2; exit 1; }
LIMIT="$(clamp_limit "${3:-25}")"
TOKEN="$(resolve_token)"
{ [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; } && { echo "[ERROR] no Discord bot token" >&2; exit 2; }
auth=(-H "Authorization: Bot ${TOKEN}" -H "User-Agent: ${UA}")
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?limit=${LIMIT}")"
printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1 || { echo "[ERROR] could not read thread ${THREAD}: $(printf '%s' "$MSGS" | head -c 160)" >&2; exit 3; }
echo "[OK] thread ${THREAD} (oldest first; '>>>' = human, ' ' = bot):"
printf '%s' "$MSGS" | jq -r 'sort_by(.id)[] | ((if (.author.bot // false) then " " else ">>> " end) + .author.username + ": " + ((.content // "")|gsub("\n";" ")))'
exit 0
fi
# ---------------------------------------------------------------------------
# --wait: block on an EXISTING thread until a human replies, no posting
# Baselines on the starter (thread id == starter message id for a forum post),
# so a reply that already landed before --wait began is caught (not missed).
# Override the baseline with --after <msg_id> to wait for replies strictly after
# a specific message (use when resuming a thread whose earlier answer you already
# consumed and you want only the NEXT reply).
# ---------------------------------------------------------------------------
if [ "${1:-}" = "--wait" ]; then
THREAD="${2:-}"; shift 2
[ -z "$THREAD" ] && { echo "[ERROR] usage: ask-forum.sh --wait <thread_id> [--timeout SEC] [--poll SEC] [--after <msg_id>]" >&2; exit 1; }
AFTER="$THREAD"
while [ "$#" -gt 0 ]; do
case "$1" in
--timeout) TIMEOUT="${2:-600}"; shift 2 ;;
--poll) POLL="${2:-8}"; shift 2 ;;
--after) AFTER="${2:-$THREAD}"; shift 2 ;;
*) shift ;;
esac
done
printf '%s' "$TIMEOUT" | grep -qE '^[0-9]+$' || TIMEOUT=600
printf '%s' "$POLL" | grep -qE '^[0-9]+$' || POLL=8
TOKEN="$(resolve_token)"
{ [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; } && { echo "[ERROR] no Discord bot token" >&2; exit 2; }
auth=(-H "Authorization: Bot ${TOKEN}" -H "User-Agent: ${UA}")
echo "[INFO] waiting on thread ${THREAD} for a human reply (up to ${TIMEOUT}s)..." >&2
DEADLINE=$(( $(date +%s) + TIMEOUT ))
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?after=${AFTER}&limit=100")"
if ! printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1; then
D="$(poll_disposition "$MSGS")"
case "$D" in bail:*) echo "[ERROR] thread ${THREAD}: ${D#bail:}" >&2; exit 3 ;; esac
sleep "$POLL"; continue
fi
HUMAN="$(emit_human "$MSGS")"
if [ -n "$HUMAN" ]; then
echo "[OK] answer received in thread ${THREAD}:"; printf '%s\n' "$HUMAN"; echo "THREAD_ID=${THREAD}"; exit 0
fi
# all-bot batch: page forward so a reply beyond the 100-msg window isn't missed
NID="$(newest_id "$MSGS")"; [ -n "$NID" ] && AFTER="$NID"
sleep "$POLL"
done
echo "[TIMEOUT] no human reply on thread ${THREAD} within ${TIMEOUT}s" >&2
echo "THREAD_ID=${THREAD}" >&2
exit 4
fi
# ---------------------------------------------------------------------------
# ask mode (default): post a question and block for the first human reply
# ---------------------------------------------------------------------------
while [ "$#" -gt 0 ]; do
case "$1" in
--title) TITLE="${2:-}"; shift 2 ;;
--timeout) TIMEOUT="${2:-600}"; shift 2 ;;
--poll) POLL="${2:-8}"; shift 2 ;;
--tag) TAGS+=("${2:-}"); shift 2 ;;
-h|--help) sed -n '2,20p' "$0"; exit 1 ;;
*) MSG="${MSG:+$MSG }$1"; shift ;;
esac
done
if [ -z "$MSG" ] && [ ! -t 0 ]; then MSG="$(cat)"; fi
if [ -z "$MSG" ]; then echo "[ERROR] no question given" >&2; exit 1; fi
printf '%s' "$TIMEOUT" | grep -qE '^[0-9]+$' || TIMEOUT=600
printf '%s' "$POLL" | grep -qE '^[0-9]+$' || POLL=8
if [ -z "$TITLE" ]; then
TITLE="$(printf '%s' "$MSG" | tr '\n' ' ' | cut -c1-60)"
[ -z "$TITLE" ] && TITLE="Question"
fi
# prepend any @mentions so the tagged person gets pinged
MENTION=""
for t in "${TAGS[@]:-}"; do
[ -z "$t" ] && continue
id="${t#@}"
printf '%s' "$id" | grep -qE '^[0-9]{17,19}$' && MENTION="${MENTION}<@${id}> "
done
CONTENT="${MENTION}${MSG}"
TOKEN="$(resolve_token)"
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "[ERROR] no Discord bot token (vault + .env both empty)" >&2
bash "$ROOT/.claude/scripts/log-skill-error.sh" "ask-forum" "no Discord bot token" >/dev/null 2>&1
exit 2
fi
auth=(-H "Authorization: Bot ${TOKEN}" -H "Content-Type: application/json" -H "User-Agent: ${UA}")
# keep the forum starter <=1900 chars; any overflow goes out as follow-up messages
STARTER_CONTENT="$CONTENT"; OVERFLOW=""
if [ "${#CONTENT}" -gt "$LIMIT_CHARS" ]; then
STARTER_CONTENT="$(trim_at_newline "${CONTENT:0:$LIMIT_CHARS}")"
OVERFLOW="${CONTENT:${#STARTER_CONTENT}}"; OVERFLOW="${OVERFLOW#$'\n'}"
fi
# --- create the forum post (thread) with the question ---
BODY="$(jq -nc --arg name "$TITLE" --arg c "$STARTER_CONTENT" '{name:$name, message:{content:$c}}')"
RESP="$(printf '%s' "$BODY" | curl -s -m 20 -w $'\n%{http_code}' "${auth[@]}" \
-X POST "$API/channels/${FORUM_ID}/threads" --data-binary @-)"
HTTP="$(printf '%s' "$RESP" | tail -n1)"
JSON="$(printf '%s' "$RESP" | sed '$d')"
if [ "$HTTP" != "201" ] && [ "$HTTP" != "200" ]; then
echo "[ERROR] could not post forum question (HTTP ${HTTP:-none}): $(printf '%s' "$JSON" | head -c 200)" >&2
bash "$ROOT/.claude/scripts/log-skill-error.sh" "ask-forum" "forum post failed" --context "http=${HTTP:-none} resp=$(printf '%s' "$JSON" | head -c 80)" >/dev/null 2>&1
exit 3
fi
THREAD="$(printf '%s' "$JSON" | jq -r '.id // empty')"
STARTER="$(printf '%s' "$JSON" | jq -r '.message.id // empty')"
if [ -z "$THREAD" ]; then
echo "[ERROR] posted but no thread id in response: $(printf '%s' "$JSON" | head -c 200)" >&2
exit 3
fi
# --- post any overflow chunks as follow-up messages (check each; warn if one fails) ---
rest="$OVERFLOW"
while [ -n "$rest" ]; do
piece="$(trim_at_newline "${rest:0:$LIMIT_CHARS}")"
fhttp="$(printf '%s' "$(jq -nc --arg c "$piece" '{content:$c}')" | \
curl -s -m 15 -o /dev/null -w '%{http_code}' "${auth[@]}" -X POST "$API/channels/${THREAD}/messages" --data-binary @-)"
[ "$fhttp" != "200" ] && echo "[WARNING] overflow chunk failed (HTTP ${fhttp}) — question may be truncated in the forum" >&2
rest="${rest:${#piece}}"; rest="${rest#$'\n'}"
done
echo "[INFO] asked in #ct-forum (thread=${THREAD}) — waiting up to ${TIMEOUT}s for a human reply..." >&2
# --- block-poll the thread for the first NON-BOT reply ---
AFTER="${STARTER:-$THREAD}" # starter id == thread id for a forum post
DEADLINE=$(( $(date +%s) + TIMEOUT ))
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
sleep "$POLL"
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?after=${AFTER}&limit=100")"
if ! printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1; then
D="$(poll_disposition "$MSGS")"
case "$D" in bail:*) echo "[ERROR] thread ${THREAD}: ${D#bail:}" >&2; exit 3 ;; esac
continue
fi
HUMAN="$(emit_human "$MSGS")"
if [ -n "$HUMAN" ]; then
echo "[OK] answer received in thread ${THREAD}:"; printf '%s\n' "$HUMAN"; echo "THREAD_ID=${THREAD}"; exit 0
fi
NID="$(newest_id "$MSGS")"; [ -n "$NID" ] && AFTER="$NID" # page forward past bot-only batches
done
echo "[TIMEOUT] no human reply within ${TIMEOUT}s. Thread stays open for a later read:" >&2
echo "THREAD_ID=${THREAD}" >&2
echo " re-read: bash .claude/scripts/ask-forum.sh --read ${THREAD} | resume wait: --wait ${THREAD}" >&2
exit 4

View File

@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""Shared helpers for the backup-verification skills (seafile / owncloud / datto-workplace).
Three sibling skills answer the same GPS-audit question from different backends:
"which customers/machines are actually backing up here?" Each skill has its own
server-side client (Seafile Web API, ownCloud occ over SSH, Datto Workplace file
API). What they SHARE — and what lives here — is:
* ClaudeTools repo-root resolution (env -> identity.json -> derived).
* A one-field SOPS vault read via the canonical vault.sh wrapper.
* An RmmClient for the "endpoint" half of the "server + RMM endpoint" cross-check:
log in to GuruRMM, list agents, and run a detection PowerShell on an agent to
prove a given sync client (SeaDrive / ownCloud / Datto Workplace) is actually
installed and signed in on the machine.
Standalone: stdlib-only transport (urllib), no third-party dependency. Skills add
this file's directory to sys.path and `import backup_common`.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any, Optional
# backup_common.py lives at <root>/.claude/scripts/backup_common.py
_THIS = Path(__file__).resolve()
_DERIVED_ROOT = _THIS.parent.parent.parent # scripts -> .claude -> repo root
RMM_BASE = os.environ.get("GURURMM_API", "http://172.16.3.30:3001")
RMM_VAULT_ENTRY = "infrastructure/gururmm-server.sops.yaml"
RMM_FIELD_EMAIL = "credentials.gururmm-api.admin-email"
RMM_FIELD_PASSWORD = "credentials.gururmm-api.admin-password"
HTTP_TIMEOUT = 60.0
class BackupError(RuntimeError):
"""Transport / auth / API error, with an optional HTTP status."""
def __init__(self, message: str, *, status: Optional[int] = None):
super().__init__(message)
self.status = status
# --- repo-root + vault --------------------------------------------------------
def resolve_root() -> Path:
"""Resolve the ClaudeTools repo root: env var, then identity.json, then derived."""
env_root = os.environ.get("CLAUDETOOLS_ROOT")
if env_root:
return Path(env_root)
identity = _DERIVED_ROOT / ".claude" / "identity.json"
if identity.exists():
try:
data = json.loads(identity.read_text(encoding="utf-8"))
root = data.get("claudetools_root")
if root:
return Path(root)
except (json.JSONDecodeError, OSError):
pass
return _DERIVED_ROOT
def vault_get_field(entry: str, field: str) -> str:
"""Read one field from a SOPS vault entry via the canonical vault.sh wrapper."""
root = resolve_root()
vault = root / ".claude" / "scripts" / "vault.sh"
if not vault.exists():
raise BackupError(f"vault wrapper not found at {vault}")
try:
done = subprocess.run(
["bash", str(vault), "get-field", entry, field],
capture_output=True, text=True, timeout=60,
)
except FileNotFoundError as exc:
raise BackupError("'bash' not found on PATH (need Git Bash for vault reads).") from exc
except subprocess.TimeoutExpired as exc:
raise BackupError(f"vault read timed out for {entry}:{field}") from exc
if done.returncode != 0:
raise BackupError(
f"vault read failed for {entry}:{field} (exit {done.returncode}): {done.stderr.strip()}"
)
value = done.stdout.strip()
if not value:
raise BackupError(f"vault returned empty for {entry}:{field}")
return value
# --- minimal JSON HTTP --------------------------------------------------------
def http_json(method: str, url: str, *, headers: Optional[dict] = None,
body: Optional[dict] = None, form: Optional[dict] = None,
timeout: float = HTTP_TIMEOUT,
basic: Optional[tuple[str, str]] = None) -> tuple[int, Any]:
"""Do an HTTP request and parse a JSON response. Returns (status, parsed).
`body` sends a JSON payload; `form` sends application/x-www-form-urlencoded
(list values expand via doseq, e.g. repeated fields the Seafile admin share
endpoint reads with getlist). Pass at most one. 4xx/5xx are returned (not
raised) so callers can inspect the error body; transport failures raise.
"""
hdrs = dict(headers or {})
data = None
if body is not None and form is not None:
raise BackupError("http_json: pass body OR form, not both")
if body is not None:
data = json.dumps(body).encode("utf-8")
hdrs.setdefault("Content-Type", "application/json")
elif form is not None:
data = urllib.parse.urlencode(form, doseq=True).encode("utf-8")
hdrs.setdefault("Content-Type", "application/x-www-form-urlencoded")
if basic is not None:
import base64
tok = base64.b64encode(f"{basic[0]}:{basic[1]}".encode()).decode("ascii")
hdrs["Authorization"] = f"Basic {tok}"
req = urllib.request.Request(url, data=data, method=method, headers=hdrs)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8", errors="replace")
return resp.getcode(), _parse(raw)
except urllib.error.HTTPError as exc:
raw = exc.read().decode("utf-8", errors="replace")
return exc.code, _parse(raw)
except urllib.error.URLError as exc:
raise BackupError(f"request to {url} failed: {exc}") from exc
def _parse(text: str) -> Any:
if not text:
return {}
try:
return json.loads(text)
except json.JSONDecodeError:
return {"_raw": text[:600]}
# --- GuruRMM client (endpoint cross-check) ------------------------------------
# One detection script, parametrized by the product name-fragments to look for.
# Returns a single JSON line describing installed products, running processes, and
# per-user config folders that match — enough to say "this client is installed and
# signed in" without guessing.
_DETECT_PS = r'''
$ErrorActionPreference='SilentlyContinue'
$patterns = @(__PATTERNS__)
function match($s){ if(-not $s){return $false}; foreach($p in $patterns){ if($s -match [regex]::Escape($p)){return $true} }; return $false }
$installed=@()
foreach($hive in @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')){
Get-ItemProperty $hive | Where-Object { match($_.DisplayName) } | ForEach-Object {
$installed += [pscustomobject]@{ name=$_.DisplayName; version=$_.DisplayVersion; location=$_.InstallLocation }
}
}
$procs = Get-Process | Where-Object { match($_.ProcessName) } | Select-Object -Expand ProcessName -Unique
$cfg=@()
foreach($d in @('Seafile','SeaDrive','ownCloud','ownCloudSync','Datto','Workplace','Workplace2')){
foreach($base in @($env:APPDATA,$env:LOCALAPPDATA,$env:USERPROFILE,"$env:USERPROFILE\Desktop")){
$pth = Join-Path $base $d
if(Test-Path $pth){ if(match($d) -or match($pth)){ $cfg += $pth } }
}
}
# signed-in heuristic: config/db files with recent writes
$fresh=@()
foreach($c in $cfg){
Get-ChildItem $c -Recurse -File -ErrorAction SilentlyContinue -Depth 2 |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-14) } |
Select-Object -First 1 | ForEach-Object { $fresh += $c }
}
[pscustomobject]@{
host=$env:COMPUTERNAME
installed=$installed
processes=@($procs)
config_dirs=@($cfg)
active_config_dirs=@($fresh | Select-Object -Unique)
detected=([bool]($installed.Count -or $procs.Count))
} | ConvertTo-Json -Depth 5 -Compress
'''
class RmmClient:
"""Thin GuruRMM API client: login, list agents, run a detection PowerShell."""
def __init__(self, base: str = RMM_BASE):
self.base = base.rstrip("/")
self._token: Optional[str] = None
self._agents: Optional[list[dict]] = None
def login(self) -> None:
email = vault_get_field(RMM_VAULT_ENTRY, RMM_FIELD_EMAIL)
pw = vault_get_field(RMM_VAULT_ENTRY, RMM_FIELD_PASSWORD)
status, body = http_json("POST", f"{self.base}/api/auth/login",
body={"email": email, "password": pw})
if status != 200 or not isinstance(body, dict) or not body.get("token"):
raise BackupError(f"GuruRMM login failed (HTTP {status}): {body}", status=status)
self._token = body["token"]
def _auth(self) -> dict:
if not self._token:
self.login()
return {"Authorization": f"Bearer {self._token}"}
def list_agents(self) -> list[dict]:
if self._agents is None:
status, body = http_json("GET", f"{self.base}/api/agents", headers=self._auth())
if status != 200 or not isinstance(body, list):
raise BackupError(f"GET /api/agents failed (HTTP {status})", status=status)
self._agents = body
return self._agents
def find_agents(self, client_substr: Optional[str] = None,
hostname_substr: Optional[str] = None) -> list[dict]:
out = []
for a in self.list_agents():
if client_substr and client_substr.lower() not in (a.get("client_name") or "").lower():
continue
if hostname_substr and hostname_substr.lower() not in (a.get("hostname") or "").lower():
continue
out.append(a)
return out
def run_ps(self, agent_id: str, script: str, timeout_seconds: int = 90,
poll_seconds: int = 4, max_polls: int = 30) -> dict:
"""Dispatch a PowerShell command to an agent and poll to completion."""
status, body = http_json(
"POST", f"{self.base}/api/agents/{agent_id}/command", headers=self._auth(),
body={"command_type": "powershell", "command": script,
"timeout_seconds": timeout_seconds})
if status not in (200, 201) or not isinstance(body, dict):
raise BackupError(f"command dispatch failed (HTTP {status}): {body}", status=status)
cid = body.get("command_id") or body.get("id")
if not cid:
raise BackupError(f"no command_id in dispatch response: {body}")
terminal = {"completed", "failed", "cancelled", "interrupted", "timeout"}
last: dict = {}
for _ in range(max_polls):
s, cb = http_json("GET", f"{self.base}/api/commands/{cid}", headers=self._auth())
if s == 200 and isinstance(cb, dict):
last = cb
if (cb.get("status") or "") in terminal:
break
time.sleep(poll_seconds)
return last
def detect_client(self, agent: dict, patterns: list[str], **kw) -> dict:
"""Run the detection script on one agent for the given product name-fragments."""
pat_lits = ",".join("'" + p.replace("'", "''") + "'" for p in patterns)
script = _DETECT_PS.replace("__PATTERNS__", pat_lits)
res = self.run_ps(agent["id"], script, **kw)
out = {"hostname": agent.get("hostname"), "client_name": agent.get("client_name"),
"agent_id": agent.get("id"), "status": res.get("status"),
"exit_code": res.get("exit_code"), "detected": None, "detail": None}
stdout = (res.get("stdout") or "").strip()
if stdout:
try:
parsed = json.loads(stdout)
out["detected"] = bool(parsed.get("detected"))
out["detail"] = parsed
except json.JSONDecodeError:
out["detail"] = {"_raw": stdout[:600]}
return out
def eprint(*a: Any) -> None:
print(*a, file=sys.stderr)

View File

@@ -6,6 +6,7 @@
# Usage: # Usage:
# discord-dm.sh <recipient> "message text" # discord-dm.sh <recipient> "message text"
# echo "message text" | discord-dm.sh <recipient> # echo "message text" | discord-dm.sh <recipient>
# discord-dm.sh read <recipient> [limit] # READ recent messages — SEE replies (default 15)
# discord-dm.sh list # show known users + channels # discord-dm.sh list # show known users + channels
# #
# <recipient> is one of: # <recipient> is one of:
@@ -51,11 +52,29 @@ if [ -z "$RECIPIENT" ] || [ "$RECIPIENT" = "-h" ] || [ "$RECIPIENT" = "--help" ]
sed -n '2,30p' "$0"; exit 1 sed -n '2,30p' "$0"; exit 1
fi fi
if [ "$RECIPIENT" = "list" ]; then print_dir; exit 0; fi if [ "$RECIPIENT" = "list" ]; then print_dir; exit 0; fi
# --- read mode: fetch recent messages so we can SEE replies (mike/winter/etc.).
# `discord-dm.sh read <user|#channel> [limit]` — reuses the recipient resolution,
# token, and DM-channel-open below, then prints history instead of sending. The
# bot participates in each DM channel it opened, so REST history returns full
# content (the Message Content intent only gates gateway events, not this fetch).
ACTION=send
if [ "$RECIPIENT" = "read" ] || [ "$RECIPIENT" = "inbox" ]; then
ACTION=read; shift
RECIPIENT="${1:-}"
[ -z "$RECIPIENT" ] && { echo "[ERROR] usage: discord-dm.sh read <user|#channel> [limit]" >&2; exit 1; }
fi
shift shift
# --- message (remaining args, else stdin) --- # --- message (send) or limit (read) ---
if [ "$#" -gt 0 ]; then MSG="$*"; elif [ ! -t 0 ]; then MSG="$(cat)"; else MSG=""; fi if [ "$ACTION" = "read" ]; then
if [ -z "$MSG" ]; then echo "[ERROR] empty message — nothing sent" >&2; exit 1; fi READ_LIMIT="${1:-15}"
printf '%s' "$READ_LIMIT" | grep -qE '^[0-9]+$' || READ_LIMIT=15
[ "$READ_LIMIT" -gt 100 ] && READ_LIMIT=100
else
if [ "$#" -gt 0 ]; then MSG="$*"; elif [ ! -t 0 ]; then MSG="$(cat)"; else MSG=""; fi
if [ -z "$MSG" ]; then echo "[ERROR] empty message — nothing sent" >&2; exit 1; fi
fi
# --- resolve recipient -> mode (dm|channel) + target id --- # --- resolve recipient -> mode (dm|channel) + target id ---
MODE=""; TARGET=""; LABEL="" MODE=""; TARGET=""; LABEL=""
@@ -99,17 +118,61 @@ if [ "$MODE" = "dm" ]; then
TARGET="$CHID" TARGET="$CHID"
fi fi
# --- post the message (printf | --data-binary @- — direct -d mangles multiline JSON) --- # --- read mode: fetch + print recent messages, then exit (no send) ---
RESP="$(printf '%s' "$(jq -nc --arg c "$MSG" '{content:$c}')" | \ if [ "$ACTION" = "read" ]; then
curl -s -m 15 -w $'\n%{http_code}' "${auth[@]}" \ MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${TARGET}/messages?limit=${READ_LIMIT}")"
-X POST "$API/channels/${TARGET}/messages" --data-binary @-)" if ! printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1; then
HTTP="$(printf '%s' "$RESP" | tail -n1)" echo "[ERROR] could not read $LABEL: $(printf '%s' "$MSGS" | head -c 200)" >&2
BODY="$(printf '%s' "$RESP" | sed '$d')" bash "$ROOT/.claude/scripts/log-skill-error.sh" "discord-dm" "read history failed for $LABEL" --context "resp=$(printf '%s' "$MSGS" | head -c 80)" >/dev/null 2>&1
exit 3
if [ "$HTTP" = "200" ]; then fi
echo "[OK] discord-dm: sent to ${LABEL} (message_id=$(printf '%s' "$BODY" | jq -r '.id // empty'))" echo "[OK] discord-dm: last ${READ_LIMIT} message(s) in ${LABEL} (oldest first; '>>>' = reply from them, ' ' = us):"
printf '%s' "$MSGS" | jq -r 'reverse[] |
((if (.author.bot // false) then " " else ">>> " end)
+ (.timestamp[0:16]) + " " + .author.username + ": "
+ ((.content // "") | gsub("\n";" ")))'
exit 0 exit 0
fi fi
echo "[ERROR] discord-dm: Discord returned ${HTTP:-no-response}${BODY}" >&2
bash "$ROOT/.claude/scripts/log-skill-error.sh" "discord-dm" "Discord send to $LABEL failed" --context "http=${HTTP:-none} resp=${BODY:0:80}" >/dev/null 2>&1 # --- chunk: Discord caps content at 2000 chars (code 50035 on overflow).
exit 3 # This tool's job is delivering LONG content intact, so split into <=1900-char
# pieces (preferring newline boundaries) and send them in order — no markers
# added, so the receiver can copy-paste the sequence back together verbatim.
LIMIT=1900
CHUNKS=()
rest="$MSG"
while [ "${#rest}" -gt "$LIMIT" ]; do
piece="${rest:0:$LIMIT}"
at_nl="${piece%$'\n'*}" # cut at the last newline in the window
if [ "${#at_nl}" -lt "${#piece}" ] && [ "${#at_nl}" -gt 0 ]; then
piece="$at_nl"
fi
CHUNKS+=("$piece")
rest="${rest:${#piece}}"
rest="${rest#$'\n'}"
done
CHUNKS+=("$rest")
# --- post the message (jq | --data-binary @- — argv/-d mangles multiline + non-ASCII JSON) ---
N=${#CHUNKS[@]}
i=0
for piece in "${CHUNKS[@]}"; do
i=$((i+1))
RESP="$(printf '%s' "$(jq -nc --arg c "$piece" '{content:$c}')" | \
curl -s -m 15 -w $'\n%{http_code}' "${auth[@]}" \
-X POST "$API/channels/${TARGET}/messages" --data-binary @-)"
HTTP="$(printf '%s' "$RESP" | tail -n1)"
BODY="$(printf '%s' "$RESP" | sed '$d')"
if [ "$HTTP" != "200" ]; then
echo "[ERROR] discord-dm: Discord returned ${HTTP:-no-response} on chunk ${i}/${N}${BODY}" >&2
bash "$ROOT/.claude/scripts/log-skill-error.sh" "discord-dm" "Discord send to $LABEL failed (chunk ${i}/${N})" --context "http=${HTTP:-none} resp=${BODY:0:80}" >/dev/null 2>&1
exit 3
fi
done
if [ "$N" -gt 1 ]; then
echo "[OK] discord-dm: sent to ${LABEL} in ${N} chunks (message_id=$(printf '%s' "$BODY" | jq -r '.id // empty'))"
else
echo "[OK] discord-dm: sent to ${LABEL} (message_id=$(printf '%s' "$BODY" | jq -r '.id // empty'))"
fi
exit 0

View File

@@ -0,0 +1,6 @@
' edr-isolation-watch-hidden.vbs — launch the EDR isolation watcher with no visible window.
' Run via the "ClaudeTools - EDR Isolation Watcher" scheduled task through wscript.exe (a GUI
' host, no console), which starts bash with window style 0 (hidden) so the every-10-min watch
' no longer flashes a console window on the desktop. Runtime env is identical to a direct bash
' launch. Mirrors gps-rmm-progress-hidden.vbs.
CreateObject("WScript.Shell").Run """C:\Program Files\Git\bin\bash.exe"" -lc ""/c/claudetools/.claude/scripts/edr-isolation-watch.sh""", 0, False

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# edr-isolation-watch.sh — interim EDR auto-isolation alerter.
#
# Polls Datto EDR for detections whose automated response included `isolate-host`
# (i.e. a machine was auto-isolated / "quarantined") and posts each NEW one to the
# private #dev-alerts channel (Mike + Howard). Runs as a plain scheduled job — no
# LLM, no tokens. Retire once the GuruRMM EDR webhook integration (Feature 6) lands.
#
# Schedule (every 10 min), e.g. cron:
# */10 * * * * bash /path/to/claudetools/.claude/scripts/edr-isolation-watch.sh >/dev/null 2>&1
#
# State: .edr-watch-state.json (gitignored) holds already-alerted alert IDs so each
# isolation is announced exactly once, even though Mike may un-isolate before the poll.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
EDR="bash $ROOT/.claude/scripts/py.sh $ROOT/.claude/skills/datto-edr/scripts/edr.py"
DM="bash $ROOT/.claude/scripts/discord-dm.sh"
export STATE="${STATE:-$ROOT/.claude/scripts/.edr-watch-state.json}"
TARGET="dev" # #dev-alerts = Mike + Howard, private
# Pull last 24h of detections (list includes responseData) and emit any NEW
# isolate-host events as one pipe-delimited line each; update state in-place.
new_events="$($EDR --json detections --days 1 --limit 500 2>/dev/null | python -c '
import sys, json, os
state_path = os.environ["STATE"]
try:
rows = json.load(sys.stdin)
rows = rows if isinstance(rows, list) else rows.get("data", rows.get("alerts", []))
except Exception:
sys.exit(0) # API hiccup: emit nothing, do not churn state
try:
seen = set(json.load(open(state_path)))
except Exception:
seen = set()
fresh = []
for a in rows:
rd = a.get("responseData") or []
names = [r.get("name") for r in rd] if isinstance(rd, list) else []
if "isolate-host" not in names:
continue
aid = a.get("id")
if not aid or aid in seen:
continue
seen.add(aid)
fresh.append("|".join(str(a.get(k, "")) for k in
("id", "hostname", "organizationName", "severity", "sourceName", "eventTime")))
# keep state bounded
json.dump(sorted(seen)[-500:], open(state_path, "w"))
print("\n".join(fresh))
' 2>/dev/null)"
[ -z "$new_events" ] && exit 0
while IFS='|' read -r aid host org sev rule ts; do
[ -z "$aid" ] && continue
msg="[EDR AUTO-ISOLATION] **$host** was auto-isolated by Datto EDR
- Client: $org
- Rule: $rule (severity: $sev)
- Time: $ts
- This machine was cut off the network by an EDR automated response. Verify it is intended before restoring.
- Console: https://azcomp4587.infocyte.com (alert id: $aid)"
if [ "${DRY_RUN:-0}" = "1" ]; then
printf '[DRY_RUN] would post to #%s:\n%s\n\n' "$TARGET" "$msg"
continue
fi
if ! printf '%s' "$msg" | $DM "$TARGET" 2>/dev/null; then
bash "$ROOT/.claude/scripts/log-skill-error.sh" "edr-isolation-watch" "discord post failed for $host ($aid)" 2>/dev/null || true
fi
done <<< "$new_events"

View File

@@ -1,5 +1,5 @@
"""Generate base64 of the WatchdogAlertsSection component.""" """Generate base64 of the WatchdogAlertsSection component."""
import base64, sys import base64, os, sys
BT = chr(96) BT = chr(96)
BULLET = "\xb7" BULLET = "\xb7"
@@ -195,7 +195,9 @@ lines = [
component = "".join(lines) component = "".join(lines)
b64 = base64.b64encode(component.encode("utf-8")).decode("ascii") b64 = base64.b64encode(component.encode("utf-8")).decode("ascii")
with open("D:/claudetools/.claude/scripts/component.b64", "w") as f: # Write next to this script — the repo root is machine-specific, never hardcode it.
out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "component.b64")
with open(out_path, "w") as f:
f.write(b64) f.write(b64)
print(f"Component: {len(component)} chars") print(f"Component: {len(component)} chars")

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# get-identity.sh — Read identity.json and export user/machine vars for attribution
#
# Source this at the start of any skill that needs attribution (bot alerts, commits,
# logs, RMM operations). Exports $USER_NAME, $USER_SHORT, $MACHINE, $USER_EMAIL.
#
# Usage:
# source .claude/scripts/get-identity.sh
# echo "[RMM] $USER_SHORT deployed to X machines..."
#
# Soft-fails: if identity.json is missing, exports "Unknown" values and returns 1
# (but does NOT exit, so the caller continues). This ensures skills never break on
# missing identity - they just attribute to "Unknown".
# Resolve the repo root from this script's own location (mirrors vault.sh) so callers
# don't have to know it. An already-set CLAUDETOOLS_ROOT still wins, and identity.json's
# claudetools_root overrides both. Never hardcode a drive letter.
if [ -z "${CLAUDETOOLS_ROOT:-}" ]; then
export CLAUDETOOLS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
fi
IDENTITY_FILE="$CLAUDETOOLS_ROOT/.claude/identity.json"
if [ ! -f "$IDENTITY_FILE" ]; then
echo "[WARNING] identity.json not found at $IDENTITY_FILE - attribution will be 'Unknown'" >&2
export USER_NAME="Unknown User"
export USER_SHORT="unknown"
export MACHINE="unknown-machine"
export USER_EMAIL="unknown@unknown.com"
return 1
fi
export USER_NAME=$(jq -r '.full_name // .user // "Unknown"' "$IDENTITY_FILE" 2>/dev/null || echo "Unknown")
export USER_SHORT=$(jq -r '.user // "unknown"' "$IDENTITY_FILE" 2>/dev/null || echo "unknown")
export MACHINE=$(jq -r '.machine // "unknown"' "$IDENTITY_FILE" 2>/dev/null || echo "unknown")
export USER_EMAIL=$(jq -r '.email // "unknown@unknown.com"' "$IDENTITY_FILE" 2>/dev/null || echo "unknown@unknown.com")
# identity.json is authoritative for the root and the vault location.
_root_override=$(jq -r '.claudetools_root // empty' "$IDENTITY_FILE" 2>/dev/null)
[ -n "$_root_override" ] && [ -d "$_root_override" ] && export CLAUDETOOLS_ROOT="$_root_override"
export VAULT_ROOT=$(jq -r '.vault_path // empty' "$IDENTITY_FILE" 2>/dev/null)
unset _root_override
# Success
return 0

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# gps-rmm-autoenroll.sh — auto-enroll loop for the GPS->RMM audit.
# Every run: push the generic Staging installer (DARK-STORM-3150) to GPS-customer
# machines that are ONLINE in ScreenConnect but missing from GuruRMM, wait for them
# to enroll, then reassign Staging agents to their real client (hostname->Syncro).
# Registered as Windows task GPS-RMM-AutoEnroll (every 30 min). Remove the task when
# targets.json is fully enrolled. Safe to re-run: server v6.77+ dedups by device_id.
set -uo pipefail
cd /c/claudetools || exit 1
LOG="projects/gps-rmm-audit/autoenroll.log"
TS="$(date '+%Y-%m-%d %H:%M')"
eval "$(bash .claude/scripts/rmm-auth.sh 2>/dev/null)" >/dev/null 2>&1
if [ -z "${TOKEN:-}" ]; then echo "$TS auth FAILED" >> "$LOG"; exit 0; fi
SEC="$(bash .claude/scripts/vault.sh get-field msp-tools/screenconnect.sops.yaml credentials.api_secret 2>/dev/null | tr -d '\r\n')"
SK="$(bash .claude/scripts/vault.sh get-field msp-tools/syncro-howard credentials.credential 2>/dev/null | tr -d '\r\n')"
if [ -z "$SK" ]; then echo "$TS syncro key read FAILED" >> "$LOG"; exit 0; fi
OUT="$(RMM="$RMM" TOK="$TOKEN" SK="$SK" SC_SECRET="$SEC" python projects/gps-rmm-audit/tools/rebuild-and-push.py 2>/dev/null | tail -2)"
PUSHED="$(echo "$OUT" | grep -oE 'pushed: [0-9]+' | grep -oE '[0-9]+' | head -1 || true)"
PUSHED="${PUSHED:-0}"
echo "$TS sweep: $(echo "$OUT" | head -1)" >> "$LOG"
if [ "${PUSHED:-0}" -gt 0 ]; then
sleep 150
RES="$(RMM="$RMM" TOK="$TOKEN" SK="$SK" python projects/gps-rmm-audit/tools/reassign-staging.py 2>/dev/null)"
MOVED="$(echo "$RES" | grep -cE '^ .+ -> ' || true)"
MOVED="${MOVED:-0}"
echo "$TS pushed $PUSHED, reassigned $MOVED:" >> "$LOG"
echo "$RES" | grep -E '^ ' >> "$LOG"
if [ "${MOVED:-0}" -gt 0 ]; then
NAMES="$(echo "$RES" | grep -E '^ .+ -> ' | sed 's/^ //' | tr '\n' '; ')"
bash .claude/scripts/post-bot-alert.sh "[RMM] auto-enroll: $NAMES" >/dev/null 2>&1
fi
fi
exit 0

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# gps-rmm-progress-check.sh — daily GPS->GuruRMM enrollment progress check.
#
# Reads projects/gps-rmm-audit/targets.json (GPS device target per client),
# pulls live GuruRMM /api/agents, computes per-client enrollment gaps, and DMs
# Howard a one-message summary. When every tracked client has met its target it
# reports "COMPLETE" so the scheduled task can be retired.
#
# Usage:
# bash gps-rmm-progress-check.sh # check + DM Howard
# bash gps-rmm-progress-check.sh --dry-run # check + print, no Discord
#
# Registered as a daily Windows scheduled task. Read-only against RMM.
set -u
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
TARGETS="$ROOT/projects/gps-rmm-audit/targets.json"
DRY=0; [ "${1:-}" = "--dry-run" ] && DRY=1
[ -f "$TARGETS" ] || { echo "[ERROR] targets.json not found at $TARGETS" >&2; exit 1; }
# --- auth + fetch agents ---
eval "$(bash "$ROOT/.claude/scripts/rmm-auth.sh" 2>/dev/null)" >/dev/null
if [ -z "${TOKEN:-}" ] || [ -z "${RMM:-}" ]; then
echo "[ERROR] RMM auth failed" >&2
bash "$ROOT/.claude/scripts/log-skill-error.sh" "gps-rmm-progress-check" "RMM auth failed" >/dev/null 2>&1
exit 1
fi
AGENTS=$(curl -s "$RMM/api/agents" -H "Authorization: Bearer $TOKEN" | tr -d '\000-\037')
[ "${AGENTS:0:1}" = "[" ] || { echo "[ERROR] /api/agents not an array: ${AGENTS:0:100}" >&2; \
bash "$ROOT/.claude/scripts/log-skill-error.sh" "gps-rmm-progress-check" "GET /api/agents non-array" >/dev/null 2>&1; exit 1; }
# --- per-client live agent counts (unique hostnames, to ignore dup agent rows) ---
COUNTS=$(echo "$AGENTS" | jq -r '[.[] | {c:.client_name, h:(.hostname|ascii_downcase)}]
| group_by(.c) | map({client:.[0].c, n:( [.[].h] | unique | length )}) | .[] | "\(.n)\t\(.client)"')
# --- compare targets vs live ---
REPORT=""; DONE_ALL=1; TOTAL_TARGET=0; TOTAL_HAVE=0; GAP_CLIENTS=0
while IFS=$'\t' read -r TGT NAME BUCKET; do
[ -z "$NAME" ] && continue
HAVE=$(echo "$COUNTS" | awk -F'\t' -v n="$NAME" '$2==n{print $1; found=1} END{if(!found)print 0}' | head -1)
HAVE=${HAVE:-0}
TOTAL_TARGET=$((TOTAL_TARGET + TGT))
TOTAL_HAVE=$((TOTAL_HAVE + HAVE))
if [ "$HAVE" -lt "$TGT" ]; then
DONE_ALL=0; GAP_CLIENTS=$((GAP_CLIENTS+1))
GAP=$((TGT - HAVE))
STATE=$([ "$HAVE" -eq 0 ] && echo "NO ORG/AGENTS" || echo "short")
REPORT="${REPORT} - ${NAME}: ${HAVE}/${TGT} in RMM (${STATE}, gap ${GAP}) [${BUCKET}]\n"
fi
done < <(jq -r '.clients[] | "\(.target)\t\(.client)\t\(.bucket)"' "$TARGETS")
DATE=$(date +%Y-%m-%d)
if [ "$DONE_ALL" -eq 1 ]; then
MSG="**GPS->RMM enrollment: COMPLETE (${DATE})** All tracked GPS clients meet their RMM device target (${TOTAL_HAVE}/${TOTAL_TARGET}). You can retire the daily check task (schtasks /Delete /TN GPS-RMM-Progress)."
else
MSG="**GPS->RMM enrollment check ${DATE}** — ${TOTAL_HAVE}/${TOTAL_TARGET} devices in RMM; ${GAP_CLIENTS} clients still short:\n${REPORT}(Glaz-Tech excluded pending billing review. Source: projects/gps-rmm-audit/targets.json)"
fi
if [ "$DRY" -eq 1 ]; then
echo -e "$MSG"
exit 0
fi
echo -e "$MSG" | bash "$ROOT/.claude/scripts/discord-dm.sh" howard

View File

@@ -0,0 +1,5 @@
' gps-rmm-progress-hidden.vbs — launch the GPS->RMM progress check with no visible window.
' Run via the GPS-RMM-Progress scheduled task through wscript.exe (GUI host, no console),
' which starts bash with window style 0 (hidden) so the daily check no longer flashes a
' console window on the desktop. Runtime env is identical to a direct bash launch.
CreateObject("WScript.Shell").Run """C:\Program Files\Git\bin\bash.exe"" -lc ""cd /c/claudetools && bash .claude/scripts/gps-rmm-progress-check.sh""", 0, False

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# guruscan-agent-test.sh - Deploy GuruScan to a Windows GuruRMM agent and run an # guruscan-agent-test.sh - Deploy GuruScan to a Windows GuruRMM agent and run an
# end-to-end smoke test (full 3-engine chain, EICAR-seeded, clean mode), pulling # end-to-end smoke test (full 3-engine chain, clean mode), pulling every log back
# every log back for review. # for review.
# #
# Phases: # Phases:
# prep - upload module to C:\GuruScan, Defender-exclude tool/test dirs, # prep - upload module to C:\GuruScan, Defender-exclude tool dirs,
# download RKill+Emsisoft, fetch HitmanPro, seed EICAR, verify ready # download RKill+Emsisoft, fetch HitmanPro, verify ready
# scan - dispatch Invoke-GuruScan.ps1 -Headless (clean mode) and poll # scan - dispatch Invoke-GuruScan.ps1 -Headless (clean mode) and poll
# collect - pull results.json + per-scanner logs into the repo # collect - pull results.json + per-scanner logs into the repo
# all - prep then scan then collect # all - prep then scan then collect
@@ -14,10 +14,9 @@
# bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all> # bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>
# #
# Mirrors the RMM plumbing in run-onboarding-diagnostic.sh (vault auth -> JWT -> # Mirrors the RMM plumbing in run-onboarding-diagnostic.sh (vault auth -> JWT ->
# chunked base64 upload -> dispatch -> poll). EICAR is the standard harmless AV # chunked base64 upload -> dispatch -> poll). Detection testing uses the real
# test file; it is assembled on the endpoint (never written to this host) and # malware-samples set via the scan-one / verify-each phases. Prep's EICAR seed is
# dropped only into a Defender-excluded folder so GuruScan's own engines are what # DISABLED by default (kept for future scanners) -- enable with SEED_EICAR=1.
# detect it.
set -u set -u
@@ -25,6 +24,11 @@ TARGET="${1:-}"
PHASE="${2:-all}" PHASE="${2:-all}"
SCANNER_ARG="${3:-}" SCANNER_ARG="${3:-}"
# EICAR test-file seeding in the prep phase. DISABLED by default -- kept (not
# deleted) so it can be switched back on to smoke-test detection as new scanners
# are added to the chain. Enable per-run with: SEED_EICAR=1 bash guruscan-agent-test.sh ...
SEED_EICAR="${SEED_EICAR:-0}"
if [ -z "$TARGET" ]; then if [ -z "$TARGET" ]; then
echo "[ERROR] Usage: bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>" >&2 echo "[ERROR] Usage: bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>" >&2
exit 1 exit 1
@@ -273,12 +277,12 @@ phase_prep() {
upload_file "$GS_DIR/$f" "C:\\GuruScan\\$f" || { _logerr "upload failed" --context "file=$f host=$AGENT_HOST"; return 1; } upload_file "$GS_DIR/$f" "C:\\GuruScan\\$f" || { _logerr "upload failed" --context "file=$f host=$AGENT_HOST"; return 1; }
done done
# 2) Defender exclusions for tool + downloads + test dirs (so Defender does not # 2) Defender exclusions for tool + downloads dirs (so Defender does not nuke
# nuke the scanner EXEs or grab EICAR before GuruScan's engines run). # the scanner EXEs before GuruScan's engines run).
local sf="$WORK_DIR/defender.ps1" local sf="$WORK_DIR/defender.ps1"
cat > "$sf" <<'PS' cat > "$sf" <<'PS'
$ErrorActionPreference='Continue' $ErrorActionPreference='Continue'
foreach($p in @('C:\GuruScan','C:\GuruScan\downloads','C:\GuruScanTest','C:\EmsisoftCmd')){ foreach($p in @('C:\GuruScan','C:\GuruScan\downloads','C:\EmsisoftCmd')){
try { Add-MpPreference -ExclusionPath $p -ErrorAction Stop; Write-Output "EXCLUDED $p" } try { Add-MpPreference -ExclusionPath $p -ErrorAction Stop; Write-Output "EXCLUDED $p" }
catch { Write-Output ("EXCLUDE-SKIP " + $p + " : " + $_.Exception.Message) } catch { Write-Output ("EXCLUDE-SKIP " + $p + " : " + $_.Exception.Message) }
} }
@@ -310,12 +314,16 @@ try {
PS PS
run_ps "$sf3" 300 70 "download-hitmanpro" || echo "[WARN] HitmanPro download dispatch issue" run_ps "$sf3" 300 70 "download-hitmanpro" || echo "[WARN] HitmanPro download dispatch issue"
# 5) seed EICAR into the Defender-excluded test folder (assembled on endpoint) # 5) [OPTIONAL] seed EICAR into a Defender-excluded test folder (assembled on
local sf4="$WORK_DIR/eicar.ps1" # the endpoint). DISABLED unless SEED_EICAR=1 -- retained so detection can be
cat > "$sf4" <<'PS' # smoke-tested as new scanners are added to the chain.
if [ "$SEED_EICAR" = "1" ]; then
local sfe="$WORK_DIR/eicar.ps1"
cat > "$sfe" <<'PS'
$ErrorActionPreference='Continue' $ErrorActionPreference='Continue'
$dir='C:\GuruScanTest' $dir='C:\GuruScanTest'
if(-not (Test-Path $dir)){New-Item -ItemType Directory -Path $dir -Force|Out-Null} if(-not (Test-Path $dir)){New-Item -ItemType Directory -Path $dir -Force|Out-Null}
try { Add-MpPreference -ExclusionPath $dir -ErrorAction Stop } catch {}
# Standard EICAR test signature, assembled from fragments so it is never stored # Standard EICAR test signature, assembled from fragments so it is never stored
# contiguously anywhere except the on-disk test file. # contiguously anywhere except the on-disk test file.
$e = 'X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR' + '-STANDARD-ANTIVIRUS-' + 'TEST-FILE!$H+H*' $e = 'X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR' + '-STANDARD-ANTIVIRUS-' + 'TEST-FILE!$H+H*'
@@ -329,9 +337,12 @@ if(Test-Path $f){
Write-Output "EICAR MISSING after write - Defender (or other AV) grabbed it despite exclusion" Write-Output "EICAR MISSING after write - Defender (or other AV) grabbed it despite exclusion"
} }
PS PS
run_ps "$sf4" 60 24 "seed-eicar" || { _logerr "EICAR seed failed" --context "host=$AGENT_HOST"; return 1; } run_ps "$sfe" 60 24 "seed-eicar" || { _logerr "EICAR seed failed" --context "host=$AGENT_HOST"; return 1; }
else
echo "[INFO] EICAR seeding disabled (set SEED_EICAR=1 to enable)"
fi
# 6) readiness check - confirm all three scanner binaries present + EICAR present # 6) readiness check - confirm all three scanner binaries + module present
local sf5="$WORK_DIR/ready.ps1" local sf5="$WORK_DIR/ready.ps1"
cat > "$sf5" <<'PS' cat > "$sf5" <<'PS'
$ErrorActionPreference='Continue' $ErrorActionPreference='Continue'
@@ -339,8 +350,7 @@ $need=@{
'RKill' ='C:\GuruScan\downloads\rkill.exe'; 'RKill' ='C:\GuruScan\downloads\rkill.exe';
'Emsisoft' ='C:\GuruScan\downloads\EmsisoftCommandlineScanner64.exe'; 'Emsisoft' ='C:\GuruScan\downloads\EmsisoftCommandlineScanner64.exe';
'HitmanPro' ='C:\GuruScan\downloads\HitmanPro_x64.exe'; 'HitmanPro' ='C:\GuruScan\downloads\HitmanPro_x64.exe';
'Module' ='C:\GuruScan\GuruScan.psm1'; 'Module' ='C:\GuruScan\GuruScan.psm1'
'EICAR' ='C:\GuruScanTest\eicar_test.com'
} }
$ok=$true $ok=$true
foreach($k in $need.Keys){ foreach($k in $need.Keys){
@@ -353,7 +363,7 @@ PS
if grep -q 'ALL-READY' "$WORK_DIR/last_result.json" 2>/dev/null || \ if grep -q 'ALL-READY' "$WORK_DIR/last_result.json" 2>/dev/null || \
echo "$(jq -r '.stdout' "$WORK_DIR/last_result.json" 2>/dev/null)" | grep -q 'ALL-READY'; then echo "$(jq -r '.stdout' "$WORK_DIR/last_result.json" 2>/dev/null)" | grep -q 'ALL-READY'; then
echo "[OK] prep complete - endpoint is READY for scan" echo "[OK] prep complete - endpoint is READY for scan"
post_alert "[RMM] GuruScan test: prepped $AGENT_HOST (module+3 scanners+EICAR staged)" post_alert "[RMM] GuruScan test: prepped $AGENT_HOST (module+3 scanners staged)"
else else
echo "[WARN] prep finished but not all components READY - review output above before scanning" echo "[WARN] prep finished but not all components READY - review output above before scanning"
fi fi

View File

@@ -33,6 +33,11 @@
# Writes: YYYY-MM-DD | MACHINE | <skill> | [<type>] <error> [ctx: <context>] # Writes: YYYY-MM-DD | MACHINE | <skill> | [<type>] <error> [ctx: <context>]
# (newest entry inserted at the top, just under the append marker). # (newest entry inserted at the top, just under the append marker).
# #
# Dedup: if an IDENTICAL entry (same date, machine, skill, message) already
# exists, no new line is added — the existing line gets a " (xN)" repeat counter
# bumped instead. Identical machine-generated failures (API retry loops) collapse
# to one line per day; a different message/context/date is still a new entry.
#
# Soft-fail by design: this NEVER breaks the caller. Missing log, missing jq, # Soft-fail by design: this NEVER breaks the caller. Missing log, missing jq,
# empty message -> prints a [WARN] to stderr and exits 0. # empty message -> prints a [WARN] to stderr and exits 0.
set -u set -u
@@ -62,7 +67,7 @@ DATE="$(date -u +%F)"
IDF="$ROOT/.claude/identity.json" IDF="$ROOT/.claude/identity.json"
MACHINE="" MACHINE=""
if command -v jq >/dev/null 2>&1 && [ -f "$IDF" ]; then if command -v jq >/dev/null 2>&1 && [ -f "$IDF" ]; then
MACHINE="$(jq -r '.machine_name // .hostname // empty' "$IDF" 2>/dev/null)" MACHINE="$(jq -r '.machine // .machine_name // .hostname // empty' "$IDF" 2>/dev/null)"
fi fi
[ -z "$MACHINE" ] && MACHINE="$(hostname 2>/dev/null || echo unknown)" [ -z "$MACHINE" ] && MACHINE="$(hostname 2>/dev/null || echo unknown)"
@@ -76,6 +81,34 @@ ENTRY="$DATE | $MACHINE | $SKILL | $MSG"
MARK="<!-- Append entries below this line -->" MARK="<!-- Append entries below this line -->"
TMP="$LOG.tmp.$$" TMP="$LOG.tmp.$$"
# Dedup pass: an identical entry today (bare, or already counted "(xN)") gets its
# repeat counter bumped in place instead of a duplicate line. Literal string
# compares only — the message may contain regex metacharacters.
DEDUP_RC=1
awk -v entry="$ENTRY" '
!bumped && $0 == entry { print entry " (x2)"; bumped=1; next }
!bumped && index($0, entry " (x") == 1 {
n = substr($0, length(entry) + 4) # text after " (x"
if (n ~ /^[0-9]+\)$/) {
sub(/\)$/, "", n)
print entry " (x" n+1 ")"; bumped=1; next
}
}
{ print }
END { exit bumped ? 0 : 1 }
' "$LOG" > "$TMP" 2>/dev/null && DEDUP_RC=0
if [ "$DEDUP_RC" -eq 0 ]; then
if mv "$TMP" "$LOG" 2>/dev/null; then
echo "[OK] duplicate entry — bumped repeat counter in errorlog.md ($SKILL)"
else
rm -f "$TMP" 2>/dev/null
echo "[WARN] log-skill-error: could not write $LOG" >&2
fi
exit 0
fi
rm -f "$TMP" 2>/dev/null
if awk -v entry="$ENTRY" -v mark="$MARK" ' if awk -v entry="$ENTRY" -v mark="$MARK" '
{ print } { print }
($0==mark && !done) { print ""; print entry; done=1 } ($0==mark && !done) { print ""; print entry; done=1 }

View File

@@ -12,6 +12,11 @@
# bash post-bot-alert.sh "message text" bot # force #bot-alerts # bash post-bot-alert.sh "message text" bot # force #bot-alerts
# echo "message text" | bash post-bot-alert.sh # echo "message text" | bash post-bot-alert.sh
# #
# Identity: This script sources get-identity.sh, making $USER_SHORT, $USER_NAME,
# $MACHINE, $USER_EMAIL available. Callers can use these in messages for correct
# attribution (e.g., "[RMM] $USER_SHORT deployed..."). The script itself doesn't
# auto-inject identity - callers must explicitly use the vars when needed.
#
# Token resolution (first hit wins): # Token resolution (first hit wins):
# 1. SOPS vault: projects/discord-bot/bot-token.sops.yaml field credentials.bot_token # 1. SOPS vault: projects/discord-bot/bot-token.sops.yaml field credentials.bot_token
# 2. projects/discord-bot/.env key DISCORD_TOKEN # 2. projects/discord-bot/.env key DISCORD_TOKEN
@@ -27,6 +32,9 @@ BOT_CHANNEL_ID="624710699771232265" # #bot-alerts — default (Syncro + gene
DEV_CHANNEL_ID="1509998508198068484" # #dev-alerts — private RMM/Dev alerts (Howard + Mike only) DEV_CHANNEL_ID="1509998508198068484" # #dev-alerts — private RMM/Dev alerts (Howard + Mike only)
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
# Load identity for attribution (soft-fail if missing)
source "$ROOT/.claude/scripts/get-identity.sh" 2>/dev/null || true
# --- message (arg or stdin) --- # --- message (arg or stdin) ---
MSG="${1:-}" MSG="${1:-}"
if [ -z "$MSG" ] && [ ! -t 0 ]; then MSG="$(cat)"; fi if [ -z "$MSG" ] && [ ! -t 0 ]; then MSG="$(cat)"; fi
@@ -35,6 +43,13 @@ if [ -z "$MSG" ]; then
exit 0 exit 0
fi fi
# Discord caps message content at 2000 chars (code 50035 on overflow). Alerts
# are one-liners by contract — truncate rather than chunk.
if [ "${#MSG}" -gt 1900 ]; then
MSG="${MSG:0:1900} ...[truncated]"
echo "[WARNING] post-bot-alert: message over 1900 chars — truncated" >&2
fi
# --- channel routing --- # --- channel routing ---
# Optional 2nd arg: "dev"/"bot" keyword, a raw channel id, or omit for auto. # Optional 2nd arg: "dev"/"bot" keyword, a raw channel id, or omit for auto.
# Auto: RMM/Dev-category prefixes -> #dev-alerts (private); everything else # Auto: RMM/Dev-category prefixes -> #dev-alerts (private); everything else
@@ -67,14 +82,15 @@ if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
exit 0 exit 0
fi fi
# --- post (jq builds JSON so the message is safely escaped) --- # --- post (jq builds the JSON; piped to curl via STDIN, never argv — a payload
PAYLOAD="$(jq -nc --arg c "$MSG" '{content: $c}')" # passed as a command-line arg on Windows goes through the argv encoding layer,
RESP="$(curl -s -m 15 -w $'\n%{http_code}' \ # which mangles non-ASCII chars into invalid JSON -> Discord 50109) ---
RESP="$(jq -nc --arg c "$MSG" '{content: $c}' | curl -s -m 15 -w $'\n%{http_code}' \
-X POST "https://discord.com/api/v10/channels/${CHANNEL_ID}/messages" \ -X POST "https://discord.com/api/v10/channels/${CHANNEL_ID}/messages" \
-H "Authorization: Bot ${TOKEN}" \ -H "Authorization: Bot ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "User-Agent: ClaudeToolsBot (claudetools, 1.0)" \ -H "User-Agent: ClaudeToolsBot (claudetools, 1.0)" \
--data-binary "$PAYLOAD" 2>/dev/null)" --data-binary @- 2>/dev/null)"
HTTP="$(printf '%s' "$RESP" | tail -n1)" HTTP="$(printf '%s' "$RESP" | tail -n1)"
BODY="$(printf '%s' "$RESP" | sed '$d')" BODY="$(printf '%s' "$RESP" | sed '$d')"

View File

@@ -0,0 +1,154 @@
#!/usr/bin/env bash
# ps-encoded.sh — dispatch PowerShell through quote-mangling layers safely.
#
# THE point of this helper: a PS payload that traverses CommandLineToArgvW
# (curl.exe args, plink, ScreenConnect's command box, RMM->cmd.exe) gets its
# embedded double-quotes stripped and its UNC backslashes halved — the single
# most-repeated friction class in errorlog.md (ref=feedback_windows_quote_stripping,
# 9+ hits). -EncodedCommand (UTF-16LE base64) has NO quotes, backslashes, or
# dollar signs to mangle, so the script arrives byte-exact. Write the script
# to a FILE (or stdin), let this helper encode + deliver it.
#
# Usage:
# ps-encoded.sh encode <script.ps1|-> print the one-liner for
# ScreenConnect / plink / any paste
# ps-encoded.sh rmm <agent-uuid> <script.ps1|-> dispatch via GuruRMM + poll
# [--timeout <sec>] agent-side timeout_seconds (default 120)
# [--user-session] context: user_session (WTS-impersonated desktop user)
# [--no-wait] dispatch only; print command id, skip polling
# [--force] override the size refusal (see below)
#
# Size guards (agent chokes on ~7KB command bodies; encoding inflates ~2.67x):
# encoded > 4000 chars -> [WARNING]; encoded > 6000 chars -> refuse unless
# --force (split the script, or stage it on the endpoint and run the file).
#
# Exit codes: 0 ok; 1 usage/encode error; 2 size refusal; 3 dispatch/poll failure.
set -u
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
_logerr() { bash "$ROOT/.claude/scripts/log-skill-error.sh" "ps-encoded" "$@" >/dev/null 2>&1 || true; }
usage() { sed -n '2,26p' "$0"; exit 1; }
read_script() { # $1 = file path or "-"
if [ "$1" = "-" ]; then cat
elif [ -f "$1" ]; then cat "$1"
else echo "[ERROR] script file not found: $1" >&2; exit 1
fi
}
encode_b64() { # stdin: UTF-8 script -> stdout: UTF-16LE base64 (no BOM, no wraps)
if command -v iconv >/dev/null 2>&1; then
iconv -f UTF-8 -t UTF-16LE | base64 -w0
else
# Git-for-Windows ships the iconv *library* (msys-iconv-2.dll) but NOT iconv.exe,
# and has no pacman to install it. Fall back to Python (present fleet-wide) for the
# UTF-16LE -> base64 encode. Binary stdin/stdout so no CRLF translation.
local py
py="$(command -v py || command -v python3 || command -v python || true)"
if [ -z "$py" ]; then
echo "[ERROR] neither iconv nor python available for UTF-16LE encoding" >&2
return 1
fi
"$py" -c "import sys,base64; sys.stdout.write(base64.b64encode(sys.stdin.buffer.read().decode('utf-8').encode('utf-16-le')).decode())"
fi
}
size_gate() { # $1 = encoded string, $2 = force flag (0/1)
local n=${#1}
if [ "$n" -gt 6000 ] && [ "${2:-0}" != "1" ]; then
echo "[ERROR] encoded payload is ${n} chars (>6000); the agent fails on bodies this large." >&2
echo " Split the script into sections, or stage it as a file on the endpoint" >&2
echo " and dispatch 'powershell -File <path>'. Override with --force." >&2
return 1
elif [ "$n" -gt 4000 ]; then
echo "[WARNING] encoded payload is ${n} chars — near the agent's body-size failure zone (~7KB)." >&2
fi
return 0
}
cmd_encode() {
local src="${1:-}"; [ -z "$src" ] && usage
local b64
b64="$(read_script "$src" | encode_b64)"
[ -z "$b64" ] && { echo "[ERROR] encoding produced nothing" >&2; _logerr "encode produced empty output" --context "src=$src"; exit 1; }
size_gate "$b64" 1 || true # encode mode: warn only, the paste target may cope
printf 'powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand %s\n' "$b64"
}
cmd_rmm() {
local agent="${1:-}"; shift || true
local src="${1:-}"; shift || true
[ -z "$agent" ] || [ -z "$src" ] && usage
local timeout=120 context="" wait=1 force=0
while [ $# -gt 0 ]; do
case "$1" in
--timeout) timeout="${2:?}"; shift 2 ;;
--user-session) context="user_session"; shift ;;
--no-wait) wait=0; shift ;;
--force) force=1; shift ;;
*) echo "[ERROR] unknown option: $1" >&2; usage ;;
esac
done
local b64
b64="$(read_script "$src" | encode_b64)"
[ -z "$b64" ] && { echo "[ERROR] encoding produced nothing" >&2; _logerr "encode produced empty output" --context "src=$src"; exit 1; }
size_gate "$b64" "$force" || exit 2
eval "$(bash "$ROOT/.claude/scripts/rmm-auth.sh")"
if [ -z "${TOKEN:-}" ] || [ -z "${RMM:-}" ]; then
echo "[ERROR] RMM auth failed (no TOKEN/RMM)" >&2
_logerr "RMM auth failed via rmm-auth.sh" --context "agent=$agent"
exit 3
fi
# command_type=shell (cmd.exe): the base64 blob is a single quote-free token,
# so no layer between here and powershell.exe can mangle it. timeout_seconds
# is the field the agent honors — 'timeout' is silently ignored (errorlog).
local oneliner payload resp cmd_id status
oneliner="powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${b64}"
payload="$(jq -nc --arg ct shell --arg cmd "$oneliner" --argjson to "$timeout" \
--arg cx "$context" \
'{command_type:$ct, command:$cmd, timeout_seconds:$to}
+ (if $cx != "" then {context:$cx} else {} end)')"
resp="$(printf '%s' "$payload" | curl -s -m 20 -X POST "$RMM/api/agents/$agent/command" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
--data-binary @-)"
cmd_id="$(printf '%s' "$resp" | jq -r '.command_id // empty' 2>/dev/null)"
status="$(printf '%s' "$resp" | jq -r '.status // empty' 2>/dev/null)"
if [ -z "$cmd_id" ]; then
echo "[ERROR] dispatch failed: $resp" >&2
_logerr "EncodedCommand dispatch failed" --context "agent=$agent resp=${resp:0:80}"
exit 3
fi
echo "[OK] dispatched command_id=$cmd_id (initial status: ${status:-unknown}, timeout_seconds=$timeout)"
[ "$wait" = "0" ] && exit 0
local max_polls=$(( (timeout + 30) / 5 )) count=0 result
while [ $count -lt $max_polls ]; do
result="$(curl -s -m 15 "$RMM/api/commands/$cmd_id" -H "Authorization: Bearer $TOKEN")"
status="$(printf '%s' "$result" | jq -r '.status // empty' 2>/dev/null)"
case "$status" in
completed|failed|cancelled|interrupted)
echo "--- status: $status ---"
printf '%s' "$result" | jq -r '
"exit_code: \(.exit_code // "n/a")",
"--- stdout ---", (.stdout // .output // ""),
"--- stderr ---", (.stderr // "")' 2>/dev/null || printf '%s\n' "$result"
[ "$status" = "completed" ] && exit 0 || exit 3 ;;
running|pending) count=$((count+1)); sleep 5 ;;
*) echo "[ERROR] empty/unknown status — response: $result" >&2
_logerr "poll returned empty status" --context "cmd=$cmd_id agent=$agent"
exit 3 ;;
esac
done
echo "[WARNING] poll timeout after $((max_polls*5))s — command $cmd_id may still be running (last: $status)"
exit 3
}
case "${1:-}" in
encode) shift; cmd_encode "$@" ;;
rmm) shift; cmd_rmm "$@" ;;
*) usage ;;
esac

View File

@@ -0,0 +1,51 @@
# register-edr-watcher.ps1
# Register the "ClaudeTools - EDR Isolation Watcher" scheduled task on this Windows
# machine (the same box that runs GPS-RMM-AutoEnroll et al). The task runs
# edr-isolation-watch.sh every 10 minutes, which polls Datto EDR for hosts auto-
# isolated by an EDR response and posts each NEW one to #dev-alerts (Mike + Howard).
#
# Interim alerting until the GuruRMM EDR webhook integration (Feature 6) lands.
# Idempotent: -Force replaces any existing task with the same name.
#
# Run from an ordinary (non-admin) PowerShell:
# powershell -ExecutionPolicy Bypass -File C:\claudetools\.claude\scripts\register-edr-watcher.ps1
$ErrorActionPreference = "Stop"
$TaskName = "ClaudeTools - EDR Isolation Watcher"
# Launch via a wscript.exe VBS wrapper (GUI-subsystem host) so bash starts hidden and
# NO console window flashes on the desktop every 10 min. Launching bash.exe directly is
# a console-subsystem app with LogonType=Interactive + Hidden=False -> visible flash.
# Mirrors the gps-rmm-progress-hidden.vbs fix. Do NOT revert to a raw bash.exe action.
$BashExe = "C:\Program Files\Git\bin\bash.exe"
if (-not (Test-Path $BashExe)) { Write-Host "[ERROR] Git bash not found at $BashExe" -ForegroundColor Red; exit 1 }
$ScriptWin = "C:\claudetools\.claude\scripts\edr-isolation-watch.sh"
if (-not (Test-Path $ScriptWin)) { Write-Host "[ERROR] Watcher not found at $ScriptWin" -ForegroundColor Red; exit 1 }
$WScript = "C:\Windows\System32\wscript.exe"
$Vbs = "C:\claudetools\.claude\scripts\edr-isolation-watch-hidden.vbs"
if (-not (Test-Path $Vbs)) { Write-Host "[ERROR] Hidden wrapper not found at $Vbs" -ForegroundColor Red; exit 1 }
$Action = New-ScheduledTaskAction -Execute $WScript -Argument "`"$Vbs`""
# Every 10 minutes, indefinitely (10-year duration avoids the MaxValue quirk).
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).Date `
-RepetitionInterval (New-TimeSpan -Minutes 10) `
-RepetitionDuration (New-TimeSpan -Days 3650)
# Run as the current user, only when logged on (needs the interactive vault/env,
# same posture as the GrepAI watcher). No admin required.
$Principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
$Settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
-MultipleInstances IgnoreNew -Hidden
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger `
-Principal $Principal -Settings $Settings `
-Description "Polls Datto EDR every 10 min for auto-isolated hosts; posts new ones to #dev-alerts. Interim until GuruRMM EDR webhook (Feature 6)." -Force | Out-Null
Write-Host "[OK] Registered scheduled task: $TaskName (every 10 min)" -ForegroundColor Green
Get-ScheduledTask -TaskName $TaskName | Select-Object TaskName, State | Format-Table -AutoSize

View File

@@ -47,14 +47,26 @@ if (-not (Test-Path $Script)) {
exit 1 exit 1
} }
# Resolve the py launcher's full path (the action's Execute wants an absolute # Resolve pythonW.exe (the GUI-subsystem Python host) so the detector runs with NO
# path; "py" alone usually resolves but we pin it for reliability under the # console window flashing on the desktop at logon / every 4h. Using py.exe or
# Task Scheduler's environment). # python.exe (console subsystem) draws a visible window each run. Prefer pythonw next
# to the active interpreter; fall back to a PATH lookup, then py.exe as a last resort.
$PyPath = $null
$PyCmd = Get-Command py -ErrorAction SilentlyContinue $PyCmd = Get-Command py -ErrorAction SilentlyContinue
if ($null -ne $PyCmd) { if ($null -ne $PyCmd) {
$PyPath = $PyCmd.Source try {
} else { $exe = (& py -c "import sys;print(sys.executable)").Trim()
$PyPath = "py" # fall back to PATH resolution at run time $wexe = Join-Path (Split-Path $exe) "pythonw.exe"
if (Test-Path $wexe) { $PyPath = $wexe }
} catch { }
}
if ($null -eq $PyPath) {
$PwCmd = Get-Command pythonw -ErrorAction SilentlyContinue
if ($null -ne $PwCmd) { $PyPath = $PwCmd.Source }
}
if ($null -eq $PyPath) {
if ($null -ne $PyCmd) { $PyPath = $PyCmd.Source } else { $PyPath = "py" }
Write-Host "[WARNING] pythonw.exe not found; task may flash a console window." -ForegroundColor Yellow
} }
$Action = New-ScheduledTaskAction ` $Action = New-ScheduledTaskAction `
@@ -76,7 +88,8 @@ $Settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Minutes 30) ` -ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
-MultipleInstances IgnoreNew ` -MultipleInstances IgnoreNew `
-StartWhenAvailable ` -StartWhenAvailable `
-DontStopOnIdleEnd -DontStopOnIdleEnd `
-Hidden
Register-ScheduledTask ` Register-ScheduledTask `
-TaskName $TaskName ` -TaskName $TaskName `

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# syncro-env.sh — Resolve the repo root and the caller's Syncro API key from the SOPS vault.
#
# Source this instead of hardcoding a repo path or an API key:
# source "$(dirname "${BASH_SOURCE[0]}")/syncro-env.sh" # or by absolute path
# curl -s "$SYNCRO_BASE/customers?api_key=$SYNCRO_API_KEY"
#
# Exports: CLAUDETOOLS_ROOT, VAULT_ROOT, SYNCRO_USER, SYNCRO_BASE, SYNCRO_API_KEY
#
# Root resolution mirrors vault.sh: derive from this script's own location, then let
# identity.json's claudetools_root override. Never hardcode a drive letter — the repo
# lives at C:/claudetools on some machines and D:/claudetools on others.
#
# Soft-fails like get-identity.sh: on any failure it warns, leaves SYNCRO_API_KEY empty,
# and returns 1 without exiting, so a sourcing skill degrades instead of dying.
_syncro_env_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export CLAUDETOOLS_ROOT="$(cd "$_syncro_env_dir/../.." && pwd)"
_identity="$CLAUDETOOLS_ROOT/.claude/identity.json"
export SYNCRO_BASE="https://computerguru.syncromsp.com/api/v1"
export SYNCRO_API_KEY=""
export SYNCRO_USER=""
export VAULT_ROOT=""
if [ ! -f "$_identity" ]; then
echo "[WARNING] identity.json not found at $_identity — Syncro calls will be unauthenticated" >&2
return 1 2>/dev/null || exit 1
fi
# identity.json is authoritative for both the repo root and the vault location.
_root_override=$(jq -r '.claudetools_root // empty' "$_identity" 2>/dev/null)
[ -n "$_root_override" ] && [ -d "$_root_override" ] && export CLAUDETOOLS_ROOT="$_root_override"
export VAULT_ROOT=$(jq -r '.vault_path // empty' "$_identity" 2>/dev/null)
export SYNCRO_USER=$(jq -r '.user // empty' "$_identity" 2>/dev/null)
# Per-user token — attribution in Syncro depends on using the right one.
# Map a user name to their vaulted key path; empty = no key vaulted for them.
_syncro_key_path() {
case "$1" in
mike) echo "msp-tools/syncro" ;;
howard) echo "msp-tools/syncro-howard" ;;
winter) echo "msp-tools/syncro-winter" ;;
*) echo "" ;;
esac
}
# Prefer the requester's key when running on someone's behalf (e.g. the Discord
# bot sets CLAUDETOOLS_REQUESTER_USER per thread) so Syncro attributes actions
# to the person who asked. Fall back to the identity.json user when the
# requester has no vaulted key (e.g. rob) — never hard-fail on the preference.
_vault_path=""
_requester="${CLAUDETOOLS_REQUESTER_USER:-}"
if [ -n "$_requester" ] && [ "$_requester" != "$SYNCRO_USER" ]; then
_req_path=$(_syncro_key_path "$_requester")
if [ -n "$_req_path" ]; then
_vault_path="$_req_path"
export SYNCRO_USER="$_requester"
echo "[INFO] Using Syncro key for requester '$_requester' (attribution)" >&2
fi
fi
[ -z "$_vault_path" ] && _vault_path=$(_syncro_key_path "$SYNCRO_USER")
if [ -z "$_vault_path" ]; then
echo "[WARNING] No Syncro key vaulted for user '$SYNCRO_USER' — enrichment skipped" >&2
return 1 2>/dev/null || exit 1
fi
SYNCRO_API_KEY=$(bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
get-field "$_vault_path" credentials.credential 2>/dev/null | tail -1)
export SYNCRO_API_KEY
if [ -z "$SYNCRO_API_KEY" ]; then
echo "[ERROR] Could not read $_vault_path from vault — is SOPS/age configured?" >&2
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "syncro-env" \
"vault read failed for $_vault_path" --context "user=$SYNCRO_USER" >/dev/null 2>&1 || true
return 1 2>/dev/null || exit 1
fi
unset _syncro_env_dir _identity _root_override _vault_path _requester _req_path
unset -f _syncro_key_path
return 0 2>/dev/null || true

View File

@@ -19,6 +19,11 @@
"type": "command", "type": "command",
"command": "bash -c 'h=\"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-backslash-winpath.sh\"; [ -f \"$h\" ] && exec bash \"$h\" || exit 0'", "command": "bash -c 'h=\"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-backslash-winpath.sh\"; [ -f \"$h\" ] && exec bash \"$h\" || exit 0'",
"timeout": 10 "timeout": 10
},
{
"type": "command",
"command": "bash -c 'h=\"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-tmp-path.sh\"; [ -f \"$h\" ] && exec bash \"$h\" || exit 0'",
"timeout": 10
} }
] ]
} }

View File

@@ -1,51 +1,68 @@
--- ---
name: agy name: agy
description: > description: "Route a task to the Google Antigravity CLI (agy) for an independent second model (sibling of grok): different-vendor second opinion, adversarial verification, code review of files/diffs, vision, live web search, one-shot text answers. Triggers: ask gemini, ask agy, agy verify, agy review, agy."
Route a task to the official Google Gemini CLI for an independent second model — a
sibling of the `grok` second-opinion router. Use for a different-vendor SECOND OPINION
or adversarial VERIFICATION of a Claude finding/design, a Gemini code REVIEW of files /
a git diff, and one-shot Gemini TEXT answers. Triggers: ask gemini, gemini verify,
second opinion from gemini, gemini review, agy ... A second model, NOT a replacement
for Claude's own codebase work.
--- ---
# AGY — Gemini capability router # AGY — Antigravity CLI capability router
Claude shells out to the locally-installed **Google Gemini CLI** (`gemini`, npm Claude shells out to the locally-installed **Google Antigravity CLI** (`agy`, a
global, v0.45.2) for a genuinely independent, different-vendor second model. native Go binary at `~/AppData/Local/agy/bin/agy`, v1.0.16) for a genuinely
AGY is the sibling of [`grok`](../grok/SKILL.md): both are second-opinion / independent, different-vendor second model. AGY is the sibling of
review routers. Use whichever you want a second model from (or both, to triangulate). [`grok`](../grok/SKILL.md): both are second-opinion / review routers. Use whichever
Verified working on this machine (2026-06-05; re-validated 2026-06-17 against the you want a second model from (or both, to triangulate). Modes verified on this
CLI's bundled help/README — JSON schema, all flags, pinned model, and live search machine (2026-07-02): text, verify, review (single file / file set / git diff),
all confirmed): text, verify, review (single file / file set / git diff), image-analyze (vision), search (live web search + source URLs).
image-analyze (vision input), search (live Google web search). All KEYLESS — they
work on Google OAuth, no API key.
**Auth:** Gemini uses **Google login (OAuth)****no API key**. Creds live at > **REWIRED 2026-07-02 — `agy` replaces the old `gemini` npm CLI.** The former
`~/.gemini/oauth_creds.json`. If calls fail with an auth error, run `gemini` > Google `gemini` CLI (npm `@google/gemini-cli`) stopped working on this account
interactively once and choose **"Login with Google"**, then retry. > with a Cloud-project eligibility error (`throwIneligibleOrProjectIdError` — it
> demanded a `GOOGLE_CLOUD_PROJECT` the personal account couldn't provide). The
> Antigravity CLI (`agy`) uses Antigravity's OWN auth and works headless with no
> project ID. The wrapper is `scripts/ask-agy.sh`; `scripts/ask-gemini.sh` is kept
> as a thin **deprecated shim** that `exec`s ask-agy.sh so old references still work.
**Backend models (multi-vendor, `--model`):** `agy models` lists them — Gemini 3.5
Flash (Low/Medium/High), Gemini 3.1 Pro (Low/High), Claude Sonnet 4.6 (Thinking),
Claude Opus 4.6 (Thinking), GPT-OSS 120B. `text` uses the CLI default (Flash, fast);
`verify`/`review*`/`image-analyze`/`search` pin the strong model
`Gemini 3.1 Pro (High)`. Override any mode with `AGY_MODEL="<friendly name>"`.
**Auth:** Antigravity's own login (`~/.gemini/antigravity-cli/`) — **no API key, no
`GOOGLE_CLOUD_PROJECT`**. If a call fails with an auth error, run `agy` interactively
once to sign in, then retry.
## The wrapper ## The wrapper
``` ```
bash "$CLAUDETOOLS_ROOT/.claude/skills/agy/scripts/ask-gemini.sh" <mode> ... bash "$CLAUDETOOLS_ROOT/.claude/skills/agy/scripts/ask-agy.sh" <mode> ...
``` ```
Authoritative headless flags (from `agy --help`, Go flag package — the web docs at
antigravity.google/docs/cli are JS-rendered SPAs and unreadable by fetch tools, so
`--help` is the source of truth): `-p/--print/--prompt` runs one prompt and prints
the response (**the prompt is the flag's VALUE and MUST be last** — a bare `-p --model`
makes "--model" the prompt); `--model "<name>"`; `--add-dir <dir>` (repeatable, gives
the file tools access to a review/vision target); `--dangerously-skip-permissions`
(auto-approve tool calls — without it print mode HANGS on an approval prompt);
`--print-timeout <dur>` (default 5m). There is **no JSON output flag** — stdout is
plain text/markdown, so the wrapper does no JSON parsing.
| Mode | Usage | What it does | | Mode | Usage | What it does |
|------|-------|--------------| |------|-------|--------------|
| `text` | `ask-gemini.sh text "<prompt>"` or `text --prompt-file <path>` | One-shot text answer from an independent model. `--prompt-file` for long content (review/summarize a doc). Default model routing. | | `text` | `ask-agy.sh text "<prompt>"` or `text --prompt-file <path>` | One-shot text answer from an independent model. `--prompt-file` for long content (review/summarize a doc). Default model routing. |
| `verify` | `ask-gemini.sh verify "<claim/finding>"` or `verify --prompt-file <path>` | Adversarial second opinion — Gemini tries to REFUTE / find gaps, returns a verdict + reasons. Pinned to the strong model. | | `verify` | `ask-agy.sh verify "<claim/finding>"` or `verify --prompt-file <path>` | Adversarial second opinion — Gemini tries to REFUTE / find gaps, returns a verdict + reasons. Pinned to the strong model. |
| `review` | `ask-gemini.sh review <file-path> ["<instructions>"]` | Gemini reads the file itself (its `read_file` tool, read-only `plan` mode) and reviews it. Path resolution: absolute, CWD-relative, or relative to `$CLAUDETOOLS_ROOT`**see the path gotcha below**. Spaces OK. Works even on gitignored files. | | `review` | `ask-agy.sh review <file-path> ["<instructions>"]` | agy reads the file itself (copied into an `--add-dir` workspace) and reviews it. Path resolution: absolute, CWD-relative, or relative to `$CLAUDETOOLS_ROOT`**see the path gotcha below**. Spaces OK. Works even on gitignored files. |
| `review-files` | `ask-gemini.sh review-files [-i "<instr>"] <f1> [f2 …]` | Review a **set** of files together (cross-file consistency, multi-file change). Same path resolution as `review` (**see gotcha below**); spaces OK. No code passed as a shell arg. | | `review-files` | `ask-agy.sh review-files [-i "<instr>"] <f1> [f2 …]` | Review a **set** of files together (cross-file consistency, multi-file change). Same path resolution as `review` (**see gotcha below**); spaces OK. No code passed as a shell arg. |
| `review-diff` | `ask-gemini.sh review-diff [-C <repo-dir>] [-i "<instr>"] <gitref> [-- <pathspec>]` | Review a **git diff** (`git diff <gitref>` from `<repo-dir>`; default repo root, use `-C` for a submodule e.g. `-C projects/msp-tools/guru-rmm`). Diff goes via the prompt file; Gemini can `read_file` changed files for full context. | | `review-diff` | `ask-agy.sh review-diff [-C <repo-dir>] [-i "<instr>"] <gitref> [-- <pathspec>]` | Review a **git diff** (`git diff <gitref>` from `<repo-dir>`; default repo root, use `-C` for a submodule e.g. `-C projects/msp-tools/guru-rmm`). Diff goes via the prompt file; Gemini can `read_file` changed files for full context. |
| `image-analyze` | `ask-gemini.sh image-analyze <image-path> ["<question>"]` | **Vision**Gemini `read_file`s the image and describes/answers about it. Pins the **pro vision model** (the default flash-lite router hallucinates image content). Path absolute or repo-relative; spaces OK. KEYLESS (works on OAuth). | | `image-analyze` | `ask-agy.sh image-analyze <image-path> ["<question>"]` | **Vision**agy reads the image (via `--add-dir`) and describes/answers about it. Pinned to the strong model. Path absolute or repo-relative; spaces OK. |
| `search` | `ask-gemini.sh search "<query>"` (or `search --prompt-file <path>`) | **Live Google web search** (sibling of `grok xsearch`) — Gemini uses its `google_web_search` tool and returns the answer **with source URLs**. KEYLESS (works on OAuth). | | `search` | `ask-agy.sh search "<query>"` (or `search --prompt-file <path>`) | **Live web search** (sibling of `grok xsearch`) — agy searches the web and returns the answer **with source URLs**. |
| `raw` | `ask-gemini.sh raw <gemini args...>` | Escape hatch — passes args straight to `gemini`. | | `raw` | `ask-agy.sh raw <agy args...>` | Escape hatch — passes args straight to `agy`. |
The script runs Gemini headless with `-o json`, extracts the answer from The script runs `agy` headless (`--dangerously-skip-permissions ... -p "<prompt>"`),
`.response` (parsing from the first `{` so the CLI's cosmetic warning lines are captures plain-text stdout (no JSON — agy has no structured-output mode), keeps stderr
ignored), and keeps stderr separate from the JSON so 429-backoff / warning noise separate for diagnostics, and retries a couple times with backoff on an empty turn.
never corrupts the parse. For review/vision it copies the target into a temp dir and `--add-dir`s it so agy's
file tools can read it regardless of location/spaces.
> [!WARNING] > [!WARNING]
> **Path gotcha for `review` / `review-files` (this has bitten us repeatedly).** > **Path gotcha for `review` / `review-files` (this has bitten us repeatedly).**
@@ -61,96 +78,84 @@ never corrupts the parse.
### Model ### Model
- `text` uses Gemini's **default routing** (currently a flash-tier model) — fast, cheap. - `text` uses the CLI **default** (Gemini 3.5 Flash) — fast, cheap.
- `verify` / `review*` pin a **strong** model — `gemini-3.1-pro-preview` (verified - `verify` / `review*` / `image-analyze` / `search` pin the strong model
available 2026-06-05, still valid 2026-06-17; the GA-looking `gemini-3.1-pro` and **`Gemini 3.1 Pro (High)`** via `--model`.
`gemini-3-pro` both `ModelNotFoundError`, so keep the `-preview` suffix). - Override any mode with `AGY_MODEL="<friendly name>"` (exact strings from
- Override either with `GEMINI_MODEL=<id>` (e.g. `GEMINI_MODEL=gemini-2.5-pro`). `agy models`, e.g. `AGY_MODEL="Claude Opus 4.6 (Thinking)"`).
- `image-analyze` and `search` also pin the strong model (`GEMINI_MODEL` still honored).
### Multimodal: image INPUT works, image GENERATION does not ### Vision
- **Image INPUT (vision) works on OAuth** — `image-analyze` reads an image with the `image-analyze` copies the image into an `--add-dir` workspace and asks agy to read
pinned **pro vision model** and describes it correctly. The default flash-lite and describe it (pinned to the strong model). Report-only — describes an image you
router HALLUCINATES image content, which is why the pro model is pinned. give it. Image/video **generation** stays [GROK](../grok/SKILL.md)'s lane
- **Image GENERATION (nano-banana) does NOT work on OAuth** — it needs a Google AI (`grok image` / `grok video`).
Studio `NANOBANANA_API_KEY` plus the `nanobanana` extension. **Deferred** for now.
Image/video **generation** stays [GROK](../grok/SKILL.md)'s lane (`grok image` /
`grok video`); AGY's multimodal support is read/analyze only.
## Machine availability (fleet) ## Machine availability (fleet)
AGY is **per-machine** — the skill syncs fleet-wide but the `gemini` binary does AGY is **per-machine** — the skill syncs fleet-wide but the `agy` binary does not.
not. Availability is gated by `identity.json` (per-machine, gitignored): Availability is gated by `identity.json` (per-machine, gitignored):
```json ```json
"gemini": { "installed": true, "agy": { "installed": true,
"binary": "C:/Users/guru/AppData/Roaming/npm/gemini", "binary": "C:/Users/guru/AppData/Local/agy/bin/agy",
"auth": "oauth", "is_fleet_host": true, "auth": "antigravity", "is_fleet_host": true,
"capabilities": ["text","verify","review","image-analyze","search"] } "capabilities": ["text","verify","review","review-files","review-diff","image-analyze","search"] }
``` ```
- If `gemini.installed` is `false` (or the block is absent), `ask-gemini.sh` exits - If `agy.installed` is `false` (or the block is absent), `ask-agy.sh` exits **3**
**3** with routing guidance instead of failing obscurely. Claude on such a with routing guidance instead of failing obscurely. Claude on such a machine
machine should NOT attempt local Gemini. should NOT attempt local agy. (The wrapper falls back to the legacy `.gemini`
- **Fleet Gemini hosts: `GURU-5070`, `GURU-BEAST-ROG`** — machines with the Gemini block if `.agy` is absent, for machines mid-migration.)
CLI installed and Google-OAuth'd. When others get it, install - **Fleet host: `GURU-5070`** (agy installed + Antigravity-authed). When others get
`@google/gemini-cli`, run `gemini` once to log in with Google, then set their it, install the Antigravity CLI, run `agy` once to sign in, then set their
`identity.json` `gemini` block (and update this line). `identity.json` `agy` block (and update this line).
**Remote routing (NOT yet wired):** a non-host machine cannot run Gemini locally. **Remote routing (NOT yet wired):** a non-host machine cannot run agy locally. Route
To fulfill an AGY request from elsewhere, route it to the host (`GURU-5070`) — the request to the host (`GURU-5070`) — same pending channels as Grok (GuruRMM agent
same pending channels as Grok (GuruRMM agent exec, a relay, or a coord-API job exec, a relay, or a coord-API job queue). Until built, AGY requests originate on the host.
queue). Until that's built, AGY requests originate on the host machine.
## When to route to Gemini (AGY) ## When to route to AGY
- **Independent verification** — a genuinely different vendor/model to red-team a - **Independent verification** — a different vendor/model to red-team a Claude
Claude finding or design before acting on it. (`verify`) finding or design before acting on it. (`verify`)
- **Second-model code review** — have Gemini read and critique a file, a set of - **Second-model code review** — agy reads and critiques a file, a set, or a diff
files, or a diff independently of Claude. (`review`, `review-files`, `review-diff`) independently of Claude. (`review`, `review-files`, `review-diff`)
- **Diverse drafts / second opinion** — alternative phrasing or approach to - **Diverse drafts / second opinion** — alternative phrasing or approach. (`text`)
compare. (`text`) - **Live web facts** — current info past Claude's cutoff, with source URLs. (`search`)
- **Google-ecosystem reach** — when a Google-side model/behavior is specifically
wanted as the comparison point.
AGY and [GROK](../grok/SKILL.md) are sibling second-opinion routers. Pick one, or AGY and [GROK](../grok/SKILL.md) are sibling second-opinion routers. Pick one, or run
run both and compare — disagreement between them is a strong signal to slow down. both and compare — disagreement between them is a strong signal to slow down.
## When NOT to ## When NOT to
- Pure classify / extract / summarize → cheaper via Tier-0 Ollama (`.claude/OLLAMA.md`). - Pure classify / extract / summarize → cheaper via Tier-0 Ollama (`.claude/OLLAMA.md`).
- Editing this repo's code → Claude's own agents own the codebase work. Gemini's - Editing this repo's code → Claude's own agents own the codebase work. The wrapper
`review*` modes are read-only (`--approval-mode plan`) by design; do not give runs agy read-only in intent (review prompts say "do not modify anything"); do not
Gemini write access to this repo. point it at write tasks in this repo.
- Image / video **generation** that's GROK's lane (`grok image` / `grok video`), - Image / video **generation** → GROK's lane. AGY vision is read/analyze only.
not Gemini here (nano-banana needs an API key — deferred). Gemini CAN analyze an - **Never** delegate unsupervised destructive / production actions to agy. Always
image you give it (`image-analyze`, vision input on OAuth). review its output before acting — like Grok, it can over-claim.
- **Never** delegate unsupervised destructive / production actions to Gemini.
Always review Gemini output before acting on it — like Grok, it can over-claim.
## Safety / operational notes ## Safety / operational notes
- `--skip-trust` is REQUIRED for headless runs (the CWD isn't a Gemini "trusted - `--dangerously-skip-permissions` is passed so print mode never HANGS on a tool
folder"). Equivalent env: `GEMINI_CLI_TRUST_WORKSPACE=true`. The wrapper passes it. approval prompt (headless). It only lets agy run its own read/search tools inside
- `review*` runs under `--approval-mode plan` (read-only): Gemini can read files its sandbox to answer — the wrapper never asks it to write to this repo.
but cannot modify anything. Do not change this to `auto_edit`/`yolo`. - The prompt is passed as the VALUE of `-p` and MUST be last on the command line;
- Gemini's `read_file` honors `.gitignore` **and** a workspace sandbox (only files all other flags (`--model`, `--add-dir`, `--print-timeout`) come before it.
inside the workspace are readable). The wrapper sidesteps both by copying each - Prompts are built in a temp file and read with `-p "$(cat <file>)"` (avoids
review target into a temp dir added via `--include-directories` — so review quote hell); stdin is closed (`</dev/null`) so `-p` never waits on stdin.
works for tracked, gitignored, and spaced-path files alike. - Output is plain text/markdown. Tool-using turns (review/search/vision) may emit a
- Prompts are passed via `-p "$(cat <prompt-file>)"` built from a temp file, not line or two of tool narration before the answer; the prompts ask agy to suppress
inline shell args (avoids quote hell with long/structured content). it, and residual narration is harmless.
- stdin is always closed (`</dev/null`) so `-p` never hangs waiting on stdin.
- stdout carries two cosmetic warning lines ("True color (24-bit) support not
detected", "Ripgrep is not available...") before output; JSON extraction from
the first `{` ignores them. A transient `429 No capacity` backoff may appear on
**stderr** and self-recovers — it does not affect the parsed answer.
## Reference ## Reference
- Binary: npm global `gemini` (`C:/Users/guru/AppData/Roaming/npm/gemini` on the - Binary: native Go `agy` (`C:/Users/guru/AppData/Local/agy/bin/agy`; on PATH). The
host; the npm global dir is on PATH). The wrapper auto-locates it or honors `GEMINI=`. wrapper auto-locates it, honors `AGY=`, or reads `identity.json` `agy.binary`.
- Version 0.45.2. Auth: Google OAuth (`~/.gemini/oauth_creds.json`), no API key. - Version 1.0.16. Auth: Antigravity login (`~/.gemini/antigravity-cli/`), no API key,
- Headless contract: `gemini -p "<prompt>" -o json --skip-trust </dev/null` no `GOOGLE_CLOUD_PROJECT`.
`{session_id, response, stats}`; answer is `.response`. - Headless contract: `agy [--model "<name>"] [--add-dir <dir>] --dangerously-skip-permissions -p "<prompt>"` → plain-text answer on stdout (no JSON).
- Migration: replaced the `gemini` npm CLI 2026-07-02 (that CLI hit
`throwIneligibleOrProjectIdError`). `ask-gemini.sh` is a deprecated shim → `ask-agy.sh`.
- Sibling router: [`grok`](../grok/SKILL.md) (image/video/live-data + second opinion). - Sibling router: [`grok`](../grok/SKILL.md) (image/video/live-data + second opinion).

View File

@@ -0,0 +1,280 @@
#!/usr/bin/env bash
# ask-agy.sh — Claude -> Google Antigravity CLI (`agy`) router (independent second model).
#
# Sibling of ask-grok.sh. Routes a task to the Antigravity CLI (`agy`, a native Go
# binary at ~/AppData/Local/agy/bin/agy) for an independent, different-vendor second
# opinion, verification, code review, vision, or live web search. Multi-model backend
# (Gemini 3.5 Flash / Gemini 3.1 Pro / Claude / GPT-OSS) selectable via --model.
#
# REPLACES the old `gemini` npm CLI (ask-gemini.sh), which broke on this account with
# a Cloud-project eligibility error (throwIneligibleOrProjectIdError). `agy` uses
# Antigravity's own auth (~/.gemini/antigravity-cli/), no GOOGLE_CLOUD_PROJECT needed.
#
# Authoritative flag reference (from `agy --help`, Go flag package — 2026-07-02, v1.0.16):
# -p | --print | --prompt Run ONE prompt non-interactively and print the response.
# STRING flag: the prompt is its VALUE. MUST be LAST on the
# line (a bare `-p --model` makes "--model" the prompt).
# --model "<name>" Model for the session. Friendly names from `agy models`,
# e.g. "Gemini 3.1 Pro (High)", "Gemini 3.5 Flash (Medium)",
# "Claude Opus 4.6 (Thinking)". Omit -> CLI default (Flash).
# --add-dir <dir> Add a directory to the workspace (repeatable). Needed so
# the agent's file tools can read a review/vision target.
# --dangerously-skip-permissions Auto-approve tool calls (else print mode can HANG
# on an approval prompt until --print-timeout). Headless-safe.
# --print-timeout <dur> Print-mode wait timeout (default 5m0s). We also wrap in the
# shell `timeout` as a hard belt-and-suspenders bound.
# (There is NO JSON/structured-output flag — stdout is plain text/markdown.)
#
# Output contract: plain text on stdout. Tool-using turns (review/search/vision) may
# emit a line or two of tool narration ("I will view the content of ...") before the
# answer; we ask the model to suppress it but tolerate residual. No JSON parsing.
#
# Usage (unchanged from the old skill so callers/SKILL.md stay valid):
# ask-agy.sh text "<prompt>" # one-shot answer
# ask-agy.sh text --prompt-file <path> # long content
# ask-agy.sh verify "<claim or finding to refute>" # adversarial check
# ask-agy.sh verify --prompt-file <path>
# ask-agy.sh review <file> [instructions] # agy reads + reviews one file
# ask-agy.sh review-files [-i "instr"] <f1> [f2 ...] # review a SET together
# ask-agy.sh review-diff [-C <repo-dir>] [-i "instr"] <gitref> [-- <pathspec>]
# ask-agy.sh image-analyze <image-path> ["question"] # vision: read image + describe
# ask-agy.sh search "<query>" # live web search + sources
# ask-agy.sh raw <agy args...> # escape hatch
#
# Exit: 0 ok, 1 no result, 2 usage, 3 not installed here, 127 agy not found.
set -uo pipefail
SELF="ask-agy"
# --- path conversion: native-Windows path for agy args (no-op off Windows) ---
# agy is a native Windows binary; Git Bash hands it POSIX paths it cannot resolve.
if command -v cygpath >/dev/null 2>&1; then
winpath() { cygpath -w -- "$1" 2>/dev/null || printf '%s' "$1"; }
else
winpath() { printf '%s' "$1"; }
fi
# --- identity.json (per-machine, gitignored): is agy installed here? ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
PYBIN="$(command -v py 2>/dev/null || command -v python 2>/dev/null || command -v python3 2>/dev/null || true)"
IDFILE=""
[ -n "${CLAUDETOOLS_ROOT:-}" ] && [ -f "$CLAUDETOOLS_ROOT/.claude/identity.json" ] && IDFILE="$CLAUDETOOLS_ROOT/.claude/identity.json"
[ -z "$IDFILE" ] && IDFILE="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/identity.json"
idagy() { # read field $1 from identity.json .agy (fallback .gemini), empty if absent
[ -f "$IDFILE" ] || { echo ""; return; }
[ -n "$PYBIN" ] || { echo ""; return; }
"$PYBIN" -c "import json,sys
try:
d=json.load(sys.stdin); g=(d.get('agy') or d.get('gemini') or {}); v=g.get('$1','')
print('' if v is None else (str(v).lower() if isinstance(v,bool) else v))
except Exception: print('')" < "$IDFILE"
}
if [ "$(idagy installed)" = "false" ]; then
echo "[$SELF] agy is not installed on this machine (identity.json agy.installed=false)." >&2
echo "[$SELF] Antigravity CLI runs only on the fleet host. Route this request there, or install agy + set identity.json agy.installed=true." >&2
exit 3
fi
# --- locate the agy binary: AGY env > identity.json agy.binary > PATH > known paths ---
AGY="${AGY:-}"
if [ -n "$AGY" ] && [ ! -x "$AGY" ] && ! command -v "$AGY" >/dev/null 2>&1; then
echo "[$SELF] AGY='$AGY' is not an executable agy binary." >&2; exit 127
fi
cand="$(idagy binary)"
[ -z "$AGY" ] && [ -n "$cand" ] && [ -x "$cand" ] && AGY="$cand"
if [ -z "$AGY" ]; then
if command -v agy >/dev/null 2>&1; then AGY="$(command -v agy)"; else
for c in "/c/Users/${USERNAME:-${USER:-x}}/AppData/Local/agy/bin/agy" \
"$HOME/AppData/Local/agy/bin/agy" "$LOCALAPPDATA/agy/bin/agy" \
"/usr/local/bin/agy" "$HOME/.local/bin/agy"; do
[ -n "$c" ] && [ -x "$c" ] && { AGY="$c"; break; }
done
fi
fi
[ -z "$AGY" ] && { echo "[$SELF] agy CLI not found (set identity.json agy.binary, AGY=, or install the Antigravity CLI)" >&2; exit 127; }
# Strong model for verify/review*/vision/search; text uses the CLI default (Flash).
STRONG_MODEL="${AGY_MODEL:-Gemini 3.1 Pro (High)}"
MODE="${1:-}"; shift 2>/dev/null || true
[ -z "$MODE" ] && { echo "usage: $SELF {text|verify|review|review-files|review-diff|image-analyze|search|raw} ..." >&2; exit 2; }
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PF="$TMP/prompt.txt"; OUT="$TMP/out.txt"; ERR="$TMP/err.txt"
REPO_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." 2>/dev/null && pwd)}"
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "agy" "$@" >/dev/null 2>&1 || true; }
TIMEOUT_CMD="timeout"
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
TIMEOUT_CMD="$(command -v gtimeout 2>/dev/null || echo timeout)"
fi
# run agy headless. $1 = hard timeout secs; remaining args = flags placed BEFORE -p.
# The prompt (from $PF) is always the LAST token, as the value of -p. agy has no JSON
# mode, so stdout is the answer (plain text); stderr kept separate for diagnostics.
LAST_FLAGS=()
run_agy() {
local to="$1"; shift
LAST_FLAGS=("$@")
"$TIMEOUT_CMD" "$to" "$AGY" "$@" --print-timeout "${to}s" -p "$(cat "$PF")" \
>"$OUT" 2>"$ERR" </dev/null || true
}
# agy occasionally returns an empty turn; retry a couple times with backoff, then fail.
emit_or_fail() {
local txt tries=0 max="${AGY_MAX_TRIES:-3}"
txt="$(sed -e 's/[[:space:]]*$//' "$OUT" | sed -e :a -e '/^\s*$/{$d;N;ba}')"
while [ -z "${txt//[[:space:]]/}" ] && [ "$tries" -lt $((max-1)) ] && [ ${#LAST_FLAGS[@]} -ge 0 ]; do
tries=$((tries+1))
echo "[$SELF] empty response - retry $tries/$((max-1)) (backoff ${tries}x3s)..." >&2
sleep $((tries*3))
run_agy 240 "${LAST_FLAGS[@]}"
txt="$(cat "$OUT")"
done
if [ -n "${txt//[[:space:]]/}" ]; then cat "$OUT"; return 0; fi
if grep -qiE 'not authenticated|please (log|sign).?in|auth(entication)? (failed|error|required)|token (has )?expired|unauthorized' "$ERR" 2>/dev/null; then
echo "[$SELF] agy auth error - run 'agy' interactively once to sign in, then retry." >&2
_logerr "agy auth/login failure" --context "mode=$MODE"
else
echo "[$SELF] no response from agy after $max attempts. stderr tail:" >&2
tail -3 "$ERR" >&2 2>/dev/null || true
_logerr "agy returned no response (empty after $max attempts)" --context "mode=$MODE err=$(tail -1 "$ERR" 2>/dev/null | tr -d '\n' | cut -c1-80)"
fi
exit 1
}
# Copy review/vision targets into an included workspace dir so agy's file tools can
# reach them regardless of location/spaces; --add-dir this dir.
INCLUDE_DIR="$TMP/inbox"
prep_includes() { mkdir -p "$INCLUDE_DIR"; }
NO_TOOLS='Do not use any tools; answer directly in text.'
NO_NARRATE='Do not narrate your tool use or steps; output only the final answer.'
case "$MODE" in
text|verify)
SRC=""
if [ "${1:-}" = "--prompt-file" ]; then
[ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
SRC="$(cat "$2")"
else
SRC="${1:-}"
fi
[ -z "$SRC" ] && { echo "usage: $SELF $MODE \"<prompt>\" | $SELF $MODE --prompt-file <path>" >&2; exit 2; }
if [ "$MODE" = "verify" ]; then
printf 'You are an adversarial reviewer giving an independent second opinion. Evaluate the following claim/finding/document: try hard to find any way it is WRONG, incomplete, unsupported, or overstated. Then give a clear VERDICT (correct / partly correct / incorrect) plus specific justification. %s\n\nContent:\n%s' "$NO_TOOLS" "$SRC" > "$PF"
run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
else
printf '%s\n\n%s' "$NO_TOOLS" "$SRC" > "$PF"
run_agy 240 --dangerously-skip-permissions
fi
emit_or_fail
;;
review|file)
[ -z "${1:-}" ] && { echo "usage: $SELF review <file-path> [instructions]" >&2; exit 2; }
target="$1"
instr="${2:-Give an independent, critical review of this file: accuracy, gaps/omissions, bugs, and concrete improvements. Be specific.}"
if [ -f "$target" ]; then resolved="$target"
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
else echo "[$SELF] file not found: $target" >&2; exit 2; fi
prep_includes
base="$(basename "$resolved")"; cp -f "$resolved" "$INCLUDE_DIR/$base"
tgt_win="$(winpath "$INCLUDE_DIR/$base")"; inc_win="$(winpath "$INCLUDE_DIR")"
printf 'Read the file at this absolute path, then perform the task and stop. Do not modify anything. %s\nPath: %s\n\nTask: %s' "$NO_NARRATE" "$tgt_win" "$instr" > "$PF"
run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
emit_or_fail
;;
review-files)
instr='Independently review these files together as a unit: correctness/bugs, gaps, cross-file consistency, and concrete improvements. Be specific and cite file:line.'
files=()
while [ $# -gt 0 ]; do
case "$1" in
-i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
*) files+=("$1"); shift ;;
esac
done
[ ${#files[@]} -eq 0 ] && { echo "usage: $SELF review-files [-i \"instructions\"] <file> [file ...]" >&2; exit 2; }
prep_includes
list=""; declare -A seen=()
for f in "${files[@]}"; do
if [ -f "$f" ]; then r="$f"
elif [ -f "$REPO_ROOT/$f" ]; then r="$REPO_ROOT/$f"
else echo "[$SELF] file not found: $f" >&2; exit 2; fi
base="$(basename "$r")"
if [ -n "${seen[$base]:-}" ]; then
n=1; while [ -e "$INCLUDE_DIR/${n}_${base}" ]; do n=$((n+1)); done; base="${n}_${base}"
fi
seen[$base]=1; cp -f "$r" "$INCLUDE_DIR/$base"
list+="- $(winpath "$INCLUDE_DIR/$base")
"
done
inc_win="$(winpath "$INCLUDE_DIR")"
printf 'Read EACH of these files (absolute paths), then perform the task across ALL of them and stop. Do not modify anything. %s\n\nFiles:\n%s\nTask: %s' "$NO_NARRATE" "$list" "$instr" > "$PF"
run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
emit_or_fail
;;
review-diff)
gdir="$REPO_ROOT"
instr='Review this git diff: correctness/bugs introduced, regressions, missing edge cases, and concrete fixes. Focus on the CHANGES. Be specific and cite file:line.'
ref=""; pathspec=()
while [ $# -gt 0 ]; do
case "$1" in
-C|--dir) gdir="${2:-}"; shift 2 2>/dev/null || shift ;;
-i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
--) shift; while [ $# -gt 0 ]; do pathspec+=("$1"); shift; done ;;
*) if [ -z "$ref" ]; then ref="$1"; else pathspec+=("$1"); fi; shift ;;
esac
done
[ -z "$ref" ] && { echo "usage: $SELF review-diff [-C <repo-dir>] [-i \"instr\"] <gitref> [-- <pathspec>]" >&2; exit 2; }
[ -d "$gdir" ] || { [ -d "$REPO_ROOT/$gdir" ] && gdir="$REPO_ROOT/$gdir"; }
git -C "$gdir" rev-parse --git-dir >/dev/null 2>&1 || { echo "[$SELF] not a git repo: $gdir" >&2; exit 2; }
if [ ${#pathspec[@]} -gt 0 ]; then
git -C "$gdir" diff "$ref" -- "${pathspec[@]}" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
else
git -C "$gdir" diff "$ref" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
fi
[ -s "$TMP/diff.txt" ] || { echo "[$SELF] empty/failed diff for '$ref' in $gdir: $(head -1 "$TMP/differr.txt" 2>/dev/null)" >&2; exit 1; }
gdir_win="$(winpath "$gdir")"
{ printf 'Review the following unified git diff. %s\n%s\nYou may read any changed file for full context (paths are relative to %s; strip a/ b/ prefixes). Do not modify anything.\n\n=== BEGIN DIFF ===\n' "$instr" "$NO_NARRATE" "$gdir_win"; cat "$TMP/diff.txt"; printf '\n=== END DIFF ===\n'; } > "$PF"
run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$gdir_win"
emit_or_fail
;;
image-analyze|image|vision)
[ -z "${1:-}" ] && { echo "usage: $SELF image-analyze <image-path> [\"question\"]" >&2; exit 2; }
target="$1"; question="${2:-Describe exactly what is in this image.}"
if [ -f "$target" ]; then resolved="$target"
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
else echo "[$SELF] image not found: $target" >&2; exit 2; fi
prep_includes
base="$(basename "$resolved")"; cp -f "$resolved" "$INCLUDE_DIR/$base"
img_win="$(winpath "$INCLUDE_DIR/$base")"; inc_win="$(winpath "$INCLUDE_DIR")"
printf 'Read the image at this absolute path and describe exactly what you see. Report only what is actually present; do not guess or invent content. Then stop. %s\nImage path: %s\n\nQuestion: %s' "$NO_NARRATE" "$img_win" "$question" > "$PF"
run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
emit_or_fail
;;
search|websearch)
SRC=""
if [ "${1:-}" = "--prompt-file" ]; then
[ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
SRC="$(cat "$2")"
else
SRC="${1:-}"
fi
[ -z "$SRC" ] && { echo "usage: $SELF search \"<query>\" | $SELF search --prompt-file <path>" >&2; exit 2; }
printf 'Search the web for current, live information answering the following, then stop. Answer concisely and ALWAYS include the source URLs you used (a Sources list of full URLs). Do not fabricate URLs. %s\n\nQuery: %s' "$NO_NARRATE" "$SRC" > "$PF"
run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
emit_or_fail
;;
raw)
"$AGY" "$@"
;;
*)
echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|image-analyze|search|raw)" >&2; exit 2 ;;
esac

View File

@@ -1,388 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ask-gemini.sh — Claude -> Google Gemini CLI router (independent second model). # ask-gemini.sh — DEPRECATED shim. The AGY skill now routes to the Antigravity CLI
# # (`agy`), not the old Google `gemini` npm CLI (which broke on this account with a
# Sibling of ask-grok.sh. Routes a task to the official Google Gemini CLI # Cloud-project eligibility error). This shim forwards to ask-agy.sh so any existing
# (`gemini`, npm global) for an independent, different-vendor second opinion, # references keep working. New callers should use ask-agy.sh directly.
# verification, or a Gemini code review. Headless, safe-by-default, JSON-parsed. DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
# exec bash "$DIR/ask-agy.sh" "$@"
# Auth is Google login (OAuth) — NO API key. Creds: ~/.gemini/oauth_creds.json.
# If a call fails with an auth error, run `gemini` interactively once and pick
# "Login with Google".
#
# Output contract (VERIFIED on GURU-5070, gemini 0.45.2):
# - Prefer JSON: `gemini -p ... -o json` -> {session_id, response, stats}.
# The answer text is `.response`. stdout may carry two cosmetic warning lines
# ("True color..." / "Ripgrep is not available...") before the JSON; we extract
# the object starting at the FIRST '{' to ignore them. stderr (429 backoff,
# warnings) is captured SEPARATELY and never fed to the JSON parser.
# - `--skip-trust` is REQUIRED headless (the CWD isn't a trusted folder).
# - stdin is always closed (</dev/null) so `-p` never hangs waiting on stdin.
#
# File reads (review*): Gemini's read_file honors .gitignore AND a workspace
# sandbox (only files under the workspace/included dirs are readable). To make
# review robust for ANY file (tracked, gitignored, with spaces), we copy each
# target into a temp dir and add it to the workspace via --include-directories.
# review-diff runs with the repo dir included so changed files read in place.
#
# Usage:
# ask-gemini.sh text "<prompt>" # one-shot answer
# ask-gemini.sh text --prompt-file <path> # long content
# ask-gemini.sh verify "<claim or finding to refute>" # adversarial check
# ask-gemini.sh verify --prompt-file <path>
# ask-gemini.sh review <file> [instructions] # gemini reads + reviews one file
# ask-gemini.sh review-files [-i "instr"] <f1> [f2 ...] # review a SET of files together
# ask-gemini.sh review-diff [-C <repo-dir>] [-i "instr"] <gitref> [-- <pathspec>]
# ask-gemini.sh image-analyze <image-path> ["question"] # vision: read_file image + describe (PRO model)
# ask-gemini.sh search "<query>" # Google-grounded live web search + sources
# ask-gemini.sh raw <gemini args...> # escape hatch
#
# Exit: 0 ok, 1 no result, 2 usage, 3 not installed here, 127 gemini/python not found.
set -uo pipefail
SELF="ask-gemini"
PY="$(command -v py 2>/dev/null || command -v python 2>/dev/null || command -v python3 2>/dev/null || true)"
[ -z "$PY" ] && { echo "[$SELF] python (py/python/python3) required for JSON parsing" >&2; exit 127; }
# --- path conversion: native-Windows path for the gemini args (no-op off Windows) ---
# gemini is a native Windows binary (npm shim -> node.exe); Git Bash hands it POSIX
# paths (/tmp, /c/.., /d/..) it cannot resolve. cygpath -w converts to C:\... on
# MSYS/Cygwin; on Linux/macOS it passes through unchanged. Explicit conversion
# removes reliance on MSYS auto-conversion (which breaks on spaces/edge cases).
if command -v cygpath >/dev/null 2>&1; then
winpath() { cygpath -w -- "$1" 2>/dev/null || printf '%s' "$1"; }
else
winpath() { printf '%s' "$1"; }
fi
# --- identity.json (per-machine, gitignored) declares whether gemini is installed here ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
IDFILE=""
[ -n "${CLAUDETOOLS_ROOT:-}" ] && [ -f "$CLAUDETOOLS_ROOT/.claude/identity.json" ] && IDFILE="$CLAUDETOOLS_ROOT/.claude/identity.json"
[ -z "$IDFILE" ] && IDFILE="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/identity.json"
idgem() { # read field $1 from identity.json .gemini (empty if absent)
[ -f "$IDFILE" ] || { echo ""; return; }
"$PY" -c "import json,sys
try:
g=(json.load(sys.stdin).get('gemini') or {}); v=g.get('$1','')
print('' if v is None else (str(v).lower() if isinstance(v,bool) else v))
except Exception: print('')" < "$IDFILE"
}
# If identity explicitly says gemini is NOT installed here, fail fast with guidance.
if [ "$(idgem installed)" = "false" ]; then
echo "[$SELF] gemini is not installed on this machine (identity.json gemini.installed=false)." >&2
echo "[$SELF] Gemini runs only on the fleet host. Route this request there, or install the gemini CLI (npm i -g @google/gemini-cli) + set identity.json gemini.installed=true." >&2
exit 3
fi
# --- locate the gemini binary: GEMINI env > identity.json gemini.binary > auto-locate ---
# An explicit GEMINI= override that isn't runnable is a user error -> fail clearly up front
# (covers absolute paths AND a bare name resolvable on PATH, e.g. GEMINI=gemini).
GEMINI="${GEMINI:-}"
if [ -n "$GEMINI" ] && [ ! -x "$GEMINI" ] && ! command -v "$GEMINI" >/dev/null 2>&1; then
echo "[$SELF] GEMINI='$GEMINI' is not an executable gemini binary." >&2; exit 127
fi
cand="$(idgem binary)"
[ -z "$GEMINI" ] && [ -n "$cand" ] && [ -x "$cand" ] && GEMINI="$cand"
if [ -z "$GEMINI" ]; then
if command -v gemini >/dev/null 2>&1; then GEMINI="$(command -v gemini)"; else
for c in "${APPDATA:-}/npm/gemini" "/c/Users/${USERNAME:-${USER:-x}}/AppData/Roaming/npm/gemini" \
"$HOME/AppData/Roaming/npm/gemini" "/usr/local/bin/gemini" "$HOME/.npm-global/bin/gemini"; do
[ -n "$c" ] && [ -x "$c" ] && { GEMINI="$c"; break; }
done
fi
fi
[ -z "$GEMINI" ] && { echo "[$SELF] gemini CLI not found (set identity.json gemini.binary, GEMINI=, or install: npm i -g @google/gemini-cli)" >&2; exit 127; }
# Model: default routing for text; a strong pinned model for verify/review.
# gemini-3.1-pro-preview verified available on this account (2026-06-05); overridable.
STRONG_MODEL="${GEMINI_MODEL:-gemini-3.1-pro-preview}"
MODE="${1:-}"; shift 2>/dev/null || true
[ -z "$MODE" ] && { echo "usage: $SELF {text|verify|review|review-files|review-diff|image-analyze|search|raw} ..." >&2; exit 2; }
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PF="$TMP/prompt.txt"; OUT="$TMP/out.txt"; ERR="$TMP/err.txt"
REPO_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." 2>/dev/null && pwd)}"
# Functional-error logger (skill name "agy"); soft-fails, never breaks the caller.
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "agy" "$@" >/dev/null 2>&1 || true; }
# gtimeout on macOS (brew coreutils), timeout elsewhere.
TIMEOUT_CMD="timeout"
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
TIMEOUT_CMD="$(command -v gtimeout 2>/dev/null || echo timeout)"
fi
# run gemini headless reading the prompt file. $1=timeout secs; rest=extra flags.
# stdout -> $OUT, stderr -> $ERR (kept separate so warning/429 noise never reaches
# the JSON parser). Never fail the script on gemini's exit code; we judge by output.
# Records the invocation so emit_or_fail can replay it once on a transient empty turn.
LAST_RUN=()
run_gemini() {
local to="$1"; shift
LAST_RUN=("$to" "$@")
"$TIMEOUT_CMD" "$to" "$GEMINI" -p "$(cat "$PF")" -o json --skip-trust "$@" \
>"$OUT" 2>"$ERR" </dev/null || true
}
# extract .response from the JSON object starting at the first '{' in $OUT.
# Parsed via stdin so Windows python never resolves a git-bash (/c/...) path.
#
# Some pinned-pro tool-using turns (notably image-analyze) leak the model's
# internal reasoning stream into .response: a stray token + a 'thought' marker
# followed by 'CRITICAL INSTRUCTION N:' lines, then the real answer. We strip
# that preamble ONLY when the signature is clearly present, so clean responses
# (text/verify/review/search) pass through byte-for-byte unchanged.
gresponse() { "$PY" -c "import json,sys,re,os
raw=sys.stdin.read()
i=raw.find('{')
if i < 0:
print(''); sys.exit(0)
try:
r=json.loads(raw[i:]).get('response','') or ''
except Exception:
print(''); sys.exit(0)
head=r[:40].lower()
leak=('thought' in head) or ('critical instruction' in r.lower()[:600])
if leak:
lines=r.split('\n')
keep=[]; dropping=True
for ln in lines:
s=ln.strip()
low=s.lower()
if dropping and (
low.endswith('thought') or low.startswith('critical instruction')
or low.startswith('thought:') or low=='' ):
continue
dropping=False
keep.append(ln)
cleaned='\n'.join(keep).strip()
r=cleaned if cleaned else r.strip()
# AGY_CLEAN: aggressive prefix scrub for tool-using turns (image-analyze), which
# can fuse a stray stream/tool token onto the front of the answer (e.g. '.',
# '.94>', 'uem_image_0_0_png}'). Off by default so text/verify/review/search are
# byte-exact. We only remove a junk run that ends in a stream delimiter (} > :)
# or a lone leading punctuation char, immediately before the first real sentence.
if os.environ.get('AGY_CLEAN') == '1' and r:
# The pro-preview tool loop sometimes prepends a numbered/markdown reasoning
# block before the actual answer. If a clear answer pivot follows such a
# preamble, keep from the pivot onward (the user-facing answer).
if re.search(r'(?im)^\s*\d+[.)]\s', r) or 'thought' in r[:60].lower():
pivs=list(re.finditer(r'(?i)(Based on the image\b|\*\*Answer:?\*\*|The image (?:contains|shows|displays)\b)', r))
if pivs:
r=r[pivs[-1].start():]
m=re.match(r'^[^\n]{0,40}?(?:\.png\)|\.jpe?g\)|[}>:)])\s*([\"A-Z].*)$', r, re.S)
if m and m.group(1):
r=m.group(1)
else:
# a short leading junk run (ASCII punctuation/digits or non-Latin stream
# tokens) before a capitalized/quoted sentence start. Bounded length so we
# never eat a real lowercase sentence or real prose.
m=re.match(r'^(?:[^A-Za-z\"]|[^\x00-\x7f]){1,8}([A-Z\"].*)$', r, re.S)
if m and m.group(1):
r=m.group(1)
r=r.strip()
print(r)" < "$OUT"; }
# detect a GENUINE auth failure in stderr (precise remediation hint). Tightened 2026-06-17 -
# the old broad regex (bare login|credential|authenticat|oauth|401) matched benign mid-run
# token-refresh lines and false-flagged working sessions as auth failures.
auth_failed() { grep -qiE 'invalid_grant|unauthorized|not authenticated|authentication failed|re-?authenticat|please (log|sign).?in|login with google|token (has )?expired|no (valid )?credentials' "$ERR" 2>/dev/null; }
# detect a quota / rate-capacity exhaustion (the pinned strong model can be capped mid-session)
quota_exhausted() { grep -qiE 'exhausted your capacity|quota|resource[_ ]?exhausted|rate limit|too many requests|429' "$ERR" 2>/dev/null; }
emit_or_fail() { # print .response; gemini intermittently returns an empty turn, so retry a few
# times with backoff before giving up (single retry was insufficient - 2 empties
# in a row caused spurious failures during live research, 2026-06-17).
# Do ALL retries first; only classify the failure (auth vs generic) AFTER exhausting them.
# (Checking auth_failed INSIDE the loop caused false aborts: a benign mid-run credential-refresh
# line in stderr matched the auth regex and killed the retries even though auth was fine. 2026-06-17.)
local txt tries=0 max="${AGY_MAX_TRIES:-3}"
txt="$(gresponse)"
while [ -z "$txt" ] && [ "$tries" -lt $((max-1)) ] && [ ${#LAST_RUN[@]} -gt 0 ]; do
tries=$((tries+1))
echo "[$SELF] empty response - retry $tries/$((max-1)) (backoff ${tries}x3s)..." >&2
sleep $((tries*3)) # 3s, 6s backoff (covers transient empties / 429s / token refresh)
run_gemini "${LAST_RUN[@]}"
txt="$(gresponse)"
done
if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi
# Quota fallback: if the pinned strong model is capacity/quota-capped, retry ONCE on the default
# (lighter) model by stripping -m from the last invocation - the default model has a separate quota.
if quota_exhausted && [ ${#LAST_RUN[@]} -gt 0 ]; then
echo "[$SELF] '$STRONG_MODEL' quota exhausted - retrying once on the default (lighter) model..." >&2
local nr=() a skip=0
for a in "${LAST_RUN[@]}"; do
if [ "$skip" = 1 ]; then skip=0; continue; fi
if [ "$a" = "-m" ]; then skip=1; continue; fi
nr+=("$a")
done
run_gemini "${nr[@]}"
txt="$(gresponse)"
if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi
fi
if auth_failed; then
echo "[$SELF] Gemini auth error - run 'gemini' interactively and choose 'Login with Google', then retry." >&2
_logerr "gemini auth/login failure" --context "mode=$MODE"
else
echo "[$SELF] no response from gemini after $max attempts. stderr tail:" >&2
tail -3 "$ERR" >&2 2>/dev/null || true
_logerr "gemini returned no response (empty after $max attempts)" --context "mode=$MODE err=$(tail -1 "$ERR" 2>/dev/null | tr -d '\n' | cut -c1-80)"
fi
exit 1
}
# Copy target files into an included temp workspace dir so gemini's read_file can
# reach them regardless of .gitignore / workspace sandbox. Echoes the included dir.
INCLUDE_DIR="$TMP/inbox"
prep_includes() { mkdir -p "$INCLUDE_DIR"; }
case "$MODE" in
text|verify)
SRC=""
if [ "${1:-}" = "--prompt-file" ]; then
[ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
SRC="$(cat "$2")"
else
SRC="${1:-}"
fi
[ -z "$SRC" ] && { echo "usage: $SELF $MODE \"<prompt>\" | $SELF $MODE --prompt-file <path>" >&2; exit 2; }
if [ "$MODE" = "verify" ]; then
printf 'You are an adversarial reviewer giving an independent second opinion. Evaluate the following claim/finding/document: try hard to find any way it is WRONG, incomplete, unsupported, or overstated. Then give a clear VERDICT (e.g. correct / partly correct / incorrect) plus specific justification. Answer in text only; do not use any tools.\n\nContent:\n%s' "$SRC" > "$PF"
run_gemini 180 -m "$STRONG_MODEL"
else
printf 'Answer the following directly in text. Do not use any tools.\n\n%s' "$SRC" > "$PF"
run_gemini 180
fi
emit_or_fail
;;
review|file)
[ -z "${1:-}" ] && { echo "usage: $SELF review <file-path> [instructions]" >&2; exit 2; }
target="$1"
instr="${2:-Give an independent, critical review of this file: accuracy, gaps/omissions, bugs, and concrete improvements. Be specific.}"
# GOTCHA: a relative path resolves against ONLY CWD or $REPO_ROOT ($CLAUDETOOLS_ROOT) --
# NOT a submodule/subdir. "server/src/x.rs" relative to a submodule fails ("file not found")
# unless CWD is that submodule. Pass ABSOLUTE paths for submodule/subtree files.
if [ -f "$target" ]; then resolved="$target"
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
else echo "[$SELF] file not found: $target" >&2; exit 2; fi
prep_includes
base="$(basename "$resolved")"
cp -f "$resolved" "$INCLUDE_DIR/$base"
tgt_win="$(winpath "$INCLUDE_DIR/$base")"
inc_win="$(winpath "$INCLUDE_DIR")"
printf 'Use your read_file tool to read the file at this absolute path, then perform the task and stop. Do not modify anything.\nPath: %s\n\nTask: %s' "$tgt_win" "$instr" > "$PF"
run_gemini 240 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$inc_win"
emit_or_fail
;;
review-files)
instr='Independently review these files together as a unit: correctness/bugs, gaps, cross-file consistency, and concrete improvements. Be specific and cite file:line.'
files=()
while [ $# -gt 0 ]; do
case "$1" in
-i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
*) files+=("$1"); shift ;;
esac
done
[ ${#files[@]} -eq 0 ] && { echo "usage: $SELF review-files [-i \"instructions\"] <file> [file ...]" >&2; exit 2; }
prep_includes
list=""
declare -A seen=()
# GOTCHA: each relative path resolves against ONLY CWD or $REPO_ROOT ($CLAUDETOOLS_ROOT) --
# NOT a submodule/subdir. Paths relative to a submodule fail unless CWD is that submodule.
# Pass ABSOLUTE paths for submodule/subtree files (e.g. build the list with `find "$(pwd)/..."`).
for f in "${files[@]}"; do
if [ -f "$f" ]; then r="$f"
elif [ -f "$REPO_ROOT/$f" ]; then r="$REPO_ROOT/$f"
else echo "[$SELF] file not found: $f" >&2; exit 2; fi
base="$(basename "$r")"
# de-collide identical basenames from different dirs
if [ -n "${seen[$base]:-}" ]; then
n=1; while [ -e "$INCLUDE_DIR/${n}_${base}" ]; do n=$((n+1)); done; base="${n}_${base}"
fi
seen[$base]=1
cp -f "$r" "$INCLUDE_DIR/$base"
list+="- $(winpath "$INCLUDE_DIR/$base")
"
done
inc_win="$(winpath "$INCLUDE_DIR")"
printf 'Use your read_file tool to read EACH of these files (absolute paths), then perform the task across ALL of them and stop. Do not modify anything.\n\nFiles:\n%s\nTask: %s' "$list" "$instr" > "$PF"
run_gemini 300 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$inc_win"
emit_or_fail
;;
review-diff)
gdir="$REPO_ROOT"
instr='Review this git diff: correctness/bugs introduced, regressions, missing edge cases, and concrete fixes. Focus on the CHANGES. Be specific and cite file:line.'
ref=""; pathspec=()
while [ $# -gt 0 ]; do
case "$1" in
-C|--dir) gdir="${2:-}"; shift 2 2>/dev/null || shift ;;
-i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
--) shift; while [ $# -gt 0 ]; do pathspec+=("$1"); shift; done ;;
*) if [ -z "$ref" ]; then ref="$1"; else pathspec+=("$1"); fi; shift ;;
esac
done
[ -z "$ref" ] && { echo "usage: $SELF review-diff [-C <repo-dir>] [-i \"instr\"] <gitref> [-- <pathspec>]" >&2; exit 2; }
[ -d "$gdir" ] || { [ -d "$REPO_ROOT/$gdir" ] && gdir="$REPO_ROOT/$gdir"; }
git -C "$gdir" rev-parse --git-dir >/dev/null 2>&1 || { echo "[$SELF] not a git repo: $gdir" >&2; exit 2; }
if [ ${#pathspec[@]} -gt 0 ]; then
git -C "$gdir" diff "$ref" -- "${pathspec[@]}" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
else
git -C "$gdir" diff "$ref" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
fi
[ -s "$TMP/diff.txt" ] || { echo "[$SELF] empty/failed diff for '$ref' in $gdir: $(head -1 "$TMP/differr.txt" 2>/dev/null)" >&2; exit 1; }
gdir_win="$(winpath "$gdir")"
{ printf 'Review the following unified git diff. %s\nYou may use your read_file tool on any changed file for full context (paths in the diff are relative to %s; strip the a/ b/ prefixes). Do not modify anything.\n\n=== BEGIN DIFF ===\n' "$instr" "$gdir_win"; cat "$TMP/diff.txt"; printf '\n=== END DIFF ===\n'; } > "$PF"
run_gemini 300 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$gdir_win"
emit_or_fail
;;
image-analyze|image|vision)
# Independent second-model VISION. The default flash-lite router hallucinates
# image content, so we PIN the pro vision model (STRONG_MODEL) and run with
# yolo approval so read_file can execute. The image is copied into an included
# temp dir (like the review modes) and handed to Gemini by absolute winpath.
[ -z "${1:-}" ] && { echo "usage: $SELF image-analyze <image-path> [\"question\"]" >&2; exit 2; }
target="$1"
question="${2:-Describe exactly what is in this image.}"
if [ -f "$target" ]; then resolved="$target"
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
else echo "[$SELF] image not found: $target" >&2; exit 2; fi
prep_includes
base="$(basename "$resolved")"
cp -f "$resolved" "$INCLUDE_DIR/$base"
img_win="$(winpath "$INCLUDE_DIR/$base")"
inc_win="$(winpath "$INCLUDE_DIR")"
# Image path goes in via %s (never as a printf format string).
printf 'Use your read_file tool to read the image at this absolute path, then describe exactly what you see. Report only what is actually present in the image; do not guess or invent content. Then stop. Do not modify anything.\nImage path: %s\n\nQuestion: %s' "$img_win" "$question" > "$PF"
run_gemini 240 -m "$STRONG_MODEL" --approval-mode yolo --include-directories "$inc_win"
AGY_CLEAN=1 emit_or_fail
;;
search|websearch)
# Google-grounded LIVE web search (mirrors grok xsearch). Gemini's
# google_web_search tool works on OAuth; run with yolo so the tool can fire.
# Query goes via the prompt file so long queries don't hit shell-quote limits.
SRC=""
if [ "${1:-}" = "--prompt-file" ]; then
[ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
SRC="$(cat "$2")"
else
SRC="${1:-}"
fi
[ -z "$SRC" ] && { echo "usage: $SELF search \"<query>\" | $SELF search --prompt-file <path>" >&2; exit 2; }
printf 'Use your google_web_search tool to find current, live information answering the following, then stop. Answer concisely and ALWAYS include the source URLs you used (a Sources list of full URLs). Do not fabricate URLs.\n\nQuery: %s' "$SRC" > "$PF"
run_gemini 180 -m "$STRONG_MODEL" --approval-mode yolo
emit_or_fail
;;
raw)
"$GEMINI" "$@"
;;
*)
echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|image-analyze|search|raw)" >&2; exit 2 ;;
esac

View File

@@ -1,16 +1,6 @@
--- ---
name: alis name: alis
description: >- description: "Build the ALIS (Medtelligent) staff bulk-import .xls (13-column template) for Cascades of Tucson (communityId 622) and read the live staff roster via the read-only API. Triggers: alis, alisonline, medtelligent, alis staff import, add staff to alis, alis security roles."
Build the ALIS (Medtelligent assisted-living EHR) staff bulk-import spreadsheet so new
staff/logins can be created in the ALIS web UI, and read the live ALIS staff roster via
the API as the setup reference. Maps a CSV/JSON of new hires onto Medtelligent's exact
13-column import template, validates Status/Login/Gender against the dropdowns, and infers
each new hire's Security Roles from how existing staff of the same Job Role are configured
(a job-role -> security-role map learned from live data). The ALIS API is READ-ONLY for
staff (no write endpoint exists) - changes happen by uploading the generated .xls. Tenant:
Cascades of Tucson (communityId 622). Triggers: alis, alisonline, medtelligent, import
staff/users into alis, alis staff import, add staff to alis, build the alis import file,
alis staff roster, alis security roles.
--- ---
# ALIS Skill (Medtelligent) — staff import builder + roster reference # ALIS Skill (Medtelligent) — staff import builder + roster reference

View File

@@ -0,0 +1,128 @@
---
name: ask-forum
description: "Ask a teammate (Mike/Winter/Howard/anyone with forum access) a question in the private #ct-forum Discord forum and get their human answer back IN THIS SAME SESSION, then act on it - a live three-way between the user, this Claude session, and the teammate. No second bot, no DMs, one tool call (the wait runs server-side in bash, zero tokens while waiting). Triggers: ask Mike/Winter in the forum, ask the team, post a question to ct-forum, get a human answer/decision/sign-off, human-in-the-loop, run something by <person>, wait for their reply."
---
# ask-forum — human-in-the-loop questions via #ct-forum
Thin wrapper over `.claude/scripts/ask-forum.sh`. Lets THIS session put a question to a
human in the private **#ct-forum** Discord forum and receive their answer back in-session,
so it can keep going. Use it whenever you need a person's answer, decision, or sign-off
mid-task and want it to flow back to the running session — instead of stopping and asking
the user to relay it.
**USE THIS SKILL — do not hand-roll `curl` against the Discord API or the bot token.**
Free-handing the raw call is exactly what produced the `&`-on-background bug (a wait that
exited instantly and never delivered the answer). The script + this contract encode the
correct thread-create, the non-bot reply filter, and the background-wait discipline. Reach
for raw API only if this skill genuinely can't do what's needed — and say so.
## When to use
- You need a teammate's **answer / decision / approval** and want it back in this session
(e.g. "ask Mike whether to deploy", "run this naming by Winter", "get a go/no-go").
- A back-and-forth where the human's reply should drive the session's next action.
Not for: one-way copy-paste delivery of a link/command (that's `discord-dm``mike`), or
routine `[SYNCRO]`/`[RMM]` status alerts (`post-bot-alert.sh`).
## Usage
```bash
# Ask + BLOCK until a human replies, then act on the answer:
bash .claude/scripts/ask-forum.sh "your question" --tag @264814939619721216
# Re-read a thread you already asked in (one-shot, no waiting):
bash .claude/scripts/ask-forum.sh --read <thread_id>
# Resume blocking on an already-posted thread (e.g. after a timeout):
bash .claude/scripts/ask-forum.sh --wait <thread_id> --timeout 1800 --poll 10
# add --after <msg_id> to wait for replies STRICTLY AFTER a message you already
# consumed (default baseline catches any reply since the thread's starter).
```
## Robustness (hardened after the 2026-07-08 review)
- **Pages past the 100-message window:** the poll advances its `after` cursor each
cycle, so a reply is found even behind many bot/overflow messages (no false timeout).
- **Rate-limit aware:** a `429` is honored (sleeps `retry_after`); a permanent Discord
error (Unknown Channel 10003, Missing Access 50001, etc.) **bails immediately** instead
of burning the full timeout; only genuine transients keep polling.
- **Long questions** (>1900 chars) chunk into the starter + follow-up messages, and each
overflow POST is status-checked (warns if one drops, so the question isn't silently
truncated).
- **`--wait` baseline** is the thread's starter id (starter id == thread id for a forum
post), so a reply that arrived *before* the wait started is caught, not missed. Because
of this, `--wait` on a thread that already holds a consumed answer re-returns it — pass
`--after <msg_id>` when you only want the NEXT reply.
- Residual caveat: content slicing assumes a UTF-8 locale (the script sets
`LC_ALL=C.UTF-8`); a forced byte-counting `LC_ALL=C` could split a multibyte char at the
1900-char cut. Not an issue under the fleet default locale.
Flags: `--title "short title"` (forum post name; defaults to first 60 chars of the
question) · `--timeout SEC` (default 600) · `--poll SEC` (default 8) · `--tag @<id>`
(repeatable — pings that person). Discord IDs: mike `264814939619721216`,
howard `624667664501178379`, winter `624666486362996755`, rob `261978810713505792`
(also in `.claude/users.json`).
## HARD RULE — long waits run as a proper background task (no shell `&`)
A blocking `--wait`/ask can sit for many minutes. Run it with the tool's
**`run_in_background: true`** and **nothing else** — do NOT append a shell `&`, `disown`,
or `nohup`. Adding `&` forks the wait off and the shell exits 0 immediately, orphaning the
poll loop so the answer is never delivered (logged friction, twice). Done right, the wait
costs **zero tokens** while pending and the harness notifies you the instant a human
replies — then you surface the answer to the user **unprompted**. See
[[feedback_background_task_no_ampersand]].
## How it works / correlation
- Posts a forum post via `POST /channels/<forum>/threads`, returns a **`thread_id`**.
- The session reads only **its own** `thread_id`, so an answer can never be confused with
another question's. Replies route to whoever asked — Mike's thread → Mike's session,
yours → yours, no crossover.
- **Any non-bot reply counts as the answer** (first human wins). Anyone with #ct-forum
access can answer; the script never filters by person. Bot chatter is filtered out.
## Model & scope (decided with Mike, 2026-07-08)
- **Forum-only.** No DM replies — answers stay team-visible.
- **Good-enough durability:** answers are captured while the session is open; Discord keeps
the history, so any thread is cheap to re-read (`--read`) later in the same session. It is
NOT restart-safe (close the session → nothing is watching); that was an accepted tradeoff
for the cheapest option. Full restart/close-safe capture would be the coord-backed
registry or the event-driven bot-router — build only if asked.
- The **BEAST bot ignores #ct-forum** (patch in `projects/discord-bot/bot/main.py`) so it
doesn't auto-answer and collide with the asking session.
## Access & permissions
- **Channel:** #ct-forum (`1522960388432465950`), private — `@everyone` View DENY; Howard +
the bot explicitly allowed; Mike sees it as owner. To let someone else answer, they need
**View Channel** on #ct-forum (grant a role like Techs, or the person). Winter/Rob are not
in by default.
### Bot capability map — the limit of control (tested 2026-07-08)
What the bot can do in #ct-forum with its current permissions, and where the wall is:
| Bot CAN (no grant needed) | Bot CANNOT (needs a permission grant) |
|---|---|
| Read messages / thread / members / guild channels | **Pin** a message → 403 (needs *Manage Messages*) |
| Create a forum post (thread), post follow-up messages | **Delete** a thread → 403 (needs *Manage Threads*) |
| Edit its OWN messages; add/remove reactions; typing | **Archive** a thread → 403 (needs *Manage Threads*) |
| Rename / lock a thread it owns (see trap below) | **Unlock** a thread once locked → 403 (needs *Manage Threads*) |
- **One-way trap:** as thread owner the bot can *lock* a thread (200) but then **cannot
unlock or rename it** (403) — locking without *Manage Threads* is irreversible. So the
skill does NOT lock/rename threads; treat thread lifecycle as off-limits until granted.
- **To enable cleanup:** owner grants the ClaudeTools bot **Manage Threads** on #ct-forum
(Edit Channel → Permissions → bot → Manage Threads → Allow). Then delete/archive/unlock
work. **Manage Messages** additionally enables pin.
- Reactions + own-message edits DO work with no grant — usable for lightweight
acknowledgement (react to a reply, or edit the question to "[answered]") if ever wanted.
## Exit codes
`0` answered · `1` usage · `2` no token · `3` Discord API error · `4` timeout (thread stays
open — resume with `--wait <thread_id>`).
## Implementation notes
- Bot token resolves from the SOPS vault
(`projects/discord-bot/bot-token.sops.yaml` field `credentials.bot_token`), falling back
to `projects/discord-bot/.env` `DISCORD_TOKEN` — works from any machine.
- Engine: `.claude/scripts/ask-forum.sh`. Command entry point: `/ask-forum`. Related:
[[discord-dm]] (person-targeted DMs / channel posts).

View File

@@ -1,12 +1,6 @@
--- ---
name: b2 name: b2
description: >- description: "Manage ACG's live Backblaze B2 account (per-client MSP360 backup destinations): buckets/keys/files, per-bucket size, storage-cost report; provisioning/deletes gated --confirm. Triggers: backblaze, b2, bucket, storage cost, backup storage, mspbackups storage."
Manage ACG's Backblaze B2 storage account (Native API v3) — the LIVE production
account (accountId 46f69bc61163, us-west-001) holding per-client MSP360/CloudBerry
backup destinations. List buckets/keys/files, compute per-bucket size, run the
headline storage-cost report (mspbackups calc); provision/delete buckets and scoped
keys (destructive ops gated behind --confirm). Read-only by default. Triggers:
backblaze, b2, b2 storage, bucket, storage cost, backup storage, mspbackups storage.
--- ---
# Backblaze B2 Skill # Backblaze B2 Skill

View File

@@ -1,15 +1,6 @@
--- ---
name: bitdefender name: bitdefender
description: >- description: "Manage the ACG Bitdefender GravityZone Cloud MSP tenant (JSON-RPC API): endpoint inventory/audit, security sweeps, packages, policies, quarantine, EDR isolate/blocklist; destructive ops gated. Triggers: bitdefender, gravityzone, infected machines, av coverage, assign policy."
Manage the ACG Bitdefender GravityZone Cloud MSP tenant (Public JSON-RPC API):
inventory/audit endpoints, live security sweeps (infected / outdated-signature /
outdated-product), client companies, install packages, custom groups, scans,
move/delete endpoints (gated), policies (full read + assign), reports, accounts,
scan tasks, notifications, push event service, quarantine, EDR (isolate /
blocklist). Live production partner tenant — treat destructive actions
conservatively. Triggers: bitdefender, gravityzone, install bitdefender on, list
endpoints, infected machines, av coverage, security sweep, endpoint protection,
assign policy, quarantine, reports, accounts.
--- ---
# Bitdefender GravityZone Skill # Bitdefender GravityZone Skill

View File

@@ -1,12 +1,6 @@
--- ---
name: coord name: coord
description: > description: "Talk to the ClaudeTools coordination API without re-deriving the schema: send/read inter-session messages or fleet broadcasts, coord todos, claim/release locks, coord status. Triggers: send a coord message, broadcast to the fleet, coord todo, claim a lock, coord status."
Talk to the ClaudeTools coordination API (inter-session messaging, fleet todos,
resource locks, component/status) without re-deriving the schema. Send/read messages
to another machine's session or BROADCAST to the fleet; create/list/complete coord
todos; claim/release work locks; read coord status. Triggers: send a coord message,
message <machine>/<user>, broadcast to the fleet, coord todo, claim a lock,
coord status, any unread coord messages.
--- ---
# coord — coordination API helper # coord — coordination API helper

View File

@@ -1,15 +1,6 @@
--- ---
name: datto-edr name: datto-edr
description: >- description: "Control the ACG Datto EDR / Datto AV tenant (azcomp4587): fleet orgs/sites/agents, MITRE-tagged detections, per-client security sweeps; gated scans, host isolation, agent deploy. Read-only default. Triggers: datto edr, datto av, infocyte, EDR detections, isolate endpoint."
Control the ACG Datto EDR / Datto AV tenant (azcomp4587 — Datto EDR is rebranded
Infocyte HUNT; per-tenant LoopBack REST API). Read the whole MSP fleet from ONE
token: per-client organizations, sites, agents (online/AV/isolation/version),
detections (MITRE-tagged alerts), and a per-client security-posture sweep. Gated
actions: trigger scans, invoke response extensions (host isolation), and emit the
agent install one-liner for RMM-pushed deployment. Read-only by default; mutating
ops require --confirm. Triggers: datto edr, datto av, infocyte, EDR detections,
isolate endpoint, run a scan, deploy edr agent, edr security sweep, endpoint
detection response, azcomp4587.
--- ---
# Datto EDR (Infocyte HUNT) Skill # Datto EDR (Infocyte HUNT) Skill
@@ -139,6 +130,16 @@ erroring `a value is required for '--url'`. Push via **GuruRMM** (`/rmm`) or any
remote-exec channel. `RegKey` auto-approves + adds to the key's target group; without remote-exec channel. `RegKey` auto-approves + adds to the key's target group; without
it the agent registers but awaits manual approval. it the agent registers but awaits manual approval.
**Offline endpoint whose GuruRMM agent is down but ScreenConnect is up:** the one-liner
is a single self-contained command, so queue it via the `screenconnect` skill's
`send-command --session <id> --command "powershell -NoProfile -ExecutionPolicy Bypass
-Command \"...Install-EDR...\"" --confirm` — SC holds it in the session's one command slot
and runs it on reconnect (verify by `agents --org` / `sweep` afterward; SC returns no
output). Used 2026-07-08 to stage REPAIRADMIN (IMC) while it was offline overnight.
**AV-swap ordering:** when replacing another AV (e.g. Bitdefender), install + verify EDR
live FIRST, then remove the old AV — never leave an unprotected window. Note Bitdefender's
API has NO uninstall (console-only); pull it via its local uninstall tool over `/rmm`.
## Safety gating ## Safety gating
- Reads never mutate and run without confirmation. - Reads never mutate and run without confirmation.

View File

@@ -108,6 +108,57 @@ installed, hook, network, events`, plus `extensions:[{id,args,order}]`.
the extension id via `GET /Extensions` (e.g. `Host Isolation [Win/Linux]`) and test on the extension id via `GET /Extensions` (e.g. `Host Isolation [Win/Linux]`) and test on
an ACG-internal box first. an ACG-internal box first.
## Alert Suppression Rules (verified live 2026-07-07)
Suppress a false-positive detection so it stops firing (and stops triggering any
attached auto-response like `isolate-host`). LoopBack models are API-reachable:
| Op | Method/path |
|---|---|
| List rules | `GET /SuppressionRules` (`[id, alertId, name, versionCount, description, organizationId, locationId, active, deleted]`) |
| Count | `GET /SuppressionRules/count` |
| Match criteria (per rule) | `GET /SuppressionRules/{id}/versions` → `[{id, suppressionRuleId, name, description, metadata:{...}}]` |
**`metadata`** is the full match-field map; each key is `{value, active, display, dataType,
operator}`. A field participates in the match ONLY when `active:true`; all active fields are
**AND**-ed. Available fields (`display`): Alert Type, Item Type, Organization Name, Location
Name, Hostname, IP Address, Name, File Path, File SHA1, File SHA256, File Signature Issuer,
**Process Command Line**, AV Threat Name, Operating System, Threat Status, Severity, Rule
Name, EPP Type, Threat Category, Process Owner, Process Owner UID, AV Hits, Parent Process
Name, Grand Parent Process Name.
**Scoping guidance (learned from the vwp-qbs RMM false-positive incident):** the tightest
safe suppression matches on **Process Command Line** (a unique fingerprint of the exact
script) plus **Grand Parent Process Name** (the launching agent, e.g. `gururmm-agent.exe`).
That trusts one exact known-good automation without blinding a whole binary. NEVER suppress
on `Name`/`File Path`/`File SHA*` of a LOLBin (powershell/rundll32/etc.) alone — it disables
the rule for that binary everywhere. Add `Rule Name` to limit to one rule; add `Hostname`/
`Organization Name`/`Location Name` to limit scope (empty `organizationId`/`locationId` on the
rule = fleet-wide, gated by the metadata match). **Do NOT blanket-whitelist an RMM agent as
grandparent by itself** — the RMM is a SYSTEM-level RCE channel and the top MSP attack path;
whitelisting all of it blinds EDR exactly where it matters.
**[DANGER] API create footgun (verified live 2026-07-07).** `POST /SuppressionRules` on this
tenant (a) **ignores `active:false` and forces the rule LIVE immediately**, and (b) auto-creates
an **EMPTY-metadata version that matches EVERYTHING**. So the two-step "create rule → then add
version" path leaves a live match-all suppression window (observed ~2 min → deleted). Also note
`GET`/`PATCH /SuppressionRules/{id}` (single-resource route) **HTTP 400** "undefined is not valid
JSON" on this tenant — read via `GET /SuppressionRules?filter={"where":{"id":"..."}}` instead;
**`DELETE /SuppressionRules/{id}` DOES work** (returns null, sets `deleted:true`). Until an
**atomic** create (metadata embedded in the POST body) is verified, **create suppressions in the
CONSOLE**, not via `raw POST`. If you must POST, verify the version's active fields IMMEDIATELY
and `DELETE` on any doubt.
**Create — CONSOLE-observed, API create RUN-unverified.** Console flow: open the alert →
**Create Suppression Rule** → check the desired Match fields → Save. This POSTs a
`SuppressionRule` (carrying `alertId`, `name`, `description`, `organizationId`, `locationId`,
`active`) plus a version whose `metadata` has the chosen fields flipped to `active:true`. To
replicate via API, POST `/SuppressionRules` then the version to `/SuppressionRules/{id}/
versions` with that metadata shape — verify the exact envelope on the next real create (build
→ read back via the GET above → delete if wrong) before relying on it. Example on this tenant:
rule `e4dd55bf-…` (name "Exfiltration Over HTTP Protocol", alertId `5d5f39b1-…`) matches
Process Command Line + Grand Parent = `gururmm-agent.exe`.
## Deployment (not a REST call) ## Deployment (not a REST call)
The agent installs by running the binary on the endpoint: The agent installs by running the binary on the endpoint:

View File

@@ -267,7 +267,7 @@ def build_parser() -> argparse.ArgumentParser:
sp.add_argument("method", help="GET/POST/PUT/DELETE") sp.add_argument("method", help="GET/POST/PUT/DELETE")
sp.add_argument("path", help="e.g. Agents or Agents/scan") sp.add_argument("path", help="e.g. Agents or Agents/scan")
sp.add_argument("--filter", help="LoopBack filter JSON (GET)") sp.add_argument("--filter", help="LoopBack filter JSON (GET)")
sp.add_argument("--data", help="request body JSON") sp.add_argument("--data", help="request body JSON, or @path to read JSON from a file")
sp.add_argument("--confirm", action="store_true", sp.add_argument("--confirm", action="store_true",
help="required for non-GET methods") help="required for non-GET methods")
return p return p
@@ -408,7 +408,13 @@ def main(argv=None) -> int:
print(f"[BLOCKED] {method} requires --confirm.", file=sys.stderr) print(f"[BLOCKED] {method} requires --confirm.", file=sys.stderr)
return 2 return 2
filt = json.loads(args.filter) if args.filter else None filt = json.loads(args.filter) if args.filter else None
body = json.loads(args.data) if args.data else None if args.data and args.data.startswith("@"):
with open(args.data[1:], "r", encoding="utf-8") as _f:
body = json.load(_f)
elif args.data:
body = json.loads(args.data)
else:
body = None
# Same tenant-wide footgun guard the scan command has: a POST to any # Same tenant-wide footgun guard the scan command has: a POST to any
# */scan endpoint with no non-empty `where` scans the ENTIRE tenant. # */scan endpoint with no non-empty `where` scans the ENTIRE tenant.
if method == "POST" and args.path.rstrip("/").lower().endswith("scan"): if method == "POST" and args.path.rstrip("/").lower().endswith("scan"):

View File

@@ -0,0 +1,3 @@
# No live secrets are cached (Basic auth is per-request), but keep scratch out.
.cache/
__pycache__/

View File

@@ -0,0 +1,74 @@
---
name: datto-workplace
description: "Read-only Datto Workplace backup verification for the GPS audit: confirm the file API is reachable under Basic auth, and (primary path) detect which GuruRMM endpoints actually run the Datto Workplace sync client. The file API is file-access only and this key sees no shared projects, so RMM endpoint detection is the real answer. Triggers: datto workplace, dattoworkplace, dwp, who backs up to datto workplace, datto sync."
---
# Datto Workplace Skill
Read-only client for ACG's live **Datto Workplace** tenant, built for GPS backup
verification. Answers: **which enrolled machines actually run the Datto Workplace
sync client** (and, where the file API allows, what projects/files are visible).
This skill is one of the backup-verification siblings alongside `seafile`,
`b2` (Backblaze), and `owncloud`. It never writes.
## Running
```bash
DWP=".claude/skills/datto-workplace/scripts/datto_workplace.py"
py "$DWP" status # file API reachable + endpoint/project count
py "$DWP" projects [--json] # file-API projects (likely empty for this key)
py "$DWP" audit --rmm --client "Birth" # GPS view: detect the client on GuruRMM endpoints
py "$DWP" audit --rmm --client Birth --json
```
Add `--json` to any command for machine-readable output. `--url` overrides the
file-API base.
## Credentials
Loaded at runtime from the SOPS vault (never hardcoded):
`msp-tools/datto-workplace.sops.yaml` -> `credentials.api-key` / `credentials.api-secret`.
Auth is **HTTP Basic**: username = api-key, password = api-secret, sent on every
request (no token exchange, nothing cached).
## The file API is limited (why RMM detection is primary)
Datto Workplace exposes a **file-access REST API v1** only. Base:
`https://acg.workplace.datto.com/api/v1` (the team path `/6/api/v1` is normalized
by the server to `/api/v1`). Endpoints (confirm with `GET /openapi.json`, surfaced
by `status`): `GET /file/projects`, `GET /file/{parentID}/files`,
`GET /file/{fileID}`, `GET /file/{fileID}/data`, `GET /file/search`, plus
`/webhook*`. There are **NO** user / device / member / team endpoints.
GOTCHA: `GET /file/projects` currently returns `{"result": []}` for this key -- the
API principal has no projects shared to it. So the file API alone **cannot
enumerate the team's backups**. `projects` handles this and says so.
Because of that, **RMM endpoint detection is the PRIMARY source of truth**.
## The `--rmm` cross-check (primary path)
`audit --rmm` logs in to GuruRMM (`infrastructure/gururmm-server.sops.yaml`) and,
for each agent (scope with `--client` so it doesn't sweep all 350+ agents), runs a
**read-only** detection PowerShell that reports installed products / running
processes / config folders matching the Datto Workplace client. An agent is
reported only if the client is actually present.
**Detection patterns must be precise:** this skill uses `["Datto Workplace",
"Workplace2"]`. A bare `"Datto"` false-matches **Datto EDR Agent**, Datto RMM, and
Datto AV -- do NOT use it. The v10 client installs as DisplayName
"Datto Workplace v10.x" in `C:\Program Files\Datto\Workplace2\` (process
`Workplace.exe`); the legacy v8 is "Datto Workplace Desktop". Matching
`Datto Workplace` + `Workplace2` catches both without catching EDR.
## Notes / gotchas
- Always scope `audit --rmm` with `--client` unless you truly mean to sweep the
whole fleet -- detection dispatches a live command to each endpoint.
- Shared plumbing (repo-root resolve, vault read, HTTP, GuruRMM client + endpoint
detection) lives in `.claude/scripts/backup_common.py`, used by all the
backup-verification siblings. Detection patterns are per-backend; this skill's
are the precise Datto Workplace set above.
- Known user: **Birth Biologic** backs up to Datto Workplace (agent hostnames
include `BB-*`, `KSTEEN*`, `EVO-X1`) -- a good `--client "Birth"` smoke test.

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Datto Workplace REST API v1 client for the `datto-workplace` skill.
Talks to ACG's live Datto Workplace tenant file API. Read-only: this is a
FILE-ACCESS API (projects + files), NOT an admin/reporting API -- there are no
user/device/member/team endpoints. It answers "what shared projects + files does
this API principal see" for the GPS backup audit.
Auth: HTTP Basic, username = api-key, password = api-secret (no token exchange;
Basic is sent on every request). Credentials come from the SOPS vault.
Base URL defaults to the team file-API host. The team path /6/api/v1 is normalized
by the server to /api/v1; override with DATTO_WORKPLACE_URL if needed.
IMPORTANT GOTCHA: GET /file/projects currently returns {"result": []} for this
key -- the principal has no projects shared to it, so the file API alone CANNOT
enumerate the team's backups. Handle empty gracefully. Because of that, RMM
endpoint detection (see datto_workplace.py `audit --rmm`) is the PRIMARY source
of "who backs up to Datto Workplace".
"""
from __future__ import annotations
import os
import sys
import urllib.parse
from pathlib import Path
from typing import Any, Optional
# Import the shared backup helpers from .claude/scripts.
_SKILL_DIR = Path(__file__).resolve().parent.parent # .../skills/datto-workplace
_SCRIPTS_DIR = _SKILL_DIR.parent.parent / "scripts" # .../.claude/scripts
sys.path.insert(0, str(_SCRIPTS_DIR))
import backup_common as bc # noqa: E402
VAULT_ENTRY = "msp-tools/datto-workplace.sops.yaml"
VAULT_FIELD_KEY = "credentials.api-key"
VAULT_FIELD_SECRET = "credentials.api-secret"
DEFAULT_URL = os.environ.get("DATTO_WORKPLACE_URL", "https://acg.workplace.datto.com/api/v1")
class DattoWorkplaceClient:
"""Basic-auth GET client for the Datto Workplace file API.
No token caching is needed -- HTTP Basic is sent per request. Credentials are
read once from the vault on first use and reused for the process lifetime.
"""
def __init__(self, base: Optional[str] = None):
self.base = (base or DEFAULT_URL).rstrip("/")
self._basic: Optional[tuple[str, str]] = None
# -- auth ------------------------------------------------------------------
def _creds(self) -> tuple[str, str]:
if self._basic is None:
key = bc.vault_get_field(VAULT_ENTRY, VAULT_FIELD_KEY)
secret = bc.vault_get_field(VAULT_ENTRY, VAULT_FIELD_SECRET)
self._basic = (key, secret)
return self._basic
def _get_raw(self, path: str, params: Optional[dict] = None) -> tuple[int, Any]:
"""Low-level GET returning (status, body); callers decide how to react."""
url = f"{self.base}{path}"
if params:
url += "?" + urllib.parse.urlencode(params)
return bc.http_json("GET", url, basic=self._creds())
def _get(self, path: str, params: Optional[dict] = None) -> Any:
status, body = self._get_raw(path, params)
if status == 401:
raise bc.BackupError(
f"Datto Workplace auth failed (HTTP 401) for {path}: check api-key/api-secret",
status=status)
if status != 200:
raise bc.BackupError(
f"Datto Workplace GET {path} failed (HTTP {status}): {body}", status=status)
return body
# -- read methods ----------------------------------------------------------
def openapi(self) -> dict:
"""Fetch the OpenAPI spec (confirms reachability + exact endpoint list)."""
body = self._get("/openapi.json")
return body if isinstance(body, dict) else {"_raw": body}
def endpoint_paths(self) -> list[str]:
"""Sorted list of paths the OpenAPI spec exposes."""
spec = self.openapi()
paths = spec.get("paths") if isinstance(spec, dict) else None
return sorted(paths.keys()) if isinstance(paths, dict) else []
def projects(self) -> list[dict]:
"""Top-level file projects visible to this API principal.
Returns [] gracefully -- for this key the principal currently has no
projects shared to it. The server is inconsistent here: some responses
are 200 {"result": []}, others are 403 "Access denied" for the exact same
Basic auth that authorizes /openapi.json seconds earlier. Both mean the
same thing for the audit (no project-level file access), so a 403/401 on
this endpoint is treated as empty rather than a hard error. A genuine
credential failure surfaces on /openapi.json (status/openapi).
"""
status, body = self._get_raw("/file/projects")
if status in (401, 403):
return []
if status != 200:
raise bc.BackupError(
f"Datto Workplace GET /file/projects failed (HTTP {status}): {body}",
status=status)
return self._as_list(body)
def files(self, parent_id: str) -> list[dict]:
"""List files/folders under a parent project or folder ID."""
body = self._get(f"/file/{parent_id}/files")
return self._as_list(body)
def file_info(self, file_id: str) -> dict:
"""Metadata for a single file/folder ID."""
body = self._get(f"/file/{file_id}")
if isinstance(body, dict):
return body.get("result") if isinstance(body.get("result"), dict) else body
return {"_raw": body}
def search(self, query: str, parent_id: Optional[str] = None) -> list[dict]:
"""Search files by name. Optional parent scope."""
params = {"query": query}
if parent_id:
params["parentID"] = parent_id
body = self._get("/file/search", params)
return self._as_list(body)
@staticmethod
def _as_list(body: Any) -> list[dict]:
"""Normalize the Datto file-API envelope to a list.
Responses wrap payloads as {"result": [...]} (or a bare list on some
endpoints); anything else yields [].
"""
if isinstance(body, dict):
res = body.get("result")
if isinstance(res, list):
return res
if isinstance(res, dict):
return [res]
return []
if isinstance(body, list):
return body
return []
def main() -> int:
try:
c = DattoWorkplaceClient()
paths = c.endpoint_paths()
projects = c.projects()
print(f"[OK] Datto Workplace file API reachable at {c.base}; "
f"{len(paths)} endpoints, {len(projects)} project(s) visible")
return 0
except bc.BackupError as exc:
print(f"[ERROR] {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""CLI for the `datto-workplace` skill -- read-only Datto Workplace backup
verification for the GPS audit.
Commands:
status file API reachable under Basic auth + endpoint/project count
projects [--json] list file-API projects (likely empty for this principal)
audit [--client NAME] [--rmm] [--json]
the GPS view: server-side projects (if any), optionally
cross-checked against GuruRMM endpoints (Datto Workplace
client installed + signed in?)
The Datto Workplace file API is FILE-ACCESS ONLY and this principal sees no shared
projects, so it CANNOT enumerate the team's backups on its own. RMM endpoint
detection (`audit --rmm`) is therefore the PRIMARY source: it detects the Datto
Workplace client on GuruRMM agents using precise patterns that do NOT false-match
Datto EDR / Datto RMM / Datto AV.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from datto_client import DattoWorkplaceClient # noqa: E402
import backup_common as bc # noqa: E402
# Precise detection patterns. "Datto Workplace" catches the DisplayName of both the
# v10 client ("Datto Workplace v10.x") and legacy v8 ("Datto Workplace Desktop");
# "Workplace2" catches the v10 install path / process family. A bare "Datto" would
# false-match "Datto EDR Agent", Datto RMM, and Datto AV -- do NOT use it.
DETECT_PATTERNS = ["Datto Workplace", "Workplace2"]
def cmd_status(c: DattoWorkplaceClient, args) -> int:
paths = c.endpoint_paths()
projects = c.projects()
if args.json:
print(json.dumps({"base": c.base, "reachable": True,
"endpoints": paths, "project_count": len(projects)}, indent=2))
return 0
print(f"[OK] Datto Workplace file API reachable at {c.base} (Basic auth 200)")
print(f"[INFO] {len(paths)} endpoints exposed:")
for p in paths:
print(f" {p}")
print(f"[INFO] {len(projects)} project(s) visible to this API principal", end="")
if not projects:
print(" - file API cannot enumerate backups; use `audit --rmm`")
else:
print()
return 0
def cmd_projects(c: DattoWorkplaceClient, args) -> int:
projects = c.projects()
if args.json:
print(json.dumps(projects, indent=2))
return 0
if not projects:
print("[INFO] no file-API projects visible to this principal.")
print("[INFO] The api-key has no projects shared to it, so GET /file/projects "
"returns []. This is expected - the Datto Workplace file API is file-access "
"only. Use `audit --rmm` to verify backups via GuruRMM endpoint detection.")
return 0
print(f"{'NAME':40} {'ID':38} {'FILES':>8}")
for p in projects:
print(f"{str(p.get('name') or '')[:40]:40} "
f"{str(p.get('id') or p.get('projectID') or '')[:38]:38} "
f"{str(p.get('fileCount') or p.get('count') or '?'):>8}")
print(f"\n{len(projects)} project(s)")
return 0
def cmd_audit(c: DattoWorkplaceClient, args) -> int:
projects = c.projects()
result = {"backend": "datto-workplace", "base": c.base,
"file_api_projects": projects, "file_api_limited": not projects}
if args.rmm:
rmm = bc.RmmClient()
rmm.login()
checks = []
agents = rmm.find_agents(client_substr=args.client) if args.client else rmm.list_agents()
for a in agents:
det = rmm.detect_client(a, DETECT_PATTERNS)
if det.get("detected"):
checks.append(det)
result["rmm_endpoints_with_client"] = checks
result["rmm_agents_scanned"] = len(agents)
if args.json:
print(json.dumps(result, indent=2))
return 0
print(f"=== Datto Workplace backup audit - {c.base} ===")
if projects:
print(f"{'PROJECT':40} {'ID':38} {'FILES':>8}")
for p in projects:
print(f"{str(p.get('name') or '')[:40]:40} "
f"{str(p.get('id') or p.get('projectID') or '')[:38]:38} "
f"{str(p.get('fileCount') or p.get('count') or '?'):>8}")
print(f"\n{len(projects)} file-API project(s)")
else:
print("[INFO] file API shows 0 projects (principal has none shared) - "
"server-side enumeration not available; relying on RMM detection.")
if args.rmm:
eps = result.get("rmm_endpoints_with_client", [])
scanned = result.get("rmm_agents_scanned", 0)
scope = f"client~'{args.client}'" if args.client else "ALL agents"
print(f"\n--- GuruRMM endpoints running the Datto Workplace client "
f"({len(eps)} of {scanned} scanned, {scope}) ---")
for e in eps:
det = e.get("detail") or {}
installed = det.get("installed") or []
prod = "-"
if isinstance(installed, list) and installed:
first = installed[0]
if isinstance(first, dict):
prod = f"{first.get('name') or '?'} {first.get('version') or ''}".strip()
elif isinstance(installed, dict):
prod = f"{installed.get('name') or '?'} {installed.get('version') or ''}".strip()
print(f" {str(e.get('hostname') or '')[:25]:25} "
f"client={str(e.get('client_name') or ''):20} "
f"product={prod}")
return 0
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog="datto-workplace",
description="Datto Workplace backup verification (GPS audit)")
p.add_argument("--url", help="override file-API base URL (default team file-API host)")
sub = p.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("status"); s.add_argument("--json", action="store_true")
pr = sub.add_parser("projects"); pr.add_argument("--json", action="store_true")
a = sub.add_parser("audit")
a.add_argument("--client", help="scope RMM detection to agents whose client_name matches")
a.add_argument("--rmm", action="store_true", help="cross-check GuruRMM endpoints (primary path)")
a.add_argument("--json", action="store_true")
return p
def main(argv=None) -> int:
args = build_parser().parse_args(argv)
c = DattoWorkplaceClient(args.url)
handlers = {"status": cmd_status, "projects": cmd_projects, "audit": cmd_audit}
try:
return handlers[args.cmd](c, args)
except bc.BackupError as exc:
print(f"[ERROR] {exc}", file=sys.stderr)
# best-effort error logging per house rule
try:
root = bc.resolve_root()
bc.subprocess.run(["bash", str(root / ".claude/scripts/log-skill-error.sh"),
"datto-workplace", str(exc)[:200]], timeout=15)
except Exception:
pass
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,11 +1,6 @@
--- ---
name: discord-dm name: discord-dm
description: > description: "Send OR read Discord messages to/from an org member DM or team channel via the ClaudeTools bot - copy-paste-friendly delivery of links/commands the terminal would mangle, and READ replies (coord/repo sync does not carry Discord answers); address people by name (mike/howard/rob/winter). Triggers: DM <person> in discord, send that link to my discord, ping <person>, did <person> reply/answer, read discord replies, check discord DMs."
Send a Discord message to an org member's DMs or a team channel via the ClaudeTools
bot — for handing a person copy-paste-friendly content the terminal would mangle
(consent links, long commands, URLs, tokens-to-rotate) or to ping someone. Addresses
people by name (mike/howard/rob/winter), not raw snowflakes. Triggers: DM/message
<person> in discord, discord DM, send that link to my discord, ping <person>.
--- ---
# discord-dm — direct Discord messaging to the org # discord-dm — direct Discord messaging to the org
@@ -32,9 +27,26 @@ DMs** and deliberate channel posts.
```bash ```bash
bash .claude/scripts/discord-dm.sh <recipient> "message text" bash .claude/scripts/discord-dm.sh <recipient> "message text"
echo "message text" | bash .claude/scripts/discord-dm.sh <recipient> echo "message text" | bash .claude/scripts/discord-dm.sh <recipient>
bash .claude/scripts/discord-dm.sh read <recipient> [limit] # READ recent messages — SEE replies (default 15)
bash .claude/scripts/discord-dm.sh list # print known users + channels bash .claude/scripts/discord-dm.sh list # print known users + channels
``` ```
## Reading replies (`read`)
Sends are one-way, but people **answer** — always `read` before assuming silence. The bot
participates in every DM channel it opened, so `GET /channels/<id>/messages` returns full
content (the Message Content privileged intent only gates *gateway* events, not this REST
fetch). Works for users and channels:
```bash
bash .claude/scripts/discord-dm.sh read mike 10 # last 10 in the mike DM
bash .claude/scripts/discord-dm.sh read #dev-alerts # last 15 in #dev-alerts
```
Output is oldest-first; lines from **them** are marked `>>>`, lines from us are indented.
After DMing someone a question, `read` that user to check for their answer — coord/repo sync
does NOT carry Discord replies, so this is the only way to see them.
`<recipient>`: `<recipient>`:
| Form | Effect | | Form | Effect |

View File

@@ -1,6 +1,6 @@
--- ---
name: drive-map name: drive-map
description: Reliably create/repoint Windows network drive maps and share shortcuts on a remote endpoint via GuruRMM. Bakes in the things that make this fight every time — runs in the user session (not SYSTEM, so maps actually appear), stores the per-host credential with cmdkey (the workgroup-PC-to-domain-share case), makes maps persistent, repoints/removes stale NAS shortcuts, and verifies access. Built for the Cascades NAS -> CS-SERVER migration but generic. description: "Reliably create/repoint Windows network drive maps on a remote endpoint via GuruRMM: runs in the user session (not SYSTEM), stores per-host creds with cmdkey, persistent maps, repoints stale NAS shortcuts, verifies access. Triggers: map a drive, drive mapping, repoint share."
--- ---
# drive-map — remote drive maps & share shortcuts that actually stick # drive-map — remote drive maps & share shortcuts that actually stick

View File

@@ -0,0 +1,107 @@
---
name: errorlog-dream
description: "Lint the fleet error log (errorlog.md): top failure contexts, repeat ref= citations (rules that are not sticking), cross-day noise clusters, resolved entries, machine-name drift; --apply-archive rotates old entries to errorlog-archive/. Triggers: errorlog dream, lint/analyze the error log, errorlog patterns."
---
# Errorlog Dream
Sibling of `memory-dream` for `errorlog.md` — the corpus of skill failures,
user corrections, and self-inflicted friction that CLAUDE.md mandates logging.
The log exists so we "never pay tokens twice for the same avoidable mistake";
this skill is the payoff step: it reads the corpus and surfaces what to fix.
Read-only by default. The single mutating op (`--apply-archive`) only moves
old entries into `errorlog-archive/YYYY-MM.md`. Everything judgment-shaped
lands in a `## PROPOSED` section for the operator.
## What it reports
`scripts/errorlog_dream.py` parses the canonical entry format
(`YYYY-MM-DD | MACHINE | skill/context | [type] msg [ctx: ...] (xN)`;
`(xN)` is the log helper's same-day repeat counter) and produces:
1. SUMMARY — totals by type (exec/correction/friction), machine, date span,
plus a count of unparsed legacy blocks (left untouched, never archived).
2. TOP CONTEXTS — failure volume by context group, weighted by `(xN)`.
3. REPEAT REFS — `ref=` citations appearing >=2x. **The highest-value signal:**
a friction entry citing a documented gotcha means that rule/memory is NOT
working; repeat citations mean it keeps failing. Checks whether the cited
memory file actually exists.
4. NOISE CLUSTERS — same machine + skill + normalized message (ids/numbers
collapsed) recurring across >=3 days or >=5 weighted hits. These need a
skill-side fix (expected-condition filter, backoff, health-gate) — not
more log lines.
5. RESOLVED — entries carrying a `[RESOLVED ...]` annotation; archive
candidates regardless of age.
6. MACHINE-NAME DRIFT — the same machine logged under multiple spellings
(case drift in identity.json).
7. ARCHIVE CANDIDATES — entries older than `--days` (default 60).
8. PROPOSED — `[STRENGTHEN?]` (repeat refs -> add a mechanical guard or
rewrite the memory), `[SUPPRESS?]` (noise clusters -> fix the skill),
`[ARCHIVE?]` (resolved entries).
## Modes
- Default — report only; prints to stdout and writes
`errorlog-archive/_reports/YYYY-MM-DD-HHMM-dream.md`.
- `--no-file` — stdout only.
- `--report-file <path>` — explicit report path.
- `--days <n>` — archive-age threshold (default 60).
- `--apply-archive` — move entries older than `--days` into
`errorlog-archive/YYYY-MM.md` (grouped by entry month, verbatim lines,
unparsed legacy blocks never moved). Idempotent.
## Running it
Stdlib only, no pip deps.
```bash
# report only
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills/errorlog-dream/scripts/errorlog_dream.py"
# rotate out entries older than 60 days
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills/errorlog-dream/scripts/errorlog_dream.py" --apply-archive
```
## The operator MUST work the PROPOSED section — that is the run's purpose
Same posture as memory-dream: the report is not the deliverable, the fixes
are. After each run:
1. **`[STRENGTHEN?]` repeat refs** — the cited prose rule demonstrably fails
under flow. Prefer a MECHANICAL guard over more prose: a PreToolUse hook,
a wrapper that makes the safe path the only path (e.g. `-EncodedCommand`
for quote-mangling layers), or a script-side preflight. If the cited
memory file is missing, fix or remove the dangling ref convention.
2. **`[SUPPRESS?]` noise clusters** — patch the failing skill: filter
expected/validation responses out of logging (the gz.py pattern +
`GZ_SUPPRESS_ERRORLOG`-style env flag), add a stop-after-2-identical-
failures guard, or health-gate a chronically-down backend.
3. **`[ARCHIVE?]` / archive candidates** — run `--apply-archive`; append
`[RESOLVED <date>]` to entries you fixed so the next run proposes them.
4. Commit + `/sync` so the trimmed log and archive reach the fleet.
If a proposal needs a decision you can't make, leave it AND say so in your
summary — don't silently skip the section.
## Self-test
`scripts/selftest.py` builds a synthetic errorlog in a temp dir and asserts
every detector fires (counters, repeat refs, noise clusters, resolved,
machine drift, archive candidates) and that `--apply-archive` moves exactly
the old entries, splits by month, preserves the marker + unparsed blocks,
and is idempotent.
```bash
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills/errorlog-dream/scripts/selftest.py"
```
## Files
```
.claude/skills/errorlog-dream/
SKILL.md this file
scripts/errorlog_dream.py the analyzer (report / --apply-archive)
scripts/selftest.py fixture-based self-test
errorlog-archive/ rotated months + _reports/ (created on use)
```

Some files were not shown because too many files have changed in this diff Show More