shakers-cli-beta 0.11.0 → 0.13.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.
@@ -4,31 +4,31 @@
4
4
 
5
5
  const SIGNUP_LANGUAGES = ['es', 'en', 'it', 'pt'];
6
6
 
7
- // Legal texts. PENDING legal/GDPR validation: not final copy. The CLI onboarding reads the es/en ones too.
7
+ // Legal texts. The CLI onboarding reads the es/en ones too.
8
8
  const LEGAL = {
9
9
  es: {
10
- signupNotice: 'Antes de crear tu cuenta: guardamos los datos que me des (tu LinkedIn, tu CV y 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.',
10
+ 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
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.',
12
12
  usageGoalDuration: 'Objetivo: medir cómo trabajas con IA para tu perfil. Duración: ~1-2 minutos.',
13
13
  interviewInfoAccessed: 'Se accede a: tus respuestas en esta entrevista y tu perfil importado. La conduce el servidor de Shakers.',
14
14
  interviewGoalDuration: 'Objetivo: conocer tu rol, tu dedicación y cómo usas la IA. Duración: ~5 minutos.',
15
15
  },
16
16
  en: {
17
- signupNotice: 'Before we create your account: we store the data you give me (your LinkedIn, your CV and 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.',
17
+ 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
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.',
19
19
  usageGoalDuration: 'Goal: measure how you work with AI for your profile. Duration: ~1-2 minutes.',
20
20
  interviewInfoAccessed: 'Accessed: your answers in this interview and your imported profile. It is conducted by the Shakers server.',
21
21
  interviewGoalDuration: 'Goal: learn your role, your dedication and how you use AI. Duration: ~5 minutes.',
22
22
  },
23
23
  it: {
24
- signupNotice: 'Prima di creare il tuo account: conserviamo i dati che mi dai (il tuo LinkedIn, il tuo CV e 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.',
24
+ 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
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.',
26
26
  usageGoalDuration: "Obiettivo: misurare come lavori con l'IA per il tuo profilo. Durata: ~1-2 minuti.",
27
27
  interviewInfoAccessed: 'Accesso a: le tue risposte in questo colloquio e il tuo profilo importato. Lo conduce il server di Shakers.',
28
28
  interviewGoalDuration: "Obiettivo: conoscere il tuo ruolo, la tua disponibilità e come usi l'IA. Durata: ~5 minuti.",
29
29
  },
30
30
  pt: {
31
- signupNotice: 'Antes de criar a sua conta: guardamos os dados que me der (o seu LinkedIn, o seu CV e 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.',
31
+ 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
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.',
33
33
  usageGoalDuration: 'Objetivo: medir como trabalha com IA para o seu perfil. Duração: ~1-2 minutos.',
34
34
  interviewInfoAccessed: 'Acede-se a: as suas respostas nesta entrevista e o seu perfil importado. É conduzida pelo servidor da Shakers.',
@@ -38,6 +38,16 @@ const LEGAL = {
38
38
 
39
39
  const LOCALES = { es: 'es-ES', en: 'en-GB', it: 'it-IT', pt: 'pt-PT' };
40
40
 
41
+ // "a, b and c" in the talent's language.
42
+ function listOf(lang, items) {
43
+ return new Intl.ListFormat(LOCALES[lang], { type: 'conjunction' }).format(items);
44
+ }
45
+
46
+ // The names of only the sources the talent actually gave (cv, linkedin), from a per-language table.
47
+ function named(sources, names) {
48
+ return (sources || []).map((s) => names[s]).filter(Boolean);
49
+ }
50
+
41
51
  function joinParts(parts) {
42
52
  return parts.filter((p) => p !== null && p !== undefined && String(p).trim() !== '').join(' · ');
43
53
  }
@@ -49,10 +59,10 @@ function amount(lang, value) {
49
59
  const COPY = {
50
60
  es: {
51
61
  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.`,
52
- welcomeQuestion: '¿Empezamos? Si me das permiso, busco tu CV en este equipo y te preparo un borrador para que solo tengas que confirmarlo.',
53
- cvSearch: 'Sí, búscalo',
54
- cvAttach: 'Te lo adjunto yo',
55
- cvAttachAsk: 'Perfecto, adjúntamelo aquí.',
62
+ 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.',
63
+ cvSearch: 'Búscalo en mi equipo',
64
+ cvAttach: 'Te lo paso',
65
+ cvAttachAsk: 'Perfecto, adjúntalo aquí o dime dónde está en tu equipo.',
56
66
  cvNotFound: 'No he encontrado tu CV en este equipo. ¿Me lo adjuntas aquí?',
57
67
  linkedinAsk: '¿Me pasas tu perfil de LinkedIn?',
58
68
  linkedinOpening: 'Te abro tu perfil de LinkedIn en el navegador para que copies la dirección.',
@@ -69,20 +79,24 @@ const COPY = {
69
79
  draftQuestion: '¿Está bien o cambio algo?',
70
80
  draftOk: 'Todo correcto',
71
81
  draftChange: 'Quiero cambiar algo',
72
- windowNotice: 'Cuando confirmes, te abro una ventana en el navegador para crear tu cuenta y guardar todo esto. Allí eliges 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. Cuando termines, vuelve aquí y seguimos.',
82
+ 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.',
83
+ 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.',
73
84
  windowLink: (link) => `Si no se te ha abierto la ventana, entra desde aquí: ${link}`,
74
85
  existingTitle: 'He visto que ya tienes cuenta. ¿Actualizo tu perfil con esto?',
75
86
  existingRate: (from, to) => `Tarifa por hora: ${from} → ${to}`,
76
87
  existingAnnual: (from, to) => `Tarifa anual: ${from} → ${to}`,
77
88
  existingHours: (from, to) => `Disponibilidad: ${from} → ${to}`,
78
89
  existingEmpty: 'sin indicar',
79
- existingLists: 'Añado lo nuevo de tu CV y tu LinkedIn (experiencias, proyectos, formación) y relleno lo que tengas vacío, sin borrar nada.',
90
+ existingUpdateQuestion: '¿Actualizo tu perfil con esto?',
91
+ existingStays: (label, kept, requested) => (kept ? `${label}: se queda en ${kept}${kept === requested ? '' : `, no ${requested}`}` : `${label}: sin cambios`),
92
+ existingStaysNote: 'Lo que ya tenías relleno no lo cambio desde aquí: si quieres otros valores, cámbialos en la web.',
93
+ existingLists: (sources) => `Añado lo nuevo de ${listOf('es', named(sources, COPY.es.importSources))} (experiencias, proyectos, formación) y relleno lo que tengas vacío, sin borrar nada.`,
80
94
  existingYes: 'Sí, actualízalo',
81
95
  existingNo: 'No, déjalo como está',
82
96
  existingKept: 'Perfecto, dejo tu perfil como está.',
83
97
  keptLabels: { workSituation: 'Situación laboral', availability: 'Disponibilidad', languages: 'Idiomas', phone: 'Teléfono' },
84
98
  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.`,
85
- 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. Te ayuda a destacar en proyectos de IA.',
99
+ 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.',
86
100
  aiUsageYes: 'Sí, analízalo',
87
101
  aiUsageSkip: 'Saltar este paso',
88
102
  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'.",
@@ -119,10 +133,10 @@ const COPY = {
119
133
  },
120
134
  en: {
121
135
  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.`,
122
- welcomeQuestion: "Shall we start? With your permission, I'll look for your CV on this computer and prepare a draft so you only have to confirm it.",
123
- cvSearch: 'Yes, look for it',
124
- cvAttach: "I'll attach it",
125
- cvAttachAsk: 'Great, attach it here.',
136
+ 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.",
137
+ cvSearch: 'Look for it on my computer',
138
+ cvAttach: "I'll send it",
139
+ cvAttachAsk: 'Great, attach it here or tell me where it is on your computer.',
126
140
  cvNotFound: "I couldn't find your CV on this computer. Could you attach it here?",
127
141
  linkedinAsk: 'Could you send me your LinkedIn profile?',
128
142
  linkedinOpening: 'I am opening your LinkedIn profile in your browser so you can copy its address.',
@@ -139,20 +153,24 @@ const COPY = {
139
153
  draftQuestion: 'Does it look right, or shall I change something?',
140
154
  draftOk: 'All correct',
141
155
  draftChange: 'I want to change something',
142
- windowNotice: "When you confirm, I'll open a window in your browser to create your account and save all this. There you 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. When you're done, come back here and we'll carry on.",
156
+ 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.",
157
+ 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.",
143
158
  windowLink: (link) => `If the window did not open, use this link: ${link}`,
144
159
  existingTitle: 'I see you already have an account. Shall I update your profile with this?',
145
160
  existingRate: (from, to) => `Hourly rate: ${from} → ${to}`,
146
161
  existingAnnual: (from, to) => `Annual rate: ${from} → ${to}`,
147
162
  existingHours: (from, to) => `Availability: ${from} → ${to}`,
148
163
  existingEmpty: 'not set',
149
- existingLists: 'I add what is new from your CV and LinkedIn (experience, projects, education) and fill in what is empty, without deleting anything.',
164
+ existingUpdateQuestion: 'Shall I update your profile with this?',
165
+ existingStays: (label, kept, requested) => (kept ? `${label}: stays at ${kept}${kept === requested ? '' : `, not ${requested}`}` : `${label}: no change`),
166
+ existingStaysNote: 'I do not change from here what you had already filled in: if you want other values, change them on the web.',
167
+ existingLists: (sources) => `I add what is new from ${listOf('en', named(sources, COPY.en.importSources))} (experience, projects, education) and fill in what is empty, without deleting anything.`,
150
168
  existingYes: 'Yes, update it',
151
169
  existingNo: 'No, leave it as it is',
152
170
  existingKept: 'Sure, I am leaving your profile as it is.',
153
171
  keptLabels: { workSituation: 'Work situation', availability: 'Availability', languages: 'Languages', phone: 'Phone' },
154
172
  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.`,
155
- 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. It helps you stand out for AI projects.',
173
+ 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.',
156
174
  aiUsageYes: 'Yes, analyse it',
157
175
  aiUsageSkip: 'Skip this step',
158
176
  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'.",
@@ -189,10 +207,10 @@ const COPY = {
189
207
  },
190
208
  it: {
191
209
  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.`,
192
- welcomeQuestion: 'Iniziamo? Se mi dai il permesso, cerco il tuo CV su questo computer e ti preparo una bozza da confermare e basta.',
193
- cvSearch: 'Sì, cercalo',
194
- cvAttach: 'Te lo allego io',
195
- cvAttachAsk: 'Perfetto, allegalo qui.',
210
+ 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.',
211
+ cvSearch: 'Cercalo nel mio computer',
212
+ cvAttach: 'Te lo mando',
213
+ cvAttachAsk: 'Perfetto, allegalo qui o dimmi dove si trova nel tuo computer.',
196
214
  cvNotFound: 'Non ho trovato il tuo CV su questo computer. Me lo alleghi qui?',
197
215
  linkedinAsk: 'Mi mandi il tuo profilo LinkedIn?',
198
216
  linkedinOpening: "Ti apro il tuo profilo LinkedIn nel browser così puoi copiarne l'indirizzo.",
@@ -209,20 +227,24 @@ const COPY = {
209
227
  draftQuestion: 'Va bene o cambio qualcosa?',
210
228
  draftOk: 'Tutto corretto',
211
229
  draftChange: 'Voglio cambiare qualcosa',
212
- windowNotice: "Quando confermi, ti apro una finestra nel browser per creare il tuo account e salvare tutto questo. Lì 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. Quando hai finito, torna qui e continuiamo.",
230
+ 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.",
231
+ 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.",
213
232
  windowLink: (link) => `Se la finestra non si è aperta, entra da qui: ${link}`,
214
233
  existingTitle: 'Ho visto che hai già un account. Aggiorno il tuo profilo con questi dati?',
215
234
  existingRate: (from, to) => `Tariffa oraria: ${from} → ${to}`,
216
235
  existingAnnual: (from, to) => `Tariffa annuale: ${from} → ${to}`,
217
236
  existingHours: (from, to) => `Disponibilità: ${from} → ${to}`,
218
237
  existingEmpty: 'non indicata',
219
- existingLists: 'Aggiungo le novità del tuo CV e di LinkedIn (esperienze, progetti, formazione) e compilo quello che hai vuoto, senza cancellare nulla.',
238
+ existingUpdateQuestion: 'Aggiorno il tuo profilo con questi dati?',
239
+ existingStays: (label, kept, requested) => (kept ? `${label}: resta ${kept}${kept === requested ? '' : `, non ${requested}`}` : `${label}: nessuna modifica`),
240
+ existingStaysNote: 'Quello che avevi già compilato non lo cambio da qui: se vuoi altri valori, cambiali sul sito.',
241
+ existingLists: (sources) => `Aggiungo le novità ${listOf('it', named(sources, { cv: 'del tuo CV', linkedin: 'del tuo LinkedIn' }))} (esperienze, progetti, formazione) e compilo quello che hai vuoto, senza cancellare nulla.`,
220
242
  existingYes: 'Sì, aggiornalo',
221
243
  existingNo: 'No, lascialo così',
222
244
  existingKept: 'Perfetto, lascio il tuo profilo così com’è.',
223
245
  keptLabels: { workSituation: 'Situazione lavorativa', availability: 'Disponibilità', languages: 'Lingue', phone: 'Telefono' },
224
246
  existingUnchanged: (items) => `Questi dati del tuo profilo non li ho cambiati, restano com'erano: ${items.join(' · ')}. Se vuoi altri valori, cambiali sul sito.`,
225
- 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. Ti aiuta a distinguerti nei progetti di IA.",
247
+ 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.",
226
248
  aiUsageYes: 'Sì, analizzalo',
227
249
  aiUsageSkip: 'Salta questo passaggio',
228
250
  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'.",
@@ -259,10 +281,10 @@ const COPY = {
259
281
  },
260
282
  pt: {
261
283
  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.`,
262
- welcomeQuestion: 'Começamos? Se me der autorização, procuro o seu CV neste computador e preparo um rascunho para que só tenha de o confirmar.',
263
- cvSearch: 'Sim, procure-o',
264
- cvAttach: 'Eu anexo-o',
265
- cvAttachAsk: 'Perfeito, anexe-o aqui.',
284
+ 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.',
285
+ cvSearch: 'Procure-o no meu computador',
286
+ cvAttach: 'Envio-o eu',
287
+ cvAttachAsk: 'Perfeito, anexe-o aqui ou diga-me onde está no seu computador.',
266
288
  cvNotFound: 'Não encontrei o seu CV neste computador. Pode anexá-lo aqui?',
267
289
  linkedinAsk: 'Pode enviar-me o seu perfil do LinkedIn?',
268
290
  linkedinOpening: 'Vou abrir o seu perfil do LinkedIn no navegador para que copie o endereço.',
@@ -279,20 +301,24 @@ const COPY = {
279
301
  draftQuestion: 'Está bem ou mudo alguma coisa?',
280
302
  draftOk: 'Está tudo certo',
281
303
  draftChange: 'Quero mudar algo',
282
- windowNotice: 'Quando confirmar, abro uma janela no seu navegador para criar a sua conta e guardar tudo isto. Lá escolhe 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. Quando terminar, volte aqui e continuamos.',
304
+ 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.',
305
+ 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.',
283
306
  windowLink: (link) => `Se a janela não abriu, entre por aqui: ${link}`,
284
307
  existingTitle: 'Vi que já tem conta. Atualizo o seu perfil com isto?',
285
308
  existingRate: (from, to) => `Tarifa por hora: ${from} → ${to}`,
286
309
  existingAnnual: (from, to) => `Tarifa anual: ${from} → ${to}`,
287
310
  existingHours: (from, to) => `Disponibilidade: ${from} → ${to}`,
288
311
  existingEmpty: 'sem indicar',
289
- existingLists: 'Acrescento o que há de novo no seu CV e no LinkedIn (experiências, projetos, formação) e preencho o que tiver vazio, sem apagar nada.',
312
+ existingUpdateQuestion: 'Atualizo o seu perfil com isto?',
313
+ existingStays: (label, kept, requested) => (kept ? `${label}: fica em ${kept}${kept === requested ? '' : `, não ${requested}`}` : `${label}: sem alterações`),
314
+ existingStaysNote: 'O que já tinha preenchido não o altero daqui: se quiser outros valores, altere-os na web.',
315
+ existingLists: (sources) => `Acrescento o que há de novo ${listOf('pt', named(sources, { cv: 'no seu CV', linkedin: 'no seu LinkedIn' }))} (experiências, projetos, formação) e preencho o que tiver vazio, sem apagar nada.`,
290
316
  existingYes: 'Sim, atualize',
291
317
  existingNo: 'Não, deixe como está',
292
318
  existingKept: 'Perfeito, deixo o seu perfil como está.',
293
319
  keptLabels: { workSituation: 'Situação profissional', availability: 'Disponibilidade', languages: 'Idiomas', phone: 'Telefone' },
294
320
  existingUnchanged: (items) => `Estes dados do seu perfil não os alterei, ficam como estavam: ${items.join(' · ')}. Se quiser outros valores, altere-os na web.`,
295
- 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. Ajuda-o a destacar-se em projetos de IA.',
321
+ 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.',
296
322
  aiUsageYes: 'Sim, analise',
297
323
  aiUsageSkip: 'Saltar este passo',
298
324
  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'.",
@@ -114,6 +114,14 @@ function printActivity(report, t, p) {
114
114
  ` ${c.success}●${c.reset} ${a.commits(g.authoredCommitCount, g.commitCount)} ${c.muted}·${c.reset} ${a.lines(g.linesAdded, g.linesDeleted)} ${c.muted}·${c.reset} ${a.velocity(g.velocity)}`,
115
115
  );
116
116
  }
117
+ // Transparency warning: the talent's own commits are going unattributed because
118
+ // the repo's git identity is not their Shakers email (display-only, never scored).
119
+ const mm = report && report.authorshipMismatch;
120
+ if (mm && Array.isArray(mm.emails) && mm.emails.length && mm.accountEmail) {
121
+ for (const line of wrap(a.authorshipMismatch(mm.emails, mm.accountEmail))) {
122
+ p(` ${c.warning}${line}${c.reset}`);
123
+ }
124
+ }
117
125
  if (w) {
118
126
  p(
119
127
  ` ${c.success}●${c.reset} ${a.workStreams(w.streamCount, w.multiDayStreams, w.maxStreamSpanDays)}`,
@@ -30,6 +30,7 @@ const {
30
30
  collectScopedWorkStreams,
31
31
  collectScopedDetectionEvidence,
32
32
  } = require('./scope-signals');
33
+ const { detectAuthorshipMismatch } = require('./authorship-mismatch');
33
34
  const { MODEL_CALL, REASON, skipped, attachModelCall } = require('./model-call-record');
34
35
  const {
35
36
  getSynthesisEndpoint,
@@ -164,6 +165,8 @@ async function resolveAiConsent({ opts, catalog, gate, injectedAsk }) {
164
165
  function runAggregation({ report, scope, consentEmail, agentRoots, root, catalog }) {
165
166
  withStaticStatus(catalog.cli.aggregatingLabel, () => {
166
167
  report.gitActivity = collectScopedGitActivity(scope.toplevels, consentEmail);
168
+ // Display-only transparency warning (never feeds scoring, see authorship-mismatch.js).
169
+ report.authorshipMismatch = detectAuthorshipMismatch(scope.toplevels, consentEmail, report.gitActivity);
167
170
  const sessionSignals = collectSessionSignals(process.env, scope.selectedCwds);
168
171
  report.sessions = sessionSignals.sessions;
169
172
  report.workStreams = collectScopedWorkStreams(scope.toplevels);