Factory CLI - Demo Edit
A hybrid command-line interface that runs either:
- Interactive TUI – a full-screen React/Ink terminal app
- Headless commands – traditional
droid headless <command> sub-commands powered by Commander
The entry-point (src/index.ts) detects how it was invoked and chooses the right mode automatically.
1. Overview of the Hybrid Architecture
src/
├── index.ts # Hybrid entry – mode detection
│
├── app.tsx # React/Ink TUI (interactive mode)
│
└── commands/ # Commander commands (headless mode)
├── droid.ts
└── login.ts
• No positional args ➜ Interactive TUI
• headless subcommand ➜ Headless mode
2 · Local Development
All dev tasks are exposed as npm scripts – never run compiled .js files directly.
| Start CLI (auto mode) | npm start |
| Start with Node inspector | npm run debug |
| Lint source | npm run lint |
| Type-check | npm run typecheck |
| Run tests | npm test |
Build JS into dist/ | npm run build |
| Produce executable bundle | npm run bundle |
| Clean build artifacts | npm run clean |
The start/debug scripts use tsx so you can edit TypeScript and restart instantly.
3 · Testing Both Modes Locally
Interactive TUI
# Launch interactive UI
npm start
You'll see a colourful Ink interface; quit with Ctrl-C.
Running in VSCode
Factory CLI can also be run inside VSCode using the Factory extension:
- First, install the Factory VSCode extension (see VSCode Extension README for installation instructions)
- Click the Run Factory button (🤖) in the editor toolbar to launch Factory CLI in a dedicated terminal
- The extension provides full VSCode context (open files, selections, diagnostics) to Factory via MCP
Headless Commands
# Show global help
npm start -- --help
# Show headless subcommands
npm start -- headless --help
# Run login interactively (headless)
npm start -- headless login
# Send message to a droid
npm start -- headless droid "Hello, Droid!" --session-id <sessionId>
The extra -- after npm start passes subsequent flags to the CLI.
4 · Development vs Production
| Dev | npm start / npm run debug | Runs from TS sources with tsx, fast reload. |
| Build | npm run build | Compiles TS → dist/. |
| Bundle | npm run bundle (calls build) | Generates single executable bundle/droid.js. |
| Publish | npm publish (bundled in prepare) | Users install droid binary from npm. |
During CI the prepare script produces the bundle automatically.
5 · Examples
Headless examples
droid headless status
droid headless login
droid headless droid "Hello" --session-id dOLpXUI8ux6YdZrg3kCs
Interactive example
droid
6 · Testing the Production droid Command
Sometimes you need to test the exact binary users will get from npm install -g factory-cli.
Follow this workflow:
npm run bundle
npm link
droid --help
droid headless status
droid headless droid "Hello" --session-id <sessionId>
npm unlink -g factory-cli
| Fast iteration / TypeScript | npm start -- <args> |
| Debug with inspector | npm run debug -- <args> |
| Validate production bundle | npm run bundle && npm link then droid headless <args> |
ℹ️ Tip: The extra -- after npm start or npm run debug passes the
remaining flags directly to the CLI.
7 · ESM & Imports
The package is "type": "module"; all runtime imports use .js extensions even though the source is TypeScript. The build pipeline rewrites them automatically.
8 · Troubleshooting
EACCES when running droid | Ensure the bundle is executable (chmod +x bundle/droid.js). npm run bundle handles this automatically. |
module not found after rename | Run npm run clean && npm run bundle to rebuild from scratch. |
| Global command still points to old code | Run npm unlink -g factory-cli && npm link to refresh the symlink. |
9 · Logging
Factory CLI has two different logging behaviors depending on the execution mode:
Interactive Mode Logging
When running in interactive TUI mode (droid with no arguments), all logging output is redirected to files to avoid interfering with the clean React/Ink interface:
- Log Directory:
~/.factory/logs/
- Log Files:
droid-log-<timestamp>.log (e.g., droid-log-2025-01-15T10-30-45-123Z.log)
- Content: All
logInfo, logException, and logWarn calls are written to the timestamped log file
- Format:
[timestamp] LEVEL: message | Context: {...}
Example log file location:
~/.factory/logs/droid-log-2025-01-15T10-30-45-123Z.log
Example log entry:
[2025-01-15T10:30:45.123Z] INFO: User started interactive session
[2025-01-15T10:30:47.456Z] ERROR: Failed to initialize MCP client: Connection refused | Context: {"retry": 1}
Headless Mode Logging
When running headless commands (droid headless <command>), logging follows standard console output patterns:
- Log Output: Directly to stdout/stderr using the standard
@factory/logging package
- Integration: Works with existing telemetry, Sentry, and monitoring systems
- Format: Standard Factory logging format with metadata support
Accessing Logs
Interactive Mode Logs:
ls -la ~/.factory/logs/
tail -f ~/.factory/logs/droid-log-*.log
cat ~/.factory/logs/droid-log-2025-01-15T10-30-45-123Z.log
Headless Mode Logs:
droid headless droid "test message" --session-id abc123
droid headless droid "test" --session-id abc123 2>&1 | tee my-session.log
Log Cleanup
Interactive mode creates a new log file for each session. To manage disk space:
find ~/.factory/logs -name "droid-log-*.log" -mtime +7 -delete
du -sh ~/.factory/logs
10 · Tool Registry & Executors Design
🔄 What Changed
After: Dynamic mapping automatically discovers tools from TUI registry
const toolMapping = buildToolMapping();
🛠 How to Add New Tools
11 · Contributing
pnpm install (or npm install) at repo root
cd apps/factory-cli
- Implement feature / fix
- Ensure
npm run lint && npm run typecheck && npm test pass
- Commit & open PR 🚀