shakers-cli 0.0.0-stage → 0.14.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 +1233 -2
- 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 +99 -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 +124 -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 +216 -0
- package/bin/mcp.js +124 -0
- package/bin/mcpb.js +5 -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 +337 -0
- package/bin/report-html.js +134 -0
- package/bin/save.js +77 -0
- package/bin/shakers.js +363 -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/bin/update-check-worker.js +70 -0
- package/bin/update.js +111 -0
- package/package.json +40 -4
- 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 +215 -0
- package/src/authorship-mismatch.js +62 -0
- package/src/authorship.js +204 -0
- package/src/automations-detector.js +170 -0
- package/src/availability-client.js +93 -0
- package/src/availability-flow.js +124 -0
- package/src/backend-request.js +191 -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 +163 -0
- package/src/certify-dimension-flow.js +135 -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/command-audit.js +105 -0
- package/src/config-dir.js +120 -0
- package/src/config.js +877 -0
- package/src/consent-flow.js +115 -0
- package/src/consent-skip.js +27 -0
- package/src/cv-file.js +62 -0
- package/src/cv-text.js +92 -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 +120 -0
- package/src/email-verification.js +152 -0
- package/src/env-paths.js +30 -0
- package/src/find-projects-cache.js +89 -0
- package/src/find-projects-client.js +148 -0
- package/src/find-projects-flow.js +164 -0
- package/src/git-activity.js +124 -0
- package/src/git-log-prefetch.js +53 -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 +3341 -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 +72 -0
- package/src/markdown-ansi.js +50 -0
- package/src/maturity.js +111 -0
- package/src/mcp-ai-usage-result-tool.js +111 -0
- package/src/mcp-ai-usage-tool.js +401 -0
- package/src/mcp-alma-tools.js +95 -0
- package/src/mcp-applications-tools.js +44 -0
- package/src/mcp-auth-form.js +480 -0
- package/src/mcp-availability-tools.js +45 -0
- package/src/mcp-certifications-tools.js +44 -0
- package/src/mcp-certify-dimension-tools.js +66 -0
- package/src/mcp-choice.js +95 -0
- package/src/mcp-detector.js +223 -0
- package/src/mcp-device-login-tools.js +177 -0
- package/src/mcp-find-projects-tools.js +69 -0
- package/src/mcp-install.js +275 -0
- package/src/mcp-instructions.js +27 -0
- package/src/mcp-invitations-tools.js +44 -0
- package/src/mcp-lang-tools.js +41 -0
- package/src/mcp-onboarding-tools.js +379 -0
- package/src/mcp-portfolios-tools.js +50 -0
- package/src/mcp-profile-tools.js +48 -0
- package/src/mcp-prompts.js +73 -0
- package/src/mcp-rate-tools.js +44 -0
- package/src/mcp-register-tools.js +441 -0
- package/src/mcp-report-tools.js +105 -0
- package/src/mcp-role-tools.js +372 -0
- package/src/mcp-saved-positions-tools.js +59 -0
- package/src/mcp-server.js +290 -0
- package/src/mcp-show-project-tools.js +52 -0
- package/src/mcp-signup-client.js +118 -0
- package/src/mcp-signup-tools.js +1171 -0
- package/src/mcp-socials-tools.js +41 -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 +374 -0
- package/src/onboarding-flow.js +1162 -0
- package/src/onboarding-funnel.js +272 -0
- package/src/onboarding-livekit-client.js +102 -0
- package/src/onboarding-status-client.js +57 -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 +133 -0
- package/src/profile-flow.js +115 -0
- package/src/profile-switch.js +46 -0
- package/src/prompt-select.js +66 -0
- package/src/rate-client.js +81 -0
- package/src/rate-flow.js +137 -0
- package/src/register-context.js +145 -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 +112 -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 +624 -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-normalizers.js +174 -0
- package/src/session-refresh.js +51 -0
- package/src/session-scan.js +475 -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 +185 -0
- package/src/show-project-client.js +58 -0
- package/src/show-project-flow.js +119 -0
- package/src/signup-client.js +103 -0
- package/src/signup-copy.js +450 -0
- package/src/signup-language.js +21 -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 +207 -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/update-flow.js +223 -0
- package/src/update-notifier.js +113 -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 +354 -0
- package/src/work-streams.js +79 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { pathToFileURL } = require('url');
|
|
7
|
+
|
|
8
|
+
const { getConfigDir } = require('./config-dir');
|
|
9
|
+
const { renderSheet } = require('./render-sheet');
|
|
10
|
+
const { reportGateForIdentity } = require('./report-gating');
|
|
11
|
+
|
|
12
|
+
const SCHEMA_VERSION = 2;
|
|
13
|
+
|
|
14
|
+
function configDir() {
|
|
15
|
+
return getConfigDir(process.env);
|
|
16
|
+
}
|
|
17
|
+
function statePath() {
|
|
18
|
+
return path.join(configDir(), 'report-state.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Stable, filesystem-safe per-project file name derived from the absolute path.
|
|
22
|
+
function projectSlug(absRoot) {
|
|
23
|
+
return crypto.createHash('sha1').update(String(absRoot)).digest('hex').slice(0, 12);
|
|
24
|
+
}
|
|
25
|
+
function htmlPathFor(absRoot) {
|
|
26
|
+
return path.join(configDir(), `report-${projectSlug(path.resolve(absRoot))}.html`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// A real file:// URL (handles spaces, Windows drive letters, etc.) so the CLI
|
|
30
|
+
// can print a link the talent can click/paste to open the report.
|
|
31
|
+
function fileUrl(p) {
|
|
32
|
+
return pathToFileURL(p).href;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function freshState() {
|
|
36
|
+
return { schemaVersion: SCHEMA_VERSION, updatedAt: null, projects: {} };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function migrateFromV1(parsed) {
|
|
40
|
+
const projects = {};
|
|
41
|
+
const legacyFootprints = parsed && parsed.footprints && typeof parsed.footprints === 'object'
|
|
42
|
+
? parsed.footprints
|
|
43
|
+
: {};
|
|
44
|
+
for (const [absRoot, fp] of Object.entries(legacyFootprints)) {
|
|
45
|
+
projects[absRoot] = {
|
|
46
|
+
root: (fp && fp.root) || absRoot,
|
|
47
|
+
updatedAt: (fp && fp.generatedAt) || null,
|
|
48
|
+
footprint: fp
|
|
49
|
+
? { generatedAt: fp.generatedAt || null, report: fp.report, maturity: fp.maturity }
|
|
50
|
+
: null,
|
|
51
|
+
certifications: {},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { schemaVersion: SCHEMA_VERSION, updatedAt: (parsed && parsed.updatedAt) || null, projects };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadState() {
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(fs.readFileSync(statePath(), 'utf8'));
|
|
61
|
+
} catch {
|
|
62
|
+
return freshState();
|
|
63
|
+
}
|
|
64
|
+
if (parsed && parsed.schemaVersion === SCHEMA_VERSION && parsed.projects && typeof parsed.projects === 'object') {
|
|
65
|
+
return { schemaVersion: SCHEMA_VERSION, updatedAt: parsed.updatedAt || null, projects: parsed.projects };
|
|
66
|
+
}
|
|
67
|
+
// Any older / unknown shape (v1 global model, or garbage) -> migrate what we can.
|
|
68
|
+
return migrateFromV1(parsed);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function saveState(state) {
|
|
72
|
+
fs.mkdirSync(configDir(), { recursive: true });
|
|
73
|
+
fs.writeFileSync(statePath(), JSON.stringify(state, null, 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getOrCreateProject(state, absRoot) {
|
|
77
|
+
if (!state.projects[absRoot]) {
|
|
78
|
+
state.projects[absRoot] = {
|
|
79
|
+
root: absRoot,
|
|
80
|
+
updatedAt: null,
|
|
81
|
+
footprint: null,
|
|
82
|
+
certifications: {},
|
|
83
|
+
backendAcceptance: {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Backward-compat: a project persisted before the two-backend chain
|
|
87
|
+
// existed (talents-ai-score, ADR-020/021).
|
|
88
|
+
if (!state.projects[absRoot].backendAcceptance) {
|
|
89
|
+
state.projects[absRoot].backendAcceptance = {};
|
|
90
|
+
}
|
|
91
|
+
return state.projects[absRoot];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ---------- rendering a SINGLE project's document ---------- */
|
|
95
|
+
|
|
96
|
+
function renderProjectHtml(project, lang, gate) {
|
|
97
|
+
return renderSheet(project, lang, gate);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ---------- persist (state only) + materialize (render HTML) ---------- */
|
|
101
|
+
// ADR-016 split: `footprint` and `certify` now PERSIST state only (no HTML file, no printed link).
|
|
102
|
+
|
|
103
|
+
function stampAndSaveState(state, absRoot) {
|
|
104
|
+
state.schemaVersion = SCHEMA_VERSION;
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
state.updatedAt = now;
|
|
107
|
+
state.projects[absRoot].updatedAt = now;
|
|
108
|
+
saveState(state);
|
|
109
|
+
return { statePath: statePath(), stateDir: configDir() };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writeProjectHtml(project, absRoot, lang, gate) {
|
|
113
|
+
const html = renderProjectHtml(project, lang, gate);
|
|
114
|
+
fs.mkdirSync(configDir(), { recursive: true });
|
|
115
|
+
const p = htmlPathFor(absRoot);
|
|
116
|
+
fs.writeFileSync(p, html);
|
|
117
|
+
return { htmlPath: p, fileUrl: fileUrl(p) };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Persist THIS project's footprint into report-state.json (keyed by absolute project path).
|
|
121
|
+
function persistFootprint({ root, report, maturity }) {
|
|
122
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
123
|
+
const state = loadState();
|
|
124
|
+
const project = getOrCreateProject(state, absRoot);
|
|
125
|
+
project.footprint = {
|
|
126
|
+
generatedAt: (report && report.generatedAt) || new Date().toISOString(),
|
|
127
|
+
report,
|
|
128
|
+
maturity,
|
|
129
|
+
};
|
|
130
|
+
return stampAndSaveState(state, absRoot);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function loadAgentClassifications(root) {
|
|
134
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
135
|
+
const state = loadState();
|
|
136
|
+
const project = state.projects && state.projects[absRoot];
|
|
137
|
+
const evaluation = project && project.footprint && project.footprint.report && project.footprint.report.agentEvaluation;
|
|
138
|
+
const evaluations = evaluation && Array.isArray(evaluation.evaluations) ? evaluation.evaluations : [];
|
|
139
|
+
const byName = {};
|
|
140
|
+
for (const e of evaluations) {
|
|
141
|
+
if (e && typeof e.name === 'string' && e.name && e.classification) {
|
|
142
|
+
byName[e.name] = e.classification;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return byName;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function recordBackendAcceptance({ root, kind, backend }) {
|
|
149
|
+
if (!kind || !backend) return null;
|
|
150
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
151
|
+
const state = loadState();
|
|
152
|
+
const project = getOrCreateProject(state, absRoot);
|
|
153
|
+
project.backendAcceptance[kind] = { backend, at: new Date().toISOString() };
|
|
154
|
+
return stampAndSaveState(state, absRoot);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function loadFootprint({ root } = {}) {
|
|
158
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
159
|
+
const state = loadState();
|
|
160
|
+
const project = state.projects && state.projects[absRoot];
|
|
161
|
+
const fp = project && project.footprint;
|
|
162
|
+
return fp && fp.report ? { report: fp.report, maturity: fp.maturity } : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The gate of `materializeProjectReport`, as a NAMED TABLE rather than an inlined chain of `&&`s — and the table IS the inventory.
|
|
166
|
+
const RENDERED_DATA = {
|
|
167
|
+
footprint: (p) => !!(p.footprint && p.footprint.report),
|
|
168
|
+
certifications: (p) => !!(p.certifications && Object.keys(p.certifications).length),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
function hasRenderableData(project) {
|
|
172
|
+
if (!project) return false;
|
|
173
|
+
return Object.values(RENDERED_DATA).some((has) => has(project));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Materialize (render + write) THIS project's cumulative HTML from persisted state, and return its path + file:// URL.
|
|
177
|
+
function materializeProjectReport({ root, lang }) {
|
|
178
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
179
|
+
const state = loadState();
|
|
180
|
+
const project = state.projects[absRoot];
|
|
181
|
+
if (!hasRenderableData(project)) return { hasData: false };
|
|
182
|
+
// issue 123 / ADR-044: the shareable HTML is gated by identity AT GENERATION TIME — this is the moment the `report` command produces the file, so this is where the gate is resolved.
|
|
183
|
+
const gate = reportGateForIdentity();
|
|
184
|
+
return { hasData: true, ...writeProjectHtml(project, absRoot, lang, gate) };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// `upsertFootprint` AND `upsertCertification` WERE HERE, and ADR-035 retired them.
|
|
188
|
+
|
|
189
|
+
function certKey(item) {
|
|
190
|
+
if (item && item.skillId != null) return `id:${item.skillId}`;
|
|
191
|
+
const name = (item && item.skillName) || '';
|
|
192
|
+
const tech = (item && item.technology) || '';
|
|
193
|
+
return `nt:${name}::${tech}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Upsert each certified Skill from this run into THIS PROJECT's report (keyed by Skill id within the project).
|
|
197
|
+
function persistCertification({ root, items }) {
|
|
198
|
+
const absRoot = path.resolve(root || process.cwd());
|
|
199
|
+
const list = Array.isArray(items) ? items.filter((i) => i && (i.skillId != null || i.skillName)) : [];
|
|
200
|
+
const state = loadState();
|
|
201
|
+
const project = getOrCreateProject(state, absRoot);
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
for (const item of list) {
|
|
204
|
+
project.certifications[certKey(item)] = { generatedAt: now, item };
|
|
205
|
+
}
|
|
206
|
+
return stampAndSaveState(state, absRoot);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
configDir,
|
|
211
|
+
statePath,
|
|
212
|
+
htmlPathFor,
|
|
213
|
+
projectSlug,
|
|
214
|
+
fileUrl,
|
|
215
|
+
loadState,
|
|
216
|
+
saveState,
|
|
217
|
+
renderProjectHtml,
|
|
218
|
+
persistFootprint,
|
|
219
|
+
persistCertification,
|
|
220
|
+
loadAgentClassifications,
|
|
221
|
+
loadFootprint,
|
|
222
|
+
recordBackendAcceptance,
|
|
223
|
+
materializeProjectReport,
|
|
224
|
+
certKey,
|
|
225
|
+
SCHEMA_VERSION,
|
|
226
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function esc(s) {
|
|
4
|
+
return String(s == null ? '' : s).replace(/[&<>"']/g, (ch) => ({
|
|
5
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
6
|
+
}[ch]));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* ---------- design tokens (Shakers "Nexia", light-only, WHITE bg) ---------- */
|
|
10
|
+
|
|
11
|
+
const TOKENS_CSS = `
|
|
12
|
+
:root{
|
|
13
|
+
/* Layer 1 — brand primitives (subset used), verbatim from the DS */
|
|
14
|
+
--ds-teal-50:#e2f2f0; --ds-teal-100:#c5e5e1; --ds-teal-300:#51b1a5;
|
|
15
|
+
--ds-teal-400:#269787; --ds-teal-500:#0e7d69; --ds-teal-600:#0b5a4c;
|
|
16
|
+
--ds-teal-700:#08473c; --ds-teal-800:#05342c; --ds-teal-900:#03211c;
|
|
17
|
+
--ds-lime-200:#f5ff96; --ds-lime-500:#d8e637; --ds-lime-600:#b0bd2d;
|
|
18
|
+
--ds-zinc-50:#fafafa; --ds-zinc-100:#f4f4f5; --ds-zinc-200:#e4e4e7;
|
|
19
|
+
--ds-zinc-300:#d4d4d8; --ds-zinc-400:#a1a1aa; --ds-zinc-500:#71717a;
|
|
20
|
+
--ds-zinc-600:#52525b; --ds-zinc-700:#3f3f46; --ds-zinc-800:#27272a; --ds-zinc-900:#18181b;
|
|
21
|
+
--ds-zinc-950:#09090b; --ds-white:#ffffff;
|
|
22
|
+
/* DS status palette (design-spec/tokens.css) — used for score bands and
|
|
23
|
+
the "partial sample" warning; functional status colors, not invented. */
|
|
24
|
+
--ds-success:#059669; --ds-success-fg:#ecfdf5;
|
|
25
|
+
--ds-warning:#f59e0b; --ds-warning-fg:#fffdf5;
|
|
26
|
+
--ds-destructive:#e11d48; --ds-destructive-fg:#fef2f2;
|
|
27
|
+
|
|
28
|
+
/* Radii ("Border Radius" frame) */
|
|
29
|
+
--r-sm:6px; --r-md:8px; --r-lg:10px; --r-xl:14px; --r-full:9999px;
|
|
30
|
+
/* Shadows (shadcn upstream) */
|
|
31
|
+
--shadow-sm:0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);
|
|
32
|
+
--shadow-md:0 1px 3px 0 rgb(0 0 0 / .1), 0 2px 4px -1px rgb(0 0 0 / .1);
|
|
33
|
+
--shadow-lg:0 1px 3px 0 rgb(0 0 0 / .1), 0 4px 6px -1px rgb(0 0 0 / .1);
|
|
34
|
+
|
|
35
|
+
/* Inter with a system fallback (no @font-face, no network): if Inter isn't
|
|
36
|
+
installed it degrades to the DS's own fallback stack. */
|
|
37
|
+
--font-sans:"Inter",ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,
|
|
38
|
+
"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,
|
|
39
|
+
"Apple Color Emoji","Segoe UI Emoji";
|
|
40
|
+
--font-mono:ui-monospace,"SF Mono","Cascadia Code","JetBrains Mono",Menlo,Consolas,monospace;
|
|
41
|
+
|
|
42
|
+
/* Layer 2 — semantic (light theme, the ONLY theme). Background is WHITE
|
|
43
|
+
(the DS's --color-background), priority #1. */
|
|
44
|
+
--bg:var(--ds-white);
|
|
45
|
+
--surface:var(--ds-white);
|
|
46
|
+
--fg:var(--ds-zinc-900);
|
|
47
|
+
--muted:var(--ds-zinc-700);
|
|
48
|
+
/* zinc-600 (not zinc-500) for caption/label text: clears WCAG AA on white. */
|
|
49
|
+
--faint:var(--ds-zinc-600);
|
|
50
|
+
--border:var(--ds-zinc-200);
|
|
51
|
+
--primary:var(--ds-teal-800);
|
|
52
|
+
--primary-fg:var(--ds-zinc-50);
|
|
53
|
+
--secondary:var(--ds-teal-50);
|
|
54
|
+
--secondary-fg:var(--ds-teal-600);
|
|
55
|
+
--emphasis:var(--ds-teal-500);
|
|
56
|
+
--emphasis-strong:var(--ds-teal-600);
|
|
57
|
+
--accent-lime:var(--ds-lime-500);
|
|
58
|
+
--accent-lime-fg:var(--ds-teal-800);
|
|
59
|
+
/* Model chip: an opaque lime tint over the surface, dark teal text. */
|
|
60
|
+
--model-bg:color-mix(in srgb,var(--ds-lime-500) 26%, var(--surface));
|
|
61
|
+
--model-fg:var(--ds-teal-800);
|
|
62
|
+
--off:var(--ds-zinc-300);
|
|
63
|
+
--track:var(--ds-zinc-100);
|
|
64
|
+
--ring:var(--ds-teal-500);
|
|
65
|
+
/* Score bands (certification): DS status colors. */
|
|
66
|
+
--band-high-bg:var(--ds-success); --band-high-fg:var(--ds-success-fg);
|
|
67
|
+
--band-mid-bg:var(--ds-warning); --band-mid-fg:var(--ds-warning-fg);
|
|
68
|
+
--band-low-bg:var(--ds-destructive); --band-low-fg:var(--ds-destructive-fg);
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
/* ---------- shared base + primitives (used by every report) ---------- */
|
|
73
|
+
|
|
74
|
+
const BASE_CSS = `
|
|
75
|
+
*{box-sizing:border-box}
|
|
76
|
+
html{color-scheme:light}
|
|
77
|
+
body{margin:0;background:var(--bg);color:var(--fg);
|
|
78
|
+
font-family:var(--font-sans);line-height:1.45;
|
|
79
|
+
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
|
|
80
|
+
padding:40px 20px 64px;}
|
|
81
|
+
.wrap{max-width:840px;margin:0 auto;overflow-x:clip}
|
|
82
|
+
.card{background:var(--surface);border:1px solid var(--border);
|
|
83
|
+
border-radius:var(--r-lg);box-shadow:var(--shadow-sm)}
|
|
84
|
+
|
|
85
|
+
/* Header */
|
|
86
|
+
header{margin-bottom:24px}
|
|
87
|
+
.badge{display:inline-flex;align-items:center;gap:8px;
|
|
88
|
+
background:var(--secondary);color:var(--secondary-fg);
|
|
89
|
+
font-size:12px;font-weight:600;letter-spacing:.02em;
|
|
90
|
+
padding:5px 12px;border-radius:var(--r-full)}
|
|
91
|
+
.badge .spark{width:7px;height:7px;border-radius:50%;background:var(--emphasis)}
|
|
92
|
+
h1{font-size:clamp(28px,5vw,36px);font-weight:700;letter-spacing:-.02em;
|
|
93
|
+
line-height:1.15;margin:16px 0 6px}
|
|
94
|
+
.sub{color:var(--muted);font-size:16px;margin:0}
|
|
95
|
+
|
|
96
|
+
/* Section scaffolding */
|
|
97
|
+
section{margin-bottom:24px}
|
|
98
|
+
.h2{font-size:13px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
|
|
99
|
+
color:var(--faint);margin:0 0 12px 2px}
|
|
100
|
+
|
|
101
|
+
/* Chips */
|
|
102
|
+
.chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
103
|
+
.chip{font-size:11px;font-weight:500;letter-spacing:.02em;color:var(--secondary-fg);
|
|
104
|
+
background:var(--secondary);padding:3px 10px;border-radius:var(--r-full);
|
|
105
|
+
flex:none;white-space:nowrap}
|
|
106
|
+
.chip.empty{background:transparent;color:var(--faint);padding-left:0}
|
|
107
|
+
|
|
108
|
+
/* Footer */
|
|
109
|
+
footer{margin-top:28px;color:var(--faint);font-size:12.5px;line-height:1.55}
|
|
110
|
+
.priv{display:flex;gap:12px;padding:16px 18px;border-radius:var(--r-lg);
|
|
111
|
+
background:var(--secondary);color:var(--secondary-fg);margin-bottom:16px}
|
|
112
|
+
.priv .lock{flex:none;font-size:16px;line-height:1.4}
|
|
113
|
+
.meta-line{font-family:var(--font-mono);font-size:11.5px}
|
|
114
|
+
.meta-line code{color:var(--emphasis-strong);font-weight:600}
|
|
115
|
+
details{margin-top:14px}
|
|
116
|
+
details summary{cursor:pointer;color:var(--muted);font-weight:500;
|
|
117
|
+
padding:8px 0;user-select:none}
|
|
118
|
+
details summary:hover{color:var(--fg)}
|
|
119
|
+
pre{background:var(--bg);border:1px solid var(--border);border-radius:var(--r-md);
|
|
120
|
+
padding:14px;overflow:auto;font-family:var(--font-mono);font-size:11.5px;
|
|
121
|
+
color:var(--muted);max-height:300px}
|
|
122
|
+
|
|
123
|
+
a:focus-visible,summary:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
|
|
124
|
+
|
|
125
|
+
@media (prefers-reduced-motion:reduce){
|
|
126
|
+
*{animation:none!important;transition:none!important}
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
// Generic, zero-network clipboard helper shared by every report.
|
|
131
|
+
const COPY_SCRIPT = `
|
|
132
|
+
(function(){
|
|
133
|
+
function fallbackCopy(text){
|
|
134
|
+
var ta=document.createElement('textarea');ta.value=text;ta.setAttribute('readonly','');
|
|
135
|
+
ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.focus();ta.select();
|
|
136
|
+
try{document.execCommand('copy');}catch(e){}document.body.removeChild(ta);
|
|
137
|
+
}
|
|
138
|
+
document.querySelectorAll('[data-copy-target]').forEach(function(btn){
|
|
139
|
+
btn.addEventListener('click',function(){
|
|
140
|
+
var target=document.getElementById(btn.getAttribute('data-copy-target'));
|
|
141
|
+
if(!target)return;var text=target.textContent;
|
|
142
|
+
var showCopied=function(){
|
|
143
|
+
var original=btn.getAttribute('data-original-label')||btn.textContent;
|
|
144
|
+
btn.setAttribute('data-original-label',original);
|
|
145
|
+
btn.textContent=btn.getAttribute('data-copied-label')||original;
|
|
146
|
+
btn.classList.add('copied');
|
|
147
|
+
setTimeout(function(){btn.textContent=original;btn.classList.remove('copied');},1800);
|
|
148
|
+
};
|
|
149
|
+
if(navigator.clipboard&&navigator.clipboard.writeText){
|
|
150
|
+
navigator.clipboard.writeText(text).then(showCopied,function(){fallbackCopy(text);showCopied();});
|
|
151
|
+
}else{fallbackCopy(text);showCopied();}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
})();
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
// Assembles a full, self-contained HTML document from the shared theme plus a report's own component CSS, body markup, and (optional) extra script.
|
|
158
|
+
function renderDocument({ lang, title, componentCss = '', body, script = '' }) {
|
|
159
|
+
const scriptTag = (COPY_SCRIPT + '\n' + script).trim();
|
|
160
|
+
return `<!DOCTYPE html>
|
|
161
|
+
<html lang="${esc(lang)}">
|
|
162
|
+
<head>
|
|
163
|
+
<meta charset="utf-8">
|
|
164
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
165
|
+
<title>${esc(title)}</title>
|
|
166
|
+
<style>${TOKENS_CSS}${BASE_CSS}${componentCss}</style>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
<div class="wrap">
|
|
170
|
+
${body}
|
|
171
|
+
</div>
|
|
172
|
+
<script>${scriptTag}</script>
|
|
173
|
+
</body>
|
|
174
|
+
</html>`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { esc, renderDocument, TOKENS_CSS, BASE_CSS, COPY_SCRIPT };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { postJsonWithTimeout } = require('./backend-request');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
6
|
+
|
|
7
|
+
function parseJson(raw) {
|
|
8
|
+
try {
|
|
9
|
+
return raw ? JSON.parse(raw) : null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function authHeaders({ accessToken } = {}) {
|
|
16
|
+
const headers = {};
|
|
17
|
+
if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function requestUsageReport({ accessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
22
|
+
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
23
|
+
|
|
24
|
+
let res;
|
|
25
|
+
try {
|
|
26
|
+
res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, authHeaders({ accessToken }));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (res.status < 200 || res.status >= 300) {
|
|
32
|
+
return { ok: false, reason: `http-${res.status}` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const json = parseJson(res.raw);
|
|
36
|
+
const data = json && typeof json === 'object' ? json.data : null;
|
|
37
|
+
if (!data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
report: data.report && typeof data.report === 'object' ? data.report : null,
|
|
42
|
+
fluency: data.fluency && typeof data.fluency === 'object' ? data.fluency : null,
|
|
43
|
+
interactionQuality:
|
|
44
|
+
data.interactionQuality && typeof data.interactionQuality === 'object'
|
|
45
|
+
? data.interactionQuality
|
|
46
|
+
: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { requestUsageReport };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared, reusable output reveal for the NEW talent commands: buffer the human
|
|
4
|
+
// render and type it out with the report's typewriter pacing (bounded by
|
|
5
|
+
// `maxTotalMs`). ONE place, so every new command animates consistently.
|
|
6
|
+
//
|
|
7
|
+
// The LOADER stays live: on a TTY `io.withProgress` animates on stderr during the
|
|
8
|
+
// fetch; only the post-fetch RENDER is buffered here and revealed afterwards.
|
|
9
|
+
//
|
|
10
|
+
// GATE (reused from the typewriter, not reinvented): no animation in `--json`
|
|
11
|
+
// (machine output), in an interactive setter (live prompts), off a TTY, or under
|
|
12
|
+
// NO_ANIMATION — in those cases writes are passed straight through.
|
|
13
|
+
|
|
14
|
+
const { typeOut, revealEnabled } = require('./typewriter');
|
|
15
|
+
|
|
16
|
+
// Returns `{ write, finish }`. When animating, `write` buffers and `finish`
|
|
17
|
+
// types the buffer out; otherwise `write` passes through and `finish` is a no-op.
|
|
18
|
+
function makeRevealSink({ json = false, interactive = false, out = null, stream = process.stdout, env = process.env, sleep } = {}) {
|
|
19
|
+
const passthrough = out || ((s) => { if (stream && typeof stream.write === 'function') stream.write(s); });
|
|
20
|
+
const animating = !json && !interactive && !out && revealEnabled({ stream, env });
|
|
21
|
+
if (!animating) return { write: passthrough, finish: async () => {} };
|
|
22
|
+
let buf = '';
|
|
23
|
+
return {
|
|
24
|
+
write: (s) => { buf += s; },
|
|
25
|
+
finish: async () => { if (buf) await typeOut(buf, { stream, ...(sleep ? { sleep } : {}) }); },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { makeRevealSink };
|