paris-immersion 0.1.14 → 0.1.15
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/install.js +9 -5
- package/dist/commands/serve.js +18 -7
- package/dist/index.js +1 -1
- package/dist/lib/relay-agent.js +26 -3
- package/package.json +1 -1
package/dist/commands/install.js
CHANGED
|
@@ -90,11 +90,15 @@ async function writeWorkspaceFiles(targetDir, boot, apiUrl, email) {
|
|
|
90
90
|
const skillDir = path.join(claudeDir, 'skills', 'paris-coach');
|
|
91
91
|
await fs.mkdir(skillDir, { recursive: true });
|
|
92
92
|
await fs.writeFile(path.join(skillDir, 'SKILL.md'), renderParisCoachSkill());
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
// /tostudy.md — NOSSO slash-command (rota confiável do curso). Escrito nos DOIS
|
|
94
|
+
// lugares que o Claude carrega: o do PROJETO e o GLOBAL (~/.claude/commands) —
|
|
95
|
+
// o global é o que faz funcionar em máquinas que já rodaram `tostudy init`, e
|
|
96
|
+
// agora vale pra todo aluno novo também. Independe do @tostudy-ai/cli.
|
|
97
|
+
const tostudyCmd = renderTostudyCommand();
|
|
98
|
+
for (const dir of [path.join(claudeDir, 'commands'), path.join(homedir(), '.claude', 'commands')]) {
|
|
99
|
+
await fs.mkdir(dir, { recursive: true });
|
|
100
|
+
await fs.writeFile(path.join(dir, 'tostudy.md'), tostudyCmd);
|
|
101
|
+
}
|
|
98
102
|
// CLAUDE.md — workspace-level rules.
|
|
99
103
|
await fs.writeFile(path.join(targetDir, 'CLAUDE.md'), renderClaudeMd({
|
|
100
104
|
immersionName: boot.immersion.name,
|
package/dist/commands/serve.js
CHANGED
|
@@ -160,13 +160,19 @@ export function resolveKickoff(cwd) {
|
|
|
160
160
|
* workspaces antigos e manter o conteúdo fresco a cada serve. Best-effort.
|
|
161
161
|
*/
|
|
162
162
|
function ensureTostudyCommand(cwd) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
const body = renderTostudyCommand();
|
|
164
|
+
// Escreve nos DOIS lugares que o Claude Code carrega slash-commands: o do
|
|
165
|
+
// PROJETO (workspace) e o GLOBAL (~/.claude/commands). É justamente o comando
|
|
166
|
+
// global que faz o terminal funcionar em máquinas que já passaram por um
|
|
167
|
+
// `tostudy init` — replicamos isso pra TODA máquina, inclusive a do aluno novo.
|
|
168
|
+
for (const dir of [path.join(cwd, '.claude', 'commands'), path.join(homedir(), '.claude', 'commands')]) {
|
|
169
|
+
try {
|
|
170
|
+
mkdirSync(dir, { recursive: true });
|
|
171
|
+
writeFileSync(path.join(dir, 'tostudy.md'), body);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
/* best-effort — se um falhar, o outro cobre; senão cai no fallback de texto */
|
|
175
|
+
}
|
|
170
176
|
}
|
|
171
177
|
}
|
|
172
178
|
export function spawnCoachPty(cwd) {
|
|
@@ -237,6 +243,11 @@ export async function startServeServer(opts) {
|
|
|
237
243
|
wss.once('error', reject);
|
|
238
244
|
});
|
|
239
245
|
wss.on('connection', (ws) => {
|
|
246
|
+
// TCP_NODELAY: sem Nagle no socket local (caminho dev ws://localhost).
|
|
247
|
+
try {
|
|
248
|
+
ws._socket?.setNoDelay?.(true);
|
|
249
|
+
}
|
|
250
|
+
catch { /* noop */ }
|
|
240
251
|
if (opts.dryRun) {
|
|
241
252
|
// Tests use this branch — we just confirm the handshake passed.
|
|
242
253
|
ws.send('READY\n');
|
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.15');
|
|
17
17
|
program
|
|
18
18
|
.command('login')
|
|
19
19
|
.description('Autenticar com sua conta Paris (device-code OR --token direto)')
|
package/dist/lib/relay-agent.js
CHANGED
|
@@ -100,12 +100,20 @@ export async function startRelayAgent(opts) {
|
|
|
100
100
|
const MAX_OUT_BUF = 256 * 1024;
|
|
101
101
|
let outBuf = '';
|
|
102
102
|
let flushTimer;
|
|
103
|
+
// Adaptativo: o PRIMEIRO chunk de uma rajada (depois de um período de silêncio)
|
|
104
|
+
// sai NA HORA — o eco de uma tecla e o início da resposta do Claude não pagam a
|
|
105
|
+
// janela inteira de coalescing. Só a CAUDA da rajada (tokens que chegam dentro
|
|
106
|
+
// da janela, streaming token-a-token) é coalescida, mantendo a escrita lisa.
|
|
107
|
+
// "Fast first byte, smooth tail." lastFlushAt marca o último envio: se passou
|
|
108
|
+
// mais de COALESCE_MS desde então E o buffer estava vazio, é um novo burst.
|
|
109
|
+
let lastFlushAt = 0;
|
|
103
110
|
const flushOut = () => {
|
|
104
111
|
flushTimer = undefined;
|
|
105
112
|
if (!outBuf)
|
|
106
113
|
return;
|
|
107
114
|
const data = outBuf;
|
|
108
115
|
outBuf = '';
|
|
116
|
+
lastFlushAt = Date.now();
|
|
109
117
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
110
118
|
try {
|
|
111
119
|
ws.send(seal(key, TYPE_DATA, Buffer.from(data, 'utf8')), { binary: true });
|
|
@@ -147,9 +155,14 @@ export async function startRelayAgent(opts) {
|
|
|
147
155
|
return;
|
|
148
156
|
}
|
|
149
157
|
pty.onData((d) => {
|
|
158
|
+
// Novo burst = buffer vazio E sem timer armado E passou >COALESCE_MS desde o
|
|
159
|
+
// último flush (= veio depois de um silêncio, p.ex. o aluno apertou Enter e o
|
|
160
|
+
// Claude começa a responder). Captura ANTES do append (depois nunca é vazio).
|
|
161
|
+
const newBurst = !outBuf && !flushTimer && Date.now() - lastFlushAt >= COALESCE_MS;
|
|
150
162
|
outBuf += d;
|
|
151
|
-
// Flush imediato se acumulou muito (evita um frame gigante)
|
|
152
|
-
|
|
163
|
+
// Flush imediato se acumulou muito (evita um frame gigante) OU se é o primeiro
|
|
164
|
+
// chunk de um novo burst (latência baixa pro início da resposta); senão coalesce.
|
|
165
|
+
if (outBuf.length >= MAX_OUT_BUF || newBurst) {
|
|
153
166
|
if (flushTimer) {
|
|
154
167
|
clearTimeout(flushTimer);
|
|
155
168
|
flushTimer = undefined;
|
|
@@ -201,7 +214,17 @@ export async function startRelayAgent(opts) {
|
|
|
201
214
|
return;
|
|
202
215
|
const url = `${relayUrl.replace(/\/$/, '')}/agent?token=${encodeURIComponent(token)}`;
|
|
203
216
|
ws = new WebSocket(url);
|
|
204
|
-
ws.on('open', () =>
|
|
217
|
+
ws.on('open', () => {
|
|
218
|
+
// TCP_NODELAY: terminal interativo manda frames pequenos; sem isso o Nagle
|
|
219
|
+
// os enfileira (~40ms/hop, pior ainda com delayed-ACK) → digitação e o 1º
|
|
220
|
+
// paint de cada resposta ficam visivelmente lentos. _socket é interno do
|
|
221
|
+
// `ws` — optional-chaining + try/catch degrada pro comportamento atual se mudar.
|
|
222
|
+
try {
|
|
223
|
+
ws._socket?.setNoDelay?.(true);
|
|
224
|
+
}
|
|
225
|
+
catch { /* noop */ }
|
|
226
|
+
log(`relay: conectado (${relayUrl})`);
|
|
227
|
+
});
|
|
205
228
|
ws.on('message', (data, isBinary) => {
|
|
206
229
|
if (!isBinary) {
|
|
207
230
|
// Controle do relay (texto): viewer-joined / viewer-left.
|
package/package.json
CHANGED