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 +23 -0
- package/dist/cli.js +256 -15
- package/dist/cli.js.map +1 -1
- package/dist/console-ui/assets/{index-Lxr-TdWe.js → index-gaFPpGMN.js} +129 -129
- package/dist/console-ui/index.html +1 -1
- package/package.json +1 -1
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 (
|
|
1254
|
-
wiql =
|
|
1255
|
-
} else if (q
|
|
1256
|
-
wiql =
|
|
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
|
|
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
|
|
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", "
|
|
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
|
|
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
|
|
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"
|
|
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|
|
|
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
|
|
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));
|