githrun

A CLI tool to run Python scrip...
Log | Files | Refs | README | LICENSE

readline.d.ts (25714B)


      1 /**
      2  * The `readline` module provides an interface for reading data from a `Readable` stream (such as `process.stdin`) one line at a time. It can be accessed
      3  * using:
      4  *
      5  * ```js
      6  * import readline from 'node:readline';
      7  * ```
      8  *
      9  * The following simple example illustrates the basic use of the `readline` module.
     10  *
     11  * ```js
     12  * import readline from 'node:readline';
     13  *
     14  * const rl = readline.createInterface({
     15  *   input: process.stdin,
     16  *   output: process.stdout
     17  * });
     18  *
     19  * rl.question('What do you think of Node.js? ', (answer) => {
     20  *   // TODO: Log the answer in a database
     21  *   console.log(`Thank you for your valuable feedback: ${answer}`);
     22  *
     23  *   rl.close();
     24  * });
     25  * ```
     26  *
     27  * Once this code is invoked, the Node.js application will not terminate until the`readline.Interface` is closed because the interface waits for data to be
     28  * received on the `input` stream.
     29  * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/readline.js)
     30  */
     31 declare module "readline" {
     32     import { Abortable, EventEmitter } from "node:events";
     33     interface Key {
     34         sequence?: string | undefined;
     35         name?: string | undefined;
     36         ctrl?: boolean | undefined;
     37         meta?: boolean | undefined;
     38         shift?: boolean | undefined;
     39     }
     40     /**
     41      * Instances of the `readline.Interface` class are constructed using the`readline.createInterface()` method. Every instance is associated with a
     42      * single `input` `Readable` stream and a single `output` `Writable` stream.
     43      * The `output` stream is used to print prompts for user input that arrives on,
     44      * and is read from, the `input` stream.
     45      * @since v0.1.104
     46      */
     47     class Interface extends EventEmitter {
     48         readonly terminal: boolean;
     49         /**
     50          * The current input data being processed by node.
     51          *
     52          * This can be used when collecting input from a TTY stream to retrieve the
     53          * current value that has been processed thus far, prior to the `line` event
     54          * being emitted. Once the `line` event has been emitted, this property will
     55          * be an empty string.
     56          *
     57          * Be aware that modifying the value during the instance runtime may have
     58          * unintended consequences if `rl.cursor` is not also controlled.
     59          *
     60          * **If not using a TTY stream for input, use the `'line'` event.**
     61          *
     62          * One possible use case would be as follows:
     63          *
     64          * ```js
     65          * const values = ['lorem ipsum', 'dolor sit amet'];
     66          * const rl = readline.createInterface(process.stdin);
     67          * const showResults = debounce(() => {
     68          *   console.log(
     69          *     '\n',
     70          *     values.filter((val) => val.startsWith(rl.line)).join(' ')
     71          *   );
     72          * }, 300);
     73          * process.stdin.on('keypress', (c, k) => {
     74          *   showResults();
     75          * });
     76          * ```
     77          * @since v0.1.98
     78          */
     79         readonly line: string;
     80         /**
     81          * The cursor position relative to `rl.line`.
     82          *
     83          * This will track where the current cursor lands in the input string, when
     84          * reading input from a TTY stream. The position of cursor determines the
     85          * portion of the input string that will be modified as input is processed,
     86          * as well as the column where the terminal caret will be rendered.
     87          * @since v0.1.98
     88          */
     89         readonly cursor: number;
     90         /**
     91          * NOTE: According to the documentation:
     92          *
     93          * > Instances of the `readline.Interface` class are constructed using the
     94          * > `readline.createInterface()` method.
     95          *
     96          * @see https://nodejs.org/dist/latest-v16.x/docs/api/readline.html#readline_class_interface
     97          */
     98         protected constructor(
     99             input: NodeJS.ReadableStream,
    100             output?: NodeJS.WritableStream,
    101             completer?: Completer | AsyncCompleter,
    102             terminal?: boolean,
    103         );
    104         /**
    105          * NOTE: According to the documentation:
    106          *
    107          * > Instances of the `readline.Interface` class are constructed using the
    108          * > `readline.createInterface()` method.
    109          *
    110          * @see https://nodejs.org/dist/latest-v16.x/docs/api/readline.html#readline_class_interface
    111          */
    112         protected constructor(options: ReadLineOptions);
    113         /**
    114          * The `rl.getPrompt()` method returns the current prompt used by `rl.prompt()`.
    115          * @since v15.3.0
    116          * @return the current prompt string
    117          */
    118         getPrompt(): string;
    119         /**
    120          * The `rl.setPrompt()` method sets the prompt that will be written to `output`whenever `rl.prompt()` is called.
    121          * @since v0.1.98
    122          */
    123         setPrompt(prompt: string): void;
    124         /**
    125          * The `rl.prompt()` method writes the `readline.Interface` instances configured`prompt` to a new line in `output` in order to provide a user with a new
    126          * location at which to provide input.
    127          *
    128          * When called, `rl.prompt()` will resume the `input` stream if it has been
    129          * paused.
    130          *
    131          * If the `readline.Interface` was created with `output` set to `null` or`undefined` the prompt is not written.
    132          * @since v0.1.98
    133          * @param preserveCursor If `true`, prevents the cursor placement from being reset to `0`.
    134          */
    135         prompt(preserveCursor?: boolean): void;
    136         /**
    137          * The `rl.question()` method displays the `query` by writing it to the `output`,
    138          * waits for user input to be provided on `input`, then invokes the `callback`function passing the provided input as the first argument.
    139          *
    140          * When called, `rl.question()` will resume the `input` stream if it has been
    141          * paused.
    142          *
    143          * If the `readline.Interface` was created with `output` set to `null` or`undefined` the `query` is not written.
    144          *
    145          * The `callback` function passed to `rl.question()` does not follow the typical
    146          * pattern of accepting an `Error` object or `null` as the first argument.
    147          * The `callback` is called with the provided answer as the only argument.
    148          *
    149          * Example usage:
    150          *
    151          * ```js
    152          * rl.question('What is your favorite food? ', (answer) => {
    153          *   console.log(`Oh, so your favorite food is ${answer}`);
    154          * });
    155          * ```
    156          *
    157          * Using an `AbortController` to cancel a question.
    158          *
    159          * ```js
    160          * const ac = new AbortController();
    161          * const signal = ac.signal;
    162          *
    163          * rl.question('What is your favorite food? ', { signal }, (answer) => {
    164          *   console.log(`Oh, so your favorite food is ${answer}`);
    165          * });
    166          *
    167          * signal.addEventListener('abort', () => {
    168          *   console.log('The food question timed out');
    169          * }, { once: true });
    170          *
    171          * setTimeout(() => ac.abort(), 10000);
    172          * ```
    173          *
    174          * If this method is invoked as it's util.promisify()ed version, it returns a
    175          * Promise that fulfills with the answer. If the question is canceled using
    176          * an `AbortController` it will reject with an `AbortError`.
    177          *
    178          * ```js
    179          * import util from 'node:util';
    180          * const question = util.promisify(rl.question).bind(rl);
    181          *
    182          * async function questionExample() {
    183          *   try {
    184          *     const answer = await question('What is you favorite food? ');
    185          *     console.log(`Oh, so your favorite food is ${answer}`);
    186          *   } catch (err) {
    187          *     console.error('Question rejected', err);
    188          *   }
    189          * }
    190          * questionExample();
    191          * ```
    192          * @since v0.3.3
    193          * @param query A statement or query to write to `output`, prepended to the prompt.
    194          * @param callback A callback function that is invoked with the user's input in response to the `query`.
    195          */
    196         question(query: string, callback: (answer: string) => void): void;
    197         question(query: string, options: Abortable, callback: (answer: string) => void): void;
    198         /**
    199          * The `rl.pause()` method pauses the `input` stream, allowing it to be resumed
    200          * later if necessary.
    201          *
    202          * Calling `rl.pause()` does not immediately pause other events (including`'line'`) from being emitted by the `readline.Interface` instance.
    203          * @since v0.3.4
    204          */
    205         pause(): this;
    206         /**
    207          * The `rl.resume()` method resumes the `input` stream if it has been paused.
    208          * @since v0.3.4
    209          */
    210         resume(): this;
    211         /**
    212          * The `rl.close()` method closes the `readline.Interface` instance and
    213          * relinquishes control over the `input` and `output` streams. When called,
    214          * the `'close'` event will be emitted.
    215          *
    216          * Calling `rl.close()` does not immediately stop other events (including `'line'`)
    217          * from being emitted by the `readline.Interface` instance.
    218          * @since v0.1.98
    219          */
    220         close(): void;
    221         /**
    222          * The `rl.write()` method will write either `data` or a key sequence identified
    223          * by `key` to the `output`. The `key` argument is supported only if `output` is
    224          * a `TTY` text terminal. See `TTY keybindings` for a list of key
    225          * combinations.
    226          *
    227          * If `key` is specified, `data` is ignored.
    228          *
    229          * When called, `rl.write()` will resume the `input` stream if it has been
    230          * paused.
    231          *
    232          * If the `readline.Interface` was created with `output` set to `null` or`undefined` the `data` and `key` are not written.
    233          *
    234          * ```js
    235          * rl.write('Delete this!');
    236          * // Simulate Ctrl+U to delete the line written previously
    237          * rl.write(null, { ctrl: true, name: 'u' });
    238          * ```
    239          *
    240          * The `rl.write()` method will write the data to the `readline` `Interface`'s`input`_as if it were provided by the user_.
    241          * @since v0.1.98
    242          */
    243         write(data: string | Buffer, key?: Key): void;
    244         write(data: undefined | null | string | Buffer, key: Key): void;
    245         /**
    246          * Returns the real position of the cursor in relation to the input
    247          * prompt + string. Long input (wrapping) strings, as well as multiple
    248          * line prompts are included in the calculations.
    249          * @since v13.5.0, v12.16.0
    250          */
    251         getCursorPos(): CursorPos;
    252         /**
    253          * events.EventEmitter
    254          * 1. close
    255          * 2. line
    256          * 3. pause
    257          * 4. resume
    258          * 5. SIGCONT
    259          * 6. SIGINT
    260          * 7. SIGTSTP
    261          * 8. history
    262          */
    263         addListener(event: string, listener: (...args: any[]) => void): this;
    264         addListener(event: "close", listener: () => void): this;
    265         addListener(event: "line", listener: (input: string) => void): this;
    266         addListener(event: "pause", listener: () => void): this;
    267         addListener(event: "resume", listener: () => void): this;
    268         addListener(event: "SIGCONT", listener: () => void): this;
    269         addListener(event: "SIGINT", listener: () => void): this;
    270         addListener(event: "SIGTSTP", listener: () => void): this;
    271         addListener(event: "history", listener: (history: string[]) => void): this;
    272         emit(event: string | symbol, ...args: any[]): boolean;
    273         emit(event: "close"): boolean;
    274         emit(event: "line", input: string): boolean;
    275         emit(event: "pause"): boolean;
    276         emit(event: "resume"): boolean;
    277         emit(event: "SIGCONT"): boolean;
    278         emit(event: "SIGINT"): boolean;
    279         emit(event: "SIGTSTP"): boolean;
    280         emit(event: "history", history: string[]): boolean;
    281         on(event: string, listener: (...args: any[]) => void): this;
    282         on(event: "close", listener: () => void): this;
    283         on(event: "line", listener: (input: string) => void): this;
    284         on(event: "pause", listener: () => void): this;
    285         on(event: "resume", listener: () => void): this;
    286         on(event: "SIGCONT", listener: () => void): this;
    287         on(event: "SIGINT", listener: () => void): this;
    288         on(event: "SIGTSTP", listener: () => void): this;
    289         on(event: "history", listener: (history: string[]) => void): this;
    290         once(event: string, listener: (...args: any[]) => void): this;
    291         once(event: "close", listener: () => void): this;
    292         once(event: "line", listener: (input: string) => void): this;
    293         once(event: "pause", listener: () => void): this;
    294         once(event: "resume", listener: () => void): this;
    295         once(event: "SIGCONT", listener: () => void): this;
    296         once(event: "SIGINT", listener: () => void): this;
    297         once(event: "SIGTSTP", listener: () => void): this;
    298         once(event: "history", listener: (history: string[]) => void): this;
    299         prependListener(event: string, listener: (...args: any[]) => void): this;
    300         prependListener(event: "close", listener: () => void): this;
    301         prependListener(event: "line", listener: (input: string) => void): this;
    302         prependListener(event: "pause", listener: () => void): this;
    303         prependListener(event: "resume", listener: () => void): this;
    304         prependListener(event: "SIGCONT", listener: () => void): this;
    305         prependListener(event: "SIGINT", listener: () => void): this;
    306         prependListener(event: "SIGTSTP", listener: () => void): this;
    307         prependListener(event: "history", listener: (history: string[]) => void): this;
    308         prependOnceListener(event: string, listener: (...args: any[]) => void): this;
    309         prependOnceListener(event: "close", listener: () => void): this;
    310         prependOnceListener(event: "line", listener: (input: string) => void): this;
    311         prependOnceListener(event: "pause", listener: () => void): this;
    312         prependOnceListener(event: "resume", listener: () => void): this;
    313         prependOnceListener(event: "SIGCONT", listener: () => void): this;
    314         prependOnceListener(event: "SIGINT", listener: () => void): this;
    315         prependOnceListener(event: "SIGTSTP", listener: () => void): this;
    316         prependOnceListener(event: "history", listener: (history: string[]) => void): this;
    317         [Symbol.asyncIterator](): NodeJS.AsyncIterator<string>;
    318     }
    319     type ReadLine = Interface; // type forwarded for backwards compatibility
    320     type Completer = (line: string) => CompleterResult;
    321     type AsyncCompleter = (line: string, callback: (err?: null | Error, result?: CompleterResult) => void) => void;
    322     type CompleterResult = [string[], string];
    323     interface ReadLineOptions {
    324         /**
    325          * The [`Readable`](https://nodejs.org/docs/latest-v16.x/api/stream.html#readable-streams) stream to listen to
    326          */
    327         input: NodeJS.ReadableStream;
    328         /**
    329          * The [`Writable`](https://nodejs.org/docs/latest-v16.x/api/stream.html#writable-streams) stream to write readline data to.
    330          */
    331         output?: NodeJS.WritableStream | undefined;
    332         /**
    333          * An optional function used for Tab autocompletion.
    334          */
    335         completer?: Completer | AsyncCompleter | undefined;
    336         /**
    337          * `true` if the `input` and `output` streams should be treated like a TTY,
    338          * and have ANSI/VT100 escape codes written to it.
    339          * Default: checking `isTTY` on the `output` stream upon instantiation.
    340          */
    341         terminal?: boolean | undefined;
    342         /**
    343          * Initial list of history lines.
    344          * This option makes sense only if `terminal` is set to `true` by the user or by an internal `output` check,
    345          * otherwise the history caching mechanism is not initialized at all.
    346          * @default []
    347          */
    348         history?: string[] | undefined;
    349         /**
    350          * Maximum number of history lines retained.
    351          * To disable the history set this value to `0`.
    352          * This option makes sense only if `terminal` is set to `true` by the user or by an internal `output` check,
    353          * otherwise the history caching mechanism is not initialized at all.
    354          * @default 30
    355          */
    356         historySize?: number | undefined;
    357         /**
    358          * If `true`, when a new input line added to the history list duplicates an older one,
    359          * this removes the older line from the list.
    360          * @default false
    361          */
    362         removeHistoryDuplicates?: boolean | undefined;
    363         /**
    364          * The prompt string to use.
    365          * @default "> "
    366          */
    367         prompt?: string | undefined;
    368         /**
    369          * If the delay between `\r` and `\n` exceeds `crlfDelay` milliseconds,
    370          * both `\r` and `\n` will be treated as separate end-of-line input.
    371          * `crlfDelay` will be coerced to a number no less than `100`.
    372          * It can be set to `Infinity`, in which case
    373          * `\r` followed by `\n` will always be considered a single newline
    374          * (which may be reasonable for [reading files](https://nodejs.org/docs/latest-v16.x/api/readline.html#example-read-file-stream-line-by-line) with `\r\n` line delimiter).
    375          * @default 100
    376          */
    377         crlfDelay?: number | undefined;
    378         /**
    379          * The duration `readline` will wait for a character
    380          * (when reading an ambiguous key sequence in milliseconds
    381          * one that can both form a complete key sequence using the input read so far
    382          * and can take additional input to complete a longer key sequence).
    383          * @default 500
    384          */
    385         escapeCodeTimeout?: number | undefined;
    386         /**
    387          * The number of spaces a tab is equal to (minimum 1).
    388          * @default 8
    389          */
    390         tabSize?: number | undefined;
    391         /**
    392          * Allows closing the interface using an AbortSignal.
    393          * Aborting the signal will internally call `close` on the interface.
    394          */
    395         signal?: AbortSignal | undefined;
    396     }
    397     /**
    398      * The `readline.createInterface()` method creates a new `readline.Interface` instance.
    399      *
    400      * ```js
    401      * import readline from 'node:readline';
    402      * const rl = readline.createInterface({
    403      *   input: process.stdin,
    404      *   output: process.stdout
    405      * });
    406      * ```
    407      *
    408      * Once the `readline.Interface` instance is created, the most common case is to
    409      * listen for the `'line'` event:
    410      *
    411      * ```js
    412      * rl.on('line', (line) => {
    413      *   console.log(`Received: ${line}`);
    414      * });
    415      * ```
    416      *
    417      * If `terminal` is `true` for this instance then the `output` stream will get
    418      * the best compatibility if it defines an `output.columns` property and emits
    419      * a `'resize'` event on the `output` if or when the columns ever change
    420      * (`process.stdout` does this automatically when it is a TTY).
    421      *
    422      * When creating a `readline.Interface` using `stdin` as input, the program
    423      * will not terminate until it receives `EOF` (Ctrl+D on
    424      * Linux/macOS, Ctrl+Z followed by Return on
    425      * Windows).
    426      * If you want your application to exit without waiting for user input, you can `unref()` the standard input stream:
    427      *
    428      * ```js
    429      * process.stdin.unref();
    430      * ```
    431      * @since v0.1.98
    432      */
    433     function createInterface(
    434         input: NodeJS.ReadableStream,
    435         output?: NodeJS.WritableStream,
    436         completer?: Completer | AsyncCompleter,
    437         terminal?: boolean,
    438     ): Interface;
    439     function createInterface(options: ReadLineOptions): Interface;
    440     /**
    441      * The `readline.emitKeypressEvents()` method causes the given `Readable` stream to begin emitting `'keypress'` events corresponding to received input.
    442      *
    443      * Optionally, `interface` specifies a `readline.Interface` instance for which
    444      * autocompletion is disabled when copy-pasted input is detected.
    445      *
    446      * If the `stream` is a `TTY`, then it must be in raw mode.
    447      *
    448      * This is automatically called by any readline instance on its `input` if the`input` is a terminal. Closing the `readline` instance does not stop
    449      * the `input` from emitting `'keypress'` events.
    450      *
    451      * ```js
    452      * readline.emitKeypressEvents(process.stdin);
    453      * if (process.stdin.isTTY)
    454      *   process.stdin.setRawMode(true);
    455      * ```
    456      * @since v0.7.7
    457      */
    458     function emitKeypressEvents(stream: NodeJS.ReadableStream, readlineInterface?: Interface): void;
    459     type Direction = -1 | 0 | 1;
    460     interface CursorPos {
    461         rows: number;
    462         cols: number;
    463     }
    464     /**
    465      * The `readline.clearLine()` method clears current line of given `TTY` stream
    466      * in a specified direction identified by `dir`.
    467      * @since v0.7.7
    468      * @param callback Invoked once the operation completes.
    469      * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`.
    470      */
    471     function clearLine(stream: NodeJS.WritableStream, dir: Direction, callback?: () => void): boolean;
    472     /**
    473      * The `readline.clearScreenDown()` method clears the given `TTY` stream from
    474      * the current position of the cursor down.
    475      * @since v0.7.7
    476      * @param callback Invoked once the operation completes.
    477      * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`.
    478      */
    479     function clearScreenDown(stream: NodeJS.WritableStream, callback?: () => void): boolean;
    480     /**
    481      * The `readline.cursorTo()` method moves cursor to the specified position in a
    482      * given `TTY` `stream`.
    483      * @since v0.7.7
    484      * @param callback Invoked once the operation completes.
    485      * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`.
    486      */
    487     function cursorTo(stream: NodeJS.WritableStream, x: number, y?: number, callback?: () => void): boolean;
    488     /**
    489      * The `readline.moveCursor()` method moves the cursor _relative_ to its current
    490      * position in a given `TTY` `stream`.
    491      *
    492      * ## Example: Tiny CLI
    493      *
    494      * The following example illustrates the use of `readline.Interface` class to
    495      * implement a small command-line interface:
    496      *
    497      * ```js
    498      * import readline from 'node:readline';
    499      * const rl = readline.createInterface({
    500      *   input: process.stdin,
    501      *   output: process.stdout,
    502      *   prompt: 'OHAI> '
    503      * });
    504      *
    505      * rl.prompt();
    506      *
    507      * rl.on('line', (line) => {
    508      *   switch (line.trim()) {
    509      *     case 'hello':
    510      *       console.log('world!');
    511      *       break;
    512      *     default:
    513      *       console.log(`Say what? I might have heard '${line.trim()}'`);
    514      *       break;
    515      *   }
    516      *   rl.prompt();
    517      * }).on('close', () => {
    518      *   console.log('Have a great day!');
    519      *   process.exit(0);
    520      * });
    521      * ```
    522      *
    523      * ## Example: Read file stream line-by-Line
    524      *
    525      * A common use case for `readline` is to consume an input file one line at a
    526      * time. The easiest way to do so is leveraging the `fs.ReadStream` API as
    527      * well as a `for await...of` loop:
    528      *
    529      * ```js
    530      * import fs from 'node:fs';
    531      * import readline from 'node:readline';
    532      *
    533      * async function processLineByLine() {
    534      *   const fileStream = fs.createReadStream('input.txt');
    535      *
    536      *   const rl = readline.createInterface({
    537      *     input: fileStream,
    538      *     crlfDelay: Infinity
    539      *   });
    540      *   // Note: we use the crlfDelay option to recognize all instances of CR LF
    541      *   // ('\r\n') in input.txt as a single line break.
    542      *
    543      *   for await (const line of rl) {
    544      *     // Each line in input.txt will be successively available here as `line`.
    545      *     console.log(`Line from file: ${line}`);
    546      *   }
    547      * }
    548      *
    549      * processLineByLine();
    550      * ```
    551      *
    552      * Alternatively, one could use the `'line'` event:
    553      *
    554      * ```js
    555      * import fs from 'node:fs';
    556      * import readline from 'node:readline';
    557      *
    558      * const rl = readline.createInterface({
    559      *   input: fs.createReadStream('sample.txt'),
    560      *   crlfDelay: Infinity
    561      * });
    562      *
    563      * rl.on('line', (line) => {
    564      *   console.log(`Line from file: ${line}`);
    565      * });
    566      * ```
    567      *
    568      * Currently, `for await...of` loop can be a bit slower. If `async` / `await`flow and speed are both essential, a mixed approach can be applied:
    569      *
    570      * ```js
    571      * import { once } from 'node:events';
    572      * import { createReadStream } from 'node:fs';
    573      * import { createInterface } from 'node:readline';
    574      *
    575      * (async function processLineByLine() {
    576      *   try {
    577      *     const rl = createInterface({
    578      *       input: createReadStream('big-file.txt'),
    579      *       crlfDelay: Infinity
    580      *     });
    581      *
    582      *     rl.on('line', (line) => {
    583      *       // Process the line.
    584      *     });
    585      *
    586      *     await once(rl, 'close');
    587      *
    588      *     console.log('File processed.');
    589      *   } catch (err) {
    590      *     console.error(err);
    591      *   }
    592      * })();
    593      * ```
    594      * @since v0.7.7
    595      * @param callback Invoked once the operation completes.
    596      * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`.
    597      */
    598     function moveCursor(stream: NodeJS.WritableStream, dx: number, dy: number, callback?: () => void): boolean;
    599 }
    600 declare module "node:readline" {
    601     export * from "readline";
    602 }
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror