shakers-cli 0.0.0-stage → 0.14.1

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 (237) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1233 -2
  3. package/bin/add-agent.js +88 -0
  4. package/bin/add-project.js +88 -0
  5. package/bin/add-role.js +61 -0
  6. package/bin/add-skill.js +88 -0
  7. package/bin/ai-usage.js +99 -0
  8. package/bin/alma.js +99 -0
  9. package/bin/applications.js +69 -0
  10. package/bin/availability.js +70 -0
  11. package/bin/certifications.js +69 -0
  12. package/bin/certify.js +73 -0
  13. package/bin/change-role.js +61 -0
  14. package/bin/config.js +124 -0
  15. package/bin/experiences.js +69 -0
  16. package/bin/find-projects.js +89 -0
  17. package/bin/invitations.js +69 -0
  18. package/bin/lang.js +70 -0
  19. package/bin/login.js +216 -0
  20. package/bin/mcp.js +124 -0
  21. package/bin/mcpb.js +5 -0
  22. package/bin/me.js +69 -0
  23. package/bin/onboarding.js +134 -0
  24. package/bin/portfolios.js +69 -0
  25. package/bin/rate.js +70 -0
  26. package/bin/register.js +337 -0
  27. package/bin/report-html.js +134 -0
  28. package/bin/save.js +77 -0
  29. package/bin/shakers.js +363 -0
  30. package/bin/share.js +72 -0
  31. package/bin/show-project.js +72 -0
  32. package/bin/socials.js +70 -0
  33. package/bin/superadmin.js +351 -0
  34. package/bin/update-check-worker.js +70 -0
  35. package/bin/update.js +111 -0
  36. package/package.json +40 -4
  37. package/src/agent-category.js +13 -0
  38. package/src/agent-evaluation.js +177 -0
  39. package/src/agent-org-chart.js +355 -0
  40. package/src/agent-synthesis.js +132 -0
  41. package/src/agent-usage.js +118 -0
  42. package/src/agents-client.js +165 -0
  43. package/src/ai-fluency-cell.js +72 -0
  44. package/src/ai-profile-client.js +45 -0
  45. package/src/ai-profile-preview.js +154 -0
  46. package/src/ai.js +66 -0
  47. package/src/alma-client.js +123 -0
  48. package/src/alma-flow.js +32 -0
  49. package/src/alma-repo-context.js +47 -0
  50. package/src/alma-shell.js +161 -0
  51. package/src/ansi.js +62 -0
  52. package/src/applications-client.js +79 -0
  53. package/src/applications-flow.js +58 -0
  54. package/src/auth-client.js +144 -0
  55. package/src/auth-device-client.js +118 -0
  56. package/src/auth-method-picker.js +44 -0
  57. package/src/auth-methods.js +35 -0
  58. package/src/auth-session-store.js +215 -0
  59. package/src/authorship-mismatch.js +62 -0
  60. package/src/authorship.js +204 -0
  61. package/src/automations-detector.js +170 -0
  62. package/src/availability-client.js +93 -0
  63. package/src/availability-flow.js +124 -0
  64. package/src/backend-request.js +191 -0
  65. package/src/blind-approval-extract.js +48 -0
  66. package/src/brand-ansi.js +14 -0
  67. package/src/brand-svg.js +9 -0
  68. package/src/browser-tools-detector.js +21 -0
  69. package/src/build-next-level.js +56 -0
  70. package/src/certifications-client.js +89 -0
  71. package/src/certifications-flow.js +75 -0
  72. package/src/certify-dimension-client.js +163 -0
  73. package/src/certify-dimension-flow.js +135 -0
  74. package/src/certify-disclaimer.js +51 -0
  75. package/src/certify-remediation-prompt.js +23 -0
  76. package/src/cli-args.js +83 -0
  77. package/src/command-audit.js +105 -0
  78. package/src/config-dir.js +120 -0
  79. package/src/config.js +877 -0
  80. package/src/consent-flow.js +115 -0
  81. package/src/consent-skip.js +27 -0
  82. package/src/cv-file.js +62 -0
  83. package/src/cv-text.js +92 -0
  84. package/src/decisions-extract.js +43 -0
  85. package/src/detect-repos.js +145 -0
  86. package/src/detected-tools.js +15 -0
  87. package/src/detection-evidence.js +105 -0
  88. package/src/detection-noise.js +132 -0
  89. package/src/detectors.js +221 -0
  90. package/src/device-auth-store.js +100 -0
  91. package/src/device-login.js +120 -0
  92. package/src/email-verification.js +152 -0
  93. package/src/env-paths.js +30 -0
  94. package/src/find-projects-cache.js +89 -0
  95. package/src/find-projects-client.js +148 -0
  96. package/src/find-projects-flow.js +164 -0
  97. package/src/git-activity.js +124 -0
  98. package/src/git-log-prefetch.js +53 -0
  99. package/src/graph-certs.js +58 -0
  100. package/src/graph-generator.js +230 -0
  101. package/src/graph-scan.js +239 -0
  102. package/src/i18n.js +3341 -0
  103. package/src/inter-fonts.js +6 -0
  104. package/src/interactive-select.js +183 -0
  105. package/src/interview-client.js +226 -0
  106. package/src/interview-transcript.js +56 -0
  107. package/src/inventory-client.js +90 -0
  108. package/src/invitations-client.js +66 -0
  109. package/src/invitations-flow.js +50 -0
  110. package/src/lang-client.js +147 -0
  111. package/src/lang-codes.js +14 -0
  112. package/src/lang-flow.js +85 -0
  113. package/src/legal-notice.js +53 -0
  114. package/src/locale.js +72 -0
  115. package/src/markdown-ansi.js +50 -0
  116. package/src/maturity.js +111 -0
  117. package/src/mcp-ai-usage-result-tool.js +111 -0
  118. package/src/mcp-ai-usage-tool.js +401 -0
  119. package/src/mcp-alma-tools.js +95 -0
  120. package/src/mcp-applications-tools.js +44 -0
  121. package/src/mcp-auth-form.js +480 -0
  122. package/src/mcp-availability-tools.js +45 -0
  123. package/src/mcp-certifications-tools.js +44 -0
  124. package/src/mcp-certify-dimension-tools.js +66 -0
  125. package/src/mcp-choice.js +95 -0
  126. package/src/mcp-detector.js +223 -0
  127. package/src/mcp-device-login-tools.js +177 -0
  128. package/src/mcp-find-projects-tools.js +69 -0
  129. package/src/mcp-install.js +275 -0
  130. package/src/mcp-instructions.js +27 -0
  131. package/src/mcp-invitations-tools.js +44 -0
  132. package/src/mcp-lang-tools.js +41 -0
  133. package/src/mcp-onboarding-tools.js +379 -0
  134. package/src/mcp-portfolios-tools.js +50 -0
  135. package/src/mcp-profile-tools.js +48 -0
  136. package/src/mcp-prompts.js +73 -0
  137. package/src/mcp-rate-tools.js +44 -0
  138. package/src/mcp-register-tools.js +441 -0
  139. package/src/mcp-report-tools.js +105 -0
  140. package/src/mcp-role-tools.js +372 -0
  141. package/src/mcp-saved-positions-tools.js +59 -0
  142. package/src/mcp-server.js +290 -0
  143. package/src/mcp-show-project-tools.js +52 -0
  144. package/src/mcp-signup-client.js +118 -0
  145. package/src/mcp-signup-tools.js +1171 -0
  146. package/src/mcp-socials-tools.js +41 -0
  147. package/src/memory-structure-detector.js +108 -0
  148. package/src/model-call-record.js +198 -0
  149. package/src/onboarding-availability.js +26 -0
  150. package/src/onboarding-client.js +374 -0
  151. package/src/onboarding-flow.js +1162 -0
  152. package/src/onboarding-funnel.js +272 -0
  153. package/src/onboarding-livekit-client.js +102 -0
  154. package/src/onboarding-status-client.js +57 -0
  155. package/src/open-file.js +32 -0
  156. package/src/osc-link.js +16 -0
  157. package/src/portfolio-client.js +164 -0
  158. package/src/portfolio-git-info.js +61 -0
  159. package/src/portfolios-client.js +85 -0
  160. package/src/portfolios-flow.js +79 -0
  161. package/src/profile-client.js +133 -0
  162. package/src/profile-flow.js +115 -0
  163. package/src/profile-switch.js +46 -0
  164. package/src/prompt-select.js +66 -0
  165. package/src/rate-client.js +81 -0
  166. package/src/rate-flow.js +137 -0
  167. package/src/register-context.js +145 -0
  168. package/src/render-ai-fluency.js +266 -0
  169. package/src/render-certification.js +177 -0
  170. package/src/render-html.js +140 -0
  171. package/src/render-report-text.js +27 -0
  172. package/src/render-sheet.js +261 -0
  173. package/src/render-terminal.js +109 -0
  174. package/src/render-usage-report-text.js +155 -0
  175. package/src/repo-context.js +215 -0
  176. package/src/report-gating.js +49 -0
  177. package/src/report-store.js +226 -0
  178. package/src/report-theme.js +177 -0
  179. package/src/report-view-client.js +50 -0
  180. package/src/reveal-output.js +29 -0
  181. package/src/roadmap-content.js +453 -0
  182. package/src/roadmap-personalization.js +132 -0
  183. package/src/roadmap-prompt.js +154 -0
  184. package/src/roles-client.js +112 -0
  185. package/src/roles-flow.js +192 -0
  186. package/src/sanitize-network-text.js +36 -0
  187. package/src/saved-positions-client.js +71 -0
  188. package/src/saved-positions-flow.js +50 -0
  189. package/src/scan-exclusions.js +50 -0
  190. package/src/scanner.js +624 -0
  191. package/src/scope-signals.js +148 -0
  192. package/src/secret-prompt.js +72 -0
  193. package/src/session-gate.js +30 -0
  194. package/src/session-normalizers.js +174 -0
  195. package/src/session-refresh.js +51 -0
  196. package/src/session-scan.js +475 -0
  197. package/src/session-signals.js +36 -0
  198. package/src/shakers-wordmark.js +17 -0
  199. package/src/share-card.js +362 -0
  200. package/src/share-certification.js +111 -0
  201. package/src/share-consent-state.js +157 -0
  202. package/src/share-payload.js +300 -0
  203. package/src/share.js +185 -0
  204. package/src/show-project-client.js +58 -0
  205. package/src/show-project-flow.js +119 -0
  206. package/src/signup-client.js +103 -0
  207. package/src/signup-copy.js +450 -0
  208. package/src/signup-language.js +21 -0
  209. package/src/skills-client.js +102 -0
  210. package/src/socials-client.js +91 -0
  211. package/src/socials-flow.js +83 -0
  212. package/src/specificity-extract.js +41 -0
  213. package/src/start-add-agents.js +306 -0
  214. package/src/start-add-portfolio.js +388 -0
  215. package/src/start-add-skills.js +260 -0
  216. package/src/stdin-ask.js +129 -0
  217. package/src/steering-extract.js +43 -0
  218. package/src/strip-internal-comments.js +45 -0
  219. package/src/tech-detector.js +295 -0
  220. package/src/templates/report-sheet.html +572 -0
  221. package/src/terminal-agents.js +207 -0
  222. package/src/terminal-format.js +85 -0
  223. package/src/terminal-progress.js +106 -0
  224. package/src/terminal-sections.js +219 -0
  225. package/src/tier-analysis.js +126 -0
  226. package/src/tier-engine.js +114 -0
  227. package/src/tier-projection.js +117 -0
  228. package/src/transcript-turns.js +49 -0
  229. package/src/translations-client.js +84 -0
  230. package/src/typewriter.js +154 -0
  231. package/src/update-flow.js +223 -0
  232. package/src/update-notifier.js +113 -0
  233. package/src/usage-model-calls.js +119 -0
  234. package/src/usage-oneshot-commands.js +180 -0
  235. package/src/usage-repo-scope.js +162 -0
  236. package/src/usage-run-steps.js +354 -0
  237. package/src/work-streams.js +79 -0
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ // MCP: view the talent's social links. Read-only (editing is CLI-only).
4
+
5
+ const LIST_SOCIALS_SCHEMA = { type: 'object', properties: {} };
6
+
7
+ function makeSocialsTools(deps = {}) {
8
+ const {
9
+ fetchSocials = (opts) => require('./socials-client').fetchSocials({}, opts),
10
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
11
+ sessionStatus = require('./auth-session-store').sessionStatus,
12
+ } = deps;
13
+
14
+ const requireLoginSession = () => {
15
+ const session = loadAuthSession();
16
+ if (sessionStatus(session) !== 'active') {
17
+ throw new Error('no active Shakers session — sign in with social_signin_start (or run `shakers login`) first (list_socials is session-gated).');
18
+ }
19
+ return session;
20
+ };
21
+
22
+ async function listSocials() {
23
+ const session = requireLoginSession();
24
+ const res = await fetchSocials({ hubAccessToken: session.hubAccessToken });
25
+ if (!res.ok) return { ok: false, reason: res.reason };
26
+ return { ok: true, social: res.social };
27
+ }
28
+
29
+ return [
30
+ {
31
+ name: 'list_socials',
32
+ title: 'Your social links',
33
+ description: 'Shows the social and web links on your Shakers profile.',
34
+ guidance: "View the talent's social links (linkedin/github/website/twitter/instagram/facebook/dribbble/behance — null when unset). Read-only. Requires an active session.",
35
+ inputSchema: LIST_SOCIALS_SCHEMA,
36
+ handler: listSocials,
37
+ },
38
+ ];
39
+ }
40
+
41
+ module.exports = { makeSocialsTools, LIST_SOCIALS_SCHEMA };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getHomeDir } = require('./env-paths');
6
+
7
+ // Deterministic (no-LLM) memory STRUCTURE detector (talents-ai-score, issue 016 / ADR-013-014).
8
+
9
+ const MAX_FILE_BYTES = 2_000_000; // safety cap: never read a pathologically huge file
10
+ const MAX_IMPORT_DEPTH = 5; // mirrors Claude Code's own documented import-hop limit
11
+ const MAX_FILES_VISITED = 100; // safety cap against import fan-out/cycles
12
+
13
+ // Claude Code's `@path/to/file` import syntax: an `@` at the start of a line or preceded by whitespace, followed by a non-space path.
14
+ const IMPORT_RE = /(?:^|\s)@(\S+)/gm;
15
+ const HEADER_RE = /^#{1,6}\s+\S/gm;
16
+
17
+ function safeReadText(file) {
18
+ try {
19
+ const st = fs.statSync(file);
20
+ if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
21
+ return fs.readFileSync(file, 'utf8');
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function countMatches(re, text) {
28
+ const matches = text.match(re);
29
+ return matches ? matches.length : 0;
30
+ }
31
+
32
+ function extractImportPaths(text) {
33
+ const paths = [];
34
+ const re = new RegExp(IMPORT_RE);
35
+ let m;
36
+ while ((m = re.exec(text)) !== null) paths.push(m[1]);
37
+ return paths;
38
+ }
39
+
40
+ function resolveImportPath(importPath, fromDir, home) {
41
+ if (importPath.startsWith('~/')) return path.join(home, importPath.slice(2));
42
+ return path.resolve(fromDir, importPath);
43
+ }
44
+
45
+ // Recursively walks `@imports` starting at `file`.
46
+ function walkImports(file, home, depth, visited, budget) {
47
+ if (depth > MAX_IMPORT_DEPTH || visited.has(file) || budget.files >= MAX_FILES_VISITED) {
48
+ return { maxDepth: depth - 1, importCount: 0 };
49
+ }
50
+ visited.add(file);
51
+ budget.files += 1;
52
+
53
+ const text = safeReadText(file);
54
+ if (text === null) return { maxDepth: depth - 1, importCount: 0 };
55
+
56
+ const importPaths = extractImportPaths(text);
57
+ if (importPaths.length === 0) return { maxDepth: depth, importCount: 0 };
58
+
59
+ let maxDepth = depth;
60
+ let importCount = importPaths.length;
61
+ const fromDir = path.dirname(file);
62
+ for (const p of importPaths) {
63
+ const resolved = resolveImportPath(p, fromDir, home);
64
+ const sub = walkImports(resolved, home, depth + 1, visited, budget);
65
+ maxDepth = Math.max(maxDepth, sub.maxDepth);
66
+ importCount += sub.importCount;
67
+ }
68
+ return { maxDepth, importCount };
69
+ }
70
+
71
+ function analyzeFile(file, home) {
72
+ const text = safeReadText(file);
73
+ if (text === null) return null;
74
+ const sections = countMatches(HEADER_RE, text);
75
+ const sizeBytes = Buffer.byteLength(text, 'utf8');
76
+ const { maxDepth, importCount } = walkImports(file, home, 1, new Set(), { files: 0 });
77
+ return { sizeBytes, sections, imports: importCount, depth: maxDepth };
78
+ }
79
+
80
+ function knownContextFiles(root, home) {
81
+ return [
82
+ { id: 'CLAUDE.md', file: path.join(root, 'CLAUDE.md') },
83
+ { id: 'CLAUDE.md (home)', file: path.join(home, '.claude', 'CLAUDE.md') },
84
+ { id: 'AGENTS.md', file: path.join(root, 'AGENTS.md') },
85
+ { id: 'GEMINI.md', file: path.join(root, 'GEMINI.md') },
86
+ ];
87
+ }
88
+
89
+ // Deterministic (no-LLM) memory structure analysis, scoped to known context files (project ∪ home).
90
+ function analyzeMemoryStructure(root) {
91
+ const home = getHomeDir();
92
+ const files = [];
93
+ let totalImports = 0;
94
+ let maxDepth = 0;
95
+
96
+ for (const { id, file } of knownContextFiles(root, home)) {
97
+ const analysis = analyzeFile(file, home);
98
+ if (!analysis) continue;
99
+ files.push({ id, ...analysis });
100
+ totalImports += analysis.imports;
101
+ maxDepth = Math.max(maxDepth, analysis.depth);
102
+ }
103
+
104
+ // "layered" (anidada/por capas) means a REAL import chain — an import that itself imports further files — not just a single flat `@import`.
105
+ return { files, totalImports, maxDepth, layered: maxDepth > 2 };
106
+ }
107
+
108
+ module.exports = { analyzeMemoryStructure };
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+
3
+ // What happened with each model call, written down (talents-ai-score, issue 109).
4
+
5
+ // The three keys.
6
+ const MODEL_CALL = Object.freeze({
7
+ SYNTHESIS: 'agentSynthesis',
8
+ EVALUATION: 'agentEvaluation',
9
+ ROADMAP: 'roadmapPersonalization',
10
+ });
11
+
12
+ const REASON = Object.freeze({
13
+ // ok:true
14
+ OK: 'ok',
15
+ // ok:true AND INCOMPLETE (2026-08-03, from a real incident).
16
+ OK_PARTIAL: 'ok-partial',
17
+ // ok:null — never attempted, and NOT a failure
18
+ NO_AGENTS: 'no-agents',
19
+ NO_ENDPOINT: 'no-endpoint-configured',
20
+ MAX_TIER: 'max-tier',
21
+ NOT_APPLICABLE: 'not-applicable',
22
+ NO_CONSENT: 'no-consent',
23
+ // ok:false — attempted, and something was lost
24
+ TIMEOUT: 'timeout',
25
+ NETWORK: 'network-error',
26
+ INVALID_URL: 'invalid-endpoint-url',
27
+ HTTP: 'http-error',
28
+ MALFORMED_JSON: 'malformed-json',
29
+ UNEXPECTED_SHAPE: 'unexpected-shape',
30
+ LOCAL_ERROR: 'local-error',
31
+ });
32
+
33
+ // The reasons that mean the call produced usable data (`ok: true`).
34
+ const SUCCESS_REASONS = new Set([REASON.OK, REASON.OK_PARTIAL]);
35
+
36
+ // "Didn't apply" — the reasons that produce `ok: null`.
37
+ const SKIP_REASONS = new Set([
38
+ REASON.NO_AGENTS,
39
+ REASON.NO_ENDPOINT,
40
+ REASON.MAX_TIER,
41
+ REASON.NOT_APPLICABLE,
42
+ REASON.NO_CONSENT,
43
+ ]);
44
+
45
+ const FAILURE_REASONS = new Set([
46
+ REASON.TIMEOUT,
47
+ REASON.NETWORK,
48
+ REASON.INVALID_URL,
49
+ REASON.HTTP,
50
+ REASON.MALFORMED_JSON,
51
+ REASON.UNEXPECTED_SHAPE,
52
+ REASON.LOCAL_ERROR,
53
+ ]);
54
+
55
+ // The rejection `.kind` tags `src/backend-request.js` attaches, mapped onto this vocabulary.
56
+ function classifyChainError(e) {
57
+ const kind = e && e.kind;
58
+ if (kind === 'timeout') return REASON.TIMEOUT;
59
+ if (kind === 'network-error') return REASON.NETWORK;
60
+ if (kind === 'invalid-url') return REASON.INVALID_URL;
61
+ if (kind === 'no-endpoint') return REASON.NO_ENDPOINT;
62
+ return REASON.LOCAL_ERROR;
63
+ }
64
+
65
+ // The canonical record.
66
+ function record({ attemptedAt, ok, reason, status = null, backend = null, elapsedMs = null, timeoutMs = null, hops = [], omitted = [] }) {
67
+ return {
68
+ attemptedAt: attemptedAt || null,
69
+ ok,
70
+ reason,
71
+ status: typeof status === 'number' ? status : null,
72
+ backend: backend || null,
73
+ elapsedMs: typeof elapsedMs === 'number' ? elapsedMs : null,
74
+ timeoutMs: typeof timeoutMs === 'number' ? timeoutMs : null,
75
+ hops: Array.isArray(hops) ? hops : [],
76
+ // What the call did NOT cover, by name.
77
+ omitted: Array.isArray(omitted) ? omitted.filter((n) => typeof n === 'string' && n) : [],
78
+ };
79
+ }
80
+
81
+ // A call that never went out.
82
+ function skipped(reason) {
83
+ return record({ attemptedAt: null, ok: null, reason: reason || REASON.NOT_APPLICABLE });
84
+ }
85
+
86
+ // A failure that happened BEFORE the wire (building the request threw: a file that can't be read, a parse that blew up).
87
+ function failedLocally(reason = REASON.LOCAL_ERROR) {
88
+ return record({ attemptedAt: new Date().toISOString(), ok: false, reason });
89
+ }
90
+
91
+ // The one-shot reporter each client module uses.
92
+ function reporterFor(onOutcome, { timeoutMs = null } = {}) {
93
+ const attemptedAt = new Date().toISOString();
94
+ const startedHr = process.hrtime.bigint();
95
+ const hops = [];
96
+ let settled = false;
97
+
98
+ const emit = (built) => {
99
+ if (settled) return null;
100
+ settled = true;
101
+ if (typeof onOutcome === 'function') onOutcome(built);
102
+ return built;
103
+ };
104
+ const elapsed = () => Number((process.hrtime.bigint() - startedHr) / 1000000n);
105
+
106
+ return {
107
+ // Handed to `requestBackend` as `trace`.
108
+ hops,
109
+ skip(reason) {
110
+ return emit(skipped(reason));
111
+ },
112
+ fail(reason, { status = null, backend = null } = {}) {
113
+ return emit(record({ attemptedAt, ok: false, reason, status, backend, elapsedMs: elapsed(), timeoutMs, hops }));
114
+ },
115
+ failFromError(e) {
116
+ return this.fail(classifyChainError(e));
117
+ },
118
+ // `omitted` non-empty flips the reason to `ok-partial` and NOT `ok` — the one decision that stops a partial answer from being recorded as a clean run.
119
+ succeed({ status = null, backend = null, omitted = [] } = {}) {
120
+ const left = Array.isArray(omitted) ? omitted.filter((n) => typeof n === 'string' && n) : [];
121
+ return emit(record({
122
+ attemptedAt,
123
+ ok: true,
124
+ reason: left.length ? REASON.OK_PARTIAL : REASON.OK,
125
+ status,
126
+ backend,
127
+ elapsedMs: elapsed(),
128
+ timeoutMs,
129
+ hops,
130
+ omitted: left,
131
+ }));
132
+ },
133
+ };
134
+ }
135
+
136
+ // Attaches a record to the report under its call key.
137
+ function attachModelCall(report, key, built) {
138
+ if (!report || typeof report !== 'object' || !key) return built;
139
+ if (!report.modelCalls || typeof report.modelCalls !== 'object') report.modelCalls = {};
140
+ report.modelCalls[key] = built;
141
+ return built;
142
+ }
143
+
144
+ function modelCall(report, key) {
145
+ const calls = report && report.modelCalls;
146
+ return calls && typeof calls === 'object' ? calls[key] || null : null;
147
+ }
148
+
149
+ // The two predicates the render layer is allowed to ask.
150
+ function callFailed(report, key) {
151
+ const c = modelCall(report, key);
152
+ return !!c && c.ok === false;
153
+ }
154
+
155
+ function callSucceeded(report, key) {
156
+ const c = modelCall(report, key);
157
+ return !!c && c.ok === true;
158
+ }
159
+
160
+ // The call worked AND left something out.
161
+ function callPartial(report, key) {
162
+ const c = modelCall(report, key);
163
+ return !!c && c.ok === true && Array.isArray(c.omitted) && c.omitted.length > 0;
164
+ }
165
+
166
+ // THE CALL LOST TIME TO A DEADLINE — and this is NOT `reason === TIMEOUT` (issue 116, found by measuring, not by reading).
167
+ function callTimedOut(report, key) {
168
+ const c = modelCall(report, key);
169
+ if (!c || c.ok !== false) return false;
170
+ if (c.reason === REASON.TIMEOUT) return true;
171
+ return Array.isArray(c.hops) && c.hops.some((h) => h && h.kind === REASON.TIMEOUT);
172
+ }
173
+
174
+ // The names the call left out, for the surface that names them. `[]` when the run
175
+ // was complete, so a caller never has to null-check before iterating.
176
+ function omittedByCall(report, key) {
177
+ const c = modelCall(report, key);
178
+ return c && Array.isArray(c.omitted) ? c.omitted : [];
179
+ }
180
+
181
+ module.exports = {
182
+ MODEL_CALL,
183
+ REASON,
184
+ SKIP_REASONS,
185
+ FAILURE_REASONS,
186
+ SUCCESS_REASONS,
187
+ classifyChainError,
188
+ skipped,
189
+ failedLocally,
190
+ reporterFor,
191
+ attachModelCall,
192
+ modelCall,
193
+ callFailed,
194
+ callSucceeded,
195
+ callPartial,
196
+ callTimedOut,
197
+ omittedByCall,
198
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { loadAuthSession, sessionStatus } = require('./auth-session-store');
4
+ const { getOnboardingStatusEndpoint } = require('./config');
5
+ const { requestOnboardingStatus } = require('./onboarding-status-client');
6
+
7
+ async function checkOnboardingCompleted({
8
+ env = process.env,
9
+ session = null,
10
+ loadSession = loadAuthSession,
11
+ endpointGetter = getOnboardingStatusEndpoint,
12
+ fetchStatus = requestOnboardingStatus,
13
+ timeoutMs,
14
+ } = {}) {
15
+ const active = session || loadSession();
16
+ if (sessionStatus(active) !== 'active') return false;
17
+ const endpoint = endpointGetter(env);
18
+ if (!endpoint) return false;
19
+ const res = await fetchStatus(
20
+ { accessToken: active.accessToken },
21
+ timeoutMs ? { endpoint, timeoutMs } : { endpoint },
22
+ );
23
+ return res.ok ? res.completed === true : false;
24
+ }
25
+
26
+ module.exports = { checkOnboardingCompleted };