brady-cli 1.1.0 → 1.2.6

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/package.json CHANGED
@@ -1,30 +1,41 @@
1
1
  {
2
- "name": "brady-cli",
3
- "version": "1.1.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "typecheck": "tsc",
8
- "build": "esbuild src/index.ts --outfile=dist/index.js --bundle --platform=node --target=node18"
9
- },
10
- "bin": {
11
- "brady": "dist/index.js"
12
- },
13
- "author": "",
14
- "license": "ISC",
15
- "devDependencies": {
16
- "@bharper7/eslint-config": "^1.0.1",
17
- "@commander-js/extra-typings": "^10.0.3",
18
- "@total-typescript/ts-reset": "^0.4.2",
19
- "@types/node": "^20.1.0",
20
- "@typescript-eslint/eslint-plugin": "^5.59.2",
21
- "esbuild": "^0.17.18",
22
- "eslint": "^8.40.0",
23
- "eslint-plugin-import": "^2.27.5",
24
- "typescript": "^5.0.4"
25
- },
26
- "dependencies": {
27
- "@changesets/cli": "^2.26.1",
28
- "commander": "^10.0.1"
29
- }
2
+ "name": "brady-cli",
3
+ "version": "1.2.6",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "packageManager": "pnpm@10.10.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/BHarper77/brady-cli.git"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc",
13
+ "build": "esbuild src/index.ts --outfile=dist/index.js --bundle --platform=node --target=node18",
14
+ "release": "pnpm run build && semantic-release",
15
+ "brady": "node dist/index.js"
16
+ },
17
+ "bin": {
18
+ "brady": "dist/index.js"
19
+ },
20
+ "author": "",
21
+ "license": "ISC",
22
+ "devDependencies": {
23
+ "@commander-js/extra-typings": "^14.0.0",
24
+ "@semantic-release/changelog": "^6.0.3",
25
+ "@semantic-release/commit-analyzer": "^13.0.1",
26
+ "@semantic-release/exec": "^6.0.3",
27
+ "@semantic-release/git": "^10.0.1",
28
+ "@semantic-release/github": "^11.0.1",
29
+ "@semantic-release/npm": "^12.0.1",
30
+ "@semantic-release/release-notes-generator": "^14.0.3",
31
+ "@total-typescript/ts-reset": "^0.6.1",
32
+ "@types/node": "^25.6.2",
33
+ "esbuild": "^0.28.0",
34
+ "semantic-release": "^24.2.5",
35
+ "typescript": "^6.0.3"
36
+ },
37
+ "dependencies": {
38
+ "@clack/prompts": "^1.3.0",
39
+ "commander": "^14.0.3"
40
+ }
30
41
  }
package/src/index.js ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const p = __importStar(require("@clack/prompts"));
41
+ const child_process_1 = require("child_process");
42
+ const commander_1 = require("commander");
43
+ const promises_1 = require("fs/promises");
44
+ const path_1 = __importDefault(require("path"));
45
+ const eslintConfig_json_1 = __importDefault(require("./eslintConfig.json"));
46
+ const DOTFILES_OWNER = "bharper77";
47
+ const DOTFILES_REPO = "dotfiles";
48
+ const SKILLS_PATH = ".agents/skills";
49
+ const program = new commander_1.Command();
50
+ program
51
+ .command("init")
52
+ .option("-d, --directory <directory>", "Directory name for project")
53
+ .action(init);
54
+ const skillsCmd = new commander_1.Command("skills").description("Manage agent skills from dotfiles");
55
+ skillsCmd.addCommand(new commander_1.Command("list")
56
+ .description("List available skills from dotfiles")
57
+ .action(listSkills));
58
+ skillsCmd.addCommand(new commander_1.Command("add")
59
+ .description("Download one or more skills into .agents/skills/")
60
+ .argument("[skill]", "Skill name to download directly (omit for interactive picker)")
61
+ .action(addSkill));
62
+ program.addCommand(skillsCmd);
63
+ program.parseAsync(process.argv);
64
+ // ---------------------------------------------------------------------------
65
+ // init command
66
+ // ---------------------------------------------------------------------------
67
+ async function init(opts) {
68
+ // project dir
69
+ exec(`mkdir ${opts.directory}`);
70
+ exec("mkdir src", opts.directory);
71
+ exec("new-item index.ts", `${opts.directory}/src`);
72
+ // initialise git repo
73
+ exec("git init", opts.directory);
74
+ exec("echo 'node_modules' 'dist' > .gitignore", opts.directory);
75
+ // initialise Node project
76
+ exec("npm init -y", opts.directory);
77
+ const packageJson = JSON.parse((await (0, promises_1.readFile)(`${opts.directory}/package.json`)).toString());
78
+ packageJson.scripts = {
79
+ start: "node src/index.js",
80
+ build: "tsc",
81
+ };
82
+ await (0, promises_1.writeFile)(`${opts.directory}/package.json`, JSON.stringify(packageJson, null, 2));
83
+ const devDependencies = [
84
+ "typescript",
85
+ "@types/node",
86
+ "@total-typescript/ts-reset",
87
+ "eslint",
88
+ "@bharper7/eslint-config",
89
+ "@typescript-eslint/eslint-plugin",
90
+ "eslint-plugin-import",
91
+ ].join(" ");
92
+ exec(`npm i -D ${devDependencies}`, opts.directory);
93
+ exec("tsc --init", opts.directory);
94
+ // eslint
95
+ await (0, promises_1.writeFile)(`${opts.directory}/.eslintrc.json`, JSON.stringify(eslintConfig_json_1.default));
96
+ exec("echo 'node_modules' 'dist' > .eslintignore", opts.directory);
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // skills commands
100
+ // ---------------------------------------------------------------------------
101
+ async function listSkills() {
102
+ await ensureGhAuth();
103
+ const entries = fetchGhJson(`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`);
104
+ const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
105
+ if (skills.length === 0) {
106
+ console.log("No skills found.");
107
+ return;
108
+ }
109
+ console.log("Available skills:");
110
+ for (const skill of skills) {
111
+ console.log(` • ${skill}`);
112
+ }
113
+ }
114
+ async function addSkill(skill) {
115
+ await ensureGhAuth();
116
+ if (skill) {
117
+ await downloadSkill(skill);
118
+ }
119
+ else {
120
+ const entries = fetchGhJson(`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`);
121
+ const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
122
+ if (skills.length === 0) {
123
+ console.log("No skills available.");
124
+ return;
125
+ }
126
+ p.intro("brady skills add");
127
+ const selected = await p.multiselect({
128
+ message: "Select skills to download (space = toggle, enter = confirm):",
129
+ options: skills.map((s) => ({ value: s, label: s })),
130
+ required: true,
131
+ });
132
+ if (p.isCancel(selected)) {
133
+ p.cancel("Cancelled.");
134
+ process.exit(0);
135
+ }
136
+ const chosen = selected;
137
+ for (const s of chosen) {
138
+ await downloadSkill(s);
139
+ }
140
+ p.outro(`Downloaded ${chosen.length} skill(s).`);
141
+ }
142
+ }
143
+ // ---------------------------------------------------------------------------
144
+ // Helpers
145
+ // ---------------------------------------------------------------------------
146
+ async function ensureGhAuth() {
147
+ try {
148
+ (0, child_process_1.execSync)("gh auth status", { stdio: "pipe" });
149
+ }
150
+ catch {
151
+ p.intro("GitHub authentication required");
152
+ const answer = await p.confirm({
153
+ message: "You are not authenticated with GitHub CLI. Run `gh auth login` now?",
154
+ });
155
+ if (p.isCancel(answer) || !answer) {
156
+ console.error("Error: Not authenticated. Run `gh auth login` to authenticate, then retry.");
157
+ process.exit(1);
158
+ }
159
+ const result = (0, child_process_1.spawnSync)("gh", ["auth", "login"], {
160
+ stdio: "inherit",
161
+ shell: "powershell.exe",
162
+ });
163
+ if (result.status !== 0) {
164
+ console.error("Authentication failed. Run `gh auth login` to authenticate, then retry.");
165
+ process.exit(1);
166
+ }
167
+ }
168
+ }
169
+ function fetchGhJson(apiPath) {
170
+ const output = (0, child_process_1.execSync)(`gh api ${apiPath}`, { encoding: "utf-8" });
171
+ return JSON.parse(output);
172
+ }
173
+ async function downloadSkill(skillName) {
174
+ const apiPath = `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}`;
175
+ const files = fetchGhJson(apiPath);
176
+ const destDir = path_1.default.join(process.cwd(), SKILLS_PATH, skillName);
177
+ await (0, promises_1.mkdir)(destDir, { recursive: true });
178
+ for (const file of files) {
179
+ if (file.type !== "file")
180
+ continue;
181
+ const fileEntry = fetchGhJson(`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}/${file.name}`);
182
+ const content = Buffer.from(fileEntry.content, "base64").toString("utf-8");
183
+ await (0, promises_1.writeFile)(path_1.default.join(destDir, file.name), content, "utf-8");
184
+ }
185
+ console.log(`✓ Downloaded skill: ${skillName}`);
186
+ }
187
+ function exec(command, cwd) {
188
+ return (0, child_process_1.execSync)(command, {
189
+ cwd,
190
+ shell: "powershell.exe",
191
+ });
192
+ }
package/src/index.ts CHANGED
@@ -1,58 +1,232 @@
1
- #!/usr/bin/env node
2
-
3
- import { execSync } from "child_process"
4
- import { readFile, writeFile } from "fs/promises"
5
- import { Command } from "commander"
6
- import eslintConfigTemplate from "./eslintConfig.json"
7
-
8
- const program = new Command()
9
-
10
- program
11
- .command("init")
12
- .option("-d, --directory <directory>", "Directory name for project")
13
- .action(init)
14
-
15
- program.parseAsync(process.argv)
16
-
17
- async function init(opts: Options) {
18
- // project dir
19
- exec(`mkdir ${opts.directory}`)
20
- exec("mkdir src", opts.directory)
21
- exec("new-item index.ts", `${opts.directory}/src`)
22
-
23
- // initialise git repo
24
- exec("git init", opts.directory)
25
- exec("echo 'node_modules' 'dist' > .gitignore", opts.directory)
26
-
27
- // initialise Node project
28
- exec("npm init -y", opts.directory)
29
-
30
- const packageJson = JSON.parse((await readFile(`${opts.directory}/package.json`)).toString())
31
- packageJson.scripts = {
32
- "start": "node src/index.js",
33
- "build": "tsc"
34
- }
35
-
36
- await writeFile(`${opts.directory}/package.json`, JSON.stringify(packageJson, null, 2))
37
-
38
- const devDependencies = [
39
- "typescript", "@types/node", "@total-typescript/ts-reset", "eslint", "@bharper7/eslint-config", "@typescript-eslint/eslint-plugin", "eslint-plugin-import"
40
- ].join(" ")
41
- exec(`npm i -D ${devDependencies}`, opts.directory)
42
- exec("tsc --init", opts.directory)
43
-
44
- // eslint
45
- await writeFile(`${opts.directory}/.eslintrc.json`, JSON.stringify(eslintConfigTemplate))
46
- exec("echo 'node_modules' 'dist' > .eslintignore", opts.directory)
47
- }
48
-
49
- function exec(command: string, cwd?: string) {
50
- return execSync(command, {
51
- cwd,
52
- shell: "powershell.exe"
53
- })
54
- }
55
-
56
- type Options = {
57
- directory: string
58
- }
1
+ #!/usr/bin/env node
2
+
3
+ import * as p from "@clack/prompts";
4
+ import { execSync, spawnSync } from "child_process";
5
+ import { Command } from "commander";
6
+ import { mkdir, readFile, writeFile } from "fs/promises";
7
+ import path from "path";
8
+
9
+ const DOTFILES_OWNER = "bharper77";
10
+ const DOTFILES_REPO = "dotfiles";
11
+ const SKILLS_PATH = ".agents/skills";
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .command("init")
17
+ .option("-d, --directory <directory>", "Directory name for project")
18
+ .action(init);
19
+
20
+ const skillsCmd = new Command("skills").description(
21
+ "Manage agent skills from dotfiles",
22
+ );
23
+
24
+ skillsCmd.addCommand(
25
+ new Command("list")
26
+ .description("List available skills from dotfiles")
27
+ .action(listSkills),
28
+ );
29
+
30
+ skillsCmd.addCommand(
31
+ new Command("add")
32
+ .description("Download one or more skills into .agents/skills/")
33
+ .argument(
34
+ "[skill]",
35
+ "Skill name to download directly (omit for interactive picker)",
36
+ )
37
+ .action(addSkill),
38
+ );
39
+
40
+ program.addCommand(skillsCmd);
41
+
42
+ program.parseAsync(process.argv);
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // init command
46
+ // ---------------------------------------------------------------------------
47
+
48
+ async function init(opts: Options) {
49
+ // project dir
50
+ exec(`mkdir ${opts.directory}`);
51
+ exec("mkdir src", opts.directory);
52
+ exec("new-item index.ts", `${opts.directory}/src`);
53
+
54
+ // initialise git repo
55
+ exec("git init", opts.directory);
56
+ exec("echo 'node_modules' 'dist' > .gitignore", opts.directory);
57
+
58
+ // initialise Node project
59
+ exec("npm init -y", opts.directory);
60
+
61
+ const packageJson = JSON.parse(
62
+ (await readFile(`${opts.directory}/package.json`)).toString(),
63
+ );
64
+ packageJson.scripts = {
65
+ start: "node src/index.js",
66
+ build: "tsc",
67
+ };
68
+
69
+ await writeFile(
70
+ `${opts.directory}/package.json`,
71
+ JSON.stringify(packageJson, null, 2),
72
+ );
73
+
74
+ const devDependencies = [
75
+ "typescript",
76
+ "@types/node",
77
+ "@total-typescript/ts-reset",
78
+ "eslint",
79
+ "@bharper7/eslint-config",
80
+ "@typescript-eslint/eslint-plugin",
81
+ "eslint-plugin-import",
82
+ ].join(" ");
83
+ exec(`npm i -D ${devDependencies}`, opts.directory);
84
+ exec("tsc --init", opts.directory);
85
+
86
+ exec("echo 'node_modules' 'dist' > .eslintignore", opts.directory);
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // skills commands
91
+ // ---------------------------------------------------------------------------
92
+
93
+ async function listSkills() {
94
+ await ensureGhAuth();
95
+
96
+ const entries = fetchGhJson<GhContentEntry[]>(
97
+ `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`,
98
+ );
99
+ const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
100
+
101
+ if (skills.length === 0) {
102
+ console.log("No skills found.");
103
+ return;
104
+ }
105
+
106
+ console.log("Available skills:");
107
+ for (const skill of skills) {
108
+ console.log(` • ${skill}`);
109
+ }
110
+ }
111
+
112
+ async function addSkill(skill?: string) {
113
+ await ensureGhAuth();
114
+
115
+ if (skill) {
116
+ await downloadSkill(skill);
117
+ } else {
118
+ const entries = fetchGhJson<GhContentEntry[]>(
119
+ `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`,
120
+ );
121
+ const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
122
+
123
+ if (skills.length === 0) {
124
+ console.log("No skills available.");
125
+ return;
126
+ }
127
+
128
+ p.intro("brady skills add");
129
+
130
+ const selected = await p.multiselect<string>({
131
+ message: "Select skills to download (space = toggle, enter = confirm):",
132
+ options: skills.map((s) => ({ value: s, label: s })),
133
+ required: true,
134
+ });
135
+
136
+ if (p.isCancel(selected)) {
137
+ p.cancel("Cancelled.");
138
+ process.exit(0);
139
+ }
140
+
141
+ const chosen = selected as string[];
142
+ for (const s of chosen) {
143
+ await downloadSkill(s);
144
+ }
145
+
146
+ p.outro(`Downloaded ${chosen.length} skill(s).`);
147
+ }
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Helpers
152
+ // ---------------------------------------------------------------------------
153
+
154
+ async function ensureGhAuth() {
155
+ try {
156
+ execSync("gh auth status", { stdio: "pipe" });
157
+ } catch {
158
+ p.intro("GitHub authentication required");
159
+ const answer = await p.confirm({
160
+ message:
161
+ "You are not authenticated with GitHub CLI. Run `gh auth login` now?",
162
+ });
163
+
164
+ if (p.isCancel(answer) || !answer) {
165
+ console.error(
166
+ "Error: Not authenticated. Run `gh auth login` to authenticate, then retry.",
167
+ );
168
+ process.exit(1);
169
+ }
170
+
171
+ const result = spawnSync("gh", ["auth", "login"], {
172
+ stdio: "inherit",
173
+ shell: "powershell.exe",
174
+ });
175
+ if (result.status !== 0) {
176
+ console.error(
177
+ "Authentication failed. Run `gh auth login` to authenticate, then retry.",
178
+ );
179
+ process.exit(1);
180
+ }
181
+ }
182
+ }
183
+
184
+ function fetchGhJson<T>(apiPath: string): T {
185
+ const output = execSync(`gh api ${apiPath}`, { encoding: "utf-8" });
186
+ return JSON.parse(output) as T;
187
+ }
188
+
189
+ async function downloadSkill(skillName: string) {
190
+ const apiPath = `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}`;
191
+ const files = fetchGhJson<GhContentEntry[]>(apiPath);
192
+
193
+ const destDir = path.join(process.cwd(), SKILLS_PATH, skillName);
194
+ await mkdir(destDir, { recursive: true });
195
+
196
+ for (const file of files) {
197
+ if (file.type !== "file") continue;
198
+ const fileEntry = fetchGhJson<GhFileEntry>(
199
+ `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}/${file.name}`,
200
+ );
201
+ const content = Buffer.from(fileEntry.content, "base64").toString("utf-8");
202
+ await writeFile(path.join(destDir, file.name), content, "utf-8");
203
+ }
204
+
205
+ console.log(`✓ Downloaded skill: ${skillName}`);
206
+ }
207
+
208
+ function exec(command: string, cwd?: string) {
209
+ return execSync(command, {
210
+ cwd,
211
+ shell: "powershell.exe",
212
+ });
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Types
217
+ // ---------------------------------------------------------------------------
218
+
219
+ type Options = {
220
+ directory: string;
221
+ };
222
+
223
+ type GhContentEntry = {
224
+ name: string;
225
+ type: "file" | "dir" | "symlink" | "submodule";
226
+ path: string;
227
+ };
228
+
229
+ type GhFileEntry = GhContentEntry & {
230
+ content: string;
231
+ encoding: string;
232
+ };