paris-immersion 0.1.26 → 0.1.28

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.
@@ -36,11 +36,14 @@ export async function loginCommand(opts) {
36
36
  if (opts.token) {
37
37
  process.stdout.write(c.dim(`Validando token via ${apiUrl}...\n`));
38
38
  try {
39
- const me = await apiFetch('/auth/me', {
39
+ // /auth/me devolve envelope { user: {...} } — sem o unwrap o installer
40
+ // dizia "Autenticado como undefined" (achado da simulação de 2026-08-13).
41
+ const meRes = await apiFetch('/auth/me', {
40
42
  method: 'GET',
41
43
  apiUrl,
42
44
  token: opts.token,
43
45
  });
46
+ const me = meRes.user ?? meRes;
44
47
  await writeCredentials({
45
48
  token: opts.token,
46
49
  user_id: me.id,
@@ -48,7 +51,7 @@ export async function loginCommand(opts) {
48
51
  api_url: apiUrl,
49
52
  obtained_at: new Date().toISOString(),
50
53
  });
51
- console.log(c.green(`✓ Autenticado como ${me.email}.`));
54
+ console.log(c.green(`✓ Autenticado como ${me.name ?? me.email}.`));
52
55
  console.log(c.dim(' Token salvo em ~/.paris/credentials.'));
53
56
  await refreshDaemonAfterLogin();
54
57
  return;
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { mentorCommand } from './commands/mentor.js';
15
15
  const program = new Command()
16
16
  .name('paris')
17
17
  .description('Paris Immersion — CLI do aluno (login, workspace, tarefas)')
18
- .version('0.1.26');
18
+ .version('0.1.28');
19
19
  program
20
20
  .command('login')
21
21
  .description('Autenticar com sua conta Paris (device-code OR --token direto)')
@@ -21,6 +21,7 @@ export function renderClaudeMd(opts) {
21
21
  3. **Transparência total.** Tudo aqui é gravado em audit_logs e o instrutor pode reabrir qualquer tarefa.
22
22
  4. **Nunca invente progresso.** Só marque o que de fato foi feito nessa sessão.
23
23
  5. **Inicie a sessão falando primeiro.** O hook SessionStart te entrega o cronograma + a tarefa atual antes do aluno digitar — abra o dia com um resumo claro (fase, tarefa, primeiro passo sugerido).
24
+ 6. **O dossiê do projeto é sagrado.** \`produto/visao.md\` precisa SEMPRE refletir o que é o projeto, a dor, a ideia, como resolve e pra quem (o /visao do Prototipador escreve nesse formato). Toda decisão nova de escopo vai pro arquivo na hora. Ele sincroniza sozinho com a equipe no fim do turno e é pré-requisito pra concluir missões de entregável — sem dossiê, o mark_task_complete recusa.
24
25
 
25
26
  ## MCP tools que você tem
26
27
 
@@ -211,6 +211,43 @@ async function refreshMarkerStatus(markerDir, marker, status) {
211
211
  } catch { /* best-effort */ }
212
212
  }
213
213
 
214
+ // ---------------------------------------------------------------------------
215
+ // Dossiê do projeto (produto/visao.md + roadmap.md do Prototipador) → Caso do
216
+ // aluno no servidor. DETERMINÍSTICO: roda em todo Stop/SessionStart e envia
217
+ // quando o arquivo mudou — o mentor sempre vê o que é o projeto, a dor e o
218
+ // plano, sem depender do modelo lembrar de anotar.
219
+ // ---------------------------------------------------------------------------
220
+
221
+ async function syncCaseDossier(apiUrl, token, markerDir) {
222
+ const statePath = path.join(markerDir, '.paris', 'case-sync.json')
223
+ let state = {}
224
+ try { state = JSON.parse(await fs.readFile(statePath, 'utf-8')) } catch {}
225
+
226
+ const files = {
227
+ visao: path.join(markerDir, 'produto', 'visao.md'),
228
+ roadmap: path.join(markerDir, 'produto', 'roadmap.md'),
229
+ }
230
+ const payload = {}
231
+ const newState = { ...state }
232
+ for (const [key, fp] of Object.entries(files)) {
233
+ try {
234
+ const st = await fs.stat(fp)
235
+ const mtime = st.mtimeMs
236
+ if (state[key] !== mtime) {
237
+ const content = await fs.readFile(fp, 'utf-8')
238
+ payload[key] = content.slice(0, 16384)
239
+ newState[key] = mtime
240
+ }
241
+ } catch { /* arquivo ainda não existe — normal antes do /visao */ }
242
+ }
243
+ if (Object.keys(payload).length === 0) return
244
+
245
+ const ok = await apiPost(apiUrl, '/immersion-app/v1/case/sync', token, payload)
246
+ if (ok) {
247
+ try { await fs.writeFile(statePath, JSON.stringify(newState)) } catch {}
248
+ }
249
+ }
250
+
214
251
  // Sincroniza o progresso REAL do curso da ToStudy pro nosso lado. O aluno faz o
215
252
  // curso pela tostudy CLI no terminal embutido, e essa conclusão NÃO dispara o
216
253
  // webhook do cursos — então é daqui que a gestão aprende o progresso. A ToStudy
@@ -266,6 +303,7 @@ async function main() {
266
303
  if (PHASE === 'stop') {
267
304
  await apiPost(apiUrl, '/immersion-app/v1/events', creds.token, { type: 'turn_ended' })
268
305
  await flushActivityQueue(apiUrl, creds.token, marker.dir)
306
+ await syncCaseDossier(apiUrl, creds.token, marker.dir)
269
307
  // Logo após cada interação é o melhor momento pra capturar uma lição que o
270
308
  // aluno acabou de concluir no curso da ToStudy.
271
309
  await syncTostudyProgress(apiUrl, creds.token, marker.dir)
@@ -341,6 +379,7 @@ async function main() {
341
379
  await apiPost(apiUrl, '/immersion-app/v1/events', creds.token, { type: 'session_started' })
342
380
  // Restos de fila de sessões anteriores (crash, rede) sobem agora.
343
381
  await flushActivityQueue(apiUrl, creds.token, marker.dir)
382
+ await syncCaseDossier(apiUrl, creds.token, marker.dir)
344
383
  }
345
384
 
346
385
  // user-prompt-submit: dá ao mentor o "o que ele pediu" em tempo real.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paris-immersion",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Paris Immersion CLI — login, install workspace, status, task control. Para alunos das imersões da Paris Group.",
5
5
  "homepage": "https://parisgroup.ai",
6
6
  "repository": {