Skip to content

Commit 603a4ae

Browse files
committed
add stdin content option
1 parent f374180 commit 603a4ae

33 files changed

Lines changed: 269 additions & 12 deletions

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,18 @@ When using `--stdin`, the specified files are effectively added to the include p
276276
> [!NOTE]
277277
> When using `--stdin`, file paths can be relative or absolute, and Repomix will automatically handle path resolution and deduplication.
278278
279+
To include command output or other ad-hoc context near the top of the packed output:
280+
281+
```bash
282+
# Include build output in the generated Repomix file
283+
npm run build 2>&1 | repomix --stdin-content
284+
285+
# Include recent logs in the generated Repomix file
286+
tail -n 200 app.log | repomix --stdin-content
287+
```
288+
289+
The `--stdin-content` option reads arbitrary stdin text and places it after the header section, before the directory structure. Use it when you want to package current error output, logs, or other runtime context together with the codebase. It cannot be combined with `--stdin`.
290+
279291
To include git logs in the output:
280292

281293
```bash
@@ -616,6 +628,7 @@ Instruction
616628
| `--quiet` | Suppress all console output except errors (useful for scripting) |
617629
| `--stdout` | Write packed output directly to stdout instead of a file (suppresses all logging) |
618630
| `--stdin` | Read file paths from stdin, one per line (specified files are processed directly) |
631+
| `--stdin-content` | Read arbitrary stdin content and include it near the top of the packed output |
619632
| `--copy` | Copy the generated output to system clipboard after processing |
620633
| `--token-count-tree [threshold]` | Show file tree with token counts; optional threshold to show only files with ≥N tokens (e.g., `--token-count-tree 100`) |
621634
| `--top-files-len <number>` | Number of largest files to show in summary (default: `5`) |
@@ -692,7 +705,7 @@ Instruction
692705
#### Watch Mode
693706
- `-w, --watch`: Watch for file changes and automatically re-pack. Debounces rapid changes (300ms) and logs a timestamp on each rebuild. Stop with `Ctrl+C`.
694707

695-
Watch mode only works with local directories, so it cannot be combined with `--remote`, a positional remote repository URL, `--stdout`, `--stdin`, `--split-output`, `--skill-generate`, or `--copy` (whether set on the command line or in your config file).
708+
Watch mode only works with local directories, so it cannot be combined with `--remote`, a positional remote repository URL, `--stdout`, `--stdin`, `--stdin-content`, `--split-output`, `--skill-generate`, or `--copy` (whether set on the command line or in your config file).
696709

697710
#### Examples
698711

src/cli/actions/defaultAction.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type RepomixOutputStyle,
99
repomixConfigCliSchema,
1010
} from '../../config/configSchema.js';
11-
import { readFilePathsFromStdin } from '../../core/file/fileStdin.js';
11+
import { readContentFromStdin, readFilePathsFromStdin } from '../../core/file/fileStdin.js';
1212
import { type PackResult, pack } from '../../core/packager.js';
1313
import { generateDefaultSkillName } from '../../core/skill/skillUtils.js';
1414
import { RepomixError, rethrowValidationErrorIfSchemaError } from '../../shared/errorHandle.js';
@@ -61,6 +61,10 @@ export const runDefaultAction = async (
6161
): Promise<DefaultActionRunnerResult> => {
6262
logger.trace('Loaded CLI options:', cliOptions);
6363

64+
if (cliOptions.stdinContent === true) {
65+
cliOptions.stdinContent = await readContentFromStdin();
66+
}
67+
6468
// Build the merged config (migration + file config + CLI options)
6569
const config = await buildMergedConfig(cwd, cliOptions);
6670

@@ -284,6 +288,10 @@ export const buildCliConfig = (options: CliOptions): RepomixConfigCli => {
284288
cliConfig.output = { ...cliConfig.output, headerText: options.headerText };
285289
}
286290

291+
if (typeof options.stdinContent === 'string') {
292+
cliConfig.output = { ...cliConfig.output, stdinContent: options.stdinContent };
293+
}
294+
287295
if (options.compress !== undefined) {
288296
cliConfig.output = { ...cliConfig.output, compress: options.compress };
289297
}

src/cli/cliRun.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export const run = async () => {
7373
).conflicts('output'),
7474
)
7575
.option('--stdin', 'Read file paths from stdin, one per line (specified files are processed directly)')
76+
.addOption(
77+
new Option(
78+
'--stdin-content',
79+
'Read arbitrary stdin content and include it near the top of the packed output',
80+
).conflicts('stdin'),
81+
)
7682
.option('--copy', 'Copy the generated output to system clipboard after processing')
7783
.option(
7884
'--token-count-tree [threshold]',
@@ -260,6 +266,9 @@ const validateWatchOptions = (directories: string[], options: CliOptions): void
260266
if (options.stdin) {
261267
throw new RepomixError('--watch cannot be used with --stdin. Watch mode discovers files automatically.');
262268
}
269+
if (options.stdinContent) {
270+
throw new RepomixError('--watch cannot be used with --stdin-content. Watch mode discovers files automatically.');
271+
}
263272
if (options.copy) {
264273
throw new RepomixError(
265274
'--watch cannot be used with --copy. Watch mode re-packs on every change, which would repeatedly overwrite the clipboard.',
@@ -291,6 +300,10 @@ export const runCli = async (directories: string[], cwd: string, options: CliOpt
291300
// Validate --watch conflicts early, before log level changes can suppress error messages
292301
validateWatchOptions(directories, options);
293302

303+
if (options.stdin && options.stdinContent) {
304+
throw new RepomixError('--stdin cannot be used with --stdin-content. Both options consume standard input.');
305+
}
306+
294307
// Set log level based on verbose and quiet flags
295308
if (options.quiet) {
296309
logger.setLogLevel(repomixLogLevels.SILENT);

src/cli/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface CliOptions extends OptionValues {
3636
dotIgnore?: boolean;
3737
defaultPatterns?: boolean;
3838
stdin?: boolean;
39+
stdinContent?: boolean | string;
3940

4041
// Remote Repository Options
4142
remote?: string;

src/config/configSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export const repomixConfigCliSchema = v.intersect([
136136
output: v.optional(
137137
v.object({
138138
stdout: v.optional(v.boolean()),
139+
stdinContent: v.optional(v.string()),
139140
}),
140141
),
141142
skillGenerate: v.optional(v.union([v.string(), v.boolean()])),

src/core/file/fileStdin.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,42 @@ export const readFilePathsFromStdin = async (
114114
throw new RepomixError('An unexpected error occurred while reading from stdin.');
115115
}
116116
};
117+
118+
/**
119+
* Reads arbitrary text content from stdin, preserving blank lines.
120+
*/
121+
export const readContentFromStdin = async (
122+
deps: StdinDependencies = {
123+
stdin: process.stdin,
124+
createReadlineInterface: readline.createInterface,
125+
},
126+
): Promise<string> => {
127+
logger.trace('Reading content from stdin...');
128+
129+
try {
130+
const { stdin } = deps;
131+
132+
if (stdin.isTTY) {
133+
throw new RepomixError(
134+
'No data provided via stdin. Please pipe content to repomix when using --stdin-content flag.',
135+
);
136+
}
137+
138+
const chunks: Buffer[] = [];
139+
for await (const chunk of stdin as Readable) {
140+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
141+
}
142+
143+
return Buffer.concat(chunks).toString('utf8');
144+
} catch (error) {
145+
if (error instanceof RepomixError) {
146+
throw error;
147+
}
148+
149+
if (error instanceof Error) {
150+
throw new RepomixError(`Failed to read content from stdin: ${error.message}`);
151+
}
152+
153+
throw new RepomixError('An unexpected error occurred while reading content from stdin.');
154+
}
155+
};

src/core/output/outputGenerate.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const createRenderContext = (outputGeneratorContext: OutputGeneratorConte
8686
),
8787
summaryNotes: generateSummaryNotes(outputGeneratorContext.config),
8888
headerText: outputGeneratorContext.config.output.headerText,
89+
stdinContent: outputGeneratorContext.config.output.stdinContent,
8990
instruction: outputGeneratorContext.instruction,
9091
treeString: outputGeneratorContext.treeString,
9192
processedFiles: outputGeneratorContext.processedFiles,
@@ -123,6 +124,7 @@ const generateParsableXmlOutput = async (renderContext: RenderContext): Promise<
123124
}
124125
: undefined,
125126
user_provided_header: renderContext.headerText,
127+
stdin_content: renderContext.stdinContent,
126128
directory_structure: renderContext.directoryStructureEnabled ? renderContext.treeString : undefined,
127129
files: renderContext.filesEnabled
128130
? {
@@ -175,6 +177,9 @@ const generateParsableJsonOutput = async (renderContext: RenderContext): Promise
175177
...(renderContext.headerText && {
176178
userProvidedHeader: renderContext.headerText,
177179
}),
180+
...(renderContext.stdinContent && {
181+
stdinContent: renderContext.stdinContent,
182+
}),
178183
...(renderContext.directoryStructureEnabled && {
179184
directoryStructure: renderContext.treeString,
180185
}),

src/core/output/outputGeneratorTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface RenderContext {
2020
readonly summaryUsageGuidelines: string;
2121
readonly summaryNotes: string;
2222
readonly headerText: string | undefined;
23+
readonly stdinContent?: string;
2324
readonly instruction: string;
2425
readonly treeString: string;
2526
readonly processedFiles: ReadonlyArray<ProcessedFile>;

src/core/output/outputStyles/markdownStyle.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export const getMarkdownTemplate = () => {
3030
# User Provided Header
3131
{{{headerText}}}
3232
33+
{{/if}}
34+
{{#if stdinContent}}
35+
# Standard Input Content
36+
{{{stdinContent}}}
37+
3338
{{/if}}
3439
{{#if directoryStructureEnabled}}
3540
# Directory Structure

src/core/output/outputStyles/plainStyle.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ User Provided Header
4040
${PLAIN_LONG_SEPARATOR}
4141
{{{headerText}}}
4242
43+
{{/if}}
44+
{{#if stdinContent}}
45+
${PLAIN_LONG_SEPARATOR}
46+
Standard Input Content
47+
${PLAIN_LONG_SEPARATOR}
48+
{{{stdinContent}}}
49+
4350
{{/if}}
4451
{{#if directoryStructureEnabled}}
4552
${PLAIN_LONG_SEPARATOR}

0 commit comments

Comments
 (0)