@keeroklab/cli 0.1.0 → 0.2.0

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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @keeroklab/cli
2
+
3
+ CLI wrapper for **Keerok Lab** — live Claude Code supervision for training sessions.
4
+
5
+ Students connect to a supervised session where the instructor can monitor terminals in real-time, send messages, and manage API budgets.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx @keeroklab/cli connect <token> --server https://keerok.tech
11
+ ```
12
+
13
+ Your instructor will provide the `<token>` via email or the session page.
14
+
15
+ ## Requirements
16
+
17
+ - **Node.js 18+** — [Download](https://nodejs.org)
18
+ - A terminal (macOS Terminal, iTerm2, Windows Terminal, etc.)
19
+
20
+ ## What happens when you connect
21
+
22
+ 1. Your token is validated against the server
23
+ 2. A secure WebSocket connection is established
24
+ 3. `claude` (Claude Code CLI) is launched in your terminal
25
+ 4. Your terminal output is streamed to the instructor's dashboard (read-only)
26
+
27
+ ## Options
28
+
29
+ ```
30
+ Usage: keerok-lab connect <token> [options]
31
+
32
+ Arguments:
33
+ token Connection token provided by the instructor
34
+
35
+ Options:
36
+ -s, --server <url> Server URL (default: "https://keerok.tech")
37
+ -c, --command <cmd> Command to run (default: "claude")
38
+ -h, --help Display help
39
+ ```
40
+
41
+ ## Security
42
+
43
+ - Your API key is **encrypted (AES-256/Fernet)** and never stored in plain text
44
+ - The key is only active during the session and **revoked automatically** when it ends
45
+ - The instructor sees your terminal in **read-only** mode (no write access)
46
+ - No personal data is kept after the session ends
47
+
48
+ ## How It Works
49
+
50
+ ```
51
+ Student terminal Keerok Server Instructor dashboard
52
+ │ │ │
53
+ ├── validate token ──────────► │ │
54
+ │◄── session config ───────── │ │
55
+ │ │ │
56
+ ├══ WebSocket ═══════════════► │ ◄═══════════════ WebSocket ══┤
57
+ │ │ │
58
+ │ terminal output ─────────► │ ──── terminal stream ──────► │
59
+ │ │ │
60
+ │◄── instructor message ────── │ ◄── send message ───────────┤
61
+ │◄── budget warning ────────── │ │
62
+ │◄── session ended ─────────── │ ◄── end session ────────────┤
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keeroklab/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI wrapper for Keerok Lab — live Claude Code supervision",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,19 +12,28 @@
12
12
  "files": [
13
13
  "src/"
14
14
  ],
15
- "keywords": ["claude", "lab", "supervision", "terminal", "claude-code", "keerok"],
15
+ "keywords": [
16
+ "claude",
17
+ "lab",
18
+ "supervision",
19
+ "terminal",
20
+ "claude-code",
21
+ "keerok"
22
+ ],
16
23
  "author": "Keerok <hello@keerok.tech> (https://keerok.tech)",
17
24
  "repository": {
18
25
  "type": "git",
19
- "url": "https://github.com/keerok/lab-cli"
26
+ "url": "https://github.com/vincentrandon/keeroklab-cli"
20
27
  },
21
28
  "homepage": "https://keerok.tech",
29
+ "bugs": {
30
+ "url": "https://github.com/vincentrandon/keeroklab-cli/issues"
31
+ },
22
32
  "license": "MIT",
23
33
  "dependencies": {
34
+ "chalk": "^5.3.0",
24
35
  "commander": "^12.0.0",
25
- "node-pty": "^1.0.0",
26
- "ws": "^8.16.0",
27
- "chalk": "^5.3.0"
36
+ "ws": "^8.16.0"
28
37
  },
29
38
  "engines": {
30
39
  "node": ">=18.0.0"
@@ -1,59 +1,60 @@
1
1
  /**
2
- * pty-capture.js — Spawns a PTY process and captures output.
3
- * Uses node-pty to create a pseudo-terminal that captures all output
4
- * including ANSI escape codes, colors, and cursor movements.
2
+ * pty-capture.js — Spawns a process and captures output.
3
+ * Uses child_process.spawn with pipes to capture terminal output
4
+ * and forward it to both stdout and the WebSocket handler.
5
5
  */
6
6
 
7
- import pty from 'node-pty';
7
+ import { spawn } from 'child_process';
8
8
  import os from 'os';
9
9
 
10
10
  export function spawnPty(command, handlers) {
11
11
  const shell = os.platform() === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
12
- const args = command === shell ? [] : ['-c', command];
13
- const useShell = command !== shell;
14
-
15
12
  const cols = process.stdout.columns || 80;
16
13
  const rows = process.stdout.rows || 24;
17
14
 
18
- const term = pty.spawn(
19
- useShell ? shell : command,
20
- useShell ? args : [],
21
- {
22
- name: 'xterm-256color',
23
- cols,
24
- rows,
25
- cwd: process.cwd(),
26
- env: {
27
- ...process.env,
28
- TERM: 'xterm-256color',
29
- },
30
- }
31
- );
15
+ const child = spawn(shell, ['-c', command], {
16
+ stdio: ['pipe', 'pipe', 'pipe'],
17
+ cwd: process.cwd(),
18
+ env: {
19
+ ...process.env,
20
+ TERM: 'xterm-256color',
21
+ FORCE_COLOR: '1',
22
+ COLUMNS: String(cols),
23
+ LINES: String(rows),
24
+ },
25
+ });
32
26
 
33
- // Forward PTY output to both stdout and the handler
34
- term.onData((data) => {
27
+ // Forward stdout to both terminal and handler
28
+ child.stdout.on('data', (data) => {
35
29
  process.stdout.write(data);
36
- handlers.onData?.(data);
30
+ handlers.onData?.(data.toString());
37
31
  });
38
32
 
39
- term.onExit(({ exitCode }) => {
40
- handlers.onExit?.(exitCode);
33
+ // Forward stderr to both terminal and handler
34
+ child.stderr.on('data', (data) => {
35
+ process.stderr.write(data);
36
+ handlers.onData?.(data.toString());
41
37
  });
42
38
 
43
- // Forward stdin to PTY
39
+ child.on('exit', (code) => {
40
+ handlers.onExit?.(code ?? 0);
41
+ });
42
+
43
+ // Forward stdin to child
44
44
  if (process.stdin.isTTY) {
45
45
  process.stdin.setRawMode(true);
46
46
  }
47
47
  process.stdin.resume();
48
48
  process.stdin.on('data', (data) => {
49
- term.write(data.toString());
49
+ if (child.stdin.writable) {
50
+ child.stdin.write(data);
51
+ }
50
52
  });
51
53
 
52
54
  // Handle terminal resize
53
55
  process.stdout.on('resize', () => {
54
56
  const newCols = process.stdout.columns;
55
57
  const newRows = process.stdout.rows;
56
- term.resize(newCols, newRows);
57
58
  handlers.onResize?.(newCols, newRows);
58
59
  });
59
60
 
@@ -62,13 +63,15 @@ export function spawnPty(command, handlers) {
62
63
 
63
64
  return {
64
65
  kill() {
65
- term.kill();
66
+ child.kill();
66
67
  },
67
68
  write(data) {
68
- term.write(data);
69
+ if (child.stdin.writable) {
70
+ child.stdin.write(data);
71
+ }
69
72
  },
70
- resize(cols, rows) {
71
- term.resize(cols, rows);
73
+ resize() {
74
+ // child_process doesn't support resize, but we notify the handler
72
75
  },
73
76
  };
74
77
  }