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>
This commit is contained in:
@@ -23,7 +23,7 @@ Follow these steps exactly when /inject-standards is invoked:
|
||||
|
||||
### 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.
|
||||
3. Select the 2–5 standards most relevant to either:
|
||||
- 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):
|
||||
|
||||
1. Read the file at `D:/claudetools/.claude/standards/<slug>.md`.
|
||||
1. Read the file at `.claude/standards/<slug>.md`.
|
||||
2. Display a header:
|
||||
```
|
||||
=== STANDARD: <slug> ===
|
||||
@@ -87,8 +87,8 @@ Reads the recent conversation, infers the task type, selects 2–5 most relevant
|
||||
|
||||
## Standards index location
|
||||
|
||||
`D:/claudetools/.claude/standards/index.yml`
|
||||
`.claude/standards/index.yml`
|
||||
|
||||
## Standards files location
|
||||
|
||||
`D:/claudetools/.claude/standards/<folder>/<name>.md`
|
||||
`.claude/standards/<folder>/<name>.md`
|
||||
|
||||
@@ -106,43 +106,28 @@ Keys are baked into the skill below. To add a new user: generate a token in Sync
|
||||
### Get API key
|
||||
|
||||
```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)
|
||||
# Fallback to dynamic detection for legacy machines that haven't updated identity.json yet
|
||||
IDENTITY_PATH="${HOME}/.claude/identity.json"
|
||||
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
|
||||
BASE="$SYNCRO_BASE"
|
||||
API_KEY="$SYNCRO_API_KEY"
|
||||
REPO_ROOT="$CLAUDETOOLS_ROOT"
|
||||
|
||||
if [ ! -f "$IDENTITY_PATH" ]; then
|
||||
echo "[ERROR] Cannot locate identity.json - run onboarding first" >&2
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "[ERROR] No Syncro API key for user '${SYNCRO_USER:-unknown}'" >&2
|
||||
exit 1
|
||||
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
|
||||
|
||||
@@ -36,17 +36,18 @@ want a periodic clean rebuild. For "I just did some work, capture it," use plain
|
||||
## Phase 0 — Setup
|
||||
|
||||
```bash
|
||||
CLAUDETOOLS_ROOT="D:/claudetools"
|
||||
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
|
||||
# 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.
|
||||
# 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)
|
||||
BASE="https://computerguru.syncromsp.com/api/v1"
|
||||
USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json")
|
||||
case "$USER_ID" in
|
||||
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;;
|
||||
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;;
|
||||
*) echo "[WARNING] Unknown user — Syncro enrichment skipped" ; API_KEY="" ;;
|
||||
esac
|
||||
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
|
||||
BASE="$SYNCRO_BASE" # read-only: GET requests ONLY in this skill
|
||||
API_KEY="$SYNCRO_API_KEY" # empty if no key vaulted for this user -> skip enrichment
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "[WARNING] No Syncro key for user '${SYNCRO_USER:-unknown}' — Syncro enrichment skipped"
|
||||
fi
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
@@ -10,7 +10,8 @@ Scan for clients and projects that have session logs but no wiki article.
|
||||
|
||||
```bash
|
||||
# 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
|
||||
slug=$(echo "$dir" | sed 's|clients/||;s|/session-logs||')
|
||||
wiki="wiki/clients/$slug.md"
|
||||
@@ -96,13 +97,17 @@ For every client wiki article that contains a `Syncro customer ID` line, pull li
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
BASE="https://computerguru.syncromsp.com/api/v1"
|
||||
USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json")
|
||||
case "$USER_ID" in
|
||||
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;;
|
||||
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;;
|
||||
*) echo "[SYNCRO] No API key for user '$USER_ID' — skipping Step 6" ; exit 0 ;;
|
||||
esac
|
||||
# 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.
|
||||
source "$(git rev-parse --show-toplevel)/.claude/scripts/syncro-env.sh" || true
|
||||
|
||||
BASE="$SYNCRO_BASE"
|
||||
API_KEY="$SYNCRO_API_KEY"
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user