azdo-cli 0.8.0-develop.167 → 0.8.0-develop.176

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 +14 -342
  2. package/dist/index.js +67 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,13 +9,10 @@ Azure DevOps CLI focused on work item read/write workflows.
9
9
  ## Features
10
10
 
11
11
  - Retrieve work items with readable output (`get-item`)
12
- - Update work item state (`set-state`)
13
- - Assign and unassign work items (`assign`)
14
- - Set any work item field by reference name (`set-field`)
12
+ - Update work item state, assignee, or any field (`set-state`, `assign`, `set-field`)
15
13
  - Create or update work items from markdown documents (`upsert`)
16
14
  - Read and post work item comments (`comments`)
17
- - Read rich-text fields as markdown (`get-md-field`)
18
- - Set rich-text fields as markdown from inline text, file, or stdin (`set-md-field`)
15
+ - Read/write rich-text fields as markdown (`get-md-field`, `set-md-field`)
19
16
  - Check branch pull request status, open PRs to `develop`, and review active comments (`pr`)
20
17
  - Persist org/project/default fields in local config (`config`)
21
18
  - List all fields of a work item (`list-fields`)
@@ -27,360 +24,35 @@ Azure DevOps CLI focused on work item read/write workflows.
27
24
  npm install -g azdo-cli
28
25
  ```
29
26
 
30
- ## Utility Scripts
31
-
32
- The repository also includes a helper script for syncing local `.env` entries into GitHub Actions secrets for the current repository:
33
-
34
- ```bash
35
- ./scripts/sync-env-to-gh-secrets.zsh
36
- ```
37
-
38
- It walks upward from the current directory until it finds a `.env`, then sets each valid `KEY=VALUE` entry with `gh secret set`. You can also limit the sync to selected keys:
39
-
40
- ```bash
41
- ./scripts/sync-env-to-gh-secrets.zsh FOO BAR
42
- ```
43
-
44
- ## Authentication and Context Resolution
45
-
46
- PAT resolution order:
47
- 1. `AZDO_PAT` environment variable
48
- 2. Stored credential from OS keyring
49
- 3. Interactive PAT prompt (then stored for next runs)
50
-
51
- Org/project resolution order:
52
- 1. `--org` + `--project` flags
53
- 2. Saved config (`azdo config set org ...`, `azdo config set project ...`)
54
- 3. Azure DevOps `origin` git remote auto-detection
55
-
56
27
  ## Quick Start
57
28
 
58
29
  ```bash
59
- # 1) Configure defaults once
30
+ # Configure defaults once
60
31
  azdo config set org myorg
61
32
  azdo config set project myproject
62
33
 
63
- # 2) Read a work item
34
+ # Read a work item
64
35
  azdo get-item 12345
65
36
 
66
- # 3) Update state
37
+ # Update state
67
38
  azdo set-state 12345 "Active"
68
39
 
69
- # 4) Create or update a work item from markdown
40
+ # Create a work item from markdown
70
41
  azdo upsert --type "User Story" --content $'---\nTitle: Improve markdown import UX\nState: New\n---'
71
42
 
72
- # 5) Review work item discussion and post an update
43
+ # Read and post comments
73
44
  azdo comments list 12345
74
45
  azdo comments add 12345 "Investigating the root cause now."
75
46
  ```
76
47
 
77
- ## Command Cheat Sheet
78
-
79
- | Command | Purpose | Common Flags |
80
- | --- | --- | --- |
81
- | `azdo get-item <id>` | Read a work item | `--short`, `--fields`, `--markdown`, `--org`, `--project` |
82
- | `azdo set-state <id> <state>` | Change work item state | `--json`, `--org`, `--project` |
83
- | `azdo assign <id> [name]` | Assign or unassign owner | `--unassign`, `--json`, `--org`, `--project` |
84
- | `azdo set-field <id> <field> <value>` | Update any field | `--json`, `--org`, `--project` |
85
- | `azdo upsert [id]` | Create or update a work item from markdown | `--content`, `--file`, `--type`, `--json`, `--org`, `--project` |
86
- | `azdo comments <subcommand>` | Read or add work item comments | `list`, `add`, `--json`, `--org`, `--project` |
87
- | `azdo get-md-field <id> <field>` | Get field as markdown | `--org`, `--project` |
88
- | `azdo set-md-field <id> <field> [content]` | Set markdown field | `--file`, `--json`, `--org`, `--project` |
89
- | `azdo list-fields <id>` | List all fields of a work item | `--json`, `--org`, `--project` |
90
- | `azdo pr <subcommand>` | Manage pull requests for the current branch | `status`, `open`, `comments`, `--json`, `--org`, `--project` |
91
- | `azdo config <subcommand>` | Manage saved settings | `set`, `get`, `list`, `unset`, `wizard`, `--json` |
92
- | `azdo clear-pat` | Remove stored PAT | none |
93
-
94
- ## Command Reference
95
-
96
- ### Core
97
-
98
- ```bash
99
- # Get full work item
100
- azdo get-item 12345
101
-
102
- # Get short view
103
- azdo get-item 12345 --short
104
-
105
- # Include extra fields for this call
106
- azdo get-item 12345 --fields "System.Tags,Microsoft.VSTS.Common.Priority"
107
-
108
- # Convert rich text fields to markdown
109
- azdo get-item 12345 --markdown
110
-
111
- ```
112
-
113
- ```bash
114
- # Set state
115
- azdo set-state 12345 "Closed"
116
-
117
- # Assign / unassign
118
- azdo assign 12345 "someone@company.com"
119
- azdo assign 12345 --unassign
120
-
121
- # Set generic field
122
- azdo set-field 12345 System.Title "Updated title"
123
- ```
124
-
125
- ### List Fields
126
-
127
- ```bash
128
- # List all fields with values (rich text fields preview first 5 lines)
129
- azdo list-fields 12345
130
-
131
- # JSON output
132
- azdo list-fields 12345 --json
133
- ```
134
-
135
- ### Markdown Display
136
-
137
- The `get-item` command can convert HTML rich-text fields to readable markdown. Resolution order:
138
-
139
- 1. `--markdown` flag enables markdown for the current call
140
- 2. Config setting: `azdo config set markdown true`
141
- 3. Default: off (HTML stripped to plain text)
142
-
143
- ### Markdown Field Commands
144
-
145
- ```bash
146
- # Read field and auto-convert HTML -> markdown
147
- azdo get-md-field 12345 System.Description
148
-
149
- # Set markdown inline
150
- azdo set-md-field 12345 System.Description "# Title\n\nSome **bold** text"
151
-
152
- # Set markdown from file
153
- azdo set-md-field 12345 System.Description --file ./description.md
154
-
155
- # Set markdown from stdin
156
- cat description.md | azdo set-md-field 12345 System.Description
157
- ```
158
-
159
- ### Pull Request Commands
160
-
161
- The `pr` command group uses the current git branch and the Azure DevOps `origin` remote automatically. It requires a PAT with `Code (Read)` scope for read operations and `Code (Read & Write)` for pull request creation.
162
-
163
- ```bash
164
- # Check whether the current branch already has pull requests
165
- azdo pr status
166
-
167
- # Open a pull request to develop
168
- azdo pr open --title "Add PR handling" --description "Implements pr status, pr open, pr comments commands"
169
-
170
- # Review active comments for the current branch's active pull request
171
- azdo pr comments
172
- ```
173
-
174
- `azdo pr status`
175
-
176
- - Lists pull requests for the current branch
177
- - Includes Azure DevOps pull request checks under each returned pull request
178
- - Prints `Checks: none reported by Azure DevOps` when a pull request has no returned checks
179
- - Shows `Detail: ...` lines for failed or errored checks when Azure DevOps provides description text
180
- - Prints `No pull requests found for branch <branch>.` when no PRs exist
181
- - Supports `--json` for machine-readable output, including a `checks` array per pull request
182
-
183
- `azdo pr open`
184
-
185
- - Requires both `--title <title>` and `--description <description>`
186
- - Targets `develop` automatically
187
- - Creates a new active pull request when none exists
188
- - Reuses the existing active PR when one already matches the branch and target
189
- - Fails with a clear error when run from `develop` or when multiple active PRs already exist
190
-
191
- `azdo pr comments`
192
-
193
- - Resolves the single active pull request for the current branch
194
- - Returns only active or pending threads with visible, non-deleted comments
195
- - Groups text output by thread and shows file context when available
196
- - Prints `Pull request #<id> has no active comments.` when the PR has no active comment threads
197
- - Fails instead of guessing when no active PR or multiple active PRs exist
198
-
199
- ### Work Item Comment Commands
200
-
201
- The `comments` command group works on a specific work item ID. It requires a PAT with `Work Items (read)` scope for listing comments and `Work Items (Read & Write)` to add a new comment.
202
-
203
- ```bash
204
- # Read the visible comment history for a work item
205
- azdo comments list 12345
206
-
207
- # Read the same history as JSON
208
- azdo comments list 12345 --json
209
-
210
- # Post a progress update
211
- azdo comments add 12345 "Investigation complete. Working on the fix next."
212
-
213
- # Post the update and return JSON
214
- azdo comments add 12345 "Queued validation run." --json
215
- ```
216
-
217
- `azdo comments list`
218
-
219
- - Resolves the target work item directly from the provided ID
220
- - Retrieves the full visible comment history and follows Azure DevOps pagination internally
221
- - Prints comments newest first with comment ID, author, timestamp, and body text
222
- - Prints `Work item #<id> has no comments.` when the work item has no visible comments
223
-
224
- `azdo comments add`
225
-
226
- - Requires a non-empty `<text>` positional argument
227
- - Preserves the supplied comment text as the submitted body
228
- - Prints `Added comment #<commentId> to work item #<id>` on success
229
- - Fails locally before any API call when the text is empty or whitespace-only
230
-
231
- ## azdo upsert
232
-
233
- `azdo upsert` accepts a single markdown work-item document and either creates a new Azure DevOps work item or updates an existing one. Omit `[id]` to create; pass `[id]` to update that work item in place. Create mode defaults to `Task`, and `--type <work item type>` lets you create `Bug`, `User Story`, `Feature`, `Epic`, and other Azure DevOps work item types.
234
-
235
- ```bash
236
- # Create a Bug from inline content
237
- azdo upsert --type Bug --content $'---\nTitle: Improve markdown import UX\nAssigned To: user@example.com\nState: New\n---'
238
-
239
- # Preserve the default Task create behavior
240
- azdo upsert --content $'---\nTitle: Follow-up task\nAssigned To: user@example.com\nState: New\n---'
241
-
242
- # Update from a file
243
- azdo upsert 12345 --file ./task-import.md
244
-
245
- # JSON output
246
- azdo upsert 12345 --content $'---\nSystem.Title: Improve markdown import UX\n---' --json
247
- ```
248
-
249
- The command requires exactly one source flag:
250
-
251
- - `azdo upsert [id] --content <markdown>`
252
- - `azdo upsert [id] --file <path>`
253
- - `azdo upsert --type <work-item-type> --content <markdown>`
254
-
255
- If `--file` succeeds, the source file is deleted after the Azure DevOps write completes. If parsing, validation, or the API call fails, the file is preserved. If deletion fails after a successful write, the command still succeeds and prints a warning.
256
-
257
- Type rules:
258
-
259
- - `--type` is optional for create and defaults to `Task`.
260
- - `--type` is only valid when creating a new work item.
261
- - Human-readable and JSON success output include the resulting work item type.
262
-
263
- ### Task Document Format
264
-
265
- The document starts with YAML front matter for scalar fields, followed by optional `##` heading sections for markdown rich-text fields.
266
-
267
- ```md
268
- ---
269
- Title: Improve markdown import UX
270
- Assigned To: user@example.com
271
- State: New
272
- Tags: cli; markdown
273
- Priority: null
274
- ---
275
-
276
- ## Description
277
-
278
- Implement a single-command task import flow.
279
-
280
- ## Acceptance Criteria
281
-
282
- - Supports create when no ID is passed
283
- - Supports update when an ID is passed
284
- - Deletes imported files only after success
285
- ```
286
-
287
- Supported friendly field names:
288
-
289
- - `Title`
290
- - `Assigned To` / `assignedTo`
291
- - `State`
292
- - `Description`
293
- - `Acceptance Criteria` / `acceptanceCriteria`
294
- - `Tags`
295
- - `Priority`
296
-
297
- Raw Azure DevOps reference names are also accepted anywhere a field name is expected, for example `System.Title` or `Microsoft.VSTS.Common.AcceptanceCriteria`.
298
-
299
- Clear semantics:
300
-
301
- - Scalar YAML fields with `null` or an empty value are treated as clears on update.
302
- - Rich-text heading sections with an empty body are treated as clears on update.
303
- - Omitted fields are untouched on update.
304
-
305
- `--json` output shape:
306
-
307
- ```json
308
- {
309
- "action": "created",
310
- "id": 12345,
311
- "workItemType": "User Story",
312
- "fields": {
313
- "System.Title": "Improve markdown import UX",
314
- "System.Description": "Implement a single-command task import flow."
315
- }
316
- }
317
- ```
318
-
319
- ### Configuration
320
-
321
- ```bash
322
- # List settings
323
- azdo config list
324
-
325
- # Interactive setup
326
- azdo config wizard
327
-
328
- # Enable markdown display for all get-item calls
329
- azdo config set markdown true
330
-
331
- # Set/get/unset values
332
- azdo config set fields "System.Tags,Custom.Priority"
333
- azdo config get fields
334
- azdo config unset fields
335
-
336
- # JSON output
337
- azdo config list --json
338
- ```
339
-
340
- ### Credential Management
341
-
342
- ```bash
343
- # Remove stored PAT from keyring
344
- azdo clear-pat
345
- ```
346
-
347
- ## JSON Output
348
-
349
- These commands support `--json` for machine-readable output:
350
- - `list-fields`
351
- - `set-state`
352
- - `assign`
353
- - `set-field`
354
- - `set-md-field`
355
- - `upsert`
356
- - `comments list|add`
357
- - `pr status|open|comments`
358
- - `config set|get|list|unset`
359
-
360
- ## Development
361
-
362
- ### Prerequisites
363
-
364
- - Node.js LTS (20+)
365
- - npm
366
-
367
- ### Setup
368
-
369
- ```bash
370
- git clone https://github.com/alkampfergit/azdo-cli.git
371
- cd azdo-cli
372
- npm install
373
- ```
374
-
375
- ### Scripts
48
+ ## Documentation
376
49
 
377
- | Command | Description |
378
- | --- | --- |
379
- | `npm run build` | Build the CLI with tsup |
380
- | `npm test` | Build and run tests with vitest |
381
- | `npm run lint` | Lint source files with ESLint |
382
- | `npm run typecheck` | Type-check with tsc (no emit) |
383
- | `npm run format` | Check formatting with Prettier |
50
+ | Topic | File |
51
+ |-------|------|
52
+ | Authentication & PAT storage | [docs/authentication.md](docs/authentication.md) |
53
+ | Linux credential store setup | [docs/linux-credential-store.md](docs/linux-credential-store.md) |
54
+ | Full command reference | [docs/commands.md](docs/commands.md) |
55
+ | Development setup | [docs/development.md](docs/development.md) |
384
56
 
385
57
  ## License
386
58
 
package/dist/index.js CHANGED
@@ -263,8 +263,10 @@ async function listWorkItemComments(context, id, pat) {
263
263
  comments
264
264
  };
265
265
  }
266
- async function addWorkItemComment(context, id, pat, text) {
267
- const response = await fetchWithErrors(buildWorkItemCommentsUrl(context, id).toString(), {
266
+ async function addWorkItemComment(context, id, pat, text, format = "html") {
267
+ const url = buildWorkItemCommentsUrl(context, id);
268
+ url.searchParams.set("format", format);
269
+ const response = await fetchWithErrors(url.toString(), {
268
270
  method: "POST",
269
271
  headers: {
270
272
  ...authHeaders(pat),
@@ -329,6 +331,8 @@ async function applyWorkItemPatch(context, id, pat, operations) {
329
331
 
330
332
  // src/services/auth.ts
331
333
  import { createInterface } from "readline";
334
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
335
+ import { dirname as dirname2, join } from "path";
332
336
 
333
337
  // src/services/credential-store.ts
334
338
  import { Entry } from "@napi-rs/keyring";
@@ -346,7 +350,9 @@ async function storePat(pat) {
346
350
  try {
347
351
  const entry = new Entry(SERVICE, ACCOUNT);
348
352
  entry.setPassword(pat);
353
+ return true;
349
354
  } catch {
355
+ return false;
350
356
  }
351
357
  }
352
358
  async function deletePat() {
@@ -360,10 +366,19 @@ async function deletePat() {
360
366
  }
361
367
 
362
368
  // src/services/auth.ts
369
+ var PAT_PROMPT = "Enter your Azure DevOps PAT: ";
370
+ var VISIBLE_CHARS = 5;
363
371
  function normalizePat(rawPat) {
364
372
  const trimmedPat = rawPat.trim();
365
373
  return trimmedPat.length > 0 ? trimmedPat : null;
366
374
  }
375
+ function maskedDisplay(pat) {
376
+ if (pat.length <= VISIBLE_CHARS * 2) {
377
+ return pat;
378
+ }
379
+ const hiddenCount = pat.length - VISIBLE_CHARS * 2;
380
+ return pat.slice(0, VISIBLE_CHARS) + "*".repeat(hiddenCount) + pat.slice(-VISIBLE_CHARS);
381
+ }
367
382
  async function promptForPat() {
368
383
  if (!process.stdin.isTTY) {
369
384
  return null;
@@ -373,10 +388,13 @@ async function promptForPat() {
373
388
  input: process.stdin,
374
389
  output: process.stderr
375
390
  });
376
- process.stderr.write("Enter your Azure DevOps PAT: ");
391
+ process.stderr.write(PAT_PROMPT);
377
392
  process.stdin.setRawMode(true);
378
393
  process.stdin.resume();
379
394
  let pat = "";
395
+ const redraw = () => {
396
+ process.stderr.write(`\r${PAT_PROMPT}${maskedDisplay(pat)}\x1B[K`);
397
+ };
380
398
  const onData = (key) => {
381
399
  const ch = key.toString("utf8");
382
400
  if (ch === "") {
@@ -394,17 +412,37 @@ async function promptForPat() {
394
412
  } else if (ch === "\x7F" || ch === "\b") {
395
413
  if (pat.length > 0) {
396
414
  pat = pat.slice(0, -1);
397
- process.stderr.write("\b \b");
415
+ redraw();
398
416
  }
399
417
  } else {
400
418
  pat += ch;
401
- process.stderr.write("*".repeat(ch.length));
419
+ redraw();
402
420
  }
403
421
  };
404
422
  process.stdin.on("data", onData);
405
423
  });
406
424
  }
407
- async function resolvePat() {
425
+ function findDotEnvPat(startDir = process.cwd()) {
426
+ let current = startDir;
427
+ while (true) {
428
+ const envFile = join(current, ".env");
429
+ if (existsSync(envFile)) {
430
+ const contents = readFileSync2(envFile, "utf8");
431
+ for (const line of contents.split("\n")) {
432
+ const match = line.match(/^AZDO_PAT\s*=\s*(.+)$/);
433
+ if (match) {
434
+ const value = match[1].trim().replace(/^["']|["']$/g, "");
435
+ if (value.length > 0) return value;
436
+ }
437
+ }
438
+ }
439
+ const parent = dirname2(current);
440
+ if (parent === current) break;
441
+ current = parent;
442
+ }
443
+ return null;
444
+ }
445
+ async function resolvePat(promptFn = promptForPat) {
408
446
  const envPat = process.env.AZDO_PAT;
409
447
  if (envPat) {
410
448
  return { pat: envPat, source: "env" };
@@ -413,11 +451,18 @@ async function resolvePat() {
413
451
  if (storedPat !== null) {
414
452
  return { pat: storedPat, source: "credential-store" };
415
453
  }
416
- const promptedPat = await promptForPat();
454
+ const dotEnvPat = findDotEnvPat();
455
+ if (dotEnvPat !== null) {
456
+ return { pat: dotEnvPat, source: "env" };
457
+ }
458
+ const promptedPat = await promptFn();
417
459
  if (promptedPat !== null) {
418
460
  const normalizedPat = normalizePat(promptedPat);
419
461
  if (normalizedPat !== null) {
420
- await storePat(normalizedPat);
462
+ const saved = await storePat(normalizedPat);
463
+ if (!saved) {
464
+ process.stderr.write("Warning: Could not save PAT to credential store. You may need to enter it again next time.\n");
465
+ }
421
466
  return { pat: normalizedPat, source: "prompt" };
422
467
  }
423
468
  }
@@ -1196,7 +1241,7 @@ function createGetMdFieldCommand() {
1196
1241
  }
1197
1242
 
1198
1243
  // src/commands/set-md-field.ts
1199
- import { existsSync, readFileSync as readFileSync2 } from "fs";
1244
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
1200
1245
  import { Command as Command8 } from "commander";
1201
1246
  function fail(message) {
1202
1247
  process.stderr.write(`Error: ${message}
@@ -1216,11 +1261,11 @@ function resolveContent(inlineContent, options) {
1216
1261
  return null;
1217
1262
  }
1218
1263
  function readFileContent(filePath) {
1219
- if (!existsSync(filePath)) {
1264
+ if (!existsSync2(filePath)) {
1220
1265
  fail(`File not found: ${filePath}`);
1221
1266
  }
1222
1267
  try {
1223
- return readFileSync2(filePath, "utf-8");
1268
+ return readFileSync3(filePath, "utf-8");
1224
1269
  } catch {
1225
1270
  fail(`Cannot read file: ${filePath}`);
1226
1271
  }
@@ -1284,7 +1329,7 @@ function createSetMdFieldCommand() {
1284
1329
  }
1285
1330
 
1286
1331
  // src/commands/upsert.ts
1287
- import { existsSync as existsSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
1332
+ import { existsSync as existsSync3, readFileSync as readFileSync4, unlinkSync } from "fs";
1288
1333
  import { Command as Command9 } from "commander";
1289
1334
 
1290
1335
  // src/services/task-document.ts
@@ -1448,12 +1493,12 @@ function loadSourceContent(options) {
1448
1493
  return { content: options.content };
1449
1494
  }
1450
1495
  const filePath = options.file;
1451
- if (!existsSync2(filePath)) {
1496
+ if (!existsSync3(filePath)) {
1452
1497
  fail2(`File not found: ${filePath}`);
1453
1498
  }
1454
1499
  try {
1455
1500
  return {
1456
- content: readFileSync3(filePath, "utf-8"),
1501
+ content: readFileSync4(filePath, "utf-8"),
1457
1502
  sourceFile: filePath
1458
1503
  };
1459
1504
  } catch {
@@ -2055,16 +2100,17 @@ function formatCommentHeader(comment) {
2055
2100
  const timestamp = comment.modifiedAt ?? comment.createdAt ?? "Unknown time";
2056
2101
  return `Comment #${comment.id} by ${author} at ${timestamp}`;
2057
2102
  }
2058
- function formatComments(result) {
2103
+ function formatComments(result, convertMarkdown) {
2059
2104
  const lines = [`Comments for work item #${result.workItemId}`];
2060
2105
  for (const comment of result.comments) {
2061
- lines.push("", formatCommentHeader(comment), comment.text);
2106
+ const text = convertMarkdown ? toMarkdown(comment.text) : comment.text;
2107
+ lines.push("", formatCommentHeader(comment), text);
2062
2108
  }
2063
2109
  return lines.join("\n");
2064
2110
  }
2065
2111
  function createCommentsListCommand() {
2066
2112
  const command = new Command12("list");
2067
- command.description("List visible comments for a work item").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (idStr, options) => {
2113
+ command.description("List visible comments for a work item").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "convert HTML comment bodies to markdown").action(async (idStr, options) => {
2068
2114
  validateOrgProjectPair(options);
2069
2115
  const id = parseWorkItemId(idStr);
2070
2116
  let context;
@@ -2082,7 +2128,7 @@ function createCommentsListCommand() {
2082
2128
  `);
2083
2129
  return;
2084
2130
  }
2085
- process.stdout.write(`${formatComments(result)}
2131
+ process.stdout.write(`${formatComments(result, options.markdown === true)}
2086
2132
  `);
2087
2133
  } catch (err) {
2088
2134
  handleCommandError(err, id, context, "read");
@@ -2092,7 +2138,7 @@ function createCommentsListCommand() {
2092
2138
  }
2093
2139
  function createCommentsAddCommand() {
2094
2140
  const command = new Command12("add");
2095
- command.description("Add a comment to a work item").argument("<id>", "work item ID").argument("<text>", "comment text").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (idStr, text, options) => {
2141
+ command.description("Add a comment to a work item").argument("<id>", "work item ID").argument("<text>", "comment text").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "post comment as markdown").action(async (idStr, text, options) => {
2096
2142
  validateOrgProjectPair(options);
2097
2143
  const id = parseWorkItemId(idStr);
2098
2144
  if (text.trim() === "") {
@@ -2102,7 +2148,8 @@ function createCommentsAddCommand() {
2102
2148
  try {
2103
2149
  context = resolveContext(options);
2104
2150
  const credential = await resolvePat();
2105
- const result = await addWorkItemComment(context, id, credential.pat, text);
2151
+ const format = options.markdown === true ? "markdown" : "html";
2152
+ const result = await addWorkItemComment(context, id, credential.pat, text, format);
2106
2153
  if (options.json) {
2107
2154
  process.stdout.write(`${JSON.stringify(result, null, 2)}
2108
2155
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.8.0-develop.167",
3
+ "version": "0.8.0-develop.176",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {