shakers-cli 0.15.0 → 0.16.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/README.md +51 -1203
- package/bin/ai-usage.js +11 -56
- package/bin/login.js +10 -10
- package/bin/mcp.js +13 -9
- package/bin/mcpb.js +1 -1
- package/bin/onboarding.js +3 -2
- package/bin/register.js +17 -4
- package/bin/report-html.js +2 -2
- package/bin/shakers-cli.js +352 -0
- package/bin/shakers.js +20 -344
- package/bin/share.js +1 -1
- package/bin/superadmin.js +5 -5
- package/bin/version.js +3 -0
- package/package.json +12 -3
- package/src/agent-category.js +1 -1
- package/src/agent-evaluation.js +3 -3
- package/src/agent-org-chart.js +5 -5
- package/src/agent-synthesis.js +3 -3
- package/src/agent-usage.js +1 -1
- package/src/agents-client.js +4 -4
- package/src/ai.js +1 -1
- package/src/ansi.js +1 -1
- package/src/auth-client.js +18 -4
- package/src/auth-method-picker.js +1 -1
- package/src/auth-methods.js +1 -1
- package/src/auth-session-store.js +11 -8
- package/src/authorship-mismatch.js +1 -1
- package/src/authorship.js +2 -2
- package/src/automations-detector.js +1 -1
- package/src/backend-request.js +3 -3
- package/src/brand-ansi.js +1 -1
- package/src/browser-tools-detector.js +1 -1
- package/src/certify-disclaimer.js +1 -1
- package/src/certify-remediation-prompt.js +1 -1
- package/src/cli-args.js +2 -2
- package/src/command-audit.js +8 -5
- package/src/config-dir.js +1 -1
- package/src/config.js +112 -48
- package/src/consent-flow.js +4 -4
- package/src/consent-skip.js +1 -1
- package/src/detection-evidence.js +2 -2
- package/src/detection-noise.js +1 -1
- package/src/detectors.js +1 -1
- package/src/device-login.js +12 -1
- package/src/email-verification.js +3 -3
- package/src/env-paths.js +1 -1
- package/src/find-projects-client.js +12 -0
- package/src/find-projects-flow.js +1 -13
- package/src/git-activity.js +1 -1
- package/src/graph-certs.js +3 -3
- package/src/graph-scan.js +9 -9
- package/src/i18n.js +124 -124
- package/src/interactive-select.js +1 -1
- package/src/interview-client.js +6 -1
- package/src/interview-transcript.js +1 -1
- package/src/legal-notice.js +1 -1
- package/src/maturity.js +6 -6
- package/src/mcp-ai-usage-tool.js +1 -1
- package/src/mcp-auth-form.js +1 -1
- package/src/mcp-detector.js +3 -3
- package/src/mcp-device-login-tools.js +2 -1
- package/src/mcp-find-projects-tools.js +1 -2
- package/src/mcp-onboarding-tools.js +4 -4
- package/src/mcp-prompts.js +2 -2
- package/src/mcp-register-tools.js +1 -0
- package/src/mcp-server.js +5 -1
- package/src/mcp-signup-client.js +2 -2
- package/src/mcp-signup-tools.js +26 -2
- package/src/memory-structure-detector.js +1 -1
- package/src/model-call-record.js +2 -2
- package/src/onboarding-flow.js +16 -16
- package/src/onboarding-funnel.js +110 -17
- package/src/open-file.js +1 -1
- package/src/osc-link.js +1 -1
- package/src/portfolio-client.js +11 -1
- package/src/portfolio-git-info.js +1 -1
- package/src/profile-flow.js +1 -1
- package/src/profile-switch.js +5 -12
- package/src/render-certification.js +10 -10
- package/src/render-html.js +6 -6
- package/src/render-sheet.js +10 -10
- package/src/render-terminal.js +6 -6
- package/src/repo-scope.js +132 -0
- package/src/report-gating.js +1 -1
- package/src/report-store.js +6 -6
- package/src/roadmap-content.js +5 -5
- package/src/roadmap-personalization.js +3 -3
- package/src/roadmap-prompt.js +2 -2
- package/src/scan-exclusions.js +1 -1
- package/src/scanner.js +14 -14
- package/src/secret-prompt.js +1 -1
- package/src/session-gate.js +1 -1
- package/src/session-refresh.js +33 -3
- package/src/session-scan.js +1 -1
- package/src/shakers-wordmark.js +1 -1
- package/src/share-card.js +2 -2
- package/src/share-certification.js +4 -4
- package/src/share-consent-state.js +7 -7
- package/src/share-payload.js +7 -7
- package/src/share.js +7 -7
- package/src/signup-client.js +1 -1
- package/src/signup-copy.js +1 -1
- package/src/skills-client.js +1 -1
- package/src/start-add-agents.js +2 -2
- package/src/start-add-portfolio.js +3 -13
- package/src/start-add-skills.js +2 -2
- package/src/stdin-ask.js +3 -3
- package/src/strip-internal-comments.js +1 -1
- package/src/tech-detector.js +2 -2
- package/src/templates/report-sheet.html +11 -13
- package/src/terminal-agents.js +8 -8
- package/src/terminal-format.js +3 -3
- package/src/terminal-sections.js +11 -11
- package/src/tier-engine.js +3 -3
- package/src/tier-projection.js +2 -2
- package/src/transcript-turns.js +1 -1
- package/src/typewriter.js +1 -1
- package/src/usage-model-calls.js +6 -6
- package/src/usage-oneshot-commands.js +5 -5
- package/src/usage-repo-scope.js +11 -129
- package/src/usage-run-steps.js +32 -184
- package/src/usage-scan.js +235 -0
- package/src/work-streams.js +1 -1
package/bin/shakers.js
CHANGED
|
@@ -10,354 +10,30 @@ if (_nodeMajor < 18) {
|
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const { migrateLegacyConfigDir } = require('../src/config-dir');
|
|
17
|
-
const { sanitizeStaleBakedEndpoints } = require('../src/config');
|
|
18
|
-
const { ensureFreshSession } = require('../src/session-refresh');
|
|
19
|
-
const { hasUsableSession } = require('../src/session-gate');
|
|
20
|
-
const { run: runAiUsage } = require('./ai-usage');
|
|
21
|
-
const { run: runCertify } = require('./certify');
|
|
22
|
-
const { run: runShare } = require('./share');
|
|
23
|
-
const { run: runReport } = require('./report-html');
|
|
24
|
-
const { run: runSuperadmin } = require('./superadmin');
|
|
25
|
-
const { run: runLogin, runLogout } = require('./login');
|
|
26
|
-
const { run: runAddSkill } = require('./add-skill');
|
|
27
|
-
const { run: runAddAgent } = require('./add-agent');
|
|
28
|
-
const { run: runAddProject } = require('./add-project');
|
|
29
|
-
const { run: runAddRole } = require('./add-role');
|
|
30
|
-
const { run: runChangeRole } = require('./change-role');
|
|
31
|
-
const { run: runFindProjects } = require('./find-projects');
|
|
32
|
-
const { run: runShowProject } = require('./show-project');
|
|
33
|
-
const { run: runSave, runUnsave } = require('./save');
|
|
34
|
-
const { run: runInvitations } = require('./invitations');
|
|
35
|
-
const { run: runApplications } = require('./applications');
|
|
36
|
-
const { run: runAvailability } = require('./availability');
|
|
37
|
-
const { run: runCertifications } = require('./certifications');
|
|
38
|
-
const { run: runMe } = require('./me');
|
|
39
|
-
const { run: runRate } = require('./rate');
|
|
40
|
-
const { run: runLang } = require('./lang');
|
|
41
|
-
const { run: runSocials } = require('./socials');
|
|
42
|
-
const { run: runExperiences } = require('./experiences');
|
|
43
|
-
const { run: runPortfolios } = require('./portfolios');
|
|
44
|
-
const { run: runAlma } = require('./alma');
|
|
45
|
-
const { run: runRegister } = require('./register');
|
|
46
|
-
const { run: runOnboarding } = require('./onboarding');
|
|
47
|
-
const { run: runConfig } = require('./config');
|
|
48
|
-
const { run: runMcp } = require('./mcp');
|
|
49
|
-
const { run: runUpdate } = require('./update');
|
|
50
|
-
const { checkOnboardingCompleted } = require('../src/onboarding-availability');
|
|
51
|
-
const { emitCommandAudit } = require('../src/command-audit');
|
|
52
|
-
const { readPackageInfo } = require('../src/update-flow');
|
|
53
|
-
const { isSuppressed, maybeNotify, triggerBackgroundCheck } = require('../src/update-notifier');
|
|
54
|
-
|
|
55
|
-
let VERSION = '';
|
|
56
|
-
try {
|
|
57
|
-
VERSION = require('../package.json').version || '';
|
|
58
|
-
} catch {
|
|
59
|
-
VERSION = '';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parseLang(argv) {
|
|
63
|
-
for (let i = 0; i < argv.length; i++) {
|
|
64
|
-
const a = argv[i];
|
|
65
|
-
if (a === '--lang' && (argv[i + 1] === 'es' || argv[i + 1] === 'en')) return argv[i + 1];
|
|
66
|
-
if (a === '--lang=es') return 'es';
|
|
67
|
-
if (a === '--lang=en') return 'en';
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
13
|
+
if (['--version', '-v', 'version'].includes(String(process.argv[2]).toLowerCase())) {
|
|
14
|
+
require('./version');
|
|
15
|
+
process.exit(0);
|
|
70
16
|
}
|
|
71
17
|
|
|
72
|
-
const
|
|
73
|
-
'ai-usage': runAiUsage,
|
|
74
|
-
certify: runCertify,
|
|
75
|
-
share: runShare,
|
|
76
|
-
report: runReport,
|
|
77
|
-
sheet: runReport,
|
|
78
|
-
'add-skill': runAddSkill,
|
|
79
|
-
'add-agent': runAddAgent,
|
|
80
|
-
'add-project': runAddProject,
|
|
81
|
-
'add-role': runAddRole,
|
|
82
|
-
'change-role': runChangeRole,
|
|
83
|
-
'find-projects': runFindProjects,
|
|
84
|
-
'show-project': runShowProject,
|
|
85
|
-
'save-project': runSave,
|
|
86
|
-
'unsave-project': runUnsave,
|
|
87
|
-
invitations: runInvitations,
|
|
88
|
-
applications: runApplications,
|
|
89
|
-
availability: runAvailability,
|
|
90
|
-
certifications: runCertifications,
|
|
91
|
-
me: runMe,
|
|
92
|
-
profile: runMe,
|
|
93
|
-
rate: runRate,
|
|
94
|
-
lang: runLang,
|
|
95
|
-
socials: runSocials,
|
|
96
|
-
experiences: runExperiences,
|
|
97
|
-
portfolios: runPortfolios,
|
|
98
|
-
alma: runAlma,
|
|
99
|
-
register: runRegister,
|
|
100
|
-
onboarding: runOnboarding,
|
|
101
|
-
config: runConfig,
|
|
102
|
-
update: runUpdate,
|
|
103
|
-
login: runLogin,
|
|
104
|
-
logout: runLogout,
|
|
105
|
-
superadmin: runSuperadmin,
|
|
106
|
-
mcp: runMcp,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const SESSION_EXEMPT = new Set(['ai-usage', 'register', 'config', 'login', 'logout', 'superadmin', 'mcp', 'share', 'alma', 'update']);
|
|
110
|
-
|
|
111
|
-
// Command FAMILIES — a navigation layer over the flat commands (which all stay
|
|
112
|
-
// valid as aliases). `shakers <family> <sub>` resolves to the flat handler; every
|
|
113
|
-
// sub is just a friendlier name for an existing command. Additive, never breaking.
|
|
114
|
-
const FAMILIES = {
|
|
115
|
-
add: { skill: 'add-skill', agent: 'add-agent', project: 'add-project', role: 'add-role' },
|
|
116
|
-
role: { add: 'add-role', change: 'change-role', main: 'change-role' },
|
|
117
|
-
profile: {
|
|
118
|
-
show: 'me', rate: 'rate', languages: 'lang', lang: 'lang', socials: 'socials',
|
|
119
|
-
experiences: 'experiences', portfolios: 'portfolios', availability: 'availability',
|
|
120
|
-
certifications: 'certifications',
|
|
121
|
-
},
|
|
122
|
-
projects: {
|
|
123
|
-
find: 'find-projects', show: 'show-project', save: 'save-project', unsave: 'unsave-project',
|
|
124
|
-
invitations: 'invitations', applications: 'applications',
|
|
125
|
-
},
|
|
126
|
-
usage: { run: 'ai-usage', report: 'report', share: 'share' },
|
|
127
|
-
account: { login: 'login', logout: 'logout', register: 'register', config: 'config', update: 'update' },
|
|
128
|
-
certs: { list: 'certifications', certify: 'certify' },
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// `shakers <family>` with no sub: obvious default, else the family help.
|
|
132
|
-
const FAMILY_DEFAULTS = { usage: 'ai-usage', profile: 'me', certs: 'certifications' };
|
|
133
|
-
|
|
134
|
-
function familyHelpText(family, lang) {
|
|
135
|
-
const map = FAMILIES[family] || {};
|
|
136
|
-
const subs = Object.keys(map);
|
|
137
|
-
// Show `shakers <family> <sub>` next to the flat alias it maps to, so both forms are discoverable.
|
|
138
|
-
const lines = subs.map((s) => ` shakers ${family} ${s} (alias: shakers ${map[s]})`).join('\n');
|
|
139
|
-
if (lang === 'es') {
|
|
140
|
-
return `\n Familia "${family}" — subcomandos (los comandos planos entre paréntesis siguen valiendo):\n${lines}\n\n Ejecuta \`shakers ${family} <subcomando> --help\` para las opciones.\n\n`;
|
|
141
|
-
}
|
|
142
|
-
return `\n "${family}" family — subcommands (the flat commands in parentheses still work):\n${lines}\n\n Run \`shakers ${family} <subcommand> --help\` for the options.\n\n`;
|
|
143
|
-
}
|
|
18
|
+
const config = require('../src/config');
|
|
144
19
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const subToken = args.find((a) => !a.startsWith('-'));
|
|
150
|
-
const sub = subToken ? subToken.toLowerCase() : null;
|
|
151
|
-
if (sub && family[sub]) {
|
|
152
|
-
return { command: family[sub], args: args.filter((_, i) => i !== args.indexOf(subToken)) };
|
|
153
|
-
}
|
|
154
|
-
if (!sub && FAMILY_DEFAULTS[command]) return { command: FAMILY_DEFAULTS[command], args };
|
|
155
|
-
// A pure family (not also a flat handler) with no valid sub → signal "show family help".
|
|
156
|
-
if (!HANDLERS[command]) return { command, args, familyHelp: true };
|
|
157
|
-
return { command, args };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function remindAccountForAiUsage(lang, args) {
|
|
161
|
-
if (!process.stdin.isTTY || args.includes('--json')) return;
|
|
162
|
-
const r = getCatalog(lang).cli.accountReminder;
|
|
163
|
-
const ask = createStdinAsk();
|
|
164
|
-
let choice;
|
|
165
|
-
try {
|
|
166
|
-
choice = await promptSelect({
|
|
167
|
-
ask,
|
|
168
|
-
stdinIsTTY: true,
|
|
169
|
-
out: (line) => process.stdout.write(` ${line}\n`),
|
|
170
|
-
header: r.question,
|
|
171
|
-
hint: r.hint,
|
|
172
|
-
items: [{ id: 'yes', label: r.yes }, { id: 'no', label: r.no }],
|
|
173
|
-
labelFor: (it) => it.label,
|
|
174
|
-
});
|
|
175
|
-
} finally {
|
|
176
|
-
ask.close();
|
|
177
|
-
}
|
|
178
|
-
if (choice && choice.id === 'yes') {
|
|
179
|
-
await runLogin(['--lang', lang]);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function usageText(lang, { hideOnboarding = false } = {}) {
|
|
184
|
-
const title = VERSION ? `shakers v${VERSION}` : 'shakers';
|
|
185
|
-
if (lang === 'es') {
|
|
186
|
-
const onboardingLine = hideOnboarding ? '' : ` onboarding La entrevista de onboarding (por voz/texto, LiveKit)\n`;
|
|
187
|
-
return `\n ${title} — evaluación de uso de IA y skills\n\n`
|
|
188
|
-
+ ` Uso: shakers <comando> [opciones]\n\n`
|
|
189
|
-
+ ` Público (sin sesión):\n`
|
|
190
|
-
+ ` ai-usage Escanea este proyecto + tu máquina y puntúa tu setup de IA\n`
|
|
191
|
-
+ ` register Registro completo: perfil, precio, uso de IA y entrevista\n`
|
|
192
|
-
+ ` mcp Servidor MCP local (stdio) para apps de IA de escritorio\n\n`
|
|
193
|
-
+ ` Requieren sesión (ejecuta antes: shakers login):\n`
|
|
194
|
-
+ onboardingLine
|
|
195
|
-
+ ` certify Certifica una dimensión de tu rol (entrevista por LiveKit)\n`
|
|
196
|
-
+ ` add-skill Declara Skills detectadas en tu perfil\n`
|
|
197
|
-
+ ` add-agent Declara un agente detectado en tu perfil\n`
|
|
198
|
-
+ ` add-project Añade este proyecto a tu portfolio\n`
|
|
199
|
-
+ ` add-role Añade un rol a tu perfil (rol de crecimiento)\n`
|
|
200
|
-
+ ` change-role Cambia tu rol principal por uno de los que ya tienes\n`
|
|
201
|
-
+ ` find-projects Lista los proyectos/posiciones disponibles para ti\n`
|
|
202
|
-
+ ` show-project Detalle de un proyecto/posición por id\n`
|
|
203
|
-
+ ` save-project/unsave-project Guarda o quita un proyecto de tus guardados\n`
|
|
204
|
-
+ ` invitations Tus invitaciones a proyectos\n`
|
|
205
|
-
+ ` applications Proyectos a los que has aplicado + estado\n`
|
|
206
|
-
+ ` availability Ver o fijar tu disponibilidad\n`
|
|
207
|
-
+ ` certifications Tus dimensiones certificadas/sin certificar\n`
|
|
208
|
-
+ ` me Tu resumen de perfil\n`
|
|
209
|
-
+ ` rate Ver o fijar tu tarifa por proyecto\n`
|
|
210
|
-
+ ` lang Ver o fijar tus idiomas y nivel\n`
|
|
211
|
-
+ ` socials Ver o fijar tus enlaces sociales\n`
|
|
212
|
-
+ ` experiences Tus experiencias laborales\n`
|
|
213
|
-
+ ` portfolios Tus piezas de portfolio\n`
|
|
214
|
-
+ ` alma Mini-shell interactivo para conversar con Alma\n`
|
|
215
|
-
+ ` report Abre el informe HTML completo y compartible\n`
|
|
216
|
-
+ ` share Tarjeta de uso con marca para LinkedIn\n\n`
|
|
217
|
-
+ ` Sesión:\n`
|
|
218
|
-
+ ` login Inicia sesión en tu cuenta de Shakers\n`
|
|
219
|
-
+ ` logout Cierra la sesión\n`
|
|
220
|
-
+ ` config Lee/escribe la configuración (bases de hub/certs)\n`
|
|
221
|
-
+ ` update Actualiza el CLI a la última versión publicada (--check solo informa)\n\n`
|
|
222
|
-
+ ` Familias (agrupan los comandos de arriba; los nombres planos siguen valiendo como alias):\n`
|
|
223
|
-
+ ` add <skill|agent|project|role> · role <add|change>\n`
|
|
224
|
-
+ ` profile <show|rate|languages|socials|experiences|portfolios|availability|certifications>\n`
|
|
225
|
-
+ ` projects <find|show|save|unsave|invitations|applications>\n`
|
|
226
|
-
+ ` usage <run|report|share> · certs <list|certify> · account <login|logout|register|config|update>\n`
|
|
227
|
-
+ ` Ejecuta \`shakers <familia>\` (sin subcomando) para ver sus subcomandos.\n\n`
|
|
228
|
-
+ ` Ejecuta shakers <comando> --help para las opciones de cada comando.\n\n`;
|
|
229
|
-
}
|
|
230
|
-
const onboardingLine = hideOnboarding ? '' : ` onboarding The onboarding interview (over voice/text, LiveKit)\n`;
|
|
231
|
-
return `\n ${title} — AI usage & skills evaluator\n\n`
|
|
232
|
-
+ ` Usage: shakers <command> [options]\n\n`
|
|
233
|
-
+ ` Public (no session):\n`
|
|
234
|
-
+ ` ai-usage Scan this project + machine and score your AI setup\n`
|
|
235
|
-
+ ` register Full registration: profile, pricing, AI usage and interview\n`
|
|
236
|
-
+ ` mcp Local MCP server (stdio) for desktop AI apps\n\n`
|
|
237
|
-
+ ` Require a session (run \`shakers login\` first):\n`
|
|
238
|
-
+ onboardingLine
|
|
239
|
-
+ ` certify Certify a dimension of your role (LiveKit interview)\n`
|
|
240
|
-
+ ` add-skill Declare detected Skills on your profile\n`
|
|
241
|
-
+ ` add-agent Declare a detected agent on your profile\n`
|
|
242
|
-
+ ` add-project Add this project to your portfolio\n`
|
|
243
|
-
+ ` add-role Add a role to your profile (a growth role)\n`
|
|
244
|
-
+ ` change-role Switch your main role to one you already have\n`
|
|
245
|
-
+ ` find-projects List the Projects/Positions available to you\n`
|
|
246
|
-
+ ` show-project Detail of a project/position by id\n`
|
|
247
|
-
+ ` save-project/unsave-project Save or remove a project from your saved list\n`
|
|
248
|
-
+ ` invitations Your project invitations\n`
|
|
249
|
-
+ ` applications Projects you applied to + status\n`
|
|
250
|
-
+ ` availability View or set your availability\n`
|
|
251
|
-
+ ` certifications Your certified/uncertified dimensions\n`
|
|
252
|
-
+ ` me Your profile summary\n`
|
|
253
|
-
+ ` rate View or set your per-project rate\n`
|
|
254
|
-
+ ` lang View or set your languages and level\n`
|
|
255
|
-
+ ` socials View or set your social links\n`
|
|
256
|
-
+ ` experiences Your work experiences\n`
|
|
257
|
-
+ ` portfolios Your portfolio pieces\n`
|
|
258
|
-
+ ` alma Interactive mini-shell to converse with Alma\n`
|
|
259
|
-
+ ` report Open the full shareable HTML report\n`
|
|
260
|
-
+ ` share Branded usage card for LinkedIn\n\n`
|
|
261
|
-
+ ` Session:\n`
|
|
262
|
-
+ ` login Sign in to your Shakers account\n`
|
|
263
|
-
+ ` logout Sign out\n`
|
|
264
|
-
+ ` config Read/write configuration (hub/certs bases)\n`
|
|
265
|
-
+ ` update Update the CLI to the latest published version (--check only reports)\n\n`
|
|
266
|
-
+ ` Families (group the commands above; the flat names still work as aliases):\n`
|
|
267
|
-
+ ` add <skill|agent|project|role> · role <add|change>\n`
|
|
268
|
-
+ ` profile <show|rate|languages|socials|experiences|portfolios|availability|certifications>\n`
|
|
269
|
-
+ ` projects <find|show|save|unsave|invitations|applications>\n`
|
|
270
|
-
+ ` usage <run|report|share> · certs <list|certify> · account <login|logout|register|config|update>\n`
|
|
271
|
-
+ ` Run \`shakers <family>\` (no subcommand) to list its subcommands.\n\n`
|
|
272
|
-
+ ` Run \`shakers <command> --help\` for a command's options.\n\n`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function gateText(command, lang) {
|
|
276
|
-
if (lang === 'es') {
|
|
277
|
-
return `\n "${command}" requiere una sesión activa.\n`
|
|
278
|
-
+ ` Inicia sesión: shakers login\n`
|
|
279
|
-
+ ` ¿Sin cuenta?: shakers register\n\n`;
|
|
280
|
-
}
|
|
281
|
-
return `\n "${command}" requires an active session.\n`
|
|
282
|
-
+ ` Sign in: shakers login\n`
|
|
283
|
-
+ ` No account?: shakers register\n\n`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function unknownText(command, lang) {
|
|
287
|
-
if (lang === 'es') {
|
|
288
|
-
return `\n Comando desconocido: "${command}".\n`
|
|
289
|
-
+ ` Ejecuta shakers para ver los comandos disponibles.\n\n`;
|
|
290
|
-
}
|
|
291
|
-
return `\n Unknown command: "${command}".\n`
|
|
292
|
-
+ ` Run \`shakers\` to see the available commands.\n\n`;
|
|
20
|
+
const problem = config.envProfileProblem(process.env);
|
|
21
|
+
if (problem) {
|
|
22
|
+
process.stderr.write(`\n shakers: ${problem}\n\n`);
|
|
23
|
+
process.exit(1);
|
|
293
24
|
}
|
|
294
25
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
migrateLegacyConfigDir(process.env);
|
|
300
|
-
sanitizeStaleBakedEndpoints(process.env);
|
|
301
|
-
// Mint a fresh hub JWT from the stored cookie once per command (email sessions only; a no-op for Google/legacy or when no session exists).
|
|
302
|
-
try { await ensureFreshSession(process.env); } catch { /* never blocks a command */ }
|
|
303
|
-
|
|
304
|
-
const token = argv.find((a) => !a.startsWith('-'));
|
|
305
|
-
|
|
306
|
-
// Single dispatch choke-point (ADR-060): audit EXACTLY once before branching, fire-and-forget (never awaited). MCP tool calls run in-process and never reach here.
|
|
307
|
-
const auditCommand =
|
|
308
|
-
!token || token.toLowerCase() === 'help' ? 'help' : token.toLowerCase();
|
|
309
|
-
emitCommandAudit({ command: auditCommand, argv, env: process.env });
|
|
310
|
-
|
|
311
|
-
// Non-blocking update notice: reads a cached version synchronously, registry check runs in a detached worker; stderr-only, suppressed for --json/non-TTY/CI/update/opt-out.
|
|
312
|
-
if (!isSuppressed({ argv, command: auditCommand, env: process.env })) {
|
|
313
|
-
maybeNotify({ currentVersion: VERSION, lang, env: process.env });
|
|
314
|
-
triggerBackgroundCheck({ name: readPackageInfo().name, env: process.env });
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!token || token.toLowerCase() === 'help') {
|
|
318
|
-
let hideOnboarding = false;
|
|
319
|
-
try {
|
|
320
|
-
hideOnboarding = await checkOnboardingCompleted({ timeoutMs: 3000 });
|
|
321
|
-
} catch {
|
|
322
|
-
hideOnboarding = false;
|
|
323
|
-
}
|
|
324
|
-
process.stdout.write(usageText(lang, { hideOnboarding }));
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
const token = argv.find((a) => !a.startsWith('-'));
|
|
28
|
+
const rest = argv.filter((_, i) => i !== argv.indexOf(token));
|
|
327
29
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const command = resolved.command;
|
|
338
|
-
const args = resolved.args;
|
|
339
|
-
|
|
340
|
-
const handler = HANDLERS[command];
|
|
341
|
-
if (!handler) {
|
|
342
|
-
process.stderr.write(unknownText(command, lang));
|
|
343
|
-
process.exitCode = 1;
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
348
|
-
const loggedIn = await hasUsableSession(process.env);
|
|
349
|
-
|
|
350
|
-
if (!wantsHelp && !SESSION_EXEMPT.has(command) && !loggedIn) {
|
|
351
|
-
process.stderr.write(gateText(command, lang));
|
|
352
|
-
process.exitCode = 1;
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (command === 'ai-usage' && !wantsHelp && !loggedIn) {
|
|
357
|
-
await remindAccountForAiUsage(lang, args);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
await handler(args);
|
|
30
|
+
if (token && token.toLowerCase() === 'mcp' && rest[0] !== 'install') {
|
|
31
|
+
require('../src/config-dir').migrateLegacyConfigDir(process.env);
|
|
32
|
+
config.sanitizeStaleBakedEndpoints(process.env);
|
|
33
|
+
require('../src/session-refresh').ensureFreshSession(process.env)
|
|
34
|
+
.catch(() => null)
|
|
35
|
+
.then(() => require('../src/command-audit').emitCommandAudit({ command: 'mcp', argv, env: process.env }));
|
|
36
|
+
require('./mcp').run(rest, { makeInterviewClient: require('../src/interview-client').createInterviewClient });
|
|
37
|
+
} else {
|
|
38
|
+
require('./shakers-cli').main();
|
|
361
39
|
}
|
|
362
|
-
|
|
363
|
-
main();
|
package/bin/share.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
// `share` — turns the project's AI-usage FOOTPRINT result into a branded card to post on LinkedIn
|
|
4
|
+
// `share` — turns the project's AI-usage FOOTPRINT result into a branded card to post on LinkedIn.
|
|
5
5
|
|
|
6
6
|
const { detectReportLang, getCatalog } = require('./../src/i18n');
|
|
7
7
|
const { generateShareCard } = require('./../src/share-card');
|
package/bin/superadmin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
// `superadmin` — NON-PROD, password-authenticated superadmin SESSION
|
|
4
|
+
// `superadmin` — NON-PROD, password-authenticated superadmin SESSION.
|
|
5
5
|
|
|
6
6
|
const http = require('http');
|
|
7
7
|
const https = require('https');
|
|
@@ -94,7 +94,7 @@ async function promptLine(ask, canPrompt, label) {
|
|
|
94
94
|
return String(await ask(label)).trim();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
// The SECRET prompt
|
|
97
|
+
// The SECRET prompt.
|
|
98
98
|
async function promptSecret(ask, canPrompt, label) {
|
|
99
99
|
if (!canPrompt) return '';
|
|
100
100
|
const read = typeof ask.secret === 'function' ? ask.secret.bind(ask) : ask;
|
|
@@ -214,7 +214,7 @@ async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, inpu
|
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
216
|
process.stdout.write(`\n ${c.sessionIntro}\n`);
|
|
217
|
-
//
|
|
217
|
+
// Superadmin is DELIBERATELY single-hop, PRIMARY (certifications service) ONLY — never hub.
|
|
218
218
|
process.stdout.write(` ${c.queryingPrimary(endpoint)}\n`);
|
|
219
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
220
|
const password = await promptSecret(ask, true, c.passwordPrompt);
|
|
@@ -230,7 +230,7 @@ async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, inpu
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
//
|
|
233
|
+
// Read-only attribution receipt for stored certifications.
|
|
234
234
|
async function runInspect({ opts, c, endpoint, password, ask, canPrompt }) {
|
|
235
235
|
const email = await resolveEmailArg(opts, c, ask, canPrompt, c.inspectEmailPrompt);
|
|
236
236
|
if (!password || !email || !isValidEmail(email)) {
|
|
@@ -308,7 +308,7 @@ async function resolveEmailArg(opts, c, ask, canPrompt, promptLabel) {
|
|
|
308
308
|
return null;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
//
|
|
311
|
+
// Open a superadmin SESSION — validate the password server-side, get a token, persist it locally.
|
|
312
312
|
async function runOpenSession({ opts, c, endpoint, password, ask, canPrompt }) {
|
|
313
313
|
const email = await resolveEmailArg(opts, c, ask, canPrompt, c.emailPrompt);
|
|
314
314
|
if (!password || !email || !isValidEmail(email)) {
|
package/bin/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shakers-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
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
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node bin/shakers.js",
|
|
17
17
|
"shakers": "node bin/shakers.js",
|
|
18
|
-
"test": "node --test test/*.test.js",
|
|
18
|
+
"test": "node --require ./test-fixtures/test-env.js --test test/*.test.js",
|
|
19
19
|
"build:mcpb": "sh scripts/build-mcpb.sh",
|
|
20
|
-
"publish:beta": "node scripts/prepare-beta.js && npm publish --tag latest && git checkout -- package.json"
|
|
20
|
+
"publish:beta": "node scripts/prepare-beta.js && npm publish --tag latest && git checkout -- package.json README.md",
|
|
21
|
+
"lint": "oxlint --deny-warnings bin src scripts test test-fixtures",
|
|
22
|
+
"check:boundaries": "node scripts/check-boundaries.js"
|
|
21
23
|
},
|
|
22
24
|
"engines": {
|
|
23
25
|
"node": ">=18"
|
|
@@ -35,8 +37,15 @@
|
|
|
35
37
|
"certification"
|
|
36
38
|
],
|
|
37
39
|
"license": "SEE LICENSE IN LICENSE",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"oxlint": "1.86.0"
|
|
42
|
+
},
|
|
38
43
|
"publishConfig": {
|
|
39
44
|
"access": "public",
|
|
40
45
|
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
},
|
|
47
|
+
"amplitude": {
|
|
48
|
+
"env": "prod",
|
|
49
|
+
"apiKey": "03bbeda1f0c96d51774faee68ce12a3"
|
|
41
50
|
}
|
|
42
51
|
}
|
package/src/agent-category.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// THE AGENT-CATEGORY VOCABULARY, AND WHICH OF IT IS A SPECIALITY
|
|
3
|
+
// THE AGENT-CATEGORY VOCABULARY, AND WHICH OF IT IS A SPECIALITY.
|
|
4
4
|
|
|
5
5
|
/** The floor category key, as the service's catalog defines it (`other-1`'s category). */
|
|
6
6
|
const FLOOR_CATEGORY = 'other';
|
package/src/agent-evaluation.js
CHANGED
|
@@ -4,7 +4,7 @@ const { scrubSecrets } = require('./agent-synthesis');
|
|
|
4
4
|
const { requestBackend } = require('./backend-request');
|
|
5
5
|
const { reporterFor, REASON } = require('./model-call-record');
|
|
6
6
|
|
|
7
|
-
// Agent definition-quality evaluation client
|
|
7
|
+
// Agent definition-quality evaluation client.
|
|
8
8
|
|
|
9
9
|
// HOW LONG TO WAIT FOR THE AGENT EVALUATION, and why it keeps growing.
|
|
10
10
|
const DEFAULT_TIMEOUT_MS = 150000;
|
|
@@ -29,7 +29,7 @@ function buildAgentEvaluationRequest(structuralAgents, definitionsByName, locale
|
|
|
29
29
|
parent: a.parent || null,
|
|
30
30
|
})),
|
|
31
31
|
promptVersion: PROMPT_VERSION,
|
|
32
|
-
//
|
|
32
|
+
// Detected report language for the rationale + description prose.
|
|
33
33
|
...(locale === 'es' || locale === 'en' ? { locale } : {}),
|
|
34
34
|
...(consentGranted === true ? { traceContentConsent: true } : {}),
|
|
35
35
|
};
|
|
@@ -103,7 +103,7 @@ function normalizeEvaluations(list) {
|
|
|
103
103
|
.map((e) => ({
|
|
104
104
|
name: e.name,
|
|
105
105
|
rationale: typeof e.rationale === 'string' ? e.rationale : '',
|
|
106
|
-
//
|
|
106
|
+
// Target-language one-line description; null when the server
|
|
107
107
|
// omitted it — the caller falls back to the verbatim phrase.
|
|
108
108
|
description: typeof e.description === 'string' && e.description ? e.description : null,
|
|
109
109
|
// v4 (agent classification): closest catalog agent + how it was matched.
|
package/src/agent-org-chart.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getHomeDir } = require('./env-paths');
|
|
6
6
|
|
|
7
|
-
// Deterministic (no-LLM) parser of the talent's AI agent org chart
|
|
7
|
+
// Deterministic (no-LLM) parser of the talent's AI agent org chart.
|
|
8
8
|
|
|
9
9
|
// Safety caps mirror scanner.js's FOOTPRINT_MAX_* (avoid pathological scans,
|
|
10
10
|
// never a product requirement).
|
|
@@ -145,12 +145,12 @@ function buildAgentFromFrontmatter(fm, filePath) {
|
|
|
145
145
|
// the card in place of the LLM `model`.
|
|
146
146
|
aiProduct: deriveAiProduct(filePath),
|
|
147
147
|
model: typeof fm.model === 'string' && fm.model ? fm.model : null,
|
|
148
|
-
// Hierarchy
|
|
148
|
+
// Hierarchy: first honour an explicit `parent` frontmatter key when a project declares one.
|
|
149
149
|
parent: typeof fm.parent === 'string' && fm.parent ? fm.parent : null,
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// `parseAgentFile` USED TO BE HERE (removed
|
|
153
|
+
// `parseAgentFile` USED TO BE HERE (removed): read one `.md` and build an agent from its frontmatter.
|
|
154
154
|
|
|
155
155
|
// Markdown body (everything after the closing ` ` of the frontmatter).
|
|
156
156
|
function bodyAfterFrontmatter(content) {
|
|
@@ -239,7 +239,7 @@ function parseAgentOrgChartFromDirs(dirs) {
|
|
|
239
239
|
return agents;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
// Deterministic (no-LLM) agent org chart, scoped to `.claude/agents/` in BOTH the project root and the home directory (
|
|
242
|
+
// Deterministic (no-LLM) agent org chart, scoped to `.claude/agents/` in BOTH the project root and the home directory (project ∪ home).
|
|
243
243
|
function parseAgentOrgChart(root) {
|
|
244
244
|
return parseAgentOrgChartFromDirs(agentDirs(root));
|
|
245
245
|
}
|
|
@@ -249,7 +249,7 @@ function parseAgentOrgChartForRoots(roots) {
|
|
|
249
249
|
return parseAgentOrgChartFromDirs(agentDirsForRoots(Array.isArray(roots) ? roots : [roots]));
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
//
|
|
252
|
+
// Returns `[{ name, description }]` — the ONLY function in this module that ever returns description/prompt content.
|
|
253
253
|
function parseAgentDescriptionsFromDirs(dirs) {
|
|
254
254
|
const seen = new Set();
|
|
255
255
|
const result = [];
|
package/src/agent-synthesis.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { requestBackend } = require('./backend-request');
|
|
4
4
|
const { reporterFor, REASON } = require('./model-call-record');
|
|
5
5
|
|
|
6
|
-
// Agent synthesis client
|
|
6
|
+
// Agent synthesis client.
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 8000;
|
|
9
9
|
|
|
@@ -28,7 +28,7 @@ const PEM_BLOCK_RE = /-----BEGIN[ A-Z]*PRIVATE KEY-----[\s\S]*?-----END[ A-Z]*PR
|
|
|
28
28
|
const CONN_STRING_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]{1,15}:\/\/)[^\s'"/:@]+:[^\s'"@]+@/g; // scheme://user:pass@ -> scheme://[REDACTED]@
|
|
29
29
|
const GENERIC_KV_RE = /\b(?:password|secret|token|api[_-]?key|bearer)\s*[:=]\s*\S+/gi;
|
|
30
30
|
|
|
31
|
-
// Heuristic, best-effort redaction — an "invisible safety net"
|
|
31
|
+
// Heuristic, best-effort redaction — an "invisible safety net", not a substitute for the talent's own judgment about what they write in agent descriptions.
|
|
32
32
|
function scrubSecrets(text) {
|
|
33
33
|
if (typeof text !== 'string' || !text) return '';
|
|
34
34
|
let out = text;
|
|
@@ -77,7 +77,7 @@ function isValidSynthesisResponse(parsed) {
|
|
|
77
77
|
|
|
78
78
|
// Requests the agent-synthesis endpoint.
|
|
79
79
|
async function requestAgentSynthesis(requestBody, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS, onOutcome = null } = {}) {
|
|
80
|
-
//
|
|
80
|
+
// The four `return null`s below are four DIFFERENT situations that used to be one.
|
|
81
81
|
const outcome = reporterFor(onOutcome, { timeoutMs });
|
|
82
82
|
if (!endpoint) {
|
|
83
83
|
outcome.skip(REASON.NO_ENDPOINT);
|
package/src/agent-usage.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getHomeDir } = require('./env-paths');
|
|
6
6
|
|
|
7
|
-
// Local Claude Code agent-usage signal
|
|
7
|
+
// Local Claude Code agent-usage signal.
|
|
8
8
|
|
|
9
9
|
const MAX_FILES = 500; // cap total session files scanned (bounded work)
|
|
10
10
|
const MAX_BYTES_PER_FILE = 8 * 1024 * 1024; // read at most an 8MB head per file
|
package/src/agents-client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { postJsonWithTimeout } = require('./backend-request');
|
|
4
4
|
const { reasonForError, WORKS_CODE_TO_REASON } = require('./skills-client');
|
|
5
5
|
|
|
6
|
-
// Talent-agent client for `start`'s "Add agent" route
|
|
6
|
+
// Talent-agent client for `start`'s "Add agent" route.
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 20000;
|
|
9
9
|
|
|
@@ -88,14 +88,14 @@ async function requestDeclareAgent(
|
|
|
88
88
|
|
|
89
89
|
const json = parseJson(res.raw);
|
|
90
90
|
if (!json || json.status !== 'OK') return { ok: false, reason: 'bad-response' };
|
|
91
|
-
// Thread the created agent's id back (relayed from Hub's declare response) so the relate step can PATCH `.../agents/:id/portfolios
|
|
91
|
+
// Thread the created agent's id back (relayed from Hub's declare response) so the relate step can PATCH `.../agents/:id/portfolios`.
|
|
92
92
|
const data = json.data && typeof json.data === 'object' ? json.data : null;
|
|
93
93
|
const rawId = data ? data.id : null;
|
|
94
94
|
const agentId = typeof rawId === 'string' || typeof rawId === 'number' ? String(rawId) : null;
|
|
95
95
|
return { ok: true, agentId };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// DIRECT to Hub
|
|
98
|
+
// DIRECT to Hub, replace-set.
|
|
99
99
|
async function requestRelateAgentPortfolios(
|
|
100
100
|
{ agentId, items, hubAccessToken } = {},
|
|
101
101
|
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
@@ -124,7 +124,7 @@ async function requestRelateAgentPortfolios(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// `POST .../agents/draft-fields` -> { ok, whatItDoes, humanDecides } | { ok:false, reason }.
|
|
127
|
-
// No X-Hub-Token. The CALLER gates consent
|
|
127
|
+
// No X-Hub-Token. The CALLER gates consent, so `consent:true` is always sent.
|
|
128
128
|
async function requestDraftAgentFields(
|
|
129
129
|
{ name, tools, model, parent, definition, accessToken } = {},
|
|
130
130
|
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
package/src/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// The single named surface for every model-backed (LLM) call this CLI makes
|
|
3
|
+
// The single named surface for every model-backed (LLM) call this CLI makes.
|
|
4
4
|
|
|
5
5
|
const agentSynthesis = require('./agent-synthesis');
|
|
6
6
|
const agentEvaluation = require('./agent-evaluation');
|
package/src/ansi.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { isInteractive } = require('./terminal-progress');
|
|
4
4
|
const { BRAND_ANSI } = require('./brand-ansi');
|
|
5
5
|
|
|
6
|
-
// THE ONE GATE for terminal colour
|
|
6
|
+
// THE ONE GATE for terminal colour.
|
|
7
7
|
|
|
8
8
|
// `NO_COLOR` per the de-facto standard: present and non-empty disables colour, whatever the value.
|
|
9
9
|
function noColorRequested(env = process.env) {
|