@thanaen/ado-cli 0.5.0 → 0.6.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/dist/cli.js CHANGED
@@ -57228,6 +57228,9 @@ function loadLocalConfig() {
57228
57228
  return {};
57229
57229
  }
57230
57230
  }
57231
+ function isInteractive() {
57232
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
57233
+ }
57231
57234
  function censorPat(pat) {
57232
57235
  if (pat.length <= 8) {
57233
57236
  return "****";
@@ -57237,18 +57240,26 @@ function censorPat(pat) {
57237
57240
  function getConfig() {
57238
57241
  const fileConfig = loadFileConfig();
57239
57242
  const localConfig = loadLocalConfig();
57240
- const pat = process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
57243
+ const pat = process.env.ADO_PAT ?? process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
57241
57244
  if (!pat) {
57242
- console.error("Missing DEVOPS_PAT environment variable or pat in config file.");
57243
- console.error(`Run "ado init" to create a config file at ${getConfigFilePath()}`);
57245
+ if (isInteractive()) {
57246
+ console.error("Missing ADO_PAT (or DEVOPS_PAT) environment variable or pat in config file.");
57247
+ console.error(`Run "ado init" to create a config file at ${getConfigFilePath()}`);
57248
+ } else {
57249
+ console.error("Error: ADO_PAT environment variable is not set.");
57250
+ console.error('Please set it in your environment or in your Claude Desktop config (claude_desktop_config.json) under the "env" key.');
57251
+ console.error("Never paste your PAT directly in the chat.");
57252
+ }
57244
57253
  process.exit(1);
57245
57254
  }
57246
57255
  const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl ?? DEFAULT_COLLECTION_URL;
57247
57256
  const project = process.env.ADO_PROJECT ?? localConfig.project ?? fileConfig.project ?? DEFAULT_PROJECT;
57248
57257
  const repo = process.env.ADO_REPO ?? localConfig.repo ?? fileConfig.repo ?? DEFAULT_REPO;
57249
57258
  if (isDefaultPlaceholder(collectionUrl) || isDefaultPlaceholder(project) || isDefaultPlaceholder(repo)) {
57250
- console.error("ADO configuration is incomplete. Set ADO_COLLECTION_URL, ADO_PROJECT, and ADO_REPO.");
57251
- console.error(`You can also run "ado init" to create a config file at ${getConfigFilePath()}`);
57259
+ console.error("ADO configuration is incomplete. Set ADO_COLLECTION_URL, ADO_PROJECT, and ADO_REPO environment variables.");
57260
+ if (isInteractive()) {
57261
+ console.error(`You can also run "ado init" to create a config file at ${getConfigFilePath()}`);
57262
+ }
57252
57263
  process.exit(1);
57253
57264
  }
57254
57265
  const insecure = process.env.ADO_INSECURE === "1" || localConfig.insecure === true || fileConfig.insecure === true;
@@ -57468,9 +57479,9 @@ async function cmdSmoke(config) {
57468
57479
  async function cmdStatus() {
57469
57480
  const localConfig = loadLocalConfig();
57470
57481
  const fileConfig = loadFileConfig();
57471
- const pat = process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
57482
+ const pat = process.env.ADO_PAT ?? process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
57472
57483
  if (!pat) {
57473
- console.error("Not ready: DEVOPS_PAT is not set");
57484
+ console.error("Not ready: ADO_PAT (or DEVOPS_PAT) is not set");
57474
57485
  process.exit(1);
57475
57486
  }
57476
57487
  const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl ?? "";
@@ -57499,17 +57510,28 @@ async function cmdStatus() {
57499
57510
  process.exit(1);
57500
57511
  }
57501
57512
  }
57502
- async function cmdRepos(config) {
57513
+ async function cmdRepos(config, args = []) {
57514
+ const json = args.includes("--json");
57503
57515
  const gitApi = await config.connection.getGitApi();
57504
57516
  const repos = await gitApi.getRepositories(config.project);
57517
+ if (json) {
57518
+ console.log(JSON.stringify(repos.map((r) => ({ id: r.id, name: r.name })), null, 2));
57519
+ return;
57520
+ }
57505
57521
  for (const repo of repos) {
57506
57522
  console.log(`${repo.id} ${repo.name}`);
57507
57523
  }
57508
57524
  }
57509
- async function cmdBranches(config, repoArg) {
57525
+ async function cmdBranches(config, args = []) {
57526
+ const json = args.includes("--json");
57527
+ const repoArg = args.find((a) => !a.startsWith("--"));
57510
57528
  const gitApi = await config.connection.getGitApi();
57511
57529
  const repo = pickRepo(config, repoArg);
57512
57530
  const refs = await gitApi.getRefs(repo, config.project, "heads/");
57531
+ if (json) {
57532
+ console.log(JSON.stringify(refs.map((r) => ({ name: String(r.name || "").replace("refs/heads/", "") })), null, 2));
57533
+ return;
57534
+ }
57513
57535
  for (const ref of refs) {
57514
57536
  const name = String(ref.name || "").replace("refs/heads/", "");
57515
57537
  console.log(name);
@@ -57554,18 +57576,25 @@ async function cmdWorkItemGet(config, idRaw, args = []) {
57554
57576
  }, null, 2));
57555
57577
  }
57556
57578
  async function cmdWorkItemsRecent(config, args = []) {
57579
+ const json = args.includes("--json");
57580
+ const filteredArgs = args.filter((a) => a !== "--json");
57557
57581
  let parsedArgs;
57558
57582
  try {
57559
- parsedArgs = parseWorkItemsRecentArgs(args);
57583
+ parsedArgs = parseWorkItemsRecentArgs(filteredArgs);
57560
57584
  } catch (error) {
57561
57585
  console.error(error instanceof Error ? error.message : String(error));
57562
- console.error("Usage: workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>]");
57586
+ console.error("Usage: workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>] [--json]");
57563
57587
  process.exit(1);
57564
57588
  }
57565
57589
  const witApi = await config.connection.getWorkItemTrackingApi();
57566
57590
  const wiqlResult = await witApi.queryByWiql({ query: buildRecentWorkItemsWiql(parsedArgs.filters) }, { project: config.project }, undefined, parsedArgs.top);
57567
- for (const wi of wiqlResult.workItems ?? []) {
57568
- console.log(wi.id);
57591
+ const ids = (wiqlResult.workItems ?? []).map((wi) => wi.id);
57592
+ if (json) {
57593
+ console.log(JSON.stringify(ids, null, 2));
57594
+ return;
57595
+ }
57596
+ for (const id of ids) {
57597
+ console.log(id);
57569
57598
  }
57570
57599
  }
57571
57600
  async function cmdWorkItemComments(config, idRaw, args = []) {
@@ -57671,12 +57700,26 @@ async function cmdWorkItemCommentUpdate(config, idRaw, commentIdRaw, args = [])
57671
57700
  text: result.text ?? text
57672
57701
  }, null, 2));
57673
57702
  }
57674
- async function cmdPrs(config, status = "active", topRaw = "10", repoArg) {
57703
+ async function cmdPrs(config, args = []) {
57704
+ const json = args.includes("--json");
57705
+ const positionals = args.filter((a) => !a.startsWith("--"));
57706
+ const status = positionals[0] ?? "active";
57707
+ const topRaw = positionals[1] ?? "10";
57708
+ const repoArg = positionals[2];
57675
57709
  const top = Number(topRaw);
57676
57710
  const boundedTop = Number.isFinite(top) && top > 0 ? Math.min(top, 50) : 10;
57677
57711
  const repo = pickRepo(config, repoArg);
57678
57712
  const gitApi = await config.connection.getGitApi();
57679
57713
  const prs = await gitApi.getPullRequests(repo, { status: mapPrStatus(status) }, config.project, undefined, undefined, boundedTop);
57714
+ if (json) {
57715
+ console.log(JSON.stringify(prs.map((pr) => ({
57716
+ id: pr.pullRequestId,
57717
+ status: prStatusName(pr.status),
57718
+ title: pr.title,
57719
+ createdBy: pr.createdBy?.displayName ?? null
57720
+ })), null, 2));
57721
+ return;
57722
+ }
57680
57723
  for (const pr of prs) {
57681
57724
  const createdBy = pr.createdBy?.displayName ?? "unknown";
57682
57725
  console.log(`#${pr.pullRequestId} [${prStatusName(pr.status)}] ${pr.title} (${createdBy})`);
@@ -57773,11 +57816,24 @@ async function cmdPrAutocomplete(config, idRaw, repoArg) {
57773
57816
  console.log(`Enabled auto-complete for PR #${id}`);
57774
57817
  }
57775
57818
  }
57776
- async function cmdBuilds(config, topRaw = "10") {
57819
+ async function cmdBuilds(config, args = []) {
57820
+ const json = args.includes("--json");
57821
+ const positionals = args.filter((a) => !a.startsWith("--"));
57822
+ const topRaw = positionals[0] ?? "10";
57777
57823
  const top = Number(topRaw);
57778
57824
  const boundedTop = Number.isFinite(top) && top > 0 ? Math.min(top, 50) : 10;
57779
57825
  const buildApi = await config.connection.getBuildApi();
57780
57826
  const builds = await buildApi.getBuilds(config.project, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, boundedTop, undefined, undefined, undefined, import_BuildInterfaces.BuildQueryOrder.QueueTimeDescending);
57827
+ if (json) {
57828
+ console.log(JSON.stringify(builds.map((b) => ({
57829
+ id: b.id,
57830
+ status: b.status,
57831
+ result: b.result ?? null,
57832
+ definition: b.definition?.name ?? null,
57833
+ sourceBranch: b.sourceBranch ?? null
57834
+ })), null, 2));
57835
+ return;
57836
+ }
57781
57837
  for (const b of builds) {
57782
57838
  console.log(`#${b.id} ${b.status}/${b.result ?? "n/a"} ${b.definition?.name ?? "unknown"} ${b.sourceBranch ?? ""}`);
57783
57839
  }
@@ -57942,6 +57998,11 @@ async function cmdPrCherryPick(config, args) {
57942
57998
  }
57943
57999
  }
57944
58000
  async function cmdInit(args) {
58001
+ if (!isInteractive()) {
58002
+ console.error("The init command requires an interactive terminal.");
58003
+ console.error("For non-interactive usage, set environment variables: ADO_PAT, ADO_COLLECTION_URL, ADO_PROJECT, ADO_REPO");
58004
+ process.exit(1);
58005
+ }
57945
58006
  const isLocal = args.includes("--local");
57946
58007
  const { createInterface } = await import("node:readline/promises");
57947
58008
  const { mkdirSync, writeFileSync, existsSync: existsSync2 } = await import("node:fs");
@@ -58000,7 +58061,7 @@ Configuration saved to ${configPath}`);
58000
58061
  function cmdConfig() {
58001
58062
  const fileConfig = loadFileConfig();
58002
58063
  const localConfig = loadLocalConfig();
58003
- const pat = process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
58064
+ const pat = process.env.ADO_PAT ?? process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
58004
58065
  const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl;
58005
58066
  const project = process.env.ADO_PROJECT ?? localConfig.project ?? fileConfig.project;
58006
58067
  const repo = process.env.ADO_REPO ?? localConfig.repo ?? fileConfig.repo;
@@ -58026,21 +58087,29 @@ Commands:
58026
58087
  config
58027
58088
  status
58028
58089
  smoke
58029
- repos
58030
- branches [repo]
58090
+ repos [--json]
58091
+ branches [repo] [--json]
58031
58092
  workitem-get <id> [--raw] [--expand=all|fields|links|relations]
58032
- workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>]
58093
+ workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>] [--json]
58033
58094
  workitem-comments <id> [top] [--top=<n>] [--order=asc|desc]
58034
58095
  workitem-comment-add <id> --text="..." [--file=path]
58035
58096
  workitem-comment-update <id> <commentId> --text="..." [--file=path]
58036
- prs [status] [top] [repo]
58097
+ prs [status] [top] [repo] [--json]
58037
58098
  pr-get <id> [repo]
58038
58099
  pr-create --title=... --source=... --target=... [--description=...] [--repo=...] [--work-items=123,456] [--tags=tag-a,tag-b]
58039
58100
  pr-update <id> [--title=...] [--description=...] [--repo=...] [--work-items=123,456] [--tags=tag-a,tag-b]
58040
58101
  pr-cherry-pick <id> --target=... [--topic=branch-name] [--repo=...]
58041
58102
  pr-approve <id> [repo]
58042
58103
  pr-autocomplete <id> [repo]
58043
- builds [top]
58104
+ builds [top] [--json]
58105
+
58106
+ Environment variables:
58107
+ ADO_PAT Personal Access Token (primary)
58108
+ DEVOPS_PAT Personal Access Token (fallback, deprecated)
58109
+ ADO_COLLECTION_URL Azure DevOps collection URL
58110
+ ADO_PROJECT Default project
58111
+ ADO_REPO Default repository
58112
+ ADO_INSECURE Set to "1" to disable TLS verification
58044
58113
  `);
58045
58114
  }
58046
58115
  async function printVersion() {
@@ -58076,10 +58145,10 @@ async function main() {
58076
58145
  await cmdSmoke(config);
58077
58146
  break;
58078
58147
  case "repos":
58079
- await cmdRepos(config);
58148
+ await cmdRepos(config, args);
58080
58149
  break;
58081
58150
  case "branches":
58082
- await cmdBranches(config, args[0]);
58151
+ await cmdBranches(config, args);
58083
58152
  break;
58084
58153
  case "workitem-get":
58085
58154
  await cmdWorkItemGet(config, args[0], args.slice(1));
@@ -58097,7 +58166,7 @@ async function main() {
58097
58166
  await cmdWorkItemCommentUpdate(config, args[0], args[1], args.slice(2));
58098
58167
  break;
58099
58168
  case "prs":
58100
- await cmdPrs(config, args[0], args[1], args[2]);
58169
+ await cmdPrs(config, args);
58101
58170
  break;
58102
58171
  case "pr-get":
58103
58172
  await cmdPrGet(config, args[0], args[1]);
@@ -58118,7 +58187,7 @@ async function main() {
58118
58187
  await cmdPrAutocomplete(config, args[0], args[1]);
58119
58188
  break;
58120
58189
  case "builds":
58121
- await cmdBuilds(config, args[0]);
58190
+ await cmdBuilds(config, args);
58122
58191
  break;
58123
58192
  default:
58124
58193
  console.error(`Unknown command: ${command}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanaen/ado-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Lightweight Azure DevOps CLI for repos, work items, pull requests, and builds",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,33 +10,36 @@ Use the `ado` CLI instead of ad-hoc curl commands.
10
10
  ## Preflight
11
11
 
12
12
  Run `ado status` before any other command. It checks token, configuration, and connectivity in one step.
13
- If it reports an issue, guide the user to run `ado init` (or `ado init --local` for per-project setup) to configure the CLI.
13
+ If it reports an issue, guide the user to set the required environment variables (`ADO_PAT`, `ADO_COLLECTION_URL`, `ADO_PROJECT`, `ADO_REPO`).
14
+ Do **not** suggest `ado init` — it requires an interactive terminal and cannot run in this context.
14
15
 
15
16
  ## Core commands
16
17
 
17
- - Initialize global config: `ado init`
18
- - Initialize local (per-project) config: `ado init --local`
19
18
  - Show resolved config: `ado config`
20
- - List repos: `ado repos`
21
- - List branches: `ado branches "MyRepo"`
19
+ - List repos: `ado repos` (or `ado repos --json` for structured output)
20
+ - List branches: `ado branches "MyRepo"` (or `--json`)
22
21
  - Get work item: `ado workitem-get <id>`
23
22
  - Get full raw work item payload: `ado workitem-get <id> --raw --expand=all`
24
- - List recent work items: `ado workitems-recent 10`
23
+ - List recent work items: `ado workitems-recent 10` (or `--json`)
25
24
  - List recent work items filtered by tag/type/state: `ado workitems-recent 20 --type=Bug --tag=bot --state=New`
26
25
  - List comments on a work item: `ado workitem-comments <id> --top=100 --order=desc`
27
26
  - Add a comment on a work item: `ado workitem-comment-add <id> --text="..."`
28
27
  - Update an existing comment on a work item: `ado workitem-comment-update <id> <commentId> --text="..."`
29
- - List PRs: `ado prs active 10 "MyRepo"`
28
+ - List PRs: `ado prs active 10 "MyRepo"` (or `--json`)
30
29
  - Get PR: `ado pr-get <id> "MyRepo"`
31
30
  - Create PR: `ado pr-create --title="..." --source="feature/x" --target="develop" --description="..." --repo="MyRepo" --work-items=123,456 --tags=backend,release-1`
32
31
  - Update PR: `ado pr-update <id> --title="..." --description="..." --repo="MyRepo" --work-items=123,456 --tags=backend,release-1`
33
32
  - Cherry-pick PR onto another branch: `ado pr-cherry-pick <id> --target="main" --topic="cherry-pick-branch" --repo="MyRepo"`
34
33
  - Approve PR: `ado pr-approve <id> "MyRepo"`
35
34
  - Enable auto-complete: `ado pr-autocomplete <id> "MyRepo"`
36
- - List builds: `ado builds 10`
35
+ - List builds: `ado builds 10` (or `--json`)
36
+
37
+ ## Structured output
38
+
39
+ Add `--json` to listing commands (`repos`, `branches`, `prs`, `builds`, `workitems-recent`) to get JSON arrays instead of tab-delimited text. Commands like `workitem-get`, `pr-get`, `workitem-comments`, and `config` already output JSON by default.
37
40
 
38
41
  ## Safety rules
39
42
 
40
- - Never print `DEVOPS_PAT`.
43
+ - Never print `ADO_PAT` or `DEVOPS_PAT`.
41
44
  - Prefer CLI commands over direct API calls.
42
45
  - Use `ADO_INSECURE=1` only when needed for trusted self-signed endpoints.