Skip to content

Commit 201092b

Browse files
committed
Remove piped input support and replace with positional argument handling
Remove piped input detection and TTY handling, replacing it with explicit positional argument support. Users attempting piped input now see a clear error with usage instructions. Variables renamed from `pipedInputPath` to `positionalArgContentPath` to reflect new behavior. Related utility functions, safety checks, and tests removed.
1 parent e3c6350 commit 201092b

File tree

8 files changed

+73
-474
lines changed

8 files changed

+73
-474
lines changed

src/index.ts

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ let tempMcpConfigPath: string | undefined
4040
let appConfig: AppConfig | undefined
4141
let yolo: boolean | undefined
4242
let confirmationPatternTriggers: string[] = []
43-
let pipedInputPath: string | undefined
43+
let positionalArgContentPath: string | undefined
4444

4545
const debugLog = util.debuglog('claude-composer')
4646

47-
export { appConfig, pipedInputPath }
47+
export { appConfig, positionalArgContentPath }
4848

4949
async function initializePatterns(): Promise<boolean> {
5050
let patternsToUse = patterns
@@ -110,9 +110,9 @@ function cleanup() {
110110
} catch (e) {}
111111
}
112112

113-
if (pipedInputPath && fs.existsSync(pipedInputPath)) {
113+
if (positionalArgContentPath && fs.existsSync(positionalArgContentPath)) {
114114
try {
115-
fs.unlinkSync(pipedInputPath)
115+
fs.unlinkSync(positionalArgContentPath)
116116
} catch (e) {}
117117
}
118118

@@ -261,6 +261,47 @@ function handleStdinData(data: Buffer): void {
261261
}
262262

263263
export async function main() {
264+
// Check for piped input and exit immediately
265+
if (!process.stdin.isTTY) {
266+
console.error(
267+
'\x1b[31m╔══════════════════════════════════════════════════════╗\x1b[0m',
268+
)
269+
console.error(
270+
'\x1b[31m║ PIPED INPUT NOT SUPPORTED ║\x1b[0m',
271+
)
272+
console.error(
273+
'\x1b[31m╠══════════════════════════════════════════════════════╣\x1b[0m',
274+
)
275+
console.error(
276+
"\x1b[31m║ Claude Composer doesn't support piped input. ║\x1b[0m",
277+
)
278+
console.error(
279+
'\x1b[31m║ ║\x1b[0m',
280+
)
281+
console.error(
282+
'\x1b[31m║ Instead, pass your content as a positional argument: ║\x1b[0m',
283+
)
284+
console.error(
285+
'\x1b[31m║ ║\x1b[0m',
286+
)
287+
console.error(
288+
'\x1b[31m║ claude "your content here" ║\x1b[0m',
289+
)
290+
console.error(
291+
'\x1b[31m║ ║\x1b[0m',
292+
)
293+
console.error(
294+
'\x1b[31m║ Example: ║\x1b[0m',
295+
)
296+
console.error(
297+
'\x1b[31m║ claude "explain this error: ..." ║\x1b[0m',
298+
)
299+
console.error(
300+
'\x1b[31m╚══════════════════════════════════════════════════════╝\x1b[0m',
301+
)
302+
process.exit(1)
303+
}
304+
264305
if (process.argv[2] === 'cc-init') {
265306
const { handleCcInit } = await import('./cli/cc-init.js')
266307
await handleCcInit(process.argv.slice(3))
@@ -448,10 +489,13 @@ export async function main() {
448489
// Save the positional argument to a file
449490
const tmpDir = os.tmpdir()
450491
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
451-
pipedInputPath = path.join(tmpDir, `claude-composer-piped-${timestamp}.txt`)
492+
positionalArgContentPath = path.join(
493+
tmpDir,
494+
`claude-composer-positional-${timestamp}.txt`,
495+
)
452496

453497
// Write the first positional argument to the file
454-
fs.writeFileSync(pipedInputPath, childArgs[0])
498+
fs.writeFileSync(positionalArgContentPath, childArgs[0])
455499

456500
// Remove the argument from childArgs
457501
childArgs.splice(0, 1)
@@ -476,11 +520,11 @@ export async function main() {
476520
process.exit(code)
477521
})
478522

479-
// Add app ready pattern if in plan mode, if there's piped input, or if we saved positional args
523+
// Add app ready pattern if in plan mode or if we saved positional args
480524
// IMPORTANT: This must be done AFTER terminal manager is initialized so response queue has targets
481-
if (appConfig?.mode === 'plan' || !process.stdin.isTTY || pipedInputPath) {
525+
if (appConfig?.mode === 'plan' || positionalArgContentPath) {
482526
const appStartedPattern = createAppReadyPattern(() => ({
483-
pipedInputPath,
527+
positionalArgContentPath,
484528
mode: appConfig?.mode,
485529
}))
486530
try {
@@ -491,57 +535,13 @@ export async function main() {
491535
}
492536
}
493537

494-
if (process.stdin.isTTY) {
495-
process.stdin.on('data', handleStdinData)
496-
} else {
497-
const fs = await import('fs')
498-
const os = await import('os')
499-
const path = await import('path')
500-
501-
const tmpDir = os.tmpdir()
502-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
503-
pipedInputPath = path.join(tmpDir, `claude-composer-piped-${timestamp}.txt`)
504-
const writeStream = fs.createWriteStream(pipedInputPath)
505-
506-
process.stdin.on('data', chunk => {
507-
writeStream.write(chunk)
508-
})
509-
510-
process.stdin.on('end', () => {
511-
writeStream.end()
512-
})
513-
514-
process.stdin.resume()
515-
516-
const tty = await import('tty')
517-
if (os.platform() !== 'win32') {
518-
try {
519-
const ttyFd = fs.openSync('/dev/tty', 'r')
520-
const ttyStream = new tty.ReadStream(ttyFd)
521-
522-
ttyStream.setRawMode(true)
523-
524-
ttyStream.on('data', handleStdinData)
525-
;(global as any).__ttyStream = ttyStream
538+
process.stdin.on('data', handleStdinData)
526539

527-
process.stdout.on('resize', () => {
528-
const newCols = process.stdout.columns || 80
529-
const newRows = process.stdout.rows || 30
530-
terminalManager.resize(newCols, newRows)
531-
})
532-
} catch (error) {
533-
// Silently fail - don't output to console after child process starts
534-
}
535-
}
536-
}
537-
538-
if (process.stdin.isTTY) {
539-
process.stdout.on('resize', () => {
540-
const newCols = process.stdout.columns || 80
541-
const newRows = process.stdout.rows || 30
542-
terminalManager.resize(newCols, newRows)
543-
})
544-
}
540+
process.stdout.on('resize', () => {
541+
const newCols = process.stdout.columns || 80
542+
const newRows = process.stdout.rows || 30
543+
terminalManager.resize(newCols, newRows)
544+
})
545545
}
546546

547547
main().catch(error => {

src/patterns/registry.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,33 +193,37 @@ const confirmationPatterns: PatternConfig[] = [
193193
]
194194

195195
export function createAppReadyPattern(
196-
getAppConfig: () => { pipedInputPath?: string; mode?: string },
196+
getAppConfig: () => { positionalArgContentPath?: string; mode?: string },
197197
): PatternConfig {
198198
const getAppReadyResponse = (): string[] | undefined => {
199199
const config = getAppConfig()
200-
const pipedInputPath = config.pipedInputPath
200+
const positionalArgContentPath = config.positionalArgContentPath
201201

202202
// If plan mode is enabled, send SHIFT+TAB twice first
203203
if (config.mode === 'plan') {
204-
if (pipedInputPath && fs.existsSync(pipedInputPath)) {
204+
if (positionalArgContentPath && fs.existsSync(positionalArgContentPath)) {
205205
try {
206-
const content = fs.readFileSync(pipedInputPath, 'utf8').trimEnd()
206+
const content = fs
207+
.readFileSync(positionalArgContentPath, 'utf8')
208+
.trimEnd()
207209
return content
208210
? ['\x1b[Z', 100, '\x1b[Z', 100, content, 500, '\r']
209211
: ['\x1b[Z', 100, '\x1b[Z']
210212
} catch (error) {}
211213
}
212-
// Even without piped content, send SHIFT+TAB in plan mode
214+
// Even without positional arg content, send SHIFT+TAB in plan mode
213215
return ['\x1b[Z', 100, '\x1b[Z']
214216
}
215217

216-
// Normal piped input mode (no plan mode)
217-
if (!pipedInputPath || !fs.existsSync(pipedInputPath)) {
218+
// Normal positional arg injection mode (no plan mode)
219+
if (!positionalArgContentPath || !fs.existsSync(positionalArgContentPath)) {
218220
return
219221
}
220222

221223
try {
222-
const content = fs.readFileSync(pipedInputPath, 'utf8').trimEnd()
224+
const content = fs
225+
.readFileSync(positionalArgContentPath, 'utf8')
226+
.trimEnd()
223227
return content ? [content, 500, '\r'] : undefined
224228
} catch (error) {}
225229
}

src/safety/checker.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { execSync } from 'child_process'
55
import type { AppConfig, PreflightOptions } from '../types/preflight.js'
66
import { askYesNo } from '../cli/prompts.js'
77
import { log, warn } from '../utils/logging.js'
8-
import { isPipedInput, exitWithPipedInputError } from '../utils/piped-input.js'
98
import { expandPath } from '../utils/file-utils.js'
109

1110
function isInTrustedRoot(appConfig: AppConfig): boolean {
@@ -64,9 +63,6 @@ export async function checkVersionControl(
6463

6564
if (!fs.existsSync(gitDir)) {
6665
if (!allowWithoutVersionControl) {
67-
if (isPipedInput()) {
68-
exitWithPipedInputError('version control check')
69-
}
7066
warn('※ Running in project without version control')
7167
const proceed = await askYesNo(
7268
'※ Do you want to continue?',
@@ -97,9 +93,6 @@ export async function checkDirtyDirectory(
9793

9894
if (gitStatus !== '') {
9995
if (!allowInDirtyDirectory) {
100-
if (isPipedInput()) {
101-
exitWithPipedInputError('dirty directory check (uncommitted changes)')
102-
}
10396
warn('※ Running in directory with uncommitted changes')
10497
const proceed = await askYesNo(
10598
'※ Do you want to continue?',
@@ -188,10 +181,6 @@ export async function handleAutomaticAcceptanceWarning(
188181
'\x1b[33m╚═════════════════════════════════════════════════════════════════╝\x1b[0m',
189182
)
190183

191-
if (isPipedInput()) {
192-
exitWithPipedInputError('yolo mode warning')
193-
}
194-
195184
const proceed = await askYesNo(
196185
'※ Do you want to continue with YOLO mode enabled?',
197186
true,

src/utils/piped-input.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.

src/utils/prompt-acceptance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export function shouldAcceptPrompt(
1111
return true
1212
}
1313

14-
// Special case: always accept pipe-on-app-ready
15-
if (match.patternId === 'pipe-on-app-ready') {
14+
// Special case: always accept inject-positional-arg-on-app-ready
15+
if (match.patternId === 'inject-positional-arg-on-app-ready') {
1616
return true
1717
}
1818

0 commit comments

Comments
 (0)