shakers-cli-beta 0.14.2 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakers-cli-beta",
3
- "version": "0.14.2",
3
+ "version": "0.15.0",
4
4
  "description": "Shakers AI usage & skills evaluator: a local-first CLI (`shakers` binary) plus a local MCP server. Commands: ai-usage (scan + classify your AI-tool setup, T0-T7), certify (certify Skills from your code), share (branded LinkedIn card).",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -3,6 +3,7 @@
3
3
  const http = require('http');
4
4
  const https = require('https');
5
5
  const crypto = require('crypto');
6
+ const { getHubBase } = require('./config');
6
7
 
7
8
  // ONE BACKEND: the certifications service (talents-ai-score, ADR-042).
8
9
 
@@ -16,6 +17,20 @@ function setClientIdentity(identity) {
16
17
  clientIdentity = identity;
17
18
  }
18
19
 
20
+ // Hub profile writes declare which surface made them; reads, auth and the onboarding funnel are not profile updates.
21
+ const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
22
+ const HUB_PROFILE_PATH_RE = /^\/(works\/(talents?\/me|me|users\/me)|works\/talent\/(portfolios|certifications\/me))(\/|$)/;
23
+ const NOT_PROFILE_UPDATE_RE = /^\/works\/talent\/me\/onboarding-events(\/|$)/;
24
+
25
+ function profileUpdateSourceHeader(url, method) {
26
+ if (!MUTATING_METHODS.has(String(method).toUpperCase())) return {};
27
+ const hubBase = getHubBase();
28
+ if (!hubBase || !url.href.startsWith(`${hubBase}/`)) return {};
29
+ const path = url.href.slice(hubBase.length).split(/[?#]/)[0];
30
+ if (!HUB_PROFILE_PATH_RE.test(path) || NOT_PROFILE_UPDATE_RE.test(path)) return {};
31
+ return { 'X-Profile-Update-Source': clientIdentity };
32
+ }
33
+
19
34
  // Extra request headers, sanitized (talents-ai-score, issue 038 — the agent certification interview needs `Authorization: Bearer <sessionToken>`).
20
35
  const UNSAFE_HEADER_VALUE_RE = /[\r\n\0]/;
21
36
 
@@ -49,6 +64,7 @@ function postJsonWithTimeout(url, body, timeoutMs, method = 'POST', idempotencyK
49
64
  Accept: 'application/json',
50
65
  // Tells CLI and MCP traffic apart from the browser.
51
66
  'X-Shakers-Client': clientIdentity,
67
+ ...profileUpdateSourceHeader(u, method),
52
68
  // Only declared when there IS a body — see the bodyless note above.
53
69
  ...(data ? { 'Content-Type': 'application/json', 'Content-Length': data.length } : {}),
54
70
  ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
@@ -129,6 +145,7 @@ function postMultipartWithTimeout(url, fields, timeoutMs, method = 'POST', extra
129
145
  Accept: 'application/json',
130
146
  // See postJsonWithTimeout's identical header for why.
131
147
  'X-Shakers-Client': clientIdentity,
148
+ ...profileUpdateSourceHeader(u, method),
132
149
  'Content-Type': `multipart/form-data; boundary=${boundary}`,
133
150
  'Content-Length': data.length,
134
151
  ...sanitizeExtraHeaders(extraHeaders),