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.
- package/GEMINI.md +92 -0
- package/README.md +145 -0
- package/cli/brn.ts +301 -0
- package/dist/cli/brn.js +274 -0
- package/dist/lib/utils.js +167 -0
- package/dist/skills/github/scripts/create_pr.js +24 -0
- package/dist/skills/github/scripts/get_repo_info.js +23 -0
- package/dist/skills/github/scripts/list_prs.js +27 -0
- package/dist/skills/github/scripts/list_repos.js +39 -0
- package/dist/skills/jira/scripts/add_comment.js +36 -0
- package/dist/skills/jira/scripts/get_ticket.js +45 -0
- package/dist/skills/jira/scripts/list_tickets.js +42 -0
- package/dist/skills/jira/scripts/update_ticket.js +30 -0
- package/dist/skills/workflow/scripts/start.js +75 -0
- package/dist/skills/workspace-manager/scripts/configure_workspace.js +59 -0
- package/dist/skills/workspace-manager/scripts/create_workspace.js +60 -0
- package/lib/utils.ts +236 -0
- package/package.json +46 -0
- package/skills/git-worktree/SKILL.md +49 -0
- package/skills/git-worktree/scripts/clone_repo.sh +53 -0
- package/skills/git-worktree/scripts/create_worktree.sh +66 -0
- package/skills/git-worktree/scripts/list_worktrees.sh +39 -0
- package/skills/git-worktree/scripts/remove_worktree.sh +47 -0
- package/skills/github/SKILL.md +51 -0
- package/skills/github/references/api_patterns.md +36 -0
- package/skills/github/scripts/create_pr.ts +38 -0
- package/skills/github/scripts/get_repo_info.ts +39 -0
- package/skills/github/scripts/list_prs.ts +45 -0
- package/skills/github/scripts/list_repos.ts +54 -0
- package/skills/jira/SKILL.md +50 -0
- package/skills/jira/references/api_patterns.md +59 -0
- package/skills/jira/scripts/add_comment.ts +41 -0
- package/skills/jira/scripts/get_ticket.ts +71 -0
- package/skills/jira/scripts/list_tickets.ts +64 -0
- package/skills/jira/scripts/update_ticket.ts +50 -0
- package/skills/workflow/SKILL.md +94 -0
- package/skills/workflow/references/coding_workflow.md +88 -0
- package/skills/workflow/references/planning_workflow.md +96 -0
- package/skills/workflow/references/review_workflow.md +104 -0
- package/skills/workflow/scripts/start.ts +93 -0
- package/skills/workspace-manager/SKILL.md +49 -0
- package/skills/workspace-manager/scripts/configure_workspace.sh +87 -0
- package/skills/workspace-manager/scripts/configure_workspace.ts +70 -0
- package/skills/workspace-manager/scripts/create_workspace.sh +66 -0
- package/skills/workspace-manager/scripts/create_workspace.ts +74 -0
- package/skills/workspace-manager/scripts/get_active_workspace.sh +24 -0
- package/skills/workspace-manager/scripts/list_workspaces.sh +31 -0
- package/skills/workspace-manager/scripts/switch_workspace.sh +38 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# List all worktrees for a repository
|
|
3
|
+
# Usage: list_worktrees.sh <repo_name>
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
REPO_NAME="$1"
|
|
8
|
+
|
|
9
|
+
if [ -z "$REPO_NAME" ]; then
|
|
10
|
+
echo "Usage: list_worktrees.sh <repo_name>"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Get the script directory to find workspace-manager
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
WORKSPACE_SCRIPT="$SCRIPT_DIR/../../workspace-manager/scripts/get_active_workspace.sh"
|
|
17
|
+
|
|
18
|
+
if [ ! -f "$WORKSPACE_SCRIPT" ]; then
|
|
19
|
+
echo "Error: workspace-manager skill not found"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Get workspace config
|
|
24
|
+
WORKSPACE_JSON=$("$WORKSPACE_SCRIPT" --json)
|
|
25
|
+
WORK_DIR=$(echo "$WORKSPACE_JSON" | yq -r '.path')
|
|
26
|
+
WORK_DIR="${WORK_DIR/#\~/$HOME}"
|
|
27
|
+
|
|
28
|
+
REPO_PATH="$WORK_DIR/$REPO_NAME"
|
|
29
|
+
|
|
30
|
+
if [ ! -d "$REPO_PATH" ]; then
|
|
31
|
+
echo "Error: Repository not found at $REPO_PATH"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
cd "$REPO_PATH"
|
|
36
|
+
|
|
37
|
+
echo "Worktrees for $REPO_NAME:"
|
|
38
|
+
echo "========================="
|
|
39
|
+
git worktree list
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Remove a worktree
|
|
3
|
+
# Usage: remove_worktree.sh <repo_name> <branch_name>
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
REPO_NAME="$1"
|
|
8
|
+
BRANCH_NAME="$2"
|
|
9
|
+
|
|
10
|
+
if [ -z "$REPO_NAME" ] || [ -z "$BRANCH_NAME" ]; then
|
|
11
|
+
echo "Usage: remove_worktree.sh <repo_name> <branch_name>"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Get the script directory to find workspace-manager
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
WORKSPACE_SCRIPT="$SCRIPT_DIR/../../workspace-manager/scripts/get_active_workspace.sh"
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$WORKSPACE_SCRIPT" ]; then
|
|
20
|
+
echo "Error: workspace-manager skill not found"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Get workspace config
|
|
25
|
+
WORKSPACE_JSON=$("$WORKSPACE_SCRIPT" --json)
|
|
26
|
+
WORK_DIR=$(echo "$WORKSPACE_JSON" | yq -r '.path')
|
|
27
|
+
WORK_DIR="${WORK_DIR/#\~/$HOME}"
|
|
28
|
+
|
|
29
|
+
REPO_PATH="$WORK_DIR/$REPO_NAME"
|
|
30
|
+
WORKTREE_PATH="$WORK_DIR/${REPO_NAME}-worktrees/$BRANCH_NAME"
|
|
31
|
+
|
|
32
|
+
if [ ! -d "$REPO_PATH" ]; then
|
|
33
|
+
echo "Error: Repository not found at $REPO_PATH"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ ! -d "$WORKTREE_PATH" ]; then
|
|
38
|
+
echo "Error: Worktree not found at $WORKTREE_PATH"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
cd "$REPO_PATH"
|
|
43
|
+
|
|
44
|
+
echo "Removing worktree for '$BRANCH_NAME'..."
|
|
45
|
+
git worktree remove "$WORKTREE_PATH"
|
|
46
|
+
|
|
47
|
+
echo "✓ Removed worktree at $WORKTREE_PATH"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brn:github
|
|
3
|
+
description: |
|
|
4
|
+
Interact with GitHub API for repository and pull request management.
|
|
5
|
+
Use when: (1) Listing repositories, (2) Getting repository info, (3) Creating PRs.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# GitHub Integration
|
|
9
|
+
|
|
10
|
+
## Description
|
|
11
|
+
The GitHub skill provides tools to interact with the GitHub API directly from the CLI. It supports authentication via personal access tokens configured in the workspace.
|
|
12
|
+
|
|
13
|
+
## Available Scripts
|
|
14
|
+
|
|
15
|
+
### `list_repos`
|
|
16
|
+
Lists repositories for a specified organization or user. Useful for discovery.
|
|
17
|
+
|
|
18
|
+
* **Usage**: `brn github list_repos [org_or_user]`
|
|
19
|
+
* **Arguments**:
|
|
20
|
+
* `org_or_user` (optional): The name of the organization or user to list repositories for. If omitted, lists repositories for the authenticated user.
|
|
21
|
+
* **Example**: `brn github list_repos my-org`
|
|
22
|
+
|
|
23
|
+
### `get_repo_info`
|
|
24
|
+
Retrieves detailed information about a specific repository.
|
|
25
|
+
|
|
26
|
+
* **Usage**: `brn github get_repo_info <owner/repo>`
|
|
27
|
+
* **Arguments**:
|
|
28
|
+
* `owner/repo`: The full repository name (e.g., `facebook/react`).
|
|
29
|
+
* **Example**: `brn github get_repo_info my-org/my-project`
|
|
30
|
+
|
|
31
|
+
### `create_pr`
|
|
32
|
+
Creates a Pull Request on GitHub.
|
|
33
|
+
|
|
34
|
+
* **Usage**: `brn github create_pr <owner/repo> <head> <base> <title>`
|
|
35
|
+
* **Arguments**:
|
|
36
|
+
* `owner/repo`: The full repository name.
|
|
37
|
+
* `head`: The name of the branch where your changes are implemented.
|
|
38
|
+
* `base`: The name of the branch you want to merge into (e.g., `main`).
|
|
39
|
+
* `title`: The title of the Pull Request.
|
|
40
|
+
* **Example**: `brn github create_pr my-org/my-project feature/login main "feat: Add login page"`
|
|
41
|
+
|
|
42
|
+
### `list_prs`
|
|
43
|
+
Lists open Pull Requests for a repository.
|
|
44
|
+
|
|
45
|
+
* **Usage**: `brn github list_prs <owner/repo>`
|
|
46
|
+
* **Arguments**:
|
|
47
|
+
* `owner/repo`: The full repository name.
|
|
48
|
+
* **Example**: `brn github list_prs my-org/my-project`
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
See [references/api_patterns.md](references/api_patterns.md) for error handling and patterns.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# GitHub API Patterns
|
|
2
|
+
|
|
3
|
+
Common patterns for GitHub API integration.
|
|
4
|
+
|
|
5
|
+
## Authentication
|
|
6
|
+
|
|
7
|
+
All requests use Bearer token authentication:
|
|
8
|
+
```
|
|
9
|
+
Authorization: Bearer <token>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Common Endpoints
|
|
13
|
+
|
|
14
|
+
| Endpoint | Method | Description |
|
|
15
|
+
|----------|--------|-------------|
|
|
16
|
+
| `/user/repos` | GET | List authenticated user's repos |
|
|
17
|
+
| `/orgs/{org}/repos` | GET | List org repos |
|
|
18
|
+
| `/repos/{owner}/{repo}` | GET | Get repo info |
|
|
19
|
+
| `/repos/{owner}/{repo}/pulls` | GET | List PRs |
|
|
20
|
+
| `/repos/{owner}/{repo}/pulls` | POST | Create PR |
|
|
21
|
+
| `/repos/{owner}/{repo}/issues` | GET | List issues |
|
|
22
|
+
|
|
23
|
+
## Error Handling
|
|
24
|
+
|
|
25
|
+
| Code | Meaning | Action |
|
|
26
|
+
|------|---------|--------|
|
|
27
|
+
| 401 | Bad credentials | Check token |
|
|
28
|
+
| 403 | Forbidden | Check token permissions |
|
|
29
|
+
| 404 | Not found | Check repo name/permissions |
|
|
30
|
+
| 422 | Validation failed | Check request body |
|
|
31
|
+
|
|
32
|
+
## Rate Limiting
|
|
33
|
+
|
|
34
|
+
- Authenticated: 5000 requests/hour
|
|
35
|
+
- Check `X-RateLimit-Remaining` header
|
|
36
|
+
- If rate limited, wait until `X-RateLimit-Reset` timestamp
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
8
|
+
interface PR {
|
|
9
|
+
number: number;
|
|
10
|
+
title: string;
|
|
11
|
+
html_url: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const [repoPath, head, base, title, body] = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
if (!repoPath || !head || !base || !title) {
|
|
17
|
+
console.log(
|
|
18
|
+
"Usage: npx tsx create_pr.ts <owner/repo> <head_branch> <base_branch> <title> [body]"
|
|
19
|
+
);
|
|
20
|
+
console.log(
|
|
21
|
+
"Example: npx tsx create_pr.ts myorg/app feature-123 main 'feat: Add feature'"
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const pr = await githubRequest<PR>(`/repos/${repoPath}/pulls`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
title,
|
|
30
|
+
head,
|
|
31
|
+
base,
|
|
32
|
+
body: body ?? "",
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(`✓ Created PR #${pr.number}: ${pr.title}`);
|
|
37
|
+
console.log(` URL: ${pr.html_url}`);
|
|
38
|
+
console.log(` ${head} → ${base}`);
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
|
|
8
|
+
interface Repo {
|
|
9
|
+
full_name: string;
|
|
10
|
+
description: string | null;
|
|
11
|
+
private: boolean;
|
|
12
|
+
default_branch: string;
|
|
13
|
+
language: string | null;
|
|
14
|
+
stargazers_count: number;
|
|
15
|
+
forks_count: number;
|
|
16
|
+
clone_url: string;
|
|
17
|
+
ssh_url: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const repoPath = process.argv[2];
|
|
21
|
+
|
|
22
|
+
if (!repoPath) {
|
|
23
|
+
console.log("Usage: npx tsx get_repo_info.ts <owner/repo>");
|
|
24
|
+
console.log("Example: npx tsx get_repo_info.ts octocat/Hello-World");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const repo = await githubRequest<Repo>(`/repos/${repoPath}`);
|
|
29
|
+
|
|
30
|
+
console.log(`Repository: ${repo.full_name}`);
|
|
31
|
+
console.log("=".repeat(50));
|
|
32
|
+
console.log(`Description: ${repo.description ?? "N/A"}`);
|
|
33
|
+
console.log(`Visibility: ${repo.private ? "Private" : "Public"}`);
|
|
34
|
+
console.log(`Default: ${repo.default_branch}`);
|
|
35
|
+
console.log(`Language: ${repo.language ?? "N/A"}`);
|
|
36
|
+
console.log(`Stars: ${repo.stargazers_count}`);
|
|
37
|
+
console.log(`Forks: ${repo.forks_count}`);
|
|
38
|
+
console.log(`Clone URL: ${repo.clone_url}`);
|
|
39
|
+
console.log(`SSH URL: ${repo.ssh_url}`);
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
|
|
8
|
+
interface PR {
|
|
9
|
+
number: number;
|
|
10
|
+
title: string;
|
|
11
|
+
draft: boolean;
|
|
12
|
+
html_url: string;
|
|
13
|
+
head: { ref: string };
|
|
14
|
+
base: { ref: string };
|
|
15
|
+
user: { login: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const repoPath = process.argv[2];
|
|
19
|
+
const state = process.argv[3] ?? "open";
|
|
20
|
+
|
|
21
|
+
if (!repoPath) {
|
|
22
|
+
console.log("Usage: npx tsx list_prs.ts <owner/repo> [state]");
|
|
23
|
+
console.log("States: open, closed, all (default: open)");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prs = await githubRequest<PR[]>(
|
|
28
|
+
`/repos/${repoPath}/pulls?state=${state}&per_page=30`
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
console.log(`Pull Requests for ${repoPath} (${state}):`);
|
|
32
|
+
console.log("=".repeat(50));
|
|
33
|
+
|
|
34
|
+
if (prs.length === 0) {
|
|
35
|
+
console.log("No pull requests found.");
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const pr of prs) {
|
|
40
|
+
const draft = pr.draft ? "📝" : "";
|
|
41
|
+
console.log(`#${pr.number} ${draft} ${pr.title}`);
|
|
42
|
+
console.log(` ${pr.head.ref} → ${pr.base.ref} by @${pr.user.login}`);
|
|
43
|
+
console.log(` ${pr.html_url}`);
|
|
44
|
+
console.log();
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
|
|
8
|
+
interface Repo {
|
|
9
|
+
full_name: string;
|
|
10
|
+
private: boolean;
|
|
11
|
+
description: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let orgOrUser = process.argv[2];
|
|
15
|
+
|
|
16
|
+
if (!orgOrUser) {
|
|
17
|
+
const ws = getActiveWorkspace();
|
|
18
|
+
if (ws.github_org) {
|
|
19
|
+
orgOrUser = ws.github_org;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let endpoint = "/user/repos?per_page=100";
|
|
24
|
+
if (orgOrUser) {
|
|
25
|
+
endpoint = `/orgs/${orgOrUser}/repos?per_page=100`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`Fetching repositories from: ${endpoint}`);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
let repos = await githubRequest<Repo[]>(endpoint);
|
|
32
|
+
|
|
33
|
+
// If org fails, try as user
|
|
34
|
+
if (repos.length === 0 && orgOrUser) {
|
|
35
|
+
repos = await githubRequest<Repo[]>(
|
|
36
|
+
`/users/${orgOrUser}/repos?per_page=100`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`Repositories${orgOrUser ? ` for ${orgOrUser}` : ""}:`);
|
|
41
|
+
console.log("=".repeat(50));
|
|
42
|
+
|
|
43
|
+
for (const repo of repos) {
|
|
44
|
+
const icon = repo.private ? "🔒" : "🌐";
|
|
45
|
+
console.log(`${icon} ${repo.full_name}`);
|
|
46
|
+
if (repo.description) {
|
|
47
|
+
console.log(` ${repo.description.slice(0, 60)}...`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("Failed to list repositories:", error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brn:jira
|
|
3
|
+
description: |
|
|
4
|
+
Interact with JIRA API for ticket management and tracking.
|
|
5
|
+
Use when: (1) Listing assigned tickets, (2) Getting details, (3) Updating status.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# JIRA Integration
|
|
9
|
+
|
|
10
|
+
## Description
|
|
11
|
+
The JIRA skill allows you to manage your tasks without leaving the terminal. It connects to your JIRA instance using the credentials configured in your workspace.
|
|
12
|
+
|
|
13
|
+
## Available Scripts
|
|
14
|
+
|
|
15
|
+
### `list_tickets`
|
|
16
|
+
Lists JIRA tickets assigned to the current user. Can be filtered by status.
|
|
17
|
+
|
|
18
|
+
* **Usage**: `brn jira list_tickets [status]`
|
|
19
|
+
* **Arguments**:
|
|
20
|
+
* `status` (optional): Filter tickets by status (e.g., "In Progress", "To Do").
|
|
21
|
+
* **Example**: `brn jira list_tickets "In Progress"`
|
|
22
|
+
|
|
23
|
+
### `get_ticket`
|
|
24
|
+
Retrieves detailed information about a specific JIRA ticket.
|
|
25
|
+
|
|
26
|
+
* **Usage**: `brn jira get_ticket <ticket_key>`
|
|
27
|
+
* **Arguments**:
|
|
28
|
+
* `ticket_key`: The JIRA issue key (e.g., `PROJ-123`).
|
|
29
|
+
* **Example**: `brn jira get_ticket PROJ-123`
|
|
30
|
+
|
|
31
|
+
### `update_ticket`
|
|
32
|
+
Updates the status (transition) of a JIRA ticket.
|
|
33
|
+
|
|
34
|
+
* **Usage**: `brn jira update_ticket <ticket_key> <transition>`
|
|
35
|
+
* **Arguments**:
|
|
36
|
+
* `ticket_key`: The JIRA issue key.
|
|
37
|
+
* `transition`: The name of the transition/status to move to (e.g., "In Progress", "Done").
|
|
38
|
+
* **Example**: `brn jira update_ticket PROJ-123 "In Progress"`
|
|
39
|
+
|
|
40
|
+
### `add_comment`
|
|
41
|
+
Adds a comment to a JIRA ticket.
|
|
42
|
+
|
|
43
|
+
* **Usage**: `brn jira add_comment <ticket_key> <comment>`
|
|
44
|
+
* **Arguments**:
|
|
45
|
+
* `ticket_key`: The JIRA issue key.
|
|
46
|
+
* `comment`: The text content of the comment.
|
|
47
|
+
* **Example**: `brn jira add_comment PROJ-123 "Released in version 1.2.0"`
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
See [references/api_patterns.md](references/api_patterns.md) for patterns.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# JIRA API Patterns
|
|
2
|
+
|
|
3
|
+
Common patterns for JIRA Cloud API integration.
|
|
4
|
+
|
|
5
|
+
## Authentication
|
|
6
|
+
|
|
7
|
+
JIRA Cloud uses Basic Auth with email and API token:
|
|
8
|
+
```
|
|
9
|
+
Authorization: Basic base64(email:api_token)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Create API token at: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
13
|
+
|
|
14
|
+
## Common Endpoints
|
|
15
|
+
|
|
16
|
+
| Endpoint | Method | Description |
|
|
17
|
+
|----------|--------|-------------|
|
|
18
|
+
| `/rest/api/3/search` | GET | Search issues with JQL |
|
|
19
|
+
| `/rest/api/3/issue/{key}` | GET | Get issue details |
|
|
20
|
+
| `/rest/api/3/issue/{key}/transitions` | GET | Get available transitions |
|
|
21
|
+
| `/rest/api/3/issue/{key}/transitions` | POST | Transition issue |
|
|
22
|
+
| `/rest/api/3/issue/{key}/comment` | POST | Add comment |
|
|
23
|
+
|
|
24
|
+
## JQL Examples
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
# My open tickets
|
|
28
|
+
assignee = currentUser() AND status != Done
|
|
29
|
+
|
|
30
|
+
# In progress in a project
|
|
31
|
+
project = PROJ AND status = "In Progress"
|
|
32
|
+
|
|
33
|
+
# Recently updated
|
|
34
|
+
assignee = currentUser() ORDER BY updated DESC
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Error Handling
|
|
38
|
+
|
|
39
|
+
| Code | Meaning | Action |
|
|
40
|
+
|------|---------|--------|
|
|
41
|
+
| 401 | Unauthorized | Check email/token |
|
|
42
|
+
| 403 | Forbidden | Check permissions |
|
|
43
|
+
| 404 | Not found | Check issue key |
|
|
44
|
+
|
|
45
|
+
## ADF Format
|
|
46
|
+
|
|
47
|
+
JIRA API v3 uses Atlassian Document Format for rich text:
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"type": "doc",
|
|
51
|
+
"version": 1,
|
|
52
|
+
"content": [
|
|
53
|
+
{
|
|
54
|
+
"type": "paragraph",
|
|
55
|
+
"content": [{"type": "text", "text": "Hello"}]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
|
|
8
|
+
const ticketKey = process.argv[2];
|
|
9
|
+
const commentText = process.argv.slice(3).join(" ");
|
|
10
|
+
|
|
11
|
+
if (!ticketKey || !commentText) {
|
|
12
|
+
console.log("Usage: npx tsx add_comment.ts <ticket_key> <comment>");
|
|
13
|
+
console.log("Example: npx tsx add_comment.ts PROJ-123 'PR created: https://...'");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// JIRA API v3 uses ADF format for comments
|
|
18
|
+
const body = {
|
|
19
|
+
body: {
|
|
20
|
+
type: "doc",
|
|
21
|
+
version: 1,
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "paragraph",
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: commentText,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
await jiraRequest(`/rest/api/3/issue/${ticketKey}/comment`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(`✓ Added comment to ${ticketKey}`);
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
|
|
8
|
+
interface Issue {
|
|
9
|
+
key: string;
|
|
10
|
+
fields: {
|
|
11
|
+
summary: string;
|
|
12
|
+
status: { name: string };
|
|
13
|
+
issuetype: { name: string };
|
|
14
|
+
priority: { name: string } | null;
|
|
15
|
+
assignee: { displayName: string } | null;
|
|
16
|
+
reporter: { displayName: string } | null;
|
|
17
|
+
created: string;
|
|
18
|
+
updated: string;
|
|
19
|
+
description: unknown;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractText(node: unknown): string {
|
|
24
|
+
if (typeof node !== "object" || node === null) return "";
|
|
25
|
+
|
|
26
|
+
const obj = node as Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
if (obj.type === "text" && typeof obj.text === "string") {
|
|
29
|
+
return obj.text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(obj.content)) {
|
|
33
|
+
return obj.content.map(extractText).join(" ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ticketKey = process.argv[2];
|
|
40
|
+
|
|
41
|
+
if (!ticketKey) {
|
|
42
|
+
console.log("Usage: npx tsx get_ticket.ts <ticket_key>");
|
|
43
|
+
console.log("Example: npx tsx get_ticket.ts PROJ-123");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { url } = getJiraConfig();
|
|
48
|
+
const issue = await jiraRequest<Issue>(`/rest/api/3/issue/${ticketKey}`);
|
|
49
|
+
|
|
50
|
+
const { fields } = issue;
|
|
51
|
+
|
|
52
|
+
console.log(`Ticket: ${issue.key}`);
|
|
53
|
+
console.log("=".repeat(60));
|
|
54
|
+
console.log(`Summary: ${fields.summary}`);
|
|
55
|
+
console.log(`Status: ${fields.status.name}`);
|
|
56
|
+
console.log(`Type: ${fields.issuetype.name}`);
|
|
57
|
+
console.log(`Priority: ${fields.priority?.name ?? "None"}`);
|
|
58
|
+
console.log(`Assignee: ${fields.assignee?.displayName ?? "Unassigned"}`);
|
|
59
|
+
console.log(`Reporter: ${fields.reporter?.displayName ?? "Unknown"}`);
|
|
60
|
+
console.log(`Created: ${fields.created.slice(0, 10)}`);
|
|
61
|
+
console.log(`Updated: ${fields.updated.slice(0, 10)}`);
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
if (fields.description) {
|
|
65
|
+
console.log("Description:");
|
|
66
|
+
console.log("-".repeat(40));
|
|
67
|
+
console.log(extractText(fields.description));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(`URL: ${url}/browse/${issue.key}`);
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
|
|
8
|
+
interface Issue {
|
|
9
|
+
key: string;
|
|
10
|
+
fields: {
|
|
11
|
+
summary: string;
|
|
12
|
+
status: { name: string };
|
|
13
|
+
priority: { name: string } | null;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface SearchResult {
|
|
18
|
+
issues: Issue[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const statusFilter = process.argv[2];
|
|
22
|
+
|
|
23
|
+
let jql = "assignee = currentUser() ORDER BY updated DESC";
|
|
24
|
+
if (statusFilter) {
|
|
25
|
+
jql = `assignee = currentUser() AND status = "${statusFilter}" ORDER BY updated DESC`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await jiraRequest<SearchResult>("/rest/api/3/search/jql", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
jql,
|
|
32
|
+
maxResults: 20,
|
|
33
|
+
fields: ["summary", "status", "priority"],
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const issues = data.issues;
|
|
38
|
+
|
|
39
|
+
console.log(`Your Tickets (${issues.length} found):`);
|
|
40
|
+
console.log("=".repeat(60));
|
|
41
|
+
|
|
42
|
+
if (issues.length === 0) {
|
|
43
|
+
console.log("No tickets found.");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const statusEmoji: Record<string, string> = {
|
|
48
|
+
"To Do": "📋",
|
|
49
|
+
"In Progress": "🔧",
|
|
50
|
+
"Code Review": "👀",
|
|
51
|
+
Done: "✅",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
for (const issue of issues) {
|
|
55
|
+
const { key, fields } = issue;
|
|
56
|
+
const summary = fields.summary.slice(0, 50);
|
|
57
|
+
const status = fields.status.name;
|
|
58
|
+
const priority = fields.priority?.name ?? "None";
|
|
59
|
+
const emoji = statusEmoji[status] ?? "📌";
|
|
60
|
+
|
|
61
|
+
console.log(`${emoji} ${key}: ${summary}`);
|
|
62
|
+
console.log(` Status: ${status} | Priority: ${priority}`);
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Update JIRA ticket status via transition
|
|
4
|
+
* Usage: npx tsx update_ticket.ts <ticket_key> <target_status>
|
|
5
|
+
*/
|
|
6
|
+
import { jiraRequest } from "../../../lib/utils.js";
|
|
7
|
+
|
|
8
|
+
interface Transition {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface TransitionsResponse {
|
|
14
|
+
transitions: Transition[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [ticketKey, targetStatus] = process.argv.slice(2);
|
|
18
|
+
|
|
19
|
+
if (!ticketKey || !targetStatus) {
|
|
20
|
+
console.log("Usage: npx tsx update_ticket.ts <ticket_key> <target_status>");
|
|
21
|
+
console.log("Example: npx tsx update_ticket.ts PROJ-123 'In Progress'");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get available transitions
|
|
26
|
+
const { transitions } = await jiraRequest<TransitionsResponse>(
|
|
27
|
+
`/rest/api/3/issue/${ticketKey}/transitions`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Find matching transition (case-insensitive)
|
|
31
|
+
const transition = transitions.find(
|
|
32
|
+
(t) => t.name.toLowerCase() === targetStatus.toLowerCase()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!transition) {
|
|
36
|
+
console.error(`Error: Transition '${targetStatus}' not available`);
|
|
37
|
+
console.error("Available transitions:");
|
|
38
|
+
for (const t of transitions) {
|
|
39
|
+
console.error(` - ${t.name}`);
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Perform transition
|
|
45
|
+
await jiraRequest(`/rest/api/3/issue/${ticketKey}/transitions`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: JSON.stringify({ transition: { id: transition.id } }),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(`✓ Transitioned ${ticketKey} to '${targetStatus}'`);
|