@thanaen/ado-cli 0.4.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;
@@ -57412,6 +57423,7 @@ var import_GitInterfaces = __toESM(require_GitInterfaces(), 1);
57412
57423
  var import_WorkItemTrackingInterfaces = __toESM(require_WorkItemTrackingInterfaces(), 1);
57413
57424
  var import_BuildInterfaces = __toESM(require_BuildInterfaces(), 1);
57414
57425
  var import_VSSInterfaces = __toESM(require_VSSInterfaces(), 1);
57426
+ process.removeAllListeners("warning");
57415
57427
  function pickRepo(config, value) {
57416
57428
  return value || config.repo;
57417
57429
  }
@@ -57467,9 +57479,9 @@ async function cmdSmoke(config) {
57467
57479
  async function cmdStatus() {
57468
57480
  const localConfig = loadLocalConfig();
57469
57481
  const fileConfig = loadFileConfig();
57470
- 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;
57471
57483
  if (!pat) {
57472
- console.error("Not ready: DEVOPS_PAT is not set");
57484
+ console.error("Not ready: ADO_PAT (or DEVOPS_PAT) is not set");
57473
57485
  process.exit(1);
57474
57486
  }
57475
57487
  const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl ?? "";
@@ -57498,17 +57510,28 @@ async function cmdStatus() {
57498
57510
  process.exit(1);
57499
57511
  }
57500
57512
  }
57501
- async function cmdRepos(config) {
57513
+ async function cmdRepos(config, args = []) {
57514
+ const json = args.includes("--json");
57502
57515
  const gitApi = await config.connection.getGitApi();
57503
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
+ }
57504
57521
  for (const repo of repos) {
57505
57522
  console.log(`${repo.id} ${repo.name}`);
57506
57523
  }
57507
57524
  }
57508
- 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("--"));
57509
57528
  const gitApi = await config.connection.getGitApi();
57510
57529
  const repo = pickRepo(config, repoArg);
57511
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
+ }
57512
57535
  for (const ref of refs) {
57513
57536
  const name = String(ref.name || "").replace("refs/heads/", "");
57514
57537
  console.log(name);
@@ -57553,18 +57576,25 @@ async function cmdWorkItemGet(config, idRaw, args = []) {
57553
57576
  }, null, 2));
57554
57577
  }
57555
57578
  async function cmdWorkItemsRecent(config, args = []) {
57579
+ const json = args.includes("--json");
57580
+ const filteredArgs = args.filter((a) => a !== "--json");
57556
57581
  let parsedArgs;
57557
57582
  try {
57558
- parsedArgs = parseWorkItemsRecentArgs(args);
57583
+ parsedArgs = parseWorkItemsRecentArgs(filteredArgs);
57559
57584
  } catch (error) {
57560
57585
  console.error(error instanceof Error ? error.message : String(error));
57561
- 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]");
57562
57587
  process.exit(1);
57563
57588
  }
57564
57589
  const witApi = await config.connection.getWorkItemTrackingApi();
57565
57590
  const wiqlResult = await witApi.queryByWiql({ query: buildRecentWorkItemsWiql(parsedArgs.filters) }, { project: config.project }, undefined, parsedArgs.top);
57566
- for (const wi of wiqlResult.workItems ?? []) {
57567
- 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);
57568
57598
  }
57569
57599
  }
57570
57600
  async function cmdWorkItemComments(config, idRaw, args = []) {
@@ -57670,12 +57700,26 @@ async function cmdWorkItemCommentUpdate(config, idRaw, commentIdRaw, args = [])
57670
57700
  text: result.text ?? text
57671
57701
  }, null, 2));
57672
57702
  }
57673
- 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];
57674
57709
  const top = Number(topRaw);
57675
57710
  const boundedTop = Number.isFinite(top) && top > 0 ? Math.min(top, 50) : 10;
57676
57711
  const repo = pickRepo(config, repoArg);
57677
57712
  const gitApi = await config.connection.getGitApi();
57678
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
+ }
57679
57723
  for (const pr of prs) {
57680
57724
  const createdBy = pr.createdBy?.displayName ?? "unknown";
57681
57725
  console.log(`#${pr.pullRequestId} [${prStatusName(pr.status)}] ${pr.title} (${createdBy})`);
@@ -57772,11 +57816,24 @@ async function cmdPrAutocomplete(config, idRaw, repoArg) {
57772
57816
  console.log(`Enabled auto-complete for PR #${id}`);
57773
57817
  }
57774
57818
  }
57775
- 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";
57776
57823
  const top = Number(topRaw);
57777
57824
  const boundedTop = Number.isFinite(top) && top > 0 ? Math.min(top, 50) : 10;
57778
57825
  const buildApi = await config.connection.getBuildApi();
57779
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
+ }
57780
57837
  for (const b of builds) {
57781
57838
  console.log(`#${b.id} ${b.status}/${b.result ?? "n/a"} ${b.definition?.name ?? "unknown"} ${b.sourceBranch ?? ""}`);
57782
57839
  }
@@ -57941,6 +57998,11 @@ async function cmdPrCherryPick(config, args) {
57941
57998
  }
57942
57999
  }
57943
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
+ }
57944
58006
  const isLocal = args.includes("--local");
57945
58007
  const { createInterface } = await import("node:readline/promises");
57946
58008
  const { mkdirSync, writeFileSync, existsSync: existsSync2 } = await import("node:fs");
@@ -57999,7 +58061,7 @@ Configuration saved to ${configPath}`);
57999
58061
  function cmdConfig() {
58000
58062
  const fileConfig = loadFileConfig();
58001
58063
  const localConfig = loadLocalConfig();
58002
- 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;
58003
58065
  const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl;
58004
58066
  const project = process.env.ADO_PROJECT ?? localConfig.project ?? fileConfig.project;
58005
58067
  const repo = process.env.ADO_REPO ?? localConfig.repo ?? fileConfig.repo;
@@ -58025,21 +58087,29 @@ Commands:
58025
58087
  config
58026
58088
  status
58027
58089
  smoke
58028
- repos
58029
- branches [repo]
58090
+ repos [--json]
58091
+ branches [repo] [--json]
58030
58092
  workitem-get <id> [--raw] [--expand=all|fields|links|relations]
58031
- workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>]
58093
+ workitems-recent [top] [--tag=<tag>] [--type=<work-item-type>] [--state=<state>] [--json]
58032
58094
  workitem-comments <id> [top] [--top=<n>] [--order=asc|desc]
58033
58095
  workitem-comment-add <id> --text="..." [--file=path]
58034
58096
  workitem-comment-update <id> <commentId> --text="..." [--file=path]
58035
- prs [status] [top] [repo]
58097
+ prs [status] [top] [repo] [--json]
58036
58098
  pr-get <id> [repo]
58037
58099
  pr-create --title=... --source=... --target=... [--description=...] [--repo=...] [--work-items=123,456] [--tags=tag-a,tag-b]
58038
58100
  pr-update <id> [--title=...] [--description=...] [--repo=...] [--work-items=123,456] [--tags=tag-a,tag-b]
58039
58101
  pr-cherry-pick <id> --target=... [--topic=branch-name] [--repo=...]
58040
58102
  pr-approve <id> [repo]
58041
58103
  pr-autocomplete <id> [repo]
58042
- 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
58043
58113
  `);
58044
58114
  }
58045
58115
  async function printVersion() {
@@ -58075,10 +58145,10 @@ async function main() {
58075
58145
  await cmdSmoke(config);
58076
58146
  break;
58077
58147
  case "repos":
58078
- await cmdRepos(config);
58148
+ await cmdRepos(config, args);
58079
58149
  break;
58080
58150
  case "branches":
58081
- await cmdBranches(config, args[0]);
58151
+ await cmdBranches(config, args);
58082
58152
  break;
58083
58153
  case "workitem-get":
58084
58154
  await cmdWorkItemGet(config, args[0], args.slice(1));
@@ -58096,7 +58166,7 @@ async function main() {
58096
58166
  await cmdWorkItemCommentUpdate(config, args[0], args[1], args.slice(2));
58097
58167
  break;
58098
58168
  case "prs":
58099
- await cmdPrs(config, args[0], args[1], args[2]);
58169
+ await cmdPrs(config, args);
58100
58170
  break;
58101
58171
  case "pr-get":
58102
58172
  await cmdPrGet(config, args[0], args[1]);
@@ -58117,7 +58187,7 @@ async function main() {
58117
58187
  await cmdPrAutocomplete(config, args[0], args[1]);
58118
58188
  break;
58119
58189
  case "builds":
58120
- await cmdBuilds(config, args[0]);
58190
+ await cmdBuilds(config, args);
58121
58191
  break;
58122
58192
  default:
58123
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.4.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.