paris-immersion 0.1.0 → 0.1.1
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.d.ts +13 -0
- package/dist/commands/install.js +46 -18
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +19 -2
- package/package.json +1 -1
|
@@ -2,3 +2,16 @@ export declare function installCommand(opts: {
|
|
|
2
2
|
dir?: string;
|
|
3
3
|
immersion?: string;
|
|
4
4
|
}): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Locate the student's workspace, or create one if none exists. Used by
|
|
7
|
+
* `paris serve` so the onboarding embedded terminal always opens a *coached*
|
|
8
|
+
* Claude session — the student never has to know `paris install` exists. This
|
|
9
|
+
* is what guarantees the "magic moment": open the terminal, Claude greets you
|
|
10
|
+
* with today's mission. Returns the workspace dir + whether it was just created.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureWorkspace(opts?: {
|
|
13
|
+
immersion?: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
dir: string;
|
|
16
|
+
created: boolean;
|
|
17
|
+
}>;
|
package/dist/commands/install.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { apiAuthFetch } from '../lib/api.js';
|
|
@@ -22,14 +23,50 @@ export async function installCommand(opts) {
|
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
process.stdout.write(c.dim('Buscando contexto da imersão...\n'));
|
|
25
|
-
const
|
|
26
|
-
const
|
|
26
|
+
const boot = await bootstrapWorkspace(opts.immersion);
|
|
27
|
+
const targetDir = path.resolve(opts.dir ?? `paris-${boot.immersion.slug}`);
|
|
28
|
+
await writeWorkspaceFiles(targetDir, boot, creds.api_url, creds.email);
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(c.green(`✓ Workspace pronto em ${targetDir}`));
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(c.bold('Próximo passo:'));
|
|
33
|
+
console.log(` cd ${path.relative(process.cwd(), targetDir) || '.'}`);
|
|
34
|
+
console.log(' claude');
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(c.dim(`Imersão: ${boot.immersion.name} · fase ${boot.phase} · dia ${boot.day_number}`));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Locate the student's workspace, or create one if none exists. Used by
|
|
40
|
+
* `paris serve` so the onboarding embedded terminal always opens a *coached*
|
|
41
|
+
* Claude session — the student never has to know `paris install` exists. This
|
|
42
|
+
* is what guarantees the "magic moment": open the terminal, Claude greets you
|
|
43
|
+
* with today's mission. Returns the workspace dir + whether it was just created.
|
|
44
|
+
*/
|
|
45
|
+
export async function ensureWorkspace(opts = {}) {
|
|
46
|
+
const existing = await findMarker();
|
|
47
|
+
if (existing)
|
|
48
|
+
return { dir: existing.dir, created: false };
|
|
49
|
+
const creds = await readCredentials();
|
|
50
|
+
if (!creds) {
|
|
51
|
+
throw new Error('Você precisa rodar `paris login` antes.');
|
|
52
|
+
}
|
|
53
|
+
const boot = await bootstrapWorkspace(opts.immersion);
|
|
54
|
+
// Predictable, findable location regardless of where `paris serve` was run.
|
|
55
|
+
const targetDir = path.join(homedir(), `paris-${boot.immersion.slug}`);
|
|
56
|
+
await writeWorkspaceFiles(targetDir, boot, creds.api_url, creds.email);
|
|
57
|
+
return { dir: targetDir, created: true };
|
|
58
|
+
}
|
|
59
|
+
async function bootstrapWorkspace(immersion) {
|
|
60
|
+
const slugQuery = immersion ? `?immersion=${encodeURIComponent(immersion)}` : '';
|
|
61
|
+
return apiAuthFetch(`/immersion-app/v1/workspace/bootstrap${slugQuery}`, {
|
|
27
62
|
method: 'POST',
|
|
28
63
|
});
|
|
29
|
-
|
|
64
|
+
}
|
|
65
|
+
/** Write the full workspace layout (marker, hooks, vendored MCP, settings, skill, CLAUDE.md). */
|
|
66
|
+
async function writeWorkspaceFiles(targetDir, boot, apiUrl, email) {
|
|
30
67
|
await fs.mkdir(targetDir, { recursive: true });
|
|
31
68
|
// .paris/workspace.json — the marker.
|
|
32
|
-
await writeMarker(targetDir, { ...boot.marker, api_url:
|
|
69
|
+
await writeMarker(targetDir, { ...boot.marker, api_url: apiUrl });
|
|
33
70
|
// .paris/hooks/paris-hook.mjs — installed Claude Code hook.
|
|
34
71
|
const hooksDir = path.join(targetDir, '.paris', 'hooks');
|
|
35
72
|
await fs.mkdir(hooksDir, { recursive: true });
|
|
@@ -40,13 +77,12 @@ export async function installCommand(opts) {
|
|
|
40
77
|
const mcpDir = path.join(targetDir, '.paris', 'mcp');
|
|
41
78
|
await fs.mkdir(mcpDir, { recursive: true });
|
|
42
79
|
await fs.writeFile(path.join(mcpDir, 'index.mjs'), await readVendoredMcpBundle(), { mode: 0o755 });
|
|
43
|
-
// .claude/settings.json — wires hooks + MCP server.
|
|
80
|
+
// .claude/settings.json — wires hooks + MCP server. Both settings.json (for
|
|
81
|
+
// interactive `claude`) and settings.local.json (the source `claude -p`
|
|
82
|
+
// reliably loads hooks from).
|
|
44
83
|
const claudeDir = path.join(targetDir, '.claude');
|
|
45
84
|
await fs.mkdir(claudeDir, { recursive: true });
|
|
46
|
-
|
|
47
|
-
// and settings.local.json (so `claude -p` does too — in -p mode, local
|
|
48
|
-
// settings are the reliably-loaded source for hooks).
|
|
49
|
-
const settingsPayload = renderSettingsJson({ apiUrl: creds.api_url });
|
|
85
|
+
const settingsPayload = renderSettingsJson({ apiUrl });
|
|
50
86
|
await fs.writeFile(path.join(claudeDir, 'settings.json'), JSON.stringify(settingsPayload, null, 2));
|
|
51
87
|
await fs.writeFile(path.join(claudeDir, 'settings.local.json'), JSON.stringify(settingsPayload, null, 2));
|
|
52
88
|
// .claude/skills/paris-coach/SKILL.md
|
|
@@ -57,16 +93,8 @@ export async function installCommand(opts) {
|
|
|
57
93
|
await fs.writeFile(path.join(targetDir, 'CLAUDE.md'), renderClaudeMd({
|
|
58
94
|
immersionName: boot.immersion.name,
|
|
59
95
|
immersionSlug: boot.immersion.slug,
|
|
60
|
-
participantEmail:
|
|
96
|
+
participantEmail: email,
|
|
61
97
|
}));
|
|
62
|
-
console.log();
|
|
63
|
-
console.log(c.green(`✓ Workspace pronto em ${targetDir}`));
|
|
64
|
-
console.log();
|
|
65
|
-
console.log(c.bold('Próximo passo:'));
|
|
66
|
-
console.log(` cd ${path.relative(process.cwd(), targetDir) || '.'}`);
|
|
67
|
-
console.log(' claude');
|
|
68
|
-
console.log();
|
|
69
|
-
console.log(c.dim(`Imersão: ${boot.immersion.name} · fase ${boot.phase} · dia ${boot.day_number}`));
|
|
70
98
|
}
|
|
71
99
|
/**
|
|
72
100
|
* Reads the bundled MCP server shipped with this package. Lives at
|
package/dist/commands/serve.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ export interface ServeOptions {
|
|
|
2
2
|
port: number;
|
|
3
3
|
allowedOrigins: string[];
|
|
4
4
|
token: string;
|
|
5
|
+
/** Working directory the embedded `claude` runs in. Defaults to process.cwd().
|
|
6
|
+
* serveCommand sets this to the provisioned workspace so the session is coached. */
|
|
7
|
+
cwd?: string;
|
|
5
8
|
/** When true, accept connections without spawning a pty — used by tests + dry-run handshake checks. */
|
|
6
9
|
dryRun?: boolean;
|
|
7
10
|
}
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { readCredentials } from '../lib/credentials.js';
|
|
4
|
+
import { ensureWorkspace } from './install.js';
|
|
4
5
|
import { c } from '../lib/colors.js';
|
|
5
6
|
const DEFAULT_ALLOWED_ORIGINS = [
|
|
6
7
|
'https://app.paris-group.com',
|
|
@@ -62,7 +63,7 @@ export async function startServeServer(opts) {
|
|
|
62
63
|
name: 'xterm-color',
|
|
63
64
|
cols: 80,
|
|
64
65
|
rows: 24,
|
|
65
|
-
cwd: process.cwd(),
|
|
66
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
66
67
|
env: {
|
|
67
68
|
...process.env,
|
|
68
69
|
TOSTUDY_MCP_ENABLED: '1',
|
|
@@ -156,9 +157,25 @@ export async function serveCommand(opts) {
|
|
|
156
157
|
process.exit(1);
|
|
157
158
|
}
|
|
158
159
|
const allowedOrigins = (process.env.PARIS_SERVE_ORIGINS?.split(',').map((s) => s.trim()).filter(Boolean)) ?? DEFAULT_ALLOWED_ORIGINS;
|
|
160
|
+
// Guarantee the embedded `claude` opens inside a provisioned (coached)
|
|
161
|
+
// workspace. If the student already ran `paris install`, we reuse it; else we
|
|
162
|
+
// bootstrap one now so the onboarding terminal is never vanilla Claude. A
|
|
163
|
+
// failure here is non-fatal — we fall back to cwd so the terminal still works.
|
|
164
|
+
let cwd = process.cwd();
|
|
165
|
+
try {
|
|
166
|
+
const { dir, created } = await ensureWorkspace({});
|
|
167
|
+
cwd = dir;
|
|
168
|
+
console.log(created
|
|
169
|
+
? ` ${c.green('✓')} workspace criado em ${c.bold(dir)}`
|
|
170
|
+
: ` ${c.dim('workspace:')} ${dir}`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
174
|
+
console.error(c.yellow(` ! sem workspace coachado (${msg}). Abrindo Claude em ${cwd}.`));
|
|
175
|
+
}
|
|
159
176
|
let handle;
|
|
160
177
|
try {
|
|
161
|
-
handle = await startServeServer({ port: portNum, allowedOrigins, token });
|
|
178
|
+
handle = await startServeServer({ port: portNum, allowedOrigins, token, cwd });
|
|
162
179
|
}
|
|
163
180
|
catch (err) {
|
|
164
181
|
const msg = err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED