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.
- package/LICENSE +59 -0
- package/README.md +1175 -0
- package/bin/add-agent.js +88 -0
- package/bin/add-project.js +88 -0
- package/bin/add-role.js +61 -0
- package/bin/add-skill.js +88 -0
- package/bin/ai-usage.js +81 -0
- package/bin/alma.js +99 -0
- package/bin/applications.js +69 -0
- package/bin/availability.js +70 -0
- package/bin/certifications.js +69 -0
- package/bin/certify.js +73 -0
- package/bin/change-role.js +61 -0
- package/bin/config.js +120 -0
- package/bin/experiences.js +69 -0
- package/bin/find-projects.js +89 -0
- package/bin/invitations.js +69 -0
- package/bin/lang.js +70 -0
- package/bin/login.js +283 -0
- package/bin/mcp.js +98 -0
- package/bin/me.js +69 -0
- package/bin/onboarding.js +134 -0
- package/bin/portfolios.js +69 -0
- package/bin/rate.js +70 -0
- package/bin/register.js +325 -0
- package/bin/report-html.js +134 -0
- package/bin/save.js +77 -0
- package/bin/shakers.js +344 -0
- package/bin/share.js +72 -0
- package/bin/show-project.js +72 -0
- package/bin/socials.js +70 -0
- package/bin/superadmin.js +351 -0
- package/package.json +40 -0
- package/src/agent-category.js +13 -0
- package/src/agent-evaluation.js +177 -0
- package/src/agent-org-chart.js +355 -0
- package/src/agent-synthesis.js +132 -0
- package/src/agent-usage.js +118 -0
- package/src/agents-client.js +165 -0
- package/src/ai-fluency-cell.js +72 -0
- package/src/ai-profile-client.js +45 -0
- package/src/ai-profile-preview.js +154 -0
- package/src/ai.js +66 -0
- package/src/alma-client.js +123 -0
- package/src/alma-flow.js +32 -0
- package/src/alma-repo-context.js +47 -0
- package/src/alma-shell.js +161 -0
- package/src/ansi.js +62 -0
- package/src/applications-client.js +79 -0
- package/src/applications-flow.js +58 -0
- package/src/auth-client.js +144 -0
- package/src/auth-device-client.js +118 -0
- package/src/auth-method-picker.js +44 -0
- package/src/auth-methods.js +35 -0
- package/src/auth-session-store.js +214 -0
- package/src/authorship.js +203 -0
- package/src/automations-detector.js +170 -0
- package/src/availability-client.js +92 -0
- package/src/availability-flow.js +105 -0
- package/src/backend-request.js +170 -0
- package/src/blind-approval-extract.js +48 -0
- package/src/brand-ansi.js +14 -0
- package/src/brand-svg.js +9 -0
- package/src/browser-tools-detector.js +21 -0
- package/src/build-next-level.js +56 -0
- package/src/certifications-client.js +89 -0
- package/src/certifications-flow.js +75 -0
- package/src/certify-dimension-client.js +217 -0
- package/src/certify-dimension-flow.js +193 -0
- package/src/certify-disclaimer.js +51 -0
- package/src/certify-remediation-prompt.js +23 -0
- package/src/cli-args.js +83 -0
- package/src/config-dir.js +120 -0
- package/src/config.js +778 -0
- package/src/consent-flow.js +115 -0
- package/src/consent-skip.js +27 -0
- package/src/decisions-extract.js +43 -0
- package/src/detect-repos.js +145 -0
- package/src/detected-tools.js +15 -0
- package/src/detection-evidence.js +105 -0
- package/src/detection-noise.js +132 -0
- package/src/detectors.js +221 -0
- package/src/device-auth-store.js +100 -0
- package/src/device-login.js +118 -0
- package/src/email-verification.js +152 -0
- package/src/env-paths.js +16 -0
- package/src/find-projects-cache.js +89 -0
- package/src/find-projects-client.js +138 -0
- package/src/find-projects-flow.js +164 -0
- package/src/git-activity.js +123 -0
- package/src/graph-certs.js +58 -0
- package/src/graph-generator.js +230 -0
- package/src/graph-scan.js +239 -0
- package/src/i18n.js +3264 -0
- package/src/inter-fonts.js +6 -0
- package/src/interactive-select.js +183 -0
- package/src/interview-client.js +226 -0
- package/src/interview-transcript.js +56 -0
- package/src/inventory-client.js +90 -0
- package/src/invitations-client.js +66 -0
- package/src/invitations-flow.js +50 -0
- package/src/lang-client.js +147 -0
- package/src/lang-codes.js +14 -0
- package/src/lang-flow.js +85 -0
- package/src/legal-notice.js +53 -0
- package/src/locale.js +51 -0
- package/src/markdown-ansi.js +50 -0
- package/src/maturity.js +111 -0
- package/src/mcp-ai-usage-result-tool.js +109 -0
- package/src/mcp-ai-usage-tool.js +333 -0
- package/src/mcp-alma-tools.js +91 -0
- package/src/mcp-applications-tools.js +42 -0
- package/src/mcp-ask-tools.js +50 -0
- package/src/mcp-auth-form.js +282 -0
- package/src/mcp-availability-tools.js +43 -0
- package/src/mcp-certifications-tools.js +42 -0
- package/src/mcp-certify-dimension-tools.js +58 -0
- package/src/mcp-detector.js +223 -0
- package/src/mcp-device-login-tools.js +173 -0
- package/src/mcp-find-projects-tools.js +67 -0
- package/src/mcp-install.js +245 -0
- package/src/mcp-invitations-tools.js +42 -0
- package/src/mcp-lang-tools.js +39 -0
- package/src/mcp-onboarding-tools.js +388 -0
- package/src/mcp-portfolios-tools.js +46 -0
- package/src/mcp-profile-tools.js +46 -0
- package/src/mcp-rate-tools.js +42 -0
- package/src/mcp-register-tools.js +517 -0
- package/src/mcp-report-tools.js +101 -0
- package/src/mcp-role-tools.js +112 -0
- package/src/mcp-saved-positions-tools.js +55 -0
- package/src/mcp-server.js +170 -0
- package/src/mcp-show-project-tools.js +50 -0
- package/src/mcp-socials-tools.js +39 -0
- package/src/memory-structure-detector.js +108 -0
- package/src/model-call-record.js +198 -0
- package/src/onboarding-availability.js +26 -0
- package/src/onboarding-client.js +364 -0
- package/src/onboarding-flow.js +1078 -0
- package/src/onboarding-livekit-client.js +102 -0
- package/src/onboarding-status-client.js +42 -0
- package/src/open-file.js +32 -0
- package/src/osc-link.js +16 -0
- package/src/portfolio-client.js +164 -0
- package/src/portfolio-git-info.js +61 -0
- package/src/portfolios-client.js +85 -0
- package/src/portfolios-flow.js +79 -0
- package/src/profile-client.js +125 -0
- package/src/profile-flow.js +115 -0
- package/src/profile-switch.js +46 -0
- package/src/prompt-select.js +29 -0
- package/src/rate-client.js +82 -0
- package/src/rate-flow.js +137 -0
- package/src/render-ai-fluency.js +266 -0
- package/src/render-certification.js +177 -0
- package/src/render-html.js +140 -0
- package/src/render-report-text.js +27 -0
- package/src/render-sheet.js +261 -0
- package/src/render-terminal.js +109 -0
- package/src/render-usage-report-text.js +155 -0
- package/src/repo-context.js +215 -0
- package/src/report-gating.js +49 -0
- package/src/report-store.js +226 -0
- package/src/report-theme.js +177 -0
- package/src/report-view-client.js +50 -0
- package/src/reveal-output.js +29 -0
- package/src/roadmap-content.js +453 -0
- package/src/roadmap-personalization.js +132 -0
- package/src/roadmap-prompt.js +154 -0
- package/src/roles-client.js +114 -0
- package/src/roles-flow.js +192 -0
- package/src/sanitize-network-text.js +36 -0
- package/src/saved-positions-client.js +71 -0
- package/src/saved-positions-flow.js +50 -0
- package/src/scan-exclusions.js +50 -0
- package/src/scanner.js +612 -0
- package/src/scope-signals.js +148 -0
- package/src/secret-prompt.js +72 -0
- package/src/session-gate.js +30 -0
- package/src/session-refresh.js +51 -0
- package/src/session-scan.js +336 -0
- package/src/session-signals.js +36 -0
- package/src/shakers-wordmark.js +17 -0
- package/src/share-card.js +362 -0
- package/src/share-certification.js +111 -0
- package/src/share-consent-state.js +157 -0
- package/src/share-payload.js +300 -0
- package/src/share.js +184 -0
- package/src/show-project-client.js +58 -0
- package/src/show-project-flow.js +119 -0
- package/src/signup-client.js +91 -0
- package/src/skills-client.js +102 -0
- package/src/socials-client.js +91 -0
- package/src/socials-flow.js +83 -0
- package/src/specificity-extract.js +41 -0
- package/src/start-add-agents.js +306 -0
- package/src/start-add-portfolio.js +388 -0
- package/src/start-add-skills.js +260 -0
- package/src/stdin-ask.js +129 -0
- package/src/steering-extract.js +43 -0
- package/src/strip-internal-comments.js +45 -0
- package/src/tech-detector.js +295 -0
- package/src/templates/report-sheet.html +572 -0
- package/src/terminal-agents.js +199 -0
- package/src/terminal-format.js +85 -0
- package/src/terminal-progress.js +106 -0
- package/src/terminal-sections.js +219 -0
- package/src/tier-analysis.js +126 -0
- package/src/tier-engine.js +114 -0
- package/src/tier-projection.js +117 -0
- package/src/transcript-turns.js +49 -0
- package/src/translations-client.js +84 -0
- package/src/typewriter.js +154 -0
- package/src/usage-model-calls.js +119 -0
- package/src/usage-oneshot-commands.js +180 -0
- package/src/usage-repo-scope.js +162 -0
- package/src/usage-run-steps.js +338 -0
- package/src/work-streams.js +74 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { buildFootprintDrawer } = require('./graph-scan');
|
|
6
|
+
const { buildCertsPayload } = require('./graph-certs');
|
|
7
|
+
const { getCatalog, label } = require('./i18n');
|
|
8
|
+
const { sanitizeRenderText } = require('./sanitize-network-text');
|
|
9
|
+
const { isFloorCategory } = require('./agent-category');
|
|
10
|
+
const { stripTemplateComments } = require('./strip-internal-comments');
|
|
11
|
+
const { DEFAULT_REPORT_GATE } = require('./report-gating');
|
|
12
|
+
// 3x3 AI-Fluency matrix block (Usage axis persisted on the report).
|
|
13
|
+
const { renderAiFluencyMatrixHtml } = require('./render-ai-fluency');
|
|
14
|
+
|
|
15
|
+
// Rendered only when the Usage axis was judged and persisted on the report.
|
|
16
|
+
function aiFluencyHtml(report, lang) {
|
|
17
|
+
const usage = report && report.aiFluencyUsage;
|
|
18
|
+
if (!usage) return '';
|
|
19
|
+
return `<div class="card reveal">${renderAiFluencyMatrixHtml({ status: 'ready', usage }, { lang })}</div>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TEMPLATE_PATH = path.join(__dirname, 'templates', 'report-sheet.html');
|
|
23
|
+
let _tpl = null;
|
|
24
|
+
// Issue 098: the template's comments are OURS and this document is the one the talent shares, so they are swept here — at load, before any talent data is in the string.
|
|
25
|
+
function template() {
|
|
26
|
+
if (_tpl == null) _tpl = stripTemplateComments(fs.readFileSync(TEMPLATE_PATH, 'utf8'));
|
|
27
|
+
return _tpl;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function esc(s) {
|
|
31
|
+
return String(s == null ? '' : s)
|
|
32
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
33
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Literal, GLOBAL placeholder substitution.
|
|
37
|
+
function fill(tpl, token, value) {
|
|
38
|
+
return tpl.split(token).join(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Escapes a value for injection inside a single-quoted JS string literal in the template's inline <script>.
|
|
42
|
+
function jsStr(s) {
|
|
43
|
+
return String(s == null ? '' : s)
|
|
44
|
+
.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
45
|
+
.replace(/</g, '\\u003c').replace(/\r?\n/g, ' ');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Tier name, translated by STABLE KEY — same rule as render-terminal.js and render-html.js.
|
|
49
|
+
function tierName(fp, t) {
|
|
50
|
+
const key = fp && fp.tier && fp.tier.key;
|
|
51
|
+
return (key && key !== 'none' && t.tierNames && t.tierNames[key])
|
|
52
|
+
|| (fp && fp.tier && fp.tier.name) || '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function bandClass(b) { return b === 'high' ? 'band-high' : b === 'mid' ? 'band-mid' : 'band-low'; }
|
|
56
|
+
|
|
57
|
+
// Localized Setup Level label from the drawer payload (ADR-016): prefer the
|
|
58
|
+
// i18n `setupLevels` catalog by key, fall back to the label baked into `fp`.
|
|
59
|
+
function setupLabel(fp, t) {
|
|
60
|
+
const key = (fp.setup && fp.setup.key) || 'none';
|
|
61
|
+
return (t && t.setupLevels && t.setupLevels[key] && t.setupLevels[key].label)
|
|
62
|
+
|| (fp.setup && fp.setup.label) || key;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function heroHtml(fp, c, t) {
|
|
66
|
+
const rank = (fp.setup && typeof fp.setup.rank === 'number') ? fp.setup.rank : 0;
|
|
67
|
+
const label = setupLabel(fp, t);
|
|
68
|
+
// Four pips: Not certified + S1/S2/S3, current filled (ADR-016).
|
|
69
|
+
const pills = Array.from({ length: 4 }, (_, i) => `<span class="${i <= rank ? 'on' : ''}"></span>`).join('');
|
|
70
|
+
const chipK = esc(fp.tier.key && fp.tier.key !== 'none' ? fp.tier.key : (fp.setup && fp.setup.code) || '');
|
|
71
|
+
return `<div class="card reveal">
|
|
72
|
+
<div class="hero">
|
|
73
|
+
<div class="ring" id="ring">
|
|
74
|
+
<svg width="132" height="132" viewBox="0 0 132 132">
|
|
75
|
+
<circle class="track" cx="66" cy="66" r="54"></circle>
|
|
76
|
+
<circle class="fill" id="ringFill" cx="66" cy="66" r="54"></circle>
|
|
77
|
+
</svg>
|
|
78
|
+
<div class="num"><b id="scoreNum">0</b><small>/ 100</small></div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="hero-meta">
|
|
81
|
+
<span class="tier-chip"><span class="k">${chipK}</span> ${esc(tierName(fp, t))}</span>
|
|
82
|
+
<div class="band-line">${c.bandLine(esc(label))}</div>
|
|
83
|
+
<div class="lvl-pills" title="S1–S3">${pills}</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The Setup Level ladder (ADR-016), with each rung's tiers and short meaning inline — meanings from the localized i18n `setupLevels` catalog (never invented).
|
|
90
|
+
function ladderHtml(fp, c, t) {
|
|
91
|
+
const cur = (fp.setup && typeof fp.setup.rank === 'number') ? fp.setup.rank : 0;
|
|
92
|
+
const catalog = (t && t.setupLevels) || {};
|
|
93
|
+
const items = (fp.ladder || []).map((r) => {
|
|
94
|
+
const cls = r.rank < cur ? 'done' : r.rank === cur ? 'current' : 'pending';
|
|
95
|
+
const node = r.rank < cur ? '✓' : '';
|
|
96
|
+
const copy = catalog[r.key] || {};
|
|
97
|
+
const label = copy.label || r.label || r.key;
|
|
98
|
+
// desc starts with the label prefix ("Assisted: …") — strip it so it isn't
|
|
99
|
+
// doubled next to the name we already show.
|
|
100
|
+
const meaning = (copy.desc || '').replace(/^[^:]+:\s*/, '');
|
|
101
|
+
const here = r.rank === cur ? ` — ${esc(c.hereYouAre)}` : '';
|
|
102
|
+
const line = meaning ? `<div class="desc">${esc(meaning)}${here}</div>` : (here ? `<div class="desc">${esc(c.hereYouAre)}</div>` : '');
|
|
103
|
+
return `<li class="${cls}"><span class="node">${node}</span><span class="name">${esc(label)}</span>${line}</li>`;
|
|
104
|
+
}).join('');
|
|
105
|
+
return `<div class="card reveal"><h3>${esc(c.ladderT)}</h3><p class="sub">${esc(c.ladderS)}</p><ul class="ladder">${items}</ul></div>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function chipsCard(title, sub, items, withDot) {
|
|
109
|
+
const chips = (items || []).map((x) => `<span class="chip">${withDot ? '<span class="d"></span>' : ''}${esc(x)}</span>`).join('');
|
|
110
|
+
const body = chips || '<span class="chip" style="opacity:.6">—</span>';
|
|
111
|
+
return `<div class="card reveal"><h3>${esc(title)}</h3><p class="sub">${esc(sub)}</p><div class="chips">${body}</div></div>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MCP SERVICES (issue 110).
|
|
115
|
+
function mcpCard(mcp, c) {
|
|
116
|
+
const services = (mcp && Array.isArray(mcp.services) ? mcp.services : []);
|
|
117
|
+
const unidentified = (mcp && typeof mcp.unidentified === 'number') ? mcp.unidentified : 0;
|
|
118
|
+
if (services.length === 0 && unidentified === 0) {
|
|
119
|
+
return `<div class="card reveal"><h3>${esc(c.mcpT)}</h3><p class="sub">${esc(c.mcpEmpty)}</p></div>`;
|
|
120
|
+
}
|
|
121
|
+
const chips = services
|
|
122
|
+
.map((s) => `<span class="chip">${esc(s.label)}${s.count > 1 ? ` ×${s.count}` : ''}</span>`)
|
|
123
|
+
.join('');
|
|
124
|
+
// The unidentified servers are stated, never omitted: the list must account for
|
|
125
|
+
// every server the tier counted.
|
|
126
|
+
const note = unidentified > 0
|
|
127
|
+
? `<p class="sub" style="margin:8px 0 0">${esc(c.mcpUnidentified(unidentified))}</p>`
|
|
128
|
+
: '';
|
|
129
|
+
return `<div class="card reveal"><h3>${esc(c.mcpT)}</h3><p class="sub">${esc(c.mcpS)}</p><div class="chips">${chips}</div>${note}</div>`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// DETECTED AGENTS, left column (issue 089).
|
|
133
|
+
function agentRow(a, c, t) {
|
|
134
|
+
const cats = (t.classification && t.classification.categories) || {};
|
|
135
|
+
const model = a.model ? `<span class="model">${esc(a.model)}</span>` : `<span class="model" style="opacity:.6">${esc(c.agentNoModel)}</span>`;
|
|
136
|
+
// The category is the agent → catalog-seed association (per agent), not the Talent's tier.
|
|
137
|
+
const catLabel = a.category ? (cats[a.category] || sanitizeRenderText(a.category)) : null;
|
|
138
|
+
// ONE COLOUR PER CATEGORY (user request), and THREE distinct facts, not two: - a known catalog category -> its own colour token (`.cat-developer` …).
|
|
139
|
+
// FOUR facts now, not three (issue 120).
|
|
140
|
+
const coloured = !!(a.category && cats[a.category] && !isFloorCategory(a.category));
|
|
141
|
+
const catClass = coloured ? `cat cat-${a.category}` : 'cat cat-none';
|
|
142
|
+
// Issue 106: the empty badge says WHICH kind of empty it is.
|
|
143
|
+
const cls = t.classification || {};
|
|
144
|
+
const emptyLabel = a.evaluationState === 'omitted'
|
|
145
|
+
? cls.agentEvalOmitted
|
|
146
|
+
: ((a.evaluationState === 'not-evaluated' || a.evaluated === false) ? c.agentNotEvaluated : c.agentNoCategory);
|
|
147
|
+
const category = catLabel
|
|
148
|
+
? `<span class="chip ${catClass}">${esc(catLabel)}</span>`
|
|
149
|
+
: `<span class="chip cat cat-none">${esc(emptyLabel)}</span>`;
|
|
150
|
+
const orchestrates = a.hasChildren ? `<span class="usage">${esc(c.agentOrchestrates)}</span>` : '';
|
|
151
|
+
const desc = a.whatItDoes ? `<p class="whatdo">${esc(a.whatItDoes)}</p>` : '';
|
|
152
|
+
return `<li class="agent${a.depth > 0 ? ' child' : ''}">
|
|
153
|
+
<div class="agent-row">
|
|
154
|
+
<span class="an">${esc(a.name)}</span>
|
|
155
|
+
${model}
|
|
156
|
+
<span class="spacer"></span>
|
|
157
|
+
${orchestrates}
|
|
158
|
+
${category}
|
|
159
|
+
</div>
|
|
160
|
+
${desc}
|
|
161
|
+
</li>`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function agentsCard(agents, c, t) {
|
|
165
|
+
const list = Array.isArray(agents) ? agents : [];
|
|
166
|
+
// SAID ONCE, VISIBLY (issue 106): if nobody was evaluated, the reason the badges are empty is that the run could not classify, not that the agents have no category.
|
|
167
|
+
const noneEvaluated = list.length > 0 && list.every((a) => a.evaluated === false);
|
|
168
|
+
// AND THE PARTIAL RUN, NAMED (2026-08-03).
|
|
169
|
+
const omitted = list.filter((a) => a.evaluationState === 'omitted').map((a) => a.name);
|
|
170
|
+
const cls = (t && t.classification) || {};
|
|
171
|
+
// OMITTED WINS OVER "NOBODY WAS EVALUATED", and the order is the fix.
|
|
172
|
+
const notice = (omitted.length && typeof cls.agentsEvalPartial === 'function')
|
|
173
|
+
? `<p class="sub" style="margin:0 0 10px">${esc(cls.agentsEvalPartial(omitted.join(', '), omitted.length))}</p>`
|
|
174
|
+
: (noneEvaluated ? `<p class="sub" style="margin:0 0 10px">${esc(c.agentsEvalMissing)}</p>` : '');
|
|
175
|
+
// The empty state is explicit on purpose: "you have none" and "the tool does not
|
|
176
|
+
// look" are different facts and used to look identical (nothing at all).
|
|
177
|
+
const body = list.length
|
|
178
|
+
? `<ul class="tree">${list.map((a) => agentRow(a, c, t)).join('')}</ul>`
|
|
179
|
+
: `<p class="sub" style="margin:0">${esc(label(c.agentsEmpty, 'No configured AI agents detected.'))}</p>`;
|
|
180
|
+
return `<div class="card reveal"><h3>${esc(label(c.agentsT, 'AI agents'))}</h3><p class="sub">${esc(label(c.agentsS, ''))}</p>${notice}${body}</div>`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function skillAcc(s, c, gate = DEFAULT_REPORT_GATE) {
|
|
184
|
+
const imps = gate.showCertifyRemediation ? (s.improvements || []).map((i) => `<li>${esc(i)}</li>`).join('') : '';
|
|
185
|
+
const improveBlk = imps ? `<div class="blk"><h5>${esc(c.comoMejorar)}</h5><ul>${imps}</ul></div>` : '';
|
|
186
|
+
return `<div class="acc"><button class="acc-head"><span class="caret">▸</span><span class="title">${esc(s.name)}</span><span class="spacer"></span><span class="score-badge ${bandClass(s.band)}">${esc(s.levelName || '—')}</span></button>
|
|
187
|
+
<div class="acc-body-wrap"><div class="acc-body-inner"><div class="acc-body">
|
|
188
|
+
<div class="blk"><h5>${esc(c.valoracion)}</h5><p>${esc(s.rationale) || '—'}</p></div>
|
|
189
|
+
${improveBlk}
|
|
190
|
+
</div></div></div></div>`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// renderSheet(project, lang) -> full self-contained HTML string with the mockup design, populated from live report-store data.
|
|
194
|
+
function renderSheet(project, lang, gate = DEFAULT_REPORT_GATE) {
|
|
195
|
+
const L = lang === 'en' ? 'en' : 'es';
|
|
196
|
+
const t = getCatalog(L);
|
|
197
|
+
const c = t.sheet;
|
|
198
|
+
const hasFoot = !!(project && project.footprint && project.footprint.report);
|
|
199
|
+
// `t` is passed now (issue 089): the drawer's agent rows use the catalog for the
|
|
200
|
+
// description fallback and nothing else — it stays optional for the graph report.
|
|
201
|
+
const fp = hasFoot ? buildFootprintDrawer(project.footprint.report, project.footprint.maturity, t) : null;
|
|
202
|
+
// buildCertsPayload returns null when the project has no Skill certs — the sheet still renders both columns, so normalize to an empty list (clean state).
|
|
203
|
+
const certs = buildCertsPayload(project, L) || { skills: [] };
|
|
204
|
+
|
|
205
|
+
// LEFT column — footprint (or a clean empty state). The tier explainer now
|
|
206
|
+
// lives INSIDE the maturity ladder (per rung), so no separate tiers card.
|
|
207
|
+
const left = hasFoot
|
|
208
|
+
? heroHtml(fp, c, t) + aiFluencyHtml(project.footprint.report, L) + ladderHtml(fp, c, t) + agentsCard(fp.agents, c, t)
|
|
209
|
+
+ chipsCard(c.toolsT, c.toolsS, fp.tools, true) + chipsCard(c.techT, c.techS, fp.technologies, false)
|
|
210
|
+
+ mcpCard(fp.mcp, c)
|
|
211
|
+
: `<div class="card reveal"><p class="sub">${esc(c.noFoot)}</p></div>`;
|
|
212
|
+
|
|
213
|
+
// RIGHT column — Skill certifications (or a clean empty state).
|
|
214
|
+
const skillsBody = certs.skills.length
|
|
215
|
+
? certs.skills.map((s) => skillAcc(s, c, gate)).join('')
|
|
216
|
+
: `<div class="card reveal"><p class="sub">${esc(c.noSkills)}</p></div>`;
|
|
217
|
+
|
|
218
|
+
const body = `
|
|
219
|
+
<section class="col-left">
|
|
220
|
+
<div class="col-head"><span class="dot"></span><h2>${esc(c.footprint)}</h2></div>
|
|
221
|
+
${left}
|
|
222
|
+
</section>
|
|
223
|
+
<section class="col-right">
|
|
224
|
+
<div class="col-head"><span class="dot"></span><h2>${esc(c.certs)}</h2></div>
|
|
225
|
+
<div class="tabs" id="tabs">
|
|
226
|
+
<span class="tab-ind" id="tabInd"></span>
|
|
227
|
+
<button class="tab active" data-tab="skills">${esc(c.skills)}<span class="cnt">${certs.skills.length}</span></button>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="panel-controls"><button class="mini-btn" id="expandAll">${esc(c.expandAll)}</button></div>
|
|
230
|
+
<div class="tabpanel show" data-panel="skills">${skillsBody}</div>
|
|
231
|
+
</section>`;
|
|
232
|
+
|
|
233
|
+
const projectName = project && project.root
|
|
234
|
+
? sanitizeRenderText(path.basename(String(project.root)))
|
|
235
|
+
: 'project';
|
|
236
|
+
|
|
237
|
+
let out = template();
|
|
238
|
+
// Document/page chrome. HTML-escaped for the markup slots, JS-escaped for the
|
|
239
|
+
// three that land inside the template's inline <script>.
|
|
240
|
+
for (const [token, value] of [
|
|
241
|
+
['__LANG__', L],
|
|
242
|
+
['__SCORE__', String(fp ? Math.max(0, Math.min(100, Math.round(fp.score))) : 0)],
|
|
243
|
+
['__PROJECT__', esc(String(projectName))],
|
|
244
|
+
['__FOOTER__', esc(c.footer)],
|
|
245
|
+
['__REPORT_TITLE__', esc(c.reportTitle)],
|
|
246
|
+
['__THEME_TOGGLE__', esc(c.themeToggle)],
|
|
247
|
+
['__THEME_DARK__', esc(c.themeDark)],
|
|
248
|
+
['__THEME_LIGHT__', esc(c.themeLight)],
|
|
249
|
+
['__JS_THEME_DARK__', jsStr(c.themeDark)],
|
|
250
|
+
['__JS_THEME_LIGHT__', jsStr(c.themeLight)],
|
|
251
|
+
['__JS_COPIED__', jsStr(c.copied)],
|
|
252
|
+
['__JS_EXPAND_ALL__', jsStr(c.expandAll)],
|
|
253
|
+
['__JS_COLLAPSE_ALL__', jsStr(c.collapseAll)],
|
|
254
|
+
['__BODY__', body],
|
|
255
|
+
]) {
|
|
256
|
+
out = fill(out, token, value);
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = { renderSheet, agentsCard };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getCatalog, categoryLabel } = require('./i18n');
|
|
4
|
+
const { sanitizeRenderText } = require('./sanitize-network-text');
|
|
5
|
+
const { DEFAULT_REPORT_GATE } = require('./report-gating');
|
|
6
|
+
const { c, bar, sep, wrap } = require('./terminal-format');
|
|
7
|
+
const { detectedTools } = require('./detected-tools');
|
|
8
|
+
const { printAgents, printActivity } = require('./terminal-agents');
|
|
9
|
+
const {
|
|
10
|
+
printMcpServices,
|
|
11
|
+
printTierAnalysis,
|
|
12
|
+
printLadder,
|
|
13
|
+
printRoadmap,
|
|
14
|
+
} = require('./terminal-sections');
|
|
15
|
+
|
|
16
|
+
// talents-ai-score: terminal parity with the HTML report.
|
|
17
|
+
|
|
18
|
+
// `lang` ('es'|'en', see src/i18n.js) decides the text catalog.
|
|
19
|
+
function renderTerminal(report, maturity, lang, opts = {}) {
|
|
20
|
+
const t = getCatalog(lang);
|
|
21
|
+
const lines = [];
|
|
22
|
+
const p = (s = '') => lines.push(s);
|
|
23
|
+
|
|
24
|
+
// issue 123 / ADR-044: the ONE identity gate.
|
|
25
|
+
const gate = opts.gate || DEFAULT_REPORT_GATE;
|
|
26
|
+
|
|
27
|
+
// ADR-016: Setup Level (S1-S3 / Not certified) replaces the 0-4 band label.
|
|
28
|
+
const setupKey = (maturity.setupLevel && maturity.setupLevel.key) || 'none';
|
|
29
|
+
const setupLabel = (t.setupLevels && t.setupLevels[setupKey] && t.setupLevels[setupKey].label) || setupKey;
|
|
30
|
+
|
|
31
|
+
// A compact brand header in BOTH modes for orientation.
|
|
32
|
+
p();
|
|
33
|
+
p(`${c.bold}${c.primary} SHAKERS${c.reset}${c.muted} · ${t.terminal.brandSub}${c.reset}`);
|
|
34
|
+
p(`${c.muted} ${new Date(report.generatedAt).toLocaleString()} · ${t.terminal.toolsDetected(detectedTools(report.tools).length, report.tools.length)}${c.reset}`);
|
|
35
|
+
|
|
36
|
+
// `--roadmap` (opts.showRoadmap): render ONLY the next-steps/roadmap section (header + steps + the copyable implementation prompt) — NOT the rest of the report.
|
|
37
|
+
if (opts.showRoadmap) {
|
|
38
|
+
sep(p);
|
|
39
|
+
if (gate.showRoadmap) {
|
|
40
|
+
printRoadmap(report, maturity, t, lang, p);
|
|
41
|
+
}
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Default report: score -> why -> tools -> technologies -> agents, then a dim hint pointing at --roadmap (so the next-steps section is discoverable).
|
|
46
|
+
|
|
47
|
+
sep(p);
|
|
48
|
+
const tierName = (maturity.tierKey && t.tierNames[maturity.tierKey]) || maturity.tierName || '';
|
|
49
|
+
const tierBit = maturity.tierKey ? t.terminal.tierInline(maturity.tierKey, tierName) : '';
|
|
50
|
+
// This report's ONE headline (dueño, 2026-08-11): the Setup Level IS the evaluation's main result, so it gets `accent` (lime) — and nothing else on this screen does.
|
|
51
|
+
const setupColor = setupKey === 'none' ? c.white : c.accent;
|
|
52
|
+
p(` ${c.bold}${setupColor}${t.terminal.setupLevel(setupLabel)}${c.reset}${c.muted}${tierBit}${c.reset}`);
|
|
53
|
+
p(` ${c.primary}${bar(maturity.score)}${c.reset} ${c.dim}${maturity.score}/100${c.reset}`);
|
|
54
|
+
|
|
55
|
+
// 2. WHY that score/tier — the rationale, right after the number.
|
|
56
|
+
sep(p);
|
|
57
|
+
printTierAnalysis(report, t, p);
|
|
58
|
+
|
|
59
|
+
// 2b. Progression ladder — what each level 0-4 and tier T0-T7 means, and the
|
|
60
|
+
// ✓/●/○ progression with unlock criteria (report req 1, full in terminal).
|
|
61
|
+
sep(p);
|
|
62
|
+
printLadder(report, t, p);
|
|
63
|
+
|
|
64
|
+
// 3. Detected tools — omitted entirely if none detected.
|
|
65
|
+
const detected = detectedTools(report.tools);
|
|
66
|
+
if (detected.length) {
|
|
67
|
+
sep(p);
|
|
68
|
+
p(` ${c.bold}${t.terminal.detectedHeading}${c.reset}`);
|
|
69
|
+
for (const tool of detected) {
|
|
70
|
+
const depthBits = Object.entries(tool.depth)
|
|
71
|
+
.filter(([, v]) => v > 0)
|
|
72
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
73
|
+
.join(', ');
|
|
74
|
+
const extra = depthBits ? `${c.dim} — ${depthBits}${c.reset}` : '';
|
|
75
|
+
const version = tool.version ? `${c.dim} v${sanitizeRenderText(tool.version)}${c.reset}` : '';
|
|
76
|
+
p(` ${c.success}●${c.reset} ${sanitizeRenderText(tool.name)}${version} ${c.muted}(${categoryLabel(lang, tool.category)})${c.reset}${extra}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 4b. MCP services (issue 110) — ALWAYS printed, empty state included, because
|
|
81
|
+
// its absence is a tier criterion the talent needs to know about.
|
|
82
|
+
sep(p);
|
|
83
|
+
printMcpServices(report, t, p);
|
|
84
|
+
|
|
85
|
+
// 5. Agents — one line per agent (+ summarized description, ↓ nesting,
|
|
86
|
+
// compact score + usage). Omitted entirely when there are no agents at all.
|
|
87
|
+
const hasAgents = Array.isArray(report.agents) && report.agents.length > 0;
|
|
88
|
+
if (hasAgents) {
|
|
89
|
+
sep(p);
|
|
90
|
+
printAgents(report, t, p, gate);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const activityLines = [];
|
|
94
|
+
if (printActivity(report, t, (s = '') => activityLines.push(s))) {
|
|
95
|
+
sep(p);
|
|
96
|
+
for (const line of activityLines) p(line);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Where the roadmap pointer went.
|
|
100
|
+
if (gate.showRoadmap) {
|
|
101
|
+
p(` ${c.dim}${t.terminal.roadmapHint}${c.reset}`);
|
|
102
|
+
}
|
|
103
|
+
p();
|
|
104
|
+
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// `wrap` is re-exported for its own unit test (issue 087): it is the one piece of the formatting with edge cases worth pinning directly.
|
|
109
|
+
module.exports = { renderTerminal, wrap, printAgents };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { detectedTools } = require('./detected-tools');
|
|
4
|
+
|
|
5
|
+
const LABELS = {
|
|
6
|
+
es: {
|
|
7
|
+
title: 'Informe de uso de IA (almacenado)',
|
|
8
|
+
platform: 'Plataforma',
|
|
9
|
+
level: 'Nivel',
|
|
10
|
+
score: 'Puntuación',
|
|
11
|
+
tier: 'Tier',
|
|
12
|
+
detected: 'Detectados',
|
|
13
|
+
categories: 'Categorías',
|
|
14
|
+
tools: 'Herramientas',
|
|
15
|
+
technologies: 'Tecnologías',
|
|
16
|
+
agents: 'Agentes',
|
|
17
|
+
mcp: 'Servidores MCP',
|
|
18
|
+
activity: 'Actividad',
|
|
19
|
+
sessions: 'Sesiones',
|
|
20
|
+
subagentRuns: 'Ejecuciones de subagentes',
|
|
21
|
+
activeHours: 'Horas activas',
|
|
22
|
+
crossToolLinks: 'Enlaces entre herramientas',
|
|
23
|
+
filesTouched: 'Ficheros tocados',
|
|
24
|
+
bashCommands: 'Comandos de shell',
|
|
25
|
+
planningSignals: 'Señales de planificación',
|
|
26
|
+
commits: 'Commits propios',
|
|
27
|
+
steering: 'Correcciones/steering',
|
|
28
|
+
decisions: 'Decisiones deliberadas',
|
|
29
|
+
fluency: 'Nivel de fluidez con IA',
|
|
30
|
+
interaction: 'Nivel de interacción (prompting)',
|
|
31
|
+
veracity: 'veracidad',
|
|
32
|
+
generatedAt: 'Generado',
|
|
33
|
+
none: '(ninguno)',
|
|
34
|
+
notEvaluated: '(sin evaluar todavía)',
|
|
35
|
+
},
|
|
36
|
+
en: {
|
|
37
|
+
title: 'AI usage report (stored)',
|
|
38
|
+
platform: 'Platform',
|
|
39
|
+
level: 'Level',
|
|
40
|
+
score: 'Score',
|
|
41
|
+
tier: 'Tier',
|
|
42
|
+
detected: 'Detected',
|
|
43
|
+
categories: 'Categories',
|
|
44
|
+
tools: 'Tools',
|
|
45
|
+
technologies: 'Technologies',
|
|
46
|
+
agents: 'Agents',
|
|
47
|
+
mcp: 'MCP servers',
|
|
48
|
+
activity: 'Activity',
|
|
49
|
+
sessions: 'Sessions',
|
|
50
|
+
subagentRuns: 'Subagent runs',
|
|
51
|
+
activeHours: 'Active hours',
|
|
52
|
+
crossToolLinks: 'Cross-tool links',
|
|
53
|
+
filesTouched: 'Files touched',
|
|
54
|
+
bashCommands: 'Shell commands',
|
|
55
|
+
planningSignals: 'Planning signals',
|
|
56
|
+
commits: 'Your commits',
|
|
57
|
+
steering: 'Steering/corrections',
|
|
58
|
+
decisions: 'Deliberate decisions',
|
|
59
|
+
fluency: 'AI fluency level',
|
|
60
|
+
interaction: 'Interaction (prompting) level',
|
|
61
|
+
veracity: 'veracity',
|
|
62
|
+
generatedAt: 'Generated',
|
|
63
|
+
none: '(none)',
|
|
64
|
+
notEvaluated: '(not evaluated yet)',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function line(out, label, value) {
|
|
69
|
+
out.push(` ${label}: ${value}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function agentName(a) {
|
|
73
|
+
if (!a || typeof a !== 'object') return null;
|
|
74
|
+
const name = typeof a.name === 'string' ? a.name : null;
|
|
75
|
+
if (!name) return null;
|
|
76
|
+
const role = typeof a.role === 'string' && a.role ? ` — ${a.role}` : '';
|
|
77
|
+
return `${name}${role}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function evaluationText(evaluation, L) {
|
|
81
|
+
if (!evaluation || typeof evaluation !== 'object' || !evaluation.level) {
|
|
82
|
+
return L.notEvaluated;
|
|
83
|
+
}
|
|
84
|
+
const veracity = evaluation.veracity ? ` (${L.veracity}: ${evaluation.veracity})` : '';
|
|
85
|
+
return `${evaluation.level}${veracity}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderUsageReportText(view, { lang = 'en' } = {}) {
|
|
89
|
+
const L = LABELS[lang] || LABELS.en;
|
|
90
|
+
const report = view && view.report ? view.report : null;
|
|
91
|
+
const out = [];
|
|
92
|
+
out.push('');
|
|
93
|
+
out.push(` ${L.title}`);
|
|
94
|
+
out.push('');
|
|
95
|
+
|
|
96
|
+
if (report) {
|
|
97
|
+
const levelName = typeof report.levelName === 'string' ? report.levelName : '';
|
|
98
|
+
line(out, L.platform, report.platform || '');
|
|
99
|
+
line(out, L.level, levelName ? `${levelName} (${report.level})` : `${report.level}`);
|
|
100
|
+
line(out, L.score, `${report.score}`);
|
|
101
|
+
line(out, L.tier, report.tierKey ? `${report.tierKey}` : `${report.tier ?? '-'}`);
|
|
102
|
+
line(out, L.detected, `${report.totalDetected}`);
|
|
103
|
+
line(out, L.categories, Array.isArray(report.categories) && report.categories.length ? report.categories.join(', ') : L.none);
|
|
104
|
+
|
|
105
|
+
const toolCount = detectedTools(report.tools).length;
|
|
106
|
+
line(out, L.tools, `${toolCount}`);
|
|
107
|
+
|
|
108
|
+
const techs = Array.isArray(report.technologies) ? report.technologies : [];
|
|
109
|
+
line(out, L.technologies, techs.length ? techs.join(', ') : L.none);
|
|
110
|
+
|
|
111
|
+
const mcpCount = report.mcp && typeof report.mcp.count === 'number' ? report.mcp.count : 0;
|
|
112
|
+
line(out, L.mcp, `${mcpCount}`);
|
|
113
|
+
|
|
114
|
+
out.push('');
|
|
115
|
+
out.push(` ${L.agents}:`);
|
|
116
|
+
const agents = Array.isArray(report.agents) ? report.agents.map(agentName).filter(Boolean) : [];
|
|
117
|
+
if (agents.length) {
|
|
118
|
+
for (const a of agents) out.push(` - ${a}`);
|
|
119
|
+
} else {
|
|
120
|
+
out.push(` ${L.none}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
out.push('');
|
|
124
|
+
out.push(` ${L.activity}:`);
|
|
125
|
+
const s = report.sessions;
|
|
126
|
+
if (s && typeof s === 'object') {
|
|
127
|
+
line(out, ` ${L.sessions}`, `${s.sessionCount}`);
|
|
128
|
+
line(out, ` ${L.subagentRuns}`, `${s.subagentRunCount ?? 0}`);
|
|
129
|
+
line(out, ` ${L.activeHours}`, `${s.activeHours ?? 0}`);
|
|
130
|
+
line(out, ` ${L.crossToolLinks}`, `${s.crossToolLinks}`);
|
|
131
|
+
line(out, ` ${L.filesTouched}`, `${s.filesTouchedCount}`);
|
|
132
|
+
line(out, ` ${L.bashCommands}`, `${s.bashCommandsCount}`);
|
|
133
|
+
line(out, ` ${L.planningSignals}`, `${s.planningSignalCount ?? 0}`);
|
|
134
|
+
}
|
|
135
|
+
const git = report.gitActivity;
|
|
136
|
+
if (git && typeof git === 'object') {
|
|
137
|
+
line(out, ` ${L.commits}`, `${git.authoredCommitCount ?? 0}`);
|
|
138
|
+
}
|
|
139
|
+
line(out, ` ${L.steering}`, `${report.steeringTraceCount ?? 0}`);
|
|
140
|
+
line(out, ` ${L.decisions}`, `${report.decisionExchangeCount ?? 0}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
out.push('');
|
|
144
|
+
line(out, L.fluency, evaluationText(view && view.fluency, L));
|
|
145
|
+
line(out, L.interaction, evaluationText(view && view.interactionQuality, L));
|
|
146
|
+
|
|
147
|
+
if (report && report.generatedAt) {
|
|
148
|
+
out.push('');
|
|
149
|
+
line(out, L.generatedAt, report.generatedAt);
|
|
150
|
+
}
|
|
151
|
+
out.push('');
|
|
152
|
+
return `${out.join('\n')}\n`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { renderUsageReportText };
|