shakers-cli-beta 0.8.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1175 -0
  3. package/bin/add-agent.js +88 -0
  4. package/bin/add-project.js +88 -0
  5. package/bin/add-role.js +61 -0
  6. package/bin/add-skill.js +88 -0
  7. package/bin/ai-usage.js +81 -0
  8. package/bin/alma.js +99 -0
  9. package/bin/applications.js +69 -0
  10. package/bin/availability.js +70 -0
  11. package/bin/certifications.js +69 -0
  12. package/bin/certify.js +73 -0
  13. package/bin/change-role.js +61 -0
  14. package/bin/config.js +120 -0
  15. package/bin/experiences.js +69 -0
  16. package/bin/find-projects.js +89 -0
  17. package/bin/invitations.js +69 -0
  18. package/bin/lang.js +70 -0
  19. package/bin/login.js +283 -0
  20. package/bin/mcp.js +98 -0
  21. package/bin/me.js +69 -0
  22. package/bin/onboarding.js +134 -0
  23. package/bin/portfolios.js +69 -0
  24. package/bin/rate.js +70 -0
  25. package/bin/register.js +325 -0
  26. package/bin/report-html.js +134 -0
  27. package/bin/save.js +77 -0
  28. package/bin/shakers.js +344 -0
  29. package/bin/share.js +72 -0
  30. package/bin/show-project.js +72 -0
  31. package/bin/socials.js +70 -0
  32. package/bin/superadmin.js +351 -0
  33. package/package.json +40 -0
  34. package/src/agent-category.js +13 -0
  35. package/src/agent-evaluation.js +177 -0
  36. package/src/agent-org-chart.js +355 -0
  37. package/src/agent-synthesis.js +132 -0
  38. package/src/agent-usage.js +118 -0
  39. package/src/agents-client.js +165 -0
  40. package/src/ai-fluency-cell.js +72 -0
  41. package/src/ai-profile-client.js +45 -0
  42. package/src/ai-profile-preview.js +154 -0
  43. package/src/ai.js +66 -0
  44. package/src/alma-client.js +123 -0
  45. package/src/alma-flow.js +32 -0
  46. package/src/alma-repo-context.js +47 -0
  47. package/src/alma-shell.js +161 -0
  48. package/src/ansi.js +62 -0
  49. package/src/applications-client.js +79 -0
  50. package/src/applications-flow.js +58 -0
  51. package/src/auth-client.js +144 -0
  52. package/src/auth-device-client.js +118 -0
  53. package/src/auth-method-picker.js +44 -0
  54. package/src/auth-methods.js +35 -0
  55. package/src/auth-session-store.js +214 -0
  56. package/src/authorship.js +203 -0
  57. package/src/automations-detector.js +170 -0
  58. package/src/availability-client.js +92 -0
  59. package/src/availability-flow.js +105 -0
  60. package/src/backend-request.js +170 -0
  61. package/src/blind-approval-extract.js +48 -0
  62. package/src/brand-ansi.js +14 -0
  63. package/src/brand-svg.js +9 -0
  64. package/src/browser-tools-detector.js +21 -0
  65. package/src/build-next-level.js +56 -0
  66. package/src/certifications-client.js +89 -0
  67. package/src/certifications-flow.js +75 -0
  68. package/src/certify-dimension-client.js +217 -0
  69. package/src/certify-dimension-flow.js +193 -0
  70. package/src/certify-disclaimer.js +51 -0
  71. package/src/certify-remediation-prompt.js +23 -0
  72. package/src/cli-args.js +83 -0
  73. package/src/config-dir.js +120 -0
  74. package/src/config.js +778 -0
  75. package/src/consent-flow.js +115 -0
  76. package/src/consent-skip.js +27 -0
  77. package/src/decisions-extract.js +43 -0
  78. package/src/detect-repos.js +145 -0
  79. package/src/detected-tools.js +15 -0
  80. package/src/detection-evidence.js +105 -0
  81. package/src/detection-noise.js +132 -0
  82. package/src/detectors.js +221 -0
  83. package/src/device-auth-store.js +100 -0
  84. package/src/device-login.js +118 -0
  85. package/src/email-verification.js +152 -0
  86. package/src/env-paths.js +16 -0
  87. package/src/find-projects-cache.js +89 -0
  88. package/src/find-projects-client.js +138 -0
  89. package/src/find-projects-flow.js +164 -0
  90. package/src/git-activity.js +123 -0
  91. package/src/graph-certs.js +58 -0
  92. package/src/graph-generator.js +230 -0
  93. package/src/graph-scan.js +239 -0
  94. package/src/i18n.js +3264 -0
  95. package/src/inter-fonts.js +6 -0
  96. package/src/interactive-select.js +183 -0
  97. package/src/interview-client.js +226 -0
  98. package/src/interview-transcript.js +56 -0
  99. package/src/inventory-client.js +90 -0
  100. package/src/invitations-client.js +66 -0
  101. package/src/invitations-flow.js +50 -0
  102. package/src/lang-client.js +147 -0
  103. package/src/lang-codes.js +14 -0
  104. package/src/lang-flow.js +85 -0
  105. package/src/legal-notice.js +53 -0
  106. package/src/locale.js +51 -0
  107. package/src/markdown-ansi.js +50 -0
  108. package/src/maturity.js +111 -0
  109. package/src/mcp-ai-usage-result-tool.js +109 -0
  110. package/src/mcp-ai-usage-tool.js +333 -0
  111. package/src/mcp-alma-tools.js +91 -0
  112. package/src/mcp-applications-tools.js +42 -0
  113. package/src/mcp-ask-tools.js +50 -0
  114. package/src/mcp-auth-form.js +282 -0
  115. package/src/mcp-availability-tools.js +43 -0
  116. package/src/mcp-certifications-tools.js +42 -0
  117. package/src/mcp-certify-dimension-tools.js +58 -0
  118. package/src/mcp-detector.js +223 -0
  119. package/src/mcp-device-login-tools.js +173 -0
  120. package/src/mcp-find-projects-tools.js +67 -0
  121. package/src/mcp-install.js +245 -0
  122. package/src/mcp-invitations-tools.js +42 -0
  123. package/src/mcp-lang-tools.js +39 -0
  124. package/src/mcp-onboarding-tools.js +388 -0
  125. package/src/mcp-portfolios-tools.js +46 -0
  126. package/src/mcp-profile-tools.js +46 -0
  127. package/src/mcp-rate-tools.js +42 -0
  128. package/src/mcp-register-tools.js +517 -0
  129. package/src/mcp-report-tools.js +101 -0
  130. package/src/mcp-role-tools.js +112 -0
  131. package/src/mcp-saved-positions-tools.js +55 -0
  132. package/src/mcp-server.js +170 -0
  133. package/src/mcp-show-project-tools.js +50 -0
  134. package/src/mcp-socials-tools.js +39 -0
  135. package/src/memory-structure-detector.js +108 -0
  136. package/src/model-call-record.js +198 -0
  137. package/src/onboarding-availability.js +26 -0
  138. package/src/onboarding-client.js +364 -0
  139. package/src/onboarding-flow.js +1078 -0
  140. package/src/onboarding-livekit-client.js +102 -0
  141. package/src/onboarding-status-client.js +42 -0
  142. package/src/open-file.js +32 -0
  143. package/src/osc-link.js +16 -0
  144. package/src/portfolio-client.js +164 -0
  145. package/src/portfolio-git-info.js +61 -0
  146. package/src/portfolios-client.js +85 -0
  147. package/src/portfolios-flow.js +79 -0
  148. package/src/profile-client.js +125 -0
  149. package/src/profile-flow.js +115 -0
  150. package/src/profile-switch.js +46 -0
  151. package/src/prompt-select.js +29 -0
  152. package/src/rate-client.js +82 -0
  153. package/src/rate-flow.js +137 -0
  154. package/src/render-ai-fluency.js +266 -0
  155. package/src/render-certification.js +177 -0
  156. package/src/render-html.js +140 -0
  157. package/src/render-report-text.js +27 -0
  158. package/src/render-sheet.js +261 -0
  159. package/src/render-terminal.js +109 -0
  160. package/src/render-usage-report-text.js +155 -0
  161. package/src/repo-context.js +215 -0
  162. package/src/report-gating.js +49 -0
  163. package/src/report-store.js +226 -0
  164. package/src/report-theme.js +177 -0
  165. package/src/report-view-client.js +50 -0
  166. package/src/reveal-output.js +29 -0
  167. package/src/roadmap-content.js +453 -0
  168. package/src/roadmap-personalization.js +132 -0
  169. package/src/roadmap-prompt.js +154 -0
  170. package/src/roles-client.js +114 -0
  171. package/src/roles-flow.js +192 -0
  172. package/src/sanitize-network-text.js +36 -0
  173. package/src/saved-positions-client.js +71 -0
  174. package/src/saved-positions-flow.js +50 -0
  175. package/src/scan-exclusions.js +50 -0
  176. package/src/scanner.js +612 -0
  177. package/src/scope-signals.js +148 -0
  178. package/src/secret-prompt.js +72 -0
  179. package/src/session-gate.js +30 -0
  180. package/src/session-refresh.js +51 -0
  181. package/src/session-scan.js +336 -0
  182. package/src/session-signals.js +36 -0
  183. package/src/shakers-wordmark.js +17 -0
  184. package/src/share-card.js +362 -0
  185. package/src/share-certification.js +111 -0
  186. package/src/share-consent-state.js +157 -0
  187. package/src/share-payload.js +300 -0
  188. package/src/share.js +184 -0
  189. package/src/show-project-client.js +58 -0
  190. package/src/show-project-flow.js +119 -0
  191. package/src/signup-client.js +91 -0
  192. package/src/skills-client.js +102 -0
  193. package/src/socials-client.js +91 -0
  194. package/src/socials-flow.js +83 -0
  195. package/src/specificity-extract.js +41 -0
  196. package/src/start-add-agents.js +306 -0
  197. package/src/start-add-portfolio.js +388 -0
  198. package/src/start-add-skills.js +260 -0
  199. package/src/stdin-ask.js +129 -0
  200. package/src/steering-extract.js +43 -0
  201. package/src/strip-internal-comments.js +45 -0
  202. package/src/tech-detector.js +295 -0
  203. package/src/templates/report-sheet.html +572 -0
  204. package/src/terminal-agents.js +199 -0
  205. package/src/terminal-format.js +85 -0
  206. package/src/terminal-progress.js +106 -0
  207. package/src/terminal-sections.js +219 -0
  208. package/src/tier-analysis.js +126 -0
  209. package/src/tier-engine.js +114 -0
  210. package/src/tier-projection.js +117 -0
  211. package/src/transcript-turns.js +49 -0
  212. package/src/translations-client.js +84 -0
  213. package/src/typewriter.js +154 -0
  214. package/src/usage-model-calls.js +119 -0
  215. package/src/usage-oneshot-commands.js +180 -0
  216. package/src/usage-repo-scope.js +162 -0
  217. package/src/usage-run-steps.js +338 -0
  218. package/src/work-streams.js +74 -0
package/src/i18n.js ADDED
@@ -0,0 +1,3264 @@
1
+ 'use strict';
2
+
3
+ const { detectLangCode } = require('./locale');
4
+ const { toLowerLang } = require('./lang-codes');
5
+ const { CATEGORIES } = require('./detectors');
6
+
7
+ // Localization of the report (HTML + terminal) and of the CLI notices tied to the report.
8
+
9
+ const catalogs = {
10
+ es: {
11
+ onboarding: {
12
+ title: 'Registro en Shakers (onboarding)',
13
+ askLinkedin: 'Tu URL de LinkedIn (obligatoria):',
14
+ askCv: 'Ruta a tu CV en PDF (opcional, Enter para omitir):',
15
+ askGithub: 'Tu URL de GitHub (opcional, Enter para omitir):',
16
+ askWebsite: 'Tu web personal (opcional, Enter para omitir):',
17
+ importStarted: 'Importando tu perfil en segundo plano mientras continuamos…',
18
+ importError: 'No se pudo iniciar la importacion de tu perfil.',
19
+ importUnavailable: 'No pudimos importar tu perfil ahora (servicio no disponible); seguimos con el registro, puedes reintentar el import mas tarde.',
20
+ importChecking: 'Importando tu perfil…',
21
+ importDone: 'Perfil importado correctamente.',
22
+ importFailed: 'No se pudo completar la importacion de tu perfil. Puedes reintentarla mas tarde desde tu perfil.',
23
+ importStillRunning: 'Tu perfil se sigue importando en segundo plano; estara listo en unos minutos.',
24
+ professionalTitle: 'Tu situacion laboral',
25
+ askWorkSituation: 'Cual es tu situacion laboral actual? Elige una opcion:',
26
+ workSituationLabels: {
27
+ FREELANCE: 'Freelance',
28
+ EMPLOYED: 'Empleado/a',
29
+ BETWEEN_JOBS: 'Entre proyectos',
30
+ STUDYING: 'Estudiando',
31
+ OTHER: 'Otro',
32
+ },
33
+ askEmploymentParticipation: 'Tu puesto es a jornada completa o parcial?',
34
+ employmentParticipationLabels: {
35
+ FULL_TIME: 'Jornada completa',
36
+ PART_TIME: 'Media jornada',
37
+ },
38
+ askFreelanceOpinion: 'Cual es tu opinion sobre el freelance?',
39
+ freelanceOpinionLabels: {
40
+ WAS_FREELANCE_BEFORE: 'Ya he sido freelance',
41
+ OPEN_TO_FREELANCE: 'Me plantearia ser freelance',
42
+ NOT_INTERESTED: 'No me interesa',
43
+ },
44
+ askChangeMotivators: 'Que te motiva del freelance o de Shakers?',
45
+ changeMotivatorLabels: {
46
+ HIGHER_RATE: 'Conseguir mejor tarifa',
47
+ SPECIFIC_PROJECT: 'Un proyecto concreto',
48
+ LEARNING_CERTIFICATION: 'Seguir aprendiendo y certificarme',
49
+ FLEXIBILITY: 'La flexibilidad de ser freelance',
50
+ COMMUNITY: 'Pertenecer a una comunidad',
51
+ LIFESTYLE_CHANGE: 'Cambiar de estilo de vida',
52
+ },
53
+ professionalSaved: 'Situacion laboral guardada.',
54
+ chooseMethodHeading: '¿Cómo quieres crear tu cuenta?',
55
+ signupTitle: 'Crea tu cuenta de Shakers',
56
+ askName: 'Nombre:',
57
+ askLastName: 'Apellidos:',
58
+ askEmail: 'Email (usa uno NUEVO; si ya existe te ofrecere iniciar sesion):',
59
+ askPassword: 'Contrasena (minimo 8 caracteres):',
60
+ askPasswordConfirm: 'Repite la contrasena:',
61
+ passwordMismatch: 'Las contrasenas no coinciden. Intentalo de nuevo.',
62
+ askFreelanceTypeSelect: 'Como trabajas? Elige una opcion:',
63
+ freelanceTypeLabels: {
64
+ FREELANCE: 'Freelance',
65
+ EMPLOYEE: 'Empleado/a',
66
+ AGENCY: 'Agencia o empresa',
67
+ POTENTIAL_FREELANCE: 'Me planteo ser freelance',
68
+ },
69
+ invalidChoice: 'Opcion no valida.',
70
+ askNewsletter: 'Quieres recibir la newsletter? (s/n):',
71
+ weakPassword: 'La contrasena no cumple los requisitos (minimo 8 caracteres).',
72
+ signupError: 'No se pudo crear la cuenta.',
73
+ freelanceIntentRequired: 'Tu intencion freelance es obligatoria para ese tipo.',
74
+ accountExists: 'Ya existe una cuenta con ese email. Vamos a iniciar sesion.',
75
+ accountReady: 'Cuenta creada. Sesion iniciada.',
76
+ sessionError: 'La cuenta se creo pero no se pudo iniciar sesion. Ejecuta `shakers login`.',
77
+ pricingTitle: 'Tu precio por proyecto',
78
+ pricingSaved: 'Precio guardado.',
79
+ askPricingFullAmount: (cur) => `Precio por proyecto a jornada completa en ${cur} (numero, sin separadores de miles, p.ej. 60000):`,
80
+ askPricingFullCurrency: 'Moneda:',
81
+ askPricingPartSelect: 'Ofreces proyectos a media jornada? (s/n):',
82
+ askPricingPartAmount: (cur) => `Precio por proyecto a media jornada en ${cur} (numero, sin separadores de miles, p.ej. 50):`,
83
+ askPricingPartCurrency: 'Moneda:',
84
+ pricingAmountInvalid: 'Importe no valido. Usa un numero sin separadores de miles, con maximo 2 decimales (p.ej. 60000 o 1500.50), entre 0 y 999999.99.',
85
+ availabilityTitle: 'Tu disponibilidad y ubicacion',
86
+ availabilityAsk: 'Quieres indicar tu disponibilidad y ubicacion ahora? (s/n):',
87
+ availableAsk: 'Estas disponible para nuevos proyectos? (s/n):',
88
+ monthlyHoursAsk: 'Cuantas horas al mes?',
89
+ onlyRemoteAsk: 'Trabajas solo en remoto? (s/n):',
90
+ countryAsk: 'Pais (codigo ISO-2 de 2 letras, p.ej. ES):',
91
+ countryInvalid: 'Codigo de pais no valido. Usa 2 letras (p.ej. ES, US, GB).',
92
+ subdivisionAsk: 'Provincia o region (opcional, Enter para omitir):',
93
+ timezoneAsk: (tz) => (tz ? `Zona horaria [${tz}] (Enter para aceptar, o escribe otra):` : 'Zona horaria (p.ej. Europe/Madrid):'),
94
+ longFullTimeAsk: 'Abierto a proyectos largos a tiempo completo? (s/n):',
95
+ phonePrefixAsk: 'Prefijo telefonico (opcional, p.ej. +34, Enter para omitir):',
96
+ phoneNumberAsk: 'Numero de telefono (opcional, Enter para omitir):',
97
+ phoneSaved: 'Telefono guardado.',
98
+ phoneUnavailable: 'No pudimos guardar tu telefono ahora. Puedes actualizarlo mas tarde.',
99
+ availabilitySaved: 'Disponibilidad guardada.',
100
+ availabilityUnavailable: 'No pudimos guardar tu disponibilidad ahora. Puedes actualizarla mas tarde.',
101
+ availabilitySkipped: 'De acuerdo, puedes indicar tu disponibilidad mas tarde.',
102
+ languagesTitle: 'Tus idiomas',
103
+ languagesAsk: 'Quieres indicar los idiomas que hablas? (s/n):',
104
+ askLanguageCode: 'Idioma:',
105
+ languageNames: {
106
+ es: 'Espanol',
107
+ en: 'Ingles',
108
+ fr: 'Frances',
109
+ ca: 'Catalan',
110
+ de: 'Aleman',
111
+ eu: 'Euskera',
112
+ it: 'Italiano',
113
+ pt: 'Portugues',
114
+ },
115
+ askLanguageLevel: 'Tu nivel en ese idioma:',
116
+ languageLevelLabels: {
117
+ NATIVE: 'Nativo o bilingue',
118
+ ADVANCED: 'Fluido en el trabajo y el dia a dia',
119
+ INTERMEDIATE: 'Puedo trabajar en el (hablado y escrito)',
120
+ INTERMEDIATE_WRITTEN: 'Puedo trabajar en el, prefiero comunicacion escrita',
121
+ },
122
+ askAnotherLanguage: 'Quieres anadir otro idioma? (s/n):',
123
+ languagesSaved: 'Idiomas guardados.',
124
+ languagesUnavailable: 'No pudimos guardar tus idiomas ahora. Puedes anadirlos mas tarde.',
125
+ languagesSkipped: 'De acuerdo, puedes indicar tus idiomas mas tarde.',
126
+ usageTitle: 'Evaluacion de tu uso de IA (opcional)',
127
+ usageInfoAccessed: 'Se accede a: la estructura de tus herramientas de IA y a extractos redactados de tu actividad en este equipo. Tu codigo y tus prompts en crudo NUNCA salen de tu maquina.',
128
+ usageGoalDuration: 'Objetivo: medir como trabajas con IA para tu perfil. Duracion: ~1-2 minutos.',
129
+ usageAccept: 'Quieres evaluar tu uso de IA ahora? (s/n):',
130
+ usageSkippedEvidence: 'Ya tienes evidencia de uso de IA en Shakers; nos saltamos este paso.',
131
+ interviewTitle: 'Entrevista de onboarding (opcional)',
132
+ interviewOnlyTitle: 'Entrevista de onboarding',
133
+ interviewInfoAccessed: 'Se accede a: tus respuestas en esta entrevista y tu perfil importado. La conduce el servidor de Shakers.',
134
+ interviewGoalDuration: 'Objetivo: conocer tu rol, tu dedicacion y como usas la IA. Duracion: ~5 minutos.',
135
+ interviewAccept: 'Quieres hacer la entrevista ahora? (s/n):',
136
+ interviewComplete: 'Entrevista completada. Gracias!',
137
+ interviewAlreadyDone: 'Ya completaste la entrevista de onboarding; no se puede repetir desde la CLI. Puedes seguir ajustando tu perfil.',
138
+ repeatWarnTitle: 'Repetir la entrevista de onboarding',
139
+ repeatWarnBody: 'Repetir SOBREESCRIBE tu entrevista de onboarding anterior y NO se puede deshacer: la evaluación previa se descarta.',
140
+ repeatConfirmPrompt: '¿Seguro que quieres repetir y sobreescribir tu onboarding?',
141
+ repeatConfirmYes: 'Sí, repetir y sobreescribir',
142
+ repeatConfirmNo: 'No, cancelar',
143
+ repeatAborted: 'De acuerdo, no se repite el onboarding.',
144
+ repeatRestarted: 'Onboarding reiniciado. Empecemos de nuevo.',
145
+ repeatNotFound: 'No tienes un onboarding todavía. Haz `shakers onboarding` primero.',
146
+ repeatAuthError: 'Tu sesión no permite esta acción. Inicia sesión de nuevo: shakers login',
147
+ repeatFailed: (reason) => `No se pudo reiniciar el onboarding (${reason}). Inténtalo de nuevo.`,
148
+ interviewAnswer: 'Tu respuesta:',
149
+ questionHeading: (n) => `Pregunta ${n}`,
150
+ interviewTurnError: 'No se pudo continuar la entrevista.',
151
+ thinking: 'Alma esta pensando…',
152
+ interviewIntro: 'Es una conversación; responde con naturalidad, sin prisa.',
153
+ interviewTransition: 'Gracias. Sigamos:',
154
+ interviewConnecting: 'Conectando con la entrevista...',
155
+ livekitMissing: 'La entrevista necesita el paquete opcional @livekit/rtc-node, que no esta instalado.\n Reejecuta el instalador de talento (SHAKERS_PROFILE=talent) para instalarlo, o hazlo a mano:\n npm install --omit=dev --prefix ~/.shakers @livekit/rtc-node\n (usa tu ruta de SHAKERS_CLI_HOME si instalaste el CLI en otro sitio).\n Luego reejecuta: shakers onboarding',
156
+ livekitOld: 'El paquete @livekit/rtc-node instalado es demasiado antiguo para text-streams.\n Actualizalo: npm install --prefix ~/.shakers @livekit/rtc-node@latest',
157
+ livekitConnectError: 'No se pudo conectar con la sala de la entrevista.',
158
+ transcriptWarn: (reason) => `Aviso: no se pudo guardar la transcripcion de la entrevista (${reason}). Puede que no quede disponible para revisarla.`,
159
+ finalizedGeneric: 'Onboarding completado.',
160
+ finalizedAt: (pct) => `Onboarding completado. Tu perfil esta al ${pct}%.`,
161
+ finalizeWarn: (reason) => `No se pudo marcar el onboarding como completado (${reason}). Puedes reintentarlo mas tarde.`,
162
+ profileReadyNoUrl: 'Tu perfil esta listo. Puedes verlo en tu cuenta de Shakers.',
163
+ profileReadyUrl: (url) => `Tu perfil esta listo: ${url}`,
164
+ help: 'Uso: shakers onboarding [--lang es|en] [--accept-disclaimer]',
165
+ },
166
+ categories: {
167
+ AGENTIC_CLI: 'CLI agéntica',
168
+ AI_EDITOR: 'Editor con IA',
169
+ IDE_ASSISTANT: 'Asistente en IDE',
170
+ COMPLETION: 'Autocompletado',
171
+ AI_TERMINAL: 'Terminal con IA',
172
+ },
173
+ mcpCategories: {
174
+ data: 'Datos',
175
+ comms: 'Comunicación',
176
+ dev: 'Desarrollo',
177
+ browser: 'Navegador',
178
+ other: 'Otro',
179
+ },
180
+ tierNames: {
181
+ T0: 'Banco vacío',
182
+ T1: 'Primera herramienta',
183
+ T2: 'Banco con notas',
184
+ T3: 'Banco conectado',
185
+ T4: 'Herramienta propia',
186
+ T5: 'Operador agéntico',
187
+ T6: 'Multi-agente',
188
+ T7: 'Taller orquestado',
189
+ },
190
+ tierAnalysis: {
191
+ heading: 'Análisis de tier: por qué este nivel',
192
+ intro: (tierKey, tierName) =>
193
+ `Tu tier actual es ${tierKey} (${tierName}). El motor de tiers es determinista: certifica un nivel `
194
+ + 'solo cuando se cumplen TODOS los criterios de ese nivel y de todos los anteriores, verificado '
195
+ + 'estrictamente de abajo hacia arriba (nunca se salta un tier inferior por tener una señal de uno '
196
+ + 'superior). A continuación se detalla, criterio por criterio, qué se ha comprobado y con qué señal '
197
+ + 'concreta de tu entorno queda respaldado.',
198
+ metHeading: 'Criterios que cumples:',
199
+ blockingLabel: 'Criterio exacto que te impide subir de tier:',
200
+ maxTierNote: 'Cumples todos los criterios de la escalera T0-T7: no hay un criterio adicional bloqueando tu progreso.',
201
+ criterion: {
202
+ t1Met: (n) => `Tienes al menos una herramienta de IA detectada y configurada en tu entorno (\`totalDetected = ${n}\`).`,
203
+ t2Met: (n) => `Dispones de al menos un fichero de contexto persistente — instrucciones, configuración o reglas — para alguna herramienta (\`context = ${n}\`).`,
204
+ t3Met: (n) => `Tienes al menos un servidor MCP conectado, dando a la IA acceso a datos o herramientas externas (\`mcpServers = ${n}\`).`,
205
+ t4Met: (n) => `Has creado activos propios — skills, comandos o reglas personalizadas — más allá de la configuración por defecto (\`custom = ${n}\`).`,
206
+ t5Met: (hasAgentic, mcp, custom) => `Operas con una CLI agéntica (Claude Code, Aider, Gemini CLI, Codex CLI o Amazon Q Developer) combinada con MCP y activos propios (\`hasAgentic = ${hasAgentic}\`, \`mcpServers = ${mcp}\`, \`custom = ${custom}\`).`,
207
+ t6Met: (n) => `Tienes un equipo de al menos 2 agentes especializados definidos (\`agentCounts.agents = ${n}\`).`,
208
+ t7Met: (n) => `Tienes automatización basada en hooks configurada (\`hooks = ${n}\`).`,
209
+ t1Blocking: (n) => `Para subir a T1 (Primera herramienta) necesitas al menos una herramienta de IA detectada — actualmente \`totalDetected = ${n}\`.`,
210
+ t2Blocking: (n) => `Para subir a T2 (Banco con notas) necesitas al menos un fichero de contexto persistente (instrucciones, configuración o reglas) — actualmente \`context = ${n}\`.`,
211
+ t3Blocking: (n) => `Para subir a T3 (Banco conectado) necesitas conectar al menos un servidor MCP — actualmente \`mcpServers = ${n}\`.`,
212
+ t4Blocking: (n) => `Para subir a T4 (Herramienta propia) necesitas crear al menos un activo propio — skill, comando o regla — más allá de la configuración por defecto — actualmente \`custom = ${n}\`.`,
213
+ t5Blocking: (hasAgentic, mcp, custom) => {
214
+ const missing = [];
215
+ if (!hasAgentic) missing.push('una CLI agéntica (Claude Code, Aider, Gemini CLI, Codex CLI o Amazon Q Developer)');
216
+ if (mcp < 1) missing.push('al menos 1 servidor MCP');
217
+ if (custom < 1) missing.push('al menos 1 activo propio (skill, comando o regla)');
218
+ return `Para subir a T5 (Operador agéntico) te falta: ${missing.join('; ')} (\`hasAgentic = ${hasAgentic}\`, \`mcpServers = ${mcp}\`, \`custom = ${custom}\`).`;
219
+ },
220
+ t6Blocking: (n) => `Para subir a T6 (Multi-agente) necesitas al menos 2 agentes especializados definidos en \`.claude/agents/\` — actualmente tienes ${n}.`,
221
+ t7Blocking: (n) => `Para subir a T7 (Taller orquestado) necesitas al menos un hook de automatización configurado — actualmente \`hooks = ${n}\`.`,
222
+ },
223
+ },
224
+ ladder: {
225
+ levelsHeading: 'Niveles de madurez (0-4)',
226
+ // ADR-016: the ladder now groups tiers by SETUP LEVEL, not the 0-4 band.
227
+ setupHeading: 'Nivel de setup',
228
+ setupIntro:
229
+ 'Tu nivel de setup (S1–S3) resume tu uso de IA de un vistazo; el tier (T0-T7) es el eje '
230
+ + 'fino del que se deriva. Ambos son deterministas. Abajo se marca lo que ya has superado (✓), '
231
+ + 'dónde estás ahora (●) y lo que queda por delante (○) con el criterio exacto que lo desbloquea.',
232
+ tiersHeading: 'Escalera de tiers (T0-T7)',
233
+ levelLabel: (n) => `Nivel ${n}`,
234
+ intro:
235
+ 'Tu nivel de madurez (0-4) resume tu uso de IA de un vistazo; el tier (T0-T7) es el eje '
236
+ + 'fino del que se deriva. Ambos son deterministas. Abajo se marca lo que ya has superado (✓), '
237
+ + 'dónde estás ahora (●) y lo que queda por delante (○) con el criterio exacto que lo desbloquea.',
238
+ reachedLabel: 'Superado',
239
+ currentLabel: 'Estás aquí',
240
+ pendingLabel: 'Pendiente',
241
+ unlockLabel: 'Para desbloquear',
242
+ legend: (done, current, pending) => `${done} superado · ${current} actual · ${pending} pendiente`,
243
+ levelDesc: {
244
+ none: 'Sin rastro de IA: no se detecta ninguna herramienta de IA en tu entorno.',
245
+ exploring: 'Explorando: tienes herramientas de IA instaladas y las estás probando.',
246
+ integrated: 'Integrado: la IA está conectada a tus proyectos con contexto persistente.',
247
+ power: 'Power user: extiendes la IA con MCP, skills/comandos propios y CLIs agénticas.',
248
+ orchestrator: 'Orquestador: operas varios agentes coordinados y automatización de principio a fin.',
249
+ },
250
+ tierDesc: {
251
+ T0: 'Banco vacío: aún no se detecta ninguna herramienta de IA.',
252
+ T1: 'Primera herramienta: usas al menos una herramienta de IA.',
253
+ T2: 'Banco con notas: ficheros de contexto persistente guían a la IA.',
254
+ T3: 'Banco conectado: un servidor MCP da a la IA acceso a tus datos y herramientas.',
255
+ T4: 'Herramienta propia: has creado tus propios ficheros de skill, comandos o reglas.',
256
+ T5: 'Operador agéntico: una CLI agéntica combina MCP y tus activos propios de punta a punta.',
257
+ T6: 'Multi-agente: un equipo de 2+ agentes especializados.',
258
+ T7: 'Taller orquestado: hooks automatizan el taller y los agentes se orquestan entre sí.',
259
+ },
260
+ },
261
+ // Agent classification against the AI-agent catalog (skill-code-certification, report req 2) + the "how to improve" tips (req 3).
262
+ classification: {
263
+ label: 'Clasificación',
264
+ noCategory: 'Sin categoría',
265
+ // CUARTO ESTADO (2026-08-03): la llamada FUE BIEN y este agente se quedó fuera.
266
+ agentEvalOmitted: 'fuera de esta ejecución',
267
+ // `count` decide la concordancia (singular/plural) para que el aviso no
268
+ // chirríe; vale para uno, algunos y todos.
269
+ agentsEvalPartial: (names, count = 2) => (count === 1
270
+ ? `Este agente se quedó fuera de esta ejecución: ${names}. La respuesta del modelo no llegó a cubrirlo, así que no es que no tenga categoría: es que no se llegó a evaluarlo. Vuelve a ejecutar \`usage\` para completarlo.`
271
+ : `Estos agentes se quedaron fuera de esta ejecución: ${names}. La respuesta del modelo no llegó a cubrirlos, así que no es que no tengan categoría: es que no se llegó a evaluarlos. Vuelve a ejecutar \`usage\` para completarlos.`),
272
+ // LA EVALUACIÓN FALLÓ ENTERA (issue 116).
273
+ agentsEvalMissing: 'La categoría de tus agentes no se pudo calcular en esta ejecución: la evaluación no llegó a responder. No es que no tengan categoría. Vuelve a ejecutar `usage` para intentarlo otra vez.',
274
+ // ADR-042 (issue 125) parte el aviso genérico en dos, y el motivo es que apuntan a sitios distintos.
275
+ agentsEvalUnreachable: 'No se pudo conectar con Shakers, así que tus agentes salen sin categoría. Comprueba tu conexión y vuelve a ejecutar `usage`.',
276
+ agentsEvalErrored: 'Shakers respondió con un error, así que tus agentes salen sin categoría. No es cosa de tu configuración; vuelve a intentarlo en un rato.',
277
+ agentsEvalTimedOut: (count) => `La evaluación de tus agentes no respondió dentro del plazo, así que esta vez salen sin categoría. Ese tiempo crece con el número de agentes y tienes ${count}, así que volver a ejecutarlo tal cual probablemente acabe igual.`,
278
+ improvementsHeading: 'Cómo mejorar este agente',
279
+ // LAS OCHO CATEGORÍAS DEL CATÁLOGO DEL SERVICIO: siete especialidades y el SUELO (issue 120; el catálogo vive en el servicio, ADR-036/037).
280
+ categories: {
281
+ developer: 'Desarrollo',
282
+ product: 'Producto',
283
+ designer: 'Diseño',
284
+ marketing: 'Marketing',
285
+ data: 'Datos',
286
+ finance: 'Finanzas',
287
+ sales: 'Ventas',
288
+ other: 'Otros / interno',
289
+ },
290
+ levels: {
291
+ L1: 'L1 · operativo',
292
+ L2: 'L2 · táctico',
293
+ L3: 'L3 · estratégico',
294
+ },
295
+ },
296
+ // LEGACY 0-4 band names — kept only for older persisted reports / the
297
+ // unchanged sent payload (ADR-016); the report shows `setupLevels` instead.
298
+ levelNames: {
299
+ none: 'Sin rastro de IA',
300
+ exploring: 'Explorando',
301
+ integrated: 'Integrado',
302
+ power: 'Power user',
303
+ orchestrator: 'Orquestador',
304
+ },
305
+ // Setup Level (Talent Certification Framework, ADR-016): the 3-value rollup that REPLACES the 0-4 band on every surface.
306
+ setupLevels: {
307
+ none: {
308
+ label: 'Sin certificar',
309
+ desc: 'Sin setup de IA: no se detecta ninguna herramienta de IA en tu entorno.',
310
+ },
311
+ S1: {
312
+ label: 'S1 · Asistido',
313
+ desc: 'Asistido: usas IA con contexto persistente (instrucciones/configuración) en tus proyectos.',
314
+ },
315
+ S2: {
316
+ label: 'S2 · Extendido',
317
+ desc: 'Extendido: amplías la IA con MCP y activos propios (ficheros de skill, comandos o reglas).',
318
+ },
319
+ S3: {
320
+ label: 'S3 · Orquestado',
321
+ desc: 'Orquestado: operas CLIs agénticas, equipos de agentes y automatización de punta a punta.',
322
+ },
323
+ },
324
+ nextSteps: {
325
+ 0: 'Instala una herramienta de IA (Claude Code, Cursor o Copilot) y pruébala en un proyecto real.',
326
+ 1: 'Añade un fichero de instrucciones al proyecto (CLAUDE.md, .cursorrules o copilot-instructions.md) para dar contexto persistente.',
327
+ 2: 'Conecta un servidor MCP o crea reglas/comandos propios para que la IA acceda a tus datos y flujos.',
328
+ 3: 'Combina una CLI agéntica con MCP y skills/comandos propios; automatiza una tarea recurrente de principio a fin.',
329
+ 4: 'Ya operas a nivel de orquestación: documenta tu setup y encadena agentes o ejecución en background.',
330
+ },
331
+ recency: {
332
+ today: 'hoy',
333
+ this_week: 'esta semana',
334
+ this_month: 'este mes',
335
+ this_quarter: 'este trimestre',
336
+ stale: 'desactualizado',
337
+ },
338
+ terminal: {
339
+ brandSub: 'perfil de uso de IA',
340
+ toolsDetected: (n, total) => `${n}/${total} herramientas detectadas`,
341
+ level: (level, name) => `Nivel ${level} · ${name}`,
342
+ // ADR-016: Setup Level shown in the top bar (replaces the 0-4 level line).
343
+ setupLevel: (label) => `Setup · ${label}`,
344
+ // Current tier appended to the top bar, next to the level (report req 1 addendum).
345
+ tierInline: (key, name) => ` · Tier ${key} · ${name}`,
346
+ detectedHeading: 'Detectadas',
347
+ activity: {
348
+ heading: 'Actividad (repos analizados)',
349
+ repos: (list) => `Repos: ${list}`,
350
+ sessions: (n) => `${n} ${n === 1 ? 'sesión' : 'sesiones'} (raíz)`,
351
+ subagents: (n) => `${n} ${n === 1 ? 'run' : 'runs'} de subagente`,
352
+ hours: (h) => `${h} h activas (gap idle > 25 min = pausa)`,
353
+ commits: (authored, total) => `${authored}/${total} commits tuyos`,
354
+ lines: (added, deleted) => `+${added}/-${deleted} líneas`,
355
+ velocity: (v) => `${v} commits/día`,
356
+ workStreams: (streams, multiDay, maxSpan) =>
357
+ `${streams} ${streams === 1 ? 'workstream' : 'workstreams'} (${multiDay} multi-día, máx ${maxSpan}d)`,
358
+ tractionLabel: 'Actividad reciente',
359
+ tSessions: (n) => `${n} sesiones (90d)`,
360
+ tDays: (n) => `${n} días/semana`,
361
+ tAgents: (d, p) => `${d} agentes detectados${p != null ? ` · ${p} en tu perfil` : ''}`,
362
+ tTools: (list) => `herramientas: ${list}`,
363
+ },
364
+ none: '(ninguna)',
365
+ environment: 'Entorno',
366
+ editors: 'editores',
367
+ noEditorsDetected: 'ninguno detectado',
368
+ // ADR-016 agent evaluation (terminal, one line per agent): compact usage
369
+ // signal derived from the local Claude Code history.
370
+ agentUsed: (n) => `usado ${n}×`,
371
+ agentUnused: 'sin uso local',
372
+ agentUsageUnavailable: '(sin historial local de Claude Code: uso no disponible)',
373
+ // ADR-016: discoverability hint for the next-steps section (behind --roadmap).
374
+ roadmapHint: 'Ejecuta `usage --roadmap` para ver los pasos que te separan del siguiente nivel de uso de IA. No son pasos de esta herramienta: son cambios en tu forma de trabajar, para las próximas semanas.',
375
+ aiProfileGenerating: 'Generando tu perfil de IA (matriz, visión y cómo trabajas)…',
376
+ aiProfilePendingRetry: 'Tu perfil de IA (matriz, visión y cómo trabajas) se está generando; re-lanza `report` en un momento para verlo.',
377
+ // issue 122 / ADR-044: se muestra cuando había una sesión y ha caducado, para que el talento entienda por qué vuelve a ver el informe completo.
378
+ sessionExpiredNotice: 'Tu sesión ha caducado. Estás viendo el informe completo como usuario general; ejecuta `login` para volver a entrar.',
379
+ nextStep: 'Siguiente paso para subir de nivel',
380
+ files: (n) => `${n} ${n === 1 ? 'fichero' : 'ficheros'}`,
381
+ lastModified: (label) => `última modificación: ${label}`,
382
+ skillsHeading: 'Skills',
383
+ },
384
+ html: {
385
+ lang: 'es',
386
+ title: (setupLabel) => `Shakers · ${setupLabel}`,
387
+ h1: 'Tu perfil de uso de IA',
388
+ sub: 'Un vistazo local a qué herramientas de IA tienes y cuánto las has configurado.',
389
+ levelOf: (level) => `Nivel ${level} de 4`,
390
+ // ADR-016: hero label for the Setup Level (replaces the 0-4 "Nivel X de 4").
391
+ setupLevelOf: 'Nivel de setup',
392
+ // Current tier shown in the hero bar next to the level (report req 1 addendum).
393
+ currentTier: (key, name) => `Tier ${key} · ${name}`,
394
+ // Suffix only: the number is already bolded separately in the markup
395
+ // (see render-html.js), so we avoid duplicating/parsing the translated string.
396
+ detectedSuffix: (total) => `de ${total} herramientas detectadas`,
397
+ maturity: 'Madurez',
398
+ tools: 'Herramientas',
399
+ // talents-ai-score: only DETECTED tools are listed now (undetected ones were pure noise — any relevant next step already lives in the tier roadmap section).
400
+ toolsEmpty: 'No se ha detectado ninguna herramienta de IA en tu entorno.',
401
+ configIntensity: 'intensidad de configuración',
402
+ files: (n) => `${n}&nbsp;${n === 1 ? 'fichero' : 'ficheros'}`,
403
+ lastModified: (dateStr) => `última modificación: ${dateStr}`,
404
+ environment: 'Entorno',
405
+ platform: 'Plataforma',
406
+ architecture: 'Arquitectura',
407
+ installedEditors: 'Editores instalados',
408
+ noEditorsDetected: 'ninguno detectado',
409
+ nextStep: 'Siguiente paso para subir de nivel',
410
+ diagramHeading: 'Agentes',
411
+ agentsEmpty: 'No se han detectado agentes de IA configurados (p. ej. .claude/agents/).',
412
+ orchestratorLabel: 'Orchestrator',
413
+ // Last-resort description fallback (talents-ai-score, real-browser user feedback: a card must NEVER show only name+model, no description at all).
414
+ agentDescriptionFromName: (name) => `Agente "${name}" (sin descripción declarada en su fichero).`,
415
+ // AI product an agent belongs to, derived from its source (proper nouns; same es/en).
416
+ aiProducts: { 'claude-code': 'Claude Code' },
417
+ // ADR-016 agent evaluation (HTML per-agent detail): definition-quality score + LLM rationale + local usage signal.
418
+ agentScoreLabel: 'Calidad de la definición (0-100)',
419
+ agentQualityLabel: 'Por qué:',
420
+ agentUsageLabel: 'Uso local (historial de Claude Code)',
421
+ agentUsedTimes: (n) => `usado ${n}×`,
422
+ agentUnused: 'sin uso local',
423
+ // Project technologies (talents-ai-score, ADR-012).
424
+ technologiesHeading: 'Tecnologías del proyecto',
425
+ technologiesEmpty: 'No se reconoció ningún framework o librería en los manifiestos de dependencias (package.json, requirements.txt, go.mod, pyproject.toml).',
426
+ // MCP servers by name (talents-ai-score, issue 015).
427
+ // Issue 110: esta clave existía desde la 015 y NO la pintaba nadie — los servidores MCP se detectaban, contaban para el tier y no se nombraban en ninguna superficie.
428
+ mcpHeading: 'Servicios conectados por MCP',
429
+ mcpEmpty: 'No se ha detectado ningún servidor MCP. Conectar uno es el criterio que te sube a T3 (Banco conectado).',
430
+ mcpUnidentified: (n) => `${n} ${n === 1 ? 'servidor más, cuyo servicio no' : 'servidores más, cuyo servicio no'} hemos podido identificar por su nombre.`,
431
+ roadmapHeading: 'Tu próximo nivel de uso de IA',
432
+ // 083: subtitulo del bloque, para que quien entra directo con --roadmap lea
433
+ // el registro antes que el contenido.
434
+ roadmapSubheading: 'Lo que sigue lo haces tú en tu trabajo, no en esta herramienta. La herramienta solo te dice por dónde.',
435
+ roadmapUpgradeWhenLabel: 'Subes de tier cuando:',
436
+ roadmapUnlocksLabel: 'Qué desbloquea',
437
+ roadmapStepsLabel: 'Pasos',
438
+ roadmapSnippetLabel: 'Snippet copiable',
439
+ roadmapTipsLabel: 'Tips de comunidad',
440
+ roadmapMistakesLabel: 'Errores comunes',
441
+ roadmapConsolidationLabel: 'Pasos de consolidación',
442
+ roadmapHonestyLabel: 'Nota de honestidad',
443
+ roadmapContentUnavailable: 'El contenido detallado de este nivel aún no está disponible en este idioma.',
444
+ // ADR-015: shown only when the 4 prose gaps below were actually replaced by a validated, project-adapted response — never on fallback to the curated content.
445
+ roadmapPersonalizedNotice: 'Contenido adaptado a tu proyecto.',
446
+ // Issue 109: los dos avisos que faltaban, hermanos del `agentsEvalMissing` que añadió la 106.
447
+ roadmapNotPersonalizedNotice: 'Este roadmap es el genérico de tu tier: esta vez no se ha podido adaptar a tu proyecto. Vuelve a ejecutar `usage` para intentarlo otra vez.',
448
+ // Issue 084: el antes y el después del roadmap.
449
+ roadmapNowLabel: 'De dónde partes',
450
+ roadmapProjectionLabel: 'Dónde te deja esta ruta',
451
+ roadmapProjectionTag: 'proyección',
452
+ roadmapProjectionNote: 'Calculado con el mismo motor que tu evaluación, aplicando el mínimo que piden estos pasos. Es una proyección, no una promesa: la evaluación mide uso real, así que darlos es condición necesaria y no suficiente.',
453
+ implementationPromptHeading: 'Prompt para implementar',
454
+ implementationPromptHint: 'Copia este prompt y pégalo en tu IA de confianza (Claude Code, Cursor, ChatGPT...) para que lo implemente en tu proyecto.',
455
+ // Copy-to-clipboard button (HTML report only — talents-ai-score): navigator.clipboard with a document.execCommand fallback, both inline, zero-network.
456
+ implementationPromptCopyLabel: 'Copiar',
457
+ implementationPromptCopiedLabel: 'Copiado ✓',
458
+ privacyNote:
459
+ 'Este informe se ha generado en local. Solo registra qué herramientas '
460
+ + 'existen, cuántas configuraciones tienes y tu nivel: nunca el contenido '
461
+ + 'de tus ficheros, rutas ni credenciales.',
462
+ metaLine: (dateStr, anonId, platform) =>
463
+ `Generado ${dateStr} · id anónimo <code>${anonId}</code> · plataforma ${platform}`,
464
+ rawData: 'Ver los datos exactos de este informe (JSON)',
465
+ },
466
+ cli: {
467
+ // Reporting redesign (skill-code-certification): el HTML ya NO es opt-in (se retira --html).
468
+ reportLink: (url) => `Abre tu informe en el navegador:\n ${url}`,
469
+ // `share` command (skill-code-certification): copy del CLI que envuelve a la tarjeta branded para LinkedIn.
470
+ share: {
471
+ help: 'share — crea una tarjeta branded con tu resultado de uso de IA (tier + nota) para compartir en LinkedIn.\n'
472
+ + ' Usa `usage` primero; `share` toma el último uso de IA de este proyecto.\n'
473
+ + ' Opciones: --root <dir>, --lang es|en',
474
+ noFootprint: 'Aún no hay uso de IA para este proyecto. Ejecuta `usage` primero y luego `share`.',
475
+ ready: (url) => `Tu tarjeta para compartir está lista — ábrela para descargar el PNG y publicarla:\n ${url}`,
476
+ hint: 'LinkedIn no permite adjuntar la imagen por URL: descarga el PNG desde la tarjeta y luego adjúntalo en tu publicación.',
477
+ error: 'No se pudo generar la tarjeta para compartir.',
478
+ disabled: '`share` está deshabilitado por ahora. Vuelve a intentarlo más adelante.',
479
+ },
480
+ accountReminder: {
481
+ question: '¿Ya tienes cuenta?',
482
+ hint: 'ai-usage es público. Iniciar sesión es opcional; flechas para moverte, enter para elegir.',
483
+ yes: 'Sí, iniciar sesión',
484
+ no: 'No, continuar sin cuenta',
485
+ },
486
+ // `report` command (ADR-016): genera y ABRE el informe HTML completo y compartible de este proyecto (uso de IA + Skills certificadas).
487
+ report: {
488
+ help: 'report — genera y abre el informe HTML completo de este proyecto (uso de IA + Skills certificadas) para compartir con tu equipo.\n'
489
+ + ' Usa `usage` (y opcionalmente `certify`) primero; `report` reúne su resultado.\n'
490
+ + ' Opciones: --root <dir>, --lang es|en, --no-open (no abre el navegador; solo imprime el enlace)',
491
+ noData: 'Aún no hay nada que mostrar para este proyecto. Ejecuta `usage` primero (y opcionalmente `certify`), luego `report`.',
492
+ ready: (url) => `Tu informe está listo:\n ${url}`,
493
+ opening: 'Abriéndolo en tu navegador…',
494
+ error: 'No se pudo generar el informe.',
495
+ },
496
+ frameworkIntroUsage:
497
+ 'Esto mide tu fluidez con IA en dos ejes: Setup (madurez de tu tooling, T0–T7, determinista) y Usage (cómo lo usas, evaluado en entrevista + código). Te sitúa en el mapa 3×3 (Explorer → AI Native). Completarlo posiciona tu perfil agéntico para los proyectos que valoran cómo trabajas con IA.',
498
+ // Terminal progress feedback (talents-ai-score): stderr-only status
499
+ // during the two slow phases (see src/terminal-progress.js).
500
+ scanningLabel: 'Escaneando entorno y detectores…',
501
+ // Loader de la fase de agregación por repo seleccionado (git + sesiones + steering/decisiones + evidencia + org-chart de agentes).
502
+ aggregatingLabel: 'Agregando señales de los repos seleccionados…',
503
+ reposScopePromptHeader: '¿Qué quieres evaluar?',
504
+ reposScopePromptHint: 'Flechas para moverte · enter para elegir',
505
+ reposScopeOptionMachine: 'Toda la máquina (todos los repos con actividad de IA)',
506
+ reposScopeOptionRepo: 'Repo actual (solo este directorio)',
507
+ reposDetectedNote: (repoCount, unassigned) =>
508
+ repoCount === 0
509
+ ? 'No se detectaron repos con sesiones de IA en esta máquina.'
510
+ : `${repoCount} ${repoCount === 1 ? 'repo' : 'repos'} con sesiones de IA detectado${repoCount === 1 ? '' : 's'}` +
511
+ (unassigned > 0
512
+ ? `; ${unassigned} ${unassigned === 1 ? 'sesión' : 'sesiones'} sin repo atribuible, excluida${unassigned === 1 ? '' : 's'}.`
513
+ : '.'),
514
+ reposFlagUnmatched: (list) => `Repos no encontrados, ignorados: ${list}.`,
515
+ reposScopeCapped: (shown, total) =>
516
+ `Evaluación de toda la máquina limitada a ${shown} de ${total} repos detectados (los más activos primero).`,
517
+ reposScopeAll: 'Evaluando todo el uso de IA de la máquina.',
518
+ synthesizingLabel: 'Sintetizando agentes con IA…',
519
+ // Roadmap personalization (talents-ai-score, ADR-015): reuses the
520
+ // same spinner mechanism as synthesizingLabel above.
521
+ personalizingRoadmapLabel: 'Personalizando roadmap…',
522
+ // ADR-016: agent definition-quality evaluation (ephemeral LLM call).
523
+ evaluatingAgentsLabel: 'Evaluando la calidad de tus agentes…',
524
+ // "Construir el siguiente nivel ahora" (issue 021): now a SECONDARY, opt-in alternative — the copyable implementation prompt (below) is the PRIMARY "how do I implement this" path.
525
+ buildNextLevelHint: 'Alternativamente, ejecuta `usage --build-next-level` para generar el fichero de partida directamente en tu proyecto.',
526
+ // Ayuda localizada (skill-code-certification / ADR-003): antes estaba hardcodeada en español en bin/report.js; ahora pasa por i18n y respeta la locale de la máquina.
527
+ help:
528
+ '\nShakers — perfil local de uso de herramientas de IA\n\n'
529
+ + 'Uso:\n usage [opciones]\n\n'
530
+ + 'Opciones:\n'
531
+ + ' --json Imprime el informe en JSON por stdout\n'
532
+ + ' --no-save No guarda el estado del informe (solo muestra)\n'
533
+ + ' --root DIR Escanea DIR en vez del directorio actual\n'
534
+ + ' --repos LIST Evalúa solo estos repos (ids o rutas, separados por comas)\n'
535
+ + ' --machine Evalúa toda la máquina (todos los repos con actividad de IA)\n'
536
+ + ' --repo Evalúa solo el repo actual\n'
537
+ + ' --scope machine|repo Igual que --machine / --repo (salta el selector)\n'
538
+ + ' --all-repos Alias de --machine\n'
539
+ + ' --roadmap Muestra los pasos para subir tu nivel de uso de IA (oculto por defecto)\n'
540
+ + ' --build-next-level Genera el starter del siguiente tier (alternativa secundaria)\n'
541
+ + ' --force Junto a --build-next-level, sobrescribe un fichero existente\n'
542
+ + ' --lang es|en Fuerza el idioma (informe + prompt) en vez de detectarlo del sistema\n'
543
+ + ' --consent-status Muestra tu decisión de guardado / correo / último envío\n'
544
+ + ' --consent-revoke Revoca el guardado (→ denegado); deja de enviar\n'
545
+ + ' --consent-reset Borra la decisión (→ sin decidir); vuelve a preguntar\n'
546
+ + ' --consent-email C Cambia el correo guardado, sin tocar la decisión\n'
547
+ + ' --set-endpoint URL Guarda el endpoint de envío de Shakers en\n'
548
+ + ' ~/.config/shakers/config.json (un host no-local debe ser\n'
549
+ + ' https). La env var tiene prioridad\n'
550
+ + ' --show-endpoint Muestra el endpoint efectivo y de dónde sale\n'
551
+ + ' -h, --help Muestra esta ayuda\n\n'
552
+ + 'El informe se genera y se muestra SIEMPRE en tu equipo. usage ya NO imprime\n'
553
+ + 'un enlace: usa el comando `report` para generar y abrir el informe HTML completo\n'
554
+ + '(uso de IA + Skills certificadas) que puedes compartir con tu equipo. Antes de\n'
555
+ + 'mostrar el resultado, la primera vez se te pregunta si quieres GUARDARLO en Shakers\n'
556
+ + '(con tu correo); se pregunta una sola vez. Reabre la pregunta con --consent-reset.\n\n'
557
+ + 'El destino de envío se resuelve así: SHAKERS_CLI_INGEST_ENDPOINT (env) > el fichero\n'
558
+ + 'de config (--set-endpoint) > ninguno. Sin endpoint, el informe se muestra pero no se\n'
559
+ + 'envía a Shakers.\n',
560
+ },
561
+ cumulative: {
562
+ title: 'Tu informe de Shakers',
563
+ privacyNote: 'Este informe se genera y se guarda solo en tu equipo. Nada se envía a Shakers salvo que des tu consentimiento explícito.',
564
+ updatedLabel: (when) => `Actualizado: ${when}`,
565
+ unknownProject: '(proyecto desconocido)',
566
+ },
567
+ buildNextLevel: {
568
+ heading: (tierKey) => `Generando el starter para subir a ${tierKey}...`,
569
+ created: (filename) => `+ creado ${filename}`,
570
+ overwritten: (filename) => `+ sobrescrito ${filename} (--force)`,
571
+ skippedExists: (filename) => `${filename} ya existe — no se sobrescribe (usa --force para sobrescribir)`,
572
+ maxTier: 'Ya estás en el tier máximo (T7): no hay siguiente nivel que construir.',
573
+ noFileTarget: 'El siguiente paso no es un fichero que este comando pueda crear — revisa el snippet del roadmap en el informe.',
574
+ unrecognizedTier: 'No se ha podido determinar tu tier actual.',
575
+ },
576
+ legalNotice: {
577
+ label: 'AVISO LEGAL',
578
+ },
579
+ consent: {
580
+ // talents-ai-score, ADR-011: the disclosure wall (what's sent / never sent, itemized) is RETIRED from the CLI — that content now lives in the repo's README.
581
+ persistIntro:
582
+ 'Este informe se ha generado y mostrado en tu equipo, siempre. '
583
+ + 'Guardarlo en Shakers es opcional y revocable en cualquier momento '
584
+ + '(usage --consent-revoke): guarda tu nivel/tier y señales '
585
+ + 'estructuradas derivadas (herramientas, MCP, memoria, '
586
+ + 'automatizaciones, agentes, tecnologías) — nunca el contenido de '
587
+ + 'tus ficheros, prompts, rutas ni credenciales. '
588
+ + 'Esta misma decisión también autoriza, solo si aceptas, que un '
589
+ + 'proveedor externo de observabilidad (Datadog) capture el contenido '
590
+ + 'que envían las llamadas de IA opcionales de este CLI (código '
591
+ + 'muestreado, definiciones de agentes, tus respuestas) — no se puede '
592
+ + 'borrar selectivamente después. Si rechazas, no se captura nada. '
593
+ + 'Dato indicativo, no '
594
+ + 'verificado, no una cualificación oficial. Eres responsable de la '
595
+ + 'información que decidas compartir; Shakers no asume responsabilidad '
596
+ + 'por los datos que envíes. El uso indebido de estas herramientas —enviar '
597
+ + 'o analizar código que no es tuyo o que no estás autorizado a analizar— '
598
+ + 'puede acarrear penalizaciones en tu cuenta de Shakers, incluida la '
599
+ + 'posible suspensión. Consulta el README de este repositorio para '
600
+ + 'más detalle.',
601
+ persistQuestion: '¿Guardar este informe en Shakers? (s/n):',
602
+ invalidAnswer: 'Respuesta no reconocida. Responde "s" (sí) o "n" (no).',
603
+ emailPrompt: 'Introduce tu correo:',
604
+ emailPromptExternal: 'Déjanos tu email para que el equipo de Shakers pueda contactarte (no lo verificamos):',
605
+ invalidEmail: 'Correo no válido, inténtalo de nuevo.',
606
+ notObtained: 'No se ha podido registrar tu respuesta; se te volverá a preguntar la próxima vez.',
607
+ // talents-ai-score, ADR-051: esta pregunta se pide ahora ANTES de cualquier llamada de IA/egress (agent-synthesis, agent-evaluation, personalización de roadmap), no después.
608
+ noReportWithoutConsent: 'Sin tu consentimiento, este informe no incluirá síntesis, evaluación ni roadmap generados por IA esta vez (el resto del informe se muestra igual). Vuelve a ejecutar `usage` y acepta para incluirlos, o usa --consent-reset si quieres que se te pregunte de nuevo.',
609
+ deniedSaved: 'Entendido, no se guardará nada. Puedes cambiar de opinión más adelante volviendo a ejecutar el comando.',
610
+ grantedSaved: (email) => `Gracias. A partir de ahora este informe se guardará automáticamente en Shakers (correo: ${email}, máx. 1 vez por hora).`,
611
+ grantedSavedExternal: (email) => `Gracias. Hemos guardado tu email (${email}) para que el equipo de Shakers pueda contactarte — no lo hemos verificado.`,
612
+ // talents-ai-score, issue 130: un talento logueado NUNCA ve el prompt de email (su cuenta ya prueba su identidad).
613
+ noSessionEmail: 'No hay un correo asociado a tu sesión todavía, así que no se puede guardar el informe esta vez. Se te preguntará de nuevo la próxima ejecución.',
614
+ skipAlreadyDecided: (decision, path) =>
615
+ `Consentimiento ya respondido (${decision === 'granted' ? 'concedido' : 'rechazado'}) — guardado en ${path}. `
616
+ + 'Usa --consent-status para verlo, --consent-revoke para rechazar o --consent-reset para volver a preguntar.',
617
+ nonInteractiveWarning:
618
+ 'Entrada no interactiva (no-TTY) detectada: si no llega ninguna respuesta por stdin, '
619
+ + 'el consentimiento no se guardará esta vez y se te volverá a preguntar la próxima vez.',
620
+ status: {
621
+ heading: 'Estado del consentimiento (guardado en Shakers)',
622
+ decisionGranted: 'Decisión: concedido (granted)',
623
+ decisionDenied: 'Decisión: rechazado (denied)',
624
+ decisionNone: 'Decisión: sin decisión todavía',
625
+ email: (value) => `Correo: ${value || '(sin correo)'}`,
626
+ verificationPending: 'Correo pendiente de verificar: no se enviará nada a Shakers hasta verificarlo (usa --consent-reset para reintentar).',
627
+ emailUnverifiedExternal: 'Correo autoafirmado, sin verificar (contacto para prospección; sí se guarda en Shakers).',
628
+ lastSentAt: (value) => `Último guardado: ${value || '(nunca)'}`,
629
+ },
630
+ revoked: 'Consentimiento revocado. No se guardará nada más automáticamente.',
631
+ reset: 'Decisión de consentimiento reiniciada. Se te preguntará de nuevo en la próxima ejecución.',
632
+ emailChanged: (email) => `Correo actualizado a ${email}. Se usará en el próximo guardado.`,
633
+ emailInvalidCli: 'Correo no válido. Uso: usage --consent-email tu@correo.com',
634
+ },
635
+ // Endpoint config (endpoint-config task): --set-endpoint / --show-endpoint copy.
636
+ endpoint: {
637
+ setOk: (url, path) => `Endpoint de ingesta guardado: ${url}\n (en ${path}). La variable de entorno SHAKERS_CLI_INGEST_ENDPOINT, si está definida, tiene prioridad.`,
638
+ errInsecureRemote: 'Endpoint rechazado: un host que no sea localhost/127.0.0.1 debe usar https:// (el endpoint decide adónde se envía tu código). Usa una URL https o un host local.',
639
+ errInvalidUrl: 'Endpoint rechazado: URL no válida. Debe ser una URL http(s) completa, p.ej. https://tu-hub/api/v1/works/usage/reports',
640
+ errEmpty: 'Endpoint rechazado: no se indicó ninguna URL. Uso: usage --set-endpoint https://tu-hub/api/v1/works/usage/reports',
641
+ showEnv: (url) => `Endpoint de ingesta efectivo: ${url}\n Origen: variable de entorno SHAKERS_CLI_INGEST_ENDPOINT.`,
642
+ showConfigFile: (url, path) => `Endpoint de ingesta efectivo: ${url}\n Origen: fichero de config (${path}).`,
643
+ showConfigInvalid: (path) => `El fichero de config (${path}) tiene un endpoint no válido o inseguro; se ignora. Corrígelo con usage --set-endpoint <url>.`,
644
+ showNone: 'No hay endpoint de ingesta configurado. Define SHAKERS_CLI_INGEST_ENDPOINT o usa usage --set-endpoint <url>. Sin él, el informe se sigue mostrando pero no se envía a Shakers.',
645
+ // talents-ai-score, ADR-020/021: el CLI habla con una CADENA de dos backends — PRIMARIO (servicio de certificaciones, arriba) y FALLBACK (hub-backend, aquí).
646
+ // ADR-042 retired the second backend, so the five `showFallback*`/ `setFallbackOk` strings that explained the chain are gone with it.
647
+ showFallbackRetired: 'Nota: tu config.json todavía tiene `ingestEndpointFallback`. Ya no se usa: el CLI habla con un solo backend. Puedes borrar esa línea a mano; dejarla no afecta a nada.',
648
+ confirmHostPrompt: (host) => `El host "${host}" no es localhost ni un dominio conocido de Shakers. Si continúas, tu código muestreado y tus señales de IA se enviarán ahí.\n Escribe el hostname exacto para confirmar (déjalo en blanco para cancelar):`,
649
+ confirmHostMismatch: 'No coincide con el hostname mostrado. Cancelado: no se ha guardado nada.',
650
+ confirmHostCancelled: 'Cancelado: no se ha guardado nada.',
651
+ },
652
+ backendChain: {
653
+ saved: () => 'Guardado en Shakers.',
654
+ },
655
+ // Why a footprint was NOT sent, in user language; null = stay silent.
656
+ sendStatus: {
657
+ reason: (reason) => {
658
+ switch (reason) {
659
+ case 'throttled':
660
+ return 'No se ha reenviado tu informe: enviaste uno hace menos de una hora. Se enviará de nuevo más tarde.';
661
+ case 'email-unverified':
662
+ return 'No se ha enviado tu informe a Shakers: tu correo aún no está verificado.';
663
+ case 'no-email':
664
+ return 'No se ha enviado tu informe a Shakers: falta tu correo.';
665
+ case 'no-endpoint-configured':
666
+ return 'No se ha enviado tu informe: el envío a Shakers no está configurado.';
667
+ case 'network-error':
668
+ return 'No se pudo enviar tu informe a Shakers (problema de conexión). Inténtalo de nuevo más tarde.';
669
+ case 'rate-limited':
670
+ case 'service-unavailable':
671
+ return 'No se pudo enviar tu informe a Shakers ahora mismo. Inténtalo de nuevo en unos minutos.';
672
+ case 'consent-denied':
673
+ case 'no-decision':
674
+ return null;
675
+ default:
676
+ return 'No se pudo enviar tu informe a Shakers ahora mismo. Inténtalo de nuevo más tarde.';
677
+ }
678
+ },
679
+ },
680
+ // Email-ownership verification (skill-code-certification / ADR-006): the OTP "modo espera" copy, shared by both binaries.
681
+ verify: {
682
+ sent: (email) => `Te enviamos un código de verificación a ${email}.`,
683
+ waitHint: 'Pega aquí el código. Pulsa "r" y Enter para reenviarlo, o Enter en blanco para cancelar.',
684
+ codePrompt: 'Código de verificación:',
685
+ verified: 'Correo verificado. Guardando tu informe en Shakers…',
686
+ invalidCode: 'Código incorrecto. Revísalo y vuelve a pegarlo.',
687
+ expired: 'El código ha caducado. Pulsa "r" y Enter para enviar uno nuevo.',
688
+ resent: (email) => `Te reenviamos un código a ${email}.`,
689
+ resendFailed: 'No se pudo reenviar el código. Inténtalo de nuevo en un momento.',
690
+ requestFailed: 'No se pudo enviar el código de verificación a Shakers. No se guardará el informe; el reporte ya se te ha mostrado.',
691
+ technicalError: 'No se pudo verificar el correo contra Shakers. No se guardará el informe; el reporte ya se te ha mostrado.',
692
+ tooManyAttempts: 'Demasiados intentos fallidos. No se ha verificado el correo, así que no se guardará el informe.',
693
+ cancelled: 'Verificación cancelada. No se guardará el informe (el reporte ya se te ha mostrado).',
694
+ unavailable: 'La verificación de correo no está disponible ahora mismo; no se guardará el informe (el reporte ya se te ha mostrado).',
695
+ },
696
+ // Skill Code Certification (skill-code-certification, issues 004/006).
697
+ // Copy for the SECOND binary `ai-certify` (resolve phase V1). Localized
698
+ // like the consent flow — legal/disclaimer copy must not default to a
699
+ // language the Talent may not read. Vocabulario CONTEXT: Talent, Skill.
700
+ certifyDimension: {
701
+ help:
702
+ 'certify — certifica una DIMENSIÓN de tu rol con una entrevista por LiveKit (como el onboarding).\n\n'
703
+ + 'Uso:\n'
704
+ + ' certify [--lang es|en] [--dimension <clave|slug|n>]\n\n'
705
+ + 'Requiere una sesión activa (ejecuta antes: shakers login).\n'
706
+ + 'Solo se ofrecen las dimensiones que puedes certificar por aquí y que aún no tienes certificadas.\n',
707
+ title: 'Certificar una dimensión',
708
+ loginRequired: '"certify" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
709
+ discovering: 'Buscando las dimensiones que puedes certificar…',
710
+ discoverFailed: (reason) => `No se pudieron obtener tus dimensiones (${reason}). Inténtalo de nuevo en un momento.`,
711
+ unmatchedNote: (keys) => `Aviso: algunas dimensiones no tienen plantilla asociada y no se pueden ofrecer aquí (${keys}).`,
712
+ noneOfferable: 'Ahora mismo no hay dimensiones certificables por aquí. Vuelve a intentarlo cuando tengas alguna disponible.',
713
+ mainRole: (role) => `Tu rol principal: ${role}`,
714
+ selectHeading: 'Elige la dimensión que quieres certificar:',
715
+ selectHint: '↑/↓ para moverte, Enter para elegir, Esc para cancelar.',
716
+ selectPrompt: (count) => `Escribe un número (1-${count}) o Enter para cancelar:`,
717
+ selectNonInteractive: 'Modo no interactivo: indica la dimensión con --dimension <clave|slug|n>.',
718
+ selectNoneChosen: 'No se eligió ninguna dimensión.',
719
+ stateExpired: 'caducada',
720
+ dimensionInvalid: 'Esa dimensión no está entre las certificables ahora mismo.',
721
+ dimensionUsing: (slug) => `Dimensión elegida: ${slug}`,
722
+ creating: 'Creando la entrevista…',
723
+ createFailed: (reason) => `No se pudo crear la entrevista (${reason}).`,
724
+ onCooldown: (day) => (day
725
+ ? `Ya hiciste esta dimensión hace poco. Podrás repetirla a partir del ${day}.`
726
+ : 'Ya hiciste esta dimensión hace poco y todavía no se puede repetir.'),
727
+ needsWeb: 'Esta dimensión lleva un ejercicio que por ahora solo se hace en la web, no por terminal.',
728
+ interviewUnavailable: (reason) => `La entrevista no pudo completarse (${reason}).`,
729
+ reportPolling: 'Calculando tu veredicto…',
730
+ reportTimeout: 'El veredicto todavía se está calculando. Consúltalo en tu perfil en unos minutos.',
731
+ reportFailed: (reason) => `No se pudo obtener el veredicto (${reason}). Consúltalo en tu perfil.`,
732
+ verdictTitle: 'Veredicto',
733
+ verdictDimension: (dim) => `Dimensión: ${dim}`,
734
+ verdictBand: (band) => `Nivel: ${band}`,
735
+ verdictCertified: 'Resultado: certificada',
736
+ verdictNotCertified: 'Resultado: no certificada esta vez',
737
+ done: 'Certificación completada.',
738
+ },
739
+ findProjects: {
740
+ help:
741
+ 'find-projects — lista los proyectos/posiciones disponibles para ti en Shakers.\n\n'
742
+ + 'Uso:\n'
743
+ + ' find-projects [--tab all|saved] [--page N] [--limit N]\n'
744
+ + ' [--attendance remote|hybrid|in-person] [--country ES]\n'
745
+ + ' [--recommended] [--lang es|en] [--json]\n\n'
746
+ + 'Requiere una sesión activa (ejecuta antes: shakers login).\n'
747
+ + 'Ordenadas por match descendente. --page para pasar página; --tab saved para las guardadas.\n'
748
+ + '--recommended muestra solo las que tienen match (filtro local de esta página).\n',
749
+ loginRequired: '"find-projects" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
750
+ title: 'Proyectos disponibles',
751
+ titleSaved: 'Proyectos guardados',
752
+ loading: 'Buscando los proyectos disponibles para ti…',
753
+ fetchFailed: (reason) => `No se pudieron obtener los proyectos (${reason}). Inténtalo de nuevo en un momento.`,
754
+ noneAvailable: 'Ahora mismo no hay proyectos disponibles para ti. Vuelve a mirar más adelante.',
755
+ noneOnPage: 'No hay más proyectos en esta página. Prueba con --page anterior.',
756
+ untitled: 'Sin título',
757
+ companyRestricted: 'empresa reservada',
758
+ hoursPerMonth: (n) => `${n} h/mes`,
759
+ budgetUnknown: 'presupuesto no disponible',
760
+ matchScore: (n) => `match ${n}`,
761
+ savedBadge: 'guardada',
762
+ projectLabel: (name) => `Proyecto: ${name}`,
763
+ attendanceIgnored: 'Aviso: --attendance no válido (usa remote|hybrid|in-person); filtro ignorado.',
764
+ countryIgnored: 'Aviso: --country no válido (usa un código ISO de 2 letras, p. ej. ES); filtro ignorado.',
765
+ showing: (from, to, total) => `Mostrando ${from}–${to} de ${total}`,
766
+ morePages: (next) => `--page ${next} para ver más`,
767
+ recommendedOnPage: (count) => `${count} recomendadas en esta página`,
768
+ recommendedNoneFallback: 'No hay recomendadas para ti ahora mismo; te muestro las disponibles.',
769
+ done: 'Listado completado.',
770
+ },
771
+ showProject: {
772
+ help:
773
+ 'show-project — muestra el detalle de un proyecto/posición.\n\n'
774
+ + 'Uso:\n show-project <número|id> [--lang es|en] [--json]\n\n'
775
+ + 'Requiere una sesión activa (ejecuta antes: shakers login).\n',
776
+ loginRequired: '"show-project" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
777
+ idRequired: 'Falta el id. Uso: show-project <número|id>.',
778
+ refNoCache: 'No hay un listado reciente. Ejecuta primero shakers find-projects y usa el número que aparece (o pasa un id).',
779
+ refOutOfRange: (count) => `Ese número no está en el último listado (hay ${count}). Ejecuta shakers find-projects otra vez.`,
780
+ loading: 'Cargando el detalle…',
781
+ fetchFailed: (reason) => `No se pudo obtener el detalle (${reason}). Inténtalo de nuevo en un momento.`,
782
+ notFound: 'No se encontró ese proyecto/posición.',
783
+ notVisible: 'Ese proyecto no está disponible para ti ahora mismo.',
784
+ untitled: 'Sin título',
785
+ companyRestricted: 'empresa reservada',
786
+ companyLine: (name) => `Empresa: ${name}`,
787
+ projectLine: (name) => `Proyecto: ${name}`,
788
+ hoursPerMonth: (n) => `${n} h/mes`,
789
+ budgetUnknown: 'presupuesto no disponible',
790
+ matchScore: (n) => `match ${n}`,
791
+ skillsLine: (s) => `Skills: ${s}`,
792
+ languagesLine: (s) => `Idiomas: ${s}`,
793
+ savedBadge: 'guardada',
794
+ appliedBadge: 'ya aplicaste',
795
+ canApplyBadge: 'puedes aplicar',
796
+ descriptionHeading: 'Descripción',
797
+ goalsHeading: 'Objetivos',
798
+ faqsHeading: 'Preguntas frecuentes',
799
+ done: 'Detalle completado.',
800
+ },
801
+ savedPositions: {
802
+ helpSave: 'save-project — guarda un proyecto/posición en tus guardados.\n\nUso:\n save-project <número|id> [--json]\n\nRequiere una sesión activa (shakers login).',
803
+ helpUnsave: 'unsave-project — quita un proyecto/posición de tus guardados.\n\nUso:\n unsave-project <número|id> [--json]\n\nRequiere una sesión activa (shakers login).',
804
+ loginRequired: 'Este comando requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
805
+ saveIdRequired: 'Falta el id. Uso: save-project <número|id>.',
806
+ unsaveIdRequired: 'Falta el id. Uso: unsave-project <número|id>.',
807
+ refNoCache: 'No hay un listado reciente. Ejecuta primero shakers find-projects y usa el número que aparece (o pasa un id).',
808
+ refOutOfRange: (count) => `Ese número no está en el último listado (hay ${count}). Ejecuta shakers find-projects otra vez.`,
809
+ saving: 'Guardando…',
810
+ unsaving: 'Quitando de guardados…',
811
+ notFound: 'No se encontró ese proyecto/posición.',
812
+ failed: (reason) => `No se pudo completar (${reason}). Inténtalo de nuevo.`,
813
+ saved: 'Guardado.',
814
+ unsaved: 'Quitado de tus guardados.',
815
+ },
816
+ invitations: {
817
+ help: 'invitations — tus invitaciones a proyectos sin leer.\n\nUso:\n invitations [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
818
+ loginRequired: '"invitations" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
819
+ title: 'Tus invitaciones',
820
+ loading: 'Buscando tus invitaciones…',
821
+ fetchFailed: (reason) => `No se pudieron obtener las invitaciones (${reason}). Inténtalo de nuevo.`,
822
+ none: 'No tienes invitaciones sin leer.',
823
+ untitled: 'Proyecto sin nombre',
824
+ hasChat: 'con chat',
825
+ countNote: (n) => `Total: ${n}.`,
826
+ done: 'Listado completado.',
827
+ },
828
+ certifications: {
829
+ help: 'certifications — tus dimensiones certificadas, sin certificar y caducadas.\n\nUso:\n certifications [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
830
+ loginRequired: '"certifications" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
831
+ title: 'Tus certificaciones',
832
+ loading: 'Cargando tus certificaciones…',
833
+ fetchFailed: (reason) => `No se pudieron obtener las certificaciones (${reason}). Inténtalo de nuevo.`,
834
+ mainRole: (name) => `Rol principal: ${name}`,
835
+ noMainRole: 'Aún no tienes un rol principal asignado.',
836
+ noDimensions: 'No hay dimensiones para tu rol todavía.',
837
+ untitled: 'Dimensión',
838
+ headingCertified: 'Certificadas',
839
+ headingExpired: 'Caducadas',
840
+ headingUncertified: 'Sin certificar',
841
+ done: 'Listado completado.',
842
+ },
843
+ roles: {
844
+ addHelp: 'add-role — añade un rol a tu perfil (rol de crecimiento).\n\nUso:\n add-role [--lang es|en]\n\nRequiere una sesión activa (shakers login).',
845
+ changeHelp: 'change-role — cambia tu rol principal por uno de los que ya tienes.\n\nUso:\n change-role [--lang es|en]\n\nRequiere una sesión activa (shakers login).',
846
+ loginRequired: 'Este comando requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
847
+ selectHint: 'Usa ↑/↓ y Enter para elegir.',
848
+ cancelled: 'Sin cambios.',
849
+ loadingAvailable: 'Cargando roles disponibles…',
850
+ loadingHeld: 'Cargando tus roles…',
851
+ savingAdd: 'Añadiendo el rol…',
852
+ savingMain: 'Guardando tu rol principal…',
853
+ addTitle: 'Añadir un rol',
854
+ addPrompt: '¿Qué rol quieres añadir a tu perfil?',
855
+ noneAvailable: 'Ya tienes todos los roles disponibles.',
856
+ addedOk: (name) => `Rol añadido: ${name}.`,
857
+ alreadyAssigned: (name) => `Ya tenías ese rol: ${name}.`,
858
+ addFailed: (reason) => `No se pudo añadir el rol (${reason}). Inténtalo de nuevo.`,
859
+ changeTitle: 'Cambiar tu rol principal',
860
+ changePrompt: '¿Cuál quieres como rol principal?',
861
+ currentMain: (name) => `Rol principal actual: ${name}`,
862
+ noOtherRoles: 'No tienes otros roles para elegir. Añade uno primero con: shakers add-role',
863
+ mainRoleTitle: 'Tu rol principal',
864
+ mainRolePrompt: 'Elige tu rol principal',
865
+ mainRolePending: 'Todavía estamos preparando tus roles. Podrás fijar tu rol principal más tarde con: shakers change-role',
866
+ mainRoleSkipped: 'De acuerdo, no cambiamos tu rol principal.',
867
+ mainRoleUnchanged: (name) => `Tu rol principal sigue siendo: ${name}.`,
868
+ mainSetOk: (name) => `Rol principal: ${name}.`,
869
+ setMainFailed: (reason) => `No se pudo fijar el rol principal (${reason}). Inténtalo de nuevo.`,
870
+ },
871
+ profile: {
872
+ help: 'me (alias profile) — tu resumen: rol, tarifa y tu trabajo con IA.\n\nUso:\n me [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
873
+ loginRequired: '"me" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
874
+ title: 'Tu perfil',
875
+ loading: 'Cargando tu resumen…',
876
+ fetchFailed: (reason) => `No se pudo obtener tu resumen (${reason}). Inténtalo de nuevo.`,
877
+ headline: (h) => `Titular: ${h}`,
878
+ role: (name) => `Rol principal: ${name}`,
879
+ freelanceType: (t) => `Modalidad: ${t}`,
880
+ completion: (pct) => `Perfil completado: ${pct}%`,
881
+ rate: (v) => `Tarifa por proyecto: ${v}`,
882
+ rateNotSet: 'sin definir',
883
+ availability: (open, hours) => `Disponibilidad: ${open} · ${hours} h/mes`,
884
+ availYes: 'abierto a trabajar',
885
+ availNo: 'no disponible',
886
+ availUnknown: 'sin definir',
887
+ languages: (codes) => `Idiomas: ${codes}`,
888
+ skillsCount: (n) => `Skills: ${n}`,
889
+ agentsCount: (n) => `Agentes: ${n}`,
890
+ cell: (c) => `Celda de fluidez con IA: ${c}`,
891
+ setup: (tier, level) => `Setup: ${tier} (${level})`,
892
+ usage: (level) => `Uso: ${level}`,
893
+ aiNative: 'AI-native',
894
+ visionHeading: 'Tu visión sobre la IA',
895
+ howIWorkHeading: 'Cómo trabajas con IA',
896
+ noAiProfile: 'Aún no tienes perfil de trabajo con IA: se construye a partir de tu evaluación de AI-usage. Ejecuta `shakers ai-usage` para generarlo y vuelve a ejecutar esto.',
897
+ done: 'Resumen completado.',
898
+ },
899
+ applications: {
900
+ help: 'applications — los proyectos a los que has aplicado y su estado.\n\nUso:\n applications [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
901
+ loginRequired: '"applications" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
902
+ title: 'Tus candidaturas',
903
+ loading: 'Buscando tus candidaturas…',
904
+ fetchFailed: (reason) => `No se pudieron obtener tus candidaturas (${reason}). Inténtalo de nuevo.`,
905
+ none: 'Aún no has aplicado a ningún proyecto.',
906
+ untitled: 'Proyecto sin nombre',
907
+ statusLine: (s) => `Estado: ${s}`,
908
+ statusUnknown: 'desconocido',
909
+ countNote: (n) => `Total: ${n}.`,
910
+ done: 'Listado completado.',
911
+ },
912
+ availability: {
913
+ help: 'availability — ver o fijar tu disponibilidad.\n\nUso:\n availability [--lang es|en] [--json] ver\n availability --set [--lang es|en] fijar (interactivo, con confirmación)\n\nRequiere una sesión activa (shakers login).',
914
+ loginRequired: '"availability" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
915
+ title: 'Tu disponibilidad',
916
+ loading: 'Cargando tu disponibilidad…',
917
+ fetchFailed: (reason) => `No se pudo obtener tu disponibilidad (${reason}). Inténtalo de nuevo.`,
918
+ notFound: 'Aún no has definido tu disponibilidad.',
919
+ openToWork: (v) => `Abierto a trabajar: ${v}`,
920
+ monthlyHours: (v) => `Horas al mes: ${v}`,
921
+ onlyRemote: (v) => `Solo remoto: ${v}`,
922
+ location: (v) => `Ubicación: ${v}`,
923
+ expires: (v) => `Caduca: ${v}`,
924
+ yes: 'sí',
925
+ no: 'no',
926
+ unknown: 'sin definir',
927
+ done: 'Listado completado.',
928
+ setTitle: 'Fijar tu disponibilidad',
929
+ setNeedsInteractive: 'Fijar la disponibilidad necesita una terminal interactiva (no se puede por tubería). Ejecútalo directamente en tu terminal.',
930
+ askOpenToWork: '¿Estás abierto a trabajar?',
931
+ askMonthlyHours: 'Horas al mes (número, p. ej. 40; Enter para no cambiar):',
932
+ monthlyHoursInvalid: 'Horas no válidas. Escribe solo el número (p. ej. 40).',
933
+ askOnlyRemote: '¿Solo trabajo remoto?',
934
+ setDiff: (open, hours, remote) => `Nuevo: abierto=${open} · ${hours} h/mes · solo remoto=${remote}`,
935
+ setConfirm: 'Esto actualiza tu perfil. ¿Confirmas? (s/n):',
936
+ setCancelled: 'Cancelado. No se cambió tu disponibilidad.',
937
+ setSaving: 'Guardando tu disponibilidad…',
938
+ setFailed: (reason) => `No se pudo guardar la disponibilidad (${reason}). Inténtalo de nuevo.`,
939
+ setDone: 'Disponibilidad actualizada.',
940
+ },
941
+ mcpInstall: {
942
+ title: 'Conectando el MCP de Shakers',
943
+ clients: { claudeDesktop: 'Claude Desktop', claudeCode: 'Claude Code', cursor: 'Cursor', geminiCli: 'Gemini CLI', codex: 'Codex (y ChatGPT en modo Codex)', vscode: 'VS Code', windsurf: 'Windsurf' },
944
+ status: {
945
+ configured: 'conectado',
946
+ unchanged: 'ya estaba conectado',
947
+ 'not-found': 'no instalado en esta máquina',
948
+ 'invalid-json': 'su configuración no es JSON válido; no la he tocado',
949
+ failed: 'no se pudo conectar',
950
+ },
951
+ removedLegacy: 'Sustituye a la entrada antigua "shakers-ai-usage".',
952
+ found: (apps) => `He encontrado: ${apps}.`,
953
+ confirm: '¿Conecto Shakers a ellas para que puedas usarlo desde tu IA? [S/n]',
954
+ declined: 'No he tocado nada. Para conectarlo más tarde, vuelve a ejecutar el instalador.',
955
+ noTerminal: 'No hay una terminal donde preguntarte, así que no he tocado nada. Vuelve a ejecutar el instalador en tu terminal para conectarlo.',
956
+ restart: 'Reinicia las apps que estén abiertas para que carguen Shakers.',
957
+ noneFound: 'No he encontrado ninguna app de IA compatible en esta máquina.',
958
+ },
959
+ rate: {
960
+ help: 'rate — ver o fijar tu tarifa por proyecto.\n\nUso:\n rate [--lang es|en] [--json] ver tu tarifa\n rate --set [--lang es|en] fijarla (interactivo, con confirmación)\n\nRequiere una sesión activa (shakers login).',
961
+ loginRequired: '"rate" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
962
+ title: 'Tu tarifa por proyecto',
963
+ loading: 'Cargando tu tarifa…',
964
+ fetchFailed: (reason) => `No se pudo obtener la tarifa (${reason}). Inténtalo de nuevo.`,
965
+ notFound: 'Aún no has definido tu tarifa.',
966
+ notSet: 'sin definir',
967
+ fullTime: (v) => `Proyecto a jornada completa: ${v}`,
968
+ partTime: (v) => `Proyecto a media jornada: ${v}`,
969
+ done: 'Listado completado.',
970
+ setTitle: 'Fijar tu tarifa por proyecto',
971
+ setNeedsInteractive: 'Fijar la tarifa necesita una terminal interactiva (no se puede por tubería). Ejecútalo directamente en tu terminal.',
972
+ setWhich: '¿Qué modalidad quieres fijar?',
973
+ modalityFull: 'jornada completa',
974
+ modalityPart: 'media jornada',
975
+ setAmount: (label) => `Importe para ${label} (solo el número):`,
976
+ setAmountInvalid: 'Importe no válido. Debe ser un número entre 0 y 999999.99.',
977
+ setCurrency: 'Moneda:',
978
+ setDiff: (label, oldV, newV) => `${label}: ${oldV} → ${newV}`,
979
+ setConfirm: 'Esto cambia tu precio. ¿Confirmas? (s/n):',
980
+ setCancelled: 'Cancelado. No se cambió tu tarifa.',
981
+ setSaving: 'Guardando tu tarifa…',
982
+ setFailed: (reason) => `No se pudo guardar la tarifa (${reason}). Inténtalo de nuevo.`,
983
+ setDone: (label, newV) => `Tarifa de ${label} actualizada: ${newV}.`,
984
+ },
985
+ lang: {
986
+ help: 'lang — ver o fijar tus idiomas y nivel.\n\nUso:\n lang [--lang es|en] [--json] ver\n lang --set [--lang es|en] añadir/actualizar (interactivo, con confirmación)\n\nRequiere una sesión activa (shakers login).',
987
+ loginRequired: '"lang" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
988
+ title: 'Tus idiomas',
989
+ loading: 'Cargando tus idiomas…',
990
+ loadingCatalog: 'Cargando el catálogo de idiomas…',
991
+ fetchFailed: (reason) => `No se pudieron obtener tus idiomas (${reason}). Inténtalo de nuevo.`,
992
+ catalogFailed: (reason) => `No se pudo cargar el catálogo de idiomas (${reason}). Inténtalo de nuevo.`,
993
+ none: 'Aún no has añadido idiomas.',
994
+ line: (name, level) => `${name}: ${level}`,
995
+ unknownLang: 'idioma',
996
+ unknownLevel: 'sin nivel',
997
+ levelLabel: (v) => ({ NATIVE: 'nativo', ADVANCED: 'avanzado', INTERMEDIATE: 'intermedio', INTERMEDIATE_WRITTEN: 'intermedio (escrito)' }[v] || v),
998
+ done: 'Listado completado.',
999
+ setTitle: 'Fijar un idioma',
1000
+ setNeedsInteractive: 'Fijar idiomas necesita una terminal interactiva (no se puede por tubería). Ejecútalo directamente en tu terminal.',
1001
+ pickLanguage: 'Elige el idioma:',
1002
+ pickLevel: 'Elige el nivel:',
1003
+ setDiff: (name, level) => `Nuevo: ${name} → ${level}`,
1004
+ setConfirm: 'Esto actualiza tus idiomas. ¿Confirmas? (s/n):',
1005
+ setCancelled: 'Cancelado. No se cambiaron tus idiomas.',
1006
+ setSaving: 'Guardando tus idiomas…',
1007
+ setFailed: (reason) => `No se pudieron guardar los idiomas (${reason}). Inténtalo de nuevo.`,
1008
+ setDone: (name) => `Idioma actualizado: ${name}.`,
1009
+ },
1010
+ socials: {
1011
+ help: 'socials — ver o fijar tus enlaces sociales.\n\nUso:\n socials [--lang es|en] [--json] ver\n socials --set [--lang es|en] añadir/actualizar/quitar (interactivo, con confirmación)\n\nRequiere una sesión activa (shakers login).',
1012
+ loginRequired: '"socials" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
1013
+ title: 'Tus enlaces sociales',
1014
+ loading: 'Cargando tus enlaces…',
1015
+ fetchFailed: (reason) => `No se pudieron obtener tus enlaces (${reason}). Inténtalo de nuevo.`,
1016
+ none: 'Aún no has añadido enlaces sociales.',
1017
+ line: (label, url) => `${label}: ${url}`,
1018
+ networkLabel: (k) => ({ linkedin: 'LinkedIn', github: 'GitHub', website: 'Web', twitter: 'Twitter/X', instagram: 'Instagram', facebook: 'Facebook', dribbble: 'Dribbble', behance: 'Behance' }[k] || k),
1019
+ done: 'Listado completado.',
1020
+ setTitle: 'Fijar un enlace social',
1021
+ setNeedsInteractive: 'Fijar enlaces necesita una terminal interactiva (no se puede por tubería). Ejecútalo directamente en tu terminal.',
1022
+ pickNetwork: 'Elige la red:',
1023
+ askUrl: (label) => `URL de ${label} (vacío para quitarlo):`,
1024
+ setDiffSet: (label, url) => `Nuevo: ${label} → ${url}`,
1025
+ setDiffClear: (label) => `Quitar: ${label}`,
1026
+ setConfirm: 'Esto actualiza tus enlaces. ¿Confirmas? (s/n):',
1027
+ setCancelled: 'Cancelado. No se cambiaron tus enlaces.',
1028
+ setSaving: 'Guardando tus enlaces…',
1029
+ setFailed: (reason) => `No se pudieron guardar los enlaces (${reason}). Inténtalo de nuevo.`,
1030
+ setDone: (label) => `${label} actualizado.`,
1031
+ setCleared: (label) => `${label} eliminado.`,
1032
+ },
1033
+ experiences: {
1034
+ help: 'experiences — tus experiencias laborales (solo lectura).\n\nUso:\n experiences [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
1035
+ loginRequired: '"experiences" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
1036
+ title: 'Tus experiencias',
1037
+ loading: 'Cargando tus experiencias…',
1038
+ fetchFailed: (reason) => `No se pudieron obtener tus experiencias (${reason}). Inténtalo de nuevo.`,
1039
+ none: 'Aún no has añadido experiencias.',
1040
+ untitled: 'Sin título',
1041
+ current: 'actualidad',
1042
+ datesLabel: (r) => `Fechas: ${r}`,
1043
+ locationLabel: (l) => `Ubicación: ${l}`,
1044
+ skillsLabel: (s) => `Skills: ${s}`,
1045
+ countNote: (n) => `Total: ${n}.`,
1046
+ done: 'Listado completado.',
1047
+ },
1048
+ portfolios: {
1049
+ help: 'portfolios — tus piezas de portfolio (solo lectura).\n\nUso:\n portfolios [--lang es|en] [--json]\n\nRequiere una sesión activa (shakers login).',
1050
+ loginRequired: '"portfolios" requiere una sesión activa.\n Falta: iniciar sesión. Ejecuta primero: shakers login',
1051
+ title: 'Tu portfolio',
1052
+ loading: 'Cargando tu portfolio…',
1053
+ fetchFailed: (reason) => `No se pudo obtener tu portfolio (${reason}). Inténtalo de nuevo.`,
1054
+ none: 'Aún no has añadido piezas de portfolio.',
1055
+ untitled: 'Sin título',
1056
+ current: 'en curso',
1057
+ datesLabel: (r) => `Fechas: ${r}`,
1058
+ locationLabel: (l) => `Ubicación: ${l}`,
1059
+ skillsLabel: (s) => `Skills: ${s}`,
1060
+ countNote: (n) => `Total: ${n}.`,
1061
+ done: 'Listado completado.',
1062
+ },
1063
+ alma: {
1064
+ help: 'alma — mini-shell interactivo para conversar con Alma, tu asistente de IA de Shakers.\n\nUso:\n alma abre la conversación (escribe línea a línea; "salir" para terminar)\n alma --lang es|en\n\nRequiere una sesión activa (shakers login). La conversación mantiene el contexto entre turnos.',
1065
+ loginRequired: '`alma` necesita una sesión activa. Inicia sesión: shakers login',
1066
+ needTty: '`alma` es interactivo y necesita una terminal.',
1067
+ title: 'Alma',
1068
+ tagline: 'tu asistente de IA de Shakers',
1069
+ replHint: 'Escribe tu mensaje y pulsa Enter. Escribe "salir" (o Enter en vacío) para terminar.',
1070
+ prompt: 'tú:',
1071
+ contextLabel: 'Leyendo tu proyecto…',
1072
+ contextConsentHeader: 'Alma puede usar un mapa (sin código) de este proyecto para responder mejor. ¿Lo compartimos?',
1073
+ contextConsentHint: 'Solo estructura: nombre, servicios, agentes, tecnologías. Nunca el código.',
1074
+ contextConsentYes: 'Sí, compartir el contexto del proyecto',
1075
+ contextConsentNo: 'No, chatear sin contexto',
1076
+ contextConsentDeclined: 'De acuerdo, hablaré con Alma sin el contexto del proyecto.',
1077
+ thinking: 'Alma pensando…',
1078
+ bye: 'Hasta luego.',
1079
+ confirmPrompt: '¿Confirmas esta acción?',
1080
+ confirmYes: 'Sí, adelante',
1081
+ confirmNo: 'No, cancela',
1082
+ irreversible: '⚠ Esta acción es irreversible.',
1083
+ webLink: (to) => `En la web esto abriría: ${to}`,
1084
+ failed: (reason) => `No se pudo hablar con Alma (${reason}). Inténtalo de nuevo en un momento.`,
1085
+ },
1086
+ certify: {
1087
+ // Aviso legal (ADR-001): asume el proyecto propiedad del Talent y le
1088
+ // atribuye la responsabilidad. Aceptación explícita obligatoria.
1089
+ disclaimer:
1090
+ 'AVISO LEGAL — léelo antes de continuar:\n'
1091
+ + ' certify envía datos de tu proyecto a Shakers para certificar tus Skills TÉCNICAS\n'
1092
+ + ' (las de tu catálogo, a partir de tu código). No evalúa cómo orquestas agentes.\n'
1093
+ + ' En esta fase (resolve) se envían tu correo y los NOMBRES de las tecnologías\n'
1094
+ + ' detectadas; la fase de certificación posterior enviará fragmentos de código.\n'
1095
+ + ' Eres el ÚNICO responsable de asegurarte de que eres propietario del código de\n'
1096
+ + ' este proyecto o de que estás autorizado a analizarlo. Shakers no asume ninguna\n'
1097
+ + ' responsabilidad por el código que envíes. Enviar código que no es tuyo o que\n'
1098
+ + ' no estás autorizado a analizar es un uso indebido de esta herramienta y puede\n'
1099
+ + ' acarrear penalizaciones en tu cuenta de Shakers, incluida la posible suspensión.\n'
1100
+ + ' NO uses esta herramienta sobre código de un tercero (p. ej. un cliente bajo NDA).\n'
1101
+ + ' Las notas son indicativas y no verificadas, no una cualificación oficial.',
1102
+ disclaimerQuestion: '¿Aceptas y continúas? (s/n):',
1103
+ disclaimerAcceptedFlag: 'Aviso legal aceptado mediante --accept-disclaimer.',
1104
+ disclaimerNonInteractive:
1105
+ 'Entrada no interactiva y sin --accept-disclaimer: no se puede obtener una '
1106
+ + 'aceptación explícita. Se cancela (no se ha enviado nada).',
1107
+ disclaimerDeclined: 'No has aceptado el aviso legal. No se ha enviado nada.',
1108
+ disclaimerInvalidAnswer: 'Respuesta no reconocida. Responde "s" (sí) o "n" (no).',
1109
+ disclaimerNoAnswer: 'No se ha obtenido respuesta. No se ha enviado nada.',
1110
+ // Certify report (terminal + HTML). Rúbrica anclada + agregación determinista (ADR-024).
1111
+ report: {
1112
+ heading: 'Resultado de certificación de Skills TÉCNICAS',
1113
+ noInterviewNote: 'Preliminar, sin entrevista: la certificación combinada de esta Skill requiere completar su entrevista.',
1114
+ disclaimer:
1115
+ 'Nota: el nivel se determina con una rúbrica anclada y una fórmula fija, por lo que '
1116
+ + 'el cálculo es determinista: con las mismas valoraciones sale el mismo nivel. Solo '
1117
+ + 'los juicios por criterio del modelo pueden variar ligeramente entre ejecuciones. Es '
1118
+ + 'una valoración indicativa, no una certificación oficial de cara al Client.',
1119
+ partialSampleWarning:
1120
+ 'Muestra parcial: por los límites de tamaño la valoración se basa en una muestra del '
1121
+ + 'código, no en todo el proyecto.',
1122
+ // ADR-016 skill levels (by decision power, NOT points) — replace the
1123
+ // numeric grade in the skill output. `key` from skillLevelForScore.
1124
+ levelLine: (label) => `Nivel: ${label}`,
1125
+ skillLevels: {
1126
+ middle: 'Middle',
1127
+ senior: 'Senior',
1128
+ expert: 'Expert',
1129
+ na: '—',
1130
+ },
1131
+ // ADR-024 rubric dimensions.
1132
+ dimensionsLabel: 'Dimensiones',
1133
+ dimensionNA: 'N/A',
1134
+ dimensionLabels: {
1135
+ idiomatic: 'Uso idiomático',
1136
+ correctness: 'Corrección y robustez',
1137
+ depth: 'Profundidad',
1138
+ structure: 'Estructura y mantenibilidad',
1139
+ testing: 'Tests',
1140
+ },
1141
+ rationaleLabel: 'Por qué',
1142
+ improvementsLabel: 'Mejoras sugeridas',
1143
+ // ADR-025 authorship receipt (atribución, NO prueba criptográfica).
1144
+ receipt: {
1145
+ label: 'Autoría',
1146
+ repoLabel: 'Repo',
1147
+ commitRangeLabel: 'Rango de commits',
1148
+ filesLabel: 'Fichero',
1149
+ authorLabel: 'Autor (git)',
1150
+ confirmedLabel: 'Autores confirmados con la identidad',
1151
+ attributedYes: '✓',
1152
+ attributedNo: '✗',
1153
+ summary: (attributed, total) => `${attributed}/${total} ficheros atribuidos a la identidad`,
1154
+ note:
1155
+ 'Traza de autoría basada en el autor de git (self-asserted); no es una prueba '
1156
+ + 'criptográfica de autoría.',
1157
+ },
1158
+ sampleSummary: (included, candidate, estTokens) =>
1159
+ `Muestra: ${included}/${candidate} ficheros · ~${estTokens} tokens`,
1160
+ partialTag: '(muestra parcial)',
1161
+ notCertified: 'No se ha podido certificar esta Skill en esta ejecución.',
1162
+ notSampleableNote: (technology) =>
1163
+ `No hay muestreo definido para la tecnología "${technology}": todavía no se puede certificar por código.`,
1164
+ htmlTitle: 'Certificación de Skills técnicas · Shakers',
1165
+ noItems: 'No hay resultados de certificación que mostrar.',
1166
+ // Coste (issue 012): input mayor = más € por run.
1167
+ costNote:
1168
+ 'Nota de coste: se analiza bastante código por Skill (hasta ~150k tokens/Skill), '
1169
+ + 'lo que tiene un coste por ejecución.',
1170
+ // Prompt de remediación (issue 011): generado en local desde las mejoras.
1171
+ remediationHeading: 'Prompt para aplicar las mejoras',
1172
+ remediationHint: 'Copia este prompt y pégalo en tu herramienta de IA (Claude Code, Cursor…) para aplicar las mejoras.',
1173
+ remediationIntro: (skillName, technology) =>
1174
+ `Ayúdame a mejorar mi código de ${skillName}${technology ? ` (${technology})` : ''} en este proyecto. `
1175
+ + 'Una revisión de código señaló estas mejoras:',
1176
+ remediationClosing:
1177
+ 'Aplícalas directamente en mi proyecto: crea o edita lo necesario, sigue las convenciones que ya uso '
1178
+ + 'y explícame brevemente qué has cambiado y por qué.',
1179
+ remediationCopyLabel: 'Copiar',
1180
+ remediationCopiedLabel: 'Copiado ✓',
1181
+ },
1182
+ },
1183
+ // Branded mini-shell chrome (skill-code-certification / ADR-014).
1184
+ login: {
1185
+ intro: 'Inicia sesión con tu email y contraseña de Shakers.',
1186
+ sessionExpiredIntro: 'Tu sesión anterior caducó. Vuelve a iniciar sesión con tu email y contraseña.',
1187
+ alreadyLoggedIn: 'Ya has iniciado sesión. Usa `logout` si quieres cambiar de cuenta.',
1188
+ emailPrompt: 'Email:',
1189
+ passwordPrompt: 'Contraseña:',
1190
+ emailInvalid: 'Email no válido. Inténtalo de nuevo.',
1191
+ needInput: 'Se necesitan email y contraseña para iniciar sesión.',
1192
+ success: 'Sesión iniciada. Tu informe ya no incluye el roadmap ni las sugerencias de mejora: tu nivel sale de certificarte.',
1193
+ expiresAt: (iso) => `La sesión caduca: ${iso}. Cuando expire te lo diré y podrás volver a entrar con \`login\`.`,
1194
+ loggedOut: 'Sesión cerrada. Vuelves a ver el informe completo, con roadmap y sugerencias.',
1195
+ notLoggedIn: 'No había ninguna sesión iniciada. No hay nada que cerrar.',
1196
+ errorInvalidCredentials: 'Credenciales incorrectas: revisa tu email y contraseña e inténtalo otra vez.',
1197
+ errorNoEmailPassword: 'Esta cuenta existe pero no tiene contraseña de email: la registraste con Google o LinkedIn. Si fue con Google, usa `login --google`. Si fue con LinkedIn, entra desde la web para añadir una contraseña por ahora.',
1198
+ errorUpstream: 'El servidor de identidad de Shakers no responde ahora mismo. No es tu contraseña; inténtalo de nuevo en un rato.',
1199
+ errorUnreachable: 'No se pudo conectar con Shakers. Comprueba tu conexión y vuelve a ejecutar `login`.',
1200
+ errorConfig: 'La dirección del servidor no es válida. Revisa la configuración del endpoint (`usage --show-endpoint`).',
1201
+ errorNoEndpoint: 'No hay endpoint configurado. login usa la misma base que usage: define SHAKERS_CLI_INGEST_ENDPOINT o usa `usage --set-endpoint <url>`.',
1202
+ errorPersist: 'El inicio de sesión funcionó, pero no se pudo guardar la sesión en disco. Revisa los permisos de ~/.config/shakers y vuelve a intentarlo.',
1203
+ errorGeneric: 'No se pudo iniciar sesión. Inténtalo de nuevo.',
1204
+ // ADR-046 — login con Google por loopback (`login --google`).
1205
+ googleIntro: 'Inicia sesión con Google. Se abrirá tu navegador para autorizar; al terminar, vuelve aquí.',
1206
+ googleOpening: 'Abriendo el navegador para iniciar sesión con Google…',
1207
+ googlePasteUrl: 'Si el navegador no se abre solo, pega esta URL en él:',
1208
+ googleWaiting: 'Esperando a que completes el acceso en el navegador…',
1209
+ googleErrorConfigFailed: 'No se pudo obtener la configuración de login de Google desde Shakers. Comprueba tu conexión y el endpoint (`usage --show-endpoint`) e inténtalo de nuevo.',
1210
+ googleErrorConfigInvalid: 'La configuración de Google que devolvió Shakers está incompleta. Es un problema del servidor, no tuyo; inténtalo más tarde.',
1211
+ googleErrorPortInUse: 'El puerto local que usa el login de Google está ocupado por otro proceso. Ciérralo y vuelve a ejecutar `login --google`.',
1212
+ googleErrorTimeout: 'Se agotó el tiempo esperando a que completaras el acceso en el navegador. Vuelve a ejecutar `login --google` cuando estés listo.',
1213
+ googleErrorState: 'La respuesta del navegador no coincidió con lo esperado (posible interferencia). Por seguridad no se inició sesión; inténtalo de nuevo.',
1214
+ googleErrorDenied: 'Cancelaste el acceso en Google (o no se concedió). No se inició sesión.',
1215
+ googleErrorGeneric: 'No se pudo iniciar sesión con Google. Inténtalo de nuevo.',
1216
+ // Google por device flow (RFC 8628): la config de Google la tiene el Hub; abres una URL y escribes un código.
1217
+ deviceIntro: (name) => `Inicia sesión con ${name}. Abre el enlace de abajo e introduce el código para autorizar; luego vuelve aquí.`,
1218
+ deviceVisit: 'Abre esta página en tu navegador:',
1219
+ deviceCodeLabel: (code) => `e introduce este código: ${code}`,
1220
+ deviceWaiting: 'Esperando a que autorices en el navegador…',
1221
+ deviceErrorExpired: 'La solicitud de acceso con Google caducó antes de autorizarse. Vuelve a ejecutar `login --google` cuando estés listo.',
1222
+ deviceErrorDenied: 'Se canceló el acceso con Google (o no se concedió). No se inició sesión.',
1223
+ deviceErrorGeneric: 'No se pudo iniciar sesión con Google. Inténtalo de nuevo.',
1224
+ chooseMethodHeading: '¿Cómo quieres iniciar sesión?',
1225
+ methodEmail: 'Email y contraseña',
1226
+ methodGoogle: 'Google',
1227
+ methodLinkedin: 'LinkedIn',
1228
+ selectHint: 'Flechas ↑/↓ para moverte · enter para elegir · esc para cancelar',
1229
+ chooseMethodPrompt: (max) => `Número (1-${max}), vacío para cancelar: `,
1230
+ },
1231
+ // `start` (talents-ai-score, ADR-054/055): la herramienta para un talento YA registrado en Shakers que quiere completar su perfil agéntico.
1232
+ start: {
1233
+ help:
1234
+ 'start — completa tu perfil de talento agéntico en Shakers: inicia sesión, evalúa tu uso de IA\n'
1235
+ + 'si todavía no lo has hecho, y desde un único menú añade o certifica skills y agentes.\n\n'
1236
+ + 'Uso:\n'
1237
+ + ' start [opciones]\n\n'
1238
+ + 'Opciones:\n'
1239
+ + ' --root DIR Analiza DIR en vez del directorio actual\n'
1240
+ + ' --lang es|en Fuerza el idioma de la salida\n'
1241
+ + ' -h, --help Muestra esta ayuda (no inicia sesión, no escanea nada)\n\n'
1242
+ + 'Requiere que hayas iniciado sesión (o te la pedirá): usa las mismas credenciales de Shakers\n'
1243
+ + 'que `login`. Si es la primera vez que ejecutas la herramienta en este proyecto, se lanza\n'
1244
+ + 'automáticamente la evaluación de uso de IA (`usage`) antes de mostrar el menú.',
1245
+ intro:
1246
+ 'Vamos allá: confirmamos tu sesión y, si es la primera vez en este proyecto, generamos tu evaluación de uso de IA antes de mostrarte el menú.',
1247
+ alreadyLoggedIn: (email) => `Ya has iniciado sesión${email ? ` como ${email}` : ''}.`,
1248
+ loginCancelled: 'No se ha iniciado sesión, así que no podemos continuar. Ejecuta `start` de nuevo cuando quieras entrar.',
1249
+ runningUsageFirst:
1250
+ 'Todavía no tienes un informe de uso de IA para este proyecto — es la base de tu perfil agéntico. Lo generamos primero.',
1251
+ menuHeading: '¿Qué quieres hacer?',
1252
+ menuHint: 'Flechas ↑/↓ para moverte · enter para elegir · esc para cancelar',
1253
+ menuPrompt: (max) => `Número (1-${max}), vacío para cancelar: `,
1254
+ menuRerun: 'Volver a lanzar la evaluación de uso de IA',
1255
+ // dueño (2026-08-12): renombrado — el label decía "Añadir" pero la acción es declarar/subir lo detectado al PERFIL de Shakers, no crear algo nuevo desde cero.
1256
+ menuAddSkills: 'Subir skills a tu perfil',
1257
+ menuAddAgents: 'Subir agentes a tu perfil',
1258
+ // talents-ai-score, ADR-059: "Añadir proyecto al portfolio" — junto a las de skills/agentes, antes de certificar (declarar cosas de tu perfil, luego certificarlas).
1259
+ menuAddPortfolio: 'Añadir proyecto al portfolio',
1260
+ menuCertifySkills: 'Certificar skills',
1261
+ menuCertifyAgents: 'Certificar agentes',
1262
+ menuExit: 'Salir',
1263
+ menuExitDesc: 'Termina esta sesión de `start`. Ejecútalo de nuevo cuando quieras seguir completando tu perfil.',
1264
+ menuRerunDesc:
1265
+ 'Re-escanea tu setup de IA y recalcula tu fluidez con IA. Obtienes tu posición actualizada en el mapa (de Explorer a AI Native) en tu perfil.',
1266
+ menuAddSkillsDesc:
1267
+ 'Detecta las tecnologías de tu proyecto y declara en tu perfil las que aún no tienes. Obtienes más skills en tu perfil (declaradas, sin verificar) → apareces en más búsquedas.',
1268
+ menuAddAgentsDesc:
1269
+ 'Detecta los agentes de IA que has construido y los declara en tu perfil. La IA pre-redacta qué hace y qué decides tú; los editas antes de guardar. Obtienes tus agentes como activos de tu perfil agéntico.',
1270
+ menuAddPortfolioDesc:
1271
+ 'Añade este proyecto a tu portfolio de Shakers: título, tipo, descripción y skills, la mayoría pre-rellenados desde tu código y tu git. Obtienes una pieza más de tu trabajo visible en tu perfil.',
1272
+ menuCertifySkillsDesc:
1273
+ 'Un reto sobre tu propio código valida tu nivel real en una skill. Obtienes la skill certificada en banda Middle/Senior/Expert → credibilidad y más peso en el matching.',
1274
+ menuCertifyAgentsDesc:
1275
+ 'Un reto valida que el agente es real, es tuyo y lo operas con criterio. Obtienes el agente Certified con su tracción → la señal más fuerte de tu perfil agéntico.',
1276
+ menuCancelled: 'Cancelado. No se ha hecho nada.',
1277
+ // dueño (2026-08-12): cierre del BUCLE del menú — usado tanto al elegir "Salir" explícitamente como al cancelar (esc / respuesta vacía) DESDE DENTRO del bucle.
1278
+ menuGoodbye: 'Sesión de `start` terminada. Ejecuta `start` de nuevo cuando quieras seguir completando tu perfil.',
1279
+ addAgentsComingSoon:
1280
+ 'Añadir agentes: próximamente. Tus agentes serán activos de tu perfil agéntico — todavía no existe esa ficha en Shakers, así que de momento no hay dónde guardarlos.',
1281
+ addSkillsNoTechnologies:
1282
+ 'No se reconoció ninguna tecnología en este proyecto (package.json, requirements.txt, go.mod…). No hay skills que añadir desde aquí.',
1283
+ addSkillsErrorNoEndpoint:
1284
+ 'No hay endpoint configurado. `start` usa la misma base que `usage`: define el endpoint con `usage --set-endpoint <url>` (o SHAKERS_CLI_INGEST_ENDPOINT).',
1285
+ addSkillsResolveErrorIntro: 'No se pudo leer tu inventario de AI-usage (si aún no has ejecutado `shakers ai-usage`, hazlo primero):',
1286
+ addSkillsNoneMatched:
1287
+ 'Este paso necesita antes tu evaluación de AI-usage: es la que descubre las skills de tu setup. Ejecuta `shakers ai-usage` y vuelve a ejecutar este comando para añadirlas a tu perfil.',
1288
+ addSkillsNoHubToken:
1289
+ 'Tu sesión no tiene el permiso necesario para añadir skills a tu perfil. Ejecuta `login` de nuevo e inténtalo otra vez.',
1290
+ addSkillsSelectHeading: 'Skills detectadas que aún no están en tu perfil (elige una o varias):',
1291
+ addSkillsSelectHint: 'Flechas ↑/↓ · espacio para marcar · "a" para marcar todas · enter para confirmar · esc para cancelar',
1292
+ addSkillsSelectPrompt: (max) => `Números separados por comas (1-${max}), vacío para cancelar: `,
1293
+ addSkillsNoneChosen: 'No has elegido ninguna skill. No se ha añadido nada a tu perfil.',
1294
+ addSkillsDeclaredOne: (skillName) => `✓ Añadida a tu perfil: ${skillName} (sin verificar).`,
1295
+ addSkillsDeclareFailed: (skillName, reason) => `✗ No se pudo añadir ${skillName}: ${reason}`,
1296
+ // Cierre de la visión (dueño, 2026-08-11): UNA vez, después del lote — no repetido por cada skill (issue 106/097's lección: repetir la misma frase por ítem es ruido, no refuerzo).
1297
+ addSkillsProfileBoost: 'Cada skill que añades suma a tu perfil: apareces en más búsquedas.',
1298
+ addSkillsHubSessionExpired: 'Tu sesión de Shakers ha caducado mientras añadíamos tus skills.',
1299
+ addSkillsOfferRelogin: '¿Quieres volver a iniciar sesión ahora para seguir? (s/n):',
1300
+ addSkillsRelaunchAfterRelogin: 'Sesión renovada. Reintentamos las skills que quedaban pendientes.',
1301
+ addSkillsRelaunchDeclined: 'Vale. Ejecuta `login` y vuelve a elegir "Añadir skills" cuando quieras.',
1302
+ addSkillsReloginFailed: 'No se pudo volver a iniciar sesión. Las skills pendientes no se han añadido.',
1303
+ addSkillsRelateNoPortfolios: 'Todavía no tienes proyectos en tu portfolio, así que las skills se han añadido sin relacionar. Puedes añadir uno con "Añadir proyecto al portfolio" y relacionarlas más tarde.',
1304
+ addSkillsRelateAsk: (skillName) => `¿Quieres relacionar "${skillName}" con una experiencia de tu perfil? (s/n):`,
1305
+ addSkillsRelateSelectHeading: (skillName) => `Relaciona "${skillName}" con una o varias experiencias:`,
1306
+ addSkillsRelateSelectHint: 'Flechas ↑/↓ · espacio para marcar · "a" para marcar todas · enter para confirmar · esc para cancelar',
1307
+ addSkillsRelateSelectPrompt: (max) => `Números separados por comas (1-${max}), vacío para omitir: `,
1308
+ addSkillsRelateSkipped: (skillName) => `"${skillName}" añadida sin relacionar.`,
1309
+ addSkillsRelated: (skillName, portfolioName) => ` ✓ "${skillName}" relacionada con "${portfolioName}".`,
1310
+ addSkillsRelateFailed: (skillName, portfolioName, reason) => ` ✗ No se pudo relacionar "${skillName}" con "${portfolioName}": ${reason}`,
1311
+ // talents-ai-score, ADR-059: "Añadir proyecto al portfolio".
1312
+ portfolioIntro:
1313
+ 'Vamos a añadir este proyecto a tu portfolio de Shakers. La mayoría de los datos vienen pre-rellenados desde tu código y tu git: confirma o edita cada uno.',
1314
+ portfolioErrorNoEndpoint:
1315
+ 'No hay endpoint configurado. `start` usa la misma base que `usage`: define el endpoint con `usage --set-endpoint <url>` (o SHAKERS_CLI_INGEST_ENDPOINT).',
1316
+ portfolioNoHubToken:
1317
+ 'Tu sesión no tiene el permiso necesario para añadir proyectos a tu portfolio. Ejecuta `login` de nuevo e inténtalo otra vez.',
1318
+ portfolioDedupCheckingLabel: 'Comprobando tu portfolio…',
1319
+ portfolioDedupCheckFailed: (reason) => `No se pudo comprobar si ya tienes un proyecto con ese nombre en tu portfolio: ${reason} Seguimos, pero podrías acabar con un duplicado.`,
1320
+ portfolioDuplicateName: (name) => `Ya tienes un proyecto llamado "${name}" en tu portfolio. No se ha creado uno duplicado — vuelve a ejecutar esta opción con otro título si quieres añadir uno distinto.`,
1321
+ // (1) Título ← nombre del directorio, editable.
1322
+ portfolioTitlePrompt: (def) => `Título del proyecto [${def}]:`,
1323
+ // (2) Tipo ← PORTFOLIO/EXPERIENCE, obligatorio. Copy de visión (dueño):
1324
+ // qué significa cada tipo para tu perfil, mostrado SOLO al resaltar.
1325
+ portfolioTypeHeading: '¿Qué tipo de entrada es?',
1326
+ portfolioTypeHint: 'Flechas ↑/↓ para moverte · enter para elegir · esc para cancelar',
1327
+ portfolioTypePrompt: (max) => `Número (1-${max}): `,
1328
+ portfolioTypePortfolio: 'Portfolio',
1329
+ portfolioTypePortfolioDesc:
1330
+ 'Lo añade a tu perfil como una pieza de portfolio: una muestra de tu trabajo que cualquiera que revise tu perfil puede ver.',
1331
+ portfolioTypeExperience: 'Experiencia',
1332
+ portfolioTypeExperienceDesc:
1333
+ 'Lo añade a tu perfil como experiencia profesional (freelance o de plantilla): cuenta en tu trayectoria, no como una pieza para mostrar.',
1334
+ // (3) Descripción ← IA pre-redacta tras consentimiento (ADR-052/059).
1335
+ portfolioDescriptionConsentDisclaimer:
1336
+ 'Para redactar un borrador de la descripción con IA, se enviarán a Shakers: el nombre del proyecto, las tecnologías detectadas y, si existen, el contenido del README y la descripción del package.json.',
1337
+ portfolioDescriptionDraftLabel: 'Borrador generado por IA:',
1338
+ portfolioDescriptionDraftingLabel: 'Redactando un borrador con IA…',
1339
+ // Editable; o free-text si declinas, si falla, o si no hay endpoint.
1340
+ portfolioDescriptionEditPrompt: 'Descripción (edítala o pulsa enter para dejar el borrador de arriba):',
1341
+ portfolioDescriptionFreeTextPrompt: 'Descripción (opcional, pulsa enter para dejarla en blanco):',
1342
+ // (4) URL ← remote git del repo, editable.
1343
+ portfolioUrlPrompt: (def) => (def ? `URL del repositorio [${def}]:` : 'URL del repositorio (opcional, no se detectó ninguna):'),
1344
+ // (5) Skills ← multi-select de las tecnologías detectadas, PRE-MARCADAS (puedes quitar, no añadir: son las que resolvimos a una skill de tu catálogo).
1345
+ portfolioSkillsResolvingLabel: 'Buscando las skills de este proyecto…',
1346
+ portfolioSkillsSelectHeading: 'Skills de este proyecto — se añaden a tu perfil y se muestran en la pieza (pre-marcadas; quita las que no apliquen):',
1347
+ portfolioSkillsSelectHint: 'Flechas ↑/↓ · espacio para marcar/desmarcar · "a" para marcar/desmarcar todas · enter para confirmar · esc para cancelar',
1348
+ portfolioSkillsSelectPrompt: (max) => `Números separados por comas (1-${max}) para cambiar la selección, vacío para dejar todas marcadas: `,
1349
+ portfolioSkillsSelectInvalid: 'No se entendió esa respuesta. Se mantienen todas las skills marcadas.',
1350
+ // (6) Cliente ← opcional, para freelance.
1351
+ portfolioClientPrompt: 'Cliente (opcional, para trabajo freelance; pulsa enter para dejarlo en blanco):',
1352
+ portfolioClientWebsitePrompt: 'Web del cliente (opcional, p.ej. acme.com; para su logo; pulsa enter para omitir):',
1353
+ portfolioCancelled: 'Cancelado. No se ha añadido nada a tu portfolio.',
1354
+ portfolioDeclaring: 'Añadiendo el proyecto a tu portfolio…',
1355
+ portfolioDeclaredSuccess: (name) => `✓ "${name}" añadido a tu portfolio.`,
1356
+ portfolioDeclareFailed: (reason) => `✗ No se pudo añadir el proyecto a tu portfolio: ${reason}`,
1357
+ portfolioHubSessionExpired: 'Tu sesión de Shakers ha caducado mientras añadíamos el proyecto a tu portfolio.',
1358
+ portfolioOfferRelogin: '¿Quieres volver a iniciar sesión ahora para seguir? (s/n):',
1359
+ portfolioRelaunchAfterRelogin: 'Sesión renovada. Reintentamos añadir el proyecto a tu portfolio.',
1360
+ portfolioRelaunchDeclined: 'Vale. Ejecuta `login` y vuelve a elegir "Añadir proyecto al portfolio" cuando quieras.',
1361
+ portfolioReloginFailed: 'No se pudo volver a iniciar sesión. El proyecto no se ha añadido a tu portfolio.',
1362
+ // talents-ai-score Phase 2: "Añadir agente al perfil".
1363
+ agentErrorNoEndpoint:
1364
+ 'No hay endpoint configurado. `start` usa la misma base que `usage`: define el endpoint con `usage --set-endpoint <url>` (o SHAKERS_CLI_INGEST_ENDPOINT).',
1365
+ agentNoHubToken:
1366
+ 'Tu sesión no tiene el permiso necesario para añadir agentes a tu perfil. Ejecuta `login` de nuevo e inténtalo otra vez.',
1367
+ agentNoAgentsDetected:
1368
+ 'Este paso necesita antes tu evaluación de AI-usage: es la que descubre los agentes de IA de tu setup. Ejecuta `shakers ai-usage` y vuelve a ejecutar este comando para añadirlos a tu perfil.',
1369
+ agentInventoryError: (reason) => `No se pudo leer tu inventario de AI-usage: ${reason}. Si aún no lo has ejecutado, corre \`shakers ai-usage\` primero; si no, revisa tu conexión e inténtalo de nuevo.`,
1370
+ agentDedupCheckingLabel: 'Comprobando tus agentes…',
1371
+ agentDedupCheckFailed: (reason) => `No se pudo comprobar si ya tienes un agente con ese nombre en tu perfil: ${reason} Seguimos, pero podrías acabar con un duplicado.`,
1372
+ agentDuplicateName: (name) => `Ya tienes un agente llamado "${name}" en tu perfil. No se ha creado uno duplicado — vuelve a ejecutar esta opción con otro nombre si quieres añadir uno distinto.`,
1373
+ agentAlreadyAdded: (name) => `Ya tienes el agente "${name}" en tu perfil; puedes relacionarlo con tus experiencias.`,
1374
+ agentIntro:
1375
+ 'Vamos a añadir uno de tus agentes a tu perfil de Shakers. Elige el agente; la IA pre-redacta qué hace y qué decides tú, y lo editas antes de guardar.',
1376
+ // (1) Elegir agente detectado.
1377
+ agentPickHeading: '¿Qué agente quieres añadir a tu perfil?',
1378
+ agentPickHint: 'Flechas ↑/↓ para moverte · enter para elegir · esc para cancelar',
1379
+ agentPickPrompt: (max) => `Número (1-${max}): `,
1380
+ agentCancelled: 'Cancelado. No se ha añadido ningún agente a tu perfil.',
1381
+ // (3) Nombre ← detectado, editable.
1382
+ agentNamePrompt: (def) => `Nombre del agente [${def}]:`,
1383
+ // (4) whatItDoes + humanDecides ← IA pre-redacta tras consentimiento (ADR-052).
1384
+ agentFieldsConsentDisclaimer:
1385
+ 'Para redactar un borrador con IA de qué hace tu agente y qué decides tú, se enviarán a Shakers: el nombre del agente, sus herramientas/modelo/agente padre detectados y un resumen de su definición (sus propias instrucciones).',
1386
+ agentDraftingLabel: 'Redactando un borrador con IA…',
1387
+ agentWhatItDoesDraftLabel: 'Qué hace — borrador generado por IA:',
1388
+ agentWhatItDoesEditPrompt: 'Qué hace (edítalo o pulsa enter para dejar el borrador de arriba):',
1389
+ agentWhatItDoesFreeTextPrompt: 'Qué hace tu agente (opcional, pulsa enter para dejarlo en blanco):',
1390
+ agentHumanDecidesDraftLabel: 'Qué haces tú alrededor del agente (supervisión/validación) — borrador generado por IA:',
1391
+ agentHumanDecidesEditPrompt: 'Qué haces tú alrededor del agente (edítalo o pulsa enter para dejar el borrador de arriba):',
1392
+ agentHumanDecidesFreeTextPrompt: 'Qué haces tú alrededor del agente / cómo lo supervisas (opcional, pulsa enter para dejarlo en blanco):',
1393
+ // catalogId ← se incluye si un `usage` previo casó el agente con el catálogo.
1394
+ agentCatalogMatched: (role) => `Lo hemos casado con el catálogo de agentes de Shakers como: ${role}.`,
1395
+ agentDeclaring: 'Añadiendo el agente a tu perfil…',
1396
+ agentDeclaredSuccess: (name) => `✓ "${name}" añadido a tu perfil.`,
1397
+ agentDeclareFailed: (reason) => `✗ No se pudo añadir el agente a tu perfil: ${reason}`,
1398
+ agentHubSessionExpired: 'Tu sesión de Shakers ha caducado mientras añadíamos el agente a tu perfil.',
1399
+ agentRelateNoPortfolios: 'Todavía no tienes experiencias en tu perfil, así que el agente se ha añadido sin relacionar. Puedes añadir una con "Añadir proyecto al portfolio" y relacionarlo más tarde.',
1400
+ agentRelateAsk: (agentName) => `¿Quieres relacionar "${agentName}" con alguna de tus experiencias? (s/n):`,
1401
+ agentRelateSelectHeading: (agentName) => `Relaciona "${agentName}" con una o varias experiencias:`,
1402
+ agentRelateSkipped: (agentName) => `"${agentName}" añadido sin relacionar.`,
1403
+ agentRelated: (agentName, portfolioName) => `✓ "${agentName}" relacionado con "${portfolioName}".`,
1404
+ agentRelateFailed: (agentName, reason) => `✗ No se pudo relacionar "${agentName}" con tus experiencias: ${reason}`,
1405
+ portfolioAddSkillsResolvingLabel: 'Buscando otras skills para tu perfil…',
1406
+ portfolioAddSkillsIntro:
1407
+ 'También detectamos otras skills en este proyecto que aún no están en tu perfil de Shakers (aparte de las que acabas de vincular al portfolio).',
1408
+ portfolioAddSkillsAllAlreadyDeclared: 'Todas las skills de este proyecto ya están en tu perfil.',
1409
+ portfolioAddSkillsHeading: 'Skills para tu perfil (pre-marcadas; quita las que no apliquen):',
1410
+ portfolioAddSkillsHint: 'Flechas ↑/↓ · espacio para marcar/desmarcar · "a" para marcar/desmarcar todas · enter para confirmar · esc para cancelar',
1411
+ portfolioAddSkillsPrompt: (max) => `Números separados por comas (1-${max}) para cambiar la selección, vacío para dejar todas marcadas: `,
1412
+ portfolioAddSkillsInvalid: 'No se entendió esa respuesta. Se mantienen todas las skills marcadas.',
1413
+ errorReason: {
1414
+ 'no-endpoint': 'no hay endpoint configurado.',
1415
+ 'no-hub-token': 'tu sesión no tiene el permiso de hub necesario; ejecuta `login` de nuevo.',
1416
+ 'hub-session-expired': 'tu sesión de hub ha caducado.',
1417
+ 'network-error': 'error de red.',
1418
+ timeout: 'se agotó el tiempo de espera.',
1419
+ 'invalid-url': 'la URL del endpoint no es válida.',
1420
+ 'bad-response': 'la respuesta del servidor no se pudo interpretar.',
1421
+ generic: 'no se pudo completar la operación.',
1422
+ },
1423
+ errorReasonHttp: (status) => `el servidor respondió con un error (HTTP ${status}).`,
1424
+ },
1425
+ superadmin: {
1426
+ // ADR-027 — sesión de superadmin autenticada por contraseña (no-prod).
1427
+ sessionIntro:
1428
+ 'Abre una sesión de superadmin (solo entornos no productivos): certify funcionará con CUALQUIER email en CUALQUIER repo, saltándose los gates de identidad y autoría.',
1429
+ passwordPrompt: 'Contraseña de superadmin:',
1430
+ emailPrompt: 'Tu email de superadmin (solo para auditoría):',
1431
+ emailInvalid: 'Email no válido. Inténtalo de nuevo.',
1432
+ needInput: 'Se requieren contraseña y email (interactivo, o --password y --email).',
1433
+ sessionReady: (email) =>
1434
+ `Sesión de superadmin abierta (auditoría: ${email}). Ahora certify usará esta sesión con cualquier email.`,
1435
+ sessionExpires: (iso) => `La sesión caduca: ${iso}.`,
1436
+ sessionHint:
1437
+ 'Ejecuta: certify --email <cualquiera> --accept-disclaimer --skill <nombre> · Para cerrarla: superadmin --logout',
1438
+ loggedOut: 'Sesión de superadmin olvidada (token local eliminado).',
1439
+ // talents-ai-score, "conmutador de perfil vía superadmin" (dueño, 2026-08-12): conveniencia de dev/testing — cambia entre perfil talent/external sin reinstalar.
1440
+ profileMenuHeading: '¿Qué quieres hacer?',
1441
+ profileMenuHint: 'Flechas ↑/↓ para moverte · enter para elegir · esc para cancelar',
1442
+ profileMenuPrompt: (max) => `Número (1-${max}), vacío para cancelar: `,
1443
+ profileMenuTalent: 'Cambiar a perfil Talent',
1444
+ profileMenuTalentCurrent: 'Cambiar a perfil Talent 《actual》',
1445
+ profileMenuTalentDesc:
1446
+ 'Superficie completa: start, login, certify (skills y agentes), añadir skills/portfolio. Purga el historial local (informe, consentimiento, sesión de talento) y surte efecto de inmediato en esta misma sesión, sin reiniciar.',
1447
+ profileMenuExternal: 'Cambiar a perfil External',
1448
+ profileMenuExternalCurrent: 'Cambiar a perfil External 《actual》',
1449
+ profileMenuExternalDesc:
1450
+ 'Superficie pública pre-pivote: usage, report, share. Purga el historial local (informe, consentimiento, sesión de talento) y surte efecto de inmediato en esta misma sesión, sin reiniciar.',
1451
+ profileMenuCancelled: 'Cancelado. No se ha cambiado nada.',
1452
+ profileAlreadyOn: (profile) => `Ya estás en el perfil ${profile === 'talent' ? 'Talent' : 'External'}. No se ha cambiado ni purgado nada.`,
1453
+ profileSwitched: (profile) => `Perfil cambiado a ${profile === 'talent' ? 'Talent' : 'External'} · historial limpiado (informe, consentimiento, sesión de talento) · efecto inmediato, sin reiniciar.`,
1454
+ // talents-ai-score, ADR-020/021 (decisión del coordinador): superadmin es DELIBERADAMENTE single-hop, SOLO PRIMARIO (servicio de certificaciones) — nunca hub.
1455
+ queryingPrimary: (endpoint) => `Consultando Shakers: ${endpoint}`,
1456
+ errorNoEndpoint:
1457
+ 'No hay endpoint configurado. Configura el backend (SHAKERS_CLI_INGEST_ENDPOINT o usage --set-endpoint) y reinténtalo.',
1458
+ errorWrongPassword: 'Contraseña de superadmin incorrecta.',
1459
+ errorDisabled:
1460
+ 'Endpoint no disponible: la sesión de superadmin está deshabilitada fuera de entornos no productivos.',
1461
+ errorGeneric: 'No se pudo abrir la sesión de superadmin. Inténtalo de nuevo.',
1462
+ // Inspect (ADR-025) — recibo de atribución de certificaciones YA guardadas.
1463
+ inspectIntro:
1464
+ 'Audita la evidencia de autoría de las certificaciones ya guardadas (solo lectura, entornos no productivos).',
1465
+ inspectEmailPrompt: 'Email cuya(s) certificación(es) quieres inspeccionar:',
1466
+ inspectNone: (email) => `No hay certificaciones guardadas para ${email}.`,
1467
+ inspectHeader: (count, email) =>
1468
+ `${count} certificación(es) guardada(s) para ${email}:`,
1469
+ inspectNote:
1470
+ 'Traza de autoría basada en el autor de git (self-asserted); no es una prueba criptográfica de autoría.',
1471
+ inspectLabels: {
1472
+ score: 'Puntuación',
1473
+ dimensions: 'Dimensiones',
1474
+ repo: 'Repo',
1475
+ commitRange: 'Rango de commits',
1476
+ sampledFiles: 'Ficheros muestreados',
1477
+ authorsConfirmed: 'Autores confirmados',
1478
+ authorsConsidered: 'Autores considerados',
1479
+ model: 'Modelo',
1480
+ when: 'Fecha',
1481
+ testOrigin: 'Cuenta de prueba',
1482
+ },
1483
+ inspectErrorGeneric: 'No se pudo inspeccionar. Inténtalo de nuevo.',
1484
+ },
1485
+ // in-shell command help and messages — the commands keep their own copy.
1486
+ repl: {
1487
+ prompt: 'ϟ shakers ›',
1488
+ // dueño (2026-08-12): el BANNER de arranque (src/repl-shell.js) ahora es profile-aware y localizado.
1489
+ bannerWelcome: 'Bienvenido a',
1490
+ bannerTalentLine1: 'Completa o actualiza tu perfil de talento',
1491
+ bannerTalentLine2: 'agéntico en Shakers: no solo qué sabes, sino',
1492
+ bannerTalentLine3: 'cómo trabajas con IA y los agentes que construyes.',
1493
+ bannerHowItWorksHeading: 'Cómo funciona',
1494
+ bannerTalentStep1Line1: '1. `start` analiza tu proyecto en local y',
1495
+ bannerTalentStep1Line2: ' detecta tu setup de IA, tus skills y agentes.',
1496
+ bannerTalentStep2Line1: '2. Añades o certificas tus skills y agentes en',
1497
+ bannerTalentStep2Line2: ' tu perfil de Shakers.',
1498
+ bannerTalentStep3Line1: '3. Un perfil más completo te posiciona para los',
1499
+ bannerTalentStep3Line2: ' proyectos que lo valoran.',
1500
+ // EXTERNAL: mismo estilo de siempre (título + lista de comandos) —
1501
+ // SOLO localizado. La lista nunca incluyó `certify`/`start`/`login`.
1502
+ bannerExternalLine1: 'Una CLI local-first que escanea tu equipo en busca de',
1503
+ bannerExternalLine2: 'herramientas de IA y te muestra un informe privado y local.',
1504
+ bannerCommandsHeading: 'Comandos',
1505
+ bannerExternalUsageDesc: 'escanea tu equipo + proyecto',
1506
+ bannerExternalMapDesc: 'informe LOCAL (grafo)',
1507
+ bannerExternalReportDesc: 'informe HTML compartible',
1508
+ bannerExternalShareDesc: 'tarjeta para LinkedIn',
1509
+ // FLUJO DE COMANDOS (issues 081 y 082) VOCABULARIO, y lo hereda la 083: aquí se habla de COMANDOS de la herramienta.
1510
+ completeNeeds: (cmd) => `(necesita: ${cmd})`,
1511
+ completeDone: '(ya ejecutado)',
1512
+ completeDoneStale: (days) => `(ya ejecutado, hace ${days} días)`,
1513
+ completeAlias: (cmd) => `(alias de ${cmd})`,
1514
+ // Los subcomandos de `certify`, con la calificacion de la 085: aqui es donde
1515
+ // el talento elige, asi que es donde no puede quedar ambiguo.
1516
+ completeSubSkills: 'Skills TÉCNICAS, desde tu código',
1517
+ completeSubAgents: 'dominio de UN agente',
1518
+ flowHeading: 'En qué orden usar la herramienta',
1519
+ flowIntro: 'Los comandos se apoyan unos en otros. Este es el orden recomendado, y si te saltas una dependencia te aviso antes de ejecutar nada.',
1520
+ flowStepFree: (n, cmd, what) => `${n}. ${cmd} — ${what}`,
1521
+ flowStepNeeds: (n, cmd, what, needs) => `${n}. ${cmd} — ${what} (necesita: ${needs})`,
1522
+ flowWhat: {
1523
+ usage: 'escanea tu entorno y puntúa tu setup de IA. Es la raíz de todo lo demás, y además deja tu identidad verificada',
1524
+ certify: 'certifica una dimensión de tu rol con una entrevista por LiveKit',
1525
+ report: 'genera el informe compartible (uso de IA + certificaciones)',
1526
+ share: 'crea la tarjeta branded para LinkedIn',
1527
+ },
1528
+ // El bloqueo dice tres cosas: qué falta, POR QUÉ este comando lo necesita, y
1529
+ // el comando exacto que lo desbloquea.
1530
+ blockedHeading: (cmd) => `\`${cmd}\` no se puede ejecutar todavía.`,
1531
+ blockedWhyFootprint: (cmd) => `Le falta el uso de IA de este proyecto: \`${cmd}\` se construye a partir de lo que detectó el escaneo, así que sin escaneo no hay nada de donde partir.`,
1532
+ blockedWhyIdentity: (cmd) => `Le falta tu identidad verificada: \`${cmd}\` abre una sesión en el servicio a tu nombre y sin correo verificado el servicio la rechaza.`,
1533
+ blockedFix: (fix) => `Ejecuta \`${fix}\` y vuelve a intentarlo.`,
1534
+ blockedNoBypass: 'No hay forma de saltarse esta comprobación: el comando fallaría igual unos segundos después, solo que sin explicación.',
1535
+ // "Ejecutado y fallido" NO está aquí a propósito: no se persiste nada en el
1536
+ // camino de fallo, así que afirmarlo sería inventarlo.
1537
+ staleFootprint: (days) => `El uso de IA de este proyecto tiene ${days} días. Si has cambiado de herramientas o de agentes, vuelve a ejecutar \`usage\` antes de compartir nada.`,
1538
+ nextCommand: (cmd, what) => `Siguiente comando de la herramienta: \`${cmd}\` — ${what}`,
1539
+ journeyDone: 'Has completado el recorrido de la herramienta: no queda ningún comando por ejecutar. Los siguientes pasos para subir tu nivel de uso de IA son otra cosa y están en `usage --roadmap`.',
1540
+ goodbye: 'Hasta pronto.',
1541
+ unknown: (cmd) => `Comando no reconocido: "${cmd}". Escribe "help" para ver los comandos disponibles.`,
1542
+ // ORDEN: lo declara src/command-graph.js (issue 081).
1543
+ help:
1544
+ 'Shakers — comandos disponibles\n\n'
1545
+ + ' usage [opciones] Escanea este proyecto y tu equipo; puntúa tu setup de IA (T0-T7)\n'
1546
+ + ' certify [opciones] Certifica una dimensión de tu rol (entrevista por LiveKit)\n'
1547
+ + ' report [opciones] Genera y abre el informe compartible (uso de IA + Skills certificadas)\n'
1548
+ + ' Necesita un `usage` de este proyecto\n'
1549
+ + ' share [opciones] Crea una tarjeta branded de tu uso de IA para compartir en LinkedIn\n'
1550
+ + ' Necesita un `usage` de este proyecto\n'
1551
+ + ' login Inicia sesión como talento registrado (email + contraseña)\n'
1552
+ + ' logout Cierra tu sesión\n'
1553
+ + ' help Muestra esta ayuda\n'
1554
+ + ' clear Limpia la pantalla\n'
1555
+ + ' exit | quit Cierra la shell\n\n'
1556
+ + 'Los flags de cada comando siguen funcionando dentro de la shell\n'
1557
+ + '(p.ej. `usage --root <dir>`, `usage --roadmap`, `certify --dimension <clave>`). Usa\n'
1558
+ + '`usage --help` o `certify --help` para ver todas sus opciones.',
1559
+ helpExternal:
1560
+ 'shakers — comandos disponibles\n\n'
1561
+ + ' usage [opciones] Escanea este proyecto y tu equipo; genera un informe LOCAL de tu setup de IA\n'
1562
+ + ' report [opciones] Abre el informe HTML compartible de este proyecto en tu navegador\n'
1563
+ + ' Necesita un `usage` de este proyecto\n'
1564
+ + ' share [opciones] Crea una tarjeta branded de tu uso de IA para compartir en LinkedIn\n'
1565
+ + ' Necesita un `usage` de este proyecto\n'
1566
+ + ' help Muestra esta ayuda\n'
1567
+ + ' clear Limpia la pantalla\n'
1568
+ + ' exit | quit Cierra la shell\n\n'
1569
+ + 'Los flags de cada comando siguen funcionando dentro de la shell\n'
1570
+ + '(p.ej. `usage --root <dir>`, `usage --json`). Usa `usage --help` para ver todas sus opciones.',
1571
+ // Sustituye al bloque de flujo (081) cuando solo hay UN comando visible: no hay orden que anunciar.
1572
+ externalIntro: 'Escribe `usage` para escanear este proyecto y tu máquina; el informe se muestra siempre en local, y nada sale de tu equipo salvo que tú decidas compartirlo.',
1573
+ },
1574
+ // The SHAREABLE project report (src/render-sheet.js + its src/templates/report-sheet.html).
1575
+ sheet: {
1576
+ reportTitle: 'Informe de uso de IA',
1577
+ footprint: 'Uso de IA',
1578
+ certs: 'Certificaciones',
1579
+ ladderT: 'Nivel de setup',
1580
+ ladderS: 'Tu nivel de uso de IA: S1–S3.',
1581
+ toolsT: 'Herramientas detectadas',
1582
+ toolsS: 'Clientes de IA presentes en tu entorno.',
1583
+ techT: 'Tecnologías del proyecto',
1584
+ techS: 'Stack reconocido en el repositorio.',
1585
+ // Issue 110: los SERVICIOS detrás de los MCP.
1586
+ mcpT: 'Servicios conectados por MCP',
1587
+ mcpS: 'Productos a los que tu IA tiene acceso.',
1588
+ mcpEmpty: 'No se ha detectado ningún servidor MCP. Conectar uno es el criterio que te sube a T3 (Banco conectado).',
1589
+ mcpUnidentified: (n) => `${n} ${n === 1 ? 'servidor cuyo servicio no' : 'servidores cuyo servicio no'} hemos podido identificar por su nombre.`,
1590
+ // 085: la pestaña decía "Skills" a secas junto a una de "Agentes", que es
1591
+ // la colisión exacta que la issue nombra.
1592
+ skills: 'Skills técnicas',
1593
+ // ---- Agentes DETECTADOS, columna izquierda (issue 089) --------------
1594
+ agentsT: 'Agentes detectados',
1595
+ agentsS: 'Todos los agentes que hay configurados en tu entorno. Certificar es otra cosa y está en la columna de la derecha.',
1596
+ agentsEmpty: 'No se ha detectado ningún agente configurado (p. ej. en .claude/agents/). Si tienes agentes y no salen aquí, es que la herramienta no los ha encontrado, no que no cuenten.',
1597
+ agentNoModel: 'sin modelo declarado',
1598
+ agentNoCategory: 'sin categoría',
1599
+ // 106: TRES estados, no dos.
1600
+ agentNotEvaluated: 'sin evaluar',
1601
+ agentsEvalMissing: 'La clasificación de estos agentes no se ha podido calcular en esta ejecución, así que no es que no tengan categoría: es que no se ha llegado a preguntar. Vuelve a ejecutar `usage` para intentarlo otra vez.',
1602
+ agentOrchestrates: 'orquesta otros agentes',
1603
+ valoracion: 'Valoración',
1604
+ comoMejorar: 'Cómo mejorar',
1605
+ bandLine: (label) => `Nivel de setup <b>${label}</b>.`,
1606
+ hereYouAre: 'Estás aquí',
1607
+ noFoot: 'Aún no hay uso de IA. Ejecuta usage en este proyecto.',
1608
+ noSkills: 'Aún no hay Skills técnicas certificadas. Ejecuta certify skills.',
1609
+ // Agent certifications were removed from the report (ADR-033); the sheet's agent-cert tab, its empty state and its detected-vs-certified copy went with it.
1610
+ footer: 'Informe generado localmente · Shakers',
1611
+ // Template chrome that used to be hardcoded Spanish in report-sheet.html (including inside its inline <script>), so an English report shipped Spanish controls.
1612
+ themeToggle: 'Cambiar tema',
1613
+ themeDark: 'Oscuro',
1614
+ themeLight: 'Claro',
1615
+ copied: 'Copiado ✓',
1616
+ expandAll: 'Expandir todo',
1617
+ collapseAll: 'Colapsar todo',
1618
+ },
1619
+ },
1620
+ en: {
1621
+ onboarding: {
1622
+ title: 'Shakers registration (onboarding)',
1623
+ askLinkedin: 'Your LinkedIn URL (required):',
1624
+ askCv: 'Path to your CV PDF (optional, Enter to skip):',
1625
+ askGithub: 'Your GitHub URL (optional, Enter to skip):',
1626
+ askWebsite: 'Your personal website (optional, Enter to skip):',
1627
+ importStarted: 'Importing your profile in the background while we continue…',
1628
+ importError: 'Could not start your profile import.',
1629
+ importUnavailable: 'We could not import your profile right now (service unavailable); we are continuing with your registration, you can retry the import later.',
1630
+ importChecking: 'Importing your profile…',
1631
+ importDone: 'Profile imported successfully.',
1632
+ importFailed: 'We could not finish importing your profile. You can retry it later from your profile.',
1633
+ importStillRunning: 'Your profile is still importing in the background; it will be ready in a few minutes.',
1634
+ professionalTitle: 'Your work situation',
1635
+ askWorkSituation: "What's your current work situation? Choose one:",
1636
+ workSituationLabels: {
1637
+ FREELANCE: 'Freelance',
1638
+ EMPLOYED: 'Employed',
1639
+ BETWEEN_JOBS: 'Between jobs',
1640
+ STUDYING: 'Studying',
1641
+ OTHER: 'Other',
1642
+ },
1643
+ askEmploymentParticipation: 'Is your role full-time or part-time?',
1644
+ employmentParticipationLabels: {
1645
+ FULL_TIME: 'Full-time',
1646
+ PART_TIME: 'Part-time',
1647
+ },
1648
+ askFreelanceOpinion: 'What is your opinion on freelancing?',
1649
+ freelanceOpinionLabels: {
1650
+ WAS_FREELANCE_BEFORE: 'I have been freelance',
1651
+ OPEN_TO_FREELANCE: 'I would consider becoming freelance',
1652
+ NOT_INTERESTED: 'Not interested',
1653
+ },
1654
+ askChangeMotivators: 'What inspires you about freelancing or Shakers?',
1655
+ changeMotivatorLabels: {
1656
+ HIGHER_RATE: 'Get a higher rate',
1657
+ SPECIFIC_PROJECT: 'A specific project',
1658
+ LEARNING_CERTIFICATION: 'Continue learning and get certified',
1659
+ FLEXIBILITY: 'The flexibility of being freelance',
1660
+ COMMUNITY: 'Belonging to a community',
1661
+ LIFESTYLE_CHANGE: 'I want to change my lifestyle',
1662
+ },
1663
+ professionalSaved: 'Work situation saved.',
1664
+ chooseMethodHeading: 'How do you want to create your account?',
1665
+ signupTitle: 'Create your Shakers account',
1666
+ askName: 'First name:',
1667
+ askLastName: 'Last name:',
1668
+ askEmail: 'Email (use a NEW one; if it already exists I will offer to sign in):',
1669
+ askPassword: 'Password (at least 8 characters):',
1670
+ askPasswordConfirm: 'Repeat the password:',
1671
+ passwordMismatch: 'The passwords do not match. Try again.',
1672
+ askFreelanceTypeSelect: 'How do you work? Choose one:',
1673
+ freelanceTypeLabels: {
1674
+ FREELANCE: 'Freelance',
1675
+ EMPLOYEE: 'Employed',
1676
+ AGENCY: 'Agency or company',
1677
+ POTENTIAL_FREELANCE: 'Considering freelancing',
1678
+ },
1679
+ invalidChoice: 'Invalid option.',
1680
+ askNewsletter: 'Do you want the newsletter? (y/n):',
1681
+ weakPassword: 'The password does not meet the requirements (at least 8 characters).',
1682
+ signupError: 'Could not create the account.',
1683
+ freelanceIntentRequired: 'Your freelance intent is required for that type.',
1684
+ accountExists: 'An account with that email already exists. Signing you in.',
1685
+ accountReady: 'Account created. Signed in.',
1686
+ sessionError: 'The account was created but sign-in failed. Run `shakers login`.',
1687
+ pricingTitle: 'Your per-project price',
1688
+ pricingSaved: 'Price saved.',
1689
+ askPricingFullAmount: (cur) => `Full-time project price in ${cur} (number, no thousands separators, e.g. 60000):`,
1690
+ askPricingFullCurrency: 'Currency:',
1691
+ askPricingPartSelect: 'Do you offer part-time projects? (y/n):',
1692
+ askPricingPartAmount: (cur) => `Part-time project price in ${cur} (number, no thousands separators, e.g. 50):`,
1693
+ askPricingPartCurrency: 'Currency:',
1694
+ pricingAmountInvalid: 'Invalid amount. Use a number with no thousands separators and at most 2 decimals (e.g. 60000 or 1500.50), between 0 and 999999.99.',
1695
+ availabilityTitle: 'Your availability and location',
1696
+ availabilityAsk: 'Do you want to set your availability and location now? (y/n):',
1697
+ availableAsk: 'Are you available for new projects? (y/n):',
1698
+ monthlyHoursAsk: 'How many hours per month?',
1699
+ onlyRemoteAsk: 'Do you work remote-only? (y/n):',
1700
+ countryAsk: 'Country (2-letter ISO-2 code, e.g. ES):',
1701
+ countryInvalid: 'Invalid country code. Use 2 letters (e.g. ES, US, GB).',
1702
+ subdivisionAsk: 'State or province (optional, Enter to skip):',
1703
+ timezoneAsk: (tz) => (tz ? `Timezone [${tz}] (Enter to accept, or type another):` : 'Timezone (e.g. Europe/Madrid):'),
1704
+ longFullTimeAsk: 'Open to long full-time projects? (y/n):',
1705
+ phonePrefixAsk: 'Phone dialling code (optional, e.g. +34, Enter to skip):',
1706
+ phoneNumberAsk: 'Phone number (optional, Enter to skip):',
1707
+ phoneSaved: 'Phone saved.',
1708
+ phoneUnavailable: 'We could not save your phone right now. You can update it later.',
1709
+ availabilitySaved: 'Availability saved.',
1710
+ availabilityUnavailable: 'We could not save your availability right now. You can update it later.',
1711
+ availabilitySkipped: 'OK, you can set your availability later.',
1712
+ languagesTitle: 'Your languages',
1713
+ languagesAsk: 'Do you want to add the languages you speak? (y/n):',
1714
+ askLanguageCode: 'Language:',
1715
+ languageNames: {
1716
+ es: 'Spanish',
1717
+ en: 'English',
1718
+ fr: 'French',
1719
+ ca: 'Catalan',
1720
+ de: 'German',
1721
+ eu: 'Basque',
1722
+ it: 'Italian',
1723
+ pt: 'Portuguese',
1724
+ },
1725
+ askLanguageLevel: 'Your level in that language:',
1726
+ languageLevelLabels: {
1727
+ NATIVE: 'Native or bilingual',
1728
+ ADVANCED: 'Fluent at work and daily life',
1729
+ INTERMEDIATE: 'Can work in it (speaking and writing)',
1730
+ INTERMEDIATE_WRITTEN: 'Can work in it, prefer written communication',
1731
+ },
1732
+ askAnotherLanguage: 'Do you want to add another language? (y/n):',
1733
+ languagesSaved: 'Languages saved.',
1734
+ languagesUnavailable: 'We could not save your languages right now. You can add them later.',
1735
+ languagesSkipped: 'OK, you can add your languages later.',
1736
+ usageTitle: 'Evaluate your AI usage (optional)',
1737
+ usageInfoAccessed: 'Accessed: the structure of your AI tools and redacted excerpts of your activity on this machine. Your raw code and prompts NEVER leave your machine.',
1738
+ usageGoalDuration: 'Goal: measure how you work with AI for your profile. Duration: ~1-2 minutes.',
1739
+ usageAccept: 'Do you want to evaluate your AI usage now? (y/n):',
1740
+ usageSkippedEvidence: 'You already have AI-usage evidence on Shakers; skipping this step.',
1741
+ interviewTitle: 'Onboarding interview (optional)',
1742
+ interviewOnlyTitle: 'Onboarding interview',
1743
+ interviewInfoAccessed: 'Accessed: your answers in this interview and your imported profile. It is conducted by the Shakers server.',
1744
+ interviewGoalDuration: 'Goal: learn your role, your dedication and how you use AI. Duration: ~5 minutes.',
1745
+ interviewAccept: 'Do you want to take the interview now? (y/n):',
1746
+ interviewComplete: 'Interview complete. Thank you!',
1747
+ interviewAlreadyDone: 'You already completed the onboarding interview; it cannot be repeated from the CLI. You can keep refining your profile.',
1748
+ repeatWarnTitle: 'Repeat the onboarding interview',
1749
+ repeatWarnBody: 'Repeating OVERWRITES your previous onboarding interview and CANNOT be undone: the previous evaluation is discarded.',
1750
+ repeatConfirmPrompt: 'Are you sure you want to repeat and overwrite your onboarding?',
1751
+ repeatConfirmYes: 'Yes, repeat and overwrite',
1752
+ repeatConfirmNo: 'No, cancel',
1753
+ repeatAborted: 'OK, not repeating the onboarding.',
1754
+ repeatRestarted: 'Onboarding reset. Let us start again.',
1755
+ repeatNotFound: 'You have no onboarding yet. Run `shakers onboarding` first.',
1756
+ repeatAuthError: 'Your session does not allow this action. Sign in again: shakers login',
1757
+ repeatFailed: (reason) => `Could not reset the onboarding (${reason}). Try again.`,
1758
+ interviewAnswer: 'Your answer:',
1759
+ questionHeading: (n) => `Question ${n}`,
1760
+ interviewTurnError: 'Could not continue the interview.',
1761
+ thinking: 'Alma is thinking…',
1762
+ interviewIntro: 'This is a conversation; answer naturally, take your time.',
1763
+ interviewTransition: 'Thanks. Let us continue:',
1764
+ interviewConnecting: 'Connecting to the interview...',
1765
+ livekitMissing: 'The interview needs the optional @livekit/rtc-node package, which is not installed.\n Re-run the talent installer (SHAKERS_PROFILE=talent) to install it, or do it by hand:\n npm install --omit=dev --prefix ~/.shakers @livekit/rtc-node\n (use your SHAKERS_CLI_HOME path if you installed the CLI elsewhere).\n Then re-run: shakers onboarding',
1766
+ livekitOld: 'The installed @livekit/rtc-node is too old for text streams.\n Update it: npm install --prefix ~/.shakers @livekit/rtc-node@latest',
1767
+ livekitConnectError: 'Could not connect to the interview room.',
1768
+ transcriptWarn: (reason) => `Warning: could not save the interview transcript (${reason}). It may not be available to review later.`,
1769
+ finalizedGeneric: 'Onboarding complete.',
1770
+ finalizedAt: (pct) => `Onboarding complete. Your profile is at ${pct}%.`,
1771
+ finalizeWarn: (reason) => `Could not mark onboarding as complete (${reason}). You can retry later.`,
1772
+ profileReadyNoUrl: 'Your profile is ready. You can view it in your Shakers account.',
1773
+ profileReadyUrl: (url) => `Your profile is ready: ${url}`,
1774
+ help: 'Usage: shakers onboarding [--lang es|en] [--accept-disclaimer]',
1775
+ },
1776
+ categories: {
1777
+ AGENTIC_CLI: 'Agentic CLI',
1778
+ AI_EDITOR: 'AI editor',
1779
+ IDE_ASSISTANT: 'IDE assistant',
1780
+ COMPLETION: 'Autocomplete',
1781
+ AI_TERMINAL: 'AI terminal',
1782
+ },
1783
+ mcpCategories: {
1784
+ data: 'Data',
1785
+ comms: 'Communication',
1786
+ dev: 'Development',
1787
+ browser: 'Browser',
1788
+ other: 'Other',
1789
+ },
1790
+ tierNames: {
1791
+ T0: 'Empty bench',
1792
+ T1: 'First tool',
1793
+ T2: 'Bench with notes',
1794
+ T3: 'Connected bench',
1795
+ T4: 'Own tooling',
1796
+ T5: 'Agentic operator',
1797
+ T6: 'Multi-agent',
1798
+ T7: 'Orchestrated workshop',
1799
+ },
1800
+ tierAnalysis: {
1801
+ heading: 'Tier analysis: why this level',
1802
+ intro: (tierKey, tierName) =>
1803
+ `Your current tier is ${tierKey} (${tierName}). The tier engine is deterministic: it certifies a `
1804
+ + 'level only when ALL criteria for that level and every level below it are met, checked strictly '
1805
+ + 'bottom-up (a signal for a higher tier never lets you skip a lower one). Below is a criterion-by-'
1806
+ + 'criterion breakdown of what was checked and the exact signal from your environment backing it.',
1807
+ metHeading: 'Criteria you meet:',
1808
+ blockingLabel: 'Exact criterion blocking your next tier:',
1809
+ maxTierNote: "You meet every criterion in the T0-T7 ladder: there's no additional criterion blocking your progress.",
1810
+ criterion: {
1811
+ t1Met: (n) => `You have at least one AI tool detected and configured in your environment (\`totalDetected = ${n}\`).`,
1812
+ t2Met: (n) => `You have at least one persistent context file — instructions, config or rules — for some tool (\`context = ${n}\`).`,
1813
+ t3Met: (n) => `You have at least one connected MCP server, giving the AI access to external data or tools (\`mcpServers = ${n}\`).`,
1814
+ t4Met: (n) => `You've created your own assets — skills, commands or custom rules — beyond the default configuration (\`custom = ${n}\`).`,
1815
+ t5Met: (hasAgentic, mcp, custom) => `You operate an agentic CLI (Claude Code, Aider, Gemini CLI, Codex CLI or Amazon Q Developer) combined with MCP and your own assets (\`hasAgentic = ${hasAgentic}\`, \`mcpServers = ${mcp}\`, \`custom = ${custom}\`).`,
1816
+ t6Met: (n) => `You have a team of at least 2 specialized agents defined (\`agentCounts.agents = ${n}\`).`,
1817
+ t7Met: (n) => `You have hook-based automation configured (\`hooks = ${n}\`).`,
1818
+ t1Blocking: (n) => `To reach T1 (First tool) you need at least one detected AI tool — currently \`totalDetected = ${n}\`.`,
1819
+ t2Blocking: (n) => `To reach T2 (Bench with notes) you need at least one persistent context file (instructions, config or rules) — currently \`context = ${n}\`.`,
1820
+ t3Blocking: (n) => `To reach T3 (Connected bench) you need to connect at least one MCP server — currently \`mcpServers = ${n}\`.`,
1821
+ t4Blocking: (n) => `To reach T4 (Own tooling) you need to create at least one asset of your own — skill, command or rule — currently \`custom = ${n}\`.`,
1822
+ t5Blocking: (hasAgentic, mcp, custom) => {
1823
+ const missing = [];
1824
+ if (!hasAgentic) missing.push('an agentic CLI (Claude Code, Aider, Gemini CLI, Codex CLI or Amazon Q Developer)');
1825
+ if (mcp < 1) missing.push('at least 1 MCP server');
1826
+ if (custom < 1) missing.push('at least 1 asset of your own (skill, command or rule)');
1827
+ return `To reach T5 (Agentic operator) you're missing: ${missing.join('; ')} (\`hasAgentic = ${hasAgentic}\`, \`mcpServers = ${mcp}\`, \`custom = ${custom}\`).`;
1828
+ },
1829
+ t6Blocking: (n) => `To reach T6 (Multi-agent) you need at least 2 specialized agents defined under \`.claude/agents/\` — currently you have ${n}.`,
1830
+ t7Blocking: (n) => `To reach T7 (Orchestrated workshop) you need at least one automation hook configured — currently \`hooks = ${n}\`.`,
1831
+ },
1832
+ },
1833
+ // Progression ladder (skill-code-certification, report req 1) — see the es block.
1834
+ ladder: {
1835
+ levelsHeading: 'Maturity levels (0-4)',
1836
+ // ADR-016: the ladder now groups tiers by SETUP LEVEL, not the 0-4 band.
1837
+ setupHeading: 'Setup Level',
1838
+ setupIntro:
1839
+ 'Your setup level (S1–S3) sums up your AI usage at a glance; the tier (T0-T7) is the '
1840
+ + 'fine-grained axis it is derived from. Both are deterministic. Below marks what you have '
1841
+ + 'already passed (✓), where you are now (●), and what lies ahead (○) with the exact criterion '
1842
+ + 'that unlocks it.',
1843
+ tiersHeading: 'Tier ladder (T0-T7)',
1844
+ levelLabel: (n) => `Level ${n}`,
1845
+ intro:
1846
+ 'Your maturity level (0-4) sums up your AI usage at a glance; the tier (T0-T7) is the '
1847
+ + 'fine-grained axis it is derived from. Both are deterministic. Below marks what you have '
1848
+ + 'already passed (✓), where you are now (●), and what lies ahead (○) with the exact criterion '
1849
+ + 'that unlocks it.',
1850
+ reachedLabel: 'Reached',
1851
+ currentLabel: 'You are here',
1852
+ pendingLabel: 'Pending',
1853
+ unlockLabel: 'To unlock',
1854
+ legend: (done, current, pending) => `${done} reached · ${current} current · ${pending} pending`,
1855
+ levelDesc: {
1856
+ none: 'No AI usage: no AI tool detected in your environment.',
1857
+ exploring: 'Exploring: you have AI tools installed and are trying them out.',
1858
+ integrated: 'Integrated: AI is wired into your projects with persistent context.',
1859
+ power: 'Power user: you extend AI with MCP, your own skills/commands and agentic CLIs.',
1860
+ orchestrator: 'Orchestrator: you run several coordinated agents and end-to-end automation.',
1861
+ },
1862
+ tierDesc: {
1863
+ T0: 'Empty bench: no AI tool detected yet.',
1864
+ T1: 'First tool: you use at least one AI tool.',
1865
+ T2: 'Bench with notes: persistent context files guide the AI.',
1866
+ T3: 'Connected bench: an MCP server gives the AI access to your data and tools.',
1867
+ T4: 'Own tooling: you have built your own skill files, commands or rules.',
1868
+ T5: 'Agentic operator: an agentic CLI drives MCP and your own assets end to end.',
1869
+ T6: 'Multi-agent: a team of 2+ specialized agents.',
1870
+ T7: 'Orchestrated workshop: hooks automate the workshop and agents orchestrate each other.',
1871
+ },
1872
+ },
1873
+ // Agent classification + improvement tips (skill-code-certification req 2/3) — see the es block.
1874
+ classification: {
1875
+ label: 'Classification',
1876
+ noCategory: 'No category',
1877
+ // FOURTH STATE — see the Spanish catalog for why it is not "not assessed" and
1878
+ // why it lives here rather than in the per-surface sections.
1879
+ agentEvalOmitted: 'left out of this run',
1880
+ // Wording holds for one, for some and for ALL — see the Spanish catalog.
1881
+ agentsEvalPartial: (names, count = 2) => (count === 1
1882
+ ? `This agent was left out of this run: ${names}. The model's response did not cover it, so it is not that it has no category: it was never assessed. Run \`usage\` again to complete it.`
1883
+ : `These agents were left out of this run: ${names}. The model's response did not cover them, so it is not that they have no category: they were never assessed. Run \`usage\` again to complete them.`),
1884
+ // Whole call lost, not a partial one — and split by cause, because a deadline that scales with agent count makes "try again" a false suggestion.
1885
+ agentsEvalMissing: "Your agents' category could not be computed this run: the assessment never answered. It is not that they have no category. Run `usage` again to retry.",
1886
+ // See the Spanish catalog: the two point the talent at different places.
1887
+ agentsEvalUnreachable: "Shakers could not be reached, so your agents show no category. Check your connection and run `usage` again.",
1888
+ agentsEvalErrored: "Shakers answered with an error, so your agents show no category. This is not your setup; try again in a while.",
1889
+ agentsEvalTimedOut: (count) => `The assessment of your agents did not answer within its deadline, so they show no category this run. That time grows with the number of agents and you have ${count}, so running it again unchanged will most likely end the same way.`,
1890
+ improvementsHeading: 'How to improve this agent',
1891
+ // Seven specialities plus the FLOOR (`other`) — see the Spanish catalog for
1892
+ // why the floor needs a label AND must keep the neutral badge.
1893
+ categories: {
1894
+ developer: 'Development',
1895
+ product: 'Product',
1896
+ designer: 'Design',
1897
+ marketing: 'Marketing',
1898
+ data: 'Data',
1899
+ finance: 'Finance',
1900
+ sales: 'Sales',
1901
+ other: 'Other / internal',
1902
+ },
1903
+ levels: {
1904
+ L1: 'L1 · operational',
1905
+ L2: 'L2 · tactical',
1906
+ L3: 'L3 · strategic',
1907
+ },
1908
+ },
1909
+ // LEGACY 0-4 band names — kept only for older persisted reports / the
1910
+ // unchanged sent payload (ADR-016); the report shows `setupLevels` instead.
1911
+ levelNames: {
1912
+ none: 'No AI usage',
1913
+ exploring: 'Exploring',
1914
+ integrated: 'Integrated',
1915
+ power: 'Power user',
1916
+ orchestrator: 'Orchestrator',
1917
+ },
1918
+ // Setup Level (Talent Certification Framework, ADR-016): the 3-value rollup that REPLACES the 0-4 band on every surface.
1919
+ setupLevels: {
1920
+ none: {
1921
+ label: 'Not certified',
1922
+ desc: 'No AI setup: no AI tool detected in your environment.',
1923
+ },
1924
+ S1: {
1925
+ label: 'S1 · Assisted',
1926
+ desc: 'Assisted: you use AI with persistent context (instructions/config) across your projects.',
1927
+ },
1928
+ S2: {
1929
+ label: 'S2 · Extended',
1930
+ desc: 'Extended: you extend AI with MCP and your own assets (skill files, commands or rules).',
1931
+ },
1932
+ S3: {
1933
+ label: 'S3 · Orchestrated',
1934
+ desc: 'Orchestrated: you run agentic CLIs, agent teams and end-to-end automation.',
1935
+ },
1936
+ },
1937
+ nextSteps: {
1938
+ 0: 'Install an AI tool (Claude Code, Cursor or Copilot) and try it on a real project.',
1939
+ 1: 'Add an instructions file to the project (CLAUDE.md, .cursorrules or copilot-instructions.md) to give it persistent context.',
1940
+ 2: 'Connect an MCP server or create your own rules/commands so the AI can reach your data and workflows.',
1941
+ 3: 'Combine an agentic CLI with MCP and your own skills/commands; automate a recurring task end to end.',
1942
+ 4: 'You already operate at orchestration level: document your setup and chain agents or background runs.',
1943
+ },
1944
+ recency: {
1945
+ today: 'today',
1946
+ this_week: 'this week',
1947
+ this_month: 'this month',
1948
+ this_quarter: 'this quarter',
1949
+ stale: 'outdated',
1950
+ },
1951
+ terminal: {
1952
+ brandSub: 'AI usage profile',
1953
+ toolsDetected: (n, total) => `${n}/${total} tools detected`,
1954
+ level: (level, name) => `Level ${level} · ${name}`,
1955
+ // ADR-016: Setup Level shown in the top bar (replaces the 0-4 level line).
1956
+ setupLevel: (label) => `Setup · ${label}`,
1957
+ // Current tier appended to the top bar, next to the level (report req 1 addendum).
1958
+ tierInline: (key, name) => ` · Tier ${key} · ${name}`,
1959
+ detectedHeading: 'Detected',
1960
+ activity: {
1961
+ heading: 'Activity (analyzed repos)',
1962
+ repos: (list) => `Repos: ${list}`,
1963
+ sessions: (n) => `${n} ${n === 1 ? 'session' : 'sessions'} (root)`,
1964
+ subagents: (n) => `${n} subagent ${n === 1 ? 'run' : 'runs'}`,
1965
+ hours: (h) => `${h} active h (idle gap > 25 min = pause)`,
1966
+ commits: (authored, total) => `${authored}/${total} commits by you`,
1967
+ lines: (added, deleted) => `+${added}/-${deleted} lines`,
1968
+ velocity: (v) => `${v} commits/day`,
1969
+ workStreams: (streams, multiDay, maxSpan) =>
1970
+ `${streams} ${streams === 1 ? 'workstream' : 'workstreams'} (${multiDay} multi-day, max ${maxSpan}d)`,
1971
+ tractionLabel: 'Traction',
1972
+ tSessions: (n) => `${n} sessions (90d)`,
1973
+ tDays: (n) => `${n} days/week`,
1974
+ tAgents: (d, p) => `${d} agents detected${p != null ? ` · ${p} on your profile` : ''}`,
1975
+ tTools: (list) => `tools: ${list}`,
1976
+ },
1977
+ none: '(none)',
1978
+ environment: 'Environment',
1979
+ editors: 'editors',
1980
+ noEditorsDetected: 'none detected',
1981
+ // ADR-016 agent evaluation (terminal, one line per agent): compact usage
1982
+ // signal derived from the local Claude Code history.
1983
+ agentUsed: (n) => `used ${n}×`,
1984
+ agentUnused: 'no local use',
1985
+ agentUsageUnavailable: '(no local Claude Code history: usage unavailable)',
1986
+ // ADR-016: discoverability hint for the next-steps section (behind --roadmap).
1987
+ roadmapHint: 'Run `usage --roadmap` to see the steps between you and the next AI-usage level. These are not steps in this tool: they are changes to how you work, over the coming weeks.',
1988
+ aiProfileGenerating: 'Generating your AI profile (matrix, vision and how you work)…',
1989
+ aiProfilePendingRetry: 'Your AI profile (matrix, vision and how you work) is still being generated; re-run `report` in a moment to see it.',
1990
+ // issue 122 / ADR-044: shown when a session existed and has expired. Opens
1991
+ // with "Your session has expired" — see the Spanish catalog.
1992
+ sessionExpiredNotice: 'Your session has expired. You are seeing the full report as a general user; run `login` to sign in again.',
1993
+ nextStep: 'Next step to level up',
1994
+ files: (n) => `${n} ${n === 1 ? 'file' : 'files'}`,
1995
+ lastModified: (label) => `last modified: ${label}`,
1996
+ // dueño (2026-08-12): "Project technologies" is renamed "Skills" for the TALENT profile only (naming unicity with the rest of the talent surface).
1997
+ skillsHeading: 'Skills',
1998
+ },
1999
+ html: {
2000
+ lang: 'en',
2001
+ title: (setupLabel) => `Shakers · ${setupLabel}`,
2002
+ h1: 'Your AI usage profile',
2003
+ sub: 'A local snapshot of which AI tools you have and how deeply you have configured them.',
2004
+ levelOf: (level) => `Level ${level} of 4`,
2005
+ // ADR-016: hero label for the Setup Level (replaces the 0-4 "Level X of 4").
2006
+ setupLevelOf: 'Setup Level',
2007
+ // Current tier shown in the hero bar next to the level (report req 1 addendum).
2008
+ currentTier: (key, name) => `Tier ${key} · ${name}`,
2009
+ detectedSuffix: (total) => `of ${total} tools detected`,
2010
+ maturity: 'Maturity',
2011
+ tools: 'Tools',
2012
+ toolsEmpty: 'No AI tool was detected in your environment.',
2013
+ configIntensity: 'configuration intensity',
2014
+ files: (n) => `${n}&nbsp;${n === 1 ? 'file' : 'files'}`,
2015
+ lastModified: (dateStr) => `last modified: ${dateStr}`,
2016
+ environment: 'Environment',
2017
+ platform: 'Platform',
2018
+ architecture: 'Architecture',
2019
+ installedEditors: 'Installed editors',
2020
+ noEditorsDetected: 'none detected',
2021
+ nextStep: 'Next step to level up',
2022
+ diagramHeading: 'Agents',
2023
+ agentsEmpty: 'No configured AI agents detected (e.g. .claude/agents/).',
2024
+ orchestratorLabel: 'Orchestrator',
2025
+ agentDescriptionFromName: (name) => `"${name}" agent (no description declared in its file).`,
2026
+ // AI product an agent belongs to, derived from its source (proper nouns; same es/en).
2027
+ aiProducts: { 'claude-code': 'Claude Code' },
2028
+ // ADR-016 agent evaluation (HTML per-agent detail): definition-quality score + LLM rationale + local usage signal.
2029
+ agentScoreLabel: 'Definition quality (0-100)',
2030
+ agentQualityLabel: 'Why:',
2031
+ agentUsageLabel: 'Local usage (Claude Code history)',
2032
+ agentUsedTimes: (n) => `used ${n}×`,
2033
+ agentUnused: 'no local use',
2034
+ // Project technologies (talents-ai-score, ADR-012). Refined: shows
2035
+ // recognized FRAMEWORKS/LIBRARIES only, not a raw dependency dump.
2036
+ technologiesHeading: 'Project technologies',
2037
+ technologiesEmpty: 'No recognized framework or library was found in the dependency manifests (package.json, requirements.txt, go.mod, pyproject.toml).',
2038
+ // MCP servers by name (talents-ai-score, issue 015).
2039
+ mcpHeading: 'Services connected over MCP',
2040
+ mcpEmpty: 'No MCP server detected. Connecting one is the criterion that takes you to T3 (Connected bench).',
2041
+ mcpUnidentified: (n) => `${n} more server${n === 1 ? '' : 's'} whose service we could not identify from its name.`,
2042
+ roadmapHeading: 'Your next AI-usage level',
2043
+ roadmapSubheading: 'What follows is yours to do at work, not in this tool. The tool only tells you where to aim.',
2044
+ roadmapUpgradeWhenLabel: 'You level up when:',
2045
+ roadmapUnlocksLabel: 'What it unlocks',
2046
+ roadmapStepsLabel: 'Steps',
2047
+ roadmapSnippetLabel: 'Copyable snippet',
2048
+ roadmapTipsLabel: 'Community tips',
2049
+ roadmapMistakesLabel: 'Common mistakes',
2050
+ roadmapConsolidationLabel: 'Consolidation steps',
2051
+ roadmapHonestyLabel: 'Honesty note',
2052
+ roadmapContentUnavailable: "This level's detailed content isn't available in this language yet.",
2053
+ roadmapPersonalizedNotice: 'Content adapted to your project.',
2054
+ // Issue 109 — see the Spanish catalog for why these say what was lost
2055
+ // rather than what went wrong, and when they are allowed to show.
2056
+ roadmapNotPersonalizedNotice: 'This roadmap is the generic one for your tier: it could not be adapted to your project this run. Run `usage` again to retry.',
2057
+ // Issue 084 — see the Spanish catalog for why the tag sits next to the
2058
+ // heading instead of in a footnote.
2059
+ roadmapNowLabel: 'Where you start from',
2060
+ roadmapProjectionLabel: 'Where this path leaves you',
2061
+ roadmapProjectionTag: 'projection',
2062
+ roadmapProjectionNote: 'Computed with the same engine as your evaluation, applying the minimum these steps ask for. It is a projection, not a promise: the evaluation measures real use, so taking them is necessary and not sufficient.',
2063
+ implementationPromptHeading: 'Implementation prompt',
2064
+ implementationPromptHint: 'Copy this prompt and paste it into your AI tool of choice (Claude Code, Cursor, ChatGPT...) so it implements this in your project.',
2065
+ implementationPromptCopyLabel: 'Copy',
2066
+ implementationPromptCopiedLabel: 'Copied ✓',
2067
+ privacyNote:
2068
+ 'This report was generated locally. It only records which tools exist, '
2069
+ + 'how many configurations you have and your level: never the content of '
2070
+ + 'your files, paths or credentials.',
2071
+ metaLine: (dateStr, anonId, platform) =>
2072
+ `Generated ${dateStr} · anonymous id <code>${anonId}</code> · platform ${platform}`,
2073
+ rawData: "View this report's exact data (JSON)",
2074
+ },
2075
+ cli: {
2076
+ // Reporting redesign (skill-code-certification): HTML is no longer opt-in (--html retired).
2077
+ reportLink: (url) => `Open your report in your browser:\n ${url}`,
2078
+ // `share` command (skill-code-certification): CLI copy wrapping the branded LinkedIn card.
2079
+ share: {
2080
+ help: 'share — build a branded card with your AI usage result (tier + score) to post on LinkedIn.\n'
2081
+ + ' Run `usage` first; `share` uses this project\'s latest AI usage.\n'
2082
+ + ' Options: --root <dir>, --lang es|en',
2083
+ noFootprint: 'No AI usage for this project yet. Run `usage` first, then `share`.',
2084
+ ready: (url) => `Your shareable card is ready — open it to download the PNG and post it:\n ${url}`,
2085
+ hint: 'LinkedIn can\'t attach an image from a URL: download the PNG from the card, then attach it to your post.',
2086
+ error: 'Could not generate the shareable card.',
2087
+ disabled: '`share` is currently disabled. Please try again later.',
2088
+ },
2089
+ accountReminder: {
2090
+ question: 'Do you already have an account?',
2091
+ hint: 'ai-usage is public. Signing in is optional; arrows to move, enter to choose.',
2092
+ yes: 'Yes, sign in',
2093
+ no: 'No, continue without an account',
2094
+ },
2095
+ // `report` command (ADR-016): builds and OPENS the full, shareable HTML report for this project (AI usage + certified Skills).
2096
+ report: {
2097
+ help: 'report — build and open the full HTML report for this project (AI usage + certified Skills) to share with your team.\n'
2098
+ + ' Run `usage` (and optionally `certify`) first; `report` gathers their result.\n'
2099
+ + ' Options: --root <dir>, --lang es|en, --no-open (do not open the browser; just print the link)',
2100
+ noData: 'Nothing to show for this project yet. Run `usage` first (and optionally `certify`), then `report`.',
2101
+ ready: (url) => `Your report is ready:\n ${url}`,
2102
+ opening: 'Opening it in your browser…',
2103
+ error: 'Could not generate the report.',
2104
+ },
2105
+ frameworkIntroUsage:
2106
+ 'This measures your AI fluency across two axes: Setup (your tooling\'s maturity, T0–T7, deterministic) and Usage (how you use it, assessed via interview + code). It places you on the 3×3 map (Explorer → AI Native). Completing it positions your agentic profile for projects that value how you work with AI.',
2107
+ scanningLabel: 'Scanning environment and detectors…',
2108
+ // Loader for the per-selected-repo aggregation phase (git + sessions + steering/decisions + evidence + agent org chart).
2109
+ aggregatingLabel: 'Aggregating signals across the selected repos…',
2110
+ reposScopePromptHeader: 'What do you want to evaluate?',
2111
+ reposScopePromptHint: 'Arrows to move · enter to choose',
2112
+ reposScopeOptionMachine: 'Whole machine (every repo with AI activity)',
2113
+ reposScopeOptionRepo: 'Current repo (this directory only)',
2114
+ reposDetectedNote: (repoCount, unassigned) =>
2115
+ repoCount === 0
2116
+ ? 'No repos with AI sessions detected on this machine.'
2117
+ : `${repoCount} ${repoCount === 1 ? 'repo' : 'repos'} with AI sessions detected` +
2118
+ (unassigned > 0
2119
+ ? `; ${unassigned} ${unassigned === 1 ? 'session' : 'sessions'} with no attributable repo, excluded.`
2120
+ : '.'),
2121
+ reposFlagUnmatched: (list) => `Repos not found, ignored: ${list}.`,
2122
+ reposScopeCapped: (shown, total) =>
2123
+ `Whole-machine evaluation capped to ${shown} of ${total} detected repos (most active first).`,
2124
+ reposScopeAll: 'Evaluating all AI usage on this machine.',
2125
+ synthesizingLabel: 'Synthesizing agents with AI…',
2126
+ personalizingRoadmapLabel: 'Personalizing roadmap…',
2127
+ // ADR-016: agent definition-quality evaluation (ephemeral LLM call).
2128
+ evaluatingAgentsLabel: 'Evaluating your agents’ quality…',
2129
+ buildNextLevelHint: 'Alternatively, run `usage --build-next-level` to generate the starter file directly in your project.',
2130
+ // Localized help (skill-code-certification / ADR-003): previously hardcoded Spanish in bin/report.js; now routed through i18n so it respects the machine locale.
2131
+ help:
2132
+ '\nShakers — local profile of your AI-tool usage\n\n'
2133
+ + 'Usage:\n usage [options]\n\n'
2134
+ + 'Options:\n'
2135
+ + ' --json Print the report as JSON on stdout\n'
2136
+ + ' --no-save Do not persist the report state (show only)\n'
2137
+ + ' --root DIR Scan DIR instead of the current directory\n'
2138
+ + ' --repos LIST Evaluate only these repos (ids or paths, comma-separated)\n'
2139
+ + ' --machine Evaluate the whole machine (every repo with AI activity)\n'
2140
+ + ' --repo Evaluate the current repo only\n'
2141
+ + ' --scope machine|repo Same as --machine / --repo (skips the picker)\n'
2142
+ + ' --all-repos Alias of --machine\n'
2143
+ + ' --roadmap Show the steps to raise your AI-usage level (hidden by default)\n'
2144
+ + ' --build-next-level Generate the next tier starter (secondary alternative)\n'
2145
+ + ' --force With --build-next-level, overwrite an existing file\n'
2146
+ + ' --lang es|en Force the language (report + prompt) instead of OS detection\n'
2147
+ + ' --consent-status Show your save decision / email / last send\n'
2148
+ + ' --consent-revoke Revoke saving (→ denied); stops sending\n'
2149
+ + ' --consent-reset Clear the decision (→ undecided); asks again\n'
2150
+ + ' --consent-email C Change the stored email, decision untouched\n'
2151
+ + ' --set-endpoint URL Save the Shakers send endpoint to\n'
2152
+ + ' ~/.config/shakers/config.json (a non-local host must be\n'
2153
+ + ' https). The env var takes precedence\n'
2154
+ + ' --show-endpoint Show the effective endpoint and where it comes from\n'
2155
+ + ' -h, --help Show this help\n\n'
2156
+ + 'The report is ALWAYS generated and shown on your machine. usage no longer\n'
2157
+ + 'prints a link: use the `report` command to build and open the full HTML report\n'
2158
+ + '(AI usage + certified Skills) you can share with your team. Before showing the\n'
2159
+ + 'result, the first time you are asked whether to SAVE it in Shakers (with your\n'
2160
+ + 'email); you are asked only once. Reopen the question with --consent-reset.\n\n'
2161
+ + 'The send destination resolves as: SHAKERS_CLI_INGEST_ENDPOINT (env) > the config\n'
2162
+ + 'file (--set-endpoint) > none. Without an endpoint, the report is shown but not sent\n'
2163
+ + 'to Shakers.\n',
2164
+ },
2165
+ // Cumulative report (skill-code-certification, reporting redesign) — English mirror.
2166
+ cumulative: {
2167
+ title: 'Your Shakers report',
2168
+ privacyNote: 'This report is generated and stored only on your machine. Nothing is sent to Shakers unless you give explicit consent.',
2169
+ updatedLabel: (when) => `Updated: ${when}`,
2170
+ unknownProject: '(unknown project)',
2171
+ },
2172
+ buildNextLevel: {
2173
+ heading: (tierKey) => `Generating the starter to reach ${tierKey}...`,
2174
+ created: (filename) => `+ created ${filename}`,
2175
+ overwritten: (filename) => `+ overwritten ${filename} (--force)`,
2176
+ skippedExists: (filename) => `${filename} already exists — not overwriting (use --force to overwrite)`,
2177
+ maxTier: "You're already at the max tier (T7): there's no next level to build.",
2178
+ noFileTarget: "The next step isn't a file this command can create — check the roadmap snippet in the report.",
2179
+ unrecognizedTier: "Couldn't determine your current tier.",
2180
+ },
2181
+ // talents-ai-score: mirrors the es catalog's `legalNotice` — see its comment.
2182
+ legalNotice: {
2183
+ label: 'LEGAL NOTICE',
2184
+ },
2185
+ consent: {
2186
+ // talents-ai-score, ADR-011: the disclosure wall (itemized sends / never sends) is RETIRED from the CLI — that content now lives in the repo's README.
2187
+ persistIntro:
2188
+ 'This report has already been generated and shown on your machine, '
2189
+ + 'always. Saving it in Shakers is optional and revocable at any '
2190
+ + "time (usage --consent-revoke): it saves your level/tier and "
2191
+ + 'structured signals derived across categories (tools, MCP, memory, '
2192
+ + 'automations, agents, technologies) — never the content of your '
2193
+ + 'files, prompts, paths or credentials. '
2194
+ + 'This same decision also authorizes, only if you accept, a '
2195
+ + 'third-party observability provider (Datadog) to capture the '
2196
+ + "content these optional AI calls send (sampled code, agent "
2197
+ + 'definitions, your answers) — it cannot be selectively deleted '
2198
+ + 'afterwards. If you decline, nothing is captured. '
2199
+ + 'Indicative data, not verified, '
2200
+ + 'not an official qualification. You are responsible for the information '
2201
+ + 'you choose to share; Shakers assumes no liability for the data you '
2202
+ + 'submit. Misuse of these tools —submitting or analyzing code that is not '
2203
+ + 'yours or that you are not authorized to analyze— may result in penalties '
2204
+ + "on your Shakers account, up to and including suspension. See this "
2205
+ + "repository's README for more detail.",
2206
+ persistQuestion: 'Save this report in Shakers? (y/n):',
2207
+ invalidAnswer: 'Answer not recognized. Reply "y" (yes) or "n" (no).',
2208
+ emailPrompt: 'Enter your email:',
2209
+ emailPromptExternal: "Leave us your email so the Shakers team can reach out (we don't verify it):",
2210
+ invalidEmail: 'Invalid email, try again.',
2211
+ notObtained: "Couldn't record your answer; you'll be asked again next time.",
2212
+ // talents-ai-score, ADR-051: this question is now asked BEFORE any AI/egress call (agent-synthesis, agent-evaluation, roadmap personalization), not after.
2213
+ noReportWithoutConsent: 'Without your consent, this report will not include AI-generated synthesis, evaluation, or roadmap this time (the rest of the report is shown as usual). Run `usage` again and accept to include them, or use --consent-reset if you want to be asked again.',
2214
+ deniedSaved: 'Understood, nothing will be saved. You can change your mind later by running the command again.',
2215
+ grantedSaved: (email) => `Thanks. From now on this report will be saved in Shakers automatically (email: ${email}, max. once per hour).`,
2216
+ grantedSavedExternal: (email) => `Thanks. We've saved your email (${email}) so the Shakers team can reach out — we haven't verified it.`,
2217
+ // talents-ai-score, issue 130: a logged-in Talent is NEVER asked for an email (their account already proves identity).
2218
+ noSessionEmail: "There's no email on file for your session yet, so this report can't be saved this time. You'll be asked again next run.",
2219
+ skipAlreadyDecided: (decision, path) =>
2220
+ `Consent already answered (${decision}) — stored at ${path}. `
2221
+ + 'Use --consent-status to view it, --consent-revoke to deny, or --consent-reset to be asked again.',
2222
+ nonInteractiveWarning:
2223
+ 'Non-interactive input (no TTY) detected: if no answer arrives via stdin, consent will not be '
2224
+ + 'saved this run and you will be asked again next time.',
2225
+ status: {
2226
+ heading: 'Consent status (saved in Shakers)',
2227
+ decisionGranted: 'Decision: granted',
2228
+ decisionDenied: 'Decision: denied',
2229
+ decisionNone: 'Decision: no decision yet',
2230
+ email: (value) => `Email: ${value || '(none)'}`,
2231
+ verificationPending: 'Email pending verification: nothing is sent to Shakers until verified (use --consent-reset to retry).',
2232
+ emailUnverifiedExternal: 'Self-affirmed email, unverified (contact for prospecting; it IS saved in Shakers).',
2233
+ lastSentAt: (value) => `Last saved: ${value || '(never)'}`,
2234
+ },
2235
+ revoked: 'Consent revoked. Nothing will be saved automatically anymore.',
2236
+ reset: 'Consent decision reset. You will be asked again on the next run.',
2237
+ emailChanged: (email) => `Email updated to ${email}. It will be used on the next save.`,
2238
+ emailInvalidCli: 'Invalid email. Usage: usage --consent-email you@example.com',
2239
+ },
2240
+ // Endpoint config (endpoint-config task): --set-endpoint / --show-endpoint copy.
2241
+ endpoint: {
2242
+ setOk: (url, path) => `Ingest endpoint saved: ${url}\n (at ${path}). The SHAKERS_CLI_INGEST_ENDPOINT environment variable, if set, takes precedence.`,
2243
+ errInsecureRemote: 'Endpoint rejected: a host other than localhost/127.0.0.1 must use https:// (the endpoint decides where your code is sent). Use an https URL or a local host.',
2244
+ errInvalidUrl: 'Endpoint rejected: invalid URL. It must be a full http(s) URL, e.g. https://your-hub/api/v1/works/usage/reports',
2245
+ errEmpty: 'Endpoint rejected: no URL provided. Usage: usage --set-endpoint https://your-hub/api/v1/works/usage/reports',
2246
+ showEnv: (url) => `Effective ingest endpoint: ${url}\n Source: SHAKERS_CLI_INGEST_ENDPOINT environment variable.`,
2247
+ showConfigFile: (url, path) => `Effective ingest endpoint: ${url}\n Source: config file (${path}).`,
2248
+ showConfigInvalid: (path) => `The config file (${path}) has an invalid or insecure endpoint; it is ignored. Fix it with usage --set-endpoint <url>.`,
2249
+ showNone: 'No ingest endpoint configured. Set SHAKERS_CLI_INGEST_ENDPOINT or use usage --set-endpoint <url>. Without it, the report is still shown but not sent to Shakers.',
2250
+ // talents-ai-score, ADR-020/021: the CLI now talks to a CHAIN of two backends — PRIMARY (certifications service, above) and FALLBACK (hub-backend, here).
2251
+ showFallbackRetired: 'Note: your config.json still has `ingestEndpointFallback`. It is no longer used: the CLI talks to a single backend. You can delete that line by hand; leaving it changes nothing.',
2252
+ confirmHostPrompt: (host) => `The host "${host}" is not localhost or a known Shakers domain. If you continue, your sampled code and AI signals will be sent there.\n Type the exact hostname to confirm (leave blank to cancel):`,
2253
+ confirmHostMismatch: "That didn't match the hostname shown. Cancelled: nothing was saved.",
2254
+ confirmHostCancelled: 'Cancelled: nothing was saved.',
2255
+ },
2256
+ backendChain: {
2257
+ saved: () => 'Saved to Shakers.',
2258
+ },
2259
+ // Why a footprint was NOT sent, in user language; null = stay silent.
2260
+ sendStatus: {
2261
+ reason: (reason) => {
2262
+ switch (reason) {
2263
+ case 'throttled':
2264
+ return 'Your report was not re-sent: you sent one less than an hour ago. It will be sent again later.';
2265
+ case 'email-unverified':
2266
+ return 'Your report was not sent to Shakers: your email is not verified yet.';
2267
+ case 'no-email':
2268
+ return 'Your report was not sent to Shakers: your email is missing.';
2269
+ case 'no-endpoint-configured':
2270
+ return 'Your report was not sent: sending to Shakers is not configured.';
2271
+ case 'network-error':
2272
+ return 'Could not send your report to Shakers (connection problem). Try again later.';
2273
+ case 'rate-limited':
2274
+ case 'service-unavailable':
2275
+ return 'Could not send your report to Shakers right now. Try again in a few minutes.';
2276
+ case 'consent-denied':
2277
+ case 'no-decision':
2278
+ return null;
2279
+ default:
2280
+ return 'Could not send your report to Shakers right now. Try again later.';
2281
+ }
2282
+ },
2283
+ },
2284
+ // Email-ownership verification (skill-code-certification / ADR-006): the OTP "wait mode" copy, shared by both binaries.
2285
+ verify: {
2286
+ sent: (email) => `We sent a verification code to ${email}.`,
2287
+ waitHint: 'Paste the code here. Press "r" then Enter to resend it, or Enter on a blank line to cancel.',
2288
+ codePrompt: 'Verification code:',
2289
+ verified: 'Email verified. Saving your report in Shakers…',
2290
+ invalidCode: 'Incorrect code. Check it and paste it again.',
2291
+ expired: 'The code has expired. Press "r" then Enter to send a new one.',
2292
+ resent: (email) => `We resent a code to ${email}.`,
2293
+ resendFailed: 'Could not resend the code. Please try again in a moment.',
2294
+ requestFailed: 'Could not send the verification code to Shakers. The report will not be saved; it has already been shown to you.',
2295
+ technicalError: 'Could not verify the email against Shakers. The report will not be saved; it has already been shown to you.',
2296
+ tooManyAttempts: 'Too many failed attempts. The email was not verified, so the report will not be saved.',
2297
+ cancelled: 'Verification cancelled. The report will not be saved (it has already been shown to you).',
2298
+ unavailable: 'Email verification is not available right now; the report will not be saved (it has already been shown to you).',
2299
+ },
2300
+ // Skill Code Certification (skill-code-certification, issues 004/006) —
2301
+ // English mirror of the `certify` catalog. Same content/invariants.
2302
+ certifyDimension: {
2303
+ help:
2304
+ 'certify — certify a DIMENSION of your role with a LiveKit interview (like onboarding).\n\n'
2305
+ + 'Usage:\n'
2306
+ + ' certify [--lang es|en] [--dimension <key|slug|n>]\n\n'
2307
+ + 'Requires an active session (run `shakers login` first).\n'
2308
+ + 'Only dimensions you can certify here and have not already certified are offered.\n',
2309
+ title: 'Certify a dimension',
2310
+ loginRequired: '"certify" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2311
+ discovering: 'Looking up the dimensions you can certify…',
2312
+ discoverFailed: (reason) => `Could not fetch your dimensions (${reason}). Try again in a moment.`,
2313
+ unmatchedNote: (keys) => `Note: some dimensions have no matching template and can't be offered here (${keys}).`,
2314
+ noneOfferable: 'There are no dimensions you can certify here right now. Check back when one becomes available.',
2315
+ mainRole: (role) => `Your main role: ${role}`,
2316
+ selectHeading: 'Pick the dimension you want to certify:',
2317
+ selectHint: '↑/↓ to move, Enter to pick, Esc to cancel.',
2318
+ selectPrompt: (count) => `Type a number (1-${count}) or Enter to cancel:`,
2319
+ selectNonInteractive: 'Non-interactive mode: pass the dimension with --dimension <key|slug|n>.',
2320
+ selectNoneChosen: 'No dimension chosen.',
2321
+ stateExpired: 'expired',
2322
+ dimensionInvalid: 'That dimension is not among the certifiable ones right now.',
2323
+ dimensionUsing: (slug) => `Chosen dimension: ${slug}`,
2324
+ creating: 'Creating the interview…',
2325
+ createFailed: (reason) => `Could not create the interview (${reason}).`,
2326
+ onCooldown: (day) => (day
2327
+ ? `You took this dimension recently. You can retake it from ${day}.`
2328
+ : 'You took this dimension recently and cannot retake it yet.'),
2329
+ needsWeb: 'This dimension has an exercise that for now is only taken on the web, not in the terminal.',
2330
+ interviewUnavailable: (reason) => `The interview could not be completed (${reason}).`,
2331
+ reportPolling: 'Computing your verdict…',
2332
+ reportTimeout: 'The verdict is still being computed. Check your profile in a few minutes.',
2333
+ reportFailed: (reason) => `Could not fetch the verdict (${reason}). Check your profile.`,
2334
+ verdictTitle: 'Verdict',
2335
+ verdictDimension: (dim) => `Dimension: ${dim}`,
2336
+ verdictBand: (band) => `Level: ${band}`,
2337
+ verdictCertified: 'Result: certified',
2338
+ verdictNotCertified: 'Result: not certified this time',
2339
+ done: 'Certification complete.',
2340
+ },
2341
+ findProjects: {
2342
+ help:
2343
+ 'find-projects — list the Projects/Positions available to you on Shakers.\n\n'
2344
+ + 'Usage:\n'
2345
+ + ' find-projects [--tab all|saved] [--page N] [--limit N]\n'
2346
+ + ' [--attendance remote|hybrid|in-person] [--country ES]\n'
2347
+ + ' [--recommended] [--lang es|en] [--json]\n\n'
2348
+ + 'Requires an active session (run `shakers login` first).\n'
2349
+ + 'Ordered by match desc. Use --page to paginate; --tab saved for your bookmarks.\n'
2350
+ + '--recommended shows only the ones with a match (client-side filter of this page).\n',
2351
+ loginRequired: '"find-projects" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2352
+ title: 'Available projects',
2353
+ titleSaved: 'Saved projects',
2354
+ loading: 'Looking up the projects available to you…',
2355
+ fetchFailed: (reason) => `Could not fetch the projects (${reason}). Try again in a moment.`,
2356
+ noneAvailable: 'There are no projects available to you right now. Check back later.',
2357
+ noneOnPage: 'No more projects on this page. Try a lower --page.',
2358
+ untitled: 'Untitled',
2359
+ companyRestricted: 'company withheld',
2360
+ hoursPerMonth: (n) => `${n} h/month`,
2361
+ budgetUnknown: 'budget not available',
2362
+ matchScore: (n) => `match ${n}`,
2363
+ savedBadge: 'saved',
2364
+ projectLabel: (name) => `Project: ${name}`,
2365
+ attendanceIgnored: 'Note: invalid --attendance (use remote|hybrid|in-person); filter ignored.',
2366
+ countryIgnored: 'Note: invalid --country (use a 2-letter ISO code, e.g. ES); filter ignored.',
2367
+ showing: (from, to, total) => `Showing ${from}–${to} of ${total}`,
2368
+ morePages: (next) => `--page ${next} to see more`,
2369
+ recommendedOnPage: (count) => `${count} recommended on this page`,
2370
+ recommendedNoneFallback: 'No recommended for you right now; showing the available ones.',
2371
+ done: 'Listing complete.',
2372
+ },
2373
+ showProject: {
2374
+ help:
2375
+ 'show-project — show the detail of a project/position.\n\n'
2376
+ + 'Usage:\n show-project <number|id> [--lang es|en] [--json]\n\n'
2377
+ + 'Requires an active session (run `shakers login` first).\n',
2378
+ loginRequired: '"show-project" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2379
+ idRequired: 'Missing id. Usage: show-project <number|id>.',
2380
+ refNoCache: 'No recent listing. Run shakers find-projects first and use the number shown (or pass an id).',
2381
+ refOutOfRange: (count) => `That number is not in the last listing (there are ${count}). Run shakers find-projects again.`,
2382
+ loading: 'Loading the detail…',
2383
+ fetchFailed: (reason) => `Could not fetch the detail (${reason}). Try again in a moment.`,
2384
+ notFound: 'That project/position was not found.',
2385
+ notVisible: 'That project is not available to you right now.',
2386
+ untitled: 'Untitled',
2387
+ companyRestricted: 'company withheld',
2388
+ companyLine: (name) => `Company: ${name}`,
2389
+ projectLine: (name) => `Project: ${name}`,
2390
+ hoursPerMonth: (n) => `${n} h/month`,
2391
+ budgetUnknown: 'budget not available',
2392
+ matchScore: (n) => `match ${n}`,
2393
+ skillsLine: (s) => `Skills: ${s}`,
2394
+ languagesLine: (s) => `Languages: ${s}`,
2395
+ savedBadge: 'saved',
2396
+ appliedBadge: 'already applied',
2397
+ canApplyBadge: 'you can apply',
2398
+ descriptionHeading: 'Description',
2399
+ goalsHeading: 'Goals',
2400
+ faqsHeading: 'FAQs',
2401
+ done: 'Detail complete.',
2402
+ },
2403
+ savedPositions: {
2404
+ helpSave: 'save-project — bookmark a project/position.\n\nUsage:\n save-project <number|id> [--json]\n\nRequires an active session (shakers login).',
2405
+ helpUnsave: 'unsave-project — remove a project/position from your saved list.\n\nUsage:\n unsave-project <number|id> [--json]\n\nRequires an active session (shakers login).',
2406
+ loginRequired: 'This command requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2407
+ saveIdRequired: 'Missing id. Usage: save-project <number|id>.',
2408
+ unsaveIdRequired: 'Missing id. Usage: unsave-project <number|id>.',
2409
+ refNoCache: 'No recent listing. Run shakers find-projects first and use the number shown (or pass an id).',
2410
+ refOutOfRange: (count) => `That number is not in the last listing (there are ${count}). Run shakers find-projects again.`,
2411
+ saving: 'Saving…',
2412
+ unsaving: 'Removing from saved…',
2413
+ notFound: 'That project/position was not found.',
2414
+ failed: (reason) => `Could not complete (${reason}). Try again.`,
2415
+ saved: 'Saved.',
2416
+ unsaved: 'Removed from your saved list.',
2417
+ },
2418
+ invitations: {
2419
+ help: 'invitations — your unread project invitations.\n\nUsage:\n invitations [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2420
+ loginRequired: '"invitations" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2421
+ title: 'Your invitations',
2422
+ loading: 'Looking up your invitations…',
2423
+ fetchFailed: (reason) => `Could not fetch the invitations (${reason}). Try again.`,
2424
+ none: 'You have no unread invitations.',
2425
+ untitled: 'Unnamed project',
2426
+ hasChat: 'with chat',
2427
+ countNote: (n) => `Total: ${n}.`,
2428
+ done: 'Listing complete.',
2429
+ },
2430
+ certifications: {
2431
+ help: 'certifications — your certified, uncertified and expired dimensions.\n\nUsage:\n certifications [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2432
+ loginRequired: '"certifications" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2433
+ title: 'Your certifications',
2434
+ loading: 'Loading your certifications…',
2435
+ fetchFailed: (reason) => `Could not fetch the certifications (${reason}). Try again.`,
2436
+ mainRole: (name) => `Main role: ${name}`,
2437
+ noMainRole: 'You have no main role assigned yet.',
2438
+ noDimensions: 'No dimensions for your role yet.',
2439
+ untitled: 'Dimension',
2440
+ headingCertified: 'Certified',
2441
+ headingExpired: 'Expired',
2442
+ headingUncertified: 'Uncertified',
2443
+ done: 'Listing complete.',
2444
+ },
2445
+ roles: {
2446
+ addHelp: 'add-role — add a role to your profile (a growth role).\n\nUsage:\n add-role [--lang es|en]\n\nRequires an active session (shakers login).',
2447
+ changeHelp: 'change-role — switch your main role to one you already have.\n\nUsage:\n change-role [--lang es|en]\n\nRequires an active session (shakers login).',
2448
+ loginRequired: 'This command requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2449
+ selectHint: 'Use ↑/↓ and Enter to choose.',
2450
+ cancelled: 'No changes.',
2451
+ loadingAvailable: 'Loading available roles…',
2452
+ loadingHeld: 'Loading your roles…',
2453
+ savingAdd: 'Adding the role…',
2454
+ savingMain: 'Saving your main role…',
2455
+ addTitle: 'Add a role',
2456
+ addPrompt: 'Which role do you want to add to your profile?',
2457
+ noneAvailable: 'You already have every available role.',
2458
+ addedOk: (name) => `Role added: ${name}.`,
2459
+ alreadyAssigned: (name) => `You already had that role: ${name}.`,
2460
+ addFailed: (reason) => `Could not add the role (${reason}). Try again.`,
2461
+ changeTitle: 'Change your main role',
2462
+ changePrompt: 'Which one do you want as your main role?',
2463
+ currentMain: (name) => `Current main role: ${name}`,
2464
+ noOtherRoles: 'You have no other roles to choose from. Add one first with: shakers add-role',
2465
+ mainRoleTitle: 'Your main role',
2466
+ mainRolePrompt: 'Choose your main role',
2467
+ mainRolePending: 'We are still preparing your roles. You can set your main role later with: shakers change-role',
2468
+ mainRoleSkipped: 'OK, we did not change your main role.',
2469
+ mainRoleUnchanged: (name) => `Your main role stays: ${name}.`,
2470
+ mainSetOk: (name) => `Main role: ${name}.`,
2471
+ setMainFailed: (reason) => `Could not set the main role (${reason}). Try again.`,
2472
+ },
2473
+ profile: {
2474
+ help: 'me (alias profile) — your summary: role, rate and your work with AI.\n\nUsage:\n me [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2475
+ loginRequired: '"me" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2476
+ title: 'Your profile',
2477
+ loading: 'Loading your summary…',
2478
+ fetchFailed: (reason) => `Could not fetch your summary (${reason}). Try again.`,
2479
+ headline: (h) => `Headline: ${h}`,
2480
+ role: (name) => `Main role: ${name}`,
2481
+ freelanceType: (t) => `Work modality: ${t}`,
2482
+ completion: (pct) => `Profile completed: ${pct}%`,
2483
+ rate: (v) => `Per-project rate: ${v}`,
2484
+ rateNotSet: 'not set',
2485
+ availability: (open, hours) => `Availability: ${open} · ${hours} h/month`,
2486
+ availYes: 'open to work',
2487
+ availNo: 'not available',
2488
+ availUnknown: 'not set',
2489
+ languages: (codes) => `Languages: ${codes}`,
2490
+ skillsCount: (n) => `Skills: ${n}`,
2491
+ agentsCount: (n) => `Agents: ${n}`,
2492
+ cell: (c) => `AI fluency cell: ${c}`,
2493
+ setup: (tier, level) => `Setup: ${tier} (${level})`,
2494
+ usage: (level) => `Usage: ${level}`,
2495
+ aiNative: 'AI-native',
2496
+ visionHeading: 'Your vision on AI',
2497
+ howIWorkHeading: 'How you work with AI',
2498
+ noAiProfile: "You don't have an AI-work profile yet — it's built from your AI-usage evaluation. Run `shakers ai-usage` to generate it, then run this again.",
2499
+ done: 'Summary complete.',
2500
+ },
2501
+ applications: {
2502
+ help: 'applications — the projects you applied to and their status.\n\nUsage:\n applications [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2503
+ loginRequired: '"applications" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2504
+ title: 'Your applications',
2505
+ loading: 'Looking up your applications…',
2506
+ fetchFailed: (reason) => `Could not fetch your applications (${reason}). Try again.`,
2507
+ none: 'You have not applied to any project yet.',
2508
+ untitled: 'Unnamed project',
2509
+ statusLine: (s) => `Status: ${s}`,
2510
+ statusUnknown: 'unknown',
2511
+ countNote: (n) => `Total: ${n}.`,
2512
+ done: 'Listing complete.',
2513
+ },
2514
+ availability: {
2515
+ help: 'availability — view or set your availability.\n\nUsage:\n availability [--lang es|en] [--json] view\n availability --set [--lang es|en] set (interactive, with confirmation)\n\nRequires an active session (shakers login).',
2516
+ loginRequired: '"availability" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2517
+ title: 'Your availability',
2518
+ loading: 'Loading your availability…',
2519
+ fetchFailed: (reason) => `Could not fetch your availability (${reason}). Try again.`,
2520
+ notFound: 'You have not set your availability yet.',
2521
+ openToWork: (v) => `Open to work: ${v}`,
2522
+ monthlyHours: (v) => `Monthly hours: ${v}`,
2523
+ onlyRemote: (v) => `Remote only: ${v}`,
2524
+ location: (v) => `Location: ${v}`,
2525
+ expires: (v) => `Expires: ${v}`,
2526
+ yes: 'yes',
2527
+ no: 'no',
2528
+ unknown: 'not set',
2529
+ done: 'Listing complete.',
2530
+ setTitle: 'Set your availability',
2531
+ setNeedsInteractive: 'Setting availability needs an interactive terminal (not a pipe). Run it directly in your terminal.',
2532
+ askOpenToWork: 'Are you open to work?',
2533
+ askMonthlyHours: 'Monthly hours (number, e.g. 40; Enter to keep):',
2534
+ monthlyHoursInvalid: 'Invalid hours. Type only the number (e.g. 40).',
2535
+ askOnlyRemote: 'Remote work only?',
2536
+ setDiff: (open, hours, remote) => `New: open=${open} · ${hours} h/month · remote-only=${remote}`,
2537
+ setConfirm: 'This updates your profile. Confirm? (y/n):',
2538
+ setCancelled: 'Cancelled. Your availability was not changed.',
2539
+ setSaving: 'Saving your availability…',
2540
+ setFailed: (reason) => `Could not save the availability (${reason}). Try again.`,
2541
+ setDone: 'Availability updated.',
2542
+ },
2543
+ mcpInstall: {
2544
+ title: 'Connecting the Shakers MCP',
2545
+ clients: { claudeDesktop: 'Claude Desktop', claudeCode: 'Claude Code', cursor: 'Cursor', geminiCli: 'Gemini CLI', codex: 'Codex (and ChatGPT in Codex mode)', vscode: 'VS Code', windsurf: 'Windsurf' },
2546
+ status: {
2547
+ configured: 'connected',
2548
+ unchanged: 'already connected',
2549
+ 'not-found': 'not installed on this machine',
2550
+ 'invalid-json': 'its config is not valid JSON; left untouched',
2551
+ failed: 'could not connect',
2552
+ },
2553
+ removedLegacy: 'Replaces the old "shakers-ai-usage" entry.',
2554
+ found: (apps) => `Found: ${apps}.`,
2555
+ confirm: 'Connect Shakers to them so you can use it from your AI? [Y/n]',
2556
+ declined: 'Nothing touched. To connect it later, run the installer again.',
2557
+ noTerminal: 'There is no terminal to ask you on, so nothing was touched. Run the installer again in your terminal to connect it.',
2558
+ restart: 'Restart any of these apps that is open so it loads Shakers.',
2559
+ noneFound: 'Found no supported AI app on this machine.',
2560
+ },
2561
+ rate: {
2562
+ help: 'rate — view or set your per-project rate.\n\nUsage:\n rate [--lang es|en] [--json] view your rate\n rate --set [--lang es|en] set it (interactive, with confirmation)\n\nRequires an active session (shakers login).',
2563
+ loginRequired: '"rate" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2564
+ title: 'Your per-project rate',
2565
+ loading: 'Loading your rate…',
2566
+ fetchFailed: (reason) => `Could not fetch the rate (${reason}). Try again.`,
2567
+ notFound: 'You have not set your rate yet.',
2568
+ notSet: 'not set',
2569
+ fullTime: (v) => `Full-time project: ${v}`,
2570
+ partTime: (v) => `Part-time project: ${v}`,
2571
+ done: 'Listing complete.',
2572
+ setTitle: 'Set your per-project rate',
2573
+ setNeedsInteractive: 'Setting the rate needs an interactive terminal (not a pipe). Run it directly in your terminal.',
2574
+ setWhich: 'Which modality do you want to set?',
2575
+ modalityFull: 'full-time',
2576
+ modalityPart: 'part-time',
2577
+ setAmount: (label) => `Amount for ${label} (number only):`,
2578
+ setAmountInvalid: 'Invalid amount. It must be a number between 0 and 999999.99.',
2579
+ setCurrency: 'Currency:',
2580
+ setDiff: (label, oldV, newV) => `${label}: ${oldV} → ${newV}`,
2581
+ setConfirm: 'This changes your price. Confirm? (y/n):',
2582
+ setCancelled: 'Cancelled. Your rate was not changed.',
2583
+ setSaving: 'Saving your rate…',
2584
+ setFailed: (reason) => `Could not save the rate (${reason}). Try again.`,
2585
+ setDone: (label, newV) => `${label} rate updated: ${newV}.`,
2586
+ },
2587
+ lang: {
2588
+ help: 'lang — view or set your languages and level.\n\nUsage:\n lang [--lang es|en] [--json] view\n lang --set [--lang es|en] add/update (interactive, with confirmation)\n\nRequires an active session (shakers login).',
2589
+ loginRequired: '"lang" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2590
+ title: 'Your languages',
2591
+ loading: 'Loading your languages…',
2592
+ loadingCatalog: 'Loading the language catalog…',
2593
+ fetchFailed: (reason) => `Could not fetch your languages (${reason}). Try again.`,
2594
+ catalogFailed: (reason) => `Could not load the language catalog (${reason}). Try again.`,
2595
+ none: 'You have not added any languages yet.',
2596
+ line: (name, level) => `${name}: ${level}`,
2597
+ unknownLang: 'language',
2598
+ unknownLevel: 'no level',
2599
+ levelLabel: (v) => ({ NATIVE: 'native', ADVANCED: 'advanced', INTERMEDIATE: 'intermediate', INTERMEDIATE_WRITTEN: 'intermediate (written)' }[v] || v),
2600
+ done: 'Listing complete.',
2601
+ setTitle: 'Set a language',
2602
+ setNeedsInteractive: 'Setting languages needs an interactive terminal (not a pipe). Run it directly in your terminal.',
2603
+ pickLanguage: 'Pick the language:',
2604
+ pickLevel: 'Pick the level:',
2605
+ setDiff: (name, level) => `New: ${name} → ${level}`,
2606
+ setConfirm: 'This updates your languages. Confirm? (y/n):',
2607
+ setCancelled: 'Cancelled. Your languages were not changed.',
2608
+ setSaving: 'Saving your languages…',
2609
+ setFailed: (reason) => `Could not save the languages (${reason}). Try again.`,
2610
+ setDone: (name) => `Language updated: ${name}.`,
2611
+ },
2612
+ socials: {
2613
+ help: 'socials — view or set your social links.\n\nUsage:\n socials [--lang es|en] [--json] view\n socials --set [--lang es|en] add/update/remove (interactive, with confirmation)\n\nRequires an active session (shakers login).',
2614
+ loginRequired: '"socials" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2615
+ title: 'Your social links',
2616
+ loading: 'Loading your links…',
2617
+ fetchFailed: (reason) => `Could not fetch your links (${reason}). Try again.`,
2618
+ none: 'You have not added any social links yet.',
2619
+ line: (label, url) => `${label}: ${url}`,
2620
+ networkLabel: (k) => ({ linkedin: 'LinkedIn', github: 'GitHub', website: 'Website', twitter: 'Twitter/X', instagram: 'Instagram', facebook: 'Facebook', dribbble: 'Dribbble', behance: 'Behance' }[k] || k),
2621
+ done: 'Listing complete.',
2622
+ setTitle: 'Set a social link',
2623
+ setNeedsInteractive: 'Setting links needs an interactive terminal (not a pipe). Run it directly in your terminal.',
2624
+ pickNetwork: 'Pick the network:',
2625
+ askUrl: (label) => `${label} URL (empty to remove it):`,
2626
+ setDiffSet: (label, url) => `New: ${label} → ${url}`,
2627
+ setDiffClear: (label) => `Remove: ${label}`,
2628
+ setConfirm: 'This updates your links. Confirm? (y/n):',
2629
+ setCancelled: 'Cancelled. Your links were not changed.',
2630
+ setSaving: 'Saving your links…',
2631
+ setFailed: (reason) => `Could not save the links (${reason}). Try again.`,
2632
+ setDone: (label) => `${label} updated.`,
2633
+ setCleared: (label) => `${label} removed.`,
2634
+ },
2635
+ experiences: {
2636
+ help: 'experiences — your work experiences (read-only).\n\nUsage:\n experiences [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2637
+ loginRequired: '"experiences" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2638
+ title: 'Your experiences',
2639
+ loading: 'Loading your experiences…',
2640
+ fetchFailed: (reason) => `Could not fetch your experiences (${reason}). Try again.`,
2641
+ none: 'You have not added any experiences yet.',
2642
+ untitled: 'Untitled',
2643
+ current: 'present',
2644
+ datesLabel: (r) => `Dates: ${r}`,
2645
+ locationLabel: (l) => `Location: ${l}`,
2646
+ skillsLabel: (s) => `Skills: ${s}`,
2647
+ countNote: (n) => `Total: ${n}.`,
2648
+ done: 'Listing complete.',
2649
+ },
2650
+ portfolios: {
2651
+ help: 'portfolios — your portfolio pieces (read-only).\n\nUsage:\n portfolios [--lang es|en] [--json]\n\nRequires an active session (shakers login).',
2652
+ loginRequired: '"portfolios" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
2653
+ title: 'Your portfolio',
2654
+ loading: 'Loading your portfolio…',
2655
+ fetchFailed: (reason) => `Could not fetch your portfolio (${reason}). Try again.`,
2656
+ none: 'You have not added any portfolio pieces yet.',
2657
+ untitled: 'Untitled',
2658
+ current: 'ongoing',
2659
+ datesLabel: (r) => `Dates: ${r}`,
2660
+ locationLabel: (l) => `Location: ${l}`,
2661
+ skillsLabel: (s) => `Skills: ${s}`,
2662
+ countNote: (n) => `Total: ${n}.`,
2663
+ done: 'Listing complete.',
2664
+ },
2665
+ alma: {
2666
+ help: 'alma — interactive mini-shell to converse with Alma, your Shakers AI assistant.\n\nUsage:\n alma open the conversation (type line by line; "exit" to quit)\n alma --lang es|en\n\nRequires an active session (shakers login). The conversation keeps its context across turns.',
2667
+ loginRequired: '`alma` needs an active session. Sign in: shakers login',
2668
+ needTty: '`alma` is interactive and needs a terminal.',
2669
+ title: 'Alma',
2670
+ tagline: 'your Shakers AI assistant',
2671
+ replHint: 'Type your message and press Enter. Type "exit" (or Enter on an empty line) to quit.',
2672
+ prompt: 'you:',
2673
+ contextLabel: 'Reading your project…',
2674
+ contextConsentHeader: 'Alma can use a (code-free) map of this project to answer better. Share it?',
2675
+ contextConsentHint: 'Structure only: name, services, agents, technologies. Never your code.',
2676
+ contextConsentYes: 'Yes, share the project context',
2677
+ contextConsentNo: 'No, chat without context',
2678
+ contextConsentDeclined: 'OK, I will talk to Alma without the project context.',
2679
+ thinking: 'Alma is thinking…',
2680
+ bye: 'See you soon.',
2681
+ confirmPrompt: 'Confirm this action?',
2682
+ confirmYes: 'Yes, go ahead',
2683
+ confirmNo: 'No, cancel',
2684
+ irreversible: '⚠ This action is irreversible.',
2685
+ webLink: (to) => `On the web this would open: ${to}`,
2686
+ failed: (reason) => `Could not reach Alma (${reason}). Try again in a moment.`,
2687
+ },
2688
+ certify: {
2689
+ disclaimer:
2690
+ 'LEGAL DISCLAIMER — read before continuing:\n'
2691
+ + ' certify sends data about your project to Shakers to certify your TECHNICAL\n'
2692
+ + ' Skills (the ones in your catalog, from your code). It does NOT assess how you\n'
2693
+ + ' orchestrate agents.\n'
2694
+ + ' In this phase (resolve) it sends your email and the NAMES of the detected\n'
2695
+ + ' technologies; the later certification phase will send code snippets.\n'
2696
+ + ' You are SOLELY responsible for ensuring you own, or are authorized to analyze,\n'
2697
+ + " this project's code. Shakers assumes no liability for the code you submit.\n"
2698
+ + ' Submitting code that is not yours or that you are not authorized to analyze is\n'
2699
+ + ' a misuse of these tools and may result in penalties on your Shakers account, up\n'
2700
+ + ' to and including suspension. Do NOT use this tool on a third party\'s code\n'
2701
+ + ' (e.g. a client under NDA). Skill scores are indicative and unverified, not an\n'
2702
+ + ' official qualification.',
2703
+ disclaimerQuestion: 'Do you accept and continue? (y/n):',
2704
+ disclaimerAcceptedFlag: 'Disclaimer accepted via --accept-disclaimer.',
2705
+ disclaimerNonInteractive:
2706
+ 'Non-interactive input and no --accept-disclaimer: cannot obtain explicit '
2707
+ + 'acceptance. Aborting (nothing was sent).',
2708
+ disclaimerDeclined: 'You did not accept the disclaimer. Nothing was sent.',
2709
+ disclaimerInvalidAnswer: 'Answer not recognized. Reply "y" (yes) or "n" (no).',
2710
+ disclaimerNoAnswer: 'No answer obtained. Nothing was sent.',
2711
+ report: {
2712
+ heading: 'TECHNICAL Skill certification result',
2713
+ noInterviewNote: 'Preliminary, no interview: this Skill\'s combined certification requires completing its interview.',
2714
+ disclaimer:
2715
+ 'Note: the level is determined from an anchored rubric with a fixed formula, so the '
2716
+ + 'aggregation is deterministic — the same per-dimension judgments always yield the '
2717
+ + 'same level. Only the model’s per-criterion judgments may vary slightly between '
2718
+ + 'runs. It is an indicative assessment, not an official Client-facing certification.',
2719
+ partialSampleWarning:
2720
+ 'Partial sample: due to size limits the assessment is based on a sample of the code, '
2721
+ + 'not the whole project.',
2722
+ // ADR-016 skill levels (by decision power, NOT points) — replace the
2723
+ // numeric grade in the skill output. `key` from skillLevelForScore.
2724
+ levelLine: (label) => `Level: ${label}`,
2725
+ skillLevels: {
2726
+ middle: 'Middle',
2727
+ senior: 'Senior',
2728
+ expert: 'Expert',
2729
+ na: '—',
2730
+ },
2731
+ // ADR-024 rubric dimensions.
2732
+ dimensionsLabel: 'Dimensions',
2733
+ dimensionNA: 'N/A',
2734
+ dimensionLabels: {
2735
+ idiomatic: 'Idiomatic usage',
2736
+ correctness: 'Correctness & robustness',
2737
+ depth: 'Depth',
2738
+ structure: 'Structure & maintainability',
2739
+ testing: 'Testing',
2740
+ },
2741
+ rationaleLabel: 'Why',
2742
+ improvementsLabel: 'Suggested improvements',
2743
+ // ADR-025 authorship receipt (attribution, NOT cryptographic proof).
2744
+ receipt: {
2745
+ label: 'Authorship',
2746
+ repoLabel: 'Repo',
2747
+ commitRangeLabel: 'Commit range',
2748
+ filesLabel: 'File',
2749
+ authorLabel: 'Author (git)',
2750
+ confirmedLabel: 'Authors confirmed against the identity',
2751
+ attributedYes: '✓',
2752
+ attributedNo: '✗',
2753
+ summary: (attributed, total) => `${attributed}/${total} files attributed to the identity`,
2754
+ note:
2755
+ 'Attribution trail based on the git author (self-asserted); it is not '
2756
+ + 'cryptographic proof of authorship.',
2757
+ },
2758
+ sampleSummary: (included, candidate, estTokens) =>
2759
+ `Sample: ${included}/${candidate} files · ~${estTokens} tokens`,
2760
+ partialTag: '(partial sample)',
2761
+ notCertified: 'This Skill could not be certified in this run.',
2762
+ notSampleableNote: (technology) =>
2763
+ `No sampling is defined for the technology "${technology}": it can't be code-certified yet.`,
2764
+ htmlTitle: 'Technical Skill Certification · Shakers',
2765
+ noItems: 'No certification results to show.',
2766
+ costNote:
2767
+ 'Cost note: a fair amount of code is analyzed per Skill (up to ~150k tokens/Skill), '
2768
+ + 'which has a per-run cost.',
2769
+ remediationHeading: 'Prompt to apply the improvements',
2770
+ remediationHint: 'Copy this prompt and paste it into your AI tool (Claude Code, Cursor…) to apply the improvements.',
2771
+ remediationIntro: (skillName, technology) =>
2772
+ `Help me improve my ${skillName}${technology ? ` (${technology})` : ''} code in this project. `
2773
+ + 'A code review flagged these improvements:',
2774
+ remediationClosing:
2775
+ 'Apply them directly in my project: create or edit whatever is needed, follow the conventions I already use, '
2776
+ + 'and briefly explain what you changed and why.',
2777
+ remediationCopyLabel: 'Copy',
2778
+ remediationCopiedLabel: 'Copied ✓',
2779
+ },
2780
+ },
2781
+ // Branded mini-shell chrome (skill-code-certification / ADR-014).
2782
+ login: {
2783
+ intro: 'Log in with your Shakers email and password.',
2784
+ sessionExpiredIntro: 'Your previous session expired. Log in again with your email and password.',
2785
+ alreadyLoggedIn: 'You are already logged in. Use `logout` if you want to switch accounts.',
2786
+ emailPrompt: 'Email:',
2787
+ passwordPrompt: 'Password:',
2788
+ emailInvalid: 'Invalid email. Try again.',
2789
+ needInput: 'Email and password are required to log in.',
2790
+ success: 'Logged in. Your report no longer includes the roadmap or the improvement suggestions: your level comes from certifying.',
2791
+ expiresAt: (iso) => `The session expires: ${iso}. When it does I will tell you, and you can log in again with \`login\`.`,
2792
+ loggedOut: 'Logged out. You are back to the full report, with roadmap and suggestions.',
2793
+ notLoggedIn: 'No session was active. Nothing to log out of.',
2794
+ errorInvalidCredentials: 'Wrong credentials: check your email and password and try again.',
2795
+ errorNoEmailPassword: 'This account exists but has no email password: you registered with Google or LinkedIn. If it was Google, use `login --google`. If it was LinkedIn, sign in on the web to add a password for now.',
2796
+ errorUpstream: "Shakers' identity server is not responding right now. It is not your password; try again in a while.",
2797
+ errorUnreachable: 'Could not reach Shakers. Check your connection and run `login` again.',
2798
+ errorConfig: 'The server address is invalid. Check the endpoint configuration (`usage --show-endpoint`).',
2799
+ errorNoEndpoint: 'No endpoint configured. login uses the same base as usage: set SHAKERS_CLI_INGEST_ENDPOINT or use `usage --set-endpoint <url>`.',
2800
+ errorPersist: 'Login succeeded, but the session could not be saved to disk. Check the permissions of ~/.config/shakers and try again.',
2801
+ errorGeneric: 'Could not log in. Try again.',
2802
+ // ADR-046 — Google login by loopback (`login --google`).
2803
+ googleIntro: 'Log in with Google. Your browser will open to authorize; when you are done, come back here.',
2804
+ googleOpening: 'Opening your browser to log in with Google…',
2805
+ googlePasteUrl: 'If your browser does not open on its own, paste this URL into it:',
2806
+ googleWaiting: 'Waiting for you to finish signing in in the browser…',
2807
+ googleErrorConfigFailed: "Could not fetch the Google login configuration from Shakers. Check your connection and the endpoint (`usage --show-endpoint`) and try again.",
2808
+ googleErrorConfigInvalid: 'The Google configuration Shakers returned is incomplete. This is a server-side problem, not yours; try again later.',
2809
+ googleErrorPortInUse: 'The local port the Google login uses is busy with another process. Close it and run `login --google` again.',
2810
+ googleErrorTimeout: 'Timed out waiting for you to finish signing in in the browser. Run `login --google` again when you are ready.',
2811
+ googleErrorState: 'The browser response did not match what was expected (possible interference). For safety no session was started; try again.',
2812
+ googleErrorDenied: 'You cancelled access in Google (or it was not granted). No session was started.',
2813
+ googleErrorGeneric: 'Could not log in with Google. Try again.',
2814
+ // Google via device flow (RFC 8628): the Hub owns Google config; you open a URL and type a code.
2815
+ deviceIntro: (name) => `Sign in with ${name}. Open the link below and enter the code to authorize; then come back here.`,
2816
+ deviceVisit: 'Open this page in your browser:',
2817
+ deviceCodeLabel: (code) => `and enter this code: ${code}`,
2818
+ deviceWaiting: 'Waiting for you to authorize in the browser…',
2819
+ deviceErrorExpired: 'The Google sign-in request expired before it was authorized. Run `login --google` again when you are ready.',
2820
+ deviceErrorDenied: 'The Google sign-in was cancelled or not granted. No session was started.',
2821
+ deviceErrorGeneric: 'Could not sign in with Google. Try again.',
2822
+ // Interactive method picker (`login` with no --email/--google/--provider): reuses the SAME single-select picker `certify agents` uses to choose an agent (src/interactive-select.js).
2823
+ chooseMethodHeading: 'How do you want to log in?',
2824
+ methodEmail: 'Email and password',
2825
+ methodGoogle: 'Google',
2826
+ methodLinkedin: 'LinkedIn',
2827
+ selectHint: 'Arrows ↑/↓ to move · enter to pick · esc to cancel',
2828
+ chooseMethodPrompt: (max) => `Number (1-${max}), empty to cancel: `,
2829
+ },
2830
+ // `start` (talents-ai-score, ADR-054/055): the tool for a talent ALREADY registered in Shakers who wants to complete their agentic profile.
2831
+ start: {
2832
+ help:
2833
+ 'start — complete your agentic talent profile in Shakers: log in, run the AI-usage\n'
2834
+ + "evaluation if you haven't already, and from a single menu add or certify skills and agents.\n\n"
2835
+ + 'Usage:\n'
2836
+ + ' start [options]\n\n'
2837
+ + 'Options:\n'
2838
+ + ' --root DIR Analyze DIR instead of the current directory\n'
2839
+ + ' --lang es|en Force the output language\n'
2840
+ + ' -h, --help Show this help (does not log in, does not scan anything)\n\n'
2841
+ + 'Requires an active login session (or it will ask you to log in): the same Shakers\n'
2842
+ + 'credentials `login` uses. If this is the first time you run the tool in this project, the\n'
2843
+ + 'AI-usage evaluation (`usage`) runs automatically before the menu is shown.',
2844
+ intro:
2845
+ "Let's get started: we'll confirm your session, and if this is the first run for this project, we'll generate your AI-usage evaluation before showing the menu.",
2846
+ alreadyLoggedIn: (email) => `You are already logged in${email ? ` as ${email}` : ''}.`,
2847
+ loginCancelled: 'No session was started, so we cannot continue. Run `start` again whenever you want to log in.',
2848
+ runningUsageFirst:
2849
+ "You don't have an AI-usage report for this project yet — it's the foundation of your agentic profile. We'll generate it first.",
2850
+ menuHeading: 'What do you want to do?',
2851
+ menuHint: 'Arrows ↑/↓ to move · enter to pick · esc to cancel',
2852
+ menuPrompt: (max) => `Number (1-${max}), empty to cancel: `,
2853
+ menuRerun: 'Run the AI-usage evaluation again',
2854
+ // dueño (2026-08-12): renamed — the label said "Add" but the action is declaring/uploading what was DETECTED to the Shakers profile, not creating something new from scratch.
2855
+ menuAddSkills: 'Upload skills to profile',
2856
+ menuAddAgents: 'Upload agents to profile',
2857
+ // talents-ai-score, ADR-059: "Add project to portfolio" — grouped with skills/agents, before certifying (declare things on your profile, then certify them).
2858
+ menuAddPortfolio: 'Add project to portfolio',
2859
+ menuCertifySkills: 'Certify skills',
2860
+ menuCertifyAgents: 'Certify agents',
2861
+ menuExit: 'Exit',
2862
+ menuExitDesc: 'Ends this `start` session. Run it again whenever you want to keep completing your profile.',
2863
+ // Per-option descriptions (dueño, 2026-08-11): shown ONLY for the HIGHLIGHTED item in the interactive picker (or compactly in the numbered fallback) — never all at once.
2864
+ menuRerunDesc:
2865
+ 'Re-scans your AI setup and recomputes your AI fluency. You get your updated position on the map (Explorer → AI Native) on your profile.',
2866
+ menuAddSkillsDesc:
2867
+ "Detects your project's technologies and declares the ones you don't have yet. You get more skills on your profile (declared, unverified) → you show up in more searches.",
2868
+ menuAddAgentsDesc:
2869
+ "Detects the AI agents you've built and declares them on your profile. AI pre-drafts what it does and what you decide; you edit before saving. You get your agents as assets of your agentic profile.",
2870
+ menuAddPortfolioDesc:
2871
+ "Adds this project to your Shakers portfolio: title, type, description and skills, most of it pre-filled from your code and your git history. You get one more piece of your work visible on your profile.",
2872
+ menuCertifySkillsDesc:
2873
+ 'A challenge on your own code validates your real level in a skill. You get the skill certified at Middle/Senior/Expert → credibility and more weight in matching.',
2874
+ menuCertifyAgentsDesc:
2875
+ 'A challenge validates the agent is real, is yours, and you run it with judgment. You get the agent Certified with its traction → the strongest signal of your agentic profile.',
2876
+ menuCancelled: 'Cancelled. Nothing was done.',
2877
+ // dueño (2026-08-12): closes the menu LOOP — used both when "Exit" is explicitly chosen and when cancelling (esc / empty answer) FROM INSIDE the loop.
2878
+ menuGoodbye: '`start` session ended. Run `start` again whenever you want to keep completing your profile.',
2879
+ addAgentsComingSoon:
2880
+ "Add agents: coming soon. Your agents will become assets of your agentic profile — that record doesn't exist in Shakers yet, so there's nowhere to save them for now.",
2881
+ addSkillsNoTechnologies:
2882
+ 'No technology was recognized in this project (package.json, requirements.txt, go.mod…). No skills to add from here.',
2883
+ addSkillsErrorNoEndpoint:
2884
+ 'No endpoint configured. `start` uses the same base as `usage`: set the endpoint with `usage --set-endpoint <url>` (or SHAKERS_CLI_INGEST_ENDPOINT).',
2885
+ addSkillsResolveErrorIntro: 'Could not read your AI-usage inventory (if you have not run `shakers ai-usage` yet, do that first):',
2886
+ addSkillsNoneMatched:
2887
+ "This step needs your AI-usage evaluation first — it's what discovers the skills in your setup. Run `shakers ai-usage`, then run this command again to add them to your profile.",
2888
+ addSkillsNoHubToken:
2889
+ 'Your session is missing the permission needed to add skills to your profile. Run `login` again and try once more.',
2890
+ addSkillsSelectHeading: 'Detected skills not yet in your profile (pick one or several):',
2891
+ addSkillsSelectHint: 'Arrows ↑/↓ · space to mark · "a" to mark all · enter to confirm · esc to cancel',
2892
+ addSkillsSelectPrompt: (max) => `Comma-separated numbers (1-${max}), empty to cancel: `,
2893
+ addSkillsNoneChosen: 'No skill was chosen. Nothing was added to your profile.',
2894
+ addSkillsDeclaredOne: (skillName) => `✓ Added to your profile: ${skillName} (unverified).`,
2895
+ addSkillsDeclareFailed: (skillName, reason) => `✗ Could not add ${skillName}: ${reason}`,
2896
+ // Vision close (dueño, 2026-08-11): ONCE, after the batch — not repeated per skill (issue 106/097's lesson: the same line repeated per item is noise, not reinforcement).
2897
+ addSkillsProfileBoost: 'Every skill you add counts toward your profile: you show up in more searches.',
2898
+ addSkillsHubSessionExpired: 'Your Shakers session expired while we were adding your skills.',
2899
+ addSkillsOfferRelogin: 'Do you want to log in again now to continue? (y/n):',
2900
+ addSkillsRelaunchAfterRelogin: 'Session renewed. Retrying the skills that were still pending.',
2901
+ addSkillsRelaunchDeclined: 'OK. Run `login` and pick "Add skills" again whenever you want.',
2902
+ addSkillsReloginFailed: 'Could not log in again. The pending skills were not added.',
2903
+ addSkillsRelateNoPortfolios: "You don't have any portfolio projects yet, so the skills were added without a relation. Add one with \"Add project to portfolio\" and relate them later.",
2904
+ addSkillsRelateAsk: (skillName) => `Do you want to relate "${skillName}" to an experience in your profile? (y/n):`,
2905
+ addSkillsRelateSelectHeading: (skillName) => `Relate "${skillName}" to one or more experiences:`,
2906
+ addSkillsRelateSelectHint: 'Arrows ↑/↓ · space to mark · "a" to mark all · enter to confirm · esc to cancel',
2907
+ addSkillsRelateSelectPrompt: (max) => `Comma-separated numbers (1-${max}), empty to skip: `,
2908
+ addSkillsRelateSkipped: (skillName) => `"${skillName}" added without a relation.`,
2909
+ addSkillsRelated: (skillName, portfolioName) => ` ✓ "${skillName}" related to "${portfolioName}".`,
2910
+ addSkillsRelateFailed: (skillName, portfolioName, reason) => ` ✗ Could not relate "${skillName}" to "${portfolioName}": ${reason}`,
2911
+ // talents-ai-score, ADR-059: "Add project to portfolio".
2912
+ portfolioIntro:
2913
+ "Let's add this project to your Shakers portfolio. Most of the data is pre-filled from your code and your git history: confirm or edit each one.",
2914
+ portfolioErrorNoEndpoint:
2915
+ 'No endpoint configured. `start` uses the same base as `usage`: set the endpoint with `usage --set-endpoint <url>` (or SHAKERS_CLI_INGEST_ENDPOINT).',
2916
+ portfolioNoHubToken:
2917
+ 'Your session is missing the permission needed to add projects to your portfolio. Run `login` again and try once more.',
2918
+ portfolioDedupCheckingLabel: 'Checking your portfolio…',
2919
+ portfolioDedupCheckFailed: (reason) => `Could not check whether you already have a project with that name in your portfolio: ${reason} Continuing, but you might end up with a duplicate.`,
2920
+ portfolioDuplicateName: (name) => `You already have a project named "${name}" in your portfolio. No duplicate was created — run this option again with a different title if you want to add a distinct one.`,
2921
+ // (1) Title ← directory name, editable.
2922
+ portfolioTitlePrompt: (def) => `Project title [${def}]:`,
2923
+ // (2) Type ← PORTFOLIO/EXPERIENCE, obligatorio. Vision copy (dueño):
2924
+ // what each type means for your profile, shown ONLY on highlight.
2925
+ portfolioTypeHeading: 'What type of entry is this?',
2926
+ portfolioTypeHint: 'Arrows ↑/↓ to move · enter to pick · esc to cancel',
2927
+ portfolioTypePrompt: (max) => `Number (1-${max}): `,
2928
+ portfolioTypePortfolio: 'Portfolio',
2929
+ portfolioTypePortfolioDesc:
2930
+ 'Adds it to your profile as a portfolio piece: a sample of your work that anyone reviewing your profile can see.',
2931
+ portfolioTypeExperience: 'Experience',
2932
+ portfolioTypeExperienceDesc:
2933
+ "Adds it to your profile as professional experience (freelance or in-house): counts toward your track record, not as a piece to show.",
2934
+ // (3) Description ← AI pre-drafts after consent (ADR-052/059).
2935
+ portfolioDescriptionConsentDisclaimer:
2936
+ 'To draft a description with AI, this will send to Shakers: the project name, the detected technologies, and — if present — the README content and the package.json description.',
2937
+ portfolioDescriptionDraftLabel: 'AI-drafted description:',
2938
+ portfolioDescriptionDraftingLabel: 'Drafting a description with AI…',
2939
+ // Editable; or free-text if you decline, if it fails, or if there's no
2940
+ // endpoint.
2941
+ portfolioDescriptionEditPrompt: 'Description (edit it or press enter to keep the draft above):',
2942
+ portfolioDescriptionFreeTextPrompt: 'Description (optional, press enter to leave it blank):',
2943
+ // (4) URL ← the repo's git remote, editable.
2944
+ portfolioUrlPrompt: (def) => (def ? `Repository URL [${def}]:` : 'Repository URL (optional, none detected):'),
2945
+ // (5) Skills ← multi-select of the detected technologies, PRE-MARKED (you can remove, not add — these are the ones already resolved to a skill in your catalog).
2946
+ portfolioSkillsResolvingLabel: "Looking up this project's skills…",
2947
+ portfolioSkillsSelectHeading: "Skills for this project — added to your profile and shown on the piece (pre-marked; remove the ones that don't apply):",
2948
+ portfolioSkillsSelectHint: 'Arrows ↑/↓ · space to mark/unmark · "a" to mark/unmark all · enter to confirm · esc to cancel',
2949
+ portfolioSkillsSelectPrompt: (max) => `Comma-separated numbers (1-${max}) to change the selection, empty to keep all marked: `,
2950
+ portfolioSkillsSelectInvalid: "That answer wasn't understood. All skills stay marked.",
2951
+ // (6) Client ← optional, for freelance work.
2952
+ portfolioClientPrompt: 'Client (optional, for freelance work; press enter to leave it blank):',
2953
+ portfolioClientWebsitePrompt: 'Client website (optional, e.g. acme.com; used for its logo; press enter to skip):',
2954
+ portfolioCancelled: 'Cancelled. Nothing was added to your portfolio.',
2955
+ portfolioDeclaring: 'Adding the project to your portfolio…',
2956
+ portfolioDeclaredSuccess: (name) => `✓ "${name}" added to your portfolio.`,
2957
+ portfolioDeclareFailed: (reason) => `✗ Could not add the project to your portfolio: ${reason}`,
2958
+ portfolioHubSessionExpired: 'Your Shakers session expired while we were adding the project to your portfolio.',
2959
+ portfolioOfferRelogin: 'Do you want to log in again now to continue? (y/n):',
2960
+ portfolioRelaunchAfterRelogin: 'Session renewed. Retrying adding the project to your portfolio.',
2961
+ portfolioRelaunchDeclined: 'OK. Run `login` and pick "Add project to portfolio" again whenever you want.',
2962
+ portfolioReloginFailed: 'Could not log in again. The project was not added to your portfolio.',
2963
+ // talents-ai-score Phase 2: "Add agent to profile". Same model as the
2964
+ // portfolio (ADR-059): AI pre-drafts, the talent edits.
2965
+ agentErrorNoEndpoint:
2966
+ 'No endpoint configured. `start` uses the same base as `usage`: set the endpoint with `usage --set-endpoint <url>` (or SHAKERS_CLI_INGEST_ENDPOINT).',
2967
+ agentNoHubToken:
2968
+ 'Your session is missing the permission needed to add agents to your profile. Run `login` again and try once more.',
2969
+ agentNoAgentsDetected:
2970
+ "This step needs your AI-usage evaluation first — it's what discovers the AI agents in your setup. Run `shakers ai-usage`, then run this command again to add them to your profile.",
2971
+ agentInventoryError: (reason) => `Could not read your AI-usage inventory: ${reason}. If you have not run it yet, run \`shakers ai-usage\` first; otherwise check your connection and try again.`,
2972
+ agentDedupCheckingLabel: 'Checking your agents…',
2973
+ agentDedupCheckFailed: (reason) => `Could not check whether you already have an agent with that name on your profile: ${reason} Continuing, but you might end up with a duplicate.`,
2974
+ agentDuplicateName: (name) => `You already have an agent named "${name}" on your profile. No duplicate was created — run this option again with a different name if you want to add a distinct one.`,
2975
+ agentAlreadyAdded: (name) => `You already have the agent "${name}" on your profile; you can relate it to your experiences.`,
2976
+ agentIntro:
2977
+ "Let's add one of your agents to your Shakers profile. Pick the agent; AI pre-drafts what it does and what you decide, and you edit before saving.",
2978
+ // (1) Pick a detected agent.
2979
+ agentPickHeading: 'Which agent do you want to add to your profile?',
2980
+ agentPickHint: 'Arrows ↑/↓ to move · enter to pick · esc to cancel',
2981
+ agentPickPrompt: (max) => `Number (1-${max}): `,
2982
+ agentCancelled: 'Cancelled. No agent was added to your profile.',
2983
+ // (3) Name ← detected, editable.
2984
+ agentNamePrompt: (def) => `Agent name [${def}]:`,
2985
+ // (4) whatItDoes + humanDecides ← AI pre-drafts after consent (ADR-052).
2986
+ agentFieldsConsentDisclaimer:
2987
+ "To draft what your agent does and what you decide with AI, this will send to Shakers: the agent name, its detected tools/model/parent, and a summary of its definition (its own instructions).",
2988
+ agentDraftingLabel: 'Drafting with AI…',
2989
+ agentWhatItDoesDraftLabel: 'What it does — AI-generated draft:',
2990
+ agentWhatItDoesEditPrompt: 'What it does (edit it or press enter to keep the draft above):',
2991
+ agentWhatItDoesFreeTextPrompt: 'What your agent does (optional, press enter to leave it blank):',
2992
+ agentHumanDecidesDraftLabel: 'What you do around the agent (supervision/validation) — AI-generated draft:',
2993
+ agentHumanDecidesEditPrompt: 'What you do around the agent (edit it or press enter to keep the draft above):',
2994
+ agentHumanDecidesFreeTextPrompt: 'What you do around the agent / how you supervise it (optional, press enter to leave it blank):',
2995
+ // catalogId ← included if a prior `usage` matched the agent to the catalog.
2996
+ agentCatalogMatched: (role) => `We matched it to the Shakers agent catalog as: ${role}.`,
2997
+ agentDeclaring: 'Adding the agent to your profile…',
2998
+ agentDeclaredSuccess: (name) => `✓ "${name}" added to your profile.`,
2999
+ agentDeclareFailed: (reason) => `✗ Could not add the agent to your profile: ${reason}`,
3000
+ agentHubSessionExpired: 'Your Shakers session expired while we were adding the agent to your profile.',
3001
+ // ADR-041: AFTER the agent is declared, offer to relate it to the experiences/projects the talent already declared (portfolioId only).
3002
+ agentRelateNoPortfolios: "You don't have any experiences in your profile yet, so the agent was added without a relation. Add one with \"Add project to portfolio\" and relate it later.",
3003
+ agentRelateAsk: (agentName) => `Do you want to relate "${agentName}" to any of your experiences? (y/n):`,
3004
+ agentRelateSelectHeading: (agentName) => `Relate "${agentName}" to one or more experiences:`,
3005
+ agentRelateSkipped: (agentName) => `"${agentName}" added without a relation.`,
3006
+ agentRelated: (agentName, portfolioName) => `✓ "${agentName}" related to "${portfolioName}".`,
3007
+ agentRelateFailed: (agentName, reason) => `✗ Could not relate "${agentName}" to your experiences: ${reason}`,
3008
+ portfolioAddSkillsResolvingLabel: 'Looking up other skills for your profile…',
3009
+ portfolioAddSkillsIntro:
3010
+ "We also detected other skills in this project that aren't in your Shakers profile yet (besides the ones you just linked to the portfolio).",
3011
+ portfolioAddSkillsAllAlreadyDeclared: "All this project's skills are already on your profile.",
3012
+ portfolioAddSkillsHeading: "Skills for your profile (pre-marked; remove the ones that don't apply):",
3013
+ portfolioAddSkillsHint: 'Arrows ↑/↓ · space to mark/unmark · "a" to mark/unmark all · enter to confirm · esc to cancel',
3014
+ portfolioAddSkillsPrompt: (max) => `Comma-separated numbers (1-${max}) to change the selection, empty to keep all marked: `,
3015
+ portfolioAddSkillsInvalid: "That answer wasn't understood. All skills stay marked.",
3016
+ errorReason: {
3017
+ 'no-endpoint': 'no endpoint configured.',
3018
+ 'no-hub-token': 'your session is missing the hub permission needed; run `login` again.',
3019
+ 'hub-session-expired': 'your hub session expired.',
3020
+ 'network-error': 'network error.',
3021
+ timeout: 'the request timed out.',
3022
+ 'invalid-url': 'the endpoint URL is invalid.',
3023
+ 'bad-response': "the server's response could not be parsed.",
3024
+ generic: 'could not complete the operation.',
3025
+ },
3026
+ errorReasonHttp: (status) => `the server answered with an error (HTTP ${status}).`,
3027
+ },
3028
+ // ADR-027 — password-authenticated superadmin SESSION (NON-PROD only).
3029
+ superadmin: {
3030
+ sessionIntro:
3031
+ 'Open a superadmin session (non-production environments only): certify will run against ANY email on ANY repo, bypassing the identity and authorship gates.',
3032
+ passwordPrompt: 'Superadmin password:',
3033
+ emailPrompt: 'Your superadmin email (for audit only):',
3034
+ emailInvalid: 'Invalid email. Try again.',
3035
+ needInput: 'Password and email are required (interactively, or --password and --email).',
3036
+ sessionReady: (email) =>
3037
+ `Superadmin session opened (audit: ${email}). certify will now use this session with any email.`,
3038
+ sessionExpires: (iso) => `Session expires: ${iso}.`,
3039
+ sessionHint:
3040
+ 'Run: certify --email <anyone> --accept-disclaimer --skill <name> · To end it: superadmin --logout',
3041
+ loggedOut: 'Superadmin session forgotten (local token removed).',
3042
+ // talents-ai-score, "profile switch via superadmin" (dueño, 2026-08-12): a dev/testing convenience — flips between the talent/ external profile without reinstalling.
3043
+ profileMenuHeading: 'What do you want to do?',
3044
+ profileMenuHint: 'Arrows ↑/↓ to move · enter to pick · esc to cancel',
3045
+ profileMenuPrompt: (max) => `Number (1-${max}), empty to cancel: `,
3046
+ profileMenuTalent: 'Switch to Talent profile',
3047
+ profileMenuTalentCurrent: 'Switch to Talent profile 《current》',
3048
+ profileMenuTalentDesc:
3049
+ 'Full surface: start, login, certify (skills and agents), add skills/portfolio. Purges local history (report, consent, talent session) and takes effect IMMEDIATELY in this same session, no restart needed.',
3050
+ profileMenuExternal: 'Switch to External profile',
3051
+ profileMenuExternalCurrent: 'Switch to External profile 《current》',
3052
+ profileMenuExternalDesc:
3053
+ 'Pre-pivot public surface: usage, report, share. Purges local history (report, consent, talent session) and takes effect IMMEDIATELY in this same session, no restart needed.',
3054
+ profileMenuCancelled: 'Cancelled. Nothing was changed.',
3055
+ profileAlreadyOn: (profile) => `You are already on the ${profile === 'talent' ? 'Talent' : 'External'} profile. Nothing was changed or purged.`,
3056
+ profileSwitched: (profile) => `Profile switched to ${profile === 'talent' ? 'Talent' : 'External'} · history cleared (report, consent, talent session) · takes effect immediately, no restart needed.`,
3057
+ // talents-ai-score, ADR-020/021 (coordinator ruling): superadmin is DELIBERATELY single-hop, PRIMARY (certifications service) ONLY — never hub.
3058
+ queryingPrimary: (endpoint) => `Querying Shakers: ${endpoint}`,
3059
+ errorNoEndpoint:
3060
+ 'No endpoint configured. Set the backend (SHAKERS_CLI_INGEST_ENDPOINT or usage --set-endpoint) and retry.',
3061
+ errorWrongPassword: 'Incorrect superadmin password.',
3062
+ errorDisabled:
3063
+ 'Endpoint unavailable: the superadmin session is disabled outside non-production environments.',
3064
+ errorGeneric: 'Could not open the superadmin session. Try again.',
3065
+ // Inspect (ADR-025) — attribution receipt for ALREADY-stored certifications.
3066
+ inspectIntro:
3067
+ 'Audit the authorship evidence of already-stored certifications (read-only, non-production).',
3068
+ inspectEmailPrompt: 'Email whose certification(s) to inspect:',
3069
+ inspectNone: (email) => `No stored certifications for ${email}.`,
3070
+ inspectHeader: (count, email) => `${count} stored certification(s) for ${email}:`,
3071
+ inspectNote:
3072
+ 'Attribution trail based on the git author (self-asserted); it is not cryptographic proof of authorship.',
3073
+ inspectLabels: {
3074
+ score: 'Score',
3075
+ dimensions: 'Dimensions',
3076
+ repo: 'Repo',
3077
+ commitRange: 'Commit range',
3078
+ sampledFiles: 'Sampled files',
3079
+ authorsConfirmed: 'Confirmed authors',
3080
+ authorsConsidered: 'Considered authors',
3081
+ model: 'Model',
3082
+ when: 'When',
3083
+ testOrigin: 'Test account',
3084
+ },
3085
+ inspectErrorGeneric: 'Could not inspect. Try again.',
3086
+ },
3087
+ // `shakers` REPL is the single entrypoint; this covers the prompt, the in-shell command help and messages — the commands keep their own copy.
3088
+ repl: {
3089
+ prompt: 'ϟ shakers ›',
3090
+ // dueño (2026-08-12): the startup BANNER (src/repl-shell.js) is now profile-aware and localized.
3091
+ bannerWelcome: 'Welcome to',
3092
+ bannerTalentLine1: 'Complete or update your agentic talent',
3093
+ bannerTalentLine2: "profile on Shakers: it's not just what you know,",
3094
+ bannerTalentLine3: "it's how you work with AI and the agents you build.",
3095
+ bannerHowItWorksHeading: 'How it works',
3096
+ bannerTalentStep1Line1: '1. `start` analyzes your project locally and',
3097
+ bannerTalentStep1Line2: ' detects your AI setup, skills, and agents.',
3098
+ bannerTalentStep2Line1: '2. You add or certify your skills and agents',
3099
+ bannerTalentStep2Line2: ' on your Shakers profile.',
3100
+ bannerTalentStep3Line1: '3. A more complete profile positions you for',
3101
+ bannerTalentStep3Line2: ' the projects that value it.',
3102
+ // EXTERNAL: same style as always (title + command list) — ONLY localized (the parity pass).
3103
+ bannerExternalLine1: 'A local-first CLI that scans this machine for AI',
3104
+ bannerExternalLine2: 'tooling and shows you a private, local usage report.',
3105
+ bannerCommandsHeading: 'Commands',
3106
+ bannerExternalUsageDesc: 'scan this machine + project',
3107
+ bannerExternalMapDesc: 'LOCAL report (graph)',
3108
+ bannerExternalReportDesc: 'shareable HTML report',
3109
+ bannerExternalShareDesc: 'card for LinkedIn',
3110
+ // COMMAND FLOW (issues 081 and 082) VOCABULARY, inherited by issue 083: this is about the tool's COMMANDS.
3111
+ completeNeeds: (cmd) => `(needs: ${cmd})`,
3112
+ completeDone: '(already run)',
3113
+ completeDoneStale: (days) => `(already run, ${days} days ago)`,
3114
+ completeAlias: (cmd) => `(alias of ${cmd})`,
3115
+ // `certify`'s subcommands, carrying the issue-085 qualification: this is
3116
+ // where the Talent chooses, so this is where it cannot stay ambiguous.
3117
+ completeSubSkills: 'TECHNICAL Skills, from your code',
3118
+ completeSubAgents: 'command of ONE agent',
3119
+ flowHeading: 'What order to use the tool in',
3120
+ flowIntro: 'The commands build on each other. This is the recommended order, and if you skip a dependency I tell you before anything runs.',
3121
+ flowStepFree: (n, cmd, what) => `${n}. ${cmd} — ${what}`,
3122
+ flowStepNeeds: (n, cmd, what, needs) => `${n}. ${cmd} — ${what} (needs: ${needs})`,
3123
+ flowWhat: {
3124
+ usage: 'scans your environment and scores your AI setup. It is the root of everything else, and it also leaves your identity verified',
3125
+ certify: 'certify a dimension of your role with a LiveKit interview',
3126
+ report: 'builds the shareable report (AI usage + certifications)',
3127
+ share: 'creates the branded card for LinkedIn',
3128
+ },
3129
+ blockedHeading: (cmd) => `\`${cmd}\` cannot run yet.`,
3130
+ blockedWhyFootprint: (cmd) => `It is missing this project\u2019s AI usage: \`${cmd}\` is built from what the scan detected, so with no scan there is nothing to build from.`,
3131
+ blockedWhyIdentity: (cmd) => `It is missing your verified identity: \`${cmd}\` opens a session with the service in your name, and without a verified email the service refuses it.`,
3132
+ blockedFix: (fix) => `Run \`${fix}\` and try again.`,
3133
+ blockedNoBypass: 'There is no way to skip this check: the command would fail seconds later anyway, only without an explanation.',
3134
+ staleFootprint: (days) => `This project\u2019s AI usage is ${days} days old. If your tools or agents have changed, run \`usage\` again before sharing anything.`,
3135
+ nextCommand: (cmd, what) => `Next tool command: \`${cmd}\` — ${what}`,
3136
+ journeyDone: 'You have completed the tool\u2019s flow: there is no command left to run. The next steps for raising your AI-usage level are a different thing, and they live in `usage --roadmap`.',
3137
+ goodbye: 'See you soon.',
3138
+ unknown: (cmd) => `Unknown command: "${cmd}". Type "help" to list the available commands.`,
3139
+ // ORDER: declared by src/command-graph.js (issue 081).
3140
+ help:
3141
+ 'Shakers — available commands\n\n'
3142
+ + ' usage [options] Scan this project + your machine; score your AI setup (T0-T7)\n'
3143
+ + ' certify [options] Certify a dimension of your role (LiveKit interview)\n'
3144
+ + ' report [options] Build and open the shareable report (AI usage + certified Skills)\n'
3145
+ + ' Needs a `usage` for this project\n'
3146
+ + ' share [options] Build a branded card of your AI usage to share on LinkedIn\n'
3147
+ + ' Needs a `usage` for this project\n'
3148
+ + ' login Log in as a registered talent (email + password)\n'
3149
+ + ' logout Log out\n'
3150
+ + ' help Show this help\n'
3151
+ + ' clear Clear the screen\n'
3152
+ + ' exit | quit Close the shell\n\n'
3153
+ + 'Each command\'s flags still work inside the shell\n'
3154
+ + '(e.g. `usage --root <dir>`, `usage --roadmap`, `certify --dimension <clave>`). Use\n'
3155
+ + '`usage --help` or `certify --help` for all their options.',
3156
+ helpExternal:
3157
+ 'shakers — available commands\n\n'
3158
+ + ' usage [options] Scan this project + your machine; build a LOCAL report of your AI setup\n'
3159
+ + ' report [options] Open the shareable HTML report for this project in your browser\n'
3160
+ + ' Needs a `usage` for this project\n'
3161
+ + ' share [options] Build a branded card of your AI usage to share on LinkedIn\n'
3162
+ + ' Needs a `usage` for this project\n'
3163
+ + ' help Show this help\n'
3164
+ + ' clear Clear the screen\n'
3165
+ + ' exit | quit Close the shell\n\n'
3166
+ + 'Each command\'s flags still work inside the shell\n'
3167
+ + '(e.g. `usage --root <dir>`, `usage --json`). Use `usage --help` for all its options.',
3168
+ // Replaces the flow block (081) when there is only ONE visible command: there is no order to announce.
3169
+ externalIntro: 'Type `usage` to scan this project and your machine; the report always shows locally, and nothing leaves your machine unless you choose to share it.',
3170
+ },
3171
+ // See the es.sheet header.
3172
+ sheet: {
3173
+ reportTitle: 'AI usage report',
3174
+ footprint: 'AI usage',
3175
+ certs: 'Certifications',
3176
+ ladderT: 'Setup Level',
3177
+ ladderS: 'Your AI-usage level: S1–S3.',
3178
+ toolsT: 'Detected tools',
3179
+ toolsS: 'AI clients present in your environment.',
3180
+ techT: 'Project technologies',
3181
+ techS: 'Stack recognized in the repository.',
3182
+ // Issue 110 — see the Spanish catalog for why the service is shown and the
3183
+ // server name never is, and why the empty state carries the T3 criterion.
3184
+ mcpT: 'Services connected over MCP',
3185
+ mcpS: 'Products your AI has access to.',
3186
+ mcpEmpty: 'No MCP server detected. Connecting one is the criterion that takes you to T3 (Connected bench).',
3187
+ mcpUnidentified: (n) => `${n} server${n === 1 ? '' : 's'} whose service we could not identify from its name.`,
3188
+ // 085: the tab said a bare "Skills" next to an "Agents" one, which is the
3189
+ // exact collision the issue names.
3190
+ skills: 'Technical Skills',
3191
+ // ---- DETECTED agents, left column (issue 089) -----------------------
3192
+ agentsT: 'Detected agents',
3193
+ agentsS: 'Every agent configured in your environment. Certifying is a different thing, and it lives in the right-hand column.',
3194
+ agentsEmpty: 'No configured agent detected (e.g. under .claude/agents/). If you have agents and they are not here, the tool did not find them — it is not that they do not count.',
3195
+ agentNoModel: 'no model declared',
3196
+ agentNoCategory: 'no category',
3197
+ // 106: THREE states, not two — see the Spanish catalog for the reasoning.
3198
+ agentNotEvaluated: 'not assessed',
3199
+ agentsEvalMissing: 'The classification for these agents could not be computed in this run, so it is not that they have no category: it is that we never got to ask. Run `usage` again to retry.',
3200
+ agentOrchestrates: 'orchestrates other agents',
3201
+ valoracion: 'Assessment',
3202
+ comoMejorar: 'How to improve',
3203
+ bandLine: (label) => `Setup Level <b>${label}</b>.`,
3204
+ hereYouAre: 'You are here',
3205
+ noFoot: 'No AI usage yet. Run usage in this project.',
3206
+ noSkills: 'No certified technical Skills yet. Run certify skills.',
3207
+ // Agent certifications were removed from the report (ADR-033); the sheet's agent-cert tab, its empty state and its detected-vs-certified copy went with it.
3208
+ footer: 'Report generated locally · Shakers',
3209
+ themeToggle: 'Toggle theme',
3210
+ themeDark: 'Dark',
3211
+ themeLight: 'Light',
3212
+ copied: 'Copied ✓',
3213
+ expandAll: 'Expand all',
3214
+ collapseAll: 'Collapse all',
3215
+ },
3216
+ },
3217
+ };
3218
+
3219
+ // Reverse map {Spanish text -> stable key}, built from the CATEGORIES catalog that detectors.js ALREADY exports (key -> Spanish text).
3220
+ const CATEGORY_KEY_BY_LABEL_ES = Object.fromEntries(
3221
+ Object.entries(CATEGORIES).map(([key, label]) => [label, key]),
3222
+ );
3223
+
3224
+ // Translates a tool's `category` (always in Spanish, as produced by the scanner) to the stable CATEGORIES key and from there to the requested language's catalog.
3225
+ function categoryLabel(lang, categoryEs) {
3226
+ const key = CATEGORY_KEY_BY_LABEL_ES[categoryEs];
3227
+ const translated = key && catalogs[getResolvedLang(lang)].categories[key];
3228
+ return translated || categoryEs;
3229
+ }
3230
+
3231
+ function getResolvedLang(lang) {
3232
+ return lang === 'es' || lang === 'en' ? lang : 'en';
3233
+ }
3234
+
3235
+ // Resolution rule: only 'es' and 'en' supported. Any code that doesn't
3236
+ // start with 'es' falls back to English (universal fallback).
3237
+ function resolveLang(langCode) {
3238
+ return langCode && /^es/i.test(langCode) ? 'es' : 'en';
3239
+ }
3240
+
3241
+ // Single entry point for report callers (bin/report.js): detects the OS language (see locale.js) and already returns the resolved catalog code ('es' or 'en').
3242
+ function detectReportLang(env = process.env) {
3243
+ return resolveLang(detectLangCode(env));
3244
+ }
3245
+
3246
+ function detectFlowLang(env = process.env) {
3247
+ return toLowerLang(detectLangCode(env));
3248
+ }
3249
+
3250
+ function getCatalog(lang) {
3251
+ return catalogs[getResolvedLang(lang)];
3252
+ }
3253
+
3254
+ function tierName(tierKey, lang) {
3255
+ const names = getCatalog(lang).tierNames;
3256
+ return (names && names[tierKey]) || tierKey;
3257
+ }
3258
+
3259
+ // Defensive i18n lookup (talents-ai-score — ported from d7c4d23, adapted).
3260
+ function label(value, fallback) {
3261
+ return typeof value === 'string' && value ? value : fallback;
3262
+ }
3263
+
3264
+ module.exports = { detectReportLang, detectFlowLang, resolveLang, getCatalog, categoryLabel, tierName, label };