shakers-cli 0.0.0-stage → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,351 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// `superadmin` — NON-PROD, password-authenticated superadmin SESSION (ADR-027, supersedes the ADR-021/022/023 provision/teardown/authorized-authoring flow).
|
|
5
|
+
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const { detectReportLang, getCatalog } = require('../src/i18n');
|
|
9
|
+
const {
|
|
10
|
+
getSuperadminSessionEndpoint,
|
|
11
|
+
getInspectCertificationsEndpoint,
|
|
12
|
+
saveSuperadminSession,
|
|
13
|
+
clearSuperadminSession,
|
|
14
|
+
loadSuperadminSession,
|
|
15
|
+
getProfile,
|
|
16
|
+
} = require('../src/config');
|
|
17
|
+
const { isValidEmail, normalizeEmail } = require('../src/share');
|
|
18
|
+
const { createStdinAsk } = require('../src/stdin-ask');
|
|
19
|
+
const { runInteractiveMultiSelect, wrapDesc } = require('../src/interactive-select');
|
|
20
|
+
const { palette } = require('../src/ansi');
|
|
21
|
+
const { switchProfile } = require('../src/profile-switch');
|
|
22
|
+
|
|
23
|
+
const REQUEST_TIMEOUT_MS = 20000;
|
|
24
|
+
|
|
25
|
+
// Only `dim`/`reset` — the numbered fallback's per-option descriptions are quiet chrome, same treatment every other picker in this CLI gives them.
|
|
26
|
+
const ANSI = palette({ reset: '\x1b[0m', dim: '\x1b[2m' });
|
|
27
|
+
|
|
28
|
+
// Minimal flag parse: --email, --lang, --inspect, --logout (all optional; the interactive prompt / a stdin pipe fills the gaps).
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const opts = {
|
|
31
|
+
email: null,
|
|
32
|
+
lang: null,
|
|
33
|
+
inspect: false,
|
|
34
|
+
logout: false,
|
|
35
|
+
};
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
if (a === '--email') opts.email = argv[++i];
|
|
39
|
+
else if (a.startsWith('--email=')) opts.email = a.slice('--email='.length);
|
|
40
|
+
else if (a === '--inspect' || a === '--audit') opts.inspect = true;
|
|
41
|
+
else if (a === '--logout' || a === '--forget') opts.logout = true;
|
|
42
|
+
else if (a === '--lang' && (argv[i + 1] === 'es' || argv[i + 1] === 'en')) opts.lang = argv[++i];
|
|
43
|
+
else if (a === '--lang=es') opts.lang = 'es';
|
|
44
|
+
else if (a === '--lang=en') opts.lang = 'en';
|
|
45
|
+
}
|
|
46
|
+
return opts;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function postJson(url, body) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
let u;
|
|
52
|
+
try {
|
|
53
|
+
u = new URL(url);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return reject(e);
|
|
56
|
+
}
|
|
57
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
58
|
+
const data = Buffer.from(JSON.stringify(body));
|
|
59
|
+
const req = lib.request(
|
|
60
|
+
u,
|
|
61
|
+
{
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
Accept: 'application/json',
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
'Content-Length': data.length,
|
|
67
|
+
},
|
|
68
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
69
|
+
},
|
|
70
|
+
(res) => {
|
|
71
|
+
let raw = '';
|
|
72
|
+
res.on('data', (c) => (raw += c));
|
|
73
|
+
res.on('end', () => {
|
|
74
|
+
let json = null;
|
|
75
|
+
try {
|
|
76
|
+
json = raw ? JSON.parse(raw) : null;
|
|
77
|
+
} catch {
|
|
78
|
+
json = null;
|
|
79
|
+
}
|
|
80
|
+
resolve({ status: res.statusCode, json });
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
req.on('timeout', () => req.destroy(Object.assign(new Error('timeout'), { kind: 'timeout' })));
|
|
85
|
+
req.on('error', reject);
|
|
86
|
+
req.write(data);
|
|
87
|
+
req.end();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Reads a line via the shared reader when available.
|
|
92
|
+
async function promptLine(ask, canPrompt, label) {
|
|
93
|
+
if (!canPrompt) return '';
|
|
94
|
+
return String(await ask(label)).trim();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The SECRET prompt (issue 102).
|
|
98
|
+
async function promptSecret(ask, canPrompt, label) {
|
|
99
|
+
if (!canPrompt) return '';
|
|
100
|
+
const read = typeof ask.secret === 'function' ? ask.secret.bind(ask) : ask;
|
|
101
|
+
return String(await read(label)).trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function profileMenuItems(c) {
|
|
105
|
+
const current = getProfile();
|
|
106
|
+
return [
|
|
107
|
+
{
|
|
108
|
+
key: 'talent',
|
|
109
|
+
label: current === 'talent' ? c.profileMenuTalentCurrent : c.profileMenuTalent,
|
|
110
|
+
desc: c.profileMenuTalentDesc,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: 'external',
|
|
114
|
+
label: current === 'external' ? c.profileMenuExternalCurrent : c.profileMenuExternal,
|
|
115
|
+
desc: c.profileMenuExternalDesc,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function askNumberedChoice(ask, promptText, count) {
|
|
121
|
+
const raw = (await ask(promptText)).trim();
|
|
122
|
+
if (!raw) return null;
|
|
123
|
+
const n = Number.parseInt(raw, 10);
|
|
124
|
+
if (!Number.isInteger(n) || n < 1 || n > count) return null;
|
|
125
|
+
return n - 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The picker itself.
|
|
129
|
+
async function chooseProfileAction({ ask, c, input, output, stdinIsTTY }) {
|
|
130
|
+
const items = profileMenuItems(c);
|
|
131
|
+
if (stdinIsTTY && typeof ask.suspend === 'function') {
|
|
132
|
+
ask.suspend();
|
|
133
|
+
const picked = await runInteractiveMultiSelect({
|
|
134
|
+
items,
|
|
135
|
+
labelFor: (i) => i.label,
|
|
136
|
+
header: c.profileMenuHeading,
|
|
137
|
+
hint: c.profileMenuHint,
|
|
138
|
+
single: true,
|
|
139
|
+
descriptionFor: (i) => i.desc || null,
|
|
140
|
+
...(input ? { input } : {}),
|
|
141
|
+
...(output ? { output } : {}),
|
|
142
|
+
});
|
|
143
|
+
ask.resume();
|
|
144
|
+
return picked && picked.length ? picked[0].key : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
process.stdout.write(`\n ${c.profileMenuHeading}\n`);
|
|
148
|
+
items.forEach((item, i) => {
|
|
149
|
+
process.stdout.write(` ${i + 1}) ${item.label}\n`);
|
|
150
|
+
if (item.desc) for (const line of wrapDesc(item.desc)) process.stdout.write(` ${ANSI.dim}${line}${ANSI.reset}\n`);
|
|
151
|
+
});
|
|
152
|
+
const idx = await askNumberedChoice(ask, ` ${c.profileMenuPrompt(items.length)}`, items.length);
|
|
153
|
+
return idx === null ? null : items[idx].key;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Runs the picker and, on a real choice, the switch itself.
|
|
157
|
+
async function runProfileMenu({ ask, c, input, output, stdinIsTTY }) {
|
|
158
|
+
const picked = await chooseProfileAction({ ask, c, input, output, stdinIsTTY });
|
|
159
|
+
if (!picked) {
|
|
160
|
+
process.stdout.write(`\n ${c.profileMenuCancelled}\n\n`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const current = getProfile();
|
|
164
|
+
if (picked === current) {
|
|
165
|
+
process.stdout.write(`\n ${c.profileAlreadyOn(picked)}\n\n`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const result = switchProfile(picked);
|
|
169
|
+
process.stdout.write(`\n ${c.profileSwitched(result.to)}\n\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, input = undefined, output = undefined } = {}) {
|
|
173
|
+
const opts = parseArgs(argv);
|
|
174
|
+
const lang = opts.lang || detectReportLang();
|
|
175
|
+
const c = getCatalog(lang).superadmin;
|
|
176
|
+
|
|
177
|
+
// --logout is local-only (no endpoint, no password): forget the stored token.
|
|
178
|
+
if (opts.logout) {
|
|
179
|
+
clearSuperadminSession();
|
|
180
|
+
process.stdout.write(`\n ${c.loggedOut}\n\n`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (opts.inspect) {
|
|
185
|
+
const endpoint = getInspectCertificationsEndpoint();
|
|
186
|
+
if (!endpoint) {
|
|
187
|
+
process.stderr.write(`\n ${c.errorNoEndpoint}\n\n`);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
process.stdout.write(`\n ${c.inspectIntro}\n`);
|
|
192
|
+
process.stdout.write(` ${c.queryingPrimary(endpoint)}\n`);
|
|
193
|
+
const canPrompt = true;
|
|
194
|
+
const ask = injectedAsk || createStdinAsk();
|
|
195
|
+
try {
|
|
196
|
+
const password = await promptSecret(ask, canPrompt, c.passwordPrompt);
|
|
197
|
+
await runInspect({ opts, c, endpoint, password, ask, canPrompt });
|
|
198
|
+
} finally {
|
|
199
|
+
if (!injectedAsk) ask.close();
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Default ("session") mode.
|
|
205
|
+
const existing = loadSuperadminSession();
|
|
206
|
+
const ask = injectedAsk || createStdinAsk();
|
|
207
|
+
try {
|
|
208
|
+
let sessionReady = !!existing;
|
|
209
|
+
if (!existing) {
|
|
210
|
+
const endpoint = getSuperadminSessionEndpoint();
|
|
211
|
+
if (!endpoint) {
|
|
212
|
+
process.stderr.write(`\n ${c.errorNoEndpoint}\n\n`);
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write(`\n ${c.sessionIntro}\n`);
|
|
217
|
+
// talents-ai-score, ADR-020/021 (coordinator ruling): superadmin is DELIBERATELY single-hop, PRIMARY (certifications service) ONLY — never hub.
|
|
218
|
+
process.stdout.write(` ${c.queryingPrimary(endpoint)}\n`);
|
|
219
|
+
// Always true (three-way review): the password has NO argv flag anymore, so it must always be collectible — either from a real TTY, the REPL's injected reader, or a piped stdin.
|
|
220
|
+
const password = await promptSecret(ask, true, c.passwordPrompt);
|
|
221
|
+
sessionReady = await runOpenSession({ opts, c, endpoint, password, ask, canPrompt: true });
|
|
222
|
+
}
|
|
223
|
+
if (sessionReady) {
|
|
224
|
+
const stdinIsTTY =
|
|
225
|
+
input !== undefined ? !!input.isTTY : !!process.stdin.isTTY;
|
|
226
|
+
await runProfileMenu({ ask, c, input, output, stdinIsTTY });
|
|
227
|
+
}
|
|
228
|
+
} finally {
|
|
229
|
+
if (!injectedAsk) ask.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ADR-025 read-only attribution receipt for stored certifications.
|
|
234
|
+
async function runInspect({ opts, c, endpoint, password, ask, canPrompt }) {
|
|
235
|
+
const email = await resolveEmailArg(opts, c, ask, canPrompt, c.inspectEmailPrompt);
|
|
236
|
+
if (!password || !email || !isValidEmail(email)) {
|
|
237
|
+
process.stderr.write(`\n ${c.needInput}\n\n`);
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let res;
|
|
243
|
+
try {
|
|
244
|
+
res = await postJson(endpoint, { password, email: normalizeEmail(email) });
|
|
245
|
+
} catch {
|
|
246
|
+
process.stderr.write(`\n ${c.inspectErrorGeneric}\n\n`);
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (res.status >= 200 && res.status < 300) {
|
|
252
|
+
const certs = (res.json && Array.isArray(res.json.certifications) && res.json.certifications) || [];
|
|
253
|
+
printInspectReceipts(certs, normalizeEmail(email), c);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (res.status === 403) process.stderr.write(`\n ${c.errorWrongPassword}\n\n`);
|
|
257
|
+
else if (res.status === 404) process.stderr.write(`\n ${c.errorDisabled}\n\n`);
|
|
258
|
+
else process.stderr.write(`\n ${c.inspectErrorGeneric}\n\n`);
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Prints the stored authorship + rubric evidence per certification. Attribution
|
|
263
|
+
// trail (git authorship), NOT cryptographic proof — stated in the note.
|
|
264
|
+
function printInspectReceipts(certs, email, c) {
|
|
265
|
+
if (certs.length === 0) {
|
|
266
|
+
process.stdout.write(`\n ${c.inspectNone(email)}\n\n`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const L = c.inspectLabels;
|
|
270
|
+
const out = [`\n ${c.inspectHeader(certs.length, email)}\n`];
|
|
271
|
+
for (const cert of certs) {
|
|
272
|
+
const dims =
|
|
273
|
+
cert.dimensionScores && typeof cert.dimensionScores === 'object'
|
|
274
|
+
? Object.entries(cert.dimensionScores)
|
|
275
|
+
.map(([k, v]) => `${k} ${v == null ? 'N/A' : `${v}/4`}`)
|
|
276
|
+
.join(', ')
|
|
277
|
+
: '—';
|
|
278
|
+
const confirmed = Array.isArray(cert.authorEmails)
|
|
279
|
+
? cert.authorEmails.filter((a) => a && a.matched).map((a) => a.email)
|
|
280
|
+
: [];
|
|
281
|
+
const considered = Array.isArray(cert.authorEmails) ? cert.authorEmails.map((a) => a.email) : [];
|
|
282
|
+
const files = Array.isArray(cert.sampledFiles) ? cert.sampledFiles : [];
|
|
283
|
+
out.push(` ── ${cert.skillName}${cert.technology ? ` (${cert.technology})` : ''}`);
|
|
284
|
+
out.push(` ${L.score}: ${cert.score == null ? 'n/a' : `${cert.score}/100`}`);
|
|
285
|
+
out.push(` ${L.dimensions}: ${dims}`);
|
|
286
|
+
if (cert.repository) out.push(` ${L.repo}: ${cert.repository}`);
|
|
287
|
+
if (cert.commitRange) out.push(` ${L.commitRange}: ${cert.commitRange}`);
|
|
288
|
+
if (files.length) out.push(` ${L.sampledFiles}: ${files.join(', ')}`);
|
|
289
|
+
if (confirmed.length) out.push(` ${L.authorsConfirmed}: ${confirmed.join(', ')}`);
|
|
290
|
+
else if (considered.length) out.push(` ${L.authorsConsidered}: ${considered.join(', ')}`);
|
|
291
|
+
out.push(` ${L.model}: ${cert.model || '—'}${cert.promptVersion ? ` · ${cert.promptVersion}` : ''}`);
|
|
292
|
+
out.push(` ${L.when}: ${cert.createdAt}`);
|
|
293
|
+
if (cert.testOrigin) out.push(` ${L.testOrigin}: ✓`);
|
|
294
|
+
out.push('');
|
|
295
|
+
}
|
|
296
|
+
out.push(` ${c.inspectNote}\n`);
|
|
297
|
+
process.stdout.write(out.join('\n'));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function resolveEmailArg(opts, c, ask, canPrompt, promptLabel) {
|
|
301
|
+
if (opts.email) return opts.email;
|
|
302
|
+
if (!canPrompt) return null;
|
|
303
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
304
|
+
const raw = String(await ask(promptLabel)).trim();
|
|
305
|
+
if (isValidEmail(raw)) return raw;
|
|
306
|
+
process.stdout.write(` ${c.emailInvalid}\n`);
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ADR-027: open a superadmin SESSION — validate the password server-side, get a token, persist it locally.
|
|
312
|
+
async function runOpenSession({ opts, c, endpoint, password, ask, canPrompt }) {
|
|
313
|
+
const email = await resolveEmailArg(opts, c, ask, canPrompt, c.emailPrompt);
|
|
314
|
+
if (!password || !email || !isValidEmail(email)) {
|
|
315
|
+
process.stderr.write(`\n ${c.needInput}\n\n`);
|
|
316
|
+
process.exitCode = 1;
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let res;
|
|
321
|
+
try {
|
|
322
|
+
res = await postJson(endpoint, { password, email: normalizeEmail(email) });
|
|
323
|
+
} catch {
|
|
324
|
+
process.stderr.write(`\n ${c.errorGeneric}\n\n`);
|
|
325
|
+
process.exitCode = 1;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (res.status >= 200 && res.status < 300 && res.json && res.json.token) {
|
|
330
|
+
saveSuperadminSession({
|
|
331
|
+
email: res.json.email || normalizeEmail(email),
|
|
332
|
+
token: res.json.token,
|
|
333
|
+
expiresAt: res.json.expiresAt || null,
|
|
334
|
+
});
|
|
335
|
+
process.stdout.write(`\n ${c.sessionReady(res.json.email || normalizeEmail(email))}\n`);
|
|
336
|
+
if (res.json.expiresAt) process.stdout.write(` ${c.sessionExpires(res.json.expiresAt)}\n`);
|
|
337
|
+
process.stdout.write(` ${c.sessionHint}\n\n`);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (res.status === 403) process.stderr.write(`\n ${c.errorWrongPassword}\n\n`);
|
|
341
|
+
else if (res.status === 404) process.stderr.write(`\n ${c.errorDisabled}\n\n`);
|
|
342
|
+
else process.stderr.write(`\n ${c.errorGeneric}\n\n`);
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = { run };
|
|
348
|
+
|
|
349
|
+
if (require.main === module) {
|
|
350
|
+
run();
|
|
351
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Detached worker: fetches the latest version from the PUBLIC, unauthenticated npm registry, writes the `{lastCheck, latest}` cache, fails silent.
|
|
5
|
+
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const { writeCache } = require('../src/update-notifier');
|
|
8
|
+
|
|
9
|
+
const FETCH_TIMEOUT_MS = 10000;
|
|
10
|
+
const MAX_BODY_BYTES = 3 * 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
// Scoped names (`@scope/pkg`) address the registry as `@scope%2fpkg`, `@` literal; install-v1 metadata still carries `dist-tags`.
|
|
13
|
+
function registryUrl(name) {
|
|
14
|
+
const p = name.startsWith('@') ? `@${name.slice(1).replace('/', '%2f')}` : name;
|
|
15
|
+
return `https://registry.npmjs.org/${p}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fetchLatest(name, timeoutMs) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
let settled = false;
|
|
21
|
+
const done = (v) => {
|
|
22
|
+
if (!settled) {
|
|
23
|
+
settled = true;
|
|
24
|
+
resolve(v);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
let req;
|
|
28
|
+
try {
|
|
29
|
+
req = https.get(
|
|
30
|
+
registryUrl(name),
|
|
31
|
+
{ headers: { Accept: 'application/vnd.npm.install-v1+json' } },
|
|
32
|
+
(res) => {
|
|
33
|
+
if (res.statusCode !== 200) {
|
|
34
|
+
res.resume();
|
|
35
|
+
return done(null);
|
|
36
|
+
}
|
|
37
|
+
let body = '';
|
|
38
|
+
res.setEncoding('utf8');
|
|
39
|
+
res.on('data', (c) => {
|
|
40
|
+
body += c;
|
|
41
|
+
if (body.length > MAX_BODY_BYTES) req.destroy();
|
|
42
|
+
});
|
|
43
|
+
res.on('end', () => {
|
|
44
|
+
try {
|
|
45
|
+
const json = JSON.parse(body);
|
|
46
|
+
const latest = json && json['dist-tags'] && json['dist-tags'].latest;
|
|
47
|
+
done(typeof latest === 'string' && latest ? latest : null);
|
|
48
|
+
} catch {
|
|
49
|
+
done(null);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
return done(null);
|
|
56
|
+
}
|
|
57
|
+
req.setTimeout(timeoutMs, () => req.destroy());
|
|
58
|
+
req.on('error', () => done(null));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
const name = process.argv[2];
|
|
64
|
+
if (!name) return;
|
|
65
|
+
const latest = await fetchLatest(name, FETCH_TIMEOUT_MS);
|
|
66
|
+
if (!latest) return;
|
|
67
|
+
writeCache({ lastCheck: Date.now(), latest });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main();
|
package/bin/update.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// `update` — update the CLI to the latest published version (CLI-only, never an MCP tool); `--check` reports without installing. Logic in src/update-flow.js.
|
|
5
|
+
|
|
6
|
+
const { detectReportLang, getCatalog } = require('../src/i18n');
|
|
7
|
+
const updateFlow = require('../src/update-flow');
|
|
8
|
+
|
|
9
|
+
function parseUpdateArgs(argv = []) {
|
|
10
|
+
const o = { help: false, check: false, json: false, lang: null };
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const a = argv[i];
|
|
13
|
+
if (a === '--help' || a === '-h') o.help = true;
|
|
14
|
+
else if (a === '--check' || a === '--dry-run') o.check = true;
|
|
15
|
+
else if (a === '--json') o.json = true;
|
|
16
|
+
else if (a === '--lang') {
|
|
17
|
+
const v = argv[++i];
|
|
18
|
+
o.lang = v === 'es' || v === 'en' ? v : null;
|
|
19
|
+
} else if (a.startsWith('--lang=')) {
|
|
20
|
+
const v = a.slice(7);
|
|
21
|
+
o.lang = v === 'es' || v === 'en' ? v : null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return o;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function run(
|
|
28
|
+
argv = process.argv.slice(2),
|
|
29
|
+
{ out = null, env = process.env, deps = null } = {},
|
|
30
|
+
) {
|
|
31
|
+
const opts = parseUpdateArgs(argv);
|
|
32
|
+
const lang = opts.lang || detectReportLang();
|
|
33
|
+
const t = getCatalog(lang).cli.update;
|
|
34
|
+
const write = out || ((s) => process.stdout.write(s));
|
|
35
|
+
const flow = deps || updateFlow;
|
|
36
|
+
|
|
37
|
+
if (opts.help) {
|
|
38
|
+
write(t.help + '\n');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const info = flow.readPackageInfo();
|
|
43
|
+
const install = flow.detectInstall(env);
|
|
44
|
+
const current = info.version;
|
|
45
|
+
|
|
46
|
+
if (!opts.json) write(` ${t.checking}\n`);
|
|
47
|
+
const latest = flow.fetchLatestVersion(info);
|
|
48
|
+
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
const updateAvailable =
|
|
51
|
+
latest.ok && current ? flow.compareVersions(current, latest.version) < 0 : null;
|
|
52
|
+
write(
|
|
53
|
+
`${JSON.stringify({
|
|
54
|
+
package: info.name,
|
|
55
|
+
current: current || null,
|
|
56
|
+
latest: latest.ok ? latest.version : null,
|
|
57
|
+
install: install.kind,
|
|
58
|
+
updateAvailable,
|
|
59
|
+
reason: latest.ok ? null : latest.reason,
|
|
60
|
+
})}\n`,
|
|
61
|
+
);
|
|
62
|
+
if (!latest.ok) process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (current) write(` ${t.current(current)}\n`);
|
|
67
|
+
|
|
68
|
+
if (!latest.ok) {
|
|
69
|
+
write(` ${t.latestUnknown(latest.reason)}\n`);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!current || flow.compareVersions(current, latest.version) >= 0) {
|
|
75
|
+
write(` ${t.upToDate(latest.version)}\n`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
write(` ${t.available(current, latest.version)}\n`);
|
|
80
|
+
|
|
81
|
+
// `--check`: report only, never install.
|
|
82
|
+
if (opts.check) {
|
|
83
|
+
write(` ${t.checkHint}\n`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const plan = flow.buildUpdatePlan(install, info, env);
|
|
88
|
+
if (!plan) {
|
|
89
|
+
if (install.kind === 'source') write(` ${t.sourceCheckout}\n`);
|
|
90
|
+
else write(` ${t.unknownInstall(flow.manualCommand(info))}\n`);
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
write(` ${t.detected(plan.label)}\n`);
|
|
96
|
+
write(` ${t.updating(info.name, current, latest.version)}\n\n`);
|
|
97
|
+
|
|
98
|
+
const result = flow.runInstall(plan, env);
|
|
99
|
+
if (result.ok) {
|
|
100
|
+
write(`\n ${t.success(latest.version)}\n ${t.reRun}\n`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
write(`\n ${t.failInstall(result.reason)}\n ${t.failHint(flow.manualCommand(info))}\n`);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { run, parseUpdateArgs };
|
|
108
|
+
|
|
109
|
+
if (require.main === module) {
|
|
110
|
+
run();
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shakers-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.14.1",
|
|
4
|
+
"description": "Shakers AI usage & skills evaluator: a local-first CLI (`shakers` binary) plus a local MCP server. Commands: ai-usage (scan + classify your AI-tool setup, T0-T7), certify (certify Skills from your code), share (branded LinkedIn card).",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shakers": "bin/shakers.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/shakers.js",
|
|
17
|
+
"shakers": "node bin/shakers.js",
|
|
18
|
+
"test": "node --test test/*.test.js",
|
|
19
|
+
"build:mcpb": "sh scripts/build-mcpb.sh",
|
|
20
|
+
"publish:beta": "node scripts/prepare-beta.js && npm publish --tag latest && git checkout -- package.json"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@livekit/rtc-node": "0.13.35"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"shakers",
|
|
30
|
+
"cli",
|
|
31
|
+
"ai-usage",
|
|
32
|
+
"ai",
|
|
33
|
+
"developer-tools",
|
|
34
|
+
"skills",
|
|
35
|
+
"certification"
|
|
36
|
+
],
|
|
37
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// THE AGENT-CATEGORY VOCABULARY, AND WHICH OF IT IS A SPECIALITY (issue 120).
|
|
4
|
+
|
|
5
|
+
/** The floor category key, as the service's catalog defines it (`other-1`'s category). */
|
|
6
|
+
const FLOOR_CATEGORY = 'other';
|
|
7
|
+
|
|
8
|
+
// True for the floor category only.
|
|
9
|
+
function isFloorCategory(category) {
|
|
10
|
+
return category === FLOOR_CATEGORY;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { FLOOR_CATEGORY, isFloorCategory };
|