shakers-cli 0.16.0 → 0.18.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 (69) hide show
  1. package/README.md +21 -6
  2. package/bin/ai-usage.js +31 -12
  3. package/bin/command-audit-worker.js +19 -0
  4. package/bin/login.js +4 -8
  5. package/bin/mcp.js +32 -21
  6. package/bin/register.js +12 -30
  7. package/bin/shakers-cli.js +15 -10
  8. package/package.json +1 -1
  9. package/src/account-marker.js +40 -0
  10. package/src/account-trace.js +23 -0
  11. package/src/agents-client.js +1 -0
  12. package/src/ai-usage-review.js +103 -0
  13. package/src/auth-client.js +7 -107
  14. package/src/auth-session-store.js +12 -11
  15. package/src/backend-request.js +3 -24
  16. package/src/cli-skill.js +77 -0
  17. package/src/command-audit.js +47 -4
  18. package/src/config.js +2 -16
  19. package/src/consent-flow.js +9 -3
  20. package/src/detect-repos.js +4 -8
  21. package/src/detectors.js +23 -0
  22. package/src/device-auth-store.js +2 -2
  23. package/src/device-login.js +11 -9
  24. package/src/env-paths.js +9 -1
  25. package/src/i18n.js +186 -102
  26. package/src/mcp-ai-usage-result-tool.js +38 -14
  27. package/src/mcp-ai-usage-tool.js +317 -172
  28. package/src/mcp-choice.js +171 -34
  29. package/src/mcp-detector.js +26 -2
  30. package/src/mcp-device-login-tools.js +40 -8
  31. package/src/mcp-install.js +292 -53
  32. package/src/mcp-instructions.js +43 -17
  33. package/src/mcp-onboarding-tools.js +1 -3
  34. package/src/mcp-prompts.js +8 -6
  35. package/src/mcp-register-tools.js +25 -14
  36. package/src/mcp-role-tools.js +59 -18
  37. package/src/mcp-server-name.js +7 -0
  38. package/src/mcp-server.js +35 -7
  39. package/src/mcp-signup-client.js +3 -6
  40. package/src/mcp-signup-flow.js +389 -0
  41. package/src/mcp-signup-tools.js +250 -133
  42. package/src/onboarding-flow.js +72 -146
  43. package/src/onboarding-funnel.js +46 -6
  44. package/src/opencode-sessions.js +92 -0
  45. package/src/profile-agents.js +26 -0
  46. package/src/render-report-text.js +1 -1
  47. package/src/report-gating.js +4 -2
  48. package/src/report-store.js +7 -2
  49. package/src/roles-flow.js +8 -5
  50. package/src/saved-positions-flow.js +3 -3
  51. package/src/scanner.js +104 -12
  52. package/src/session-gate.js +1 -4
  53. package/src/session-normalizers.js +114 -8
  54. package/src/session-refresh.js +5 -16
  55. package/src/session-scan.js +167 -22
  56. package/src/share-consent-state.js +2 -2
  57. package/src/share-payload.js +4 -1
  58. package/src/share.js +2 -2
  59. package/src/signup-client.js +12 -39
  60. package/src/signup-copy.js +545 -58
  61. package/src/skill-install.js +102 -0
  62. package/src/stdin-ask.js +13 -1
  63. package/src/tier-engine.js +14 -2
  64. package/src/trust-narration.js +32 -0
  65. package/src/usage-run-steps.js +68 -6
  66. package/src/usage-scan.js +11 -8
  67. package/src/brand-svg.js +0 -9
  68. package/src/inter-fonts.js +0 -6
  69. package/src/mcp-auth-form.js +0 -480
@@ -7,35 +7,290 @@ const SIGNUP_LANGUAGES = ['es', 'en', 'it', 'pt'];
7
7
  // Legal texts. The CLI onboarding reads the es/en ones too.
8
8
  const LEGAL = {
9
9
  es: {
10
+ terms: (terms, privacy) => `Al continuar, aceptas los ${terms('Términos')} y la ${privacy('Política de Privacidad')}.`,
11
+ newsletter: 'Quiero recibir contenido sobre el futuro del trabajo',
10
12
  signupNotice: (sources) => `Antes de crear tu cuenta: guardamos los datos que me des (${listOf('es', [...named(sources, COPY.es.importSources), 'lo que sé de ti'])}) y podemos contactarte. Te completo el perfil automáticamente y cualquier campo lo puedes cambiar después en la web. La entrevista de onboarding la puedes hacer luego aquí mismo en el chat o en la web.`,
11
- usageInfoAccessed: 'Se accede a: la estructura de tus herramientas de IA y a extractos anonimizados de tu actividad en este equipo. Tu código y tus prompts en crudo NUNCA salen de tu máquina.',
13
+ usageInfoAccessed: "Se accede a: tus herramientas de IA, el historial de tus proyectos de código y tus conversaciones con asistentes de IA de programación en todo este equipo. Si apruebas la subida, Shakers recibe fragmentos literales de esas conversaciones (hasta 40 extractos de tus mensajes y 20 comandos, de 200 caracteres como máximo), la configuración y las definiciones de tus agentes y recuentos de actividad, tras quitar lo que parezca un secreto, un email o una ruta. Un modelo de IA de un proveedor externo (OpenAI, Google o Anthropic) los analiza por encargo de Shakers para calcular tu evaluación y después los descarta: solo se guarda el resultado (niveles, puntuaciones, recuentos y los nombres de tus herramientas y agentes). No lo uses en un equipo ni con conversaciones que contengan datos que no puedas compartir: de clientes, de tu empresa o de terceros.",
12
14
  usageGoalDuration: 'Objetivo: medir cómo trabajas con IA para tu perfil. Duración: ~1-2 minutos.',
13
15
  interviewInfoAccessed: 'Se accede a: tus respuestas en esta entrevista y tu perfil importado. La conduce el servidor de Shakers.',
14
16
  interviewGoalDuration: 'Objetivo: conocer tu rol, tu dedicación y cómo usas la IA. Duración: ~5 minutos.',
15
17
  },
16
18
  en: {
19
+ terms: (terms, privacy) => `By continuing, you accept the ${terms('Terms')} and the ${privacy('Privacy Policy')}.`,
20
+ newsletter: 'I want to receive content about the future of work',
17
21
  signupNotice: (sources) => `Before we create your account: we store the data you give me (${listOf('en', [...named(sources, COPY.en.importSources), 'what I know about you'])}) and may contact you. I fill in your profile automatically and you can change any field later on the web. You can do the onboarding interview afterwards right here in the chat or on the web.`,
18
- 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.',
22
+ usageInfoAccessed: "Accessed: your AI tools, the history of your code projects and your conversations with AI coding assistants across this whole machine. If you approve the upload, Shakers receives literal fragments of those conversations (up to 40 excerpts of your messages and 20 commands, 200 characters at most each), the configuration and definitions of your agents, and activity counts, after removing anything that looks like a secret, an email or a path. An AI model from an external provider (OpenAI, Google or Anthropic) analyses them on Shakers' behalf to compute your evaluation and then discards them: only the result is kept (levels, scores, counts and the names of your tools and agents). Do not use it on a machine or with conversations that hold data you are not allowed to share: client, employer or third-party data.",
19
23
  usageGoalDuration: 'Goal: measure how you work with AI for your profile. Duration: ~1-2 minutes.',
20
24
  interviewInfoAccessed: 'Accessed: your answers in this interview and your imported profile. It is conducted by the Shakers server.',
21
25
  interviewGoalDuration: 'Goal: learn your role, your dedication and how you use AI. Duration: ~5 minutes.',
22
26
  },
23
27
  it: {
28
+ terms: (terms, privacy) => `Continuando, accetti i ${terms('Termini')} e l'${privacy('Informativa sulla privacy')}.`,
29
+ newsletter: 'Voglio ricevere contenuti sul futuro del lavoro',
24
30
  signupNotice: (sources) => `Prima di creare il tuo account: conserviamo i dati che mi dai (${listOf('it', [...named(sources, COPY.it.importSources), 'quello che so di te'])}) e possiamo contattarti. Completo il tuo profilo in automatico e puoi cambiare qualsiasi campo in seguito sul sito. Il colloquio di onboarding puoi farlo dopo sul sito.`,
25
- usageInfoAccessed: 'Accesso a: la struttura dei tuoi strumenti di IA ed estratti anonimizzati della tua attività su questo computer. Il tuo codice e i tuoi prompt originali NON escono MAI dalla tua macchina.',
31
+ usageInfoAccessed: "Accesso a: i tuoi strumenti di IA, la cronologia dei tuoi progetti di codice e le tue conversazioni con assistenti di IA per programmare su tutto questo computer. Se approvi il caricamento, Shakers riceve frammenti letterali di queste conversazioni (fino a 40 estratti dei tuoi messaggi e 20 comandi, al massimo 200 caratteri ciascuno), la configurazione e le definizioni dei tuoi agenti e conteggi di attività, dopo aver tolto ciò che sembra un segreto, un'email o un percorso. Un modello di IA di un fornitore esterno (OpenAI, Google o Anthropic) li analizza per conto di Shakers per calcolare la tua valutazione e poi li scarta: si conserva solo il risultato (livelli, punteggi, conteggi e i nomi dei tuoi strumenti e agenti). Non usarlo su un computer né con conversazioni che contengono dati che non puoi condividere: di clienti, del tuo datore di lavoro o di terzi.",
26
32
  usageGoalDuration: "Obiettivo: misurare come lavori con l'IA per il tuo profilo. Durata: ~1-2 minuti.",
27
33
  interviewInfoAccessed: 'Accesso a: le tue risposte in questo colloquio e il tuo profilo importato. Lo conduce il server di Shakers.',
28
34
  interviewGoalDuration: "Obiettivo: conoscere il tuo ruolo, la tua disponibilità e come usi l'IA. Durata: ~5 minuti.",
29
35
  },
30
36
  pt: {
37
+ terms: (terms, privacy) => `Ao continuar, aceita os ${terms('Termos')} e a ${privacy('Política de Privacidade')}.`,
38
+ newsletter: 'Quero receber conteúdos sobre o futuro do trabalho',
31
39
  signupNotice: (sources) => `Antes de criar a sua conta: guardamos os dados que me der (${listOf('pt', [...named(sources, COPY.pt.importSources), 'o que sei sobre si'])}) e podemos contactá-lo. Preencho o seu perfil automaticamente e pode alterar qualquer campo depois na web. A entrevista de onboarding pode fazê-la depois na web.`,
32
- usageInfoAccessed: 'Acede-se a: a estrutura das suas ferramentas de IA e excertos anonimizados da sua atividade neste computador. O seu código e os seus prompts em bruto NUNCA saem da sua máquina.',
40
+ usageInfoAccessed: "Acede-se a: as suas ferramentas de IA, o histórico dos seus projetos de código e as suas conversas com assistentes de IA de programação em todo este computador. Se aprovar o envio, a Shakers recebe fragmentos literais dessas conversas (até 40 excertos das suas mensagens e 20 comandos, com 200 caracteres no máximo cada), a configuração e as definições dos seus agentes e contagens de atividade, depois de retirar o que pareça um segredo, um email ou um caminho. Um modelo de IA de um fornecedor externo (OpenAI, Google ou Anthropic) analisa-os por conta da Shakers para calcular a sua avaliação e depois descarta-os: só se guarda o resultado (níveis, pontuações, contagens e os nomes das suas ferramentas e agentes). Não o use num computador nem com conversas que contenham dados que não possa partilhar: de clientes, do seu empregador ou de terceiros.",
33
41
  usageGoalDuration: 'Objetivo: medir como trabalha com IA para o seu perfil. Duração: ~1-2 minutos.',
34
42
  interviewInfoAccessed: 'Acede-se a: as suas respostas nesta entrevista e o seu perfil importado. É conduzida pelo servidor da Shakers.',
35
43
  interviewGoalDuration: 'Objetivo: conhecer o seu perfil, a sua dedicação e como usa a IA. Duração: ~5 minutos.',
36
44
  },
37
45
  };
38
46
 
47
+ const TRUST = {
48
+ es: {
49
+ heading: 'Qué hace Shakers en este paso:',
50
+ failed: 'Este paso no se ha completado; no se ha cambiado nada más.',
51
+ mcp: {
52
+ ai_usage: 'si dices que sí, analiza en este ordenador tus herramientas de IA, el historial de tus proyectos de código y tus conversaciones con asistentes de programación con IA (Claude Code, Codex, Gemini, Cursor, Continue, Cline/Roo, Aider, Pi, OpenCode). En el alta se analizan todas las conversaciones de este equipo; si analizas un repo concreto, solo las de ese repo. Todavía no se envía nada: antes te enseño qué se enviaría y tú decides. No lo uses en un equipo ni con conversaciones que tengan datos que no puedes compartir (de clientes, de tu empresa o de terceros).',
53
+ ai_usage_upload: 'te enseña qué se enviaría a Shakers y solo lo envía si lo apruebas: hasta 40 extractos literales de tus propios mensajes (correcciones y decisiones) y 20 comandos, de 200 caracteres como máximo cada uno; la configuración y la definición de tus agentes; y recuentos de actividad. Antes se quita lo que parece un secreto, un email o una ruta, pero el filtro es aproximado: no lo apruebes si hay datos que no puedes compartir (de clientes, de tu empresa o de terceros). Un modelo de IA de un proveedor externo (OpenAI, Google o Anthropic) lo analiza por encargo de Shakers para calcular tu evaluación y después se descarta; solo se guarda el resultado: niveles, puntuaciones, recuentos y los nombres de tus herramientas y agentes. Revisión humana: «Solicitar revisión humana» en la web o talent@shakersworks.com; para pedir que se borre, el mismo correo.',
54
+ ai_usage_result: 'lee de tu cuenta de Shakers el resultado del análisis que aprobaste. No lee nada de este ordenador ni cambia nada.',
55
+ logout: 'borra la sesión de Shakers guardada en este ordenador (~/.config/shakers) y avisa a Shakers de que la cierras. No cambia nada de tu perfil.',
56
+ list_addable: 'lee de tu cuenta de Shakers las skills y agentes de IA encontrados en el análisis que enviaste. No cambia nada.',
57
+ add_skill: 'añade a tu perfil de Shakers la skill que elijas. No lee nada de este ordenador.',
58
+ add_agent: 'añade a tu perfil de Shakers el agente de IA que elijas, con la descripción que acordéis. No lee nada más de este ordenador.',
59
+ add_project: 'te pregunta antes y, si dices que sí, lee el nombre de la carpeta del proyecto, la dirección web de su código y las fechas de su primer y último cambio, y los envía a tu portfolio de Shakers. No envía tu código.',
60
+ suggest_register_context: 'te pregunta antes y, si dices que sí, mira los nombres de los PDF y Word de Escritorio, Documentos, Descargas e iCloud Drive que parezcan un CV, tu zona horaria y el nombre, email y usuario de GitHub de tus herramientas de programación. No abre esos archivos ni envía nada.',
61
+ read_cv: 'te pregunta antes y, si dices que sí, lee el texto del archivo de CV que permitas para preparar el borrador de tu perfil. No envía nada a Shakers.',
62
+ signup: "lleva tu alta en Shakers paso a paso aquí en el chat. Antes de leer algo de este ordenador o de enviar tus datos a Shakers te pregunta, y no crea la cuenta hasta que confirmes tu borrador.",
63
+ signup_start: 'empieza tu alta aquí en el chat. Todavía no lee nada de tu ordenador ni envía nada.',
64
+ signup_email: 'te pregunta con qué email crear tu cuenta. No envía nada todavía.',
65
+ signup_draft: 'te enseña el borrador de tu perfil preparado con tu CV y tu LinkedIn. No crea la cuenta ni envía nada hasta que lo confirmes.',
66
+ signup_create_account: 'tras tu confirmación y tu aceptación de los Términos, envía a Shakers tu LinkedIn, tu CV y el borrador para preparar tu perfil, y abre en tu navegador una página de Shakers (shakersworks.com) con un código donde creas la cuenta. Tu contraseña nunca pasa por este chat.',
67
+ signup_status: 'consulta en Shakers si tu cuenta ya existe y cómo va la importación de tu perfil. No cambia nada.',
68
+ update_existing_profile: 'te enseña qué rellenaría tu borrador en los campos vacíos de tu perfil y solo lo guarda si dices que sí. Nunca sobrescribe lo que ya tenías.',
69
+ import_profile: 'envía a Shakers tu LinkedIn, GitHub o web para rellenar lo vacío de tu perfil; un archivo de CV de este ordenador solo se envía si dices que sí. Lo que ya escribiste se mantiene.',
70
+ save_profile_details: 'guarda en tu perfil de Shakers los datos que confirmaste (situación, tarifas, disponibilidad, ubicación, idiomas y teléfono). En una cuenta existente solo rellena lo vacío.',
71
+ open_web: 'abre Shakers en tu navegador con tu sesión iniciada, con un enlace de un solo uso que caduca en 2 minutos. No cambia nada.',
72
+ open_linkedin_profile: 'abre tu perfil de LinkedIn en tu navegador para que copies su dirección. No lee ni envía nada.',
73
+ social_signin_start: 'prepara el acceso: te da un enlace a una página de Shakers (shakersworks.com) y un código. Allí compruebas que el código coincide y apruebas. Tu contraseña solo se escribe en la web de Shakers, nunca en este chat. Si no has pedido tú este acceso, deniégalo.',
74
+ social_signin_poll: 'comprueba si ya aprobaste el acceso y, si es así, guarda la sesión solo en este ordenador (~/.config/shakers). Desde entonces este chat puede leer tu perfil y guardar en él lo que confirmes. Para cerrarla: pídeme cerrar sesión.',
75
+ onboarding_interview_start: 'empieza la entrevista de onboarding después de preguntarte dónde y cómo responder. Tus respuestas se envían a Shakers para conocer tu perfil. No lee nada de tu ordenador.',
76
+ onboarding_interview_turn: 'envía a Shakers la respuesta que diste o aprobaste y trae la siguiente pregunta.',
77
+ onboarding_interview_complete: 'cierra la entrevista y la guarda en Shakers.',
78
+ repeat_onboarding_interview: 'borra en Shakers tu entrevista de onboarding anterior para que puedas repetirla. No se puede deshacer.',
79
+ onboarding_finish: 'marca tu onboarding como completado en Shakers.',
80
+ list_available_roles: 'lee de Shakers los roles que puedes añadir a tu perfil. No cambia nada.',
81
+ add_role: 'añade a tu perfil de Shakers el rol que elijas.',
82
+ list_my_roles: 'lee tus roles de Shakers y los que te sugieren tu entrevista y tu CV. No cambia nada.',
83
+ set_main_role: 'te pregunta cuál es tu rol principal y lo guarda en tu perfil de Shakers.',
84
+ report: 'enseña tu informe de IA guardado en Shakers o el de tu último análisis en este ordenador, y lo guarda como archivo en este ordenador.',
85
+ share: 'crea en este ordenador una tarjeta con tu informe de IA para compartir en LinkedIn. No envía nada.',
86
+ list_certifiable_dimensions: 'lee de Shakers qué áreas de tu rol principal puedes certificar. No cambia nada.',
87
+ find_projects: 'lee de Shakers los proyectos abiertos que mejor encajan contigo. No cambia nada.',
88
+ show_project: 'lee de Shakers el detalle de un proyecto. No cambia nada.',
89
+ save_project: 'guarda ese proyecto en tus proyectos guardados de Shakers.',
90
+ unsave_project: 'quita ese proyecto de tus proyectos guardados de Shakers.',
91
+ list_invitations: 'lee tus invitaciones a proyectos en Shakers. No cambia nada.',
92
+ list_applications: 'lee tus candidaturas en Shakers. No cambia nada.',
93
+ get_availability: 'lee tu disponibilidad de tu perfil de Shakers. No cambia nada.',
94
+ list_certifications: 'lee tus certificaciones de Shakers. No cambia nada.',
95
+ get_profile: 'lee tu perfil de Shakers. No cambia nada.',
96
+ get_rate: 'lee tu tarifa de tu perfil de Shakers. No cambia nada.',
97
+ get_languages: 'lee tus idiomas de tu perfil de Shakers. No cambia nada.',
98
+ list_socials: 'lee tus redes de tu perfil de Shakers. No cambia nada.',
99
+ list_experiences: 'lee tus experiencias de tu perfil de Shakers. No cambia nada.',
100
+ list_portfolios: 'lee tus proyectos de portfolio de tu perfil de Shakers. No cambia nada.',
101
+ alma_chat: 'envía tu mensaje a Alma, la asistente de Shakers, y te enseña su respuesta. Si propone un cambio en tu perfil, te pregunta antes de hacerlo.',
102
+ alma_decision: 'envía a Shakers tu sí o tu no al cambio de perfil que propuso Alma; solo con un sí se cambia tu perfil.',
103
+ },
104
+ },
105
+ en: {
106
+ heading: 'What Shakers does in this step:',
107
+ failed: 'This step did not complete; nothing else was changed.',
108
+ mcp: {
109
+ ai_usage: "if you say yes, it analyses on this computer your AI tools, the history of your code projects and your conversations with AI coding assistants (Claude Code, Codex, Gemini, Cursor, Continue, Cline/Roo, Aider, Pi, OpenCode). Sign-up analyses every conversation on this computer; analysing a specific repo covers only that repo's conversations. Nothing is sent yet: first I show you what would be sent and you decide. Do not use it on a computer or with conversations holding data you are not allowed to share (client, employer or third-party data).",
110
+ ai_usage_upload: 'shows you what would be sent to Shakers and sends it only if you approve: up to 40 literal excerpts of your own messages (corrections and decisions) and 20 commands, 200 characters max each; the configuration and definitions of your agents; and activity counts. Anything that looks like a secret, an email or a path is removed first, but the filter is approximate: do not approve it if there is data you are not allowed to share (client, employer or third-party data). An AI model from an external provider (OpenAI, Google or Anthropic) analyses it on behalf of Shakers to compute your evaluation and then it is discarded; only the result is kept: levels, scores, counts and the names of your tools and agents. Human review: "Request human review" on the web or talent@shakersworks.com; to ask for deletion, the same email.',
111
+ ai_usage_result: 'reads from your Shakers account the result of the analysis you approved. It reads nothing on this computer and changes nothing.',
112
+ logout: 'deletes the Shakers session saved on this computer (~/.config/shakers) and tells Shakers you are signing out. It does not change your profile.',
113
+ list_addable: 'reads from your Shakers account the skills and AI agents found in the analysis you sent. It changes nothing.',
114
+ add_skill: 'adds the skill you pick to your Shakers profile. It reads nothing on this computer.',
115
+ add_agent: 'adds the AI agent you pick, with the description you agree on, to your Shakers profile. It reads nothing else on this computer.',
116
+ add_project: 'asks you first and, if you say yes, reads the project folder name, the web address of its code and the dates of its first and last change, and sends them to your Shakers portfolio. It does not send your code.',
117
+ suggest_register_context: 'asks you first and, if you say yes, looks at the names of PDF and Word files in Desktop, Documents, Downloads and iCloud Drive that look like a CV, your time zone and the name, email and GitHub user saved by your coding tools. It does not open those files or send anything.',
118
+ read_cv: 'asks you first and, if you say yes, reads the text of the CV file you allow to prepare your profile draft. It sends nothing to Shakers.',
119
+ signup: "runs your Shakers sign-up step by step here in the chat. It asks you before reading anything on this computer or sending your data to Shakers, and creates no account until you confirm your draft.",
120
+ signup_start: 'starts your sign-up here in the chat. It does not read anything on your computer or send anything yet.',
121
+ signup_email: 'asks which email to create your account with. Nothing is sent yet.',
122
+ signup_draft: 'shows the profile draft prepared from your CV and LinkedIn. No account is created and nothing is sent until you confirm.',
123
+ signup_create_account: 'after your confirmation and your acceptance of the Terms, sends your LinkedIn, CV and draft to Shakers to prepare your profile, and opens in your browser a Shakers page (shakersworks.com) with a code where you create the account. Your password never goes through this chat.',
124
+ signup_status: 'checks on Shakers whether your account exists and how your profile import is going. It changes nothing.',
125
+ update_existing_profile: 'shows what your draft would fill in the empty fields of your profile and saves it only if you say yes. It never overwrites what you already had.',
126
+ import_profile: 'sends your LinkedIn, GitHub or website to Shakers to fill the empty parts of your profile; a CV file from this computer is sent only if you say yes. What you already wrote is kept.',
127
+ save_profile_details: 'saves the details you confirmed (situation, rates, availability, location, languages and phone) to your Shakers profile. On an existing account it only fills empty fields.',
128
+ open_web: 'opens Shakers in your browser already signed in, with a single-use link that expires in 2 minutes. It changes nothing.',
129
+ open_linkedin_profile: 'opens your LinkedIn profile in your browser so you can copy its address. It reads and sends nothing.',
130
+ social_signin_start: 'prepares the sign-in: it gives you a link to a Shakers page (shakersworks.com) and a code. There you check the code matches and approve. Your password is typed only on the Shakers website, never in this chat. If you did not ask for this access, deny it.',
131
+ social_signin_poll: 'checks whether you approved the access and, if so, saves the session only on this computer (~/.config/shakers). From then on this chat can read your profile and save to it what you confirm. To end it: ask me to sign out.',
132
+ onboarding_interview_start: 'starts the onboarding interview after asking where and how you want to answer. Your answers are sent to Shakers to get to know your profile. It reads nothing on your computer.',
133
+ onboarding_interview_turn: 'sends Shakers the answer you gave or approved and brings the next question.',
134
+ onboarding_interview_complete: 'closes the interview and saves it on Shakers.',
135
+ repeat_onboarding_interview: 'deletes your previous onboarding interview on Shakers so you can take it again. This cannot be undone.',
136
+ onboarding_finish: 'marks your onboarding as completed on Shakers.',
137
+ list_available_roles: 'reads from Shakers the roles you can add to your profile. It changes nothing.',
138
+ add_role: 'adds the role you pick to your Shakers profile.',
139
+ list_my_roles: 'reads your roles on Shakers and the ones suggested by your interview and CV. It changes nothing.',
140
+ set_main_role: 'asks which role is your main one and saves it to your Shakers profile.',
141
+ report: 'shows your AI report saved on Shakers or the one from your last analysis on this computer, and saves it as a file on this computer.',
142
+ share: 'creates on this computer a card with your AI report to share on LinkedIn. It sends nothing.',
143
+ list_certifiable_dimensions: 'reads from Shakers which areas of your main role you can certify. It changes nothing.',
144
+ find_projects: 'reads from Shakers the open projects that fit you best. It changes nothing.',
145
+ show_project: "reads a project's details from Shakers. It changes nothing.",
146
+ save_project: 'saves that project to your saved projects on Shakers.',
147
+ unsave_project: 'removes that project from your saved projects on Shakers.',
148
+ list_invitations: 'reads your project invitations on Shakers. It changes nothing.',
149
+ list_applications: 'reads your applications on Shakers. It changes nothing.',
150
+ get_availability: 'reads your availability from your Shakers profile. It changes nothing.',
151
+ list_certifications: 'reads your Shakers certifications. It changes nothing.',
152
+ get_profile: 'reads your Shakers profile. It changes nothing.',
153
+ get_rate: 'reads your rate from your Shakers profile. It changes nothing.',
154
+ get_languages: 'reads your languages from your Shakers profile. It changes nothing.',
155
+ list_socials: 'reads your social links from your Shakers profile. It changes nothing.',
156
+ list_experiences: 'reads your experiences from your Shakers profile. It changes nothing.',
157
+ list_portfolios: 'reads your portfolio projects from your Shakers profile. It changes nothing.',
158
+ alma_chat: 'sends your message to Alma, the Shakers assistant, and shows her answer. If she proposes a change to your profile, you are asked before it is made.',
159
+ alma_decision: 'sends Shakers your yes or no to the profile change Alma proposed; only a yes changes your profile.',
160
+ },
161
+ },
162
+ it: {
163
+ heading: 'Cosa fa Shakers in questo passaggio:',
164
+ failed: "Questo passaggio non è stato completato; non è stato cambiato nient'altro.",
165
+ mcp: {
166
+ ai_usage: 'se dici di sì, analizza su questo computer i tuoi strumenti di IA, la cronologia dei tuoi progetti di codice e le tue conversazioni con assistenti di programmazione con IA (Claude Code, Codex, Gemini, Cursor, Continue, Cline/Roo, Aider, Pi, OpenCode). Nella registrazione si analizzano tutte le conversazioni di questo computer; se analizzi un repo specifico, solo quelle di quel repo. Non viene ancora inviato nulla: prima ti mostro cosa verrebbe inviato e decidi tu. Non usarlo su un computer né con conversazioni che contengono dati che non puoi condividere (di clienti, del tuo datore di lavoro o di terzi).',
167
+ ai_usage_upload: "ti mostra cosa verrebbe inviato a Shakers e lo invia solo se lo approvi: fino a 40 estratti letterali dei tuoi messaggi (correzioni e decisioni) e 20 comandi, di massimo 200 caratteri ciascuno; la configurazione e le definizioni dei tuoi agenti; e conteggi di attività. Prima si rimuove ciò che sembra un segreto, un'email o un percorso, ma il filtro è approssimativo: non approvarlo se ci sono dati che non puoi condividere (di clienti, del tuo datore di lavoro o di terzi). Un modello di IA di un fornitore esterno (OpenAI, Google o Anthropic) lo analizza per conto di Shakers per calcolare la tua valutazione e poi viene scartato; si conserva solo il risultato: livelli, punteggi, conteggi e i nomi dei tuoi strumenti e agenti. Revisione umana: «Richiedi una revisione umana» sul sito o talent@shakersworks.com; per chiederne la cancellazione, la stessa email.",
168
+ ai_usage_result: "legge dal tuo account Shakers il risultato dell'analisi che hai approvato. Non legge nulla di questo computer e non cambia nulla.",
169
+ logout: 'cancella la sessione di Shakers salvata su questo computer (~/.config/shakers) e avvisa Shakers che la chiudi. Non cambia nulla del tuo profilo.',
170
+ list_addable: "legge dal tuo account Shakers le skill e gli agenti di IA trovati nell'analisi che hai inviato. Non cambia nulla.",
171
+ add_skill: 'aggiunge al tuo profilo Shakers la skill che scegli. Non legge nulla di questo computer.',
172
+ add_agent: "aggiunge al tuo profilo Shakers l'agente di IA che scegli, con la descrizione concordata. Non legge nient'altro di questo computer.",
173
+ add_project: "ti chiede prima e, se dici di sì, legge il nome della cartella del progetto, l'indirizzo web del suo codice e le date della prima e dell'ultima modifica, e li invia al tuo portfolio Shakers. Non invia il tuo codice.",
174
+ suggest_register_context: "ti chiede prima e, se dici di sì, guarda i nomi dei PDF e Word in Scrivania, Documenti, Download e iCloud Drive che sembrano un CV, il tuo fuso orario e il nome, l'email e l'utente GitHub dei tuoi strumenti di programmazione. Non apre quei file e non invia nulla.",
175
+ read_cv: 'ti chiede prima e, se dici di sì, legge il testo del CV che permetti per preparare la bozza del tuo profilo. Non invia nulla a Shakers.',
176
+ signup: "porta avanti la tua registrazione a Shakers passo dopo passo qui in chat. Ti chiede prima di leggere qualcosa da questo computer o di inviare i tuoi dati a Shakers, e non crea l'account finché non confermi la bozza.",
177
+ signup_start: 'inizia la tua registrazione qui in chat. Non legge ancora nulla dal tuo computer e non invia nulla.',
178
+ signup_email: 'ti chiede con quale email creare il tuo account. Non viene ancora inviato nulla.',
179
+ signup_draft: "ti mostra la bozza del tuo profilo preparata con il tuo CV e il tuo LinkedIn. Non crea l'account e non invia nulla finché non confermi.",
180
+ signup_create_account: "dopo la tua conferma e la tua accettazione dei Termini, invia a Shakers il tuo LinkedIn, il tuo CV e la bozza per preparare il profilo, e apre nel browser una pagina di Shakers (shakersworks.com) con un codice dove crei l'account. La tua password non passa mai da questa chat.",
181
+ signup_status: "controlla su Shakers se il tuo account esiste già e come procede l'importazione del profilo. Non cambia nulla.",
182
+ update_existing_profile: 'ti mostra cosa riempirebbe la bozza nei campi vuoti del tuo profilo e lo salva solo se dici di sì. Non sovrascrive mai ciò che avevi già.',
183
+ import_profile: 'invia a Shakers il tuo LinkedIn, GitHub o sito per riempire le parti vuote del profilo; un CV di questo computer si invia solo se dici di sì. Ciò che hai già scritto resta.',
184
+ save_profile_details: 'salva nel tuo profilo Shakers i dati che hai confermato (situazione, tariffe, disponibilità, località, lingue e telefono). In un account esistente riempie solo i campi vuoti.',
185
+ open_web: 'apre Shakers nel browser con la sessione già avviata, con un link monouso che scade in 2 minuti. Non cambia nulla.',
186
+ open_linkedin_profile: "apre il tuo profilo LinkedIn nel browser perché tu ne copi l'indirizzo. Non legge né invia nulla.",
187
+ social_signin_start: "prepara l'accesso: ti dà un link a una pagina di Shakers (shakersworks.com) e un codice. Lì controlli che il codice coincida e approvi. La tua password si scrive solo sul sito di Shakers, mai in questa chat. Se non hai chiesto tu questo accesso, rifiutalo.",
188
+ social_signin_poll: "controlla se hai già approvato l'accesso e, in tal caso, salva la sessione solo su questo computer (~/.config/shakers). Da allora questa chat può leggere il tuo profilo e salvarci ciò che confermi. Per chiuderla: chiedimi di uscire.",
189
+ onboarding_interview_start: 'avvia il colloquio di onboarding dopo averti chiesto dove e come rispondere. Le tue risposte vengono inviate a Shakers per conoscere il tuo profilo. Non legge nulla dal tuo computer.',
190
+ onboarding_interview_turn: 'invia a Shakers la risposta che hai dato o approvato e porta la domanda successiva.',
191
+ onboarding_interview_complete: 'chiude il colloquio e lo salva su Shakers.',
192
+ repeat_onboarding_interview: 'cancella su Shakers il tuo colloquio di onboarding precedente per poterlo ripetere. Non si può annullare.',
193
+ onboarding_finish: 'segna il tuo onboarding come completato su Shakers.',
194
+ list_available_roles: 'legge da Shakers i ruoli che puoi aggiungere al tuo profilo. Non cambia nulla.',
195
+ add_role: 'aggiunge al tuo profilo Shakers il ruolo che scegli.',
196
+ list_my_roles: 'legge i tuoi ruoli su Shakers e quelli suggeriti dal colloquio e dal CV. Non cambia nulla.',
197
+ set_main_role: 'ti chiede qual è il tuo ruolo principale e lo salva nel tuo profilo Shakers.',
198
+ report: 'mostra il tuo report di IA salvato su Shakers o quello della tua ultima analisi su questo computer, e lo salva come file su questo computer.',
199
+ share: 'crea su questo computer una scheda con il tuo report di IA da condividere su LinkedIn. Non invia nulla.',
200
+ list_certifiable_dimensions: 'legge da Shakers quali aree del tuo ruolo principale puoi certificare. Non cambia nulla.',
201
+ find_projects: 'legge da Shakers i progetti aperti più adatti a te. Non cambia nulla.',
202
+ show_project: 'legge da Shakers il dettaglio di un progetto. Non cambia nulla.',
203
+ save_project: 'salva quel progetto tra i tuoi progetti salvati su Shakers.',
204
+ unsave_project: 'toglie quel progetto dai tuoi progetti salvati su Shakers.',
205
+ list_invitations: 'legge i tuoi inviti a progetti su Shakers. Non cambia nulla.',
206
+ list_applications: 'legge le tue candidature su Shakers. Non cambia nulla.',
207
+ get_availability: 'legge la tua disponibilità dal tuo profilo Shakers. Non cambia nulla.',
208
+ list_certifications: 'legge le tue certificazioni Shakers. Non cambia nulla.',
209
+ get_profile: 'legge il tuo profilo Shakers. Non cambia nulla.',
210
+ get_rate: 'legge la tua tariffa dal tuo profilo Shakers. Non cambia nulla.',
211
+ get_languages: 'legge le tue lingue dal tuo profilo Shakers. Non cambia nulla.',
212
+ list_socials: 'legge i tuoi social dal tuo profilo Shakers. Non cambia nulla.',
213
+ list_experiences: 'legge le tue esperienze dal tuo profilo Shakers. Non cambia nulla.',
214
+ list_portfolios: 'legge i progetti del tuo portfolio dal tuo profilo Shakers. Non cambia nulla.',
215
+ alma_chat: "invia il tuo messaggio ad Alma, l'assistente di Shakers, e ti mostra la sua risposta. Se propone una modifica al tuo profilo, ti chiede prima di farla.",
216
+ alma_decision: 'invia a Shakers il tuo sì o il tuo no alla modifica del profilo proposta da Alma; solo un sì cambia il tuo profilo.',
217
+ },
218
+ },
219
+ pt: {
220
+ heading: 'O que a Shakers faz neste passo:',
221
+ failed: 'Este passo não foi concluído; não se alterou mais nada.',
222
+ mcp: {
223
+ ai_usage: 'se disser que sim, analisa neste computador as suas ferramentas de IA, o histórico dos seus projetos de código e as suas conversas com assistentes de programação com IA (Claude Code, Codex, Gemini, Cursor, Continue, Cline/Roo, Aider, Pi, OpenCode). No registo analisam-se todas as conversas deste computador; se analisar um repositório concreto, só as desse repositório. Ainda não se envia nada: primeiro mostro-lhe o que seria enviado e decide você. Não o use num computador nem com conversas que tenham dados que não pode partilhar (de clientes, da sua empresa ou de terceiros).',
224
+ ai_usage_upload: 'mostra-lhe o que seria enviado para a Shakers e só o envia se o aprovar: até 40 excertos literais das suas próprias mensagens (correções e decisões) e 20 comandos, com 200 caracteres no máximo cada um; a configuração e as definições dos seus agentes; e contagens de atividade. Antes retira-se o que parece um segredo, um email ou um caminho, mas o filtro é aproximado: não o aprove se houver dados que não pode partilhar (de clientes, da sua empresa ou de terceiros). Um modelo de IA de um fornecedor externo (OpenAI, Google ou Anthropic) analisa-o por conta da Shakers para calcular a sua avaliação e depois é descartado; só se guarda o resultado: níveis, pontuações, contagens e os nomes das suas ferramentas e agentes. Revisão humana: «Solicitar revisão humana» na web ou talent@shakersworks.com; para pedir que seja apagado, o mesmo email.',
225
+ ai_usage_result: 'lê da sua conta Shakers o resultado da análise que aprovou. Não lê nada deste computador nem altera nada.',
226
+ logout: 'apaga a sessão da Shakers guardada neste computador (~/.config/shakers) e avisa a Shakers de que a fecha. Não altera o seu perfil.',
227
+ list_addable: 'lê da sua conta Shakers as skills e os agentes de IA encontrados na análise que enviou. Não altera nada.',
228
+ add_skill: 'adiciona ao seu perfil Shakers a skill que escolher. Não lê nada deste computador.',
229
+ add_agent: 'adiciona ao seu perfil Shakers o agente de IA que escolher, com a descrição combinada. Não lê mais nada deste computador.',
230
+ add_project: 'pergunta-lhe antes e, se disser que sim, lê o nome da pasta do projeto, o endereço web do código e as datas da primeira e da última alteração, e envia-os para o seu portfolio Shakers. Não envia o seu código.',
231
+ suggest_register_context: 'pergunta-lhe antes e, se disser que sim, vê os nomes dos PDF e Word em Secretária, Documentos, Transferências e iCloud Drive que pareçam um CV, o seu fuso horário e o nome, email e utilizador GitHub das suas ferramentas de programação. Não abre esses ficheiros nem envia nada.',
232
+ read_cv: 'pergunta-lhe antes e, se disser que sim, lê o texto do CV que autorizar para preparar o rascunho do seu perfil. Não envia nada para a Shakers.',
233
+ signup: "conduz o seu registo na Shakers passo a passo aqui no chat. Pergunta-lhe antes de ler algo deste computador ou de enviar os seus dados para a Shakers, e não cria a conta até confirmar o rascunho.",
234
+ signup_start: 'começa o seu registo aqui no chat. Ainda não lê nada do seu computador nem envia nada.',
235
+ signup_email: 'pergunta-lhe com que email criar a sua conta. Ainda não se envia nada.',
236
+ signup_draft: 'mostra-lhe o rascunho do seu perfil preparado com o seu CV e o seu LinkedIn. Não cria a conta nem envia nada até confirmar.',
237
+ signup_create_account: 'depois da sua confirmação e da sua aceitação dos Termos, envia para a Shakers o seu LinkedIn, o seu CV e o rascunho para preparar o perfil, e abre no navegador uma página da Shakers (shakersworks.com) com um código onde cria a conta. A sua palavra-passe nunca passa por este chat.',
238
+ signup_status: 'consulta na Shakers se a sua conta já existe e como vai a importação do perfil. Não altera nada.',
239
+ update_existing_profile: 'mostra-lhe o que o rascunho preencheria nos campos vazios do seu perfil e só guarda se disser que sim. Nunca substitui o que já tinha.',
240
+ import_profile: 'envia para a Shakers o seu LinkedIn, GitHub ou site para preencher o que está vazio no perfil; um CV deste computador só é enviado se disser que sim. O que já escreveu mantém-se.',
241
+ save_profile_details: 'guarda no seu perfil Shakers os dados que confirmou (situação, tarifas, disponibilidade, localização, idiomas e telefone). Numa conta existente só preenche o que está vazio.',
242
+ open_web: 'abre a Shakers no navegador com a sessão iniciada, com um link de utilização única que expira em 2 minutos. Não altera nada.',
243
+ open_linkedin_profile: 'abre o seu perfil do LinkedIn no navegador para copiar o endereço. Não lê nem envia nada.',
244
+ social_signin_start: 'prepara o acesso: dá-lhe um link para uma página da Shakers (shakersworks.com) e um código. Lá confirma que o código coincide e aprova. A sua palavra-passe só se escreve no site da Shakers, nunca neste chat. Se não foi você que pediu este acesso, recuse-o.',
245
+ social_signin_poll: 'verifica se já aprovou o acesso e, nesse caso, guarda a sessão só neste computador (~/.config/shakers). A partir daí este chat pode ler o seu perfil e guardar nele o que confirmar. Para a fechar: peça-me para terminar a sessão.',
246
+ onboarding_interview_start: 'começa a entrevista de onboarding depois de lhe perguntar onde e como responder. As suas respostas são enviadas para a Shakers para conhecer o seu perfil. Não lê nada do seu computador.',
247
+ onboarding_interview_turn: 'envia para a Shakers a resposta que deu ou aprovou e traz a pergunta seguinte.',
248
+ onboarding_interview_complete: 'fecha a entrevista e guarda-a na Shakers.',
249
+ repeat_onboarding_interview: 'apaga na Shakers a sua entrevista de onboarding anterior para a poder repetir. Não se pode desfazer.',
250
+ onboarding_finish: 'marca o seu onboarding como concluído na Shakers.',
251
+ list_available_roles: 'lê da Shakers as funções que pode adicionar ao seu perfil. Não altera nada.',
252
+ add_role: 'adiciona ao seu perfil Shakers a função que escolher.',
253
+ list_my_roles: 'lê as suas funções na Shakers e as sugeridas pela entrevista e pelo CV. Não altera nada.',
254
+ set_main_role: 'pergunta-lhe qual é a sua função principal e guarda-a no seu perfil Shakers.',
255
+ report: 'mostra o seu relatório de IA guardado na Shakers ou o da sua última análise neste computador, e guarda-o como ficheiro neste computador.',
256
+ share: 'cria neste computador um cartão com o seu relatório de IA para partilhar no LinkedIn. Não envia nada.',
257
+ list_certifiable_dimensions: 'lê da Shakers que áreas da sua função principal pode certificar. Não altera nada.',
258
+ find_projects: 'lê da Shakers os projetos abertos que mais se ajustam a si. Não altera nada.',
259
+ show_project: 'lê da Shakers o detalhe de um projeto. Não altera nada.',
260
+ save_project: 'guarda esse projeto nos seus projetos guardados na Shakers.',
261
+ unsave_project: 'retira esse projeto dos seus projetos guardados na Shakers.',
262
+ list_invitations: 'lê os seus convites para projetos na Shakers. Não altera nada.',
263
+ list_applications: 'lê as suas candidaturas na Shakers. Não altera nada.',
264
+ get_availability: 'lê a sua disponibilidade do seu perfil Shakers. Não altera nada.',
265
+ list_certifications: 'lê as suas certificações Shakers. Não altera nada.',
266
+ get_profile: 'lê o seu perfil Shakers. Não altera nada.',
267
+ get_rate: 'lê a sua tarifa do seu perfil Shakers. Não altera nada.',
268
+ get_languages: 'lê os seus idiomas do seu perfil Shakers. Não altera nada.',
269
+ list_socials: 'lê as suas redes do seu perfil Shakers. Não altera nada.',
270
+ list_experiences: 'lê as suas experiências do seu perfil Shakers. Não altera nada.',
271
+ list_portfolios: 'lê os projetos do seu portfolio do seu perfil Shakers. Não altera nada.',
272
+ alma_chat: 'envia a sua mensagem à Alma, a assistente da Shakers, e mostra-lhe a resposta. Se propuser uma alteração ao seu perfil, pergunta-lhe antes de a fazer.',
273
+ alma_decision: 'envia para a Shakers o seu sim ou não à alteração de perfil que a Alma propôs; só um sim altera o seu perfil.',
274
+ },
275
+ },
276
+ };
277
+
278
+ const LEGAL_BASE_URL = 'https://www.shakersworks.com';
279
+ const LEGAL_PREFIXES = { es: '', en: 'en/', pt: 'pt/', it: 'it/' };
280
+
281
+ function termsLinks(lang) {
282
+ const code = String(lang || 'en').toLowerCase().split('-')[0];
283
+ const prefix = code in LEGAL_PREFIXES ? LEGAL_PREFIXES[code] : 'en/';
284
+ return { terms: `${LEGAL_BASE_URL}/${prefix}condiciones-generales`, privacy: `${LEGAL_BASE_URL}/${prefix}politica-de-privacidad` };
285
+ }
286
+
287
+ function termsText(lang, link) {
288
+ const code = String(lang || 'en').toLowerCase().split('-')[0];
289
+ const legal = LEGAL[code] || LEGAL.en;
290
+ const links = termsLinks(code);
291
+ return legal.terms((label) => link(label, links.terms), (label) => link(label, links.privacy));
292
+ }
293
+
39
294
  const LOCALES = { es: 'es-ES', en: 'en-GB', it: 'it-IT', pt: 'pt-PT' };
40
295
 
41
296
  // "a, b and c" in the talent's language.
@@ -68,15 +323,14 @@ function amount(lang, value) {
68
323
 
69
324
  const COPY = {
70
325
  es: {
71
- welcome: (name) => `¡Hola${name ? `, ${name}` : ''}! Gracias por querer unirte a Shakers 🙌\n\nSomos una comunidad de profesionales tech freelance. Nuestro objetivo es tener siempre tu versión más actual como profesional (lo que sabes hacer hoy, cómo trabajas con IA y qué buscas) para conectarte con proyectos que de verdad encajen contigo.\n\nEl alta son tres pasos y la mayor parte la hago yo por ti:\n1. Tu perfil (2 min). Lo relleno a partir de tu CV y LinkedIn, tú solo revisas y confirmas.\n2. Cómo trabajas con IA (1-2 min, opcional). Analizo qué herramientas usas en este equipo. Tu código y tus prompts nunca salen de tu máquina.\n3. Una breve entrevista (opcional). Para afinar tu rol y lo que buscas. Puedes hacerla ahora o más tarde.\n\nTodo lo puedes editar después desde la web.`,
72
- welcomeQuestion: '¿Empezamos? Pásame tu CV, o si lo tienes en este equipo, te lo busco yo. Con él te preparo un borrador para que solo tengas que confirmarlo.',
73
- cvSearch: 'Búscalo en mi equipo',
74
- cvAttach: 'Te lo paso',
75
- cvAttachAsk: 'Perfecto, adjúntalo aquí o dime dónde está en tu equipo.',
76
- cvNotFound: 'No he encontrado tu CV en este equipo. ¿Me lo adjuntas aquí?',
326
+ welcome: (name) => `¡Hola${name ? `, ${name}` : ''}! Gracias por querer unirte a Shakers 🙌\n\nSomos una comunidad de profesionales tech freelance. Nuestro objetivo es tener siempre tu versión más actual como profesional (lo que sabes hacer hoy, cómo trabajas con IA y qué buscas) para conectarte con proyectos que de verdad encajen contigo.\n\nEl alta son tres pasos y la mayor parte la hago yo por ti:\n1. Tu perfil (2 min). Lo relleno a partir de tu CV y LinkedIn, tú solo revisas y confirmas.\n2. Cómo trabajas con IA (1-2 min, opcional). Analizo tus herramientas de IA, el historial de tus proyectos y tus conversaciones con asistentes de IA en este ordenador. Antes de enviar nada a Shakers te enseño qué se enviaría y tú decides.\n3. Una breve entrevista (opcional). Para afinar tu rol y lo que buscas. Puedes hacerla ahora o más tarde.\n\nTodo lo puedes editar después desde la web.`,
327
+ welcomeQuestion: 'Pásame tu CV y tu LinkedIn, o si lo prefieres, busco tu CV en este ordenador.',
328
+ cvAttachAsk: 'Perfecto, adjúntalo aquí o dime dónde está en tu ordenador.',
329
+ cvNotFound: 'No he encontrado tu CV en este ordenador. ¿Me lo adjuntas aquí?',
77
330
  linkedinAsk: '¿Me pasas tu perfil de LinkedIn?',
78
331
  linkedinOpening: 'Te abro tu perfil de LinkedIn en el navegador para que copies la dirección.',
79
332
  linkedinOpened: 'Te he abierto tu perfil de LinkedIn; copia la dirección de la barra y pégamela aquí.',
333
+ webNext: "En la web puedes revisar tu perfil y ver los proyectos que encajan contigo.",
80
334
  emailKnown: (email) => `¿Te registro con ${email}? Usa el email que más mires: ahí te llegarán los proyectos que encajen contigo y las novedades de Shakers.`,
81
335
  emailUnknown: '¿Con qué email te registro? Usa el que más mires: ahí te llegarán los proyectos que encajen contigo y las novedades de Shakers.',
82
336
  emailWorkHint: 'Si es el de tu trabajo actual, quizá prefieras uno personal para no perder el acceso si cambias de empresa.',
@@ -86,12 +340,20 @@ const COPY = {
86
340
  years: (n) => `${n} años de experiencia`,
87
341
  hours: (h) => `${h} h/mes`,
88
342
  rates: (hourly, annual, estimated = true) => (estimated ? `Tarifa estimada: ${joinParts([hourly != null ? `${amount('es', hourly)} €/h` : null, annual != null ? `${amount('es', annual)} €/año` : null])} (estimación mía, ajústala)` : `Tarifa: ${joinParts([hourly != null ? `${amount('es', hourly)} €/h` : null, annual != null ? `${amount('es', annual)} €/año` : null])}`),
89
- draftQuestion: '¿Está bien o cambio algo?',
90
- draftOk: 'Todo correcto',
343
+ draftQuestion: '¿Creo tu cuenta en Shakers con estos datos o cambio algo?',
344
+ draftOk: 'Sí, crea mi cuenta en Shakers con estos datos',
91
345
  draftChange: 'Quiero cambiar algo',
92
- windowNotice: 'Cuando me confirmes, solo faltará crear tu cuenta para guardar todo esto y que puedas entrar en Shakers cuando quieras.\n\nTe abro una ventana en el navegador, elige cómo entrar:\n· Con Google\n· Con email y contraseña\n\nLa contraseña la escribes allí directamente, nunca pasa por este chat. En esa misma pantalla aceptas los términos y la política de privacidad.\n\n👉 Mira tu navegador. Cuando termines, vuelve aquí y seguimos.',
93
- windowNoticeSignIn: 'Cuando me confirmes, solo faltará entrar en la cuenta que ya tienes en Shakers con este email para guardar todo esto.\n\nTe abro una ventana en el navegador, elige cómo entrar:\n· Con Google\n· Con email y contraseña\n\nLa contraseña la escribes allí directamente, nunca pasa por este chat.\n\n👉 Mira tu navegador. Cuando termines, vuelve aquí y seguimos.',
94
- windowLink: (link) => `Si no se te ha abierto la ventana, entra desde aquí: ${link}`,
346
+ windowNotice: "Solo falta crear tu cuenta para guardar todo esto y que puedas entrar en Shakers cuando quieras. La creas en la web de Shakers (shakersworks.com) con Google, LinkedIn o email y contraseña.\n\nLa contraseña solo se escribe en la web de Shakers, nunca pasa por este chat. Si no has pedido tú este acceso, deniégalo.",
347
+ windowNoticeSignIn: "Solo falta entrar en la cuenta que ya tienes en Shakers con este email para guardar todo esto. Entras en la web de Shakers (shakersworks.com) como siempre: Google, LinkedIn o email y contraseña.\n\nLa contraseña solo se escribe en la web de Shakers, nunca pasa por este chat. Si no has pedido tú este acceso, deniégalo.",
348
+ windowBack: "👉 Mira tu navegador. Cuando termines, vuelve aquí y seguimos.",
349
+ windowLink: (link, code) => `Te abro esta página de Shakers en el navegador; si no se abre, entra desde aquí: ${link}\n\nCódigo: ${code}\nComprueba que la página muestra este mismo código y pulsa Aprobar.`,
350
+ termsQuestion: '¿Aceptas para crear tu cuenta?',
351
+ termsAccept: 'Acepto',
352
+ termsDecline: 'No acepto',
353
+ termsDeclined: 'Sin aceptar los Términos y la Política de Privacidad no puedo crear tu cuenta. No he creado nada ni he enviado tus datos a Shakers. Si cambias de idea, dímelo y seguimos.',
354
+ newsletterQuestion: 'Opcional: ¿quieres recibir nuestro contenido por email? Puedes cambiarlo cuando quieras.',
355
+ newsletterNo: 'No, gracias',
356
+ accountReady: (created) => `${created ? 'Tu cuenta ya está creada.' : 'Ya has entrado en tu cuenta de Shakers.'} Desde ahora, en este ordenador, puedo usar tu cuenta para guardar en tu perfil lo que me confirmes; nada más se envía sin preguntarte. La sesión se guarda solo en este ordenador; para cerrarla, pídeme que cierre la sesión de Shakers.`,
95
357
  existingTitle: 'He visto que ya tienes cuenta. ¿Actualizo tu perfil con esto?',
96
358
  existingRate: (from, to) => `Tarifa por hora: ${from} → ${to}`,
97
359
  existingAnnual: (from, to) => `Tarifa anual: ${from} → ${to}`,
@@ -108,13 +370,18 @@ const COPY = {
108
370
  situationLabels: { FREELANCE: 'Freelance', EMPLOYED: 'Empleado/a', BETWEEN_JOBS: 'Entre proyectos', STUDYING: 'Estudiando', OTHER: 'Otro' },
109
371
  participationLabels: { FULL_TIME: 'jornada completa', PART_TIME: 'media jornada' },
110
372
  existingUnchanged: (items) => `Estos datos de tu perfil no los he cambiado, se quedan como estaban: ${items.join(' · ')}. Si quieres otros valores, cámbialos en la web.`,
111
- aiUsageQuestion: '¿Quieres que analice cómo trabajas con IA? Es opcional y tarda 1-2 min. Solo veo qué herramientas usas, tu código y tus prompts no salen de tu máquina. Miro el uso de IA de todo este equipo, que doy por tuyo. Te ayuda a destacar en proyectos de IA.',
373
+ aiUsageQuestion: "¿Quieres que analice cómo trabajas con IA? Es opcional y tarda 1-2 min. Miro el uso de IA de todo este equipo, que doy por tuyo, y antes de subir nada te enseño lo que se enviaría. Te ayuda a destacar en proyectos de IA.",
112
374
  aiUsageYes: 'Sí, analízalo',
113
375
  aiUsageSkip: 'Saltar este paso',
114
376
  aiUsageSkipped: "Sin problema, seguimos sin él. Te aviso de que es lo que más pesa para que te lleguen proyectos: lo puedes lanzar cuando quieras diciéndome 'analiza mi uso de IA'.",
377
+ aiUsageAsk: "¿Quieres que analice cómo trabajas con IA? Es opcional y tarda 1-2 min. Te ayuda a destacar en proyectos de IA.",
378
+ aiUsageStart: "¿Empiezo el análisis?",
115
379
  consent: {
116
380
  yes: 'Sí, adelante',
117
381
  no: 'No',
382
+ cvSearchYes: 'Sí, busca mi CV en este ordenador para mi alta en Shakers',
383
+ cvReadYes: (file) => `Sí, lee mi CV (${file}) para mi alta en Shakers`,
384
+ cvSendYes: (file) => `Sí, envía mi CV (${file}) a mi cuenta de Shakers`,
118
385
  cvSearch: 'Shakers buscará en este ordenador archivos que puedan ser tu CV: mira los nombres de los archivos PDF y Word de tus carpetas Escritorio, Documentos, Descargas e iCloud Drive, y lee tu zona horaria y el nombre, el email y el usuario de GitHub que guardan tus herramientas de programación. No abre esos archivos y no envía nada. ¿Continuamos?',
119
386
  cvRead: (file) => `Shakers leerá el archivo ${file} para preparar el borrador de tu perfil. Todavía no se envía nada. ¿Continuamos?`,
120
387
  cvSend: (file) => `Shakers enviará el archivo ${file} a tu cuenta de Shakers para completar tu perfil. ¿Continuamos?`,
@@ -130,10 +397,10 @@ const COPY = {
130
397
  answerModeOwn: 'Las contesto yo',
131
398
  answerModeFull: 'Hazla entera',
132
399
  interviewOnWeb: 'La entrevista de onboarding la puedes hacer en la web: al terminar te abro Shakers con tu sesión iniciada.',
133
- livekitUnavailable: 'La entrevista no puede arrancar en este equipo. Puedes hacerla en la web: te abro Shakers con tu sesión iniciada.',
400
+ livekitUnavailable: 'La entrevista no puede arrancar en este ordenador. Puedes hacerla en la web: te abro Shakers con tu sesión iniciada.',
134
401
  rolesIntro: 'Con todo lo que me has contado, encajas en estos perfiles:',
135
- roleRecommended: (name) => `**${name}** (recomendado)`,
136
- roleName: (name) => `**${name}**`,
402
+ roleRecommended: (name) => `${name} (recomendado)`,
403
+ roleName: (name) => name,
137
404
  roleReason: { IMPORT: 'Por tu CV y tu LinkedIn', ONBOARDING_INTERVIEW: 'Por lo que me contaste en la entrevista', MATCH: 'Por tu perfil en Shakers' },
138
405
  openProjects: (n) => (n === 1 ? 'Ahora mismo hay 1 proyecto abierto que busca este perfil.' : `Ahora mismo hay ${n} proyectos abiertos que buscan este perfil.`),
139
406
  rolesOutro: 'Tu perfil principal es el que ven primero las empresas y el que usamos para recomendarte proyectos. Puedes añadir más y cambiarlo cuando quieras.',
@@ -142,6 +409,7 @@ const COPY = {
142
409
  mainRoleAsk: '¿A qué te dedicas? Dime tu rol con tus palabras y lo busco en el catálogo.',
143
410
  mainRoleNotListed: 'Otro rol',
144
411
  catalogueReason: { role: (words) => `Encaja con tu rol (${words})`, stack: (words) => `Encaja con tu stack (${words})` },
412
+ emailLabel: 'Email',
145
413
  hourlyRateLabel: 'Tarifa por hora',
146
414
  annualRateLabel: 'Tarifa anual',
147
415
  perHour: '€/h',
@@ -152,15 +420,14 @@ const COPY = {
152
420
  no: 'No',
153
421
  },
154
422
  en: {
155
- welcome: (name) => `Hi${name ? `, ${name}` : ''}! Thanks for wanting to join Shakers 🙌\n\nWe are a community of freelance tech professionals. Our goal is to always have the most up-to-date version of you as a professional (what you can do today, how you work with AI and what you are looking for) to connect you with projects that truly fit you.\n\nSigning up takes three steps and I do most of it for you:\n1. Your profile (2 min). I fill it in from your CV and LinkedIn; you just review and confirm.\n2. How you work with AI (1-2 min, optional). I look at which tools you use on this computer. Your code and your prompts never leave your machine.\n3. A short interview (optional). To fine-tune your role and what you are looking for. You can do it now or later.\n\nYou can edit everything later on the web.`,
156
- welcomeQuestion: "Shall we start? Send me your CV, or if it is on this computer, I'll look for it. With it I'll prepare a draft so you only have to confirm it.",
157
- cvSearch: 'Look for it on my computer',
158
- cvAttach: "I'll send it",
423
+ welcome: (name) => `Hi${name ? `, ${name}` : ''}! Thanks for wanting to join Shakers 🙌\n\nWe are a community of freelance tech professionals. Our goal is to always have the most up-to-date version of you as a professional (what you can do today, how you work with AI and what you are looking for) to connect you with projects that truly fit you.\n\nSigning up takes three steps and I do most of it for you:\n1. Your profile (2 min). I fill it in from your CV and LinkedIn; you just review and confirm.\n2. How you work with AI (1-2 min, optional). I analyse your AI tools, the history of your projects and your conversations with AI assistants on this computer. Before anything is sent to Shakers I show you what would be sent and you decide.\n3. A short interview (optional). To fine-tune your role and what you are looking for. You can do it now or later.\n\nYou can edit everything later on the web.`,
424
+ welcomeQuestion: "Send me your CV and your LinkedIn, or if you prefer, I'll look for your CV on this computer.",
159
425
  cvAttachAsk: 'Great, attach it here or tell me where it is on your computer.',
160
426
  cvNotFound: "I couldn't find your CV on this computer. Could you attach it here?",
161
427
  linkedinAsk: 'Could you send me your LinkedIn profile?',
162
428
  linkedinOpening: 'I am opening your LinkedIn profile in your browser so you can copy its address.',
163
429
  linkedinOpened: 'I have opened your LinkedIn profile; copy the address from the address bar and paste it here.',
430
+ webNext: "On the web you can review your profile and see the projects that fit you.",
164
431
  emailKnown: (email) => `Shall I sign you up with ${email}? Use the email you check most: that is where the projects that fit you and Shakers news will arrive.`,
165
432
  emailUnknown: 'Which email shall I sign you up with? Use the one you check most: that is where the projects that fit you and Shakers news will arrive.',
166
433
  emailWorkHint: 'If it is your current work email, you may prefer a personal one so you do not lose access if you change companies.',
@@ -170,12 +437,20 @@ const COPY = {
170
437
  years: (n) => `${n} years of experience`,
171
438
  hours: (h) => `${h} h/month`,
172
439
  rates: (hourly, annual, estimated = true) => (estimated ? `Estimated rate: ${joinParts([hourly != null ? `${amount('en', hourly)} €/h` : null, annual != null ? `${amount('en', annual)} €/year` : null])} (my estimate, adjust it)` : `Rate: ${joinParts([hourly != null ? `${amount('en', hourly)} €/h` : null, annual != null ? `${amount('en', annual)} €/year` : null])}`),
173
- draftQuestion: 'Does it look right, or shall I change something?',
174
- draftOk: 'All correct',
440
+ draftQuestion: 'Shall I create your Shakers account with these details, or change something?',
441
+ draftOk: 'Yes, create my Shakers account with these details',
175
442
  draftChange: 'I want to change something',
176
- windowNotice: "Once you confirm, all that's left is to create your account to save all this and so you can get into Shakers whenever you like.\n\nI'll open a window in your browser; choose how to sign in:\n· With Google\n· With email and password\n\nYou type the password there directly; it never goes through this chat. On that same screen you accept the terms and the privacy policy.\n\n👉 Check your browser. When you're done, come back here and we'll carry on.",
177
- windowNoticeSignIn: "Once you confirm, all that's left is to sign in to the account you already have on Shakers with this email to save all this.\n\nI'll open a window in your browser; choose how to sign in:\n· With Google\n· With email and password\n\nYou type the password there directly; it never goes through this chat.\n\n👉 Check your browser. When you're done, come back here and we'll carry on.",
178
- windowLink: (link) => `If the window did not open, use this link: ${link}`,
443
+ windowNotice: "All that's left is to create your account to save all this and so you can get into Shakers whenever you like. You create it on the Shakers website (shakersworks.com) with Google, LinkedIn or email and password.\n\nYour password is typed only on the Shakers website, it never goes through this chat. If you did not ask for this access, deny it.",
444
+ windowNoticeSignIn: "All that's left is to sign in to the account you already have on Shakers with this email to save all this. You sign in on the Shakers website (shakersworks.com) as usual: Google, LinkedIn or email and password.\n\nYour password is typed only on the Shakers website, it never goes through this chat. If you did not ask for this access, deny it.",
445
+ windowBack: "👉 Check your browser. When you're done, come back here and we'll carry on.",
446
+ windowLink: (link, code) => `I'm opening this Shakers page in your browser; if it does not open, use this link: ${link}\n\nCode: ${code}\nCheck that the page shows this same code and click Approve.`,
447
+ termsQuestion: 'Do you accept, to create your account?',
448
+ termsAccept: 'I accept',
449
+ termsDecline: 'I do not accept',
450
+ termsDeclined: 'Without accepting the Terms and the Privacy Policy I cannot create your account. I have not created anything or sent your data to Shakers. If you change your mind, tell me and we carry on.',
451
+ newsletterQuestion: 'Optional: do you want to receive our content by email? You can change it any time.',
452
+ newsletterNo: 'No, thanks',
453
+ accountReady: (created) => `${created ? 'Your account is now created.' : 'You are now signed in to your Shakers account.'} From now on, on this computer, I can use your account to save to your profile what you confirm; nothing else is sent without asking you. The session is stored only on this computer; to end it, ask me to sign out of Shakers.`,
179
454
  existingTitle: 'I see you already have an account. Shall I update your profile with this?',
180
455
  existingRate: (from, to) => `Hourly rate: ${from} → ${to}`,
181
456
  existingAnnual: (from, to) => `Annual rate: ${from} → ${to}`,
@@ -192,13 +467,18 @@ const COPY = {
192
467
  situationLabels: { FREELANCE: 'Freelance', EMPLOYED: 'Employed', BETWEEN_JOBS: 'Between jobs', STUDYING: 'Studying', OTHER: 'Other' },
193
468
  participationLabels: { FULL_TIME: 'full-time', PART_TIME: 'part-time' },
194
469
  existingUnchanged: (items) => `I did not change these details of your profile; they stay as they were: ${items.join(' · ')}. If you want other values, change them on the web.`,
195
- aiUsageQuestion: 'Do you want me to analyse how you work with AI? It is optional and takes 1-2 min. I only see which tools you use; your code and your prompts do not leave your machine. I look at the AI usage across this whole machine, which I take to be yours. It helps you stand out for AI projects.',
470
+ aiUsageQuestion: "Do you want me to analyse how you work with AI? It is optional and takes 1-2 min. I look at the AI usage across this whole machine, which I take to be yours, and I show you what would be sent before uploading anything. It helps you stand out for AI projects.",
196
471
  aiUsageYes: 'Yes, analyse it',
197
472
  aiUsageSkip: 'Skip this step',
198
473
  aiUsageSkipped: "No problem, we carry on without it. Heads-up: it is what weighs most for projects to reach you. You can run it whenever you like by telling me 'analyse my AI usage'.",
474
+ aiUsageAsk: "Do you want me to analyse how you work with AI? It is optional and takes 1-2 min. It helps you stand out for AI projects.",
475
+ aiUsageStart: "Shall I start the analysis?",
199
476
  consent: {
200
477
  yes: 'Yes, go ahead',
201
478
  no: 'No',
479
+ cvSearchYes: 'Yes, look for my CV on this computer for my Shakers sign-up',
480
+ cvReadYes: (file) => `Yes, read my CV (${file}) for my Shakers sign-up`,
481
+ cvSendYes: (file) => `Yes, send my CV (${file}) to my Shakers account`,
202
482
  cvSearch: 'Shakers will look on this computer for files that may be your CV: it lists the names of the PDF and Word files in your Desktop, Documents, Downloads and iCloud Drive folders, and reads your time zone and the name, email and GitHub user saved by your coding tools. It does not open those files and nothing is sent. Continue?',
203
483
  cvRead: (file) => `Shakers will read the file ${file} to prepare your profile draft. Nothing is sent yet. Continue?`,
204
484
  cvSend: (file) => `Shakers will send the file ${file} to your Shakers account to fill in your profile. Continue?`,
@@ -214,10 +494,10 @@ const COPY = {
214
494
  answerModeOwn: "I'll answer myself",
215
495
  answerModeFull: 'Do it all for me',
216
496
  interviewOnWeb: 'You can do the onboarding interview on the web: when we finish I will open Shakers for you, already signed in.',
217
- livekitUnavailable: 'The interview cannot start on this machine. You can do it on the web: I will open Shakers with you signed in.',
497
+ livekitUnavailable: 'The interview cannot start on this computer. You can do it on the web: I will open Shakers with you signed in.',
218
498
  rolesIntro: 'With everything you have told me, you fit these profiles:',
219
- roleRecommended: (name) => `**${name}** (recommended)`,
220
- roleName: (name) => `**${name}**`,
499
+ roleRecommended: (name) => `${name} (recommended)`,
500
+ roleName: (name) => name,
221
501
  roleReason: { IMPORT: 'From your CV and LinkedIn', ONBOARDING_INTERVIEW: 'From what you told me in the interview', MATCH: 'From your Shakers profile' },
222
502
  openProjects: (n) => (n === 1 ? 'Right now there is 1 open project looking for this profile.' : `Right now there are ${n} open projects looking for this profile.`),
223
503
  rolesOutro: 'Your main profile is the one companies see first and the one we use to recommend projects to you. You can add more and change it whenever you like.',
@@ -226,6 +506,7 @@ const COPY = {
226
506
  mainRoleAsk: 'What do you do? Tell me your role in your own words and I will look it up in the catalogue.',
227
507
  mainRoleNotListed: 'Another role',
228
508
  catalogueReason: { role: (words) => `Matches your role (${words})`, stack: (words) => `Matches your stack (${words})` },
509
+ emailLabel: 'Email',
229
510
  hourlyRateLabel: 'Hourly rate',
230
511
  annualRateLabel: 'Annual rate',
231
512
  perHour: '€/hour',
@@ -236,15 +517,14 @@ const COPY = {
236
517
  no: 'No',
237
518
  },
238
519
  it: {
239
- welcome: (name) => `Ciao${name ? `, ${name}` : ''}! Grazie per voler entrare in Shakers 🙌\n\nSiamo una community di professionisti tech freelance. Il nostro obiettivo è avere sempre la tua versione più aggiornata come professionista (cosa sai fare oggi, come lavori con l'IA e cosa cerchi) per metterti in contatto con progetti che fanno davvero per te.\n\nL'iscrizione richiede tre passaggi e la maggior parte la faccio io per te:\n1. Il tuo profilo (2 min). Lo compilo a partire dal tuo CV e da LinkedIn, tu devi solo rivederlo e confermarlo.\n2. Come lavori con l'IA (1-2 min, facoltativo). Analizzo quali strumenti usi su questo computer. Il tuo codice e i tuoi prompt non escono mai dalla tua macchina.\n3. Un breve colloquio (facoltativo). Per definire meglio il tuo ruolo e cosa cerchi. Lo fai sul sito, quando vuoi.\n\nPuoi modificare tutto in seguito dal sito.`,
240
- welcomeQuestion: 'Iniziamo? Mandami il tuo CV, oppure, se ce l’hai su questo computer, lo cerco io. Con quello ti preparo una bozza da confermare e basta.',
241
- cvSearch: 'Cercalo nel mio computer',
242
- cvAttach: 'Te lo mando',
520
+ welcome: (name) => `Ciao${name ? `, ${name}` : ''}! Grazie per voler entrare in Shakers 🙌\n\nSiamo una community di professionisti tech freelance. Il nostro obiettivo è avere sempre la tua versione più aggiornata come professionista (cosa sai fare oggi, come lavori con l'IA e cosa cerchi) per metterti in contatto con progetti che fanno davvero per te.\n\nL'iscrizione richiede tre passaggi e la maggior parte la faccio io per te:\n1. Il tuo profilo (2 min). Lo compilo a partire dal tuo CV e da LinkedIn, tu devi solo rivederlo e confermarlo.\n2. Come lavori con l'IA (1-2 min, facoltativo). Analizzo i tuoi strumenti di IA, la cronologia dei tuoi progetti e le tue conversazioni con assistenti di IA su questo computer. Prima di inviare qualsiasi cosa a Shakers ti mostro cosa verrebbe inviato e decidi tu.\n3. Un breve colloquio (facoltativo). Per definire meglio il tuo ruolo e cosa cerchi. Lo fai sul sito, quando vuoi.\n\nPuoi modificare tutto in seguito dal sito.`,
521
+ welcomeQuestion: 'Mandami il tuo CV e il tuo LinkedIn, oppure, se preferisci, cerco il tuo CV su questo computer.',
243
522
  cvAttachAsk: 'Perfetto, allegalo qui o dimmi dove si trova nel tuo computer.',
244
523
  cvNotFound: 'Non ho trovato il tuo CV su questo computer. Me lo alleghi qui?',
245
524
  linkedinAsk: 'Mi mandi il tuo profilo LinkedIn?',
246
525
  linkedinOpening: "Ti apro il tuo profilo LinkedIn nel browser così puoi copiarne l'indirizzo.",
247
526
  linkedinOpened: "Ti ho aperto il tuo profilo LinkedIn; copia l'indirizzo dalla barra e incollalo qui.",
527
+ webNext: "Sul sito puoi rivedere il tuo profilo e vedere i progetti adatti a te.",
248
528
  emailKnown: (email) => `Ti registro con ${email}? Usa l'email che controlli di più: lì ti arriveranno i progetti adatti a te e le novità di Shakers.`,
249
529
  emailUnknown: "Con quale email ti registro? Usa quella che controlli di più: lì ti arriveranno i progetti adatti a te e le novità di Shakers.",
250
530
  emailWorkHint: "Se è quella del tuo lavoro attuale, forse ne preferisci una personale per non perdere l'accesso se cambi azienda.",
@@ -254,12 +534,20 @@ const COPY = {
254
534
  years: (n) => `${n} anni di esperienza`,
255
535
  hours: (h) => `${h} h/mese`,
256
536
  rates: (hourly, annual, estimated = true) => (estimated ? `Tariffa stimata: ${joinParts([hourly != null ? `${amount('it', hourly)} €/h` : null, annual != null ? `${amount('it', annual)} €/anno` : null])} (una mia stima, modificala pure)` : `Tariffa: ${joinParts([hourly != null ? `${amount('it', hourly)} €/h` : null, annual != null ? `${amount('it', annual)} €/anno` : null])}`),
257
- draftQuestion: 'Va bene o cambio qualcosa?',
258
- draftOk: 'Tutto corretto',
537
+ draftQuestion: 'Creo il tuo account Shakers con questi dati o cambio qualcosa?',
538
+ draftOk: 'Sì, crea il mio account Shakers con questi dati',
259
539
  draftChange: 'Voglio cambiare qualcosa',
260
- windowNotice: "Quando mi confermi, mancherà solo creare il tuo account per salvare tutto questo e poter entrare in Shakers quando vuoi.\n\nTi apro una finestra nel browser, scegli come entrare:\n· Con Google\n· Con email e password\n\nLa password la scrivi direttamente lì, non passa mai da questa chat. Nella stessa schermata accetti i termini e l'informativa sulla privacy.\n\n👉 Guarda il tuo browser. Quando hai finito, torna qui e continuiamo.",
261
- windowNoticeSignIn: "Quando mi confermi, mancherà solo entrare nell'account che hai già su Shakers con questa email per salvare tutto questo.\n\nTi apro una finestra nel browser, scegli come entrare:\n· Con Google\n· Con email e password\n\nLa password la scrivi direttamente lì, non passa mai da questa chat.\n\n👉 Guarda il tuo browser. Quando hai finito, torna qui e continuiamo.",
262
- windowLink: (link) => `Se la finestra non si è aperta, entra da qui: ${link}`,
540
+ windowNotice: "Manca solo creare il tuo account per salvare tutto questo e poter entrare in Shakers quando vuoi. Lo crei sul sito di Shakers (shakersworks.com) con Google, LinkedIn o email e password.\n\nLa password si scrive solo sul sito di Shakers, non passa mai da questa chat. Se non hai chiesto tu questo accesso, rifiutalo.",
541
+ windowNoticeSignIn: "Manca solo entrare nell'account che hai già su Shakers con questa email per salvare tutto questo. Entri sul sito di Shakers (shakersworks.com) come sempre: Google, LinkedIn o email e password.\n\nLa password si scrive solo sul sito di Shakers, non passa mai da questa chat. Se non hai chiesto tu questo accesso, rifiutalo.",
542
+ windowBack: "👉 Guarda il tuo browser. Quando hai finito, torna qui e continuiamo.",
543
+ windowLink: (link, code) => `Apro questa pagina di Shakers nel tuo browser; se non si apre, entra da qui: ${link}\n\nCodice: ${code}\nControlla che la pagina mostri lo stesso codice e premi Approva.`,
544
+ termsQuestion: 'Accetti, per creare il tuo account?',
545
+ termsAccept: 'Accetto',
546
+ termsDecline: 'Non accetto',
547
+ termsDeclined: "Senza accettare i Termini e l'Informativa sulla privacy non posso creare il tuo account. Non ho creato nulla né inviato i tuoi dati a Shakers. Se cambi idea, dimmelo e continuiamo.",
548
+ newsletterQuestion: 'Facoltativo: vuoi ricevere i nostri contenuti via email? Puoi cambiarlo quando vuoi.',
549
+ newsletterNo: 'No, grazie',
550
+ accountReady: (created) => `${created ? 'Il tuo account è stato creato.' : 'Sei entrato nel tuo account Shakers.'} Da ora, su questo computer, posso usare il tuo account per salvare nel profilo ciò che confermi; nient\'altro viene inviato senza chiedertelo. La sessione resta solo su questo computer; per chiuderla, chiedimi di uscire da Shakers.`,
263
551
  existingTitle: 'Ho visto che hai già un account. Aggiorno il tuo profilo con questi dati?',
264
552
  existingRate: (from, to) => `Tariffa oraria: ${from} → ${to}`,
265
553
  existingAnnual: (from, to) => `Tariffa annuale: ${from} → ${to}`,
@@ -276,13 +564,18 @@ const COPY = {
276
564
  situationLabels: { FREELANCE: 'Freelance', EMPLOYED: 'Dipendente', BETWEEN_JOBS: "Tra un lavoro e l'altro", STUDYING: 'Studente', OTHER: 'Altro' },
277
565
  participationLabels: { FULL_TIME: 'tempo pieno', PART_TIME: 'part-time' },
278
566
  existingUnchanged: (items) => `Questi dati del tuo profilo non li ho cambiati, restano com'erano: ${items.join(' · ')}. Se vuoi altri valori, cambiali sul sito.`,
279
- aiUsageQuestion: "Vuoi che analizzi come lavori con l'IA? È facoltativo e richiede 1-2 min. Vedo solo quali strumenti usi, il tuo codice e i tuoi prompt non escono dalla tua macchina. Guardo l'uso dell'IA su tutto questo computer, che considero tuo. Ti aiuta a distinguerti nei progetti di IA.",
567
+ aiUsageQuestion: "Vuoi che analizzi come lavori con l'IA? È facoltativo e richiede 1-2 min. Guardo l'uso dell'IA su tutto questo computer, che considero tuo, e prima di caricare qualcosa ti mostro cosa verrebbe inviato. Ti aiuta a distinguerti nei progetti di IA.",
280
568
  aiUsageYes: 'Sì, analizzalo',
281
569
  aiUsageSkip: 'Salta questo passaggio',
282
570
  aiUsageSkipped: "Nessun problema, andiamo avanti senza. Ti avviso che è ciò che pesa di più perché ti arrivino progetti: puoi lanciarlo quando vuoi dicendomi 'analizza il mio uso dell'IA'.",
571
+ aiUsageAsk: "Vuoi che analizzi come lavori con l'IA? È facoltativo e richiede 1-2 min. Ti aiuta a distinguerti nei progetti di IA.",
572
+ aiUsageStart: "Avvio l'analisi?",
283
573
  consent: {
284
574
  yes: 'Sì, procedi',
285
575
  no: 'No',
576
+ cvSearchYes: 'Sì, cerca il mio CV in questo computer per la mia iscrizione a Shakers',
577
+ cvReadYes: (file) => `Sì, leggi il mio CV (${file}) per la mia iscrizione a Shakers`,
578
+ cvSendYes: (file) => `Sì, invia il mio CV (${file}) al mio account Shakers`,
286
579
  cvSearch: "Shakers cercherà su questo computer i file che potrebbero essere il tuo CV: guarda i nomi dei file PDF e Word nelle cartelle Scrivania, Documenti, Download e iCloud Drive, e legge il tuo fuso orario e il nome, l'email e l'utente GitHub salvati dai tuoi strumenti di programmazione. Non apre quei file e non invia nulla. Continuiamo?",
287
580
  cvRead: (file) => `Shakers leggerà il file ${file} per preparare la bozza del tuo profilo. Per ora non viene inviato nulla. Continuiamo?`,
288
581
  cvSend: (file) => `Shakers invierà il file ${file} al tuo account Shakers per completare il tuo profilo. Continuiamo?`,
@@ -300,8 +593,8 @@ const COPY = {
300
593
  interviewOnWeb: 'Il colloquio di onboarding puoi farlo sul sito: alla fine ti apro Shakers con la sessione già avviata.',
301
594
  livekitUnavailable: 'Il colloquio non può partire su questo computer. Puoi farlo sul sito: ti apro Shakers con la sessione già avviata.',
302
595
  rolesIntro: 'Con tutto quello che mi hai raccontato, sei adatto a questi profili:',
303
- roleRecommended: (name) => `**${name}** (consigliato)`,
304
- roleName: (name) => `**${name}**`,
596
+ roleRecommended: (name) => `${name} (consigliato)`,
597
+ roleName: (name) => name,
305
598
  roleReason: { IMPORT: 'Dal tuo CV e da LinkedIn', ONBOARDING_INTERVIEW: 'Da quello che mi hai raccontato nel colloquio', MATCH: 'Dal tuo profilo su Shakers' },
306
599
  openProjects: (n) => (n === 1 ? "In questo momento c'è 1 progetto aperto che cerca questo profilo." : `In questo momento ci sono ${n} progetti aperti che cercano questo profilo.`),
307
600
  rolesOutro: 'Il tuo profilo principale è quello che le aziende vedono per primo e quello che usiamo per consigliarti progetti. Puoi aggiungerne altri e cambiarlo quando vuoi.',
@@ -310,6 +603,7 @@ const COPY = {
310
603
  mainRoleAsk: 'Di cosa ti occupi? Dimmi il tuo ruolo con parole tue e lo cerco nel catalogo.',
311
604
  mainRoleNotListed: 'Un altro ruolo',
312
605
  catalogueReason: { role: (words) => `Corrisponde al tuo ruolo (${words})`, stack: (words) => `Corrisponde al tuo stack (${words})` },
606
+ emailLabel: 'Email',
313
607
  hourlyRateLabel: 'Tariffa oraria',
314
608
  annualRateLabel: 'Tariffa annuale',
315
609
  perHour: '€/h',
@@ -320,15 +614,14 @@ const COPY = {
320
614
  no: 'No',
321
615
  },
322
616
  pt: {
323
- welcome: (name) => `Olá${name ? `, ${name}` : ''}! Obrigado por querer juntar-se à Shakers 🙌\n\nSomos uma comunidade de profissionais tech freelance. O nosso objetivo é ter sempre a sua versão mais atual como profissional (o que sabe fazer hoje, como trabalha com IA e o que procura) para o pôr em contacto com projetos que encaixem mesmo consigo.\n\nO registo tem três passos e a maior parte faço eu por si:\n1. O seu perfil (2 min). Preencho-o a partir do seu CV e do LinkedIn; só tem de rever e confirmar.\n2. Como trabalha com IA (1-2 min, opcional). Analiso que ferramentas usa neste computador. O seu código e os seus prompts nunca saem da sua máquina.\n3. Uma breve entrevista (opcional). Para afinar o seu perfil e o que procura. Faz-se na web, quando quiser.\n\nPode editar tudo depois na web.`,
324
- welcomeQuestion: 'Começamos? Envie-me o seu CV ou, se o tiver neste computador, procuro-o eu. Com ele preparo um rascunho para que só tenha de o confirmar.',
325
- cvSearch: 'Procure-o no meu computador',
326
- cvAttach: 'Envio-o eu',
617
+ welcome: (name) => `Olá${name ? `, ${name}` : ''}! Obrigado por querer juntar-se à Shakers 🙌\n\nSomos uma comunidade de profissionais tech freelance. O nosso objetivo é ter sempre a sua versão mais atual como profissional (o que sabe fazer hoje, como trabalha com IA e o que procura) para o pôr em contacto com projetos que encaixem mesmo consigo.\n\nO registo tem três passos e a maior parte faço eu por si:\n1. O seu perfil (2 min). Preencho-o a partir do seu CV e do LinkedIn; só tem de rever e confirmar.\n2. Como trabalha com IA (1-2 min, opcional). Analiso as suas ferramentas de IA, o histórico dos seus projetos e as suas conversas com assistentes de IA neste computador. Antes de enviar alguma coisa para a Shakers mostro-lhe o que seria enviado e decide você.\n3. Uma breve entrevista (opcional). Para afinar o seu perfil e o que procura. Faz-se na web, quando quiser.\n\nPode editar tudo depois na web.`,
618
+ welcomeQuestion: 'Envie-me o seu CV e o seu LinkedIn ou, se preferir, procuro o seu CV neste computador.',
327
619
  cvAttachAsk: 'Perfeito, anexe-o aqui ou diga-me onde está no seu computador.',
328
620
  cvNotFound: 'Não encontrei o seu CV neste computador. Pode anexá-lo aqui?',
329
621
  linkedinAsk: 'Pode enviar-me o seu perfil do LinkedIn?',
330
622
  linkedinOpening: 'Vou abrir o seu perfil do LinkedIn no navegador para que copie o endereço.',
331
623
  linkedinOpened: 'Abri o seu perfil do LinkedIn; copie o endereço da barra e cole-o aqui.',
624
+ webNext: "Na web pode rever o seu perfil e ver os projetos que encaixam consigo.",
332
625
  emailKnown: (email) => `Registo-o com ${email}? Use o email que consulta mais: é aí que vai receber os projetos que encaixam consigo e as novidades da Shakers.`,
333
626
  emailUnknown: 'Com que email o registo? Use o que consulta mais: é aí que vai receber os projetos que encaixam consigo e as novidades da Shakers.',
334
627
  emailWorkHint: 'Se for o do seu trabalho atual, talvez prefira um pessoal para não perder o acesso se mudar de empresa.',
@@ -338,12 +631,20 @@ const COPY = {
338
631
  years: (n) => `${n} anos de experiência`,
339
632
  hours: (h) => `${h} h/mês`,
340
633
  rates: (hourly, annual, estimated = true) => (estimated ? `Tarifa estimada: ${joinParts([hourly != null ? `${amount('pt', hourly)} €/h` : null, annual != null ? `${amount('pt', annual)} €/ano` : null])} (estimativa minha, ajuste-a)` : `Tarifa: ${joinParts([hourly != null ? `${amount('pt', hourly)} €/h` : null, annual != null ? `${amount('pt', annual)} €/ano` : null])}`),
341
- draftQuestion: 'Está bem ou mudo alguma coisa?',
342
- draftOk: 'Está tudo certo',
634
+ draftQuestion: 'Crio a sua conta Shakers com estes dados ou mudo alguma coisa?',
635
+ draftOk: 'Sim, crie a minha conta Shakers com estes dados',
343
636
  draftChange: 'Quero mudar algo',
344
- windowNotice: 'Quando me confirmar, só faltará criar a sua conta para guardar tudo isto e poder entrar na Shakers quando quiser.\n\nAbro-lhe uma janela no navegador, escolha como entrar:\n· Com Google\n· Com email e palavra-passe\n\nEscreve a palavra-passe diretamente lá; nunca passa por este chat. Nesse mesmo ecrã aceita os termos e a política de privacidade.\n\n👉 Veja o seu navegador. Quando terminar, volte aqui e continuamos.',
345
- windowNoticeSignIn: 'Quando me confirmar, só faltará entrar na conta que já tem na Shakers com este email para guardar tudo isto.\n\nAbro-lhe uma janela no navegador, escolha como entrar:\n· Com Google\n· Com email e palavra-passe\n\nEscreve a palavra-passe diretamente lá; nunca passa por este chat.\n\n👉 Veja o seu navegador. Quando terminar, volte aqui e continuamos.',
346
- windowLink: (link) => `Se a janela não abriu, entre por aqui: ${link}`,
637
+ windowNotice: "Só falta criar a sua conta para guardar tudo isto e poder entrar na Shakers quando quiser. Cria-a no site da Shakers (shakersworks.com) com Google, LinkedIn ou email e palavra-passe.\n\nA palavra-passe só se escreve no site da Shakers, nunca passa por este chat. Se não foi você que pediu este acesso, recuse-o.",
638
+ windowNoticeSignIn: "Só falta entrar na conta que já tem na Shakers com este email para guardar tudo isto. Entra no site da Shakers (shakersworks.com) como sempre: Google, LinkedIn ou email e palavra-passe.\n\nA palavra-passe só se escreve no site da Shakers, nunca passa por este chat. Se não foi você que pediu este acesso, recuse-o.",
639
+ windowBack: "👉 Veja o seu navegador. Quando terminar, volte aqui e continuamos.",
640
+ windowLink: (link, code) => `Abro esta página da Shakers no seu navegador; se não abrir, entre por aqui: ${link}\n\nCódigo: ${code}\nConfirme que a página mostra este mesmo código e carregue em Aprovar.`,
641
+ termsQuestion: 'Aceita, para criar a sua conta?',
642
+ termsAccept: 'Aceito',
643
+ termsDecline: 'Não aceito',
644
+ termsDeclined: 'Sem aceitar os Termos e a Política de Privacidade não posso criar a sua conta. Não criei nada nem enviei os seus dados para a Shakers. Se mudar de ideias, diga-me e continuamos.',
645
+ newsletterQuestion: 'Opcional: quer receber os nossos conteúdos por email? Pode mudar isto quando quiser.',
646
+ newsletterNo: 'Não, obrigado',
647
+ accountReady: (created) => `${created ? 'A sua conta já está criada.' : 'Já entrou na sua conta Shakers.'} A partir de agora, neste computador, posso usar a sua conta para guardar no seu perfil o que confirmar; nada mais é enviado sem lhe perguntar. A sessão fica guardada só neste computador; para a fechar, peça-me para terminar a sessão da Shakers.`,
347
648
  existingTitle: 'Vi que já tem conta. Atualizo o seu perfil com isto?',
348
649
  existingRate: (from, to) => `Tarifa por hora: ${from} → ${to}`,
349
650
  existingAnnual: (from, to) => `Tarifa anual: ${from} → ${to}`,
@@ -360,13 +661,18 @@ const COPY = {
360
661
  situationLabels: { FREELANCE: 'Freelancer', EMPLOYED: 'Por conta de outrem', BETWEEN_JOBS: 'Entre trabalhos', STUDYING: 'A estudar', OTHER: 'Outra' },
361
662
  participationLabels: { FULL_TIME: 'tempo inteiro', PART_TIME: 'tempo parcial' },
362
663
  existingUnchanged: (items) => `Estes dados do seu perfil não os alterei, ficam como estavam: ${items.join(' · ')}. Se quiser outros valores, altere-os na web.`,
363
- aiUsageQuestion: 'Quer que analise como trabalha com IA? É opcional e demora 1-2 min. Só vejo que ferramentas usa; o seu código e os seus prompts não saem da sua máquina. Analiso o uso de IA em todo este computador, que considero seu. Ajuda-o a destacar-se em projetos de IA.',
664
+ aiUsageQuestion: "Quer que analise como trabalha com IA? É opcional e demora 1-2 min. Analiso o uso de IA em todo este computador, que considero seu, e antes de enviar algo mostro-lhe o que seria enviado. Ajuda-o a destacar-se em projetos de IA.",
364
665
  aiUsageYes: 'Sim, analise',
365
666
  aiUsageSkip: 'Saltar este passo',
366
667
  aiUsageSkipped: "Sem problema, seguimos sem ele. Aviso-o de que é o que mais pesa para lhe chegarem projetos: pode lançá-lo quando quiser dizendo-me 'analisa o meu uso de IA'.",
668
+ aiUsageAsk: "Quer que analise como trabalha com IA? É opcional e demora 1-2 min. Ajuda-o a destacar-se em projetos de IA.",
669
+ aiUsageStart: "Começo a análise?",
367
670
  consent: {
368
671
  yes: 'Sim, avançar',
369
672
  no: 'Não',
673
+ cvSearchYes: 'Sim, procure o meu CV neste computador para o meu registo na Shakers',
674
+ cvReadYes: (file) => `Sim, leia o meu CV (${file}) para o meu registo na Shakers`,
675
+ cvSendYes: (file) => `Sim, envie o meu CV (${file}) para a minha conta Shakers`,
370
676
  cvSearch: 'A Shakers vai procurar neste computador ficheiros que possam ser o seu CV: vê os nomes dos ficheiros PDF e Word das pastas Secretária, Documentos, Transferências e iCloud Drive, e lê o seu fuso horário e o nome, o email e o utilizador de GitHub guardados pelas suas ferramentas de programação. Não abre esses ficheiros e não envia nada. Continuamos?',
371
677
  cvRead: (file) => `A Shakers vai ler o ficheiro ${file} para preparar o rascunho do seu perfil. Ainda não é enviado nada. Continuamos?`,
372
678
  cvSend: (file) => `A Shakers vai enviar o ficheiro ${file} para a sua conta Shakers para completar o seu perfil. Continuamos?`,
@@ -384,8 +690,8 @@ const COPY = {
384
690
  interviewOnWeb: 'Pode fazer a entrevista de onboarding na web: no fim abro-lhe o Shakers com a sessão iniciada.',
385
691
  livekitUnavailable: 'A entrevista não pode começar neste computador. Pode fazê-la na web: abro-lhe a Shakers com a sessão iniciada.',
386
692
  rolesIntro: 'Com tudo o que me contou, encaixa nestes perfis:',
387
- roleRecommended: (name) => `**${name}** (recomendado)`,
388
- roleName: (name) => `**${name}**`,
693
+ roleRecommended: (name) => `${name} (recomendado)`,
694
+ roleName: (name) => name,
389
695
  roleReason: { IMPORT: 'Pelo seu CV e pelo LinkedIn', ONBOARDING_INTERVIEW: 'Pelo que me contou na entrevista', MATCH: 'Pelo seu perfil na Shakers' },
390
696
  openProjects: (n) => (n === 1 ? 'Neste momento há 1 projeto aberto que procura este perfil.' : `Neste momento há ${n} projetos abertos que procuram este perfil.`),
391
697
  rolesOutro: 'O seu perfil principal é o que as empresas veem primeiro e o que usamos para lhe recomendar projetos. Pode adicionar mais e alterá-lo quando quiser.',
@@ -394,6 +700,7 @@ const COPY = {
394
700
  mainRoleAsk: 'A que se dedica? Diga-me o seu perfil por palavras suas e eu procuro-o no catálogo.',
395
701
  mainRoleNotListed: 'Outro perfil',
396
702
  catalogueReason: { role: (words) => `Corresponde ao seu perfil (${words})`, stack: (words) => `Corresponde ao seu stack (${words})` },
703
+ emailLabel: 'Email',
397
704
  hourlyRateLabel: 'Tarifa por hora',
398
705
  annualRateLabel: 'Tarifa anual',
399
706
  perHour: '€/h',
@@ -405,6 +712,177 @@ const COPY = {
405
712
  },
406
713
  };
407
714
 
715
+ const REVIEW = {
716
+ es: {
717
+ cliQuestion: '¿Lo subo a tu perfil? [s/N]:',
718
+ cliIncludeExcerpts: '¿Incluir los extractos de texto? [S/n]:',
719
+ cliExcludeAgents: 'Si quieres dejar fuera algún agente, escribe sus nombres separados por comas (Enter para ninguno):',
720
+ cliUnknownAgents: (unknown, known) => `No reconozco: ${unknown}. Los agentes son: ${known}.`,
721
+ cliRepoHint: 'Para dejar fuera un repositorio, vuelve a ejecutar el comando eligiendo solo los que quieras con --repos.',
722
+ title: 'Todavía no se ha subido nada a tu perfil de Shakers. Esto es lo que se subiría:',
723
+ sentTitle: 'Esto es lo que se ha subido:',
724
+ tools: (list) => `Herramientas de IA detectadas: ${list}.`,
725
+ technologies: (list) => `Tecnologías de tus proyectos: ${list}.`,
726
+ activity: (sessions, days) => `Tu actividad con IA: ${sessions === 1 ? '1 sesión' : `${sessions} sesiones`}${days === null ? '' : `, unos ${days} días activos por semana`}.`,
727
+ repos: (list, commits) => `Repositorios analizados: ${list}. De ellos solo se envían cifras (${commits === 1 ? '1 commit' : `${commits} commits`}, líneas cambiadas, tipos de fichero), nunca su nombre ni su código.`,
728
+ agents: (list, definitions, summaries) => `Agentes de IA detectados: ${list}. De cada uno se envía ${listOf('es', ['su nombre', 'sus herramientas', 'su modelo', 'de qué agente depende', ...(definitions > 0 ? ['el texto de su definición (sin secretos)'] : []), ...(summaries > 0 ? ['el resumen de lo que hace'] : [])])}.`,
729
+ excerpts: (conversation, commands) => `Extractos de texto: ${conversation} fragmentos literales de tus mensajes a la IA y ${commands} comandos, de 200 caracteres como máximo, tras quitar lo que parezca un secreto, una ruta o un email.`,
730
+ noExcerpts: 'Extractos de texto: ninguno.',
731
+ notSent: 'No se envía: tu código, tus conversaciones completas ni los nombres de tus repositorios.',
732
+ explanationTitle: 'Qué hace Shakers con esto:',
733
+ explanation: [
734
+ 'Te sitúa en dos ejes. Configuración: qué tienes montado (herramientas, servidores MCP, agentes), de menos a más: Asistido, Extendido u Orquestado. Uso: cómo diriges a la IA (si le das instrucciones concretas, si revisas lo que propone), de menos a más: Reactivo, Deliberado o Riguroso.',
735
+ "La configuración se calcula con reglas fijas a partir de lo detectado. El nivel de uso y los textos sobre cómo trabajas los escribe un modelo de IA de un proveedor externo (OpenAI, Google o Anthropic) por encargo de Shakers. Ese modelo lee los extractos de texto, los comandos y las definiciones de tus agentes para calcular tu evaluación y después los descarta: Shakers guarda el resultado (niveles, puntuaciones, recuentos y los nombres de tus herramientas y agentes), no esos textos.",
736
+ 'En tu perfil aparece la sección «Mi trabajo con IA»: tus dos niveles, tu casilla en la matriz (por ejemplo Practitioner o Architect), tus herramientas y agentes, y dos textos que puedes editar en la web.',
737
+ ],
738
+ afterwards: 'Si no estás de acuerdo con el resultado, pulsa «Solicitar revisión humana» en esa sección de la web o escribe a talent@shakersworks.com. Hoy no puedes ocultar la sección tú mismo; para pedir que se borre, escribe a ese mismo correo.',
739
+ excludeHint: 'Si quieres dejar fuera algún repositorio, algún agente o los extractos de texto, dímelo al responder.',
740
+ question: '¿Lo subo a tu perfil?',
741
+ yes: 'Subir a mi perfil',
742
+ no: 'No subir',
743
+ formExcerpts: 'Incluir los extractos de texto',
744
+ formRepo: (name) => `Incluir el repositorio ${name}`,
745
+ formAgent: (name) => `Incluir el agente ${name}`,
746
+ declined: 'No se ha subido nada. El análisis se queda solo en tu ordenador.',
747
+ nothingLeft: 'Has dejado fuera todos los repositorios, así que no se ha subido nada.',
748
+ throttled: 'Ya subiste un análisis hace menos de una hora, así que no se ha subido nada. Puedes volver a intentarlo más tarde.',
749
+ uploaded: 'Subido a tu perfil. Shakers lo está evaluando y el resultado tarda unos minutos.',
750
+ uploadFailed: 'No se ha podido subir. Tu análisis se queda solo en tu ordenador; puedes volver a intentarlo más tarde.',
751
+ resultTitle: 'Tu resultado en «Mi trabajo con IA»:',
752
+ setupLevel: (level) => `Configuración: ${level}.`,
753
+ usageLevel: (level) => `Uso: ${level}.`,
754
+ cell: (name) => `Tu casilla en la matriz: ${name}.`,
755
+ levels: { ASSISTED: 'Asistido', EXTENDED: 'Extendido', ORCHESTRATED: 'Orquestado', REACTIVE: 'Reactivo', DELIBERATE: 'Deliberado', RIGOROUS: 'Riguroso' },
756
+ resultPending: 'Tu resultado todavía no está en «Mi trabajo con IA». Si acabas de subir tu análisis, aparecerá en unos minutos.',
757
+ },
758
+ en: {
759
+ cliQuestion: 'Shall I upload it to your profile? [y/N]:',
760
+ cliIncludeExcerpts: 'Include the text excerpts? [Y/n]:',
761
+ cliExcludeAgents: 'If you want to leave out an agent, type their names separated by commas (Enter for none):',
762
+ cliUnknownAgents: (unknown, known) => `I do not recognise: ${unknown}. The agents are: ${known}.`,
763
+ cliRepoHint: 'To leave out a repository, run the command again choosing only the ones you want with --repos.',
764
+ title: 'Nothing has been uploaded to your Shakers profile yet. This is what would be uploaded:',
765
+ sentTitle: 'This is what was uploaded:',
766
+ tools: (list) => `AI tools detected: ${list}.`,
767
+ technologies: (list) => `Technologies in your projects: ${list}.`,
768
+ activity: (sessions, days) => `Your AI activity: ${sessions === 1 ? '1 session' : `${sessions} sessions`}${days === null ? '' : `, about ${days} active days a week`}.`,
769
+ repos: (list, commits) => `Repositories analysed: ${list}. Only figures about them are sent (${commits === 1 ? '1 commit' : `${commits} commits`}, lines changed, file types), never their name or their code.`,
770
+ agents: (list, definitions, summaries) => `AI agents detected: ${list}. For each one we send ${listOf('en', ['its name', 'its tools', 'its model', 'which agent it reports to', ...(definitions > 0 ? ['the text of its definition (with secrets removed)'] : []), ...(summaries > 0 ? ['the summary of what it does'] : [])])}.`,
771
+ excerpts: (conversation, commands) => `Text excerpts: ${conversation} literal fragments of your messages to the AI and ${commands} commands, 200 characters at most, after removing anything that looks like a secret, a path or an email.`,
772
+ noExcerpts: 'Text excerpts: none.',
773
+ notSent: 'Not sent: your code, your full conversations or the names of your repositories.',
774
+ explanationTitle: 'What Shakers does with it:',
775
+ explanation: [
776
+ 'It places you on two axes. Setup: what you have in place (tools, MCP servers, agents), from less to more: Assisted, Extended or Orchestrated. Usage: how you steer the AI (whether you give it concrete instructions, whether you review what it proposes), from less to more: Reactive, Deliberate or Rigorous.',
777
+ "Setup is worked out with fixed rules from what was detected. The usage level and the texts about how you work are written by an AI model from an external provider (OpenAI, Google or Anthropic) on Shakers' behalf. That model reads the text excerpts, the commands and your agents' definitions to compute your evaluation and then discards them: Shakers keeps the result (levels, scores, counts and the names of your tools and agents), not those texts.",
778
+ 'Your profile shows the "My work with AI" section: your two levels, your square in the matrix (for example Practitioner or Architect), your tools and agents, and two texts you can edit on the web.',
779
+ ],
780
+ afterwards: 'If you disagree with the result, press "Request human review" in that section on the web or write to talent@shakersworks.com. Today you cannot hide the section yourself; to ask for it to be deleted, write to that same address.',
781
+ excludeHint: 'If you want to leave out a repository, an agent or the text excerpts, tell me when you answer.',
782
+ question: 'Shall I upload it to your profile?',
783
+ yes: 'Upload to my profile',
784
+ no: 'Do not upload',
785
+ formExcerpts: 'Include the text excerpts',
786
+ formRepo: (name) => `Include the repository ${name}`,
787
+ formAgent: (name) => `Include the agent ${name}`,
788
+ declined: 'Nothing was uploaded. The analysis stays on your computer only.',
789
+ nothingLeft: 'You left out every repository, so nothing was uploaded.',
790
+ throttled: 'You uploaded an analysis less than an hour ago, so nothing was uploaded. You can try again later.',
791
+ uploaded: 'Uploaded to your profile. Shakers is evaluating it and the result takes a few minutes.',
792
+ uploadFailed: 'It could not be uploaded. Your analysis stays on your computer only; you can try again later.',
793
+ resultTitle: 'Your result in "My work with AI":',
794
+ setupLevel: (level) => `Setup: ${level}.`,
795
+ usageLevel: (level) => `Usage: ${level}.`,
796
+ cell: (name) => `Your square in the matrix: ${name}.`,
797
+ levels: { ASSISTED: 'Assisted', EXTENDED: 'Extended', ORCHESTRATED: 'Orchestrated', REACTIVE: 'Reactive', DELIBERATE: 'Deliberate', RIGOROUS: 'Rigorous' },
798
+ resultPending: 'Your result is not in "My work with AI" yet. If you have just uploaded your analysis, it will show in a few minutes.',
799
+ },
800
+ it: {
801
+ cliQuestion: 'Lo carico sul tuo profilo? [s/N]:',
802
+ cliIncludeExcerpts: 'Includere gli estratti di testo? [S/n]:',
803
+ cliExcludeAgents: 'Se vuoi escludere un agente, scrivi i nomi separati da virgole (Invio per nessuno):',
804
+ cliUnknownAgents: (unknown, known) => `Non riconosco: ${unknown}. Gli agenti sono: ${known}.`,
805
+ cliRepoHint: 'Per escludere un repository, esegui di nuovo il comando scegliendo solo quelli che vuoi con --repos.',
806
+ title: "Non è stato ancora caricato nulla sul tuo profilo Shakers. Ecco cosa verrebbe caricato:",
807
+ sentTitle: "Ecco cosa è stato caricato:",
808
+ tools: (list) => `Strumenti di IA rilevati: ${list}.`,
809
+ technologies: (list) => `Tecnologie dei tuoi progetti: ${list}.`,
810
+ activity: (sessions, days) => `La tua attività con l'IA: ${sessions === 1 ? '1 sessione' : `${sessions} sessioni`}${days === null ? '' : `, circa ${days} giorni attivi a settimana`}.`,
811
+ repos: (list, commits) => `Repository analizzati: ${list}. Di questi si inviano solo cifre (${commits} commit, righe modificate, tipi di file), mai il nome né il codice.`,
812
+ agents: (list, definitions, summaries) => `Agenti di IA rilevati: ${list}. Di ognuno si inviano ${listOf('it', ['il nome', 'gli strumenti', 'il modello', 'da quale agente dipende', ...(definitions > 0 ? ['il testo della sua definizione (senza segreti)'] : []), ...(summaries > 0 ? ['il riassunto di ciò che fa'] : [])])}.`,
813
+ excerpts: (conversation, commands) => `Estratti di testo: ${conversation} frammenti letterali dei tuoi messaggi all'IA e ${commands} comandi, al massimo 200 caratteri, dopo aver tolto ciò che sembra un segreto, un percorso o un'email.`,
814
+ noExcerpts: 'Estratti di testo: nessuno.',
815
+ notSent: 'Non si inviano: il tuo codice, le tue conversazioni complete né i nomi dei tuoi repository.',
816
+ explanationTitle: 'Cosa fa Shakers con questi dati:',
817
+ explanation: [
818
+ "Ti colloca su due assi. Configurazione: cosa hai impostato (strumenti, server MCP, agenti), dal meno al più: Assistito, Esteso o Orchestrato. Uso: come guidi l'IA (se le dai istruzioni concrete, se controlli ciò che propone), dal meno al più: Reattivo, Deliberato o Rigoroso.",
819
+ "La configurazione si calcola con regole fisse da ciò che è stato rilevato. Il livello di uso e i testi su come lavori li scrive un modello di IA di un fornitore esterno (OpenAI, Google o Anthropic) per conto di Shakers. Quel modello legge gli estratti di testo, i comandi e le definizioni dei tuoi agenti per calcolare la tua valutazione e poi li scarta: Shakers conserva il risultato (livelli, punteggi, conteggi e i nomi dei tuoi strumenti e agenti), non quei testi.",
820
+ "Sul tuo profilo compare la sezione «Il mio lavoro con l'IA»: i tuoi due livelli, la tua casella nella matrice (per esempio Practitioner o Architect), i tuoi strumenti e agenti, e due testi che puoi modificare sul sito.",
821
+ ],
822
+ afterwards: 'Se non sei d\'accordo con il risultato, premi «Richiedi una revisione umana» in quella sezione del sito o scrivi a talent@shakersworks.com. Oggi non puoi nascondere la sezione da solo; per chiedere che venga cancellata, scrivi allo stesso indirizzo.',
823
+ excludeHint: 'Se vuoi escludere un repository, un agente o gli estratti di testo, dimmelo quando rispondi.',
824
+ question: 'Lo carico sul tuo profilo?',
825
+ yes: 'Carica sul mio profilo',
826
+ no: 'Non caricare',
827
+ formExcerpts: 'Includi gli estratti di testo',
828
+ formRepo: (name) => `Includi il repository ${name}`,
829
+ formAgent: (name) => `Includi l'agente ${name}`,
830
+ declined: "Non è stato caricato nulla. L'analisi resta solo sul tuo computer.",
831
+ nothingLeft: 'Hai escluso tutti i repository, quindi non è stato caricato nulla.',
832
+ throttled: "Hai caricato un'analisi meno di un'ora fa, quindi non è stato caricato nulla. Puoi riprovare più tardi.",
833
+ uploaded: 'Caricato sul tuo profilo. Shakers lo sta valutando e il risultato richiede qualche minuto.',
834
+ uploadFailed: 'Non è stato possibile caricarlo. La tua analisi resta solo sul tuo computer; puoi riprovare più tardi.',
835
+ resultTitle: "Il tuo risultato in «Il mio lavoro con l'IA»:",
836
+ setupLevel: (level) => `Configurazione: ${level}.`,
837
+ usageLevel: (level) => `Uso: ${level}.`,
838
+ cell: (name) => `La tua casella nella matrice: ${name}.`,
839
+ levels: { ASSISTED: 'Assistito', EXTENDED: 'Esteso', ORCHESTRATED: 'Orchestrato', REACTIVE: 'Reattivo', DELIBERATE: 'Deliberato', RIGOROUS: 'Rigoroso' },
840
+ resultPending: "Il tuo risultato non è ancora in «Il mio lavoro con l'IA». Se hai appena caricato la tua analisi, comparirà tra qualche minuto.",
841
+ },
842
+ pt: {
843
+ cliQuestion: 'Carrego-o no seu perfil? [s/N]:',
844
+ cliIncludeExcerpts: 'Incluir os excertos de texto? [S/n]:',
845
+ cliExcludeAgents: 'Se quiser deixar de fora algum agente, escreva os nomes separados por vírgulas (Enter para nenhum):',
846
+ cliUnknownAgents: (unknown, known) => `Não reconheço: ${unknown}. Os agentes são: ${known}.`,
847
+ cliRepoHint: 'Para deixar de fora um repositório, volte a executar o comando escolhendo só os que quiser com --repos.',
848
+ title: 'Ainda não foi carregado nada no seu perfil da Shakers. Isto é o que seria carregado:',
849
+ sentTitle: 'Isto é o que foi carregado:',
850
+ tools: (list) => `Ferramentas de IA detetadas: ${list}.`,
851
+ technologies: (list) => `Tecnologias dos seus projetos: ${list}.`,
852
+ activity: (sessions, days) => `A sua atividade com IA: ${sessions === 1 ? '1 sessão' : `${sessions} sessões`}${days === null ? '' : `, cerca de ${days} dias ativos por semana`}.`,
853
+ repos: (list, commits) => `Repositórios analisados: ${list}. Deles só se enviam números (${commits === 1 ? '1 commit' : `${commits} commits`}, linhas alteradas, tipos de ficheiro), nunca o nome nem o código.`,
854
+ agents: (list, definitions, summaries) => `Agentes de IA detetados: ${list}. De cada um envia-se ${listOf('pt', ['o nome', 'as ferramentas', 'o modelo', 'de que agente depende', ...(definitions > 0 ? ['o texto da sua definição (sem segredos)'] : []), ...(summaries > 0 ? ['o resumo do que faz'] : [])])}.`,
855
+ excerpts: (conversation, commands) => `Excertos de texto: ${conversation} fragmentos literais das suas mensagens à IA e ${commands} comandos, com 200 caracteres no máximo, depois de retirar o que pareça um segredo, um caminho ou um email.`,
856
+ noExcerpts: 'Excertos de texto: nenhum.',
857
+ notSent: 'Não se envia: o seu código, as suas conversas completas nem os nomes dos seus repositórios.',
858
+ explanationTitle: 'O que a Shakers faz com isto:',
859
+ explanation: [
860
+ 'Situa-o em dois eixos. Configuração: o que tem montado (ferramentas, servidores MCP, agentes), de menos para mais: Assistido, Estendido ou Orquestrado. Uso: como orienta a IA (se lhe dá instruções concretas, se revê o que propõe), de menos para mais: Reativo, Deliberado ou Rigoroso.',
861
+ "A configuração calcula-se com regras fixas a partir do que foi detetado. O nível de uso e os textos sobre como trabalha são escritos por um modelo de IA de um fornecedor externo (OpenAI, Google ou Anthropic) por conta da Shakers. Esse modelo lê os excertos de texto, os comandos e as definições dos seus agentes para calcular a sua avaliação e depois descarta-os: a Shakers guarda o resultado (níveis, pontuações, contagens e os nomes das suas ferramentas e agentes), não esses textos.",
862
+ 'No seu perfil aparece a secção «Meu trabalho com IA»: os seus dois níveis, a sua casa na matriz (por exemplo Practitioner ou Architect), as suas ferramentas e agentes, e dois textos que pode editar na web.',
863
+ ],
864
+ afterwards: 'Se não concordar com o resultado, carregue em «Solicitar revisão humana» nessa secção da web ou escreva para talent@shakersworks.com. Hoje não pode ocultar a secção sozinho; para pedir que seja apagada, escreva para esse mesmo endereço.',
865
+ excludeHint: 'Se quiser deixar de fora algum repositório, algum agente ou os excertos de texto, diga-me ao responder.',
866
+ question: 'Carrego-o no seu perfil?',
867
+ yes: 'Carregar no meu perfil',
868
+ no: 'Não carregar',
869
+ formExcerpts: 'Incluir os excertos de texto',
870
+ formRepo: (name) => `Incluir o repositório ${name}`,
871
+ formAgent: (name) => `Incluir o agente ${name}`,
872
+ declined: 'Não foi carregado nada. A análise fica só no seu computador.',
873
+ nothingLeft: 'Deixou de fora todos os repositórios, por isso não foi carregado nada.',
874
+ throttled: 'Carregou uma análise há menos de uma hora, por isso não foi carregado nada. Pode tentar mais tarde.',
875
+ uploaded: 'Carregado no seu perfil. A Shakers está a avaliá-lo e o resultado demora alguns minutos.',
876
+ uploadFailed: 'Não foi possível carregá-lo. A sua análise fica só no seu computador; pode tentar mais tarde.',
877
+ resultTitle: 'O seu resultado em «Meu trabalho com IA»:',
878
+ setupLevel: (level) => `Configuração: ${level}.`,
879
+ usageLevel: (level) => `Uso: ${level}.`,
880
+ cell: (name) => `A sua casa na matriz: ${name}.`,
881
+ levels: { ASSISTED: 'Assistido', EXTENDED: 'Estendido', ORCHESTRATED: 'Orquestrado', REACTIVE: 'Reativo', DELIBERATE: 'Deliberado', RIGOROUS: 'Rigoroso' },
882
+ resultPending: 'O seu resultado ainda não está em «Meu trabalho com IA». Se acabou de carregar a sua análise, vai aparecer dentro de alguns minutos.',
883
+ },
884
+ };
885
+
408
886
  function toSignupLanguage(code) {
409
887
  const base = String(code || '').toLowerCase().split(/[-_]/)[0];
410
888
  return SIGNUP_LANGUAGES.includes(base) ? base : null;
@@ -414,11 +892,19 @@ function signupCopy(lang) {
414
892
  return COPY[toSignupLanguage(lang) || 'en'];
415
893
  }
416
894
 
895
+ function trustCopy(lang) {
896
+ return TRUST[toSignupLanguage(lang) || 'en'];
897
+ }
898
+
417
899
  function legalCopy(lang) {
418
900
  return LEGAL[toSignupLanguage(lang) || 'en'];
419
901
  }
420
902
 
421
- // The profile draft the talent confirms before the account exists: one line per group, empty parts left out.
903
+ function reviewCopy(lang) {
904
+ return REVIEW[toSignupLanguage(lang) || 'en'];
905
+ }
906
+
907
+ // The profile draft the talent confirms before the account exists, with every field the account is created with: one line per group, empty parts left out.
422
908
  function renderDraft(lang, d = {}) {
423
909
  const c = signupCopy(lang);
424
910
  const has = (v) => v !== null && v !== undefined && String(v).trim() !== '';
@@ -431,6 +917,7 @@ function renderDraft(lang, d = {}) {
431
917
  joinParts([has(d.yearsOfExperience) ? c.years(d.yearsOfExperience) : null, d.stack]),
432
918
  joinParts([d.languages, d.workMode, has(d.monthlyHours) ? c.hours(d.monthlyHours) : null]),
433
919
  has(d.hourlyRate) || has(d.annualRate) ? c.rates(has(d.hourlyRate) ? d.hourlyRate : null, has(d.annualRate) ? d.annualRate : null, d.ratesFromTalent !== true) : '',
920
+ has(d.email) ? `${c.emailLabel}: ${d.email}` : '',
434
921
  ].filter(Boolean);
435
922
  return [c.draftTitle, ...lines].join('\n');
436
923
  }
@@ -447,4 +934,4 @@ function renderRole(lang, { name, source, evidence, openProjects, recommended })
447
934
  ].filter(Boolean).join('\n');
448
935
  }
449
936
 
450
- module.exports = { SIGNUP_LANGUAGES, LEGAL, signupCopy, legalCopy, toSignupLanguage, renderDraft, renderRole, amount };
937
+ module.exports = { SIGNUP_LANGUAGES, LEGAL, REVIEW, signupCopy, legalCopy, trustCopy, termsText, reviewCopy, toSignupLanguage, renderDraft, renderRole, amount };