betterfiles-autolabel

Skill: betterfiles-autolabel Version: 0.1.0 (Phase 1B — taxonomy locked, implementation pending) Inbox: teamsteph@betterfiles.com Tokens: /home/opsadmin/.openclaw/workspace-betterfiles/gmail-tokens.json Audit table: Supabase CCP (svueekfvfrvhylxygktb) → gmail_label_audit Systemd timer: betterfiles-autolabel-sweep.{service,timer} (Phase 4) Plan: /home/opsadmin/.claude/plans/betterfiles-autolabel-skill-2026-04-27.md

Trigger keywords

  • “autolabel emails”
  • “label teamsteph”
  • “gmail label sweep”
  • “betterfiles inbox”
  • “teamsteph label”

CLI usage (Phase 2+)

# Preview what labels would be applied to one thread
node bin/bf-autolabel preview <gmail-thread-id>
 
# Classify all INBOX messages from last N minutes (shadow = no writes)
node bin/bf-autolabel sweep-once --since=15m --shadow
node bin/bf-autolabel sweep-once --since=15m --apply
 
# One-shot backfill of 201+ unread INBOX messages
node bin/bf-autolabel backfill --scope=inbox --dry-run
node bin/bf-autolabel backfill --scope=inbox --apply
 
# 90-day scope (Phase 3, after INBOX clean)
node bin/bf-autolabel backfill --scope=90d --dry-run
 
# Re-derive taxonomy from live HubSpot pipeline 816046
node bin/bf-autolabel taxonomy-rebuild
 
# Tail the audit log (last 50 ops)
node bin/bf-autolabel audit-tail
 
# Roll back all operations since timestamp
node bin/bf-autolabel rollback --since=2026-04-27T00:00:00Z

Shared libs (adopt, do not duplicate)

// Gmail API client — atomic token refresh, rate limiting, batchModify
const { GmailClient } = require('/home/opsadmin/.openclaw/workspace/scripts/lib/gmail-client');
 
// 4-tier address matcher — use prefix:'BetterFiles/' for new taxonomy
const { GmailLabelMatcher } = require('/home/opsadmin/.openclaw/workspace/scripts/lib/gmail-label-matcher');

GmailClient constructor for this skill:

new GmailClient({
  tokensPath: '/home/opsadmin/.openclaw/workspace-betterfiles/gmail-tokens.json'
})

GmailLabelMatcher for new taxonomy (sweep + Push):

new GmailLabelMatcher(labels, { prefix: 'BetterFiles/' })

GmailLabelMatcher for backfill (matching old labels to new):

new GmailLabelMatcher(labels, { prefix: '' })  // all user labels

Classifier flow

Thread received
      │
      ▼
classifier-rules.js (deterministic, free)
      │ confidence >= 0.75?
      ├─ YES → apply label + log action='rule'
      │
      └─ NO (ambiguous)
            │
            ▼
      classifier-llm.js (Haiku 4.5 via Portkey 127.0.0.1:18900)
            │ confidence >= 0.6?
            ├─ YES → apply label + log action='llm'
            │
            └─ NO → apply BetterFiles/Review + log action='review'

Portkey auth: Authorization: Bearer $PORTKEY_API_KEY (from master.env). Proxy injects x-portkey-config — do NOT add x-portkey-virtual-key directly.

Invariants (enforced in code, not just policy)

  1. NEVER modify or delete any label matching /^Hiver-/i
  2. NEVER remove a label that was not created by this skill (check audit_log for source='betterfiles-autolabel')
  3. NEVER write to gmail-tokens.json outside the GmailClient.refreshToken() path
  4. NEVER modify messages in SENT, TRASH, or SPAM
  5. ALWAYS write gmail_label_audit row BEFORE applying the Gmail label (fail-safe: if audit write fails, skip Gmail op)
  6. Cost ceiling: if LLM daily cost > $5, switch to rules-only mode and alert Discord ops

Label taxonomy (locked 2026-04-27, derived from live HubSpot pipeline 816046)

Stage labels — BetterFiles/Stage/

Gmail label pathHubSpot stage IDDisplay order
BetterFiles/Stage/00-New-Property10068853790
BetterFiles/Stage/01-Due-Diligence11708664461
BetterFiles/Stage/02-New-Property-InvestorLift13079606142
BetterFiles/Stage/03-Send-Out-Prop10068853803
BetterFiles/Stage/04-Property-Sent-Out11175981294
BetterFiles/Stage/05-Previewing8160475
BetterFiles/Stage/06-EX-Make-Offer8160486
BetterFiles/Stage/07-EX-Follow-Up-on-Offer10285538057
BetterFiles/Stage/08-Negotiating-Price-Reduction13331377208
BetterFiles/Stage/09-Follow-Up-w-CH-Skip-Trace-Seller11916183589
BetterFiles/Stage/10-Need-to-Send-Follow-Up132886339210
BetterFiles/Stage/11-Finding-a-Buyer101457136811
BetterFiles/Stage/12-Buyer-Showing101863684112
BetterFiles/Stage/13-Review-Offers102906073213
BetterFiles/Stage/14-Sold-FU-to-see-if-Buyer-Performing103211319814
BetterFiles/Stage/15-Waiting-On-Wholesaler-Contract81604915
BetterFiles/Stage/16-Send-Contract-To-Buyer100696558216
BetterFiles/Stage/17-Contract-Sent-to-Buyer81605017
BetterFiles/Stage/18-Pending-EMD-Needed100046456018
BetterFiles/Stage/19-Pending-Delayed100008733019
BetterFiles/Stage/20-Pending-EMD-Received81605120
BetterFiles/Stage/21-Final-Steps-For-Close131834658021
BetterFiles/Stage/22-Funding-Needed97358285122
BetterFiles/Stage/23-Funded132483232423
BetterFiles/Stage/24-Set-For-Recording131849433724
BetterFiles/Stage/25-Closed-Payment-Pending97359429025
BetterFiles/Stage/26-Closed-Disbursement-Pending131849433826
BetterFiles/Stage/27-Closed-Seller-Vacating-Property98445786127
BetterFiles/Stage/28-Closed81605228
BetterFiles/Stage/29-Cancelled-Need-to-Refund-Money100008733129
BetterFiles/Stage/30-Lost-Watch107374886430
BetterFiles/Stage/31-Lost-Sold-skip-trace107374785931
BetterFiles/Stage/32-Lost83040132

Stage label slugs are emoji-stripped, special-char-normalized versions of HubSpot stage names. Taxonomy rebuild (bf-autolabel taxonomy-rebuild) re-derives from live pipeline 816046 and updates this file.

Action labels — BetterFiles/Action/

Gmail label pathTrigger
BetterFiles/Action/Wire-Pendingsubject regex: wire
BetterFiles/Action/EMD-Neededsubject regex: earnest money
BetterFiles/Action/Sig-NeededDocuSign sender + awaiting signature
BetterFiles/Action/Refund-Duestage=29-Cancelled + refund keyword
BetterFiles/Action/Funding-Neededstage=22-Funding-Needed keyword
BetterFiles/Action/Recording-Pendingsubject regex: recording

Source labels — BetterFiles/Source/

Gmail label pathTrigger
BetterFiles/Source/DocuSignsender domain: docusign.net
BetterFiles/Source/Escrowsender domain in escrow company list OR subject: escrow
BetterFiles/Source/Titlesender domain in title company list OR subject: title
BetterFiles/Source/Buyerthread participant matches buyer email on deal
BetterFiles/Source/Sellerthread participant matches seller email on deal
BetterFiles/Source/Lendersubject: lender
BetterFiles/Source/Attorneysubject: attorney

Property labels — BetterFiles/Property/

Dynamically created per deal. Slug derived from HubSpot property_address field:

  • Lowercase, spaces to dashes, strip punctuation
  • Example: 1354 Orange Ave, Signal Hill1354-orange-ave-signal-hill
  • Only created for active deals (stages 00–28)
  • Archive closed-deal property labels after 90 days (Phase 7)

Review label — BetterFiles/Review

Applied when classifier confidence < 0.6. Stephania’s manual triage queue. Flat label (no sub-hierarchy).

Audit log schema (Supabase CCP)

Table: gmail_label_audit (created Phase 2B)

ColumnTypeNotes
idBIGSERIALPK
occurred_atTIMESTAMPTZdefault now()
inboxTEXTteamsteph@betterfiles.com
message_idTEXTGmail msg ID
thread_idTEXTGmail thread ID
actionTEXT’add’ / ‘remove’ / ‘create-label’ / ‘shadow’
label_nameTEXTfull Gmail label path
label_idTEXTGmail label ID (nullable until label created)
classifierTEXT’rule’ / ‘llm’ / ‘manual’ / ‘push’
confidenceNUMERIC(4,3)classifier confidence score
reasonTEXThuman-readable explanation
rule_idTEXTwhich rule matched (if classifier=rule)
llm_modelTEXTmodel used (if classifier=llm)
portkey_request_idTEXTPortkey trace ID
cost_centsNUMERIC(8,4)LLM cost in cents
sweep_run_idUUIDgroups all ops from one sweep run
UNIQUE(message_id, label_name, action)idempotency

Stage cache

Path: /home/opsadmin/.openclaw/workspace-betterfiles/data/stage-cache.json TTL: 1 hour Format: { fetchedAt: ISO, stages: [{id, label, displayOrder, labelPath}] } Manual invalidation: bf-autolabel taxonomy-rebuild

File layout

~/.claude/skills/betterfiles-autolabel/
  SKILL.md                    ← this file
  bin/
    bf-autolabel              ← CLI entry (Phase 2A)
  lib/
    classifier-rules.js       ← deterministic rules (Phase 2C)
    classifier-llm.js         ← Haiku 4.5 via Portkey (Phase 2D)
    audit-log.js              ← Supabase gmail_label_audit writer (Phase 2B)
    hubspot-stage-fetcher.js  ← live pipeline 816046 + 1h cache (Phase 2A)
  tests/
    test-classifier-rules.js
    test-classifier-llm.js
    test-audit-log.js
    test-hubspot-stage-fetcher.js

Shared libs required (DO NOT DUPLICATE):

  • /home/opsadmin/.openclaw/workspace/scripts/lib/gmail-client.js
  • /home/opsadmin/.openclaw/workspace/scripts/lib/gmail-label-matcher.js
  • /home/opsadmin/.openclaw/workspace/scripts/lib/logger.js
  • /home/opsadmin/.openclaw/workspace/scripts/lib/state-manager.js
  • /home/opsadmin/.openclaw/workspace/scripts/lib/retry.js

Invokes / Invoked by

Invokes: _summary, dispo-lifecycle Invoked by: SKILL, SKILL