@factory/cli 0.1.2-dev.5 → 0.55.2

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 CHANGED
@@ -31,19 +31,57 @@ src/
31
31
 
32
32
  All dev tasks are exposed as **npm scripts** – never run compiled `.js` files directly.
33
33
 
34
- | Purpose | Command |
35
- | ------------------------- | ------------------- |
36
- | Start CLI (auto mode) | `npm start` |
37
- | Start with Node inspector | `npm run debug` |
38
- | Lint source | `npm run lint` |
39
- | Type-check | `npm run typecheck` |
40
- | Run tests | `npm test` |
41
- | Build JS into `dist/` | `npm run build` |
42
- | Produce executable bundle | `npm run bundle` |
43
- | Clean build artifacts | `npm run clean` |
34
+ | Purpose | Command |
35
+ | -------------------------------- | --------------------- |
36
+ | Start CLI (auto mode) | `npm start` |
37
+ | Start with Node inspector | `npm run debug` |
38
+ | Set up shell alias for local dev | `npm run setup:alias` |
39
+ | Build SEA for current platform | `npm run build` |
40
+ | Build and run the binary | `npm run build:run` |
41
+ | Lint source | `npm run lint` |
42
+ | Type-check | `npm run typecheck` |
43
+ | Run tests | `npm test` |
44
+ | Produce executable bundle | `npm run bundle` |
45
+ | Clean build artifacts | `npm run clean` |
44
46
 
45
47
  The `start`/`debug` scripts use **tsx** so you can edit TypeScript and restart instantly.
46
48
 
49
+ ### Shell Alias Setup
50
+
51
+ For easier local development, you can set up a `droid` alias that runs the CLI from your current directory:
52
+
53
+ ```bash
54
+ # Add alias to ~/.zshrc (checks if already exists)
55
+ npm run setup:alias
56
+
57
+ # Start a new terminal or source the file
58
+ source ~/.zshrc
59
+
60
+ # Now you can run 'droid' from anywhere
61
+ droid
62
+ ```
63
+
64
+ ### Building Standalone Executables (SEA)
65
+
66
+ The CLI can be compiled into Single Executable Applications (SEA) for different platforms:
67
+
68
+ ```bash
69
+ # Build for your current platform automatically
70
+ npm run build
71
+
72
+ # Build and immediately run the binary
73
+ npm run build:run
74
+
75
+ # Build for specific platforms
76
+ npm run build:sea:mac:arm64 # macOS Apple Silicon
77
+ npm run build:sea:mac:x64 # macOS Intel
78
+ npm run build:sea:linux:arm64 # Linux ARM64
79
+ npm run build:sea:linux:x64 # Linux x64
80
+ npm run build:sea:windows:x64 # Windows x64
81
+ ```
82
+
83
+ The `build` script automatically detects your OS and architecture, then builds the appropriate binary to `dist/{platform}/{arch}/droid` (or `droid.exe` on Windows).
84
+
47
85
  ---
48
86
 
49
87
  ## 3 · Testing Both Modes Locally
@@ -90,7 +128,6 @@ The extra `--` after `npm start` passes subsequent flags to the CLI.
90
128
  | Phase | Command(s) | Result |
91
129
  | ----------- | ------------------------------------ | ---------------------------------------------- |
92
130
  | **Dev** | `npm start` / `npm run debug` | Runs from TS sources with tsx, fast reload. |
93
- | **Build** | `npm run build` | Compiles TS → `dist/`. |
94
131
  | **Bundle** | `npm run bundle` (calls `build`) | Generates single executable `bundle/droid.js`. |
95
132
  | **Publish** | `npm publish` (bundled in `prepare`) | Users install `droid` binary from npm. |
96
133
 
@@ -110,9 +147,59 @@ droid headless status
110
147
  droid headless login
111
148
 
112
149
  # Talk to Droid
113
- droid headless droid "Hello" --session-id dOLpXUI8ux6YdZrg3kCs
150
+ droid headless "Hello" --session-id dOLpXUI8ux6YdZrg3kCs
151
+
152
+ # Streaming input mode
153
+ # Send multiple messages over stdin, maintaining conversation state
154
+ echo '{"role":"user","content":"hello"}' | droid exec --input-format stream-json --output-format stream-json
114
155
  ```
115
156
 
157
+ ### Streaming Input Mode (Droid Manager Integration)
158
+
159
+ The CLI supports a **streaming input mode** designed for Droid Manager integration, allowing a single process to handle multiple user messages while maintaining conversation state.
160
+
161
+ **Usage Pattern:**
162
+
163
+ ```javascript
164
+ // Spawn process once
165
+ const droid = spawn('droid', [
166
+ 'exec',
167
+ '--input-format',
168
+ 'stream-json',
169
+ '--output-format',
170
+ 'stream-json',
171
+ '--auto',
172
+ 'low',
173
+ ]);
174
+
175
+ // Send messages over time (JSONL format via stdin)
176
+ droid.stdin.write('{"role":"user","content":"hello"}\n');
177
+
178
+ // Read events from stdout (JSONL format)
179
+ droid.stdout.on('data', (chunk) => {
180
+ const events = chunk.toString().split('\n').filter(Boolean);
181
+ events.forEach((line) => {
182
+ const event = JSON.parse(line);
183
+ // Handle: user messages, assistant responses, tool calls, etc.
184
+ });
185
+ });
186
+
187
+ // Send another message later
188
+ droid.stdin.write('{"role":"user","content":"what can you do?"}\n');
189
+
190
+ // Close stdin when done to trigger graceful shutdown
191
+ droid.stdin.end();
192
+ ```
193
+
194
+ **Key Points:**
195
+
196
+ - **Input**: JSONL (newline-delimited JSON) via stdin, each line is `Anthropic.MessageParam` with `role: "user"`
197
+ - **Output**: JSONL events via stdout (same format as `--output-format stream-json`)
198
+ - **stdin stays open** until explicitly closed by the caller
199
+ - **Messages processed sequentially** - one at a time
200
+ - **Session persists** across all messages in the process lifetime
201
+ - **Graceful shutdown** when stdin closes
202
+
116
203
  ### Interactive example
117
204
 
118
205
  ```bash
@@ -138,7 +225,7 @@ npm link
138
225
  # 3. Use it anywhere
139
226
  droid --help
140
227
  droid headless status
141
- droid headless droid "Hello" --session-id <sessionId>
228
+ droid headless "Hello" --session-id <sessionId>
142
229
 
143
230
  # 4. (Optional) Un-link when finished
144
231
  npm unlink -g factory-cli
@@ -224,10 +311,10 @@ cat ~/.factory/logs/droid-log-2025-01-15T10-30-45-123Z.log
224
311
 
225
312
  ```bash
226
313
  # Logs appear directly in terminal output
227
- droid headless droid "test message" --session-id abc123
314
+ droid headless "test message" --session-id abc123
228
315
 
229
316
  # Redirect to file if needed
230
- droid headless droid "test" --session-id abc123 2>&1 | tee my-session.log
317
+ droid headless "test" --session-id abc123 2>&1 | tee my-session.log
231
318
  ```
232
319
 
233
320
  ### Log Cleanup
@@ -269,10 +356,137 @@ const toolMapping = buildToolMapping();
269
356
 
270
357
  ---
271
358
 
272
- ## 11 · Contributing
359
+ ## 11 · E2E Testing of the TUI
360
+
361
+ The Factory CLI includes end-to-end tests for the terminal UI using Microsoft's `@microsoft/tui-test`.
362
+
363
+ ### Testing Framework
364
+
365
+ - Framework: `@microsoft/tui-test`
366
+ - Shell: bash
367
+ - Config: `apps/cli/tui-test.config.ts`
368
+
369
+ ### Prerequisites
370
+
371
+ The E2E tests require a valid `FACTORY_API_KEY` environment variable to authenticate with Factory services. You can generate an API key from [https://dev.app.factory.ai/settings/api-keys](https://dev.app.factory.ai/settings/api-keys).
372
+
373
+ ```bash
374
+ export FACTORY_API_KEY="your-api-key-here"
375
+ npm run test:e2e
376
+ ```
377
+
378
+ The tests launch the CLI via a wrapper script that configures the environment:
379
+
380
+ - program: `./e2e-tests/test-workspace/run-droid.sh`
381
+ - The wrapper exports all required env vars (e.g., `FACTORY_HOME_OVERRIDE`) and ensures correct execution context for tests.
382
+ - rows/columns: 30x80
383
+
384
+ ### Test Layout
385
+
386
+ ```
387
+ apps/cli/
388
+ ├── e2e-tests/
389
+ │ ├── chat-input.test.ts # Text entry, deletion, Ctrl+C, file suggestions, cursor nav
390
+ │ ├── settings.template.json # Copied to each test's .factory-dev
391
+ │ ├── templates/ # Blueprint copied to each test workspace
392
+ │ │ ├── package.json
393
+ │ │ ├── tsconfig.json
394
+ │ │ └── src/
395
+ │ └── test-workspace/ # Contains wrapper scripts only
396
+ │ ├── run-droid.sh # Creates unique workspace per invocation
397
+ │ ├── run-droid.ps1 # Windows version
398
+ │ └── run-droid.cmd # Windows CMD wrapper
399
+ └── tui-test.config.ts # TUI test config using the wrapper
400
+ ```
401
+
402
+ ### Running Tests
403
+
404
+ ```bash
405
+ # From apps/cli
406
+ npm run test:e2e # bundles CLI then runs all e2e tests
407
+
408
+ # Run a specific test file (or append :line)
409
+ npm run test:e2e e2e-tests/chat-input.test.ts
410
+
411
+ # Tests automatically create isolated workspaces - no setup needed!
412
+ # Each test invocation gets its own /tmp/droid-test-* directory
413
+ ```
414
+
415
+ ### Debugging
416
+
417
+ At any point of time, you can access the serialized terminal view using:
418
+
419
+ ```typescript
420
+ const terminalView = terminal.serialize().view;
421
+ ```
422
+
423
+ Tracing is enabled in config (`trace: true`). Traces are saved under `tui-traces/`.
424
+
425
+ ```bash
426
+ # Replay a trace
427
+ npm run show-trace tui-traces/<trace-file>
428
+ # Or directly
429
+ npx @microsoft/tui-test show-trace tui-traces/<trace-file>
430
+ ```
431
+
432
+ ### Test Environment Isolation
433
+
434
+ Each test case runs in **complete isolation** with its own workspace and `.factory-dev` directory:
435
+
436
+ - **Per-test workspaces**: The wrapper scripts (`run-droid.sh`, `run-droid.ps1`) automatically create a unique temporary workspace for each test invocation
437
+ - **Unique identifiers**: Workspaces are named with PID and timestamp: `/tmp/droid-test-{pid}-{timestamp}-XXXXXX`
438
+ - **Template copying**: Files from `e2e-tests/templates/` (package.json, tsconfig.json, src/) are copied to each test workspace
439
+ - **Automatic cleanup**: Temporary workspaces are cleaned up when tests complete (via trap on Unix, try/finally on Windows)
440
+ - **Parallel execution**: Tests can run in parallel without state interference since each has its own `.factory-dev`
441
+
442
+ Settings template (copied to each test's `.factory-dev/settings.json`):
443
+
444
+ ```json
445
+ {
446
+ "model": "claude-sonnet-4-20250514",
447
+ "cloudSessionSync": false,
448
+ "diffMode": "github"
449
+ }
450
+ ```
451
+
452
+ ### What’s Covered
453
+
454
+ Current suite validates:
455
+
456
+ - Text entry visibility
457
+ - Backspace/Delete behavior (including forward delete)
458
+ - Ctrl+C warning and double Ctrl+C exit
459
+ - File suggestions via @: show, filter, navigate, select, dismiss
460
+ - Cursor navigation and in-line editing
461
+
462
+ ### Writing New Tests
463
+
464
+ Global config already provides the program and env. You can optionally tweak terminal size per test file:
465
+
466
+ ```ts
467
+ import { test, expect } from '@microsoft/tui-test';
468
+
469
+ test.use({ rows: 24, columns: 100 });
470
+
471
+ test('example', async ({ terminal }) => {
472
+ await expect(
473
+ terminal.getByText('standing in an open terminal', {
474
+ full: false,
475
+ strict: false,
476
+ })
477
+ ).toBeVisible({ timeout: 10000 });
478
+ terminal.write('Hello');
479
+ await expect(terminal.getByText('Hello')).toBeVisible();
480
+ });
481
+ ```
482
+
483
+ ---
484
+
485
+ ## 12 · Contributing
273
486
 
274
487
  1. `pnpm install` (or `npm install`) at repo root
275
- 2. `cd apps/factory-cli`
488
+ 2. `cd apps/cli`
276
489
  3. Implement feature / fix
277
490
  4. Ensure `npm run lint && npm run typecheck && npm test` pass
278
- 5. Commit & open PR 🚀
491
+ 5. Run E2E tests with `npm run test:e2e`
492
+ 6. Commit & open PR 🚀
package/bin/droid ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * JavaScript shim for the droid command.
5
+ *
6
+ * This shim finds and executes the appropriate platform-specific binary.
7
+ * For x64 platforms, it detects AVX2 support and selects the appropriate
8
+ * binary (regular or baseline for older CPUs).
9
+ *
10
+ * In optimized installations, this file is replaced with a hard link to
11
+ * the actual binary, avoiding Node.js startup overhead.
12
+ *
13
+ * Based on esbuild's approach: https://github.com/evanw/esbuild/pull/1621
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const child_process = require('child_process');
19
+
20
+ const {
21
+ PLATFORM_PACKAGES,
22
+ getPlatformKey,
23
+ getBinaryName,
24
+ getBinaryPathWithInfo,
25
+ resolveBinaryPath,
26
+ } = require('../platform.js');
27
+
28
+ function runBinary(binaryPath, args) {
29
+ const result = child_process.spawnSync(binaryPath, args, {
30
+ stdio: 'inherit',
31
+ windowsHide: true,
32
+ });
33
+
34
+ if (result.error) {
35
+ throw result.error;
36
+ }
37
+
38
+ return result;
39
+ }
40
+
41
+ function getBinaryPath() {
42
+ const platformKey = getPlatformKey();
43
+ const packages = PLATFORM_PACKAGES[platformKey];
44
+
45
+ if (!packages) {
46
+ console.error(`Error: Unsupported platform: ${platformKey}`);
47
+ console.error(
48
+ `Supported platforms: ${Object.keys(PLATFORM_PACKAGES).join(', ')}`
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ // Try to get binary from optionalDependencies
54
+ const result = getBinaryPathWithInfo();
55
+ if (result) {
56
+ return result;
57
+ }
58
+
59
+ // Fallback: check if binary exists alongside this script (for development)
60
+ const localBinary = path.join(
61
+ __dirname,
62
+ '..',
63
+ 'dist',
64
+ process.platform,
65
+ process.arch,
66
+ getBinaryName()
67
+ );
68
+ if (fs.existsSync(localBinary)) {
69
+ return {
70
+ path: localBinary,
71
+ pkg: 'local',
72
+ hasBaseline: !!packages.baseline,
73
+ };
74
+ }
75
+
76
+ console.error(
77
+ `Error: Could not find the droid binary for ${platformKey}.\n\n` +
78
+ `This usually means the optional dependency "${packages.regular}" was not installed.\n` +
79
+ `Try running: npm install\n\n` +
80
+ `If you're using npm with --ignore-scripts, the binary may not have been set up correctly.`
81
+ );
82
+ process.exit(1);
83
+ }
84
+
85
+ // Get the binary path and execute
86
+ const { path: binPath, pkg, hasBaseline } = getBinaryPath();
87
+ const args = process.argv.slice(2);
88
+
89
+ try {
90
+ const result = runBinary(binPath, args);
91
+
92
+ // Check for illegal instruction error (CPU doesn't support AVX2)
93
+ // This can happen on Windows where we couldn't detect AVX2 upfront
94
+ if (result.signal === 'SIGILL' && hasBaseline && !pkg.includes('baseline')) {
95
+ console.error(
96
+ `\nNote: Your CPU doesn't support AVX2 instructions. Trying baseline build...`
97
+ );
98
+ // Try baseline
99
+ const baselinePath = resolveBinaryPath(
100
+ PLATFORM_PACKAGES[getPlatformKey()].baseline
101
+ );
102
+ if (baselinePath) {
103
+ const baselineResult = runBinary(baselinePath, args);
104
+ process.exit(baselineResult.status ?? 0);
105
+ }
106
+ }
107
+
108
+ process.exit(result.status ?? 0);
109
+ } catch (e) {
110
+ console.error(`Error executing droid: ${e.message}`);
111
+ process.exit(1);
112
+ }