@ssheleg/sheleg-dev 0.10.5 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,70 @@ All notable changes to this project are documented here.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
  Versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.11.0 — a trigger literal gets one home, and four facts get their dates
8
+
9
+ The 2026-08-29 skill audit found the pack's two Google skills advertising the same
10
+ end-user sign-in triggers, one skill re-teaching another's contract more weakly, and
11
+ three external facts stated without the date they were true on. All of it shipped green
12
+ through a 23-check validator, because none of it was a check yet.
13
+
14
+ - **`google-auth` and `google-signin` no longer compete for the same prompt** (audit
15
+ DEV-01, HIGH). Both front-matter descriptions carried `"google login"`, `"GIS"`,
16
+ `"Sign In with Google"` and «вход через Google» — four literal collisions between the
17
+ skill that owns end-user sign-in and the skill that owns the server library surface,
18
+ with which one loads left to the host's guess. The end-user set is stripped from
19
+ `google-auth`, whose description now speaks the server vocabulary (ADC, service
20
+ accounts and their keys, Workload Identity Federation, `verifyIdToken`); the
21
+ cross-deferral sentence stays. A new validator check,
22
+ `check_trigger_literals_have_one_home`, compares the quoted trigger literals of every
23
+ skill pair and refuses a collision — watched failing in both directions (a re-planted
24
+ `"google login"`, and a stale entry in `TRIGGERS_SHARED_BY_DESIGN`, the enumerated
25
+ exemption that carries the one deliberate share: `webhook signature`, which the
26
+ umbrella routes to `stripe-billing`). One new negative self-test in CI; the local
27
+ floor moves 42 → 43. The verdict line moves 23 → 24 checks.
28
+ - **`google-auth` stops re-teaching ID-token verification with a weaker contract**
29
+ (DEV-02). Its section 3 taught the sign-in verification omitting the `nonce` binding
30
+ and `email_verified` — the two checks `google-signin`'s checklist adds above the
31
+ library call, drifting live under a section that duplicated the other skill's core
32
+ job. Cut to the bare `verifyIdToken` / `verify_oauth2_token` call plus an explicit
33
+ statement of what the call does NOT check and where the sign-in contract lives. The
34
+ partial `g_csrf_token` bullet in Security Best Practices is likewise now a pointer.
35
+ - **The Stripe `apiVersion` pins carry their date** (DEV-03). `2026-07-29.dahlia` was
36
+ pinned undated in a document that itself says the API moves monthly — and it was
37
+ already one release behind: checked 2026-08-30 against `docs.stripe.com/changelog`,
38
+ where `2026-08-26.dahlia` is current. Both pin sites now say when they were true and
39
+ send the reader to the changelog before pinning.
40
+ - **The crypto `sign()` block is labeled as ONE provider's scheme** (DEV-04). The
41
+ SKILL taught `md5(base64(json) + apiKey)` under a provider-neutral heading two
42
+ screens after claiming the section holds for four providers — it is Heleket's scheme
43
+ alone. Checked 2026-08-30: Coinbase Commerce signs HMAC-SHA256
44
+ (`X-CC-Webhook-Signature`), NOWPayments HMAC-SHA512 (`x-nowpayments-sig`), BTCPay
45
+ HMAC-SHA256 (`BTCPay-Sig`). The pattern transfers; the algorithm does not, and the
46
+ intro now says which is which.
47
+ - **`allow_enhanced_conversions` moves to the tag it belongs on** (DEV-05). The setup
48
+ step put the flag on the GA4 `G-` config, where it enables nothing for Google Ads;
49
+ where it appears at all it belongs on the `AW-` config command. Checked 2026-08-30:
50
+ Google's current setup docs (`support.google.com/google-ads/answer/9888145`,
51
+ `…/13258081`) no longer document the flag — the live mechanism is the account-level
52
+ toggle plus `gtag('set', 'user_data', …)` — and the step now states both facts.
53
+ - **`ad-tracking`'s UTM Attribution section moved into
54
+ `references/event-tracking.md`.** The DEV-05 fix pushed the body past the 4750-token
55
+ house working limit (23 tokens of headroom going in), and the house answer at the
56
+ limit is a split, not a trim — same seam as v0.10.0's cancellation split.
57
+ Body ~4753 tokens after the move; `stripe-billing`'s recomputed numbers in
58
+ `docs/evals/stripe-billing.md` moved with its dated pins (4737 tokens, 383 lines).
59
+ - **The social card now names all five trades** (umbrella deferral, 2026-08-29). The
60
+ committed `docs/assets/social-preview.png` is compared pixel-for-pixel by the
61
+ umbrella's site test against the card it generates from its `skills.json` role cell,
62
+ and the role grows `errors` for the seventh skill. Regenerated with the umbrella's
63
+ own generator from the exact cell it will adopt — `integrations: money in, tracking,
64
+ errors, sign-in, speed` — verified by reproducing the previous committed card
65
+ byte-for-byte from the previous cell first. README and `marketplace.json` carry the
66
+ same five-trade phrasing. Measured while regenerating: at scale 3 the new eyebrow
67
+ paints to 1 px of the canvas edge, because the umbrella's `fitScale` measures without
68
+ the tracking `drawText` adds — nothing is truncated, and the fix (if wanted) belongs
69
+ in `og-card.js`, not here.
70
+
7
71
  ## v0.10.5 — the installers refuse the shadow they used to write
8
72
 
9
73
  Both install channels — `npx @ssheleg/sheleg-dev` and `install.sh` — now consult the
package/README.md CHANGED
@@ -23,7 +23,7 @@ Agent Skills standard directly, scanning `~/.agents/skills` — where `npx skill
23
23
  add` puts this pack — at rank 500.
24
24
 
25
25
  The integration layer a product reaches once it has users: **money in, tracking,
26
- sign-in, and speed.**
26
+ errors, sign-in, and speed.**
27
27
 
28
28
  Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
29
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/sheleg-dev",
3
- "version": "0.10.5",
3
+ "version": "0.11.0",
4
4
  "description": "Seven integration skills for the layer a product reaches once it has users: Stripe subscription billing and cancellation retention, crypto payments, ad and conversion tracking under Consent Mode v2, Google sign-in and the wider Google auth surface, error tracking, and Core Web Vitals. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "sheleg-dev": "bin/sheleg-dev.js"
@@ -3,7 +3,7 @@
3
3
  "name": "sheleg-dev",
4
4
  "displayName": "Sheleg Dev",
5
5
  "description": "The integration layer a product needs once it has users: Stripe subscription billing reconciled into your own database, crypto payments that survive over-payment and duplicate webhooks, ad and conversion tracking under Consent Mode v2, Google sign-in with the account-linking guards, the wider Google auth surface, and Core Web Vitals work that moves the score, and Sentry error tracking wired so it does not forward your own credentials to a third party and so a stack trace names the commit that caused it.",
6
- "version": "0.10.5",
6
+ "version": "0.11.0",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -157,7 +157,20 @@ Both share consent state, first-party cookies, and the dataLayer pipeline.
157
157
 
158
158
  Enhanced Conversions improve attribution by matching hashed user data (email, phone) with Google accounts. Setup:
159
159
 
160
- 1. Enable in GA4 config: `allow_enhanced_conversions: true`
160
+ 1. Where the flag appears at all, it belongs on the **Google Ads config — the
161
+ `AW-` tag, never the GA4 `G-` tag** (an earlier revision of this skill put it
162
+ on the `G-` config, where it enables nothing for Ads):
163
+
164
+ ```javascript
165
+ gtag('config', 'AW-XXXXXXXXX', { allow_enhanced_conversions: true });
166
+ ```
167
+
168
+ Checked 2026-08-30: Google's current setup docs
169
+ (`support.google.com/google-ads/answer/9888145`, `…/13258081`) no longer
170
+ document the flag at all — the live mechanism is the account-level toggle
171
+ (step 3) plus `user_data` (step 2). Keep the flag if your tag already
172
+ carries it; do not copy it onto the `G-` config.
173
+
161
174
  2. Set user data before conversion events fire:
162
175
 
163
176
  ```javascript
@@ -323,32 +336,10 @@ Strategies** for `next/script` strategy choice and SPA route changes.
323
336
  the parent page's.
324
337
  ## UTM Attribution
325
338
 
326
- ### Capture Flow
327
-
328
- 1. `UtmCapture` component reads UTM params from URL on page load → stores in localStorage
329
- 2. On registration (credentials): read UTM from localStorage, send in POST body, save to DB
330
- 3. On registration (OAuth): UTM persists in localStorage through redirect → synced after return via API call
331
- 4. Mixpanel `people.set_once()` stores initial UTM as permanent user properties
332
- 5. Clear localStorage after successful DB sync
333
-
334
- ### UTM Parameters
335
-
336
- | Parameter | Purpose | Example |
337
- |---|---|---|
338
- | `utm_source` | Traffic source | `google`, `facebook`, `newsletter` |
339
- | `utm_medium` | Marketing medium | `cpc`, `social`, `email` |
340
- | `utm_campaign` | Campaign name | `spring_sale_2025` |
341
- | `utm_content` | Ad creative variant | `banner_a`, `text_link` |
342
- | `utm_term` | Paid search keyword | `ai+chatbot` |
343
-
344
- ### gclid / fbclid / li_fat_id
345
-
346
- Google, Meta, and LinkedIn append their own click IDs to URLs:
347
- - `gclid` — Google Ads click ID (stored in `_gcl_aw` cookie by gtag.js automatically)
348
- - `fbclid` — Meta click ID (stored in `_fbc` cookie by fbevents.js automatically)
349
- - `li_fat_id` — LinkedIn click ID (handled by Insight Tag)
350
-
351
- These are handled automatically by the respective SDKs. Do not strip them from URLs.
339
+ Read `references/event-tracking.md` → **UTM Attribution** for the capture flow
340
+ (localStorage through OAuth redirects into the DB), the five-parameter table,
341
+ and the platform click IDs (`gclid`/`fbclid`/`li_fat_id`) — which the SDKs
342
+ handle automatically; do not strip them from URLs.
352
343
 
353
344
  ---
354
345
 
@@ -13,6 +13,8 @@
13
13
  - [Ecommerce Events](#ecommerce-events)
14
14
  - [SaaS / Subscription Events](#saas--subscription-events)
15
15
  - [trackEvent Wrapper Pattern](#trackevent-wrapper-pattern)
16
+ - [User identification](#user-identification)
17
+ - [UTM Attribution](#utm-attribution)
16
18
 
17
19
  ## Table of Contents
18
20
 
@@ -23,6 +25,8 @@
23
25
  - [Ecommerce Events](#ecommerce-events)
24
26
  - [SaaS / Subscription Events](#saas--subscription-events)
25
27
  - [trackEvent Wrapper Pattern](#trackevent-wrapper-pattern)
28
+ - [User identification](#user-identification)
29
+ - [UTM Attribution](#utm-attribution)
26
30
 
27
31
  ## Event Types
28
32
 
@@ -243,3 +247,38 @@ Moved out of `SKILL.md` on 2026-08-16: the body was 5273 tokens against a
243
247
  < 5000 budget, and identity is the other half of the question this file already
244
248
  owns — an event name says what happened, identity says who it happened to.
245
249
 
250
+
251
+ ---
252
+
253
+ ## UTM Attribution
254
+
255
+ Moved out of `SKILL.md` on 2026-08-30: the body crossed the 4750-token house
256
+ working limit when the Enhanced Conversions fix landed, and attribution
257
+ parameters are schema — the question this file already owns.
258
+
259
+ ### Capture Flow
260
+
261
+ 1. `UtmCapture` component reads UTM params from URL on page load → stores in localStorage
262
+ 2. On registration (credentials): read UTM from localStorage, send in POST body, save to DB
263
+ 3. On registration (OAuth): UTM persists in localStorage through redirect → synced after return via API call
264
+ 4. Mixpanel `people.set_once()` stores initial UTM as permanent user properties
265
+ 5. Clear localStorage after successful DB sync
266
+
267
+ ### UTM Parameters
268
+
269
+ | Parameter | Purpose | Example |
270
+ |---|---|---|
271
+ | `utm_source` | Traffic source | `google`, `facebook`, `newsletter` |
272
+ | `utm_medium` | Marketing medium | `cpc`, `social`, `email` |
273
+ | `utm_campaign` | Campaign name | `spring_sale_2025` |
274
+ | `utm_content` | Ad creative variant | `banner_a`, `text_link` |
275
+ | `utm_term` | Paid search keyword | `ai+chatbot` |
276
+
277
+ ### gclid / fbclid / li_fat_id
278
+
279
+ Google, Meta, and LinkedIn append their own click IDs to URLs:
280
+ - `gclid` — Google Ads click ID (stored in `_gcl_aw` cookie by gtag.js automatically)
281
+ - `fbclid` — Meta click ID (stored in `_fbc` cookie by fbevents.js automatically)
282
+ - `li_fat_id` — LinkedIn click ID (handled by Insight Tag)
283
+
284
+ These are handled automatically by the respective SDKs. Do not strip them from URLs.
@@ -24,7 +24,9 @@ production.
24
24
  This skill is provider-neutral. Concrete request/response shapes for one gateway
25
25
  live in [`references/heleket-provider.md`](references/heleket-provider.md);
26
26
  the invariants below hold for Coinbase Commerce, NOWPayments, BTCPay, Heleket
27
- and anything else that issues an invoice and calls you back.
27
+ and anything else that issues an invoice and calls you back. One thing is NOT
28
+ provider-neutral and is labeled where it appears: the webhook signature
29
+ *algorithm*, which differs per gateway.
28
30
 
29
31
  > **Choosing a provider is a business and compliance decision, not a technical
30
32
  > one.** Crypto payment processors differ sharply in regulatory standing —
@@ -117,9 +119,20 @@ waterfall below.
117
119
 
118
120
  The callback is an unauthenticated public endpoint until you prove otherwise.
119
121
 
122
+ **The `sign()` below is ONE provider's scheme — Heleket's:
123
+ `md5(base64(json) + apiKey)`.** The *pattern* (constant-time compare, sign the
124
+ raw body, verify before parsing) transfers; the algorithm does not. Checked
125
+ 2026-08-30: Coinbase Commerce signs with **HMAC-SHA256** of the raw body
126
+ (`X-CC-Webhook-Signature`), NOWPayments with **HMAC-SHA512** of the
127
+ sorted-key JSON (`x-nowpayments-sig`), BTCPay with **HMAC-SHA256**
128
+ (`BTCPay-Sig`). Take the algorithm from your provider's own reference — for
129
+ Heleket's full shapes see
130
+ [`references/heleket-provider.md`](references/heleket-provider.md).
131
+
120
132
  ```ts
121
133
  import { createHash, timingSafeEqual } from 'node:crypto';
122
134
 
135
+ // Heleket's scheme. Swap the digest for your provider — see the note above.
123
136
  function sign(payload: unknown, apiKey: string): string {
124
137
  const json = JSON.stringify(payload);
125
138
  return createHash('md5')
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: google-auth
3
3
  description: >-
4
- Use when implementing Google authentication in a Node.js or Python web application — Google
5
- login, OAuth 2.0 flows, verifying Google ID tokens, service account authentication,
6
- Application Default Credentials, Google Identity Services, Workload Identity Federation, API
7
- keys, or working with google-auth-library (Node.js) or google-auth (Python). Covers ID token
8
- verification and security best practices. Triggers - "google auth", "google login", "google
9
- sign in", "OAuth 2.0 Google", "google-auth-library", "Sign In with Google", "Google Identity
10
- Services", "GIS", "ADC", "Application Default Credentials", "service account", "Google ID
11
- token", "verifyIdToken", "GOOGLE_APPLICATION_CREDENTIALS", "Google SSO", "вход через Google",
12
- "авторизация Google", "сервисный аккаунт", "проверить ID-токен", "гугл-логин". For end-user
13
- web sign-in only, use the google-signin skill instead.
4
+ Use when a server authenticates to Google in a Node.js or Python application — OAuth 2.0
5
+ flows, verifying Google ID tokens server-side, service account authentication and keys,
6
+ Application Default Credentials, Workload Identity Federation, API keys, or working with
7
+ google-auth-library (Node.js) or google-auth (Python). Covers server-side ID token
8
+ verification and security best practices. Triggers - "google auth", "OAuth 2.0 Google",
9
+ "google-auth-library", "ADC", "Application Default Credentials", "service account",
10
+ "Workload Identity Federation", "Google ID token", "verifyIdToken",
11
+ "GOOGLE_APPLICATION_CREDENTIALS", "Google SSO", "авторизация Google", "сервисный аккаунт",
12
+ "ключи сервисного аккаунта", "проверить ID-токен". For end-user web sign-in only, use the
13
+ google-signin skill instead.
14
14
  ---
15
15
 
16
16
  # Google Authentication for Node.js & Python
@@ -45,7 +45,7 @@ pip install google-api-python-client
45
45
  |--------|----------|-------------------|---------------------------|
46
46
  | **ADC** | Same identity for all users, server-to-server | `GoogleAuth` | `google.auth.default()` |
47
47
  | **OAuth 2.0** | Actions on behalf of end users | `OAuth2Client` | `google_auth_oauthlib.flow.Flow` |
48
- | **Sign In with Google (GIS)** | User sign-in/sign-up on websites | GIS JS SDK + `verifyIdToken()` | GIS JS SDK + `id_token.verify_oauth2_token()` |
48
+ | **Sign In with Google (GIS)** | User sign-in/sign-up on websites — the google-signin skill's ground | GIS JS SDK + `verifyIdToken()` | GIS JS SDK + `id_token.verify_oauth2_token()` |
49
49
  | **JWT / Service Account** | Server-to-server, single identity | `JWT` | `service_account.Credentials` |
50
50
  | **API Key** | Public data, no user context | `OAuth2Client({ apiKey })` | passed to `googleapiclient.discovery.build(developerKey=)` |
51
51
  | **Compute** | On GCP with attached service account | `Compute` | `google.auth.compute_engine.Credentials` |
@@ -134,7 +134,7 @@ credentials = flow.credentials
134
134
 
135
135
  For the complete OAuth 2.0 flow (parameters, token exchange, refresh, revocation, incremental auth), see [references/oauth2-web-server.md](references/oauth2-web-server.md).
136
136
 
137
- ### 3. Sign In with Google — ID Token Verification
137
+ ### 3. Verifying a Google ID Token — the library call
138
138
 
139
139
  **Node.js**
140
140
 
@@ -147,10 +147,6 @@ const ticket = await client.verifyIdToken({
147
147
  audience: WEB_CLIENT_ID,
148
148
  });
149
149
  const payload = ticket.getPayload();
150
- const userId = payload['sub'];
151
- const email = payload['email'];
152
- const name = payload['name'];
153
- const picture = payload['picture'];
154
150
  ```
155
151
 
156
152
  **Python**
@@ -159,22 +155,14 @@ const picture = payload['picture'];
159
155
  from google.oauth2 import id_token
160
156
  from google.auth.transport import requests
161
157
 
162
- request = requests.Request()
163
-
164
- payload = id_token.verify_oauth2_token(
165
- token,
166
- request,
167
- WEB_CLIENT_ID
168
- )
169
- user_id = payload['sub']
170
- email = payload['email']
171
- name = payload.get('name', '')
172
- picture = payload.get('picture')
158
+ payload = id_token.verify_oauth2_token(token, requests.Request(), WEB_CLIENT_ID)
173
159
  ```
174
160
 
175
- Verification checks: JWT signature, `aud`, `exp`, `iss` (accounts.google.com).
176
-
177
- For GIS integration, CSRF protection, and hosted domain validation, see [references/sign-in-with-google.md](references/sign-in-with-google.md).
161
+ The call checks signature, `aud`, `exp` and `iss` — **and nothing else**. That is
162
+ the library contract, not the web sign-in contract: for the sign-in security
163
+ checklist (`nonce` binding, `email_verified`, login-CSRF defense, account
164
+ linking) use the **google-signin** skill — it is the one home for that contract,
165
+ and this skill deliberately does not restate it.
178
166
 
179
167
  ### 4. JWT / Service Account
180
168
 
@@ -264,7 +252,7 @@ if credentials.expired and credentials.refresh_token:
264
252
  - Store `refresh_token` securely; it's only returned on first authorization
265
253
  - Validate external credential configurations before use (check `token_url`, `service_account_impersonation_url` point to googleapis.com)
266
254
  - Prefer Workload Identity Federation over service account keys for non-GCP environments
267
- - For GIS: verify `g_csrf_token` with double-submit-cookie pattern
255
+ - For end-user web sign-in, apply the google-signin skill's full checklist — a partial restatement here is how the two skills drifted apart once already
268
256
  - **Python-specific**: reuse a single `google.auth.transport.requests.Request()` instance across verifications for connection pooling; do not create a new one per call in hot paths
269
257
 
270
258
  ## Reference Files
@@ -90,6 +90,8 @@ export function getStripe(): Stripe {
90
90
  if (!client) {
91
91
  const key = process.env.STRIPE_SECRET_KEY;
92
92
  if (!key) throw new Error("STRIPE_SECRET_KEY is not set");
93
+ // Pin checked 2026-08-30 — 2026-08-26.dahlia was already newer. Confirm
94
+ // against Stripe's changelog before pinning; this line goes stale monthly.
93
95
  client = new Stripe(key, { apiVersion: "2026-07-29.dahlia", maxNetworkRetries: 2 });
94
96
  }
95
97
  return client;
@@ -171,7 +173,7 @@ await stripe.checkout.sessions.create({
171
173
 
172
174
  `{CHECKOUT_SESSION_ID}` is substituted by Stripe, not rendered by you. On
173
175
  `2026-03-25.dahlia`+ an `integration_identifier` label lets you compare flows in
174
- the Dashboard.
176
+ the Dashboard (floor checked 2026-08-30 against Stripe's changelog).
175
177
 
176
178
  Three decisions belong to `stripe-best-practices`: **usage-based billing**
177
179
  (Metronome for anything new; Billing Meters is a low-level primitive), **tax**