brn-toolkit 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.
Files changed (48) hide show
  1. package/GEMINI.md +92 -0
  2. package/README.md +145 -0
  3. package/cli/brn.ts +301 -0
  4. package/dist/cli/brn.js +274 -0
  5. package/dist/lib/utils.js +167 -0
  6. package/dist/skills/github/scripts/create_pr.js +24 -0
  7. package/dist/skills/github/scripts/get_repo_info.js +23 -0
  8. package/dist/skills/github/scripts/list_prs.js +27 -0
  9. package/dist/skills/github/scripts/list_repos.js +39 -0
  10. package/dist/skills/jira/scripts/add_comment.js +36 -0
  11. package/dist/skills/jira/scripts/get_ticket.js +45 -0
  12. package/dist/skills/jira/scripts/list_tickets.js +42 -0
  13. package/dist/skills/jira/scripts/update_ticket.js +30 -0
  14. package/dist/skills/workflow/scripts/start.js +75 -0
  15. package/dist/skills/workspace-manager/scripts/configure_workspace.js +59 -0
  16. package/dist/skills/workspace-manager/scripts/create_workspace.js +60 -0
  17. package/lib/utils.ts +236 -0
  18. package/package.json +46 -0
  19. package/skills/git-worktree/SKILL.md +49 -0
  20. package/skills/git-worktree/scripts/clone_repo.sh +53 -0
  21. package/skills/git-worktree/scripts/create_worktree.sh +66 -0
  22. package/skills/git-worktree/scripts/list_worktrees.sh +39 -0
  23. package/skills/git-worktree/scripts/remove_worktree.sh +47 -0
  24. package/skills/github/SKILL.md +51 -0
  25. package/skills/github/references/api_patterns.md +36 -0
  26. package/skills/github/scripts/create_pr.ts +38 -0
  27. package/skills/github/scripts/get_repo_info.ts +39 -0
  28. package/skills/github/scripts/list_prs.ts +45 -0
  29. package/skills/github/scripts/list_repos.ts +54 -0
  30. package/skills/jira/SKILL.md +50 -0
  31. package/skills/jira/references/api_patterns.md +59 -0
  32. package/skills/jira/scripts/add_comment.ts +41 -0
  33. package/skills/jira/scripts/get_ticket.ts +71 -0
  34. package/skills/jira/scripts/list_tickets.ts +64 -0
  35. package/skills/jira/scripts/update_ticket.ts +50 -0
  36. package/skills/workflow/SKILL.md +94 -0
  37. package/skills/workflow/references/coding_workflow.md +88 -0
  38. package/skills/workflow/references/planning_workflow.md +96 -0
  39. package/skills/workflow/references/review_workflow.md +104 -0
  40. package/skills/workflow/scripts/start.ts +93 -0
  41. package/skills/workspace-manager/SKILL.md +49 -0
  42. package/skills/workspace-manager/scripts/configure_workspace.sh +87 -0
  43. package/skills/workspace-manager/scripts/configure_workspace.ts +70 -0
  44. package/skills/workspace-manager/scripts/create_workspace.sh +66 -0
  45. package/skills/workspace-manager/scripts/create_workspace.ts +74 -0
  46. package/skills/workspace-manager/scripts/get_active_workspace.sh +24 -0
  47. package/skills/workspace-manager/scripts/list_workspaces.sh +31 -0
  48. package/skills/workspace-manager/scripts/switch_workspace.sh +38 -0
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BRN-Toolkit CLI - Main entry point
4
+ * Usage: brn <command> [options]
5
+ */
6
+ import { $ } from "zx";
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { join, dirname } from "path";
10
+ import { fileURLToPath } from "url";
11
+ import * as readline from "readline";
12
+ import YAML from "yaml";
13
+ $.verbose = false;
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ // Handle both source (cli/brn.ts) and compiled (dist/cli/brn.js) locations
17
+ const isCompiled = __dirname.includes(join("dist", "cli"));
18
+ const PROJECT_ROOT = isCompiled ? join(__dirname, "..", "..") : join(__dirname, "..");
19
+ const BRN_DIR = join(homedir(), ".brn");
20
+ const CONFIG_FILE = join(BRN_DIR, "config.yaml");
21
+ // Simple readline interface for prompts
22
+ function prompt(question) {
23
+ const rl = readline.createInterface({
24
+ input: process.stdin,
25
+ output: process.stdout,
26
+ });
27
+ return new Promise((resolve) => {
28
+ rl.question(question, (answer) => {
29
+ rl.close();
30
+ resolve(answer.trim());
31
+ });
32
+ });
33
+ }
34
+ async function runSkill(skill, script, args) {
35
+ // Determine where to look for scripts
36
+ // Prefer dist/skills/.../*.js for speed, fallback to skills/.../*.ts
37
+ const distScriptsDir = join(PROJECT_ROOT, "dist", "skills", skill, "scripts");
38
+ const srcScriptsDir = join(PROJECT_ROOT, "skills", skill, "scripts");
39
+ const jsPath = join(distScriptsDir, `${script}.js`);
40
+ const tsPath = join(srcScriptsDir, `${script}.ts`);
41
+ const shPath = join(srcScriptsDir, `${script}.sh`);
42
+ // Enable verbose to show output of the child script
43
+ const previousVerbose = $.verbose;
44
+ $.verbose = true;
45
+ try {
46
+ if (existsSync(jsPath)) {
47
+ // Run compiled JS with node
48
+ await $ `node ${jsPath} ${args}`;
49
+ }
50
+ else if (existsSync(tsPath)) {
51
+ // Run TS with tsx
52
+ await $ `npx tsx ${tsPath} ${args}`;
53
+ }
54
+ else if (existsSync(shPath)) {
55
+ // Run shell
56
+ await $ `bash ${shPath} ${args}`;
57
+ }
58
+ else {
59
+ // Try without extension in src
60
+ const exactPath = join(srcScriptsDir, script);
61
+ if (existsSync(exactPath)) {
62
+ if (script.endsWith(".sh"))
63
+ await $ `bash ${exactPath} ${args}`;
64
+ else
65
+ await $ `npx tsx ${exactPath} ${args}`;
66
+ }
67
+ else {
68
+ console.error(`Skill script not found: ${skill}/${script}`);
69
+ process.exit(1);
70
+ }
71
+ }
72
+ }
73
+ finally {
74
+ $.verbose = previousVerbose;
75
+ }
76
+ }
77
+ async function setup() {
78
+ console.log("");
79
+ console.log("🚀 BRN Setup Wizard");
80
+ console.log("===================");
81
+ console.log("");
82
+ // Create config directory
83
+ if (!existsSync(BRN_DIR)) {
84
+ mkdirSync(BRN_DIR, { recursive: true });
85
+ console.log(`✓ Created ${BRN_DIR}`);
86
+ }
87
+ // Check for existing config
88
+ let config = { version: "1.0", active_workspace: "", workspaces: {} };
89
+ if (existsSync(CONFIG_FILE)) {
90
+ config = YAML.parse(readFileSync(CONFIG_FILE, "utf-8"));
91
+ console.log(`✓ Found existing config with ${Object.keys(config.workspaces).length} workspace(s)`);
92
+ console.log("");
93
+ }
94
+ // Workspace name
95
+ const defaultName = Object.keys(config.workspaces).length === 0 ? "personal" : "";
96
+ const name = await prompt(`Workspace name [${defaultName}]: `) || defaultName;
97
+ if (!name) {
98
+ console.log("❌ Workspace name is required");
99
+ process.exit(1);
100
+ }
101
+ // Work directory
102
+ const defaultPath = join(homedir(), "dev", name, "auto");
103
+ const workDir = await prompt(`Work directory [${defaultPath}]: `) || defaultPath;
104
+ // GitHub token (optional)
105
+ console.log("");
106
+ console.log("📦 GitHub Configuration (optional, press Enter to skip)");
107
+ const githubToken = await prompt("GitHub token: ");
108
+ const githubOrg = await prompt("Default GitHub Organization/User: ");
109
+ // JIRA config (optional)
110
+ console.log("");
111
+ console.log("📋 JIRA Configuration (optional, press Enter to skip)");
112
+ const jiraUrl = await prompt("JIRA URL (e.g., https://company.atlassian.net): ");
113
+ let jiraEmail = "";
114
+ let jiraToken = "";
115
+ if (jiraUrl) {
116
+ jiraEmail = await prompt("JIRA email: ");
117
+ jiraToken = await prompt("JIRA API token: ");
118
+ }
119
+ // Automation settings
120
+ console.log("");
121
+ console.log("⚙️ Automation Settings");
122
+ console.log(" By default, all automation is OFF for safety.");
123
+ const enableAuto = await prompt("Enable any automation? (y/N): ");
124
+ let automation = {
125
+ github_auto_push: false,
126
+ github_auto_pr: false,
127
+ jira_auto_transition: false,
128
+ jira_auto_comment: false,
129
+ };
130
+ if (enableAuto.toLowerCase() === "y") {
131
+ const autoPush = await prompt(" Auto-push commits? (y/N): ");
132
+ const autoPr = await prompt(" Auto-create PRs? (y/N): ");
133
+ const autoTransition = await prompt(" Auto-update JIRA status? (y/N): ");
134
+ const autoComment = await prompt(" Auto-add JIRA comments? (y/N): ");
135
+ automation = {
136
+ github_auto_push: autoPush.toLowerCase() === "y",
137
+ github_auto_pr: autoPr.toLowerCase() === "y",
138
+ jira_auto_transition: autoTransition.toLowerCase() === "y",
139
+ jira_auto_comment: autoComment.toLowerCase() === "y",
140
+ };
141
+ }
142
+ // Build workspace config
143
+ const workspace = {
144
+ path: workDir,
145
+ automation,
146
+ };
147
+ if (githubToken)
148
+ workspace.github_token = githubToken;
149
+ if (githubOrg)
150
+ workspace.github_org = githubOrg;
151
+ if (jiraUrl) {
152
+ workspace.jira_url = jiraUrl;
153
+ workspace.jira_email = jiraEmail;
154
+ workspace.jira_token = jiraToken;
155
+ }
156
+ // Save config
157
+ config.workspaces[name] = workspace;
158
+ if (!config.active_workspace) {
159
+ config.active_workspace = name;
160
+ }
161
+ writeFileSync(CONFIG_FILE, YAML.stringify(config));
162
+ // Create work directory
163
+ if (!existsSync(workDir)) {
164
+ mkdirSync(workDir, { recursive: true });
165
+ }
166
+ console.log("");
167
+ console.log("✅ Setup complete!");
168
+ console.log("");
169
+ console.log(` Workspace: ${name}`);
170
+ console.log(` Work dir: ${workDir}`);
171
+ console.log(` Config: ${CONFIG_FILE}`);
172
+ console.log("");
173
+ console.log("Next steps:");
174
+ console.log(" • Use 'brn workspace list' to see workspaces");
175
+ console.log(" • Use 'brn workspace switch <name>' to change workspace");
176
+ console.log(" • Ask your AI assistant to 'list my JIRA tickets'");
177
+ console.log("");
178
+ }
179
+ async function workspaceList() {
180
+ if (!existsSync(CONFIG_FILE)) {
181
+ console.log("No workspaces configured. Run: brn setup");
182
+ return;
183
+ }
184
+ const config = YAML.parse(readFileSync(CONFIG_FILE, "utf-8"));
185
+ const active = config.active_workspace;
186
+ console.log("Workspaces:");
187
+ console.log("===========");
188
+ for (const [name, ws] of Object.entries(config.workspaces)) {
189
+ const workspace = ws;
190
+ const marker = name === active ? "* " : " ";
191
+ const activeLabel = name === active ? " [ACTIVE]" : "";
192
+ console.log(`${marker}${name} (${workspace.path})${activeLabel}`);
193
+ }
194
+ }
195
+ async function workspaceSwitch(name) {
196
+ if (!existsSync(CONFIG_FILE)) {
197
+ console.log("No workspaces configured. Run: brn setup");
198
+ return;
199
+ }
200
+ const config = YAML.parse(readFileSync(CONFIG_FILE, "utf-8"));
201
+ if (!config.workspaces[name]) {
202
+ console.log(`Workspace '${name}' not found.`);
203
+ console.log("Available:", Object.keys(config.workspaces).join(", "));
204
+ return;
205
+ }
206
+ config.active_workspace = name;
207
+ writeFileSync(CONFIG_FILE, YAML.stringify(config));
208
+ console.log(`✓ Switched to workspace '${name}'`);
209
+ }
210
+ function showHelp() {
211
+ console.log(`
212
+ BRN - AI Developer Workflow Toolkit
213
+
214
+ Usage:
215
+ brn <command> [options]
216
+ brn <skill> <script> [args]
217
+
218
+ Commands:
219
+ start <ticket> Shortcut for workflow start <ticket>
220
+ setup Interactive setup wizard
221
+ workspace list List all workspaces
222
+ workspace switch <n> Switch to workspace <n>
223
+ help Show this help
224
+
225
+ Skills:
226
+ github <script> Run a GitHub skill script (e.g., list_repos)
227
+ jira <script> Run a JIRA skill script (e.g., list_tickets)
228
+ git-worktree <script> Run a Git Worktree skill script (e.g., clone_repo)
229
+
230
+ Examples:
231
+ brn start PROJ-123
232
+ brn github list_repos
233
+ brn jira list_tickets
234
+ brn workspace switch work
235
+ `);
236
+ }
237
+ // Main
238
+ const args = process.argv.slice(2);
239
+ const command = args[0];
240
+ switch (command) {
241
+ case "start":
242
+ await runSkill("workflow", "start", args.slice(1));
243
+ break;
244
+ case "setup":
245
+ await setup();
246
+ break;
247
+ case "workspace":
248
+ if (args[1] === "list") {
249
+ await workspaceList();
250
+ }
251
+ else if (args[1] === "switch" && args[2]) {
252
+ await workspaceSwitch(args[2]);
253
+ }
254
+ else {
255
+ console.log("Usage: brn workspace [list|switch <name>]");
256
+ }
257
+ break;
258
+ case "help":
259
+ case "--help":
260
+ case "-h":
261
+ case undefined:
262
+ showHelp();
263
+ break;
264
+ default:
265
+ // Generic skill runner: brn <skill> <script> [...args]
266
+ if (command && args[1]) {
267
+ await runSkill(command, args[1], args.slice(2));
268
+ }
269
+ else {
270
+ console.log(`Unknown command: ${command}`);
271
+ showHelp();
272
+ process.exit(1);
273
+ }
274
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Shared utilities for brn scripts
3
+ */
4
+ import { $ } from "zx";
5
+ import { readFileSync, existsSync, writeFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { join } from "path";
8
+ import YAML from "yaml";
9
+ $.verbose = false;
10
+ /**
11
+ * Get the brn config from ~/.brn/config.yaml
12
+ */
13
+ export function getBrnConfig() {
14
+ const configPath = join(homedir(), ".brn", "config.yaml");
15
+ if (!existsSync(configPath)) {
16
+ console.error("Error: No config found at ~/.brn/config.yaml");
17
+ console.error("Run: create_workspace.sh <name> <path>");
18
+ process.exit(1);
19
+ }
20
+ const content = readFileSync(configPath, "utf-8");
21
+ return YAML.parse(content);
22
+ }
23
+ /**
24
+ * Save the brn config to ~/.brn/config.yaml
25
+ */
26
+ export function saveBrnConfig(config) {
27
+ const configPath = join(homedir(), ".brn", "config.yaml");
28
+ writeFileSync(configPath, YAML.stringify(config));
29
+ }
30
+ /**
31
+ * Update the active workspace configuration
32
+ */
33
+ export function updateWorkspace(updates) {
34
+ const config = getBrnConfig();
35
+ const activeWorkspaceName = config.active_workspace;
36
+ if (!config.workspaces[activeWorkspaceName]) {
37
+ console.error(`Error: Active workspace '${activeWorkspaceName}' not found`);
38
+ process.exit(1);
39
+ }
40
+ config.workspaces[activeWorkspaceName] = {
41
+ ...config.workspaces[activeWorkspaceName],
42
+ ...updates,
43
+ };
44
+ saveBrnConfig(config);
45
+ }
46
+ /**
47
+ * Get the active workspace config
48
+ */
49
+ export function getActiveWorkspace() {
50
+ const config = getBrnConfig();
51
+ const name = config.active_workspace;
52
+ const workspace = config.workspaces[name];
53
+ if (!workspace) {
54
+ console.error(`Error: Workspace '${name}' not found in config`);
55
+ process.exit(1);
56
+ }
57
+ return { name, ...workspace };
58
+ }
59
+ /**
60
+ * Check if an automation action is enabled for the active workspace.
61
+ * All automation is OFF by default for safety.
62
+ */
63
+ export function isAutomationEnabled(action) {
64
+ const workspace = getActiveWorkspace();
65
+ return workspace.automation?.[action] ?? false;
66
+ }
67
+ /**
68
+ * Require automation to be enabled, or prompt user to confirm.
69
+ * Returns true if action should proceed, false if user declined.
70
+ */
71
+ export function checkAutomation(action, description) {
72
+ if (isAutomationEnabled(action)) {
73
+ return true;
74
+ }
75
+ console.log(`⚠️ Automation disabled: ${description}`);
76
+ console.log(` To enable, run: configure_workspace.sh <workspace> automation.${action} true`);
77
+ console.log(` Skipping automatic execution.`);
78
+ return false;
79
+ }
80
+ /**
81
+ * Get GitHub token from active workspace
82
+ */
83
+ export function getGitHubToken() {
84
+ const workspace = getActiveWorkspace();
85
+ if (!workspace.github_token) {
86
+ console.error("Error: github_token not configured for this workspace");
87
+ console.error(`Run: configure_workspace.sh ${workspace.name} github_token <token>`);
88
+ process.exit(1);
89
+ }
90
+ return workspace.github_token;
91
+ }
92
+ /**
93
+ * Get JIRA config from active workspace
94
+ */
95
+ export function getJiraConfig() {
96
+ const workspace = getActiveWorkspace();
97
+ if (!workspace.jira_url) {
98
+ console.error("Error: jira_url not configured");
99
+ process.exit(1);
100
+ }
101
+ if (!workspace.jira_email) {
102
+ console.error("Error: jira_email not configured");
103
+ process.exit(1);
104
+ }
105
+ if (!workspace.jira_token) {
106
+ console.error("Error: jira_token not configured");
107
+ process.exit(1);
108
+ }
109
+ return {
110
+ url: workspace.jira_url.replace(/\/$/, ""),
111
+ email: workspace.jira_email,
112
+ token: workspace.jira_token,
113
+ };
114
+ }
115
+ /**
116
+ * Make a GitHub API request
117
+ */
118
+ export async function githubRequest(endpoint, options = {}) {
119
+ const token = getGitHubToken();
120
+ console.log(`[GitHub API] Requesting: ${endpoint}`);
121
+ const response = await fetch(`https://api.github.com${endpoint}`, {
122
+ ...options,
123
+ headers: {
124
+ Authorization: `Bearer ${token}`,
125
+ Accept: "application/vnd.github+json",
126
+ "X-GitHub-Api-Version": "2022-11-28",
127
+ "Content-Type": "application/json",
128
+ ...options.headers,
129
+ },
130
+ });
131
+ if (!response.ok) {
132
+ const error = await response.json().catch(() => ({}));
133
+ console.error(`Error: ${response.status} - ${response.statusText}`);
134
+ if (error.message)
135
+ console.error(` ${error.message}`);
136
+ process.exit(1);
137
+ }
138
+ return response.json();
139
+ }
140
+ /**
141
+ * Make a JIRA API request
142
+ */
143
+ export async function jiraRequest(endpoint, options = {}) {
144
+ const { url, email, token } = getJiraConfig();
145
+ const auth = Buffer.from(`${email}:${token}`).toString("base64");
146
+ const response = await fetch(`${url}${endpoint}`, {
147
+ ...options,
148
+ headers: {
149
+ Authorization: `Basic ${auth}`,
150
+ Accept: "application/json",
151
+ "Content-Type": "application/json",
152
+ ...options.headers,
153
+ },
154
+ });
155
+ if (!response.ok) {
156
+ const error = await response.json().catch(() => ({}));
157
+ console.error(`Error: ${response.status} - ${response.statusText}`);
158
+ if (error.errorMessages)
159
+ console.error(` ${error.errorMessages}`);
160
+ process.exit(1);
161
+ }
162
+ // Handle 204 No Content
163
+ if (response.status === 204) {
164
+ return {};
165
+ }
166
+ return response.json();
167
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Create a pull request
4
+ * Usage: npx tsx create_pr.ts <owner/repo> <head> <base> <title> [body]
5
+ */
6
+ import { githubRequest } from "../../../lib/utils.js";
7
+ const [repoPath, head, base, title, body] = process.argv.slice(2);
8
+ if (!repoPath || !head || !base || !title) {
9
+ console.log("Usage: npx tsx create_pr.ts <owner/repo> <head_branch> <base_branch> <title> [body]");
10
+ console.log("Example: npx tsx create_pr.ts myorg/app feature-123 main 'feat: Add feature'");
11
+ process.exit(1);
12
+ }
13
+ const pr = await githubRequest(`/repos/${repoPath}/pulls`, {
14
+ method: "POST",
15
+ body: JSON.stringify({
16
+ title,
17
+ head,
18
+ base,
19
+ body: body ?? "",
20
+ }),
21
+ });
22
+ console.log(`✓ Created PR #${pr.number}: ${pr.title}`);
23
+ console.log(` URL: ${pr.html_url}`);
24
+ console.log(` ${head} → ${base}`);
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Get repository information
4
+ * Usage: npx tsx get_repo_info.ts <owner/repo>
5
+ */
6
+ import { githubRequest } from "../../../lib/utils.js";
7
+ const repoPath = process.argv[2];
8
+ if (!repoPath) {
9
+ console.log("Usage: npx tsx get_repo_info.ts <owner/repo>");
10
+ console.log("Example: npx tsx get_repo_info.ts octocat/Hello-World");
11
+ process.exit(1);
12
+ }
13
+ const repo = await githubRequest(`/repos/${repoPath}`);
14
+ console.log(`Repository: ${repo.full_name}`);
15
+ console.log("=".repeat(50));
16
+ console.log(`Description: ${repo.description ?? "N/A"}`);
17
+ console.log(`Visibility: ${repo.private ? "Private" : "Public"}`);
18
+ console.log(`Default: ${repo.default_branch}`);
19
+ console.log(`Language: ${repo.language ?? "N/A"}`);
20
+ console.log(`Stars: ${repo.stargazers_count}`);
21
+ console.log(`Forks: ${repo.forks_count}`);
22
+ console.log(`Clone URL: ${repo.clone_url}`);
23
+ console.log(`SSH URL: ${repo.ssh_url}`);
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * List pull requests for a repository
4
+ * Usage: npx tsx list_prs.ts <owner/repo> [state]
5
+ */
6
+ import { githubRequest } from "../../../lib/utils.js";
7
+ const repoPath = process.argv[2];
8
+ const state = process.argv[3] ?? "open";
9
+ if (!repoPath) {
10
+ console.log("Usage: npx tsx list_prs.ts <owner/repo> [state]");
11
+ console.log("States: open, closed, all (default: open)");
12
+ process.exit(1);
13
+ }
14
+ const prs = await githubRequest(`/repos/${repoPath}/pulls?state=${state}&per_page=30`);
15
+ console.log(`Pull Requests for ${repoPath} (${state}):`);
16
+ console.log("=".repeat(50));
17
+ if (prs.length === 0) {
18
+ console.log("No pull requests found.");
19
+ process.exit(0);
20
+ }
21
+ for (const pr of prs) {
22
+ const draft = pr.draft ? "📝" : "";
23
+ console.log(`#${pr.number} ${draft} ${pr.title}`);
24
+ console.log(` ${pr.head.ref} → ${pr.base.ref} by @${pr.user.login}`);
25
+ console.log(` ${pr.html_url}`);
26
+ console.log();
27
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * List repositories for an organization or user
4
+ * Usage: npx tsx list_repos.ts [org_or_user]
5
+ */
6
+ import { githubRequest, getActiveWorkspace } from "../../../lib/utils.js";
7
+ let orgOrUser = process.argv[2];
8
+ if (!orgOrUser) {
9
+ const ws = getActiveWorkspace();
10
+ if (ws.github_org) {
11
+ orgOrUser = ws.github_org;
12
+ }
13
+ }
14
+ let endpoint = "/user/repos?per_page=100";
15
+ if (orgOrUser) {
16
+ endpoint = `/orgs/${orgOrUser}/repos?per_page=100`;
17
+ }
18
+ console.log(`Fetching repositories from: ${endpoint}`);
19
+ try {
20
+ let repos = await githubRequest(endpoint);
21
+ // If org fails, try as user
22
+ if (repos.length === 0 && orgOrUser) {
23
+ repos = await githubRequest(`/users/${orgOrUser}/repos?per_page=100`);
24
+ }
25
+ console.log(`Repositories${orgOrUser ? ` for ${orgOrUser}` : ""}:`);
26
+ console.log("=".repeat(50));
27
+ for (const repo of repos) {
28
+ const icon = repo.private ? "🔒" : "🌐";
29
+ console.log(`${icon} ${repo.full_name}`);
30
+ if (repo.description) {
31
+ console.log(` ${repo.description.slice(0, 60)}...`);
32
+ }
33
+ console.log();
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.error("Failed to list repositories:", error);
38
+ process.exit(1);
39
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Add a comment to a JIRA ticket
4
+ * Usage: npx tsx add_comment.ts <ticket_key> <comment>
5
+ */
6
+ import { jiraRequest } from "../../../lib/utils.js";
7
+ const ticketKey = process.argv[2];
8
+ const commentText = process.argv.slice(3).join(" ");
9
+ if (!ticketKey || !commentText) {
10
+ console.log("Usage: npx tsx add_comment.ts <ticket_key> <comment>");
11
+ console.log("Example: npx tsx add_comment.ts PROJ-123 'PR created: https://...'");
12
+ process.exit(1);
13
+ }
14
+ // JIRA API v3 uses ADF format for comments
15
+ const body = {
16
+ body: {
17
+ type: "doc",
18
+ version: 1,
19
+ content: [
20
+ {
21
+ type: "paragraph",
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: commentText,
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ },
31
+ };
32
+ await jiraRequest(`/rest/api/3/issue/${ticketKey}/comment`, {
33
+ method: "POST",
34
+ body: JSON.stringify(body),
35
+ });
36
+ console.log(`✓ Added comment to ${ticketKey}`);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Get JIRA ticket details
4
+ * Usage: npx tsx get_ticket.ts <ticket_key>
5
+ */
6
+ import { jiraRequest, getJiraConfig } from "../../../lib/utils.js";
7
+ function extractText(node) {
8
+ if (typeof node !== "object" || node === null)
9
+ return "";
10
+ const obj = node;
11
+ if (obj.type === "text" && typeof obj.text === "string") {
12
+ return obj.text;
13
+ }
14
+ if (Array.isArray(obj.content)) {
15
+ return obj.content.map(extractText).join(" ");
16
+ }
17
+ return "";
18
+ }
19
+ const ticketKey = process.argv[2];
20
+ if (!ticketKey) {
21
+ console.log("Usage: npx tsx get_ticket.ts <ticket_key>");
22
+ console.log("Example: npx tsx get_ticket.ts PROJ-123");
23
+ process.exit(1);
24
+ }
25
+ const { url } = getJiraConfig();
26
+ const issue = await jiraRequest(`/rest/api/3/issue/${ticketKey}`);
27
+ const { fields } = issue;
28
+ console.log(`Ticket: ${issue.key}`);
29
+ console.log("=".repeat(60));
30
+ console.log(`Summary: ${fields.summary}`);
31
+ console.log(`Status: ${fields.status.name}`);
32
+ console.log(`Type: ${fields.issuetype.name}`);
33
+ console.log(`Priority: ${fields.priority?.name ?? "None"}`);
34
+ console.log(`Assignee: ${fields.assignee?.displayName ?? "Unassigned"}`);
35
+ console.log(`Reporter: ${fields.reporter?.displayName ?? "Unknown"}`);
36
+ console.log(`Created: ${fields.created.slice(0, 10)}`);
37
+ console.log(`Updated: ${fields.updated.slice(0, 10)}`);
38
+ console.log();
39
+ if (fields.description) {
40
+ console.log("Description:");
41
+ console.log("-".repeat(40));
42
+ console.log(extractText(fields.description));
43
+ }
44
+ console.log();
45
+ console.log(`URL: ${url}/browse/${issue.key}`);
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * List JIRA tickets assigned to current user
4
+ * Usage: npx tsx list_tickets.ts [status]
5
+ */
6
+ import { jiraRequest } from "../../../lib/utils.js";
7
+ const statusFilter = process.argv[2];
8
+ let jql = "assignee = currentUser() ORDER BY updated DESC";
9
+ if (statusFilter) {
10
+ jql = `assignee = currentUser() AND status = "${statusFilter}" ORDER BY updated DESC`;
11
+ }
12
+ const data = await jiraRequest("/rest/api/3/search/jql", {
13
+ method: "POST",
14
+ body: JSON.stringify({
15
+ jql,
16
+ maxResults: 20,
17
+ fields: ["summary", "status", "priority"],
18
+ }),
19
+ });
20
+ const issues = data.issues;
21
+ console.log(`Your Tickets (${issues.length} found):`);
22
+ console.log("=".repeat(60));
23
+ if (issues.length === 0) {
24
+ console.log("No tickets found.");
25
+ process.exit(0);
26
+ }
27
+ const statusEmoji = {
28
+ "To Do": "📋",
29
+ "In Progress": "🔧",
30
+ "Code Review": "👀",
31
+ Done: "✅",
32
+ };
33
+ for (const issue of issues) {
34
+ const { key, fields } = issue;
35
+ const summary = fields.summary.slice(0, 50);
36
+ const status = fields.status.name;
37
+ const priority = fields.priority?.name ?? "None";
38
+ const emoji = statusEmoji[status] ?? "📌";
39
+ console.log(`${emoji} ${key}: ${summary}`);
40
+ console.log(` Status: ${status} | Priority: ${priority}`);
41
+ console.log();
42
+ }