Skip to content

INSTALL: remove PropelAuth mentions, add Claude.ai connector section - #1

Merged
shiftcontrol-dan merged 10 commits into
mainfrom
dan/remove-propelauth-add-claude-ai
May 14, 2026
Merged

INSTALL: remove PropelAuth mentions, add Claude.ai connector section#1
shiftcontrol-dan merged 10 commits into
mainfrom
dan/remove-propelauth-add-claude-ai

Conversation

@shiftcontrol-dan

Copy link
Copy Markdown
Contributor

Summary

Customers never experience PropelAuth as a brand — they see a ShiftControl sign-in screen. This PR rewrites the two PropelAuth mentions in INSTALL.md to describe what the user actually sees, and adds a Claude.ai (web + mobile) connector section that mirrors what's now live on shiftcontrol.io/llms-install.txt.

Changes

Where Before After
INSTALL.md intro "authenticate to ShiftControl once via PropelAuth" "sign in to ShiftControl once in your browser"
INSTALL.md, Claude Desktop section "browser tab opens for PropelAuth login" "browser tab opens for ShiftControl sign-in"
INSTALL.md, new section ### Claude.ai (web + mobile) with the Customize → Connectors flow

Type of change

  • Documentation only

Checklist

  • Commit is signed (verified by GitHub)
  • Followed CONTRIBUTING.md
  • No new MCP tool dependencies introduced
  • No skill behavior changes — docs only

Test plan

  • Verified grep -i propelauth returns zero hits across *.md / *.yaml / *.json / *.txt in the repo
  • Manual diff review against the live llms-install.txt on shiftcontrol.io to confirm the new Claude.ai section is consistent

Notes for the merge

This is also the first-PR shakedown for this repo's CI. After approving, watch for the validate and verify checks to pass green before merging — that proves branch protection + signed-commit enforcement work end-to-end on a real PR. If either check fails, that's a CI bug to fix, not a doc problem.

Customers never experience PropelAuth as a brand — they see a ShiftControl
sign-in. Rewriting the intro paragraph and the Claude Desktop first-use note
to match what the user actually sees.

Also adds a new Claude.ai (web + mobile) section using the Customize →
Connectors flow, between the Claude Desktop and Claude Code sections.
Mirrors the section added to shiftcontrol.io/llms-install.txt.
@amazon-inspector-singapore

Copy link
Copy Markdown

⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done

@amazon-inspector-singapore

Copy link
Copy Markdown

✅ I finished the code review, and didn't find any security or code quality issues.

Customers don't think in 'audit log' terms — that's internal vocabulary. The
practice of appending a short note to the app's 'notes' field on each change
stays (it's useful context for whoever looks at the record next), but the
framing shifts from 'audit trail' to 'short context note for the next reader.'

Concrete changes:
- CONTRIBUTING.md: drop the 'Audit trail in notes' rule entirely (Dan's call —
  it was over-prescribed; the skills naturally do the right thing without
  elevating it to a hard rule). Remaining rules renumber 1, 2, 3.
- README.md: 'audit-logged' / 'audit-friendly' / 'audit notes' → context-note
  framing focused on the next-human-to-read-this-app.
- SKILL.md: 'audit log' → 'change history'; 'audit line' → 'new line'; the
  notes-line example no longer includes 'via skill vX.Y.Z' — humans reading
  the record don't care about the skill version.
- proposal-format.md: '{audit string}' → '{note string}'; 'audit reviewer'
  → 'someone reviewing the record later'.
- vendor-name-mapping.md: 'auditable' → 'leaves a clear record'.

Kept (user-facing verbs, not internal jargon):
- README 'audit spend' — user-facing verb.
- SKILL.md trigger phrase 'Audit our subscription data' — that's how users
  actually phrase the request.
Annual contracts dominate SaaS pricing. A 90-day default window risks missing
the most recent renewal invoice for any app billed annually — i.e. half the
user's apps, in practice. 18 months covers every cadence (monthly / quarterly
/ annual) plus a buffer to confirm you have the latest invoice.

If the user wants a narrower window ('just this month', 'last quarter'),
the skill honors that. Wider than 18 months stays rare and explicit, since
old invoices are usually superseded.

Updates:
- SKILL.md Step 2: 90 days → 18 months, with rationale.
- SKILL.md sample proposal: 'no invoice in the last 90 days' → '... 18 months'.
- references/invoice-detection.md: rewritten Time-Window section reflecting
  the new default and dropping the '400 days for annual contracts' clause
  (now subsumed by the 18-month default).
Mid-period invoices (seat additions, plan upgrades, prorated adjustments)
are partial charges. Computing per-user cost from them produces silently-
wrong numbers. The skill now classifies invoice type first, then extracts
only the fields that are reliable for that type.

SKILL.md Step 4 changes:
- New 'Classify the invoice type first' subsection at the top
- Incremental signals listed inline (subject/body text, amount, service period,
  position relative to a renewal invoice)
- Per-unit cost and billing frequency are explicitly marked 'Full only'
- Plan/tier and contract end date marked as STILL usable from incrementals
  (they are often the BEST source for plan changes specifically)
- 'Only incrementals for this vendor' is now its own surfacing path

invoice-detection.md adds:
- Full vs incremental classification section with both sides' tells
- Field-by-field reliability matrix
- Multi-invoice combining rules (full + incrementals → use each for its
  strongest signal; only-incrementals → skip cost, propose plan + dates)
- Annual-vs-monthly cost normalization note (separate but adjacent gotcha
  where an annual invoice quotes $84/user but storage is $7 + 'year')
…ce handling

Five changes Dan flagged:

1. Notes are REPLACED, not appended. The skill runs repeatedly; one
   'Updated from X dated Y' line per app at all times. Match pattern is
   'line starts with "Updated from " and contains "invoice dated <YYYY-MM-DD>"';
   user-written content in the notes is preserved.

2. Reseller annotation in the note string. Slack-via-Salesforce and
   Google-Workspace-via-ShiftControl are the canonical examples — note
   becomes 'Updated from Slack invoice dated 2026-03-15 (billed via Salesforce)'
   so the next reader understands why the From-address didn't match the product.

3. Slack-via-Salesforce (and Heroku-via-Salesforce, future Figma-via-Adobe)
   added to vendor-name-mapping.md as 'acquired-product domain rebinds'.
   Body content is authoritative; From-domain can mislead.

4. PDF invoices now PARSED (not skipped). Most email MCPs return attachment
   contents; most AI assistants can read PDFs natively. Removed PDF from the
   'v0.1.0 doesn't do' list. Brief mention in SKILL.md Step 4; details in
   invoice-detection.md.

5. Web-hosted invoices: skill ASKS the user before following 'click here to
   view your invoice' links. Suggested phrasing is in the doc. If the user
   agrees AND the assistant has a fetch capability, fetch + parse. Removed
   from 'v0.1.0 doesn't do' list.

Display change in the proposal block: 'note will be added:' → 'notes update:'.
The new phrasing is more honest about replace-not-append semantics and is
intentional per proposal-format.md.

Reseller line-item matching gets its own subsection in vendor-name-mapping.md
(rule 4a) — one reseller invoice with Google Workspace + JumpCloud line items
becomes two separate ShiftControl updates.
Without CODEOWNERS, branch protection's PR-review requirement enforces
'1 approver' but doesn't route the request to a specific reviewer. With
this file, GitHub auto-requests review from @shiftcontrol-dan on every PR
that touches anything in the repo.

The 'require code owner reviews' flag on the branch protection rule was
intentionally left off in the initial setup (since CODEOWNERS didn't
exist). Now that CODEOWNERS lands, that flag can be flipped on if you
want to require code-owner approval specifically (rather than 'any 1
approver').
Mined 18 months of a real ShiftControl invoice inbox to verify and refine the
detection / matching guidance. Three corrections and several additions.

CORRECTIONS:
- Slack-via-Salesforce was overstated. Real accounts continue receiving Slack
  invoices from feedback@slack.com even post-acquisition. Removed the hard
  claim; phrased as 'pattern varies per acquisition and per account, dont
  assume, read the body.'

ADDITIONS to vendor-name-mapping.md:
- Real-world vendor-to-sender catalog: Slack, Notion, GitHub, Granola, Framer,
  Anthropic, 1Password, JumpCloud, Cloudflare, Zoom, AWS. Includes typical
  subject patterns and format (inline / PDF / Stripe-template).
- Stripe-template receipts recognized as a vendor-cluster pattern (Granola,
  Framer, Anthropic all share 'Your receipt from <Vendor> #<NUMBER>').

ADDITIONS to invoice-detection.md:
- Dedicated invoice mailbox + forwarding rules: many orgs route invoices to
  invoices@<company>.io via forwarding. Sender appears as the alias; real
  vendor is in the body. Skill must read body to identify the vendor in
  these cases.
- False-positive cluster: outbound payments (Workato-paying-you via Coupa Pay
  is the textbook example, but also customer payments via Stripe / Sequence
  HQ, professional services, one-time SSL/domain/hardware, telecom, bank /
  tax / regulatory).
- Reseller catalog: Ingram Micro Asia (Imcloudservicedesk.hk@cloud.im), uses
  2026SIHK<NUMBER> invoice format and sends Credit Memos (reduce balances,
  do not parse as new charges). AWS Marketplace for third-party SaaS.

ADDITION to SKILL.md Step 3:
- Outbound-payments filter
- Dedicated-mailbox forwarding callout (read body for vendor)
Three things in one commit:

1. AUTO-RELEASE WORKFLOW (.github/workflows/release.yaml + top-level VERSION)
   Modeled on the google_api_sdk pattern. On push to main where VERSION
   changes (or workflow_dispatch), the workflow:
   - Reads VERSION (validates SemVer)
   - Skips if v<VERSION> tag already exists
   - Imports a CI signing GPG key (secrets.GPG_PRIVATE_KEY/_PASSPHRASE)
   - Creates a signed tag v<VERSION>, pushes it
   - Builds per-skill zips (skills/<name>/ → <name>-v<VERSION>.zip)
   - Creates a GitHub release with --generate-notes + zip artifacts

   VERSION pinned to 0.1.0 to match the existing tag; the workflow will
   detect the existing tag and skip on first deploy, then activate cleanly
   on the next bump.

   CONTRIBUTING.md documents the GPG setup steps (gpg --gen-key, export
   private to GPG_PRIVATE_KEY, public to GitHub signing keys).

2. SALESFORCE-BILLED SLACK — both patterns coexist
   Concrete evidence (a real Salesforce invoice .eml for Slack annual):
   - From: Salesforce APAC Billing <billing@apac.salesforce.com>
   - Subject: 'salesforce.com Invoice <NUMBER>, <Customer Org>'
   - Body does NOT mention Slack; product is only in the PDF attachment
   - PDF filename: 'Salesforce_Invoice_<NUMBER>.pdf'

   Documented as: SMB / monthly contracts continue billing from
   feedback@slack.com; enterprise / annual contracts move to Salesforce.
   This is the canonical case for why PDF parsing matters. Other
   Salesforce-billed products on the same pattern: Heroku, Tableau,
   MuleSoft, ExactTarget.

3. INGRAM IS A DISTRIBUTOR (direction matters)
   Real Ingram traffic in a ShiftControl-style inbox is mostly NOT for
   products the org consumes — it's invoices for products the org SELLS
   THROUGH Ingram to downstream customers. Only invoices where the org is
   the listed 'Bill To' / 'Customer' / 'Sold To' represent real
   subscription costs.

   Detection signal: parse the body / PDF for the customer-side field.
   Match the org → propose update. Different name → outbound channel sale
   → filter out, surface in 'channel / resale invoices skipped' section.

   Credit Memos and payment-received notifications: don't propose changes
   from these regardless of direction. They modify balance, not per-unit
   cost. Mention informationally.

4. CORRECTION: shared billing inboxes are GROUPS, not forwarding rules.
   Earlier text said the invoices@ alias appears as the SENDER because of
   forwarding rules — that's wrong. The pattern is:
   - invoices@<company>.io is a Google Group / DL on the RECEIVER side
   - Each SaaS subscription's billing-contact email is set to that address
   - Invoices arrive with the actual vendor as From and the group as To
   - Gmail's API sometimes shows the group as From when a personal mailbox
     reads via group membership (a display quirk, not a forwarding rule)

   SKILL.md Step 3 and invoice-detection.md updated to reflect this and to
   guide the skill toward 'to:invoices@<their-company>.io newer_than:18m'
   as the highest-precision starting search.
GPG_PRIVATE_KEY and GPG_PASSPHRASE are configured as ShiftControl-io
org-level secrets (already backing release workflows in other repos like
google_api_sdk). No per-repo setup needed; both secrets are automatically
available to this repo's release.yaml.

Replaced the 'CI signing key setup' how-to with a brief note pointing at
the org secrets, plus a section for forkers who'd need their own
equivalent setup outside ShiftControl-io.
Org-level secrets are an infra concern, not something a skill contributor
needs to know about. The release.yaml workflow references the secrets
directly; that's enough.
@shiftcontrol-dan
shiftcontrol-dan merged commit 8371e8b into main May 14, 2026
2 checks passed
@shiftcontrol-dan
shiftcontrol-dan deleted the dan/remove-propelauth-add-claude-ai branch May 14, 2026 07:27
shiftcontrol-dan added a commit that referenced this pull request May 14, 2026
* Bump VERSION to 0.2.0

PR #1 brought substantial changes since v0.1.0 — bumping to 0.2.0 to
trigger the new auto-release workflow and produce a v0.2.0 tag + release
with the current skill content.

What landed since v0.1.0 (per PR #1):

- Multi-platform install — new Claude.ai (web + mobile) connector section
  alongside Claude Desktop, Claude Code, Cursor, Windsurf, Cline,
  Continue.dev, ChatGPT, Gemini CLI
- refresh-subscription-info default search window: 90 days → 18 months
  (catches annual renewals)
- Full vs incremental invoice classification — prevents silent wrong
  per-user-cost extraction from prorated charges
- Notes replace-not-append — single 'Updated from X invoice dated Y' line
  per app, doesn't accumulate across monthly re-runs
- PDF parsing enabled (canonical case: Salesforce-billed Slack where
  product is only in the PDF)
- Web-hosted invoices: skill asks before fetching
- Real-world vendor catalog covering Slack, Notion, GitHub, Granola,
  Framer, Anthropic, 1Password, JumpCloud, Cloudflare, Zoom, AWS
- Distributors-vs-resellers direction handling (Ingram = distributor;
  Bill-To field disambiguates consumption vs channel sale)
- Shared billing inbox correction (Google Group on the receiver side)
- Outbound-payments and other false-positive filters
- CODEOWNERS routing to @shiftcontrol-dan
- INSTALL/README marketing-voice rewrite, audit-log terminology removed
- Auto-release workflow itself (.github/workflows/release.yaml) — what
  this VERSION bump exercises for the first time

Merging this PR triggers release.yaml, which signs and pushes a v0.2.0
tag and creates a GitHub release with the per-skill zip attached.

* validate-skills: drop PR path filter so the check always reports

Branch protection on main requires the 'validate' status check, but the
previous path filter ('skills/**') meant the workflow skipped on PRs that
didn't touch skill files (like #2, which only bumps VERSION). A skipped
required workflow leaves the check pending forever, blocking merge.

Fix: run on every PR into main. The workflow is cheap (yq frontmatter
checks + a few greps), so the cost of always running is negligible.

The push trigger keeps its path filter — no need to re-run validate on
main pushes that didn't touch skills.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants