Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ All notable changes to this project will be documented in this file.
- **ESLint with TypeScript support** (#493) — static analysis for common runtime anti-patterns
- **Integration** — available in CLI pre-flight checks

### Fixed — CLI node-pty Validation
- **squad start --tunnel** (#711) — validates node-pty availability before creating RemoteBridge or tunnel, preventing terminal corruption when package is missing
- Graceful error message guides users to install node-pty when required
- Includes smoke test coverage for node-pty failure scenarios

### Fixed — CLI Terminal Rendering
- Eliminated scroll-to-top flicker caused by Ink's fullscreen `clearTerminal` path firing on every render cycle
- Reduced re-render churn via memoized elapsed-time display (one-second granularity gate) and consolidated animation intervals
Expand Down
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/squad-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@
"react": "^19.2.4",
"vscode-jsonrpc": "^8.2.1"
},
"optionalDependencies": {
"node-pty": "^1.1.0",
"qrcode-terminal": "^0.12.0"
},
Comment thread
andikrueger marked this conversation as resolved.
Resolved
"devDependencies": {
"@types/node": "^22.0.0",
"@types/react": "^19.2.14",
Expand Down
37 changes: 32 additions & 5 deletions packages/squad-cli/src/cli/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const RESET = '\x1b[0m';
const DIM = '\x1b[2m';
const GREEN = '\x1b[32m';
const YELLOW = '\x1b[33m';
const MISSING_MODULE_RE =
/\b(?:MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND)\b|Cannot find module|Cannot find package/i;

export interface StartOptions {
tunnel: boolean;
Expand All @@ -38,7 +40,36 @@ export interface StartOptions {
command?: string;
}

async function checkNodePty(): Promise<any> {
try {
// @ts-ignore — node-pty is an optional native dependency
return await import('node-pty');
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
if (MISSING_MODULE_RE.test(detail)) {
throw new Error('node-pty not available. Install it for PTY support:');
}
throw new Error('node-pty not available. Install it for PTY support:', {
cause: detail,
});
}
}

export async function runStart(cwd: string, options: StartOptions): Promise<void> {
// ─── Verify node-pty availability FIRST (before any side effects) ───
let nodePty: any;
try {
nodePty = await checkNodePty();
} catch (err) {
Comment thread
andikrueger marked this conversation as resolved.
Resolved
const detail = err instanceof Error && typeof err.cause === 'string' ? err.cause : undefined;
console.error(`${YELLOW}✗${RESET} ${(err as Error).message}`);
console.error(` ${DIM}npm install -g node-pty${RESET}`);
if (detail) {
console.error(`\n${DIM}Error: ${detail}${RESET}`);
}
process.exit(1);
}

const { repo, branch } = getGitInfo(cwd);
const machine = getMachineId();
const squadDir = storage.existsSync(path.join(cwd, '.squad'))
Expand All @@ -47,7 +78,7 @@ export async function runStart(cwd: string, options: StartOptions): Promise<void
? path.join(cwd, '.ai-team')
: '';

// ─── Setup remote bridge FIRST (before PTY takes over terminal) ───
// ─── Setup remote bridge (after verifying PTY is available) ───
let bridge: RemoteBridge | null = null;
let tunnelUrl = '';

Expand Down Expand Up @@ -120,10 +151,6 @@ export async function runStart(cwd: string, options: StartOptions): Promise<void
}

// ─── Spawn copilot in PTY ─────────────────────────────────
// Dynamic import node-pty (native module)
// @ts-expect-error — node-pty is an optional native dependency
const nodePty = await import('node-pty');

const copilotExePath = path.join(
'C:', 'ProgramData', 'global-npm', 'node_modules', '@github', 'copilot',
'node_modules', '@github', 'copilot-win32-x64', 'copilot.exe'
Expand Down
Loading
Loading