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.
Files changed (123) hide show
  1. package/README.md +51 -1203
  2. package/bin/ai-usage.js +11 -56
  3. package/bin/login.js +10 -10
  4. package/bin/mcp.js +13 -9
  5. package/bin/mcpb.js +1 -1
  6. package/bin/onboarding.js +3 -2
  7. package/bin/register.js +17 -4
  8. package/bin/report-html.js +2 -2
  9. package/bin/shakers-cli.js +352 -0
  10. package/bin/shakers.js +20 -344
  11. package/bin/share.js +1 -1
  12. package/bin/superadmin.js +5 -5
  13. package/bin/version.js +3 -0
  14. package/package.json +12 -3
  15. package/src/agent-category.js +1 -1
  16. package/src/agent-evaluation.js +3 -3
  17. package/src/agent-org-chart.js +5 -5
  18. package/src/agent-synthesis.js +3 -3
  19. package/src/agent-usage.js +1 -1
  20. package/src/agents-client.js +4 -4
  21. package/src/ai.js +1 -1
  22. package/src/ansi.js +1 -1
  23. package/src/auth-client.js +18 -4
  24. package/src/auth-method-picker.js +1 -1
  25. package/src/auth-methods.js +1 -1
  26. package/src/auth-session-store.js +11 -8
  27. package/src/authorship-mismatch.js +1 -1
  28. package/src/authorship.js +2 -2
  29. package/src/automations-detector.js +1 -1
  30. package/src/backend-request.js +3 -3
  31. package/src/brand-ansi.js +1 -1
  32. package/src/browser-tools-detector.js +1 -1
  33. package/src/certify-disclaimer.js +1 -1
  34. package/src/certify-remediation-prompt.js +1 -1
  35. package/src/cli-args.js +2 -2
  36. package/src/command-audit.js +8 -5
  37. package/src/config-dir.js +1 -1
  38. package/src/config.js +112 -48
  39. package/src/consent-flow.js +4 -4
  40. package/src/consent-skip.js +1 -1
  41. package/src/detection-evidence.js +2 -2
  42. package/src/detection-noise.js +1 -1
  43. package/src/detectors.js +1 -1
  44. package/src/device-login.js +12 -1
  45. package/src/email-verification.js +3 -3
  46. package/src/env-paths.js +1 -1
  47. package/src/find-projects-client.js +12 -0
  48. package/src/find-projects-flow.js +1 -13
  49. package/src/git-activity.js +1 -1
  50. package/src/graph-certs.js +3 -3
  51. package/src/graph-scan.js +9 -9
  52. package/src/i18n.js +124 -124
  53. package/src/interactive-select.js +1 -1
  54. package/src/interview-client.js +6 -1
  55. package/src/interview-transcript.js +1 -1
  56. package/src/legal-notice.js +1 -1
  57. package/src/maturity.js +6 -6
  58. package/src/mcp-ai-usage-tool.js +1 -1
  59. package/src/mcp-auth-form.js +1 -1
  60. package/src/mcp-detector.js +3 -3
  61. package/src/mcp-device-login-tools.js +2 -1
  62. package/src/mcp-find-projects-tools.js +1 -2
  63. package/src/mcp-onboarding-tools.js +4 -4
  64. package/src/mcp-prompts.js +2 -2
  65. package/src/mcp-register-tools.js +1 -0
  66. package/src/mcp-server.js +5 -1
  67. package/src/mcp-signup-client.js +2 -2
  68. package/src/mcp-signup-tools.js +26 -2
  69. package/src/memory-structure-detector.js +1 -1
  70. package/src/model-call-record.js +2 -2
  71. package/src/onboarding-flow.js +16 -16
  72. package/src/onboarding-funnel.js +110 -17
  73. package/src/open-file.js +1 -1
  74. package/src/osc-link.js +1 -1
  75. package/src/portfolio-client.js +11 -1
  76. package/src/portfolio-git-info.js +1 -1
  77. package/src/profile-flow.js +1 -1
  78. package/src/profile-switch.js +5 -12
  79. package/src/render-certification.js +10 -10
  80. package/src/render-html.js +6 -6
  81. package/src/render-sheet.js +10 -10
  82. package/src/render-terminal.js +6 -6
  83. package/src/repo-scope.js +132 -0
  84. package/src/report-gating.js +1 -1
  85. package/src/report-store.js +6 -6
  86. package/src/roadmap-content.js +5 -5
  87. package/src/roadmap-personalization.js +3 -3
  88. package/src/roadmap-prompt.js +2 -2
  89. package/src/scan-exclusions.js +1 -1
  90. package/src/scanner.js +14 -14
  91. package/src/secret-prompt.js +1 -1
  92. package/src/session-gate.js +1 -1
  93. package/src/session-refresh.js +33 -3
  94. package/src/session-scan.js +1 -1
  95. package/src/shakers-wordmark.js +1 -1
  96. package/src/share-card.js +2 -2
  97. package/src/share-certification.js +4 -4
  98. package/src/share-consent-state.js +7 -7
  99. package/src/share-payload.js +7 -7
  100. package/src/share.js +7 -7
  101. package/src/signup-client.js +1 -1
  102. package/src/signup-copy.js +1 -1
  103. package/src/skills-client.js +1 -1
  104. package/src/start-add-agents.js +2 -2
  105. package/src/start-add-portfolio.js +3 -13
  106. package/src/start-add-skills.js +2 -2
  107. package/src/stdin-ask.js +3 -3
  108. package/src/strip-internal-comments.js +1 -1
  109. package/src/tech-detector.js +2 -2
  110. package/src/templates/report-sheet.html +11 -13
  111. package/src/terminal-agents.js +8 -8
  112. package/src/terminal-format.js +3 -3
  113. package/src/terminal-sections.js +11 -11
  114. package/src/tier-engine.js +3 -3
  115. package/src/tier-projection.js +2 -2
  116. package/src/transcript-turns.js +1 -1
  117. package/src/typewriter.js +1 -1
  118. package/src/usage-model-calls.js +6 -6
  119. package/src/usage-oneshot-commands.js +5 -5
  120. package/src/usage-repo-scope.js +11 -129
  121. package/src/usage-run-steps.js +32 -184
  122. package/src/usage-scan.js +235 -0
  123. package/src/work-streams.js +1 -1
@@ -0,0 +1,352 @@
1
+ 'use strict';
2
+
3
+ const { detectReportLang, getCatalog } = require('../src/i18n');
4
+ const { promptSelect } = require('../src/prompt-select');
5
+ const { createStdinAsk } = require('../src/stdin-ask');
6
+ const { migrateLegacyConfigDir } = require('../src/config-dir');
7
+ const { sanitizeStaleBakedEndpoints } = require('../src/config');
8
+ const { ensureFreshSession } = require('../src/session-refresh');
9
+ const { hasUsableSession } = require('../src/session-gate');
10
+ const { run: runAiUsage } = require('./ai-usage');
11
+ const { run: runCertify } = require('./certify');
12
+ const { run: runShare } = require('./share');
13
+ const { run: runReport } = require('./report-html');
14
+ const { run: runSuperadmin } = require('./superadmin');
15
+ const { run: runLogin, runLogout } = require('./login');
16
+ const { run: runAddSkill } = require('./add-skill');
17
+ const { run: runAddAgent } = require('./add-agent');
18
+ const { run: runAddProject } = require('./add-project');
19
+ const { run: runAddRole } = require('./add-role');
20
+ const { run: runChangeRole } = require('./change-role');
21
+ const { run: runFindProjects } = require('./find-projects');
22
+ const { run: runShowProject } = require('./show-project');
23
+ const { run: runSave, runUnsave } = require('./save');
24
+ const { run: runInvitations } = require('./invitations');
25
+ const { run: runApplications } = require('./applications');
26
+ const { run: runAvailability } = require('./availability');
27
+ const { run: runCertifications } = require('./certifications');
28
+ const { run: runMe } = require('./me');
29
+ const { run: runRate } = require('./rate');
30
+ const { run: runLang } = require('./lang');
31
+ const { run: runSocials } = require('./socials');
32
+ const { run: runExperiences } = require('./experiences');
33
+ const { run: runPortfolios } = require('./portfolios');
34
+ const { run: runAlma } = require('./alma');
35
+ const { run: runRegister } = require('./register');
36
+ const { run: runOnboarding } = require('./onboarding');
37
+ const { run: runConfig } = require('./config');
38
+ const { run: runUpdate } = require('./update');
39
+ const { checkOnboardingCompleted } = require('../src/onboarding-availability');
40
+ const { emitCommandAudit } = require('../src/command-audit');
41
+ const { readPackageInfo } = require('../src/update-flow');
42
+ const { isSuppressed, maybeNotify, triggerBackgroundCheck } = require('../src/update-notifier');
43
+
44
+ let VERSION = '';
45
+ try {
46
+ VERSION = require('../package.json').version || '';
47
+ } catch {
48
+ VERSION = '';
49
+ }
50
+
51
+ function parseLang(argv) {
52
+ for (let i = 0; i < argv.length; i++) {
53
+ const a = argv[i];
54
+ if (a === '--lang' && (argv[i + 1] === 'es' || argv[i + 1] === 'en')) return argv[i + 1];
55
+ if (a === '--lang=es') return 'es';
56
+ if (a === '--lang=en') return 'en';
57
+ }
58
+ return null;
59
+ }
60
+
61
+ const HANDLERS = {
62
+ 'ai-usage': runAiUsage,
63
+ certify: runCertify,
64
+ share: runShare,
65
+ report: runReport,
66
+ sheet: runReport,
67
+ 'add-skill': runAddSkill,
68
+ 'add-agent': runAddAgent,
69
+ 'add-project': runAddProject,
70
+ 'add-role': runAddRole,
71
+ 'change-role': runChangeRole,
72
+ 'find-projects': runFindProjects,
73
+ 'show-project': runShowProject,
74
+ 'save-project': runSave,
75
+ 'unsave-project': runUnsave,
76
+ invitations: runInvitations,
77
+ applications: runApplications,
78
+ availability: runAvailability,
79
+ certifications: runCertifications,
80
+ me: runMe,
81
+ profile: runMe,
82
+ rate: runRate,
83
+ lang: runLang,
84
+ socials: runSocials,
85
+ experiences: runExperiences,
86
+ portfolios: runPortfolios,
87
+ alma: runAlma,
88
+ register: runRegister,
89
+ onboarding: runOnboarding,
90
+ config: runConfig,
91
+ update: runUpdate,
92
+ login: runLogin,
93
+ logout: runLogout,
94
+ superadmin: runSuperadmin,
95
+ mcp: () => require('../src/mcp-install').runMcpInstall(),
96
+ };
97
+
98
+ const SESSION_EXEMPT = new Set(['ai-usage', 'register', 'config', 'login', 'logout', 'superadmin', 'mcp', 'share', 'alma', 'update']);
99
+
100
+ // Command FAMILIES — a navigation layer over the flat commands (which all stay
101
+ // valid as aliases). `shakers <family> <sub>` resolves to the flat handler; every
102
+ // sub is just a friendlier name for an existing command. Additive, never breaking.
103
+ const FAMILIES = {
104
+ add: { skill: 'add-skill', agent: 'add-agent', project: 'add-project', role: 'add-role' },
105
+ role: { add: 'add-role', change: 'change-role', main: 'change-role' },
106
+ profile: {
107
+ show: 'me', rate: 'rate', languages: 'lang', lang: 'lang', socials: 'socials',
108
+ experiences: 'experiences', portfolios: 'portfolios', availability: 'availability',
109
+ certifications: 'certifications',
110
+ },
111
+ projects: {
112
+ find: 'find-projects', show: 'show-project', save: 'save-project', unsave: 'unsave-project',
113
+ invitations: 'invitations', applications: 'applications',
114
+ },
115
+ usage: { run: 'ai-usage', report: 'report', share: 'share' },
116
+ account: { login: 'login', logout: 'logout', register: 'register', config: 'config', update: 'update' },
117
+ certs: { list: 'certifications', certify: 'certify' },
118
+ };
119
+
120
+ // `shakers <family>` with no sub: obvious default, else the family help.
121
+ const FAMILY_DEFAULTS = { usage: 'ai-usage', profile: 'me', certs: 'certifications' };
122
+
123
+ function familyHelpText(family, lang) {
124
+ const map = FAMILIES[family] || {};
125
+ const subs = Object.keys(map);
126
+ // Show `shakers <family> <sub>` next to the flat alias it maps to, so both forms are discoverable.
127
+ const lines = subs.map((s) => ` shakers ${family} ${s} (alias: shakers ${map[s]})`).join('\n');
128
+ if (lang === 'es') {
129
+ 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`;
130
+ }
131
+ 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`;
132
+ }
133
+
134
+ // Resolves a family + sub into a flat command, mutating nothing the flat path relies on.
135
+ function resolveFamily(command, args) {
136
+ const family = FAMILIES[command];
137
+ if (!family) return { command, args };
138
+ const subToken = args.find((a) => !a.startsWith('-'));
139
+ const sub = subToken ? subToken.toLowerCase() : null;
140
+ if (sub && family[sub]) {
141
+ return { command: family[sub], args: args.filter((_, i) => i !== args.indexOf(subToken)) };
142
+ }
143
+ if (!sub && FAMILY_DEFAULTS[command]) return { command: FAMILY_DEFAULTS[command], args };
144
+ // A pure family (not also a flat handler) with no valid sub → signal "show family help".
145
+ if (!HANDLERS[command]) return { command, args, familyHelp: true };
146
+ return { command, args };
147
+ }
148
+
149
+ async function remindAccountForAiUsage(lang, args) {
150
+ if (!process.stdin.isTTY || args.includes('--json')) return;
151
+ const r = getCatalog(lang).cli.accountReminder;
152
+ const ask = createStdinAsk();
153
+ let choice;
154
+ try {
155
+ choice = await promptSelect({
156
+ ask,
157
+ stdinIsTTY: true,
158
+ out: (line) => process.stdout.write(` ${line}\n`),
159
+ header: r.question,
160
+ hint: r.hint,
161
+ items: [{ id: 'yes', label: r.yes }, { id: 'no', label: r.no }],
162
+ labelFor: (it) => it.label,
163
+ });
164
+ } finally {
165
+ ask.close();
166
+ }
167
+ if (choice && choice.id === 'yes') {
168
+ await runLogin(['--lang', lang]);
169
+ }
170
+ }
171
+
172
+ function usageText(lang, { hideOnboarding = false } = {}) {
173
+ const title = VERSION ? `shakers v${VERSION}` : 'shakers';
174
+ if (lang === 'es') {
175
+ const onboardingLine = hideOnboarding ? '' : ` onboarding La entrevista de onboarding (por voz/texto, LiveKit)\n`;
176
+ return `\n ${title} — evaluación de uso de IA y skills\n\n`
177
+ + ` Uso: shakers <comando> [opciones]\n\n`
178
+ + ` Público (sin sesión):\n`
179
+ + ` ai-usage Escanea este proyecto + tu máquina y puntúa tu setup de IA\n`
180
+ + ` register Registro completo: perfil, precio, uso de IA y entrevista\n`
181
+ + ` mcp Servidor MCP local (stdio) para apps de IA de escritorio\n\n`
182
+ + ` Requieren sesión (ejecuta antes: shakers login):\n`
183
+ + onboardingLine
184
+ + ` certify Certifica una dimensión de tu rol (entrevista por LiveKit)\n`
185
+ + ` add-skill Declara Skills detectadas en tu perfil\n`
186
+ + ` add-agent Declara un agente detectado en tu perfil\n`
187
+ + ` add-project Añade este proyecto a tu portfolio\n`
188
+ + ` add-role Añade un rol a tu perfil (rol de crecimiento)\n`
189
+ + ` change-role Cambia tu rol principal por uno de los que ya tienes\n`
190
+ + ` find-projects Lista los proyectos/posiciones disponibles para ti\n`
191
+ + ` show-project Detalle de un proyecto/posición por id\n`
192
+ + ` save-project/unsave-project Guarda o quita un proyecto de tus guardados\n`
193
+ + ` invitations Tus invitaciones a proyectos\n`
194
+ + ` applications Proyectos a los que has aplicado + estado\n`
195
+ + ` availability Ver o fijar tu disponibilidad\n`
196
+ + ` certifications Tus dimensiones certificadas/sin certificar\n`
197
+ + ` me Tu resumen de perfil\n`
198
+ + ` rate Ver o fijar tu tarifa por proyecto\n`
199
+ + ` lang Ver o fijar tus idiomas y nivel\n`
200
+ + ` socials Ver o fijar tus enlaces sociales\n`
201
+ + ` experiences Tus experiencias laborales\n`
202
+ + ` portfolios Tus piezas de portfolio\n`
203
+ + ` alma Mini-shell interactivo para conversar con Alma\n`
204
+ + ` report Abre el informe HTML completo y compartible\n`
205
+ + ` share Tarjeta de uso con marca para LinkedIn\n\n`
206
+ + ` Sesión:\n`
207
+ + ` login Inicia sesión en tu cuenta de Shakers\n`
208
+ + ` logout Cierra la sesión\n`
209
+ + ` config Lee/escribe la configuración (bases de hub/certs)\n`
210
+ + ` update Actualiza el CLI a la última versión publicada (--check solo informa)\n\n`
211
+ + ` Familias (agrupan los comandos de arriba; los nombres planos siguen valiendo como alias):\n`
212
+ + ` add <skill|agent|project|role> · role <add|change>\n`
213
+ + ` profile <show|rate|languages|socials|experiences|portfolios|availability|certifications>\n`
214
+ + ` projects <find|show|save|unsave|invitations|applications>\n`
215
+ + ` usage <run|report|share> · certs <list|certify> · account <login|logout|register|config|update>\n`
216
+ + ` Ejecuta \`shakers <familia>\` (sin subcomando) para ver sus subcomandos.\n\n`
217
+ + ` Ejecuta shakers <comando> --help para las opciones de cada comando.\n\n`;
218
+ }
219
+ const onboardingLine = hideOnboarding ? '' : ` onboarding The onboarding interview (over voice/text, LiveKit)\n`;
220
+ return `\n ${title} — AI usage & skills evaluator\n\n`
221
+ + ` Usage: shakers <command> [options]\n\n`
222
+ + ` Public (no session):\n`
223
+ + ` ai-usage Scan this project + machine and score your AI setup\n`
224
+ + ` register Full registration: profile, pricing, AI usage and interview\n`
225
+ + ` mcp Local MCP server (stdio) for desktop AI apps\n\n`
226
+ + ` Require a session (run \`shakers login\` first):\n`
227
+ + onboardingLine
228
+ + ` certify Certify a dimension of your role (LiveKit interview)\n`
229
+ + ` add-skill Declare detected Skills on your profile\n`
230
+ + ` add-agent Declare a detected agent on your profile\n`
231
+ + ` add-project Add this project to your portfolio\n`
232
+ + ` add-role Add a role to your profile (a growth role)\n`
233
+ + ` change-role Switch your main role to one you already have\n`
234
+ + ` find-projects List the Projects/Positions available to you\n`
235
+ + ` show-project Detail of a project/position by id\n`
236
+ + ` save-project/unsave-project Save or remove a project from your saved list\n`
237
+ + ` invitations Your project invitations\n`
238
+ + ` applications Projects you applied to + status\n`
239
+ + ` availability View or set your availability\n`
240
+ + ` certifications Your certified/uncertified dimensions\n`
241
+ + ` me Your profile summary\n`
242
+ + ` rate View or set your per-project rate\n`
243
+ + ` lang View or set your languages and level\n`
244
+ + ` socials View or set your social links\n`
245
+ + ` experiences Your work experiences\n`
246
+ + ` portfolios Your portfolio pieces\n`
247
+ + ` alma Interactive mini-shell to converse with Alma\n`
248
+ + ` report Open the full shareable HTML report\n`
249
+ + ` share Branded usage card for LinkedIn\n\n`
250
+ + ` Session:\n`
251
+ + ` login Sign in to your Shakers account\n`
252
+ + ` logout Sign out\n`
253
+ + ` config Read/write configuration (hub/certs bases)\n`
254
+ + ` update Update the CLI to the latest published version (--check only reports)\n\n`
255
+ + ` Families (group the commands above; the flat names still work as aliases):\n`
256
+ + ` add <skill|agent|project|role> · role <add|change>\n`
257
+ + ` profile <show|rate|languages|socials|experiences|portfolios|availability|certifications>\n`
258
+ + ` projects <find|show|save|unsave|invitations|applications>\n`
259
+ + ` usage <run|report|share> · certs <list|certify> · account <login|logout|register|config|update>\n`
260
+ + ` Run \`shakers <family>\` (no subcommand) to list its subcommands.\n\n`
261
+ + ` Run \`shakers <command> --help\` for a command's options.\n\n`;
262
+ }
263
+
264
+ function gateText(command, lang) {
265
+ if (lang === 'es') {
266
+ return `\n "${command}" requiere una sesión activa.\n`
267
+ + ` Inicia sesión: shakers login\n`
268
+ + ` ¿Sin cuenta?: shakers register\n\n`;
269
+ }
270
+ return `\n "${command}" requires an active session.\n`
271
+ + ` Sign in: shakers login\n`
272
+ + ` No account?: shakers register\n\n`;
273
+ }
274
+
275
+ function unknownText(command, lang) {
276
+ if (lang === 'es') {
277
+ return `\n Comando desconocido: "${command}".\n`
278
+ + ` Ejecuta shakers para ver los comandos disponibles.\n\n`;
279
+ }
280
+ return `\n Unknown command: "${command}".\n`
281
+ + ` Run \`shakers\` to see the available commands.\n\n`;
282
+ }
283
+
284
+ async function main() {
285
+ const argv = process.argv.slice(2);
286
+ const lang = parseLang(argv) || detectReportLang();
287
+
288
+ migrateLegacyConfigDir(process.env);
289
+ sanitizeStaleBakedEndpoints(process.env);
290
+ // 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).
291
+ try { await ensureFreshSession(process.env); } catch { /* never blocks a command */ }
292
+
293
+ const token = argv.find((a) => !a.startsWith('-'));
294
+
295
+ // Single dispatch choke-point: audit EXACTLY once before branching, fire-and-forget (never awaited). MCP tool calls run in-process and never reach here.
296
+ const auditCommand =
297
+ !token || token.toLowerCase() === 'help' ? 'help' : token.toLowerCase();
298
+ emitCommandAudit({ command: auditCommand, argv, env: process.env });
299
+
300
+ // 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.
301
+ if (!isSuppressed({ argv, command: auditCommand, env: process.env })) {
302
+ maybeNotify({ currentVersion: VERSION, lang, env: process.env });
303
+ triggerBackgroundCheck({ name: readPackageInfo().name, env: process.env });
304
+ }
305
+
306
+ if (!token || token.toLowerCase() === 'help') {
307
+ let hideOnboarding = false;
308
+ try {
309
+ hideOnboarding = await checkOnboardingCompleted({ timeoutMs: 3000 });
310
+ } catch {
311
+ hideOnboarding = false;
312
+ }
313
+ process.stdout.write(usageText(lang, { hideOnboarding }));
314
+ return;
315
+ }
316
+
317
+ const rawCommand = token.toLowerCase();
318
+ const rawArgs = argv.filter((_, i) => i !== argv.indexOf(token));
319
+
320
+ // Family navigation layer (flat commands still work as aliases).
321
+ const resolved = resolveFamily(rawCommand, rawArgs);
322
+ if (resolved.familyHelp) {
323
+ process.stdout.write(familyHelpText(rawCommand, lang));
324
+ return;
325
+ }
326
+ const command = resolved.command;
327
+ const args = resolved.args;
328
+
329
+ const handler = HANDLERS[command];
330
+ if (!handler) {
331
+ process.stderr.write(unknownText(command, lang));
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+
336
+ const wantsHelp = args.includes('--help') || args.includes('-h');
337
+ const loggedIn = await hasUsableSession(process.env);
338
+
339
+ if (!wantsHelp && !SESSION_EXEMPT.has(command) && !loggedIn) {
340
+ process.stderr.write(gateText(command, lang));
341
+ process.exitCode = 1;
342
+ return;
343
+ }
344
+
345
+ if (command === 'ai-usage' && !wantsHelp && !loggedIn) {
346
+ await remindAccountForAiUsage(lang, args);
347
+ }
348
+
349
+ await handler(args);
350
+ }
351
+
352
+ module.exports = { main };