paris-immersion 0.1.15 → 0.1.16
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/serve.js +22 -0
- package/dist/index.js +1 -1
- package/dist/lib/relay-agent.d.ts +2 -0
- package/dist/lib/relay-agent.js +1 -0
- package/dist/lib/self-update.d.ts +6 -0
- package/dist/lib/self-update.js +76 -0
- package/package.json +1 -1
package/dist/commands/serve.js
CHANGED
|
@@ -7,6 +7,7 @@ import { readCredentials } from '../lib/credentials.js';
|
|
|
7
7
|
import { ensureWorkspace } from './install.js';
|
|
8
8
|
import { startRelayAgent } from '../lib/relay-agent.js';
|
|
9
9
|
import { renderTostudyCommand } from '../templates/tostudy-command.js';
|
|
10
|
+
import { maybeSelfUpdate } from '../lib/self-update.js';
|
|
10
11
|
import { c } from '../lib/colors.js';
|
|
11
12
|
/**
|
|
12
13
|
* Restore the executable bit on node-pty's `spawn-helper`. Some install paths
|
|
@@ -340,6 +341,12 @@ export async function serveCommand(opts) {
|
|
|
340
341
|
console.error(c.red('Não logado.') + ' Rode: ' + c.bold('paris login'));
|
|
341
342
|
process.exit(1);
|
|
342
343
|
}
|
|
344
|
+
// Auto-update no startup (throttle 3h): o aluno NUNCA roda comando de update à
|
|
345
|
+
// mão. Se há versão nova no npm, atualiza ANTES de servir e sai — o daemon
|
|
346
|
+
// (launchd/systemd KeepAlive) respawna na versão nova. Best-effort.
|
|
347
|
+
if (await maybeSelfUpdate((m) => console.log(` ${c.dim(m)}`))) {
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
343
350
|
const portNum = opts.port == null ? 31000 : Number(opts.port);
|
|
344
351
|
if (!Number.isFinite(portNum) || portNum < 0 || portNum > 65535) {
|
|
345
352
|
console.error(c.red(`Porta inválida: ${opts.port}`));
|
|
@@ -422,8 +429,11 @@ export async function serveCommand(opts) {
|
|
|
422
429
|
.catch(() => { });
|
|
423
430
|
console.log(` ${c.dim('Ctrl+C pra encerrar.')}`);
|
|
424
431
|
console.log();
|
|
432
|
+
let updateTimer;
|
|
425
433
|
const shutdown = () => {
|
|
426
434
|
try {
|
|
435
|
+
if (updateTimer)
|
|
436
|
+
clearInterval(updateTimer);
|
|
427
437
|
relay?.close();
|
|
428
438
|
}
|
|
429
439
|
catch {
|
|
@@ -432,6 +442,18 @@ export async function serveCommand(opts) {
|
|
|
432
442
|
handle.close();
|
|
433
443
|
process.exit(0);
|
|
434
444
|
};
|
|
445
|
+
// Auto-update periódico: a cada 3h, SE não houver sessão do curso em uso, se
|
|
446
|
+
// atualiza e reinicia (o daemon respawna na versão nova) — sem ninguém rodar
|
|
447
|
+
// nada. Gate por isBusy() pra nunca interromper o aluno no meio do curso.
|
|
448
|
+
updateTimer = setInterval(() => {
|
|
449
|
+
if (relay?.isBusy())
|
|
450
|
+
return;
|
|
451
|
+
void maybeSelfUpdate((m) => console.log(` ${c.dim(m)}`)).then((updated) => {
|
|
452
|
+
if (updated)
|
|
453
|
+
shutdown();
|
|
454
|
+
});
|
|
455
|
+
}, 3 * 3600_000);
|
|
456
|
+
updateTimer.unref?.();
|
|
435
457
|
process.on('SIGINT', shutdown);
|
|
436
458
|
process.on('SIGTERM', shutdown);
|
|
437
459
|
}
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { daemonCommand, restartCommand } from './commands/daemon.js';
|
|
|
13
13
|
const program = new Command()
|
|
14
14
|
.name('paris')
|
|
15
15
|
.description('Paris Immersion — CLI do aluno (login, workspace, tarefas)')
|
|
16
|
-
.version('0.1.
|
|
16
|
+
.version('0.1.16');
|
|
17
17
|
program
|
|
18
18
|
.command('login')
|
|
19
19
|
.description('Autenticar com sua conta Paris (device-code OR --token direto)')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface RelayAgentHandle {
|
|
2
2
|
close: () => void;
|
|
3
|
+
/** true se há uma sessão do Claude viva (pty) — usado pra não auto-atualizar/reiniciar no meio do curso. */
|
|
4
|
+
isBusy: () => boolean;
|
|
3
5
|
}
|
|
4
6
|
/**
|
|
5
7
|
* Inicia o agente relay. Busca o serve-token + info do relay na API (Bearer
|
package/dist/lib/relay-agent.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Se houver uma versão MAIS NOVA no npm, atualiza global e retorna true (o
|
|
3
|
+
* chamador deve `process.exit(0)` pra o daemon respawnar na nova). Throttle de 3h.
|
|
4
|
+
* `force` ignora o throttle (usado no startup; o periódico respeita o throttle).
|
|
5
|
+
*/
|
|
6
|
+
export declare function maybeSelfUpdate(log?: (m: string) => void, force?: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-update do `paris serve`. O aluno NUNCA deve rodar um comando de update à
|
|
3
|
+
* mão — o serve (rodando como daemon) checa o npm, se atualiza sozinho e o
|
|
4
|
+
* KeepAlive do launchd/systemd respawna no binário novo. Best-effort: qualquer
|
|
5
|
+
* falha (offline, sem permissão) é engolida e o serve segue na versão atual.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
const PKG = 'paris-immersion';
|
|
15
|
+
const THROTTLE_MS = 3 * 3600_000; // checa no máximo a cada 3h
|
|
16
|
+
function currentVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const req = createRequire(import.meta.url);
|
|
19
|
+
return req('../../package.json').version;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** retorna 1 se a > b, -1 se a < b, 0 se igual (semver x.y.z, ignora pré-release). */
|
|
26
|
+
function cmpSemver(a, b) {
|
|
27
|
+
const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
|
|
28
|
+
const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
|
|
29
|
+
for (let i = 0; i < 3; i++) {
|
|
30
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
31
|
+
return 1;
|
|
32
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
async function stampPath() {
|
|
38
|
+
const dir = path.join(homedir(), '.paris');
|
|
39
|
+
await fs.mkdir(dir, { recursive: true }).catch(() => { });
|
|
40
|
+
return path.join(dir, '.update-check');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Se houver uma versão MAIS NOVA no npm, atualiza global e retorna true (o
|
|
44
|
+
* chamador deve `process.exit(0)` pra o daemon respawnar na nova). Throttle de 3h.
|
|
45
|
+
* `force` ignora o throttle (usado no startup; o periódico respeita o throttle).
|
|
46
|
+
*/
|
|
47
|
+
export async function maybeSelfUpdate(log = () => { }, force = false) {
|
|
48
|
+
if (process.env.PARIS_NO_SELF_UPDATE)
|
|
49
|
+
return false; // escape-hatch (dev/CI/offline forçado)
|
|
50
|
+
try {
|
|
51
|
+
const stamp = await stampPath();
|
|
52
|
+
if (!force) {
|
|
53
|
+
try {
|
|
54
|
+
const last = Number(await fs.readFile(stamp, 'utf8'));
|
|
55
|
+
if (Number.isFinite(last) && Date.now() - last < THROTTLE_MS)
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* sem stamp — checa */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
await fs.writeFile(stamp, String(Date.now())).catch(() => { });
|
|
63
|
+
const { stdout } = await execFileAsync('npm', ['view', PKG, 'version'], { timeout: 15000 });
|
|
64
|
+
const latest = stdout.trim();
|
|
65
|
+
const current = currentVersion();
|
|
66
|
+
if (!/^\d+\.\d+\.\d+/.test(latest) || cmpSemver(latest, current) <= 0)
|
|
67
|
+
return false;
|
|
68
|
+
log(`nova versão da CLI: ${current} → ${latest}. Atualizando…`);
|
|
69
|
+
await execFileAsync('npm', ['install', '-g', `${PKG}@${latest}`], { timeout: 120_000 });
|
|
70
|
+
log(`atualizado para ${latest}. Reiniciando o agente…`);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false; // best-effort — nunca quebra o serve
|
|
75
|
+
}
|
|
76
|
+
}
|
package/package.json
CHANGED