squad-kit 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] — 2026-04-28
11
+
12
+ ### What's new
13
+
14
+ - GitHub Issues works as the issue tracker: set owner/repo in config, PAT (or
15
+ `GITHUB_TOKEN` / `GITHUB_HOST`) in secrets, search and import like Jira or
16
+ Azure. Console Secrets page has GitHub host and token fields plus a
17
+ connectivity test; `squad doctor` probes the GitHub REST API.
18
+
19
+ ### Breaking changes
20
+
21
+ - Linear is no longer a tracker type. Use GitHub Issues, Jira, or Azure, or
22
+ set tracker to `none`. Update `.squad/config.yaml` if you had `linear`.
23
+
24
+ ## [0.6.1] — 2026-04-26
25
+
26
+ ### Bug fixes
27
+
28
+ - Azure DevOps console tracker search was failing on first load and on text
29
+ search with HTTP 400. WIQL queries now require the team project (`@project`)
30
+ in the WHERE clause; HTTP 400 responses include a clearer hint about
31
+ org/project or WIQL scoping.
32
+
10
33
  ## [0.6.0] — 2026-04-29
11
34
 
12
35
  ### Why this release
package/dist/cli.js CHANGED
@@ -495,6 +495,10 @@ function mergeSecrets(base, patch) {
495
495
  azure: {
496
496
  ...base.tracker?.azure ?? {},
497
497
  ...filterEmpty(patch.tracker?.azure ?? {})
498
+ },
499
+ github: {
500
+ ...base.tracker?.github ?? {},
501
+ ...filterEmpty(patch.tracker?.github ?? {})
498
502
  }
499
503
  }
500
504
  };
@@ -768,6 +772,8 @@ function overlayTrackerEnv(base) {
768
772
  const azOrg = process.env.AZURE_DEVOPS_ORG;
769
773
  const azProj = process.env.AZURE_DEVOPS_PROJECT;
770
774
  const azPat = process.env.AZURE_DEVOPS_PAT ?? process.env.SQUAD_TRACKER_API_KEY;
775
+ const ghHost = process.env.GITHUB_HOST;
776
+ const ghPat = process.env.GITHUB_TOKEN ?? process.env.SQUAD_TRACKER_API_KEY;
771
777
  return {
772
778
  ...base,
773
779
  tracker: {
@@ -780,6 +786,10 @@ function overlayTrackerEnv(base) {
780
786
  organization: azOrg ?? base.tracker?.azure?.organization,
781
787
  project: azProj ?? base.tracker?.azure?.project,
782
788
  pat: azPat ?? base.tracker?.azure?.pat
789
+ },
790
+ github: {
791
+ host: ghHost ?? base.tracker?.github?.host,
792
+ pat: ghPat ?? base.tracker?.github?.pat
783
793
  }
784
794
  }
785
795
  };
@@ -858,6 +868,27 @@ async function probeAzureConnectivity(secrets, config2) {
858
868
  return { ok: false, detail: err.message };
859
869
  }
860
870
  }
871
+ async function probeGitHubConnectivity(secrets, _config) {
872
+ const gh = overlayTrackerEnv(secrets).tracker?.github ?? {};
873
+ const pat = gh.pat ?? "";
874
+ const rawHost = (gh.host ?? "api.github.com").replace(/^https?:\/\//i, "").replace(/\/+$/, "");
875
+ const apiPath = rawHost === "api.github.com" ? "" : "/api/v3";
876
+ const url = `https://${rawHost}${apiPath}/user`;
877
+ try {
878
+ const res = await fetch(url, {
879
+ headers: {
880
+ Authorization: `Bearer ${pat}`,
881
+ Accept: "application/vnd.github+json",
882
+ "X-GitHub-Api-Version": "2022-11-28",
883
+ "User-Agent": "squad-kit"
884
+ }
885
+ });
886
+ if (res.ok) return { ok: true };
887
+ return { ok: false, status: res.status, detail: (await res.text()).slice(0, 200) };
888
+ } catch (err) {
889
+ return { ok: false, detail: err.message };
890
+ }
891
+ }
861
892
 
862
893
  // src/commands/config/shared.ts
863
894
  import { input, password } from "@inquirer/prompts";
@@ -1250,13 +1281,13 @@ var AzureDevOpsClient = class {
1250
1281
  const limit = Math.min(50, Math.max(1, opts?.limit ?? 25));
1251
1282
  const q = query.trim();
1252
1283
  let wiql;
1253
- if (/^\d+$/.test(q)) {
1254
- wiql = `SELECT [System.Id] FROM WorkItems WHERE [System.Id] = ${q}`;
1255
- } else if (q.length === 0) {
1256
- wiql = "SELECT [System.Id] FROM WorkItems ORDER BY [System.ChangedDate] DESC";
1284
+ if (q.length === 0) {
1285
+ wiql = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project ORDER BY [System.ChangedDate] DESC";
1286
+ } else if (/^\d+$/.test(q)) {
1287
+ wiql = `SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.Id] = ${q}`;
1257
1288
  } else {
1258
1289
  const esc = q.replace(/'/g, "''");
1259
- wiql = `SELECT [System.Id] FROM WorkItems WHERE [System.Title] CONTAINS STRING '${esc}' ORDER BY [System.ChangedDate] DESC`;
1290
+ wiql = `SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.Title] CONTAINS '${esc}' ORDER BY [System.ChangedDate] DESC`;
1260
1291
  }
1261
1292
  const wiqlUrl = `${this.baseUrl}/wiql?api-version=${this.apiVersion}`;
1262
1293
  let wiqlRes;
@@ -1350,6 +1381,13 @@ var AzureDevOpsClient = class {
1350
1381
  status
1351
1382
  );
1352
1383
  }
1384
+ if (status === 400) {
1385
+ return new TrackerError(
1386
+ id === "search" ? `Azure DevOps rejected the search query (HTTP 400). This usually means the WIQL is not scoped to a team project, or the configured organization/project (${this.cfg.organization}/${this.cfg.project}) is wrong.` : `Azure DevOps rejected the request (HTTP 400).`,
1387
+ "other",
1388
+ status
1389
+ );
1390
+ }
1353
1391
  if (status === 429) {
1354
1392
  return new TrackerError(`Azure DevOps rate limit hit (HTTP 429). Wait a minute and retry.`, "rate-limited", status);
1355
1393
  }
@@ -1372,6 +1410,166 @@ function stripHtml(html) {
1372
1410
  return out.trim();
1373
1411
  }
1374
1412
 
1413
+ // src/tracker/github.ts
1414
+ var PUBLIC_HOST = "api.github.com";
1415
+ var ID_PREFIX_RE = /^[\w.-]+\/[\w.-]+#/;
1416
+ var GitHubClient = class {
1417
+ constructor(cfg) {
1418
+ this.cfg = cfg;
1419
+ if (!cfg.owner) throw new Error('GitHubClient: missing "owner".');
1420
+ if (!cfg.repo) throw new Error('GitHubClient: missing "repo".');
1421
+ if (!cfg.pat) throw new Error('GitHubClient: missing "pat".');
1422
+ const host = (cfg.host ?? PUBLIC_HOST).replace(/^https?:\/\//i, "").replace(/\/+$/, "");
1423
+ const apiPath = host === PUBLIC_HOST ? "" : "/api/v3";
1424
+ this.baseUrl = `https://${host}${apiPath}`;
1425
+ this.webBase = host === PUBLIC_HOST ? "https://github.com" : `https://${host}`;
1426
+ this.authHeader = `Bearer ${cfg.pat}`;
1427
+ }
1428
+ cfg;
1429
+ name = "github";
1430
+ authHeader;
1431
+ baseUrl;
1432
+ webBase;
1433
+ async fetchIssue(id) {
1434
+ const number = stripIdPrefix(id);
1435
+ const url = `${this.baseUrl}/repos/${encodeURIComponent(this.cfg.owner)}/${encodeURIComponent(
1436
+ this.cfg.repo
1437
+ )}/issues/${encodeURIComponent(number)}`;
1438
+ let res;
1439
+ try {
1440
+ res = await fetch(url, { headers: this.headers() });
1441
+ } catch (err) {
1442
+ throw new TrackerError(`GitHub fetch failed: ${err.message}`, "network");
1443
+ }
1444
+ if (!res.ok) throw this.mapHttpError(res, number);
1445
+ const body = await res.json();
1446
+ return this.toFetchResult(body);
1447
+ }
1448
+ async searchIssues(query, opts) {
1449
+ const limit = Math.min(50, Math.max(1, opts?.limit ?? 25));
1450
+ const q = query.trim();
1451
+ const repoPath = `${encodeURIComponent(this.cfg.owner)}/${encodeURIComponent(this.cfg.repo)}`;
1452
+ if (q.length === 0) {
1453
+ const url2 = `${this.baseUrl}/repos/${repoPath}/issues?state=all&sort=updated&direction=desc&per_page=${limit}`;
1454
+ let res2;
1455
+ try {
1456
+ res2 = await fetch(url2, { headers: this.headers() });
1457
+ } catch (err) {
1458
+ throw new TrackerError(`GitHub search failed: ${err.message}`, "network");
1459
+ }
1460
+ if (!res2.ok) throw this.mapHttpError(res2, "search");
1461
+ const body2 = await res2.json();
1462
+ return (body2 ?? []).map((it) => this.toSearchRow(it));
1463
+ }
1464
+ if (/^\d+$/.test(q) || ID_PREFIX_RE.test(q)) {
1465
+ try {
1466
+ const issue = await this.fetchIssue(q);
1467
+ return [
1468
+ {
1469
+ id: issue.id,
1470
+ title: issue.title,
1471
+ type: issue.type,
1472
+ status: issue.status,
1473
+ url: issue.url
1474
+ }
1475
+ ];
1476
+ } catch (err) {
1477
+ if (err instanceof TrackerError && err.kind === "not-found") return [];
1478
+ throw err;
1479
+ }
1480
+ }
1481
+ const searchQuery = `${q} repo:${this.cfg.owner}/${this.cfg.repo}`;
1482
+ const url = `${this.baseUrl}/search/issues?q=${encodeURIComponent(searchQuery)}&sort=updated&order=desc&per_page=${limit}`;
1483
+ let res;
1484
+ try {
1485
+ res = await fetch(url, { headers: this.headers() });
1486
+ } catch (err) {
1487
+ throw new TrackerError(`GitHub search failed: ${err.message}`, "network");
1488
+ }
1489
+ if (!res.ok) throw this.mapHttpError(res, "search");
1490
+ const body = await res.json();
1491
+ return (body.items ?? []).map((it) => this.toSearchRow(it));
1492
+ }
1493
+ async downloadAttachments(refs, targetDir, opts) {
1494
+ if (refs.length === 0) return [];
1495
+ return downloadAttachmentsWith(refs, targetDir, this.headers(), opts);
1496
+ }
1497
+ toFetchResult(body) {
1498
+ return {
1499
+ id: String(body.number ?? ""),
1500
+ title: body.title ?? "(no title)",
1501
+ description: body.body ?? "",
1502
+ acceptanceCriteria: "",
1503
+ url: body.html_url ?? `${this.webBase}/${this.cfg.owner}/${this.cfg.repo}/issues/${body.number ?? ""}`,
1504
+ labels: Array.isArray(body.labels) ? body.labels.map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean) : [],
1505
+ type: body.pull_request ? "Pull Request" : "Issue",
1506
+ assignee: body.assignee?.login,
1507
+ status: body.state,
1508
+ attachments: [],
1509
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1510
+ };
1511
+ }
1512
+ toSearchRow(body) {
1513
+ return {
1514
+ id: String(body.number ?? ""),
1515
+ title: body.title ?? "(no title)",
1516
+ type: body.pull_request ? "Pull Request" : "Issue",
1517
+ status: body.state,
1518
+ url: body.html_url ?? `${this.webBase}/${this.cfg.owner}/${this.cfg.repo}/issues/${body.number ?? ""}`
1519
+ };
1520
+ }
1521
+ headers() {
1522
+ return {
1523
+ authorization: this.authHeader,
1524
+ accept: "application/vnd.github+json",
1525
+ "x-github-api-version": "2022-11-28",
1526
+ "user-agent": "squad-kit"
1527
+ };
1528
+ }
1529
+ mapHttpError(res, id) {
1530
+ const status = res.status;
1531
+ if (status === 401 || status === 403) {
1532
+ if (res.headers.get("x-ratelimit-remaining") === "0") {
1533
+ return new TrackerError(
1534
+ `GitHub rate limit hit (HTTP ${status}). Wait until the limit resets and retry.`,
1535
+ "rate-limited",
1536
+ status
1537
+ );
1538
+ }
1539
+ return new TrackerError(
1540
+ `GitHub authentication failed (HTTP ${status}). Check your PAT in .squad/secrets.yaml; it needs "repo" scope (or "Issues: read" for fine-grained tokens).`,
1541
+ "auth",
1542
+ status
1543
+ );
1544
+ }
1545
+ if (status === 404) {
1546
+ return new TrackerError(
1547
+ id === "search" ? `GitHub search failed (HTTP 404) for ${this.cfg.owner}/${this.cfg.repo}.` : `GitHub issue "${id}" not found in ${this.cfg.owner}/${this.cfg.repo} (HTTP 404). Check the id and tracker.workspace / tracker.project in .squad/config.yaml.`,
1548
+ "not-found",
1549
+ status
1550
+ );
1551
+ }
1552
+ if (status === 422) {
1553
+ return new TrackerError(
1554
+ `GitHub rejected the search query (HTTP 422). Try a simpler query or a bare issue number.`,
1555
+ "other",
1556
+ status
1557
+ );
1558
+ }
1559
+ if (status === 429) {
1560
+ return new TrackerError(`GitHub rate limit hit (HTTP 429). Wait a minute and retry.`, "rate-limited", status);
1561
+ }
1562
+ return new TrackerError(
1563
+ id === "search" ? `GitHub search failed (HTTP ${status}).` : `GitHub request failed (HTTP ${status}).`,
1564
+ "other",
1565
+ status
1566
+ );
1567
+ }
1568
+ };
1569
+ function stripIdPrefix(id) {
1570
+ return id.replace(ID_PREFIX_RE, "");
1571
+ }
1572
+
1375
1573
  // src/tracker/adf.ts
1376
1574
  var BLOCK_TYPES = /* @__PURE__ */ new Set([
1377
1575
  "paragraph",
@@ -1558,29 +1756,44 @@ function clientFor(config2, secrets) {
1558
1756
  }
1559
1757
  return { client: new AzureDevOpsClient({ organization, project, pat: az.pat }) };
1560
1758
  }
1759
+ case "github": {
1760
+ const gh = secrets.tracker?.github ?? {};
1761
+ const owner = config2.tracker.workspace;
1762
+ const repo = config2.tracker.project;
1763
+ if (!owner || !repo || !gh.pat) {
1764
+ return {
1765
+ error: {
1766
+ kind: "missing-credentials",
1767
+ message: "GitHub credentials are incomplete.",
1768
+ detail: "Set tracker.workspace (owner) and tracker.project (repo) in .squad/config.yaml, then run `squad config set tracker` to enter the PAT, or set GITHUB_TOKEN (and optionally GITHUB_HOST for GHES). Run `squad doctor` to verify the connection."
1769
+ }
1770
+ };
1771
+ }
1772
+ return { client: new GitHubClient({ owner, repo, pat: gh.pat, host: gh.host }) };
1773
+ }
1561
1774
  case "none":
1562
1775
  default:
1563
1776
  return {
1564
1777
  error: {
1565
1778
  kind: "unsupported-tracker",
1566
1779
  message: `Tracker type "${config2.tracker.type}" does not support auto-fetch in 0.2.0.`,
1567
- detail: "Run `squad config set tracker` to use jira or azure for fetch, or pass `--no-fetch` on new-story. Run `squad doctor` to review tracker setup."
1780
+ detail: "Run `squad config set tracker` to use jira, azure, or github for fetch, or pass `--no-fetch` on new-story. Run `squad doctor` to review tracker setup."
1568
1781
  }
1569
1782
  };
1570
1783
  }
1571
1784
  }
1572
1785
 
1573
1786
  // src/commands/config/set-tracker.ts
1574
- var TYPES = ["none", "github", "linear", "jira", "azure"];
1787
+ var TYPES = ["none", "github", "jira", "azure"];
1575
1788
  function parseType(t) {
1576
1789
  if (!t) {
1577
1790
  throw new Error(
1578
- "Pass --type (none|jira|azure|github|linear) with --yes, or run `squad config set tracker` without --yes in a TTY to pick a type."
1791
+ "Pass --type (none|jira|azure|github) with --yes, or run `squad config set tracker` without --yes in a TTY to pick a type."
1579
1792
  );
1580
1793
  }
1581
1794
  if (!TYPES.includes(t)) {
1582
1795
  throw new Error(
1583
- `Invalid --type "${t}". Use none | jira | azure | github | linear, or run \`squad config set tracker\` interactively.`
1796
+ `Invalid --type "${t}". Use none | jira | azure | github, or run \`squad config set tracker\` interactively.`
1584
1797
  );
1585
1798
  }
1586
1799
  return t;
@@ -1613,7 +1826,6 @@ async function runConfigSetTracker(opts = {}) {
1613
1826
  choices: [
1614
1827
  { name: "None", value: "none" },
1615
1828
  { name: "GitHub Issues", value: "github" },
1616
- { name: "Linear", value: "linear" },
1617
1829
  { name: "Jira", value: "jira" },
1618
1830
  { name: "Azure DevOps", value: "azure" }
1619
1831
  ],
@@ -1623,7 +1835,7 @@ async function runConfigSetTracker(opts = {}) {
1623
1835
  let nextTracker;
1624
1836
  if (type === "none") {
1625
1837
  nextTracker = { type: "none" };
1626
- } else if (type === "github" || type === "linear") {
1838
+ } else if (type === "github") {
1627
1839
  nextTracker = { ...config2.tracker, type };
1628
1840
  } else if (type === "jira") {
1629
1841
  if (interactive) {
@@ -1857,7 +2069,6 @@ async function runInit(opts) {
1857
2069
  choices: [
1858
2070
  { name: "None", value: "none" },
1859
2071
  { name: "GitHub Issues", value: "github" },
1860
- { name: "Linear", value: "linear" },
1861
2072
  { name: "Jira", value: "jira" },
1862
2073
  { name: "Azure DevOps", value: "azure" }
1863
2074
  ],
@@ -2203,7 +2414,6 @@ function readPlanMetadata(planFileAbsPath) {
2203
2414
  // src/core/tracker.ts
2204
2415
  var VALIDATORS = {
2205
2416
  github: /^(?:[\w.-]+\/[\w.-]+#)?\d+$/,
2206
- linear: /^[A-Z][A-Z0-9]+-\d+$/i,
2207
2417
  jira: /^[A-Z][A-Z0-9]+-\d+$/,
2208
2418
  azure: /^\d+$/
2209
2419
  };
@@ -5396,6 +5606,14 @@ async function checkTrackerConfig(_paths, ctx) {
5396
5606
  detail: "Azure DevOps requires tracker.workspace (organization) and tracker.project"
5397
5607
  };
5398
5608
  }
5609
+ if (t.type === "github" && (!t.workspace?.trim() || !t.project?.trim())) {
5610
+ return {
5611
+ id: "tracker-config",
5612
+ name: "tracker configuration",
5613
+ status: "fail",
5614
+ detail: "GitHub requires tracker.workspace (owner) and tracker.project (repo)"
5615
+ };
5616
+ }
5399
5617
  return { id: "tracker-config", name: "tracker configuration", status: "ok" };
5400
5618
  }
5401
5619
  function formatClientError(err) {
@@ -5459,6 +5677,16 @@ async function checkTrackerConnectivity(paths, ctx) {
5459
5677
  detail: r.status !== void 0 ? `HTTP ${r.status}` : r.detail ?? "request failed"
5460
5678
  };
5461
5679
  }
5680
+ if (client.name === "github") {
5681
+ const r = await probeGitHubConnectivity(secrets, ctx.config);
5682
+ if (r.ok) return { id: "tracker-live", name: "tracker connectivity", status: "ok", detail: "GitHub REST" };
5683
+ return {
5684
+ id: "tracker-live",
5685
+ name: "tracker connectivity",
5686
+ status: "fail",
5687
+ detail: r.status !== void 0 ? `HTTP ${r.status}` : r.detail ?? "request failed"
5688
+ };
5689
+ }
5462
5690
  return {
5463
5691
  id: "tracker-live",
5464
5692
  name: "tracker connectivity",
@@ -6433,6 +6661,10 @@ function mountSecretsApi(app, opts) {
6433
6661
  organization: s.tracker?.azure?.organization ?? null,
6434
6662
  project: s.tracker?.azure?.project ?? null,
6435
6663
  pat: maskToken(s.tracker?.azure?.pat)
6664
+ },
6665
+ github: {
6666
+ host: s.tracker?.github?.host ?? null,
6667
+ pat: maskToken(s.tracker?.github?.pat)
6436
6668
  }
6437
6669
  }
6438
6670
  });
@@ -6453,6 +6685,10 @@ function mountSecretsApi(app, opts) {
6453
6685
  organization: z5.string().optional(),
6454
6686
  project: z5.string().optional(),
6455
6687
  pat: z5.string().optional()
6688
+ }).optional(),
6689
+ github: z5.object({
6690
+ host: z5.string().optional(),
6691
+ pat: z5.string().optional()
6456
6692
  }).optional()
6457
6693
  }).optional()
6458
6694
  });
@@ -6484,6 +6720,11 @@ function mountSecretsApi(app, opts) {
6484
6720
  const s = loadSecrets(opts.paths.secretsFile);
6485
6721
  return c.json(await probeAzureConnectivity(s, cfg));
6486
6722
  });
6723
+ app.post("/api/secrets/test/github", async (c) => {
6724
+ const cfg = loadConfig(opts.paths.configFile);
6725
+ const s = loadSecrets(opts.paths.secretsFile);
6726
+ return c.json(await probeGitHubConnectivity(s, cfg));
6727
+ });
6487
6728
  }
6488
6729
 
6489
6730
  // src/console/api/tracker.ts
@@ -7218,7 +7459,7 @@ async function runRmPlan(planPathOrSequence, opts = {}) {
7218
7459
  // src/cli.ts
7219
7460
  var program = new Command();
7220
7461
  program.name("squad").description("Plan once, execute cheap. A 3-step SDD workflow CLI.").version("0.6.0");
7221
- program.command("init").description("Bootstrap .squad/ in the current directory").option("--agents <list>", "Comma-separated agent list: claude-code,cursor,copilot,gemini").option("--tracker <type>", "Tracker type: none|github|linear|jira|azure").option(
7462
+ program.command("init").description("Bootstrap .squad/ in the current directory").option("--agents <list>", "Comma-separated agent list: claude-code,cursor,copilot,gemini").option("--tracker <type>", "Tracker type: none|github|jira|azure").option(
7222
7463
  "--tracker-workspace <hostOrOrg>",
7223
7464
  "Jira host or Azure org (non-interactive default; also written to config for jira/azure)"
7224
7465
  ).option("--tracker-project <name>", "Azure project name (non-interactive; written to config when set)").option("--name <name>", "Project name").option("--language <lang>", "Primary language").option("--force", "Overwrite existing files", false).option("-y, --yes", "Accept defaults (non-interactive)", false).option(
@@ -7266,7 +7507,7 @@ var config = program.command("config").description("Inspect and change .squad/ s
7266
7507
  config.command("show").description("Show the current .squad/config.yaml and .squad/secrets.yaml state (secrets masked)").option("--json", "Emit as JSON to stdout (secrets still masked)", false).action(wrap(runConfigShow));
7267
7508
  var set = config.command("set").description("Interactively change a section");
7268
7509
  set.command("planner").description("Set or change the planner provider, model override, and credentials").option("--provider <name>", "Skip the provider prompt (anthropic|openai|google)").option("-y, --yes", "Fail fast instead of prompting for missing values", false).action(wrap(runConfigSetPlanner));
7269
- set.command("tracker").description("Set or change the tracker type, workspace, and credentials").option("--type <name>", "Skip the tracker-type prompt (none|jira|azure|github|linear)").option("-y, --yes", "Fail fast instead of prompting for missing values", false).action(wrap(runConfigSetTracker));
7510
+ set.command("tracker").description("Set or change the tracker type, workspace, and credentials").option("--type <name>", "Skip the tracker-type prompt (none|jira|azure|github)").option("-y, --yes", "Fail fast instead of prompting for missing values", false).action(wrap(runConfigSetTracker));
7270
7511
  var unset = config.command("unset").description("Disable or clear a section");
7271
7512
  unset.command("planner").description("Disable the direct planner (keeps provider credentials unless --remove-credentials)").option("--remove-credentials", "Also delete planner keys from .squad/secrets.yaml", false).option("-y, --yes", "Skip the confirmation prompt", false).action(wrap(runConfigUnsetPlanner));
7272
7513
  unset.command("tracker").description('Set tracker.type back to "none" (keeps credentials unless --remove-credentials)').option("--remove-credentials", "Also delete tracker credentials from .squad/secrets.yaml", false).option("-y, --yes", "Skip the confirmation prompt", false).action(wrap(runConfigUnsetTracker));