azdo-cli 0.2.0-001-azdo-cli-base.5 → 0.2.0-002-get-item-command.10

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 (3) hide show
  1. package/README.md +47 -54
  2. package/dist/index.js +270 -2
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1,79 +1,72 @@
1
- # bd - Beads
1
+ # azdo-cli
2
2
 
3
- **Distributed, git-backed graph issue tracker for AI agents.**
3
+ A command-line interface for Azure DevOps.
4
4
 
5
- **Platforms:** macOS, Linux, Windows, FreeBSD
5
+ [![npm version](https://img.shields.io/npm/v/azdo-cli)](https://www.npmjs.com/package/azdo-cli)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
7
 
7
- [![License](https://img.shields.io/github/license/steveyegge/beads)](LICENSE)
8
- [![Go Report Card](https://goreportcard.com/badge/github.com/steveyegge/beads)](https://goreportcard.com/report/github.com/steveyegge/beads)
9
- [![Release](https://img.shields.io/github/v/release/steveyegge/beads)](https://github.com/steveyegge/beads/releases)
10
- [![npm version](https://img.shields.io/npm/v/@beads/bd)](https://www.npmjs.com/package/@beads/bd)
11
- [![PyPI](https://img.shields.io/pypi/v/beads-mcp)](https://pypi.org/project/beads-mcp/)
8
+ ## Installation
12
9
 
13
- Beads provides a persistent, structured memory for coding agents. It replaces messy markdown plans with a dependency-aware graph, allowing agents to handle long-horizon tasks without losing context.
10
+ ```bash
11
+ npm install -g azdo-cli
12
+ ```
14
13
 
15
- ## ⚡ Quick Start
14
+ ## Usage
16
15
 
17
16
  ```bash
18
- # Install beads CLI (system-wide - don't clone this repo into your project)
19
- curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
20
-
21
- # Initialize in YOUR project
22
- cd your-project
23
- bd init
17
+ # Show help
18
+ azdo --help
24
19
 
25
- # Tell your agent
26
- echo "Use 'bd' for task tracking" >> AGENTS.md
20
+ # Show version
21
+ azdo --version
22
+ azdo -v
27
23
  ```
28
24
 
29
- **Note:** Beads is a CLI tool you install once and use everywhere. You don't need to clone this repository into your project.
25
+ ## Current Status
30
26
 
31
- ## 🛠 Features
27
+ This project is in early development (v0.2.0). The base CLI scaffold is in place with support for `--help` and `--version`. Azure DevOps commands (work items, pipelines, repos, etc.) will be added in future releases.
32
28
 
33
- * **Git as Database:** Issues stored as JSONL in `.beads/`. Versioned, branched, and merged like code.
34
- * **Agent-Optimized:** JSON output, dependency tracking, and auto-ready task detection.
35
- * **Zero Conflict:** Hash-based IDs (`bd-a1b2`) prevent merge collisions in multi-agent/multi-branch workflows.
36
- * **Invisible Infrastructure:** SQLite local cache for speed; background daemon for auto-sync.
37
- * **Compaction:** Semantic "memory decay" summarizes old closed tasks to save context window.
29
+ ## Development
38
30
 
39
- ## 📖 Essential Commands
40
-
41
- | Command | Action |
42
- | --- | --- |
43
- | `bd ready` | List tasks with no open blockers. |
44
- | `bd create "Title" -p 0` | Create a P0 task. |
45
- | `bd update <id> --claim` | Atomically claim a task (sets assignee + in_progress). |
46
- | `bd dep add <child> <parent>` | Link tasks (blocks, related, parent-child). |
47
- | `bd show <id>` | View task details and audit trail. |
31
+ ### Prerequisites
48
32
 
49
- ## 🔗 Hierarchy & Workflow
33
+ - Node.js LTS (20+)
34
+ - npm
50
35
 
51
- Beads supports hierarchical IDs for epics:
36
+ ### Setup
52
37
 
53
- * `bd-a3f8` (Epic)
54
- * `bd-a3f8.1` (Task)
55
- * `bd-a3f8.1.1` (Sub-task)
56
-
57
- **Stealth Mode:** Run `bd init --stealth` to use Beads locally without committing files to the main repo. Perfect for personal use on shared projects.
38
+ ```bash
39
+ git clone https://github.com/alkampfergit/azdo-cli.git
40
+ cd azdo-cli
41
+ npm install
42
+ ```
58
43
 
59
- **Contributor vs Maintainer:** When working on open-source projects:
44
+ ### Scripts
60
45
 
61
- * **Contributors** (forked repos): Run `bd init --contributor` to route planning issues to a separate repo (e.g., `~/.beads-planning`). Keeps experimental work out of PRs.
62
- * **Maintainers** (write access): Beads auto-detects maintainer role via SSH URLs or HTTPS with credentials. Only need `git config beads.role maintainer` if using GitHub HTTPS without credentials but you have write access.
46
+ | Command | Description |
47
+ | --- | --- |
48
+ | `npm run build` | Build the CLI with tsup |
49
+ | `npm test` | Run tests with vitest |
50
+ | `npm run lint` | Lint source files with ESLint |
51
+ | `npm run typecheck` | Type-check with tsc (no emit) |
52
+ | `npm run format` | Check formatting with Prettier |
63
53
 
64
- ## 📦 Installation
54
+ ### Tech Stack
65
55
 
66
- * **npm:** `npm install -g @beads/bd`
67
- * **Homebrew:** `brew install beads`
68
- * **Go:** `go install github.com/steveyegge/beads/cmd/bd@latest`
56
+ - **TypeScript 5.x** (strict mode, ES modules)
57
+ - **commander.js** CLI framework
58
+ - **tsup** Bundler (single-file ESM output)
59
+ - **vitest** — Test runner
60
+ - **ESLint + Prettier** — Linting and formatting
69
61
 
70
- **Requirements:** Linux, FreeBSD, macOS, or Windows.
62
+ ### Branch Strategy
71
63
 
72
- ## 🌐 Community Tools
64
+ This project follows **GitFlow**:
73
65
 
74
- See [docs/COMMUNITY_TOOLS.md](docs/COMMUNITY_TOOLS.md) for a curated list of community-built UIs, extensions, and integrationsincluding terminal interfaces, web UIs, editor extensions, and native apps.
66
+ - `master`stable releases
67
+ - `develop` — integration branch
68
+ - `feature/*` — feature branches off `develop`
75
69
 
76
- ## 📝 Documentation
70
+ ## License
77
71
 
78
- * [Installing](docs/INSTALLING.md) | [Agent Workflow](AGENT_INSTRUCTIONS.md) | [Copilot Setup](docs/COPILOT_INTEGRATION.md) | [Articles](ARTICLES.md) | [Sync Branch Mode](docs/PROTECTED_BRANCHES.md) | [Troubleshooting](docs/TROUBLESHOOTING.md) | [FAQ](docs/FAQ.md)
79
- * [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/steveyegge/beads)
72
+ [MIT](LICENSE)
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command } from "commander";
4
+ import { Command as Command2 } from "commander";
5
5
 
6
6
  // src/version.ts
7
7
  import { readFileSync } from "fs";
@@ -11,9 +11,277 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
12
12
  var version = pkg.version;
13
13
 
14
+ // src/commands/get-item.ts
15
+ import { Command } from "commander";
16
+
17
+ // src/services/azdo-client.ts
18
+ async function getWorkItem(context, id, pat) {
19
+ const url = `https://dev.azure.com/${context.org}/${context.project}/_apis/wit/workitems/${id}?api-version=7.1`;
20
+ const token = Buffer.from(`:${pat}`).toString("base64");
21
+ let response;
22
+ try {
23
+ response = await fetch(url, {
24
+ headers: {
25
+ Authorization: `Basic ${token}`
26
+ }
27
+ });
28
+ } catch {
29
+ throw new Error("NETWORK_ERROR");
30
+ }
31
+ if (response.status === 401) throw new Error("AUTH_FAILED");
32
+ if (response.status === 403) throw new Error("PERMISSION_DENIED");
33
+ if (response.status === 404) throw new Error("NOT_FOUND");
34
+ if (!response.ok) {
35
+ throw new Error(`HTTP_${response.status}`);
36
+ }
37
+ const data = await response.json();
38
+ return {
39
+ id: data.id,
40
+ rev: data.rev,
41
+ title: data.fields["System.Title"],
42
+ state: data.fields["System.State"],
43
+ type: data.fields["System.WorkItemType"],
44
+ assignedTo: data.fields["System.AssignedTo"]?.displayName ?? null,
45
+ description: data.fields["System.Description"] ?? null,
46
+ areaPath: data.fields["System.AreaPath"],
47
+ iterationPath: data.fields["System.IterationPath"],
48
+ url: data._links.html.href
49
+ };
50
+ }
51
+
52
+ // src/services/auth.ts
53
+ import { createInterface } from "readline";
54
+
55
+ // src/services/credential-store.ts
56
+ import { Entry } from "@napi-rs/keyring";
57
+ var SERVICE = "azdo-cli";
58
+ var ACCOUNT = "pat";
59
+ async function getPat() {
60
+ try {
61
+ const entry = new Entry(SERVICE, ACCOUNT);
62
+ return entry.getPassword();
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ async function storePat(pat) {
68
+ try {
69
+ const entry = new Entry(SERVICE, ACCOUNT);
70
+ entry.setPassword(pat);
71
+ } catch {
72
+ }
73
+ }
74
+
75
+ // src/services/auth.ts
76
+ async function promptForPat() {
77
+ if (!process.stdin.isTTY) {
78
+ return null;
79
+ }
80
+ return new Promise((resolve2) => {
81
+ const rl = createInterface({
82
+ input: process.stdin,
83
+ output: process.stderr
84
+ });
85
+ process.stderr.write("Enter your Azure DevOps PAT: ");
86
+ process.stdin.setRawMode(true);
87
+ process.stdin.resume();
88
+ let pat = "";
89
+ const onData = (key) => {
90
+ const ch = key.toString("utf8");
91
+ if (ch === "") {
92
+ process.stdin.setRawMode(false);
93
+ process.stdin.removeListener("data", onData);
94
+ rl.close();
95
+ process.stderr.write("\n");
96
+ resolve2(null);
97
+ } else if (ch === "\r" || ch === "\n") {
98
+ process.stdin.setRawMode(false);
99
+ process.stdin.removeListener("data", onData);
100
+ rl.close();
101
+ process.stderr.write("\n");
102
+ resolve2(pat);
103
+ } else if (ch === "\x7F" || ch === "\b") {
104
+ if (pat.length > 0) {
105
+ pat = pat.slice(0, -1);
106
+ process.stderr.write("\b \b");
107
+ }
108
+ } else {
109
+ pat += ch;
110
+ process.stderr.write("*");
111
+ }
112
+ };
113
+ process.stdin.on("data", onData);
114
+ });
115
+ }
116
+ async function resolvePat() {
117
+ const envPat = process.env.AZDO_PAT;
118
+ if (envPat) {
119
+ return { pat: envPat, source: "env" };
120
+ }
121
+ const storedPat = await getPat();
122
+ if (storedPat !== null) {
123
+ return { pat: storedPat, source: "credential-store" };
124
+ }
125
+ const promptedPat = await promptForPat();
126
+ if (promptedPat !== null) {
127
+ await storePat(promptedPat);
128
+ return { pat: promptedPat, source: "prompt" };
129
+ }
130
+ throw new Error(
131
+ "Authentication cancelled. Set AZDO_PAT environment variable or run again to enter a PAT."
132
+ );
133
+ }
134
+
135
+ // src/services/git-remote.ts
136
+ import { execSync } from "child_process";
137
+ var patterns = [
138
+ // HTTPS (current): https://dev.azure.com/{org}/{project}/_git/{repo}
139
+ /^https?:\/\/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/.+$/,
140
+ // HTTPS (legacy + DefaultCollection): https://{org}.visualstudio.com/DefaultCollection/{project}/_git/{repo}
141
+ /^https?:\/\/([^.]+)\.visualstudio\.com\/DefaultCollection\/([^/]+)\/_git\/.+$/,
142
+ // HTTPS (legacy): https://{org}.visualstudio.com/{project}/_git/{repo}
143
+ /^https?:\/\/([^.]+)\.visualstudio\.com\/([^/]+)\/_git\/.+$/,
144
+ // SSH (current): git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
145
+ /^git@ssh\.dev\.azure\.com:v3\/([^/]+)\/([^/]+)\/.+$/,
146
+ // SSH (legacy): {org}@vs-ssh.visualstudio.com:v3/{org}/{project}/{repo}
147
+ /^[^@]+@vs-ssh\.visualstudio\.com:v3\/([^/]+)\/([^/]+)\/.+$/
148
+ ];
149
+ function parseAzdoRemote(url) {
150
+ for (const pattern of patterns) {
151
+ const match = url.match(pattern);
152
+ if (match) {
153
+ return { org: match[1], project: match[2] };
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+ function detectAzdoContext() {
159
+ let remoteUrl;
160
+ try {
161
+ remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
162
+ } catch {
163
+ throw new Error("Not in a git repository. Provide --org and --project explicitly.");
164
+ }
165
+ const context = parseAzdoRemote(remoteUrl);
166
+ if (!context) {
167
+ throw new Error('Git remote "origin" is not an Azure DevOps URL. Provide --org and --project explicitly.');
168
+ }
169
+ return context;
170
+ }
171
+
172
+ // src/commands/get-item.ts
173
+ function stripHtml(html) {
174
+ let text = html;
175
+ text = text.replace(/<br\s*\/?>/gi, "\n");
176
+ text = text.replace(/<\/?(p|div)>/gi, "\n");
177
+ text = text.replace(/<li>/gi, "\n");
178
+ text = text.replace(/<[^>]*>/g, "");
179
+ text = text.replace(/&amp;/g, "&");
180
+ text = text.replace(/&lt;/g, "<");
181
+ text = text.replace(/&gt;/g, ">");
182
+ text = text.replace(/&quot;/g, '"');
183
+ text = text.replace(/&#39;/g, "'");
184
+ text = text.replace(/&nbsp;/g, " ");
185
+ text = text.replace(/\n{3,}/g, "\n\n");
186
+ return text.trim();
187
+ }
188
+ function formatWorkItem(workItem, short) {
189
+ const lines = [];
190
+ const label = (name) => name.padEnd(13);
191
+ lines.push(`${label("ID:")}${workItem.id}`);
192
+ lines.push(`${label("Type:")}${workItem.type}`);
193
+ lines.push(`${label("Title:")}${workItem.title}`);
194
+ lines.push(`${label("State:")}${workItem.state}`);
195
+ lines.push(`${label("Assigned To:")}${workItem.assignedTo ?? "Unassigned"}`);
196
+ if (!short) {
197
+ lines.push(`${label("Area:")}${workItem.areaPath}`);
198
+ lines.push(`${label("Iteration:")}${workItem.iterationPath}`);
199
+ }
200
+ lines.push(`${label("URL:")}${workItem.url}`);
201
+ lines.push("");
202
+ const descriptionText = workItem.description ? stripHtml(workItem.description) : "";
203
+ if (short) {
204
+ const descLines = descriptionText.split("\n").filter((l) => l.trim() !== "");
205
+ const firstThree = descLines.slice(0, 3);
206
+ const truncated = descLines.length > 3;
207
+ const descSummary = firstThree.join("\n") + (truncated ? "\n..." : "");
208
+ lines.push(`${label("Description:")}${descSummary}`);
209
+ } else {
210
+ lines.push("Description:");
211
+ lines.push(descriptionText);
212
+ }
213
+ return lines.join("\n");
214
+ }
215
+ function createGetItemCommand() {
216
+ const command = new Command("get-item");
217
+ command.description("Retrieve an Azure DevOps work item by ID").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--short", "show abbreviated output").action(
218
+ async (idStr, options) => {
219
+ const id = parseInt(idStr, 10);
220
+ if (!Number.isInteger(id) || id <= 0) {
221
+ process.stderr.write(
222
+ `Error: Work item ID must be a positive integer. Got: "${idStr}"
223
+ `
224
+ );
225
+ process.exit(1);
226
+ }
227
+ const hasOrg = options.org !== void 0;
228
+ const hasProject = options.project !== void 0;
229
+ if (hasOrg !== hasProject) {
230
+ process.stderr.write(
231
+ "Error: --org and --project must both be provided, or both omitted.\n"
232
+ );
233
+ process.exit(1);
234
+ }
235
+ let context;
236
+ try {
237
+ if (options.org && options.project) {
238
+ context = { org: options.org, project: options.project };
239
+ } else {
240
+ context = detectAzdoContext();
241
+ }
242
+ const credential = await resolvePat();
243
+ const workItem = await getWorkItem(context, id, credential.pat);
244
+ const output = formatWorkItem(workItem, options.short ?? false);
245
+ process.stdout.write(output + "\n");
246
+ } catch (err) {
247
+ const error = err instanceof Error ? err : new Error(String(err));
248
+ const msg = error.message;
249
+ if (msg === "AUTH_FAILED") {
250
+ process.stderr.write(
251
+ 'Error: Authentication failed. Check that your PAT is valid and has the "Work Items (read)" scope.\n'
252
+ );
253
+ } else if (msg === "NOT_FOUND") {
254
+ process.stderr.write(
255
+ `Error: Work item ${id} not found in ${context.org}/${context.project}.
256
+ `
257
+ );
258
+ } else if (msg === "PERMISSION_DENIED") {
259
+ process.stderr.write(
260
+ `Error: Access denied. Your PAT may lack permissions for project "${context.project}".
261
+ `
262
+ );
263
+ } else if (msg === "NETWORK_ERROR") {
264
+ process.stderr.write(
265
+ "Error: Could not connect to Azure DevOps. Check your network connection.\n"
266
+ );
267
+ } else if (msg.includes("Not in a git repository") || msg.includes("is not an Azure DevOps URL") || msg.includes("Authentication cancelled")) {
268
+ process.stderr.write(`Error: ${msg}
269
+ `);
270
+ } else {
271
+ process.stderr.write(`Error: ${msg}
272
+ `);
273
+ }
274
+ process.exit(1);
275
+ }
276
+ }
277
+ );
278
+ return command;
279
+ }
280
+
14
281
  // src/index.ts
15
- var program = new Command();
282
+ var program = new Command2();
16
283
  program.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
284
+ program.addCommand(createGetItemCommand());
17
285
  program.showHelpAfterError();
18
286
  program.parse();
19
287
  if (process.argv.length <= 2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.2.0-001-azdo-cli-base.5",
3
+ "version": "0.2.0-002-get-item-command.10",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
+ "@napi-rs/keyring": "^1.2.0",
25
26
  "commander": "^14.0.3"
26
27
  },
27
28
  "devDependencies": {