brady-cli 1.1.0 → 1.2.5
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/.github/prompts/plan-bradyCliSkillsCommands.prompt.md +47 -0
- package/.github/workflows/main.yml +18 -0
- package/.github/workflows/publish.yml +30 -0
- package/.releaserc.json +21 -0
- package/CHANGELOG.md +3 -4
- package/README.md +111 -0
- package/dist/index.js +2232 -650
- package/package.json +39 -28
- package/src/eslintConfig.json +2 -2
- package/src/index.js +192 -0
- package/src/index.ts +238 -58
- package/tsconfig.json +94 -92
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.eslintrc.json +0 -3
package/package.json
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
2
|
+
"name": "brady-cli",
|
|
3
|
+
"version": "1.2.5",
|
|
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/eslintConfig.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "@bharper7/eslint-config"
|
|
1
|
+
{
|
|
2
|
+
"extends": "@bharper7/eslint-config"
|
|
3
3
|
}
|
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,238 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { Command } from "commander"
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
import eslintConfigTemplate from "./eslintConfig.json";
|
|
9
|
+
|
|
10
|
+
const DOTFILES_OWNER = "bharper77";
|
|
11
|
+
const DOTFILES_REPO = "dotfiles";
|
|
12
|
+
const SKILLS_PATH = ".agents/skills";
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command("init")
|
|
18
|
+
.option("-d, --directory <directory>", "Directory name for project")
|
|
19
|
+
.action(init);
|
|
20
|
+
|
|
21
|
+
const skillsCmd = new Command("skills").description(
|
|
22
|
+
"Manage agent skills from dotfiles",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
skillsCmd.addCommand(
|
|
26
|
+
new Command("list")
|
|
27
|
+
.description("List available skills from dotfiles")
|
|
28
|
+
.action(listSkills),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
skillsCmd.addCommand(
|
|
32
|
+
new Command("add")
|
|
33
|
+
.description("Download one or more skills into .agents/skills/")
|
|
34
|
+
.argument(
|
|
35
|
+
"[skill]",
|
|
36
|
+
"Skill name to download directly (omit for interactive picker)",
|
|
37
|
+
)
|
|
38
|
+
.action(addSkill),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
program.addCommand(skillsCmd);
|
|
42
|
+
|
|
43
|
+
program.parseAsync(process.argv);
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// init command
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
async function init(opts: Options) {
|
|
50
|
+
// project dir
|
|
51
|
+
exec(`mkdir ${opts.directory}`);
|
|
52
|
+
exec("mkdir src", opts.directory);
|
|
53
|
+
exec("new-item index.ts", `${opts.directory}/src`);
|
|
54
|
+
|
|
55
|
+
// initialise git repo
|
|
56
|
+
exec("git init", opts.directory);
|
|
57
|
+
exec("echo 'node_modules' 'dist' > .gitignore", opts.directory);
|
|
58
|
+
|
|
59
|
+
// initialise Node project
|
|
60
|
+
exec("npm init -y", opts.directory);
|
|
61
|
+
|
|
62
|
+
const packageJson = JSON.parse(
|
|
63
|
+
(await readFile(`${opts.directory}/package.json`)).toString(),
|
|
64
|
+
);
|
|
65
|
+
packageJson.scripts = {
|
|
66
|
+
start: "node src/index.js",
|
|
67
|
+
build: "tsc",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
await writeFile(
|
|
71
|
+
`${opts.directory}/package.json`,
|
|
72
|
+
JSON.stringify(packageJson, null, 2),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const devDependencies = [
|
|
76
|
+
"typescript",
|
|
77
|
+
"@types/node",
|
|
78
|
+
"@total-typescript/ts-reset",
|
|
79
|
+
"eslint",
|
|
80
|
+
"@bharper7/eslint-config",
|
|
81
|
+
"@typescript-eslint/eslint-plugin",
|
|
82
|
+
"eslint-plugin-import",
|
|
83
|
+
].join(" ");
|
|
84
|
+
exec(`npm i -D ${devDependencies}`, opts.directory);
|
|
85
|
+
exec("tsc --init", opts.directory);
|
|
86
|
+
|
|
87
|
+
// eslint
|
|
88
|
+
await writeFile(
|
|
89
|
+
`${opts.directory}/.eslintrc.json`,
|
|
90
|
+
JSON.stringify(eslintConfigTemplate),
|
|
91
|
+
);
|
|
92
|
+
exec("echo 'node_modules' 'dist' > .eslintignore", opts.directory);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// skills commands
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
async function listSkills() {
|
|
100
|
+
await ensureGhAuth();
|
|
101
|
+
|
|
102
|
+
const entries = fetchGhJson<GhContentEntry[]>(
|
|
103
|
+
`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`,
|
|
104
|
+
);
|
|
105
|
+
const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
|
|
106
|
+
|
|
107
|
+
if (skills.length === 0) {
|
|
108
|
+
console.log("No skills found.");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log("Available skills:");
|
|
113
|
+
for (const skill of skills) {
|
|
114
|
+
console.log(` • ${skill}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function addSkill(skill?: string) {
|
|
119
|
+
await ensureGhAuth();
|
|
120
|
+
|
|
121
|
+
if (skill) {
|
|
122
|
+
await downloadSkill(skill);
|
|
123
|
+
} else {
|
|
124
|
+
const entries = fetchGhJson<GhContentEntry[]>(
|
|
125
|
+
`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}`,
|
|
126
|
+
);
|
|
127
|
+
const skills = entries.filter((e) => e.type === "dir").map((e) => e.name);
|
|
128
|
+
|
|
129
|
+
if (skills.length === 0) {
|
|
130
|
+
console.log("No skills available.");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
p.intro("brady skills add");
|
|
135
|
+
|
|
136
|
+
const selected = await p.multiselect<string>({
|
|
137
|
+
message: "Select skills to download (space = toggle, enter = confirm):",
|
|
138
|
+
options: skills.map((s) => ({ value: s, label: s })),
|
|
139
|
+
required: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (p.isCancel(selected)) {
|
|
143
|
+
p.cancel("Cancelled.");
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const chosen = selected as string[];
|
|
148
|
+
for (const s of chosen) {
|
|
149
|
+
await downloadSkill(s);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
p.outro(`Downloaded ${chosen.length} skill(s).`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Helpers
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
async function ensureGhAuth() {
|
|
161
|
+
try {
|
|
162
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
163
|
+
} catch {
|
|
164
|
+
p.intro("GitHub authentication required");
|
|
165
|
+
const answer = await p.confirm({
|
|
166
|
+
message:
|
|
167
|
+
"You are not authenticated with GitHub CLI. Run `gh auth login` now?",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (p.isCancel(answer) || !answer) {
|
|
171
|
+
console.error(
|
|
172
|
+
"Error: Not authenticated. Run `gh auth login` to authenticate, then retry.",
|
|
173
|
+
);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = spawnSync("gh", ["auth", "login"], {
|
|
178
|
+
stdio: "inherit",
|
|
179
|
+
shell: "powershell.exe",
|
|
180
|
+
});
|
|
181
|
+
if (result.status !== 0) {
|
|
182
|
+
console.error(
|
|
183
|
+
"Authentication failed. Run `gh auth login` to authenticate, then retry.",
|
|
184
|
+
);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function fetchGhJson<T>(apiPath: string): T {
|
|
191
|
+
const output = execSync(`gh api ${apiPath}`, { encoding: "utf-8" });
|
|
192
|
+
return JSON.parse(output) as T;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function downloadSkill(skillName: string) {
|
|
196
|
+
const apiPath = `repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}`;
|
|
197
|
+
const files = fetchGhJson<GhContentEntry[]>(apiPath);
|
|
198
|
+
|
|
199
|
+
const destDir = path.join(process.cwd(), SKILLS_PATH, skillName);
|
|
200
|
+
await mkdir(destDir, { recursive: true });
|
|
201
|
+
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
if (file.type !== "file") continue;
|
|
204
|
+
const fileEntry = fetchGhJson<GhFileEntry>(
|
|
205
|
+
`repos/${DOTFILES_OWNER}/${DOTFILES_REPO}/contents/${SKILLS_PATH}/${skillName}/${file.name}`,
|
|
206
|
+
);
|
|
207
|
+
const content = Buffer.from(fileEntry.content, "base64").toString("utf-8");
|
|
208
|
+
await writeFile(path.join(destDir, file.name), content, "utf-8");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`✓ Downloaded skill: ${skillName}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function exec(command: string, cwd?: string) {
|
|
215
|
+
return execSync(command, {
|
|
216
|
+
cwd,
|
|
217
|
+
shell: "powershell.exe",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Types
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
type Options = {
|
|
226
|
+
directory: string;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
type GhContentEntry = {
|
|
230
|
+
name: string;
|
|
231
|
+
type: "file" | "dir" | "symlink" | "submodule";
|
|
232
|
+
path: string;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
type GhFileEntry = GhContentEntry & {
|
|
236
|
+
content: string;
|
|
237
|
+
encoding: string;
|
|
238
|
+
};
|