runbook-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.runbook ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "dev": "npm run dev",
3
+ "build": "npm run build ; echo Build complete!"
4
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Munene Mwirigi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # runbook
2
+
3
+ Remember project commands. Run them from anywhere.
4
+
5
+ ## The Problem
6
+
7
+ Every project runs differently. You can't remember. You check the README every time.
8
+
9
+ ```bash
10
+ # Is it...
11
+ npm run dev
12
+ # or
13
+ npm start
14
+ # or
15
+ pnpm dev
16
+ # or
17
+ python manage.py runserver
18
+ # or
19
+ go run main.go
20
+ ```
21
+
22
+ **Stop guessing. Stop checking.**
23
+
24
+ ## The Solution
25
+
26
+ Set it once. Run it forever.
27
+
28
+ ```bash
29
+ # Set commands
30
+ runbook set dev "npm run dev"
31
+ runbook set test "npm test"
32
+ runbook set build "npm run build"
33
+
34
+ # Run from anywhere in the project
35
+ runbook dev
36
+
37
+ # That's it.
38
+ ```
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ npm install -g runbook-cli
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Set Commands
49
+
50
+ ```bash
51
+ # Basic
52
+ runbook set dev "npm run dev"
53
+
54
+ # With install
55
+ runbook set dev "npm install && npm run dev"
56
+
57
+ # Backend
58
+ runbook set start "python manage.py runserver"
59
+
60
+ # Docker
61
+ runbook set up "docker-compose up -d"
62
+
63
+ # Multi-step
64
+ runbook set deploy "npm run build && npm run test && git push"
65
+ ```
66
+
67
+ ### Run Commands
68
+
69
+ ```bash
70
+ # Run a command
71
+ runbook dev
72
+
73
+ # Works from any subfolder
74
+ cd src/components
75
+ runbook dev # Still works!
76
+
77
+ # Short alias
78
+ rb dev
79
+ ```
80
+
81
+ ### List Commands
82
+
83
+ ```bash
84
+ # See all commands
85
+ runbook list
86
+
87
+ # Or just
88
+ runbook
89
+ ```
90
+
91
+ ### Delete Commands
92
+
93
+ ```bash
94
+ runbook delete dev
95
+ # or
96
+ runbook rm dev
97
+ ```
98
+
99
+ ### Project Info
100
+
101
+ ```bash
102
+ runbook info
103
+ ```
104
+
105
+ ## How It Works
106
+
107
+ - Finds your git root automatically
108
+ - Stores commands in `.runbook` in project root
109
+ - Works from any subfolder
110
+ - Team shares via git (commit `.runbook`)
111
+
112
+ ## Example Workflow
113
+
114
+ ```bash
115
+ # New project
116
+ cd my-project
117
+ runbook set dev "npm install && npm run dev"
118
+ runbook set test "npm test"
119
+
120
+ # 3 months later, can't remember
121
+ runbook
122
+ # Shows: dev, test
123
+
124
+ runbook dev
125
+ # Runs instantly
126
+
127
+ # In subfolder
128
+ cd src/pages
129
+ runbook dev
130
+ # Still works!
131
+ ```
132
+
133
+ ## Team Usage
134
+
135
+ Commit `.runbook` to git:
136
+
137
+ ```bash
138
+ git add .runbook
139
+ git commit -m "Add runbook commands"
140
+ ```
141
+
142
+ Now everyone runs:
143
+ ```bash
144
+ runbook dev # Works for everyone
145
+ ```
146
+
147
+ ## Commands Reference
148
+
149
+ ```bash
150
+ runbook set <name> <command> # Set a command
151
+ runbook <name> # Run a command
152
+ runbook list # List all commands
153
+ runbook delete <name> # Delete a command
154
+ runbook info # Show project info
155
+ runbook # Show available commands
156
+ rb # Short alias
157
+ ```
158
+
159
+ ## Examples
160
+
161
+ ### Node.js
162
+ ```bash
163
+ runbook set dev "npm install && npm run dev"
164
+ runbook set build "npm run build"
165
+ runbook set test "npm test"
166
+ ```
167
+
168
+ ### Python/Django
169
+ ```bash
170
+ runbook set dev "pip install -r requirements.txt && python manage.py runserver"
171
+ runbook set migrate "python manage.py migrate"
172
+ runbook set shell "python manage.py shell"
173
+ ```
174
+
175
+ ### Go
176
+ ```bash
177
+ runbook set dev "go run main.go"
178
+ runbook set build "go build -o bin/app"
179
+ runbook set test "go test ./..."
180
+ ```
181
+
182
+ ### Docker
183
+ ```bash
184
+ runbook set up "docker-compose up -d"
185
+ runbook set down "docker-compose down"
186
+ runbook set logs "docker-compose logs -f"
187
+ ```
188
+
189
+ ### Full Stack
190
+ ```bash
191
+ runbook set dev "docker-compose up -d && npm run dev"
192
+ runbook set backend "cd backend && python manage.py runserver"
193
+ runbook set frontend "cd frontend && npm start"
194
+ ```
195
+
196
+ ## Why runbook?
197
+
198
+ - **No more README diving** - Commands right there
199
+ - **Works everywhere** - Any subfolder, always works
200
+ - **Team onboarding** - New dev runs `runbook dev`, done
201
+ - **Cross-language** - Node, Python, Go, Rust - doesn't matter
202
+ - **Zero config** - Just set and run
203
+
204
+ ## Data Storage
205
+
206
+ Commands stored in `.runbook` in project root (JSON format).
207
+
208
+ ```json
209
+ {
210
+ "dev": "npm run dev",
211
+ "test": "npm test",
212
+ "build": "npm run build"
213
+ }
214
+ ```
215
+
216
+ ## License
217
+
218
+ MIT
219
+
220
+ ## Author
221
+
222
+ Built by [Brian Mwirigi](https://github.com/brian-mwirigi)
223
+
224
+ ---
225
+
226
+ **Stop thinking. Start running.**
@@ -0,0 +1,11 @@
1
+ export interface RunbookCommands {
2
+ [key: string]: string;
3
+ }
4
+ export declare function getRunbookPath(): string;
5
+ export declare function loadCommands(): RunbookCommands;
6
+ export declare function saveCommands(commands: RunbookCommands): void;
7
+ export declare function setCommand(name: string, command: string): void;
8
+ export declare function getCommand(name: string): string | undefined;
9
+ export declare function deleteCommand(name: string): boolean;
10
+ export declare function listCommands(): RunbookCommands;
11
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAID,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED,wBAAgB,YAAY,IAAI,eAAe,CAa9C;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAG5D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQnD;AAED,wBAAgB,YAAY,IAAI,eAAe,CAE9C"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRunbookPath = getRunbookPath;
4
+ exports.loadCommands = loadCommands;
5
+ exports.saveCommands = saveCommands;
6
+ exports.setCommand = setCommand;
7
+ exports.getCommand = getCommand;
8
+ exports.deleteCommand = deleteCommand;
9
+ exports.listCommands = listCommands;
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const git_1 = require("./git");
13
+ const RUNBOOK_FILE = '.runbook';
14
+ function getRunbookPath() {
15
+ const projectRoot = (0, git_1.getProjectRoot)();
16
+ return (0, path_1.join)(projectRoot, RUNBOOK_FILE);
17
+ }
18
+ function loadCommands() {
19
+ const runbookPath = getRunbookPath();
20
+ if (!(0, fs_1.existsSync)(runbookPath)) {
21
+ return {};
22
+ }
23
+ try {
24
+ const content = (0, fs_1.readFileSync)(runbookPath, 'utf-8');
25
+ return JSON.parse(content);
26
+ }
27
+ catch (error) {
28
+ return {};
29
+ }
30
+ }
31
+ function saveCommands(commands) {
32
+ const runbookPath = getRunbookPath();
33
+ (0, fs_1.writeFileSync)(runbookPath, JSON.stringify(commands, null, 2), 'utf-8');
34
+ }
35
+ function setCommand(name, command) {
36
+ const commands = loadCommands();
37
+ commands[name] = command;
38
+ saveCommands(commands);
39
+ }
40
+ function getCommand(name) {
41
+ const commands = loadCommands();
42
+ return commands[name];
43
+ }
44
+ function deleteCommand(name) {
45
+ const commands = loadCommands();
46
+ if (!(name in commands)) {
47
+ return false;
48
+ }
49
+ delete commands[name];
50
+ saveCommands(commands);
51
+ return true;
52
+ }
53
+ function listCommands() {
54
+ return loadCommands();
55
+ }
56
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":";;AAUA,wCAGC;AAED,oCAaC;AAED,oCAGC;AAED,gCAIC;AAED,gCAGC;AAED,sCAQC;AAED,oCAEC;AA1DD,2BAA6D;AAC7D,+BAA4B;AAC5B,+BAAuC;AAMvC,MAAM,YAAY,GAAG,UAAU,CAAC;AAEhC,SAAgB,cAAc;IAC5B,MAAM,WAAW,GAAG,IAAA,oBAAc,GAAE,CAAC;IACrC,OAAO,IAAA,WAAI,EAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,YAAY;IAC1B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,IAAI,CAAC,IAAA,eAAU,EAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAA,iBAAY,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,QAAyB;IACpD,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,IAAA,kBAAa,EAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACzE,CAAC;AAED,SAAgB,UAAU,CAAC,IAAY,EAAE,OAAe;IACtD,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IACzB,YAAY,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC;AAED,SAAgB,UAAU,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,aAAa,CAAC,IAAY;IACxC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,CAAC,CAAC,IAAI,IAAI,QAAQ,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtB,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,YAAY;IAC1B,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function findGitRoot(startPath?: string): string | null;
2
+ export declare function getProjectRoot(): string;
3
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAIA,wBAAgB,WAAW,CAAC,SAAS,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAgB5E;AAED,wBAAgB,cAAc,IAAI,MAAM,CAQvC"}
package/dist/git.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findGitRoot = findGitRoot;
4
+ exports.getProjectRoot = getProjectRoot;
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ function findGitRoot(startPath = process.cwd()) {
8
+ let currentPath = startPath;
9
+ while (true) {
10
+ if ((0, fs_1.existsSync)((0, path_1.join)(currentPath, '.git'))) {
11
+ return currentPath;
12
+ }
13
+ const parentPath = (0, path_1.join)(currentPath, '..');
14
+ if (parentPath === currentPath) {
15
+ // Reached root without finding .git
16
+ return null;
17
+ }
18
+ currentPath = parentPath;
19
+ }
20
+ }
21
+ function getProjectRoot() {
22
+ const gitRoot = findGitRoot();
23
+ if (gitRoot) {
24
+ return gitRoot;
25
+ }
26
+ // Fallback to current directory if not a git repo
27
+ return process.cwd();
28
+ }
29
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":";;AAIA,kCAgBC;AAED,wCAQC;AA7BD,2BAAgC;AAChC,+BAA4B;AAE5B,SAAgB,WAAW,CAAC,YAAoB,OAAO,CAAC,GAAG,EAAE;IAC3D,IAAI,WAAW,GAAG,SAAS,CAAC;IAE5B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,MAAM,UAAU,GAAG,IAAA,WAAI,EAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;YAC/B,oCAAoC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,WAAW,GAAG,UAAU,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAgB,cAAc;IAC5B,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,kDAAkD;IAClD,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const cli_table3_1 = __importDefault(require("cli-table3"));
10
+ const child_process_1 = require("child_process");
11
+ const commands_1 = require("./commands");
12
+ const git_1 = require("./git");
13
+ const program = new commander_1.Command();
14
+ program
15
+ .name('runbook')
16
+ .description('Remember project commands. Run them from anywhere.')
17
+ .version('1.0.0');
18
+ // Set command
19
+ program
20
+ .command('set')
21
+ .description('Set a command for this project')
22
+ .argument('<name>', 'Command name (e.g., dev, test, build)')
23
+ .argument('<command>', 'Command to run (e.g., "npm run dev")')
24
+ .action((name, command) => {
25
+ (0, commands_1.setCommand)(name, command);
26
+ console.log(chalk_1.default.green(`\n✓ Set '${name}' → ${command}`));
27
+ console.log(chalk_1.default.gray(` Run with: runbook ${name}\n`));
28
+ });
29
+ // Delete command
30
+ program
31
+ .command('delete')
32
+ .alias('rm')
33
+ .description('Delete a command')
34
+ .argument('<name>', 'Command name to delete')
35
+ .action((name) => {
36
+ if ((0, commands_1.deleteCommand)(name)) {
37
+ console.log(chalk_1.default.green(`\n✓ Deleted '${name}'\n`));
38
+ }
39
+ else {
40
+ console.log(chalk_1.default.yellow(`\n⚠ Command '${name}' not found\n`));
41
+ }
42
+ });
43
+ // List commands
44
+ program
45
+ .command('list')
46
+ .alias('ls')
47
+ .description('List all commands for this project')
48
+ .action(() => {
49
+ const commands = (0, commands_1.listCommands)();
50
+ const entries = Object.entries(commands);
51
+ if (entries.length === 0) {
52
+ console.log(chalk_1.default.yellow('\nNo commands set for this project.'));
53
+ console.log(chalk_1.default.gray('Set one with: runbook set <name> <command>\n'));
54
+ return;
55
+ }
56
+ console.log(chalk_1.default.bold.cyan(`\nCommands for ${(0, git_1.getProjectRoot)()}\n`));
57
+ const table = new cli_table3_1.default({
58
+ head: [chalk_1.default.cyan.bold('Name'), chalk_1.default.cyan.bold('Command')],
59
+ style: { head: [], border: [] },
60
+ colWidths: [15, 65],
61
+ wordWrap: true,
62
+ });
63
+ for (const [name, command] of entries) {
64
+ table.push([chalk_1.default.green(name), chalk_1.default.white(command)]);
65
+ }
66
+ console.log(table.toString());
67
+ console.log(chalk_1.default.gray(`\nRun with: runbook <name>`));
68
+ console.log(chalk_1.default.gray(`Stored in: ${(0, commands_1.getRunbookPath)()}\n`));
69
+ });
70
+ // Info command
71
+ program
72
+ .command('info')
73
+ .description('Show project info')
74
+ .action(() => {
75
+ console.log(chalk_1.default.cyan('\nProject Info\n'));
76
+ console.log(chalk_1.default.white('Root: ') + chalk_1.default.gray((0, git_1.getProjectRoot)()));
77
+ console.log(chalk_1.default.white('Runbook: ') + chalk_1.default.gray((0, commands_1.getRunbookPath)()));
78
+ const commands = (0, commands_1.listCommands)();
79
+ console.log(chalk_1.default.white('Commands: ') + chalk_1.default.gray(Object.keys(commands).length.toString()));
80
+ console.log();
81
+ });
82
+ // Run command (default action)
83
+ program
84
+ .argument('[name]', 'Command name to run')
85
+ .action((name) => {
86
+ if (!name) {
87
+ // Show list if no command specified
88
+ const commands = (0, commands_1.listCommands)();
89
+ const entries = Object.entries(commands);
90
+ if (entries.length === 0) {
91
+ console.log(chalk_1.default.yellow('\nNo commands set for this project.'));
92
+ console.log(chalk_1.default.gray('Set one with: runbook set <name> <command>\n'));
93
+ return;
94
+ }
95
+ console.log(chalk_1.default.bold.cyan('\nAvailable commands:\n'));
96
+ for (const [cmdName] of entries) {
97
+ console.log(chalk_1.default.green(` runbook ${cmdName}`));
98
+ }
99
+ console.log();
100
+ return;
101
+ }
102
+ const command = (0, commands_1.getCommand)(name);
103
+ if (!command) {
104
+ console.log(chalk_1.default.red(`\n✗ Command '${name}' not found`));
105
+ console.log(chalk_1.default.gray('Set it with: runbook set ' + name + ' <command>\n'));
106
+ process.exit(1);
107
+ }
108
+ console.log(chalk_1.default.cyan(`\n→ Running: ${command}\n`));
109
+ // Run command in shell
110
+ const child = (0, child_process_1.spawn)(command, [], {
111
+ shell: true,
112
+ stdio: 'inherit',
113
+ cwd: (0, git_1.getProjectRoot)(),
114
+ });
115
+ child.on('exit', (code) => {
116
+ process.exit(code || 0);
117
+ });
118
+ });
119
+ program.parse();
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "runbook-cli",
3
+ "version": "1.0.0",
4
+ "description": "Remember project commands. Run them from anywhere.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "runbook": "./dist/index.js",
8
+ "rb": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepare": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "commands",
18
+ "project",
19
+ "cli",
20
+ "dev",
21
+ "workflow",
22
+ "automation"
23
+ ],
24
+ "author": "Brian Mwirigi",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "chalk": "^4.1.2",
28
+ "commander": "^11.1.0",
29
+ "cli-table3": "^0.6.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.10.6",
33
+ "tsx": "^4.7.0",
34
+ "typescript": "^5.3.3"
35
+ },
36
+ "engines": {
37
+ "node": ">=16.0.0"
38
+ }
39
+ }
@@ -0,0 +1,59 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getProjectRoot } from './git';
4
+
5
+ export interface RunbookCommands {
6
+ [key: string]: string;
7
+ }
8
+
9
+ const RUNBOOK_FILE = '.runbook';
10
+
11
+ export function getRunbookPath(): string {
12
+ const projectRoot = getProjectRoot();
13
+ return join(projectRoot, RUNBOOK_FILE);
14
+ }
15
+
16
+ export function loadCommands(): RunbookCommands {
17
+ const runbookPath = getRunbookPath();
18
+
19
+ if (!existsSync(runbookPath)) {
20
+ return {};
21
+ }
22
+
23
+ try {
24
+ const content = readFileSync(runbookPath, 'utf-8');
25
+ return JSON.parse(content);
26
+ } catch (error) {
27
+ return {};
28
+ }
29
+ }
30
+
31
+ export function saveCommands(commands: RunbookCommands): void {
32
+ const runbookPath = getRunbookPath();
33
+ writeFileSync(runbookPath, JSON.stringify(commands, null, 2), 'utf-8');
34
+ }
35
+
36
+ export function setCommand(name: string, command: string): void {
37
+ const commands = loadCommands();
38
+ commands[name] = command;
39
+ saveCommands(commands);
40
+ }
41
+
42
+ export function getCommand(name: string): string | undefined {
43
+ const commands = loadCommands();
44
+ return commands[name];
45
+ }
46
+
47
+ export function deleteCommand(name: string): boolean {
48
+ const commands = loadCommands();
49
+ if (!(name in commands)) {
50
+ return false;
51
+ }
52
+ delete commands[name];
53
+ saveCommands(commands);
54
+ return true;
55
+ }
56
+
57
+ export function listCommands(): RunbookCommands {
58
+ return loadCommands();
59
+ }
package/src/git.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ export function findGitRoot(startPath: string = process.cwd()): string | null {
6
+ let currentPath = startPath;
7
+
8
+ while (true) {
9
+ if (existsSync(join(currentPath, '.git'))) {
10
+ return currentPath;
11
+ }
12
+
13
+ const parentPath = join(currentPath, '..');
14
+ if (parentPath === currentPath) {
15
+ // Reached root without finding .git
16
+ return null;
17
+ }
18
+
19
+ currentPath = parentPath;
20
+ }
21
+ }
22
+
23
+ export function getProjectRoot(): string {
24
+ const gitRoot = findGitRoot();
25
+ if (gitRoot) {
26
+ return gitRoot;
27
+ }
28
+
29
+ // Fallback to current directory if not a git repo
30
+ return process.cwd();
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import Table from 'cli-table3';
6
+ import { spawn } from 'child_process';
7
+ import { setCommand, getCommand, deleteCommand, listCommands, getRunbookPath } from './commands';
8
+ import { getProjectRoot } from './git';
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('runbook')
14
+ .description('Remember project commands. Run them from anywhere.')
15
+ .version('1.0.0');
16
+
17
+ // Set command
18
+ program
19
+ .command('set')
20
+ .description('Set a command for this project')
21
+ .argument('<name>', 'Command name (e.g., dev, test, build)')
22
+ .argument('<command>', 'Command to run (e.g., "npm run dev")')
23
+ .action((name: string, command: string) => {
24
+ setCommand(name, command);
25
+ console.log(chalk.green(`\n✓ Set '${name}' → ${command}`));
26
+ console.log(chalk.gray(` Run with: runbook ${name}\n`));
27
+ });
28
+
29
+ // Delete command
30
+ program
31
+ .command('delete')
32
+ .alias('rm')
33
+ .description('Delete a command')
34
+ .argument('<name>', 'Command name to delete')
35
+ .action((name: string) => {
36
+ if (deleteCommand(name)) {
37
+ console.log(chalk.green(`\n✓ Deleted '${name}'\n`));
38
+ } else {
39
+ console.log(chalk.yellow(`\n⚠ Command '${name}' not found\n`));
40
+ }
41
+ });
42
+
43
+ // List commands
44
+ program
45
+ .command('list')
46
+ .alias('ls')
47
+ .description('List all commands for this project')
48
+ .action(() => {
49
+ const commands = listCommands();
50
+ const entries = Object.entries(commands);
51
+
52
+ if (entries.length === 0) {
53
+ console.log(chalk.yellow('\nNo commands set for this project.'));
54
+ console.log(chalk.gray('Set one with: runbook set <name> <command>\n'));
55
+ return;
56
+ }
57
+
58
+ console.log(chalk.bold.cyan(`\nCommands for ${getProjectRoot()}\n`));
59
+
60
+ const table = new Table({
61
+ head: [chalk.cyan.bold('Name'), chalk.cyan.bold('Command')],
62
+ style: { head: [], border: [] },
63
+ colWidths: [15, 65],
64
+ wordWrap: true,
65
+ });
66
+
67
+ for (const [name, command] of entries) {
68
+ table.push([chalk.green(name), chalk.white(command)]);
69
+ }
70
+
71
+ console.log(table.toString());
72
+ console.log(chalk.gray(`\nRun with: runbook <name>`));
73
+ console.log(chalk.gray(`Stored in: ${getRunbookPath()}\n`));
74
+ });
75
+
76
+ // Info command
77
+ program
78
+ .command('info')
79
+ .description('Show project info')
80
+ .action(() => {
81
+ console.log(chalk.cyan('\nProject Info\n'));
82
+ console.log(chalk.white('Root: ') + chalk.gray(getProjectRoot()));
83
+ console.log(chalk.white('Runbook: ') + chalk.gray(getRunbookPath()));
84
+
85
+ const commands = listCommands();
86
+ console.log(chalk.white('Commands: ') + chalk.gray(Object.keys(commands).length.toString()));
87
+ console.log();
88
+ });
89
+
90
+ // Run command (default action)
91
+ program
92
+ .argument('[name]', 'Command name to run')
93
+ .action((name?: string) => {
94
+ if (!name) {
95
+ // Show list if no command specified
96
+ const commands = listCommands();
97
+ const entries = Object.entries(commands);
98
+
99
+ if (entries.length === 0) {
100
+ console.log(chalk.yellow('\nNo commands set for this project.'));
101
+ console.log(chalk.gray('Set one with: runbook set <name> <command>\n'));
102
+ return;
103
+ }
104
+
105
+ console.log(chalk.bold.cyan('\nAvailable commands:\n'));
106
+ for (const [cmdName] of entries) {
107
+ console.log(chalk.green(` runbook ${cmdName}`));
108
+ }
109
+ console.log();
110
+ return;
111
+ }
112
+
113
+ const command = getCommand(name);
114
+
115
+ if (!command) {
116
+ console.log(chalk.red(`\n✗ Command '${name}' not found`));
117
+ console.log(chalk.gray('Set it with: runbook set ' + name + ' <command>\n'));
118
+ process.exit(1);
119
+ }
120
+
121
+ console.log(chalk.cyan(`\n→ Running: ${command}\n`));
122
+
123
+ // Run command in shell
124
+ const child = spawn(command, [], {
125
+ shell: true,
126
+ stdio: 'inherit',
127
+ cwd: getProjectRoot(),
128
+ });
129
+
130
+ child.on('exit', (code) => {
131
+ process.exit(code || 0);
132
+ });
133
+ });
134
+
135
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node"
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }