shakers-cli 0.15.0 → 0.16.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.
Files changed (123) hide show
  1. package/README.md +51 -1203
  2. package/bin/ai-usage.js +11 -56
  3. package/bin/login.js +10 -10
  4. package/bin/mcp.js +13 -9
  5. package/bin/mcpb.js +1 -1
  6. package/bin/onboarding.js +3 -2
  7. package/bin/register.js +17 -4
  8. package/bin/report-html.js +2 -2
  9. package/bin/shakers-cli.js +352 -0
  10. package/bin/shakers.js +20 -344
  11. package/bin/share.js +1 -1
  12. package/bin/superadmin.js +5 -5
  13. package/bin/version.js +3 -0
  14. package/package.json +12 -3
  15. package/src/agent-category.js +1 -1
  16. package/src/agent-evaluation.js +3 -3
  17. package/src/agent-org-chart.js +5 -5
  18. package/src/agent-synthesis.js +3 -3
  19. package/src/agent-usage.js +1 -1
  20. package/src/agents-client.js +4 -4
  21. package/src/ai.js +1 -1
  22. package/src/ansi.js +1 -1
  23. package/src/auth-client.js +18 -4
  24. package/src/auth-method-picker.js +1 -1
  25. package/src/auth-methods.js +1 -1
  26. package/src/auth-session-store.js +11 -8
  27. package/src/authorship-mismatch.js +1 -1
  28. package/src/authorship.js +2 -2
  29. package/src/automations-detector.js +1 -1
  30. package/src/backend-request.js +3 -3
  31. package/src/brand-ansi.js +1 -1
  32. package/src/browser-tools-detector.js +1 -1
  33. package/src/certify-disclaimer.js +1 -1
  34. package/src/certify-remediation-prompt.js +1 -1
  35. package/src/cli-args.js +2 -2
  36. package/src/command-audit.js +8 -5
  37. package/src/config-dir.js +1 -1
  38. package/src/config.js +112 -48
  39. package/src/consent-flow.js +4 -4
  40. package/src/consent-skip.js +1 -1
  41. package/src/detection-evidence.js +2 -2
  42. package/src/detection-noise.js +1 -1
  43. package/src/detectors.js +1 -1
  44. package/src/device-login.js +12 -1
  45. package/src/email-verification.js +3 -3
  46. package/src/env-paths.js +1 -1
  47. package/src/find-projects-client.js +12 -0
  48. package/src/find-projects-flow.js +1 -13
  49. package/src/git-activity.js +1 -1
  50. package/src/graph-certs.js +3 -3
  51. package/src/graph-scan.js +9 -9
  52. package/src/i18n.js +124 -124
  53. package/src/interactive-select.js +1 -1
  54. package/src/interview-client.js +6 -1
  55. package/src/interview-transcript.js +1 -1
  56. package/src/legal-notice.js +1 -1
  57. package/src/maturity.js +6 -6
  58. package/src/mcp-ai-usage-tool.js +1 -1
  59. package/src/mcp-auth-form.js +1 -1
  60. package/src/mcp-detector.js +3 -3
  61. package/src/mcp-device-login-tools.js +2 -1
  62. package/src/mcp-find-projects-tools.js +1 -2
  63. package/src/mcp-onboarding-tools.js +4 -4
  64. package/src/mcp-prompts.js +2 -2
  65. package/src/mcp-register-tools.js +1 -0
  66. package/src/mcp-server.js +5 -1
  67. package/src/mcp-signup-client.js +2 -2
  68. package/src/mcp-signup-tools.js +26 -2
  69. package/src/memory-structure-detector.js +1 -1
  70. package/src/model-call-record.js +2 -2
  71. package/src/onboarding-flow.js +16 -16
  72. package/src/onboarding-funnel.js +110 -17
  73. package/src/open-file.js +1 -1
  74. package/src/osc-link.js +1 -1
  75. package/src/portfolio-client.js +11 -1
  76. package/src/portfolio-git-info.js +1 -1
  77. package/src/profile-flow.js +1 -1
  78. package/src/profile-switch.js +5 -12
  79. package/src/render-certification.js +10 -10
  80. package/src/render-html.js +6 -6
  81. package/src/render-sheet.js +10 -10
  82. package/src/render-terminal.js +6 -6
  83. package/src/repo-scope.js +132 -0
  84. package/src/report-gating.js +1 -1
  85. package/src/report-store.js +6 -6
  86. package/src/roadmap-content.js +5 -5
  87. package/src/roadmap-personalization.js +3 -3
  88. package/src/roadmap-prompt.js +2 -2
  89. package/src/scan-exclusions.js +1 -1
  90. package/src/scanner.js +14 -14
  91. package/src/secret-prompt.js +1 -1
  92. package/src/session-gate.js +1 -1
  93. package/src/session-refresh.js +33 -3
  94. package/src/session-scan.js +1 -1
  95. package/src/shakers-wordmark.js +1 -1
  96. package/src/share-card.js +2 -2
  97. package/src/share-certification.js +4 -4
  98. package/src/share-consent-state.js +7 -7
  99. package/src/share-payload.js +7 -7
  100. package/src/share.js +7 -7
  101. package/src/signup-client.js +1 -1
  102. package/src/signup-copy.js +1 -1
  103. package/src/skills-client.js +1 -1
  104. package/src/start-add-agents.js +2 -2
  105. package/src/start-add-portfolio.js +3 -13
  106. package/src/start-add-skills.js +2 -2
  107. package/src/stdin-ask.js +3 -3
  108. package/src/strip-internal-comments.js +1 -1
  109. package/src/tech-detector.js +2 -2
  110. package/src/templates/report-sheet.html +11 -13
  111. package/src/terminal-agents.js +8 -8
  112. package/src/terminal-format.js +3 -3
  113. package/src/terminal-sections.js +11 -11
  114. package/src/tier-engine.js +3 -3
  115. package/src/tier-projection.js +2 -2
  116. package/src/transcript-turns.js +1 -1
  117. package/src/typewriter.js +1 -1
  118. package/src/usage-model-calls.js +6 -6
  119. package/src/usage-oneshot-commands.js +5 -5
  120. package/src/usage-repo-scope.js +11 -129
  121. package/src/usage-run-steps.js +32 -184
  122. package/src/usage-scan.js +235 -0
  123. package/src/work-streams.js +1 -1
@@ -2,9 +2,10 @@
2
2
 
3
3
  const { postJsonWithTimeout, extractSetCookie } = require('./backend-request');
4
4
 
5
- // Login / token acquisition against the Hub's better-auth email engine (owner decision, supersedes ADR-031 for the email path).
5
+ // Login / token acquisition against the Hub's better-auth email engine.
6
6
 
7
7
  const DEFAULT_TIMEOUT_MS = 20000;
8
+ const SIGN_OUT_TIMEOUT_MS = 3000;
8
9
  const SESSION_COOKIE_NAME = 'better-auth.session_token';
9
10
  // Refresh the cookie session's own validity from the Set-Cookie Max-Age; fall
10
11
  // back to 7 days (better-auth's default) when the attribute is absent.
@@ -88,12 +89,13 @@ function normalizeSignIn(status, raw, headers) {
88
89
  }
89
90
 
90
91
  // Mints a hub JWT from a better-auth session cookie: `GET {tokenEndpoint}` with the `Cookie` header.
91
- async function requestAuthToken({ cookie } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
92
+ async function requestAuthToken({ cookie, sessionToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
92
93
  if (!endpoint) return { ok: false, reason: 'no-endpoint' };
93
- if (!cookie) return { ok: false, reason: 'no-cookie' };
94
+ if (!cookie && !sessionToken) return { ok: false, reason: 'no-cookie' };
95
+ const credential = cookie ? { Cookie: cookie } : { Authorization: `Bearer ${sessionToken}` };
94
96
  let res;
95
97
  try {
96
- res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, { Cookie: cookie });
98
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, credential);
97
99
  } catch (e) {
98
100
  return { ok: false, reason: (e && e.kind) || 'network-error' };
99
101
  }
@@ -133,7 +135,19 @@ async function requestLogin(
133
135
  };
134
136
  }
135
137
 
138
+ async function requestSignOut({ cookie, sessionToken } = {}, { endpoint, origin = null, timeoutMs = SIGN_OUT_TIMEOUT_MS } = {}) {
139
+ if (!endpoint || (!cookie && !sessionToken)) return { ok: false, reason: 'no-credential' };
140
+ const credential = cookie ? { Cookie: cookie, ...(origin ? { Origin: origin } : {}) } : { Authorization: `Bearer ${sessionToken}` };
141
+ try {
142
+ const res = await postJsonWithTimeout(endpoint, {}, timeoutMs, 'POST', null, credential);
143
+ return { ok: res.status >= 200 && res.status < 300, status: res.status };
144
+ } catch (e) {
145
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
146
+ }
147
+ }
148
+
136
149
  module.exports = {
150
+ requestSignOut,
137
151
  requestLogin,
138
152
  requestAuthToken,
139
153
  normalizeSignIn,
@@ -3,7 +3,7 @@
3
3
  const { runInteractiveMultiSelect } = require('./interactive-select');
4
4
  const { methodsForFlow, methodLabel } = require('./auth-methods');
5
5
 
6
- // The shared interactive method picker for the CLI (talents-ai-score).
6
+ // The shared interactive method picker for the CLI.
7
7
  async function askNumberedChoice(ask, promptText, count) {
8
8
  const raw = (await ask(promptText)).trim();
9
9
  if (!raw) return null;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // The SINGLE source of truth for which auth methods exist and where each one is offered (talents-ai-score).
3
+ // The SINGLE source of truth for which auth methods exist and where each one is offered.
4
4
  const AUTH_METHODS = [
5
5
  { id: 'email', labelKey: 'methodEmail', availableFor: { login: true, register: true } },
6
6
  { id: 'google', labelKey: 'methodGoogle', availableFor: { login: true, register: true } },
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const { getConfigDir } = require('./config-dir');
7
7
 
8
- // `~/.config/shakers/auth-session.json` — the logged-in Talent's session (talents-ai-score, issue 122 / ADR-044).
8
+ // `~/.config/shakers/auth-session.json` — the logged-in Talent's session.
9
9
 
10
10
  // Hub's `UserSubtypeEnum` (shared/domain/enum/user-subtype.enum.ts), copied VERBATIM — this repo never invents a value the wire does not send.
11
11
  const PLATFORM_TALENT_USER_TYPES = new Set(['WORKS_TALENT', 'WORKS_PARTNER']);
@@ -89,12 +89,12 @@ function saveAuthSession(session, env = process.env, { fs: fsImpl = fs } = {}) {
89
89
  const hubAccessToken = explicitHubToken || (typeof session.accessToken === 'string' && session.accessToken ? session.accessToken : null);
90
90
 
91
91
  const cookie = typeof session.cookie === 'string' && session.cookie ? session.cookie : null;
92
+ const sessionToken = typeof session.sessionToken === 'string' && session.sessionToken ? session.sessionToken : null;
92
93
  let expiresAt;
93
94
  let accessTokenExpiresAt;
94
- if (cookie) {
95
- expiresAt = (typeof session.cookieExpiresAt === 'string' && session.cookieExpiresAt)
96
- ? session.cookieExpiresAt
97
- : (typeof session.expiresAt === 'string' && session.expiresAt ? session.expiresAt : null);
95
+ if (cookie || sessionToken) {
96
+ expiresAt = [session.cookieExpiresAt, session.sessionExpiresAt, session.expiresAt]
97
+ .find((v) => typeof v === 'string' && v) || expiresAtFromToken(session.accessToken);
98
98
  accessTokenExpiresAt = (typeof session.accessTokenExpiresAt === 'string' && session.accessTokenExpiresAt)
99
99
  ? session.accessTokenExpiresAt
100
100
  : expiresAtFromToken(session.accessToken);
@@ -109,6 +109,7 @@ function saveAuthSession(session, env = process.env, { fs: fsImpl = fs } = {}) {
109
109
  expiresAt: expiresAt ?? null,
110
110
  accessTokenExpiresAt: accessTokenExpiresAt ?? null,
111
111
  cookie,
112
+ sessionToken,
112
113
  email: (typeof session.email === 'string' && session.email ? session.email : null)
113
114
  || emailFromHubToken(hubAccessToken),
114
115
  hubAccessToken,
@@ -157,7 +158,8 @@ function loadAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
157
158
  // better-auth) or a JWT (Google/legacy, or an email session whose JWT is cached).
158
159
  const hasCookie = typeof parsed.cookie === 'string' && parsed.cookie;
159
160
  const hasToken = typeof parsed.accessToken === 'string' && parsed.accessToken;
160
- if (!hasCookie && !hasToken) return null;
161
+ const sessionToken = typeof parsed.sessionToken === 'string' && parsed.sessionToken ? parsed.sessionToken : null;
162
+ if (!hasCookie && !hasToken && !sessionToken) return null;
161
163
  const hubAccessToken = typeof parsed.hubAccessToken === 'string' && parsed.hubAccessToken ? parsed.hubAccessToken : null;
162
164
  const email = (typeof parsed.email === 'string' && parsed.email ? parsed.email : null) || emailFromHubToken(hubAccessToken);
163
165
  return {
@@ -165,6 +167,7 @@ function loadAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
165
167
  expiresAt: typeof parsed.expiresAt === 'string' ? parsed.expiresAt : null,
166
168
  accessTokenExpiresAt: typeof parsed.accessTokenExpiresAt === 'string' ? parsed.accessTokenExpiresAt : null,
167
169
  cookie: hasCookie ? parsed.cookie : null,
170
+ sessionToken,
168
171
  email,
169
172
  hubAccessToken,
170
173
  userType: typeof parsed.userType === 'string' && KNOWN_USER_TYPES.has(parsed.userType) ? parsed.userType : null,
@@ -184,8 +187,8 @@ function clearAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
184
187
 
185
188
  // `none | active | expired`, the three states the caller needs to give the Talent a true message.
186
189
  function sessionStatus(session, now = Date.now()) {
187
- // A credential is either the durable cookie (email) or a JWT (Google/legacy).
188
- const hasCred = !!(session && ((typeof session.accessToken === 'string' && session.accessToken) || (typeof session.cookie === 'string' && session.cookie)));
190
+ // A credential is a durable cookie or session token, or a JWT (Google/legacy).
191
+ const hasCred = !!(session && [session.accessToken, session.cookie, session.sessionToken].some((v) => typeof v === 'string' && v));
189
192
  if (!hasCred) return 'none';
190
193
  const expiry = Date.parse(String(session.expiresAt || ''));
191
194
  if (!Number.isFinite(expiry)) return 'expired';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // Display-only authorship-transparency check (talents-ai-score).
3
+ // Display-only authorship-transparency check.
4
4
  //
5
5
  // Warns the talent when their commits in the selected repos are authored under
6
6
  // an email that is not their Shakers account email, so their own work would be
package/src/authorship.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { execFileSync } = require('child_process');
4
4
  const path = require('path');
5
5
 
6
- // Git AUTHORSHIP + provenance for the certify phase (skill-code-certification, ADR-017).
6
+ // Git AUTHORSHIP + provenance for the certify phase.
7
7
 
8
8
  const { takePrefetchedGitLog } = require('./git-log-prefetch');
9
9
 
@@ -164,7 +164,7 @@ function buildAttributionPredicate(verifiedEmail, authorizedSet) {
164
164
  };
165
165
  }
166
166
 
167
- // Applies the ADR-017 gate (with the ADR-023 widening for test identities) to ONE sampled Skill.
167
+ // Applies the authorship gate (widened for test identities) to ONE sampled Skill.
168
168
  function attributeSample(sample, verifiedEmail, authorship, authorizedSet = null) {
169
169
  const isAttributable = buildAttributionPredicate(verifiedEmail, authorizedSet);
170
170
  const files = Array.isArray(sample.files) ? sample.files : [];
@@ -5,7 +5,7 @@ const path = require('path');
5
5
  const { execFileSync } = require('child_process');
6
6
  const { getHomeDir } = require('./env-paths');
7
7
 
8
- // Deterministic (no-LLM) automations detector (talents-ai-score, issue 017 / ADR-013-014).
8
+ // Deterministic (no-LLM) automations detector.
9
9
 
10
10
  const AI_CLI_NAMES = ['claude', 'aider', 'gemini', 'codex'];
11
11
  const AI_CLI_RE = new RegExp(`\\b(${AI_CLI_NAMES.join('|')})\\b`, 'i');
@@ -5,7 +5,7 @@ const https = require('https');
5
5
  const crypto = require('crypto');
6
6
  const { getHubBase } = require('./config');
7
7
 
8
- // ONE BACKEND: the certifications service (talents-ai-score, ADR-042).
8
+ // ONE BACKEND: the certifications service.
9
9
 
10
10
  const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
11
11
 
@@ -31,7 +31,7 @@ function profileUpdateSourceHeader(url, method) {
31
31
  return { 'X-Profile-Update-Source': clientIdentity };
32
32
  }
33
33
 
34
- // Extra request headers, sanitized (talents-ai-score, issue 038 — the agent certification interview needs `Authorization: Bearer <sessionToken>`).
34
+ // Extra request headers, sanitized (the agent certification interview needs `Authorization: Bearer <sessionToken>`).
35
35
  const UNSAFE_HEADER_VALUE_RE = /[\r\n\0]/;
36
36
 
37
37
  function sanitizeExtraHeaders(extraHeaders) {
@@ -183,7 +183,7 @@ async function requestBackend({
183
183
  throw Object.assign(new Error('no endpoint configured'), { kind: 'no-endpoint' });
184
184
  }
185
185
 
186
- // Optional trace (talents-ai-score, issue 109).
186
+ // Optional trace.
187
187
  const note = (entry) => {
188
188
  if (Array.isArray(trace)) trace.push(entry);
189
189
  };
package/src/brand-ansi.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // BRAND_ANSI — the Shakers brand palette, reincarnated as ANSI 256-colour SGR codes (talents-ai-score, terminal design-consistency pass).
3
+ // BRAND_ANSI — the Shakers brand palette, reincarnated as ANSI 256-colour SGR codes.
4
4
 
5
5
  const BRAND_ANSI = {
6
6
  primary: '\x1b[38;5;73m',
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // Deterministic (no-LLM) browser tools detector (talents-ai-score, issue 018 / ADR-013-014).
3
+ // Deterministic (no-LLM) browser tools detector.
4
4
 
5
5
  const BROWSER_DEPENDENCY_NAMES = new Set(['playwright', '@playwright/test', 'puppeteer', 'puppeteer-core']);
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // Legal disclaimer + EXPLICIT acceptance gate (ADR-001), shown before egress. Skill-code certify was retired (certify is now a dimension LiveKit interview); consumers today are the add-skill/add-agent/add-project consent gates.
3
+ // Legal disclaimer + EXPLICIT acceptance gate, shown before egress. Skill-code certify was retired (certify is now a dimension LiveKit interview); consumers today are the add-skill/add-agent/add-project consent gates.
4
4
 
5
5
  const { renderLegalNotice } = require('./legal-notice');
6
6
  const { styleQuestion } = require('./ansi');
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { getCatalog } = require('./i18n');
4
4
 
5
- // Client-side "remediation prompt" for the certify report (skill-code- certification, issue 011).
5
+ // Client-side "remediation prompt" for the certify report.
6
6
  function buildRemediationPrompt(item, lang) {
7
7
  const r = getCatalog(lang).certify.report;
8
8
  const improvements =
package/src/cli-args.js CHANGED
@@ -39,7 +39,7 @@ function parseArgs(argv) {
39
39
  repo: false,
40
40
  scope: null,
41
41
  noAi: false,
42
- // Issue 103: turns the section reveal off. Same name in both parsers, and the
42
+ // Turns the section reveal off. Same name in both parsers, and the
43
43
  // env var `NO_ANIMATION` does the same thing (the `NO_COLOR` pattern of 079).
44
44
  noAnimation: false,
45
45
  };
@@ -59,7 +59,7 @@ function parseArgs(argv) {
59
59
  else if (a === '--show-endpoint') opts.showEndpoint = true;
60
60
  else if (a === '--build-next-level') opts.buildNextLevel = true;
61
61
  else if (a === '--force') opts.force = true;
62
- // ADR-016: the tier roadmap / "next steps" block is removed from the
62
+ // The tier roadmap / "next steps" block is removed from the
63
63
  // default footprint output; `--roadmap` (alias `--next`) shows it again.
64
64
  else if (a === '--roadmap' || a === '--next') opts.roadmap = true;
65
65
  else if (a === '--lang') opts.lang = VALID_LANGS.has(argv[++i]) ? argv[i] : null;
@@ -3,7 +3,7 @@
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
  const { scrubSecrets } = require('./agent-synthesis');
6
- const { loadAuthSession } = require('./auth-session-store');
6
+ const { loadAuthSession, decodeJwtPayload } = require('./auth-session-store');
7
7
  const { getCertsBase } = require('./config');
8
8
  const { postJsonWithTimeout } = require('./backend-request');
9
9
 
@@ -46,6 +46,11 @@ function machineHint() {
46
46
  return { osUser, hostname };
47
47
  }
48
48
 
49
+ function jwtExpired(token, now) {
50
+ const payload = decodeJwtPayload(token);
51
+ return !!payload && typeof payload.exp === 'number' && payload.exp * 1000 <= now;
52
+ }
53
+
49
54
  // Builds the record + headers; returns null when there is no endpoint to post to.
50
55
  function buildAudit(command, argv, env) {
51
56
  let base;
@@ -57,10 +62,8 @@ function buildAudit(command, argv, env) {
57
62
  if (!base) return null;
58
63
 
59
64
  const session = loadAuthSession(env);
60
- const token =
61
- session && (session.accessToken || session.hubAccessToken)
62
- ? session.accessToken || session.hubAccessToken
63
- : null;
65
+ const stored = (session && (session.accessToken || session.hubAccessToken)) || null;
66
+ const token = stored && !jwtExpired(stored, Date.now()) ? stored : null;
64
67
 
65
68
  const body = {
66
69
  command: String(command || 'unknown').slice(0, 100),
package/src/config-dir.js CHANGED
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const { getHomeDir, readEnv } = require('./env-paths');
6
6
 
7
- // Single source of truth for the CLI's config directory (ADR-045).
7
+ // Single source of truth for the CLI's config directory.
8
8
 
9
9
  const DIR_NAME = 'shakers';
10
10
  const LEGACY_DIR_NAME = 'ai-footprint';