shakers-cli-beta 0.8.0-beta.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 (218) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1175 -0
  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 +81 -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 +120 -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 +283 -0
  20. package/bin/mcp.js +98 -0
  21. package/bin/me.js +69 -0
  22. package/bin/onboarding.js +134 -0
  23. package/bin/portfolios.js +69 -0
  24. package/bin/rate.js +70 -0
  25. package/bin/register.js +325 -0
  26. package/bin/report-html.js +134 -0
  27. package/bin/save.js +77 -0
  28. package/bin/shakers.js +344 -0
  29. package/bin/share.js +72 -0
  30. package/bin/show-project.js +72 -0
  31. package/bin/socials.js +70 -0
  32. package/bin/superadmin.js +351 -0
  33. package/package.json +40 -0
  34. package/src/agent-category.js +13 -0
  35. package/src/agent-evaluation.js +177 -0
  36. package/src/agent-org-chart.js +355 -0
  37. package/src/agent-synthesis.js +132 -0
  38. package/src/agent-usage.js +118 -0
  39. package/src/agents-client.js +165 -0
  40. package/src/ai-fluency-cell.js +72 -0
  41. package/src/ai-profile-client.js +45 -0
  42. package/src/ai-profile-preview.js +154 -0
  43. package/src/ai.js +66 -0
  44. package/src/alma-client.js +123 -0
  45. package/src/alma-flow.js +32 -0
  46. package/src/alma-repo-context.js +47 -0
  47. package/src/alma-shell.js +161 -0
  48. package/src/ansi.js +62 -0
  49. package/src/applications-client.js +79 -0
  50. package/src/applications-flow.js +58 -0
  51. package/src/auth-client.js +144 -0
  52. package/src/auth-device-client.js +118 -0
  53. package/src/auth-method-picker.js +44 -0
  54. package/src/auth-methods.js +35 -0
  55. package/src/auth-session-store.js +214 -0
  56. package/src/authorship.js +203 -0
  57. package/src/automations-detector.js +170 -0
  58. package/src/availability-client.js +92 -0
  59. package/src/availability-flow.js +105 -0
  60. package/src/backend-request.js +170 -0
  61. package/src/blind-approval-extract.js +48 -0
  62. package/src/brand-ansi.js +14 -0
  63. package/src/brand-svg.js +9 -0
  64. package/src/browser-tools-detector.js +21 -0
  65. package/src/build-next-level.js +56 -0
  66. package/src/certifications-client.js +89 -0
  67. package/src/certifications-flow.js +75 -0
  68. package/src/certify-dimension-client.js +217 -0
  69. package/src/certify-dimension-flow.js +193 -0
  70. package/src/certify-disclaimer.js +51 -0
  71. package/src/certify-remediation-prompt.js +23 -0
  72. package/src/cli-args.js +83 -0
  73. package/src/config-dir.js +120 -0
  74. package/src/config.js +778 -0
  75. package/src/consent-flow.js +115 -0
  76. package/src/consent-skip.js +27 -0
  77. package/src/decisions-extract.js +43 -0
  78. package/src/detect-repos.js +145 -0
  79. package/src/detected-tools.js +15 -0
  80. package/src/detection-evidence.js +105 -0
  81. package/src/detection-noise.js +132 -0
  82. package/src/detectors.js +221 -0
  83. package/src/device-auth-store.js +100 -0
  84. package/src/device-login.js +118 -0
  85. package/src/email-verification.js +152 -0
  86. package/src/env-paths.js +16 -0
  87. package/src/find-projects-cache.js +89 -0
  88. package/src/find-projects-client.js +138 -0
  89. package/src/find-projects-flow.js +164 -0
  90. package/src/git-activity.js +123 -0
  91. package/src/graph-certs.js +58 -0
  92. package/src/graph-generator.js +230 -0
  93. package/src/graph-scan.js +239 -0
  94. package/src/i18n.js +3264 -0
  95. package/src/inter-fonts.js +6 -0
  96. package/src/interactive-select.js +183 -0
  97. package/src/interview-client.js +226 -0
  98. package/src/interview-transcript.js +56 -0
  99. package/src/inventory-client.js +90 -0
  100. package/src/invitations-client.js +66 -0
  101. package/src/invitations-flow.js +50 -0
  102. package/src/lang-client.js +147 -0
  103. package/src/lang-codes.js +14 -0
  104. package/src/lang-flow.js +85 -0
  105. package/src/legal-notice.js +53 -0
  106. package/src/locale.js +51 -0
  107. package/src/markdown-ansi.js +50 -0
  108. package/src/maturity.js +111 -0
  109. package/src/mcp-ai-usage-result-tool.js +109 -0
  110. package/src/mcp-ai-usage-tool.js +333 -0
  111. package/src/mcp-alma-tools.js +91 -0
  112. package/src/mcp-applications-tools.js +42 -0
  113. package/src/mcp-ask-tools.js +50 -0
  114. package/src/mcp-auth-form.js +282 -0
  115. package/src/mcp-availability-tools.js +43 -0
  116. package/src/mcp-certifications-tools.js +42 -0
  117. package/src/mcp-certify-dimension-tools.js +58 -0
  118. package/src/mcp-detector.js +223 -0
  119. package/src/mcp-device-login-tools.js +173 -0
  120. package/src/mcp-find-projects-tools.js +67 -0
  121. package/src/mcp-install.js +245 -0
  122. package/src/mcp-invitations-tools.js +42 -0
  123. package/src/mcp-lang-tools.js +39 -0
  124. package/src/mcp-onboarding-tools.js +388 -0
  125. package/src/mcp-portfolios-tools.js +46 -0
  126. package/src/mcp-profile-tools.js +46 -0
  127. package/src/mcp-rate-tools.js +42 -0
  128. package/src/mcp-register-tools.js +517 -0
  129. package/src/mcp-report-tools.js +101 -0
  130. package/src/mcp-role-tools.js +112 -0
  131. package/src/mcp-saved-positions-tools.js +55 -0
  132. package/src/mcp-server.js +170 -0
  133. package/src/mcp-show-project-tools.js +50 -0
  134. package/src/mcp-socials-tools.js +39 -0
  135. package/src/memory-structure-detector.js +108 -0
  136. package/src/model-call-record.js +198 -0
  137. package/src/onboarding-availability.js +26 -0
  138. package/src/onboarding-client.js +364 -0
  139. package/src/onboarding-flow.js +1078 -0
  140. package/src/onboarding-livekit-client.js +102 -0
  141. package/src/onboarding-status-client.js +42 -0
  142. package/src/open-file.js +32 -0
  143. package/src/osc-link.js +16 -0
  144. package/src/portfolio-client.js +164 -0
  145. package/src/portfolio-git-info.js +61 -0
  146. package/src/portfolios-client.js +85 -0
  147. package/src/portfolios-flow.js +79 -0
  148. package/src/profile-client.js +125 -0
  149. package/src/profile-flow.js +115 -0
  150. package/src/profile-switch.js +46 -0
  151. package/src/prompt-select.js +29 -0
  152. package/src/rate-client.js +82 -0
  153. package/src/rate-flow.js +137 -0
  154. package/src/render-ai-fluency.js +266 -0
  155. package/src/render-certification.js +177 -0
  156. package/src/render-html.js +140 -0
  157. package/src/render-report-text.js +27 -0
  158. package/src/render-sheet.js +261 -0
  159. package/src/render-terminal.js +109 -0
  160. package/src/render-usage-report-text.js +155 -0
  161. package/src/repo-context.js +215 -0
  162. package/src/report-gating.js +49 -0
  163. package/src/report-store.js +226 -0
  164. package/src/report-theme.js +177 -0
  165. package/src/report-view-client.js +50 -0
  166. package/src/reveal-output.js +29 -0
  167. package/src/roadmap-content.js +453 -0
  168. package/src/roadmap-personalization.js +132 -0
  169. package/src/roadmap-prompt.js +154 -0
  170. package/src/roles-client.js +114 -0
  171. package/src/roles-flow.js +192 -0
  172. package/src/sanitize-network-text.js +36 -0
  173. package/src/saved-positions-client.js +71 -0
  174. package/src/saved-positions-flow.js +50 -0
  175. package/src/scan-exclusions.js +50 -0
  176. package/src/scanner.js +612 -0
  177. package/src/scope-signals.js +148 -0
  178. package/src/secret-prompt.js +72 -0
  179. package/src/session-gate.js +30 -0
  180. package/src/session-refresh.js +51 -0
  181. package/src/session-scan.js +336 -0
  182. package/src/session-signals.js +36 -0
  183. package/src/shakers-wordmark.js +17 -0
  184. package/src/share-card.js +362 -0
  185. package/src/share-certification.js +111 -0
  186. package/src/share-consent-state.js +157 -0
  187. package/src/share-payload.js +300 -0
  188. package/src/share.js +184 -0
  189. package/src/show-project-client.js +58 -0
  190. package/src/show-project-flow.js +119 -0
  191. package/src/signup-client.js +91 -0
  192. package/src/skills-client.js +102 -0
  193. package/src/socials-client.js +91 -0
  194. package/src/socials-flow.js +83 -0
  195. package/src/specificity-extract.js +41 -0
  196. package/src/start-add-agents.js +306 -0
  197. package/src/start-add-portfolio.js +388 -0
  198. package/src/start-add-skills.js +260 -0
  199. package/src/stdin-ask.js +129 -0
  200. package/src/steering-extract.js +43 -0
  201. package/src/strip-internal-comments.js +45 -0
  202. package/src/tech-detector.js +295 -0
  203. package/src/templates/report-sheet.html +572 -0
  204. package/src/terminal-agents.js +199 -0
  205. package/src/terminal-format.js +85 -0
  206. package/src/terminal-progress.js +106 -0
  207. package/src/terminal-sections.js +219 -0
  208. package/src/tier-analysis.js +126 -0
  209. package/src/tier-engine.js +114 -0
  210. package/src/tier-projection.js +117 -0
  211. package/src/transcript-turns.js +49 -0
  212. package/src/translations-client.js +84 -0
  213. package/src/typewriter.js +154 -0
  214. package/src/usage-model-calls.js +119 -0
  215. package/src/usage-oneshot-commands.js +180 -0
  216. package/src/usage-repo-scope.js +162 -0
  217. package/src/usage-run-steps.js +338 -0
  218. package/src/work-streams.js +74 -0
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ // `lang` flow: view the talent's languages + levels, and `--set` (interactive,
4
+ // confirmation) to add/update one. The write is REPLACE-ALL, so we merge the
5
+ // chosen language into the current set and PATCH the whole list.
6
+
7
+ const { styleLabelPrefix } = require('./ansi');
8
+ const { LEVELS } = require('./lang-client');
9
+
10
+ function makeLangDeps(overrides = {}) {
11
+ const client = require('./lang-client');
12
+ return {
13
+ fetchLanguages: (opts) => client.fetchLanguages({}, opts),
14
+ fetchLanguageCatalog: (opts) => client.fetchLanguageCatalog({}, opts),
15
+ saveLanguages: (opts) => client.saveLanguages({}, opts),
16
+ promptSelect: (opts) => require('./prompt-select').promptSelect(opts),
17
+ ...overrides,
18
+ };
19
+ }
20
+
21
+ function renderView(io, fp, languages) {
22
+ io.section(fp.title);
23
+ if (!languages.length) { io.notify(fp.none); io.success(fp.done); return; }
24
+ languages.forEach((l) => io.notify(styleLabelPrefix(fp.line(l.name || l.code || fp.unknownLang, l.level ? fp.levelLabel(l.level) : fp.unknownLevel))));
25
+ io.success(fp.done);
26
+ }
27
+
28
+ async function runSet({ io, ask, session, catalog, opts, deps }) {
29
+ const fp = catalog.lang;
30
+ const hubAccessToken = session ? session.hubAccessToken : null;
31
+ if (!opts.stdinIsTTY) { io.error(fp.setNeedsInteractive); return { ok: false, reason: 'non-interactive' }; }
32
+ const selOut = (s) => io.notify(s);
33
+
34
+ const cur = await io.withProgress(fp.loading, () => deps.fetchLanguages({ hubAccessToken }));
35
+ if (!cur.ok) { io.error(fp.fetchFailed(cur.reason)); return { ok: false, reason: cur.reason }; }
36
+ const cat = await io.withProgress(fp.loadingCatalog, () => deps.fetchLanguageCatalog({}));
37
+ if (!cat.ok) { io.error(fp.catalogFailed(cat.reason)); return { ok: false, reason: cat.reason }; }
38
+
39
+ const current = Array.isArray(cur.languages) ? cur.languages : [];
40
+ io.section(fp.setTitle);
41
+ if (current.length) current.forEach((l) => io.notify(styleLabelPrefix(fp.line(l.name || l.code, l.level ? fp.levelLabel(l.level) : fp.unknownLevel))));
42
+
43
+ const langItem = await deps.promptSelect({
44
+ ask, stdinIsTTY: opts.stdinIsTTY, out: selOut, header: fp.pickLanguage,
45
+ items: cat.catalog, labelFor: (l) => l.name, input: opts.input, output: opts.output,
46
+ });
47
+ if (!langItem) { io.notify(fp.setCancelled); return { ok: true, cancelled: true }; }
48
+
49
+ const levelItem = await deps.promptSelect({
50
+ ask, stdinIsTTY: opts.stdinIsTTY, out: selOut, header: fp.pickLevel,
51
+ items: LEVELS.map((v) => ({ label: fp.levelLabel(v), value: v })), labelFor: (x) => x.label, input: opts.input, output: opts.output,
52
+ });
53
+ if (!levelItem) { io.notify(fp.setCancelled); return { ok: true, cancelled: true }; }
54
+
55
+ // Merge into the full set (replace-all): keep every other language, upsert this one.
56
+ const merged = current.filter((l) => l.id !== langItem.id).map((l) => ({ id: l.id, level: l.level }));
57
+ merged.push({ id: langItem.id, level: levelItem.value });
58
+
59
+ io.notify(fp.setDiff(langItem.name, fp.levelLabel(levelItem.value)));
60
+ const confirmed = await io.confirm(fp.setConfirm);
61
+ if (!confirmed) { io.notify(fp.setCancelled); return { ok: true, cancelled: true }; }
62
+
63
+ const res = await io.withProgress(fp.setSaving, () => deps.saveLanguages({ hubAccessToken, languages: merged }));
64
+ if (!res.ok) { io.error(fp.setFailed(res.reason)); return { ok: false, reason: res.reason }; }
65
+ io.success(fp.setDone(langItem.name));
66
+ return { ok: true, set: { id: langItem.id, level: levelItem.value } };
67
+ }
68
+
69
+ async function runLang({ io, rawOut, ask, session, catalog, opts = {}, deps = makeLangDeps() }) {
70
+ const fp = catalog.lang;
71
+ if (opts.set) return runSet({ io, ask, session, catalog, opts, deps });
72
+
73
+ const hubAccessToken = session ? session.hubAccessToken : null;
74
+ const doFetch = () => deps.fetchLanguages({ hubAccessToken });
75
+ const res = opts.json ? await doFetch() : await io.withProgress(fp.loading, doFetch);
76
+ if (!res.ok) { io.error(fp.fetchFailed(res.reason)); return { ok: false, reason: res.reason }; }
77
+ if (opts.json) {
78
+ (rawOut || ((s) => process.stdout.write(s)))(`${JSON.stringify({ ok: true, languages: res.languages }, null, 2)}\n`);
79
+ return { ok: true, languages: res.languages };
80
+ }
81
+ renderView(io, fp, res.languages);
82
+ return { ok: true, languages: res.languages };
83
+ }
84
+
85
+ module.exports = { makeLangDeps, runLang, renderView };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ // Shared PRESENTATION for the CLI's legal/consent disclosures (talents-ai-score).
4
+
5
+ const { palette, colorEnabled } = require('./ansi');
6
+ const { BRAND_ANSI } = require('./brand-ansi');
7
+
8
+ const RULE_CHAR = '─';
9
+ const RULE_WIDTH = 62;
10
+
11
+ function renderLegalNotice(text, catalog, { stream = process.stdout } = {}) {
12
+ if (!colorEnabled({ stream })) return text;
13
+ const ANSI = palette({ reset: '\x1b[0m', bold: '\x1b[1m', warning: BRAND_ANSI.warning }, { stream });
14
+ const rule = `${ANSI.warning}${RULE_CHAR.repeat(RULE_WIDTH)}${ANSI.reset}`;
15
+ const label = catalog && catalog.legalNotice ? catalog.legalNotice.label : null;
16
+ const lines = [];
17
+ if (label) lines.push(`${ANSI.bold}${ANSI.warning}⚠ ${label}${ANSI.reset}`);
18
+ lines.push(rule, text, rule);
19
+ // TRAILING NEWLINE (colour path only) — WHERE THE ANSWER LANDS.
20
+ return lines.join('\n') + '\n';
21
+ }
22
+
23
+ function splitTrailingPrompt(text) {
24
+ const s = String(text == null ? '' : text);
25
+ const bracket = s.match(/\[[^\]\n]*\/[^\]\n]*\]\s*$/);
26
+ if (!bracket) return { body: s, prompt: '' };
27
+ const beforeBracket = s.slice(0, bracket.index).replace(/\s+$/, '');
28
+ if (!beforeBracket) return { body: '', prompt: s }; // the bracket IS the whole text
29
+ const lastChar = beforeBracket.slice(-1);
30
+ const isSentenceEnd = lastChar === '.' || lastChar === '?' || lastChar === '!';
31
+ // Search for the PRECEDING boundary in everything BEFORE that terminator — never in the terminator itself.
32
+ const searchIn = isSentenceEnd ? beforeBracket.slice(0, -1) : beforeBracket;
33
+ const boundary = Math.max(searchIn.lastIndexOf('. '), searchIn.lastIndexOf('? '), searchIn.lastIndexOf('! '));
34
+ if (boundary === -1) return { body: '', prompt: s };
35
+ return { body: s.slice(0, boundary + 1), prompt: s.slice(boundary + 2) };
36
+ }
37
+
38
+ // Returns `{ box, prompt }`.
39
+ function renderLegalNoticeSplit(text, catalog, { stream = process.stdout } = {}) {
40
+ if (!colorEnabled({ stream })) return { box: null, prompt: text };
41
+ const { body, prompt } = splitTrailingPrompt(text);
42
+ if (!prompt) return { box: renderLegalNotice(text, catalog, { stream }), prompt: null };
43
+ const ANSI = palette({ reset: '\x1b[0m', bold: '\x1b[1m', warning: BRAND_ANSI.warning }, { stream });
44
+ const rule = `${ANSI.warning}${RULE_CHAR.repeat(RULE_WIDTH)}${ANSI.reset}`;
45
+ const label = catalog && catalog.legalNotice ? catalog.legalNotice.label : null;
46
+ const lines = [];
47
+ if (label) lines.push(`${ANSI.bold}${ANSI.warning}⚠ ${label}${ANSI.reset}`);
48
+ if (body) lines.push(rule, body, rule);
49
+ else lines.push(rule); // the whole text WAS the question — nothing to box.
50
+ return { box: lines.join('\n'), prompt };
51
+ }
52
+
53
+ module.exports = { renderLegalNotice, renderLegalNoticeSplit, splitTrailingPrompt };
package/src/locale.js ADDED
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync } = require('child_process');
4
+
5
+ // Operating system language detection, to localize the report (see i18n.js).
6
+
7
+ // Extracts a 2-letter language code ("es", "en"...) from a common locale string: "es_ES.UTF-8", "es-ES", "es", or a list "es_ES:en" (LANGUAGE format, the first element is taken).
8
+ function langFromLocaleString(raw) {
9
+ if (!raw) return null;
10
+ const value = String(raw).trim();
11
+ if (!value || /^(c|posix)$/i.test(value)) return null;
12
+ const first = value.split(':')[0];
13
+ const match = first.match(/^([a-zA-Z]{2})/);
14
+ return match ? match[1].toLowerCase() : null;
15
+ }
16
+
17
+ function langFromEnv(env) {
18
+ for (const key of ['LC_ALL', 'LANG', 'LANGUAGE']) {
19
+ const lang = langFromLocaleString(env[key]);
20
+ if (lang) return lang;
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function langFromAppleLocale() {
26
+ if (process.platform !== 'darwin') return null;
27
+ try {
28
+ const out = execFileSync('defaults', ['read', '-g', 'AppleLocale'], {
29
+ timeout: 2000,
30
+ stdio: ['ignore', 'pipe', 'ignore'],
31
+ }).toString('utf8');
32
+ return langFromLocaleString(out);
33
+ } catch {
34
+ return null; // key not set, command missing, or any other failure: ignored
35
+ }
36
+ }
37
+
38
+ function langFromIntl() {
39
+ try {
40
+ return langFromLocaleString(Intl.DateTimeFormat().resolvedOptions().locale);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ // Returns the detected language code ('es', 'en', 'fr', ...) or null if no signal could be resolved.
47
+ function detectLangCode(env = process.env) {
48
+ return langFromEnv(env) || langFromAppleLocale() || langFromIntl() || null;
49
+ }
50
+
51
+ module.exports = { detectLangCode, langFromLocaleString };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const { palette } = require('./ansi');
4
+ const { BRAND_ANSI } = require('./brand-ansi');
5
+
6
+ function mdPalette(opts = {}) {
7
+ return palette({
8
+ bold: '\x1b[1m',
9
+ italic: '\x1b[3m',
10
+ dim: '\x1b[2m',
11
+ code: BRAND_ANSI.accent,
12
+ heading: `\x1b[1m${BRAND_ANSI.primary}`,
13
+ reset: '\x1b[0m',
14
+ }, opts);
15
+ }
16
+
17
+ function inline(text, C) {
18
+ const codes = [];
19
+ let s = String(text).replace(/`([^`]+)`/g, (_m, c) => { codes.push(c); return `\u0000${codes.length - 1}\u0000`; });
20
+ s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, u) => `${t} (${u})`);
21
+ s = s.replace(/\*\*([^*]+)\*\*/g, (_m, x) => `${C.bold}${x}${C.reset}`);
22
+ s = s.replace(/__([^_]+)__/g, (_m, x) => `${C.bold}${x}${C.reset}`);
23
+ s = s.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, (_m, p, x) => `${p}${C.italic}${x}${C.reset}`);
24
+ s = s.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, (_m, p, x) => `${p}${C.italic}${x}${C.reset}`);
25
+ s = s.replace(/\u0000(\d+)\u0000/g, (_m, i) => `${C.code}${codes[Number(i)]}${C.reset}`);
26
+ return s;
27
+ }
28
+
29
+ function renderMarkdown(text, opts = {}) {
30
+ const C = mdPalette(opts);
31
+ const lines = String(text ?? '').split('\n');
32
+ const out = [];
33
+ let inFence = false;
34
+ for (const raw of lines) {
35
+ if (/^\s*```/.test(raw)) { inFence = !inFence; continue; }
36
+ if (inFence) { out.push(`${C.dim}${raw}${C.reset}`); continue; }
37
+ const h = raw.match(/^\s*(#{1,6})\s+(.*)$/);
38
+ if (h) { out.push(`${C.heading}${inline(h[2], C)}${C.reset}`); continue; }
39
+ const bq = raw.match(/^\s*>\s?(.*)$/);
40
+ if (bq) { out.push(`${C.dim}│${C.reset} ${inline(bq[1], C)}`); continue; }
41
+ const b = raw.match(/^(\s*)[-*+]\s+(.*)$/);
42
+ if (b) { out.push(`${b[1]}${C.bold}•${C.reset} ${inline(b[2], C)}`); continue; }
43
+ const n = raw.match(/^(\s*)(\d+)\.\s+(.*)$/);
44
+ if (n) { out.push(`${n[1]}${C.bold}${n[2]}.${C.reset} ${inline(n[3], C)}`); continue; }
45
+ out.push(inline(raw, C));
46
+ }
47
+ return out.join('\n');
48
+ }
49
+
50
+ module.exports = { renderMarkdown };
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const { computeTierResult, AGENTIC_IDS } = require('./tier-engine');
4
+
5
+ // AI usage maturity classification.
6
+
7
+ const LEVELS = [
8
+ { level: 0, key: 'none', name: 'Sin rastro de IA', emoji: '○' },
9
+ { level: 1, key: 'exploring', name: 'Explorando', emoji: '◔' },
10
+ { level: 2, key: 'integrated', name: 'Integrado', emoji: '◑' },
11
+ { level: 3, key: 'power', name: 'Power user', emoji: '◕' },
12
+ { level: 4, key: 'orchestrator', name: 'Orquestador', emoji: '●' },
13
+ ];
14
+
15
+ // DECISION (talents-ai-score, signal expansion): the level definitions (LEVELS, HANDOFF §4) have NOT been touched.
16
+ function depthTotals(tools) {
17
+ let instructions = 0; // project instructions/rules files
18
+ let mcp = 0; // configured MCP servers
19
+ let custom = 0; // own skills + commands + rules
20
+ let hooks = 0;
21
+
22
+ for (const t of tools) {
23
+ if (!t.detected) continue;
24
+ const d = t.depth || {};
25
+ instructions += (d.instructions || 0) + (d.config || 0);
26
+ mcp += d.mcpServers || 0;
27
+ custom += (d.skills || 0) + (d.commands || 0) + (d.rules || 0);
28
+ hooks += d.hooks || 0;
29
+ }
30
+ return { instructions, mcp, custom, hooks };
31
+ }
32
+
33
+ // Score model 0-100 for the visual meter — RECALIBRATED (ADR-008, skill-code-certification).
34
+ const SCORE_MODEL = [
35
+ { key: 'breadth', weight: 12, full: 3 }, // number of AI tools detected
36
+ { key: 'context', weight: 16, full: 2 }, // instructions + config files
37
+ { key: 'mcp', weight: 18, full: 2 }, // configured MCP servers
38
+ { key: 'custom', weight: 20, full: 4 }, // own skills + commands + rules
39
+ { key: 'agentic', weight: 8, full: 1 }, // has an agentic CLI (0/1)
40
+ { key: 'hooks', weight: 14, full: 1 }, // hook-based automation
41
+ { key: 'multiAgent', weight: 12, full: 2 }, // agent definitions (>=2 = top)
42
+ ];
43
+
44
+ function computeScore({ breadth, context, mcp, custom, agentic, hooks, multiAgent }) {
45
+ const values = { breadth, context, mcp, custom, agentic, hooks, multiAgent };
46
+ let total = 0;
47
+ for (const dim of SCORE_MODEL) {
48
+ const v = values[dim.key] || 0;
49
+ total += dim.weight * Math.min(v / dim.full, 1);
50
+ }
51
+ return Math.max(0, Math.min(100, Math.round(total)));
52
+ }
53
+
54
+ function classify(report) {
55
+ const detected = report.tools.filter((t) => t.detected);
56
+ const breadth = detected.length;
57
+ const d = depthTotals(detected);
58
+ const hasAgentic = detected.some((t) => AGENTIC_IDS.includes(t.id));
59
+ const agentCount =
60
+ report && report.agentCounts && typeof report.agentCounts.agents === 'number'
61
+ ? report.agentCounts.agents
62
+ : 0;
63
+
64
+ // Score scope reverted to PROJECT ∪ HOME (skill-code-certification / ADR-010, which reverts ADR-009).
65
+ const score = computeScore({
66
+ breadth,
67
+ context: d.instructions, // depthTotals already folds `config` into instructions
68
+ mcp: d.mcp,
69
+ custom: d.custom,
70
+ agentic: hasAgentic ? 1 : 0,
71
+ hooks: d.hooks,
72
+ multiAgent: agentCount,
73
+ });
74
+
75
+ // Band 0-4 derived from the tier engine (issue 019, single source of truth).
76
+ const tierResult = computeTierResult(report);
77
+ const level = tierResult.band;
78
+ const meta = LEVELS[level];
79
+
80
+ return {
81
+ level: meta.level,
82
+ key: meta.key,
83
+ name: meta.name,
84
+ emoji: meta.emoji,
85
+ score,
86
+ breadth,
87
+ depth: d,
88
+ hasAgentic,
89
+ next: nextStep(level, { breadth, ...d, hasAgentic }),
90
+ // Tier (T0-T7, issue 019): the fine-grained axis the band is derived from.
91
+ tier: tierResult.tier,
92
+ tierKey: tierResult.tierKey,
93
+ tierName: tierResult.tierName,
94
+ // Setup Level (ADR-016): the 3-value rollup shown to the talent, derived
95
+ // from the tier. `{key, code, rank, emoji}`; label localized via i18n.
96
+ setupLevel: tierResult.setupLevel,
97
+ };
98
+ }
99
+
100
+ function nextStep(level) {
101
+ const steps = {
102
+ 0: 'Instala una herramienta de IA (Claude Code, Cursor o Copilot) y pruébala en un proyecto real.',
103
+ 1: 'Añade un fichero de instrucciones al proyecto (CLAUDE.md, .cursorrules o copilot-instructions.md) para dar contexto persistente.',
104
+ 2: 'Conecta un servidor MCP o crea reglas/comandos propios para que la IA acceda a tus datos y flujos.',
105
+ 3: 'Combina una CLI agéntica con MCP y skills/comandos propios; automatiza una tarea recurrente de principio a fin.',
106
+ 4: 'Ya operas a nivel de orquestación: documenta tu setup y encadena agentes o ejecución en background.',
107
+ };
108
+ return steps[level];
109
+ }
110
+
111
+ module.exports = { classify, LEVELS, AGENTIC_IDS };
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const FLOOR_METHOD = 'floor';
4
+ const FLOOR_CODE = 'other-1';
5
+ const POLL_INTERVAL_MS = 5000;
6
+ const POLL_CAP_MS = 90000;
7
+ const INVENTORY_TIMEOUT_MS = 15000;
8
+
9
+ const RESULT_INPUT_SCHEMA = { type: 'object', properties: {} };
10
+
11
+ function isClassified(agents) {
12
+ if (agents.length === 0) return false;
13
+ const anyMethod = agents.some((a) => typeof a.method === 'string' && a.method);
14
+ if (anyMethod) {
15
+ return agents.every(
16
+ (a) => typeof a.method === 'string' && a.method && a.method !== FLOOR_METHOD,
17
+ );
18
+ }
19
+ return agents.every((a) => a.code && a.code !== FLOOR_CODE);
20
+ }
21
+
22
+ function defaultSleep(ms) {
23
+ return new Promise((resolve) => setTimeout(resolve, ms));
24
+ }
25
+
26
+ async function pollClassifiedInventory({
27
+ accessToken,
28
+ endpoint,
29
+ requestDiscoveredInventory,
30
+ sleep = defaultSleep,
31
+ now = () => Date.now(),
32
+ intervalMs = POLL_INTERVAL_MS,
33
+ capMs = POLL_CAP_MS,
34
+ }) {
35
+ const start = now();
36
+ let agents = [];
37
+ while (true) {
38
+ const inv = await requestDiscoveredInventory(
39
+ { accessToken },
40
+ { endpoint, timeoutMs: INVENTORY_TIMEOUT_MS },
41
+ );
42
+ if (inv.ok) {
43
+ agents = Array.isArray(inv.agents) ? inv.agents : [];
44
+ if (isClassified(agents)) {
45
+ return { status: 'ready', reason: 'classified', agents };
46
+ }
47
+ }
48
+ if (now() - start + intervalMs > capMs) {
49
+ return { status: 'pending', reason: 'classifying', agents };
50
+ }
51
+ await sleep(intervalMs);
52
+ }
53
+ }
54
+
55
+ const READY_MESSAGE = 'Shakers finished classifying the agents.';
56
+ const PENDING_MESSAGE = 'Shakers is still classifying the agents — this is normal, call ai_usage_result again shortly to keep waiting.';
57
+
58
+ function makeAiUsageResultTool(deps = {}) {
59
+ const {
60
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
61
+ sessionStatus = require('./auth-session-store').sessionStatus,
62
+ getUsageDiscoveredInventoryEndpoint = require('./config').getUsageDiscoveredInventoryEndpoint,
63
+ requestDiscoveredInventory = require('./inventory-client').requestDiscoveredInventory,
64
+ sleep = defaultSleep,
65
+ now = () => Date.now(),
66
+ intervalMs = POLL_INTERVAL_MS,
67
+ capMs = POLL_CAP_MS,
68
+ } = deps;
69
+
70
+ async function handler() {
71
+ const session = loadAuthSession();
72
+ if (sessionStatus(session) !== 'active') {
73
+ throw new Error('no active Shakers session — call the login tool (or run `shakers login`) first.');
74
+ }
75
+ const endpoint = getUsageDiscoveredInventoryEndpoint();
76
+ if (!endpoint) {
77
+ throw new Error('discovered-inventory endpoint is not configured (set SHAKERS_CLI_INGEST_ENDPOINT).');
78
+ }
79
+
80
+ const result = await pollClassifiedInventory({
81
+ accessToken: session.accessToken,
82
+ endpoint,
83
+ requestDiscoveredInventory,
84
+ sleep,
85
+ now,
86
+ intervalMs,
87
+ capMs,
88
+ });
89
+ return {
90
+ ...result,
91
+ message: result.status === 'ready' ? READY_MESSAGE : PENDING_MESSAGE,
92
+ };
93
+ }
94
+
95
+ return {
96
+ name: 'ai_usage_result',
97
+ description:
98
+ "Retrieve the enriched Shakers classification for the talent's agents after ai_usage. Shakers classifies agents and writes their descriptions server-side (no client LLM), so this reads the result: per agent { name, tools, model, category, role, level, code, whatItDoes }. Session-keyed, no arguments. Server-side classification can take a few minutes, so this does a bounded internal poll (~90s max, never a long-held call): returns { status: 'ready', agents } once every agent has been classified, or { status: 'pending', agents } while Shakers is still processing (call it again to keep waiting — 'pending' is normal progress, not an error). Present agents to the talent by NAME and category/role only; the `code` field is an internal handle for add_agent, never show it. Requires an active session.",
99
+ inputSchema: RESULT_INPUT_SCHEMA,
100
+ handler,
101
+ };
102
+ }
103
+
104
+ module.exports = {
105
+ makeAiUsageResultTool,
106
+ isClassified,
107
+ pollClassifiedInventory,
108
+ RESULT_INPUT_SCHEMA,
109
+ };