paris-immersion 0.1.13 → 0.1.14

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,6 +10,7 @@ import { renderClaudeMd } from '../templates/claude-md.js';
10
10
  import { renderSettingsJson } from '../templates/settings.js';
11
11
  import { renderParisCoachSkill } from '../templates/skill.js';
12
12
  import { renderHookScript } from '../templates/hook.js';
13
+ import { renderTostudyCommand } from '../templates/tostudy-command.js';
13
14
  export async function installCommand(opts) {
14
15
  const creds = await readCredentials();
15
16
  if (!creds) {
@@ -89,6 +90,11 @@ async function writeWorkspaceFiles(targetDir, boot, apiUrl, email) {
89
90
  const skillDir = path.join(claudeDir, 'skills', 'paris-coach');
90
91
  await fs.mkdir(skillDir, { recursive: true });
91
92
  await fs.writeFile(path.join(skillDir, 'SKILL.md'), renderParisCoachSkill());
93
+ // .claude/commands/tostudy.md — NOSSO slash-command /tostudy (rota confiável do
94
+ // curso). Garante que /tostudy sempre exista, independente do @tostudy-ai/cli.
95
+ const commandsDir = path.join(claudeDir, 'commands');
96
+ await fs.mkdir(commandsDir, { recursive: true });
97
+ await fs.writeFile(path.join(commandsDir, 'tostudy.md'), renderTostudyCommand());
92
98
  // CLAUDE.md — workspace-level rules.
93
99
  await fs.writeFile(path.join(targetDir, 'CLAUDE.md'), renderClaudeMd({
94
100
  immersionName: boot.immersion.name,
@@ -38,11 +38,6 @@ export interface ServeHandle {
38
38
  close: () => void;
39
39
  }
40
40
  export declare function resolveKickoff(cwd: string): string;
41
- /**
42
- * Spawn the coached pty (`claude '/tostudy-<curso>'` inside the provisioned
43
- * workspace). Shared by the local ws server and the relay agent. Throws if
44
- * node-pty can't load/spawn — callers report it on their channel.
45
- */
46
41
  export declare function spawnCoachPty(cwd: string): IPty;
47
42
  /**
48
43
  * Start a localhost WebSocket server that brokers a pty (running `claude`) to a
@@ -1,11 +1,12 @@
1
1
  import { WebSocketServer } from 'ws';
2
2
  import { createRequire } from 'node:module';
3
- import { promises as fs, chmodSync, existsSync, statSync, readdirSync, readFileSync } from 'node:fs';
3
+ import { promises as fs, chmodSync, existsSync, statSync, readdirSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { readCredentials } from '../lib/credentials.js';
7
7
  import { ensureWorkspace } from './install.js';
8
8
  import { startRelayAgent } from '../lib/relay-agent.js';
9
+ import { renderTostudyCommand } from '../templates/tostudy-command.js';
9
10
  import { c } from '../lib/colors.js';
10
11
  /**
11
12
  * Restore the executable bit on node-pty's `spawn-helper`. Some install paths
@@ -119,6 +120,10 @@ export function resolveKickoff(cwd) {
119
120
  if (names.size === 0)
120
121
  return COURSE_TUTOR_PROMPT;
121
122
  const all = [...names];
123
+ // Nosso /tostudy (escrito pelo install/serve, sempre presente) é a rota
124
+ // CONFIÁVEL — preferimos ele: o aluno e o tutor sempre rodam via /tostudy.
125
+ if (all.some((n) => n.toLowerCase() === 'tostudy'))
126
+ return '/tostudy';
122
127
  // Curso ATIVO deste workspace = fonte da verdade pra escolher entre vários cursos.
123
128
  let slug = '';
124
129
  try {
@@ -148,10 +153,28 @@ export function resolveKickoff(cwd) {
148
153
  * workspace). Shared by the local ws server and the relay agent. Throws if
149
154
  * node-pty can't load/spawn — callers report it on their channel.
150
155
  */
156
+ /**
157
+ * Garante que o slash-command `/tostudy` exista no workspace ANTES do Claude
158
+ * subir (slash-commands carregam no startup — criados depois não aparecem sem
159
+ * reload). O `paris install` já escreve, mas reescrevemos aqui pra cobrir
160
+ * workspaces antigos e manter o conteúdo fresco a cada serve. Best-effort.
161
+ */
162
+ function ensureTostudyCommand(cwd) {
163
+ try {
164
+ const dir = path.join(cwd, '.claude', 'commands');
165
+ mkdirSync(dir, { recursive: true });
166
+ writeFileSync(path.join(dir, 'tostudy.md'), renderTostudyCommand());
167
+ }
168
+ catch {
169
+ /* best-effort — se falhar, o resolveKickoff cai no fallback de texto */
170
+ }
171
+ }
151
172
  export function spawnCoachPty(cwd) {
152
173
  const req = createRequire(import.meta.url);
153
174
  ensureSpawnHelperExecutable(req);
154
175
  const ptyMod = req('node-pty');
176
+ // Escreve /tostudy ANTES de resolver o kickoff + subir o Claude.
177
+ ensureTostudyCommand(cwd);
155
178
  const kickoff = resolveKickoff(cwd);
156
179
  const shell = process.env.SHELL || (process.platform === 'win32' ? 'powershell.exe' : 'bash');
157
180
  const shellArgs = process.platform === 'win32'
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.13');
16
+ .version('0.1.14');
17
17
  program
18
18
  .command('login')
19
19
  .description('Autenticar com sua conta Paris (device-code OR --token direto)')
@@ -139,7 +139,7 @@ async function main() {
139
139
  if (inCourse) {
140
140
  lines.push('## FASE DO CURSO DE ONBOARDING')
141
141
  lines.push('O aluno está na etapa do curso **' + course.title + '** (progresso: **' + course.lessonsCompleted + '/' + course.totalLessons + '**).')
142
- lines.push('Conduza o curso da ToStudy DIRETO pela CLI (o aluno já chega logado no install): rode no bash **tostudy courses --json**, depois **tostudy select <id>** pra ativar o curso, e ensine lição por lição com **tostudy next** / **tostudy progress** / **tostudy lesson** / **tostudy hint**. Se existir um slash-command **/tostudy-<curso>**, pode usá-lo. NÃO trabalhe em missões da imersão enquanto o curso não terminar. (Fallback: ferramentas MCP **get_course**/**complete_lesson** do paris.)')
142
+ lines.push('Rode **/tostudy** pra conduzir o curso da ToStudy (o comando está instalado no workspace e o aluno já chega logado). Ele ativa o curso e ensina lição por lição. NÃO trabalhe em missões da imersão enquanto o curso não terminar. (Fallback: ferramentas MCP **get_course**/**complete_lesson** do paris.)')
143
143
  if (PHASE === 'session-start') {
144
144
  lines.push('')
145
145
  lines.push('**Comece falando primeiro**: rode /tostudy e dê as boas-vindas. O aluno ainda não digitou nada.')
@@ -0,0 +1,10 @@
1
+ /**
2
+ * O slash-command `/tostudy` que O NOSSO LADO escreve no workspace
3
+ * (`<ws>/.claude/commands/tostudy.md`). É a rota CONFIÁVEL pra iniciar o curso:
4
+ * o `@tostudy-ai/cli` só instala um comando específico do curso (`/tostudy-<slug>`)
5
+ * SE rodar `init` — e mesmo então, se o Claude já tinha subido, ele não recarrega
6
+ * slash-commands. Escrevendo `/tostudy` nós mesmos (no `paris install` E no
7
+ * `paris serve`, antes do Claude subir), garantimos que `/tostudy` SEMPRE exista
8
+ * e SEMPRE funcione, conduzindo o curso real via a tostudy CLI.
9
+ */
10
+ export declare function renderTostudyCommand(): string;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * O slash-command `/tostudy` que O NOSSO LADO escreve no workspace
3
+ * (`<ws>/.claude/commands/tostudy.md`). É a rota CONFIÁVEL pra iniciar o curso:
4
+ * o `@tostudy-ai/cli` só instala um comando específico do curso (`/tostudy-<slug>`)
5
+ * SE rodar `init` — e mesmo então, se o Claude já tinha subido, ele não recarrega
6
+ * slash-commands. Escrevendo `/tostudy` nós mesmos (no `paris install` E no
7
+ * `paris serve`, antes do Claude subir), garantimos que `/tostudy` SEMPRE exista
8
+ * e SEMPRE funcione, conduzindo o curso real via a tostudy CLI.
9
+ */
10
+ export function renderTostudyCommand() {
11
+ return `---
12
+ description: Inicia ou retoma o curso de onboarding da imersão (tutor ToStudy no terminal)
13
+ ---
14
+
15
+ Você é o **tutor do curso de onboarding** da imersão Paris Group, rodando no terminal do aluno. Conduza o curso da ToStudy em **português**, no ritmo do aluno. O aluno já chega logado na conta dele da ToStudy (auto-login no install).
16
+
17
+ ## 1. Prepare o curso (em silêncio, antes de falar)
18
+
19
+ 1. Rode \`tostudy courses --json\` para ver o curso em que o aluno está matriculado.
20
+ 2. Rode \`tostudy select <id-ou-número-do-curso>\` para ativá-lo (escolha o curso da imersão; normalmente só há um).
21
+ 3. Rode \`tostudy progress --json\` para ver onde ele parou.
22
+
23
+ ## 2. Ensine lição por lição
24
+
25
+ - \`tostudy next\` — avança para a próxima lição (marca a atual como concluída).
26
+ - \`tostudy lesson\` / \`tostudy theory\` — conteúdo da lição atual.
27
+ - \`tostudy hint\` — dica progressiva quando o aluno travar.
28
+ - \`tostudy validate\` — valida exercícios.
29
+ - \`tostudy progress\` — status atual (lição X de Y, %).
30
+
31
+ Explique cada lição com suas palavras, traga exemplos do contexto de negócio do aluno e só avance quando ele entender. **Comece dando as boas-vindas** e já ensinando a primeira lição — o aluno ainda não digitou nada. NÃO trabalhe em missões da imersão enquanto o curso não terminar.
32
+
33
+ ## 3. Fallback (se a ToStudy não estiver disponível)
34
+
35
+ Use as ferramentas MCP do paris: \`get_course\` (currículo + progresso) e \`complete_lesson\` (marcar lição concluída).
36
+ `;
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paris-immersion",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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": {