paris-immersion 0.1.20 → 0.1.22
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.
package/dist/commands/login.js
CHANGED
|
@@ -2,7 +2,32 @@ import { hostname, platform } from 'node:os';
|
|
|
2
2
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
3
|
import { apiFetch, ApiError, defaultApiUrl } from '../lib/api.js';
|
|
4
4
|
import { writeCredentials } from '../lib/credentials.js';
|
|
5
|
+
import { daemonStatus, restartDaemon } from '../lib/daemon.js';
|
|
5
6
|
import { c } from '../lib/colors.js';
|
|
7
|
+
/**
|
|
8
|
+
* Após um login bem-sucedido, religa o daemon do `paris serve` SE ele já estiver
|
|
9
|
+
* instalado. Sem isto, o daemon em execução segue com o loginToken ANTIGO em
|
|
10
|
+
* memória (capturado no boot — `relay-agent.ts` nunca relê o disco): `paris login`
|
|
11
|
+
* gravava as credenciais novas mas o agente vivo não as via, então um re-login NÃO
|
|
12
|
+
* alcançava o terminal embutido (ficava "Esperando o agente" pra sempre, mesmo o
|
|
13
|
+
* aluno tendo relogado — bug real, a tela escalada manda exatamente `paris login`).
|
|
14
|
+
* Reiniciar faz o launchd/systemd subir um processo NOVO que lê o token fresco e
|
|
15
|
+
* reconecta ao relay. Best-effort (nunca derruba o login). Guard por daemonStatus()
|
|
16
|
+
* pra não auto-instalar serviço em quem só roda `paris login` fora do onboarding —
|
|
17
|
+
* e pra ser no-op no 1º install (o daemon ainda nem existe; o `paris daemon install`
|
|
18
|
+
* do instalador cuida disso logo depois).
|
|
19
|
+
*/
|
|
20
|
+
async function refreshDaemonAfterLogin() {
|
|
21
|
+
try {
|
|
22
|
+
if (!(await daemonStatus()))
|
|
23
|
+
return;
|
|
24
|
+
const { mechanism } = await restartDaemon();
|
|
25
|
+
console.log(c.dim(` Agente do terminal embutido religado (${mechanism}).`));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* best-effort — o token já está no disco; o daemon religa no próximo boot/restart */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
6
31
|
export async function loginCommand(opts) {
|
|
7
32
|
const apiUrl = opts.apiUrl ?? defaultApiUrl();
|
|
8
33
|
// --token: skip device-code flow. Usado pelo installer (PARIS_ONBOARDING_TOKEN
|
|
@@ -25,6 +50,7 @@ export async function loginCommand(opts) {
|
|
|
25
50
|
});
|
|
26
51
|
console.log(c.green(`✓ Autenticado como ${me.email}.`));
|
|
27
52
|
console.log(c.dim(' Token salvo em ~/.paris/credentials.'));
|
|
53
|
+
await refreshDaemonAfterLogin();
|
|
28
54
|
return;
|
|
29
55
|
}
|
|
30
56
|
catch (err) {
|
|
@@ -74,6 +100,7 @@ export async function loginCommand(opts) {
|
|
|
74
100
|
});
|
|
75
101
|
console.log(c.green(`✓ Autenticado como ${poll.user.email}.`));
|
|
76
102
|
console.log(c.dim(` Token salvo em ~/.paris/credentials.`));
|
|
103
|
+
await refreshDaemonAfterLogin();
|
|
77
104
|
return;
|
|
78
105
|
}
|
|
79
106
|
if (poll.status === 'authorization_pending') {
|
|
@@ -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:
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
/**
|
|
9
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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.
|
|
17
|
+
.version('0.1.22');
|
|
18
18
|
program
|
|
19
19
|
.command('login')
|
|
20
20
|
.description('Autenticar com sua conta Paris (device-code OR --token direto)')
|
|
@@ -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. (
|
|
23
|
-
c. Se **ainda "não autenticado"
|
|
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