paris-immersion 0.1.21 → 0.1.23

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.
@@ -10,9 +10,12 @@ export type TostudyLoginOptions = {
10
10
  * ela é async, então no install o code volta 409 e o login não acontece; aqui,
11
11
  * no momento de abrir o curso, ela já costuma ter terminado).
12
12
  *
13
- * REGRA do produto: se o aluno JÁ tem sessão ToStudy (a conta dele), NÃO
14
- * mexemos — o curso da imersão entra nela via claim-by-email. Só logamos via
15
- * code quando NÃO há sessão (ou com --force).
13
+ * REGRA do produto: respeitamos a sessão ToStudy do aluno (a conta dele, com o
14
+ * curso da imersão via claim-by-email) — MAS só se ela realmente TIVER o curso.
15
+ * Se a sessão está numa conta SEM a matrícula (lista de cursos vazia — ex.: conta
16
+ * pessoal/Google que não recebeu o claim, ou split de conta), reconectamos via
17
+ * code na conta matriculada (resolvida por externalUserId). --force reconecta
18
+ * sempre.
16
19
  *
17
20
  * NUNCA loga o `code` (é single-use, efêmero) — só o resultado.
18
21
  */
@@ -5,14 +5,26 @@ import { apiFetch, defaultApiUrl, ApiError } from '../lib/api.js';
5
5
  import { c } from '../lib/colors.js';
6
6
  const execFileAsync = promisify(execFile);
7
7
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8
- /** `tostudy courses` exit 0 = sessão ToStudy válida nesta máquina. */
9
- async function tostudyAuthed() {
8
+ /**
9
+ * Quantos cursos a sessão ToStudy desta máquina enxerga.
10
+ * - null → sem sessão válida (comando falhou / não autenticado).
11
+ * - 0 → sessão válida MAS sem nenhum curso — conta ERRADA: logada numa conta
12
+ * que não tem a matrícula da imersão (ex.: conta pessoal/Google, ou um
13
+ * split de conta onde a matrícula ficou noutra conta).
14
+ * - N>0 → sessão válida com cursos (a conta certa, via claim-by-email).
15
+ *
16
+ * É a distinção que faltava: "tem sessão" NÃO é o mesmo que "tem o curso". Antes
17
+ * checávamos só o exit-code de `tostudy courses` → uma conta vazia passava como
18
+ * "já conectado" e o curso nunca abria.
19
+ */
20
+ async function tostudyCourseCount() {
10
21
  try {
11
- await execFileAsync('tostudy', ['courses'], { timeout: 12000 });
12
- return true;
22
+ const { stdout } = await execFileAsync('tostudy', ['courses', '--json'], { timeout: 12000 });
23
+ const parsed = JSON.parse(stdout);
24
+ return Array.isArray(parsed) ? parsed.length : 0;
13
25
  }
14
26
  catch {
15
- return false;
27
+ return null;
16
28
  }
17
29
  }
18
30
  /**
@@ -21,9 +33,12 @@ async function tostudyAuthed() {
21
33
  * ela é async, então no install o code volta 409 e o login não acontece; aqui,
22
34
  * no momento de abrir o curso, ela já costuma ter terminado).
23
35
  *
24
- * REGRA do produto: se o aluno JÁ tem sessão ToStudy (a conta dele), NÃO
25
- * mexemos — o curso da imersão entra nela via claim-by-email. Só logamos via
26
- * code quando NÃO há sessão (ou com --force).
36
+ * REGRA do produto: respeitamos a sessão ToStudy do aluno (a conta dele, com o
37
+ * curso da imersão via claim-by-email) — MAS só se ela realmente TIVER o curso.
38
+ * Se a sessão está numa conta SEM a matrícula (lista de cursos vazia — ex.: conta
39
+ * pessoal/Google que não recebeu o claim, ou split de conta), reconectamos via
40
+ * code na conta matriculada (resolvida por externalUserId). --force reconecta
41
+ * sempre.
27
42
  *
28
43
  * NUNCA loga o `code` (é single-use, efêmero) — só o resultado.
29
44
  */
@@ -36,9 +51,18 @@ export async function tostudyLoginCommand(opts = {}) {
36
51
  process.exitCode = 1;
37
52
  return;
38
53
  }
39
- if (!opts.force && (await tostudyAuthed())) {
40
- log(c.green('ToStudy já está conectado — o curso da imersão usa essa conta.'));
41
- return;
54
+ if (!opts.force) {
55
+ const count = await tostudyCourseCount();
56
+ if (count !== null && count > 0) {
57
+ log(c.green('ToStudy já está conectado — o curso da imersão usa essa conta.'));
58
+ return;
59
+ }
60
+ if (count === 0) {
61
+ // Sessão válida porém numa conta SEM o curso da imersão → segue pro login
62
+ // por code (que resolve pra conta matriculada via externalUserId). Sem
63
+ // isso, a conta errada era tratada como "já conectado" e travava o curso.
64
+ log(c.dim('sessão ToStudy sem o curso da imersão — reconectando na conta da imersão…'));
65
+ }
42
66
  }
43
67
  // Mintar o code single-use. A provisão é async: enquanto pendente, a rota
44
68
  // responde 409 (tostudy_provision_pending) e dispara a provisão em background
@@ -91,7 +115,8 @@ export async function tostudyLoginCommand(opts = {}) {
91
115
  await execFileAsync('tostudy', ['login', '--code', code, '--api-url', apiUrl], { timeout: 20000 });
92
116
  }
93
117
  catch { /* o verify abaixo decide */ }
94
- if (await tostudyAuthed()) {
118
+ const finalCount = await tostudyCourseCount();
119
+ if (finalCount !== null && finalCount > 0) {
95
120
  log(c.green('✓ ToStudy conectado à conta da imersão. Reabra o curso.'));
96
121
  }
97
122
  else {
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { tostudyLoginCommand } from './commands/tostudy-login.js';
14
14
  const program = new Command()
15
15
  .name('paris')
16
16
  .description('Paris Immersion — CLI do aluno (login, workspace, tarefas)')
17
- .version('0.1.21');
17
+ .version('0.1.23');
18
18
  program
19
19
  .command('login')
20
20
  .description('Autenticar com sua conta Paris (device-code OR --token direto)')
package/dist/lib/api.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { readCredentials } from './credentials.js';
2
- const DEFAULT_API_URL = process.env.PARIS_API_URL ?? 'http://localhost:3011';
2
+ // CLI de ALUNO: o default é PROD. O wizard/instalador exporta PARIS_API_URL no
3
+ // one-liner, mas um aluno que roda `paris login` na mão (sem essa env) caía em
4
+ // localhost:3011 → "fetch failed". Default = prod resolve isso. Dev local:
5
+ // exporte PARIS_API_URL=http://localhost:3011.
6
+ const DEFAULT_API_URL = process.env.PARIS_API_URL ?? 'https://paris-immersion-api-production.up.railway.app';
3
7
  export class ApiError extends Error {
4
8
  status;
5
9
  body;
@@ -19,8 +19,8 @@ Você é o **tutor do curso de onboarding** da imersão Paris Group, rodando no
19
19
  Faça isto **em silêncio** (sem narrar comandos), e só fale com o aluno no passo (c) se precisar:
20
20
 
21
21
  a. Rode \`tostudy courses --json\`. Se listar o curso → vá pro passo 1.
22
- b. Se **"não autenticado"**: rode \`paris tostudy-login\` e depois \`tostudy courses --json\` de novo. (Conecta automático a conta da imersão; normal na primeira vez.)
23
- c. Se **ainda "não autenticado"**: rode \`tostudy login\`. Isso abre uma aba no navegador do aluno pra ele entrar na conta ToStudy. Aí SIM fale com ele, gentil e curto: **"Abri uma aba pra você entrar no ToStudy — é rapidinho: faça o login e volte aqui que eu já começo."** Aguarde, e quando ele voltar rode \`tostudy courses --json\` de novo.
22
+ b. Se a lista vier **VAZIA** (\`[]\`) **ou** der **"não autenticado"**: rode \`paris tostudy-login\` e depois \`tostudy courses --json\` de novo. (Reconecta na conta MATRICULADA da imersão — inclusive quando já há uma sessão logada em OUTRA conta, sem a matrícula; é comum e se resolve sozinho.) Se agora listar → vá pro passo 1.
23
+ c. Se **ainda** vier vazia ou "não autenticado": rode \`tostudy login\`. Isso abre uma aba no navegador do aluno pra ele entrar na conta ToStudy. Aí SIM fale com ele, gentil e curto: **"Abri uma aba pra você entrar no ToStudy — é rapidinho: faça o login e volte aqui que eu já começo."** Aguarde, e quando ele voltar rode \`tostudy courses --json\` de novo.
24
24
  d. Só se DEPOIS de (c) ainda não houver curso: diga **"Seu curso está sendo preparado, tenta reabrir em 1 minuto — qualquer coisa, fala com a equipe da imersão."** e use o fallback do passo 3 (MCP). Nunca trave nem fique em loop.
25
25
 
26
26
  ## 1. Prepare o curso (em silêncio, antes de falar)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paris-immersion",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
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": {