@trycadence/cli 0.1.2 → 0.1.4
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/cadence +551 -25
- package/package.json +1 -1
package/dist/cadence
CHANGED
|
@@ -1451,9 +1451,13 @@ function createCadenceClient(options = {}) {
|
|
|
1451
1451
|
cli: {
|
|
1452
1452
|
start: (input) => rpc.auth.cli.start.mutate(input),
|
|
1453
1453
|
poll: (input) => rpc.auth.cli.poll.query(input),
|
|
1454
|
-
complete: (input) => rpc.auth.cli.complete.mutate(input)
|
|
1454
|
+
complete: (input) => rpc.auth.cli.complete.mutate(input),
|
|
1455
|
+
refresh: (input) => rpc.auth.cli.refresh.mutate(input)
|
|
1455
1456
|
}
|
|
1456
1457
|
},
|
|
1458
|
+
actors: {
|
|
1459
|
+
ensureWorkspaceAgent: (input) => rpc.actors.ensureWorkspaceAgent.mutate(input)
|
|
1460
|
+
},
|
|
1457
1461
|
events: {
|
|
1458
1462
|
list: (input) => rpc.events.list.query(input)
|
|
1459
1463
|
},
|
|
@@ -1467,7 +1471,8 @@ function createCadenceClient(options = {}) {
|
|
|
1467
1471
|
attach: (input) => rpc.tickets.attach.mutate(input),
|
|
1468
1472
|
update: (input) => rpc.tickets.update.mutate(input),
|
|
1469
1473
|
changeStatus: (input) => rpc.tickets.changeStatus.mutate(input),
|
|
1470
|
-
complete: (input) => rpc.tickets.complete.mutate(input)
|
|
1474
|
+
complete: (input) => rpc.tickets.complete.mutate(input),
|
|
1475
|
+
log: (input) => rpc.tickets.log.mutate(input)
|
|
1471
1476
|
},
|
|
1472
1477
|
intake: {
|
|
1473
1478
|
create: (input) => rpc.intake.create.mutate(input),
|
|
@@ -1476,6 +1481,7 @@ function createCadenceClient(options = {}) {
|
|
|
1476
1481
|
sessions: {
|
|
1477
1482
|
start: (input) => rpc.sessions.start.mutate(input),
|
|
1478
1483
|
end: (input) => rpc.sessions.end.mutate(input),
|
|
1484
|
+
files: (input) => rpc.sessions.files.mutate(input),
|
|
1479
1485
|
current: (input) => rpc.sessions.current.query(input),
|
|
1480
1486
|
leases: {
|
|
1481
1487
|
create: (input) => rpc.sessions.leases.create.mutate(input),
|
|
@@ -1485,7 +1491,13 @@ function createCadenceClient(options = {}) {
|
|
|
1485
1491
|
changesets: {
|
|
1486
1492
|
create: (input) => rpc.changesets.create.mutate(input),
|
|
1487
1493
|
get: (input) => rpc.changesets.get.query(input),
|
|
1488
|
-
|
|
1494
|
+
context: (input) => rpc.changesets.context.query(input),
|
|
1495
|
+
list: (input) => rpc.changesets.list.query(input),
|
|
1496
|
+
notes: {
|
|
1497
|
+
get: (input) => rpc.changesets.notes.get.query(input),
|
|
1498
|
+
put: (input) => rpc.changesets.notes.put.mutate(input),
|
|
1499
|
+
apply: (input) => rpc.changesets.notes.markApplied.mutate(input)
|
|
1500
|
+
}
|
|
1489
1501
|
},
|
|
1490
1502
|
projects: {
|
|
1491
1503
|
default: () => rpc.projects.default.query(),
|
|
@@ -1497,14 +1509,23 @@ function createCadenceClient(options = {}) {
|
|
|
1497
1509
|
|
|
1498
1510
|
// src/index.ts
|
|
1499
1511
|
import { spawnSync } from "child_process";
|
|
1500
|
-
import {
|
|
1501
|
-
import {
|
|
1512
|
+
import { createHash, randomUUID } from "crypto";
|
|
1513
|
+
import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
|
|
1514
|
+
import { basename, dirname, isAbsolute, join, parse } from "path";
|
|
1502
1515
|
import { createInterface } from "readline/promises";
|
|
1503
1516
|
var ticketPriorities = ["low", "normal", "high", "urgent"];
|
|
1504
1517
|
var ticketStatuses = ["backlog", "ready", "in_progress", "blocked", "review", "done", "abandoned"];
|
|
1518
|
+
var sessionFileChangeKinds = ["added", "modified", "deleted", "renamed", "unknown"];
|
|
1519
|
+
var workLogEntryKinds = ["intent", "decision", "rationale", "action", "verification", "blocker", "correction", "note"];
|
|
1520
|
+
var workLogParentSelectors = ["last", "ticket-last", "session-last", "last-decision", "last-correction", "last-action"];
|
|
1521
|
+
var changesetPrNoteSources = ["agent", "human", "system"];
|
|
1505
1522
|
var defaultLeaseTtlSeconds = 5 * 60 * 60;
|
|
1506
1523
|
var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
|
|
1507
1524
|
var defaultCliWebBaseUrl = "https://cadence.deploy.lvl8studios.com";
|
|
1525
|
+
var credentialRefreshSkewMs = 60 * 1000;
|
|
1526
|
+
var credentialRefreshLockTimeoutMs = 10 * 1000;
|
|
1527
|
+
var credentialRefreshLockStaleMs = 60 * 1000;
|
|
1528
|
+
var credentialRefreshLockPollMs = 100;
|
|
1508
1529
|
|
|
1509
1530
|
class CliError extends Error {
|
|
1510
1531
|
code;
|
|
@@ -1524,14 +1545,20 @@ function readFlagValue(argv, index, flag) {
|
|
|
1524
1545
|
return value;
|
|
1525
1546
|
}
|
|
1526
1547
|
var knownCommandPaths = [
|
|
1548
|
+
["actors", "ensure-workspace-agent"],
|
|
1527
1549
|
["auth", "login"],
|
|
1528
1550
|
["auth", "status"],
|
|
1529
1551
|
["auth", "logout"],
|
|
1530
1552
|
["changesets", "create"],
|
|
1531
1553
|
["changesets", "list"],
|
|
1532
1554
|
["changesets", "get"],
|
|
1555
|
+
["changesets", "current"],
|
|
1556
|
+
["changesets", "notes", "get"],
|
|
1557
|
+
["changesets", "notes", "put"],
|
|
1558
|
+
["changesets", "notes", "apply"],
|
|
1533
1559
|
["sessions", "start"],
|
|
1534
1560
|
["sessions", "end"],
|
|
1561
|
+
["sessions", "files"],
|
|
1535
1562
|
["sessions", "current"],
|
|
1536
1563
|
["intake", "dismiss"],
|
|
1537
1564
|
["intake"],
|
|
@@ -1539,6 +1566,7 @@ var knownCommandPaths = [
|
|
|
1539
1566
|
["tickets", "complete"],
|
|
1540
1567
|
["tickets", "release"],
|
|
1541
1568
|
["tickets", "claim"],
|
|
1569
|
+
["tickets", "log"],
|
|
1542
1570
|
["tickets", "update"],
|
|
1543
1571
|
["tickets", "create"],
|
|
1544
1572
|
["tickets", "list"],
|
|
@@ -1603,7 +1631,10 @@ function parseCliArgs(argv) {
|
|
|
1603
1631
|
continue;
|
|
1604
1632
|
}
|
|
1605
1633
|
if (arg.startsWith("--")) {
|
|
1606
|
-
|
|
1634
|
+
const key = arg.slice(2);
|
|
1635
|
+
const value = readFlagValue(argv, index, arg);
|
|
1636
|
+
options[key] = key === "file" && options[key] ? `${options[key]}
|
|
1637
|
+
${value}` : value;
|
|
1607
1638
|
index += 1;
|
|
1608
1639
|
continue;
|
|
1609
1640
|
}
|
|
@@ -1632,12 +1663,40 @@ function safeJsonParse(source, filePath) {
|
|
|
1632
1663
|
}
|
|
1633
1664
|
const record = parsed;
|
|
1634
1665
|
const server = typeof record.server === "string" ? record.server : undefined;
|
|
1666
|
+
const webBaseUrl = typeof record.webBaseUrl === "string" ? record.webBaseUrl : undefined;
|
|
1667
|
+
const profile = typeof record.profile === "string" ? record.profile : undefined;
|
|
1635
1668
|
const projectId = typeof record.projectId === "string" ? record.projectId : typeof record.project === "string" ? record.project : undefined;
|
|
1669
|
+
const profiles = parseProfiles(record.profiles, filePath);
|
|
1636
1670
|
return {
|
|
1637
1671
|
...server ? { server } : {},
|
|
1638
|
-
...
|
|
1672
|
+
...webBaseUrl ? { webBaseUrl } : {},
|
|
1673
|
+
...projectId ? { projectId } : {},
|
|
1674
|
+
...profile ? { profile } : {},
|
|
1675
|
+
...profiles ? { profiles } : {}
|
|
1639
1676
|
};
|
|
1640
1677
|
}
|
|
1678
|
+
function parseProfiles(value, filePath) {
|
|
1679
|
+
if (value === undefined) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1683
|
+
throw new CliError("CONFIG_ERROR", `${filePath} profiles must be a JSON object.`);
|
|
1684
|
+
}
|
|
1685
|
+
const profiles = {};
|
|
1686
|
+
for (const [name, rawProfile] of Object.entries(value)) {
|
|
1687
|
+
if (!rawProfile || typeof rawProfile !== "object" || Array.isArray(rawProfile)) {
|
|
1688
|
+
throw new CliError("CONFIG_ERROR", `${filePath} profile '${name}' must be a JSON object.`);
|
|
1689
|
+
}
|
|
1690
|
+
const profileRecord = rawProfile;
|
|
1691
|
+
const server = typeof profileRecord.server === "string" ? profileRecord.server : undefined;
|
|
1692
|
+
const webBaseUrl = typeof profileRecord.webBaseUrl === "string" ? profileRecord.webBaseUrl : undefined;
|
|
1693
|
+
profiles[name] = {
|
|
1694
|
+
...server ? { server } : {},
|
|
1695
|
+
...webBaseUrl ? { webBaseUrl } : {}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
return profiles;
|
|
1699
|
+
}
|
|
1641
1700
|
async function readOptionalConfig(filePath) {
|
|
1642
1701
|
const file = Bun.file(filePath);
|
|
1643
1702
|
if (!await file.exists()) {
|
|
@@ -1657,12 +1716,104 @@ async function mergeConfigFile(filePath, updates) {
|
|
|
1657
1716
|
...updates
|
|
1658
1717
|
});
|
|
1659
1718
|
}
|
|
1660
|
-
|
|
1719
|
+
function parseAgentIdentityCache(source, filePath) {
|
|
1720
|
+
const parsed = JSON.parse(source);
|
|
1721
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1722
|
+
throw new CliError("CONFIG_ERROR", `${filePath} must contain a JSON object.`);
|
|
1723
|
+
}
|
|
1724
|
+
const record = parsed;
|
|
1725
|
+
const identities = record.identities;
|
|
1726
|
+
if (record.version !== 1 || typeof record.workspaceRef !== "string" || typeof record.workspaceName !== "string" || !identities || typeof identities !== "object" || Array.isArray(identities)) {
|
|
1727
|
+
throw new CliError("CONFIG_ERROR", `${filePath} is not a valid Cadence agent identity cache.`);
|
|
1728
|
+
}
|
|
1729
|
+
const normalizedIdentities = {};
|
|
1730
|
+
for (const [key, value] of Object.entries(identities)) {
|
|
1731
|
+
if (!value || typeof value !== "object") {
|
|
1732
|
+
throw new CliError("CONFIG_ERROR", `${filePath} contains an invalid identity entry.`);
|
|
1733
|
+
}
|
|
1734
|
+
const identity2 = value;
|
|
1735
|
+
if (typeof identity2.actorId !== "string" || typeof identity2.displayName !== "string" || identity2.kind !== "agent" || typeof identity2.agentKind !== "string" || typeof identity2.updatedAt !== "string") {
|
|
1736
|
+
throw new CliError("CONFIG_ERROR", `${filePath} contains an invalid identity entry.`);
|
|
1737
|
+
}
|
|
1738
|
+
normalizedIdentities[key] = {
|
|
1739
|
+
actorId: identity2.actorId,
|
|
1740
|
+
displayName: identity2.displayName,
|
|
1741
|
+
kind: "agent",
|
|
1742
|
+
agentKind: identity2.agentKind,
|
|
1743
|
+
updatedAt: identity2.updatedAt
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
version: 1,
|
|
1748
|
+
workspaceRef: record.workspaceRef,
|
|
1749
|
+
workspaceName: record.workspaceName,
|
|
1750
|
+
identities: normalizedIdentities
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
async function readOptionalAgentIdentityCache(filePath) {
|
|
1754
|
+
const file = Bun.file(filePath);
|
|
1755
|
+
if (!await file.exists()) {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
return parseAgentIdentityCache(await file.text(), filePath);
|
|
1759
|
+
}
|
|
1760
|
+
function agentIdentityCachePath(config, options) {
|
|
1761
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1762
|
+
const cadenceDirectory = config.localConfigPath ? dirname(config.localConfigPath) : config.repoConfigPath ? dirname(config.repoConfigPath) : join(cwd, ".cadence");
|
|
1763
|
+
return join(cadenceDirectory, "agent-identities.json");
|
|
1764
|
+
}
|
|
1765
|
+
async function writeAgentIdentityCache(filePath, cache) {
|
|
1766
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1767
|
+
await writeFile(filePath, `${JSON.stringify(cache, null, 2)}
|
|
1768
|
+
`);
|
|
1769
|
+
}
|
|
1770
|
+
async function ensureWorkspaceAgentIdentity(parsed, config, client, options) {
|
|
1771
|
+
const projectId = requireProjectId(config);
|
|
1772
|
+
const agentKind = requireOption(parsed, "agent-kind");
|
|
1773
|
+
const filePath = agentIdentityCachePath(config, options);
|
|
1774
|
+
const existing = await readOptionalAgentIdentityCache(filePath);
|
|
1775
|
+
const explicitWorkspaceRef = parsed.options["workspace-ref"];
|
|
1776
|
+
const workspaceRef = explicitWorkspaceRef ?? existing?.workspaceRef ?? `local:${randomUUID()}`;
|
|
1777
|
+
const workspaceName = parsed.options["workspace-name"] ?? existing?.workspaceName ?? basename(options.cwd ?? process.cwd());
|
|
1778
|
+
const identities = explicitWorkspaceRef && existing && explicitWorkspaceRef !== existing.workspaceRef ? {} : { ...existing?.identities ?? {} };
|
|
1779
|
+
const ensured = await client.actors.ensureWorkspaceAgent({
|
|
1780
|
+
projectId,
|
|
1781
|
+
workspaceRef,
|
|
1782
|
+
workspaceName,
|
|
1783
|
+
agentKind,
|
|
1784
|
+
...parsed.options["display-name"] ? { displayName: parsed.options["display-name"] } : {}
|
|
1785
|
+
});
|
|
1786
|
+
const updatedAt = new Date().toISOString();
|
|
1787
|
+
const cache = {
|
|
1788
|
+
version: 1,
|
|
1789
|
+
workspaceRef,
|
|
1790
|
+
workspaceName,
|
|
1791
|
+
identities: {
|
|
1792
|
+
...identities,
|
|
1793
|
+
[agentKind]: {
|
|
1794
|
+
actorId: ensured.actorId,
|
|
1795
|
+
displayName: ensured.displayName,
|
|
1796
|
+
kind: "agent",
|
|
1797
|
+
agentKind: ensured.agentKind,
|
|
1798
|
+
updatedAt
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
await writeAgentIdentityCache(filePath, cache);
|
|
1803
|
+
return {
|
|
1804
|
+
...cache.identities[agentKind],
|
|
1805
|
+
workspaceRef: cache.workspaceRef,
|
|
1806
|
+
workspaceName: cache.workspaceName
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
async function findRepoCadenceDirectory(cwd) {
|
|
1661
1810
|
let current = cwd;
|
|
1662
1811
|
while (true) {
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1812
|
+
const cadenceDirectory = join(current, ".cadence");
|
|
1813
|
+
const repoConfig = join(cadenceDirectory, "config.json");
|
|
1814
|
+
const localConfig = join(cadenceDirectory, "config.local.json");
|
|
1815
|
+
if (await Bun.file(repoConfig).exists() || await Bun.file(localConfig).exists()) {
|
|
1816
|
+
return cadenceDirectory;
|
|
1666
1817
|
}
|
|
1667
1818
|
const parent = dirname(current);
|
|
1668
1819
|
if (parent === current) {
|
|
@@ -1678,18 +1829,34 @@ async function resolveCliConfig(flags, options = {}) {
|
|
|
1678
1829
|
const env = options.env ?? process.env;
|
|
1679
1830
|
const cwd = options.cwd ?? process.cwd();
|
|
1680
1831
|
const globalConfigPath = join(getConfigHome(env), "config.json");
|
|
1681
|
-
const
|
|
1832
|
+
const repoCadenceDirectory = await findRepoCadenceDirectory(cwd);
|
|
1833
|
+
const repoConfigPath = repoCadenceDirectory ? join(repoCadenceDirectory, "config.json") : null;
|
|
1834
|
+
const localConfigPath = repoCadenceDirectory ? join(repoCadenceDirectory, "config.local.json") : null;
|
|
1682
1835
|
const repoConfigPromise = repoConfigPath ? readOptionalConfig(repoConfigPath) : Promise.resolve({});
|
|
1683
|
-
const
|
|
1836
|
+
const localConfigPromise = localConfigPath ? readOptionalConfig(localConfigPath) : Promise.resolve({});
|
|
1837
|
+
const [globalConfig, repoConfig, localConfig] = await Promise.all([
|
|
1684
1838
|
readOptionalConfig(globalConfigPath),
|
|
1685
|
-
repoConfigPromise
|
|
1839
|
+
repoConfigPromise,
|
|
1840
|
+
localConfigPromise
|
|
1686
1841
|
]);
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1842
|
+
const profiles = {
|
|
1843
|
+
...globalConfig.profiles ?? {},
|
|
1844
|
+
...repoConfig.profiles ?? {},
|
|
1845
|
+
...localConfig.profiles ?? {}
|
|
1846
|
+
};
|
|
1847
|
+
const envProfile = env.CADENCE_PROFILE;
|
|
1848
|
+
const profile = envProfile ?? localConfig.profile ?? repoConfig.profile ?? globalConfig.profile ?? null;
|
|
1849
|
+
const profileConfig = profile ? profiles[profile] : undefined;
|
|
1850
|
+
const server = flags.server ?? env.CADENCE_SERVER ?? localConfig.server ?? profileConfig?.server ?? repoConfig.server ?? globalConfig.server ?? defaultCliApiBaseUrl;
|
|
1851
|
+
const webBaseUrl = env.CADENCE_WEB_BASE_URL ?? localConfig.webBaseUrl ?? profileConfig?.webBaseUrl ?? repoConfig.webBaseUrl ?? globalConfig.webBaseUrl ?? deriveWebBaseUrl(server);
|
|
1852
|
+
const projectId = flags.project ?? localConfig.projectId ?? repoConfig.projectId ?? globalConfig.projectId ?? null;
|
|
1689
1853
|
return {
|
|
1690
1854
|
server,
|
|
1855
|
+
webBaseUrl,
|
|
1856
|
+
profile,
|
|
1691
1857
|
projectId,
|
|
1692
1858
|
repoConfigPath,
|
|
1859
|
+
localConfigPath,
|
|
1693
1860
|
globalConfigPath
|
|
1694
1861
|
};
|
|
1695
1862
|
}
|
|
@@ -1740,7 +1907,7 @@ function isInteractive(options) {
|
|
|
1740
1907
|
return options.isInteractive ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
1741
1908
|
}
|
|
1742
1909
|
function getCliWebBaseUrl(config, parsed, options) {
|
|
1743
|
-
return parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL ??
|
|
1910
|
+
return parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL ?? config.webBaseUrl;
|
|
1744
1911
|
}
|
|
1745
1912
|
function deriveWebBaseUrl(server) {
|
|
1746
1913
|
try {
|
|
@@ -1775,6 +1942,17 @@ async function sleep2(milliseconds, options) {
|
|
|
1775
1942
|
}
|
|
1776
1943
|
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1777
1944
|
}
|
|
1945
|
+
async function writeInteractiveStatus(message, options) {
|
|
1946
|
+
if (!isInteractive(options)) {
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (options.writeStatus) {
|
|
1950
|
+
await options.writeStatus(message);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
process.stderr.write(`${message}
|
|
1954
|
+
`);
|
|
1955
|
+
}
|
|
1778
1956
|
function encodeCredential(credential) {
|
|
1779
1957
|
return JSON.stringify(credential);
|
|
1780
1958
|
}
|
|
@@ -1802,6 +1980,68 @@ async function readStoredCredential(store, server) {
|
|
|
1802
1980
|
const rawCredential = await store.getCredential(server);
|
|
1803
1981
|
return rawCredential ? decodeCredential(rawCredential) : null;
|
|
1804
1982
|
}
|
|
1983
|
+
function normalizeServerForCredentialLock(server) {
|
|
1984
|
+
try {
|
|
1985
|
+
return new URL(server).toString().replace(/\/$/, "");
|
|
1986
|
+
} catch {
|
|
1987
|
+
return server.trim();
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
function credentialRefreshLockPath(config) {
|
|
1991
|
+
const normalizedServer = normalizeServerForCredentialLock(config.server);
|
|
1992
|
+
const serverHash = createHash("sha256").update(normalizedServer).digest("hex").slice(0, 24);
|
|
1993
|
+
return join(dirname(config.globalConfigPath), "locks", `credential-refresh-${serverHash}.lock`);
|
|
1994
|
+
}
|
|
1995
|
+
async function waitForCredentialRefreshLockPoll() {
|
|
1996
|
+
await new Promise((resolve) => setTimeout(resolve, credentialRefreshLockPollMs));
|
|
1997
|
+
}
|
|
1998
|
+
async function removeStaleCredentialRefreshLock(lockPath, now) {
|
|
1999
|
+
try {
|
|
2000
|
+
const lockStats = await stat(lockPath);
|
|
2001
|
+
if (now - lockStats.mtimeMs < credentialRefreshLockStaleMs) {
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
2005
|
+
return true;
|
|
2006
|
+
} catch (error) {
|
|
2007
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
2008
|
+
return false;
|
|
2009
|
+
}
|
|
2010
|
+
throw error;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
async function acquireCredentialRefreshLock(config) {
|
|
2014
|
+
const lockPath = credentialRefreshLockPath(config);
|
|
2015
|
+
const deadline = Date.now() + credentialRefreshLockTimeoutMs;
|
|
2016
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
2017
|
+
while (true) {
|
|
2018
|
+
try {
|
|
2019
|
+
await mkdir(lockPath);
|
|
2020
|
+
await writeFile(join(lockPath, "holder.json"), `${JSON.stringify({
|
|
2021
|
+
pid: process.pid,
|
|
2022
|
+
server: normalizeServerForCredentialLock(config.server),
|
|
2023
|
+
acquiredAt: new Date().toISOString()
|
|
2024
|
+
}, null, 2)}
|
|
2025
|
+
`);
|
|
2026
|
+
return { path: lockPath };
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
2029
|
+
throw error;
|
|
2030
|
+
}
|
|
2031
|
+
const now = Date.now();
|
|
2032
|
+
const removedStaleLock = await removeStaleCredentialRefreshLock(lockPath, now);
|
|
2033
|
+
if (!removedStaleLock && now >= deadline) {
|
|
2034
|
+
throw new CliError("AUTH_REFRESH_LOCK_TIMEOUT", "Timed out waiting for another Cadence credential refresh to finish.", {
|
|
2035
|
+
server: config.server
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
await waitForCredentialRefreshLockPoll();
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async function releaseCredentialRefreshLock(lock) {
|
|
2043
|
+
await rm(lock.path, { recursive: true, force: true });
|
|
2044
|
+
}
|
|
1805
2045
|
async function requireCredentialStore(store) {
|
|
1806
2046
|
if (!await store.isAvailable()) {
|
|
1807
2047
|
throw new CliError("CREDENTIAL_STORE_UNAVAILABLE", "OS secure credential storage is unavailable.");
|
|
@@ -1855,6 +2095,7 @@ function helpText() {
|
|
|
1855
2095
|
" cadence auth login [--json]",
|
|
1856
2096
|
" cadence auth status [--json]",
|
|
1857
2097
|
" cadence auth logout [--json]",
|
|
2098
|
+
" cadence actors ensure-workspace-agent --agent-kind <kind> [--workspace-name <name>] [--workspace-ref <ref>] [--display-name <name>] [--project <project-id>] [--json]",
|
|
1858
2099
|
" cadence status [--project <project-id>] [--json]",
|
|
1859
2100
|
" cadence projects list [--json]",
|
|
1860
2101
|
" cadence work overview [--project <project-id>] [--json]",
|
|
@@ -1864,36 +2105,103 @@ function helpText() {
|
|
|
1864
2105
|
" cadence tickets attach <ticket-id> --from-intake <intake-id> --if-version <version> [--project <project-id>] [--json]",
|
|
1865
2106
|
" cadence tickets create --title <text> [--from-intake <intake-id>] [--project <project-id>] [--json]",
|
|
1866
2107
|
" cadence tickets update <ticket-id> --if-version <version> [--title <text>] [--description <text>] [--priority <priority>] [--status <status>] [--project <project-id>] [--json]",
|
|
1867
|
-
" cadence tickets claim <ticket-id> --session <session-id> [--project <project-id>] [--json]",
|
|
2108
|
+
" cadence tickets claim <ticket-id> --session <session-id> [--actor <actor-id>] [--project <project-id>] [--json]",
|
|
1868
2109
|
" cadence tickets release <ticket-id> --lease <lease-id> [--project <project-id>] [--json]",
|
|
2110
|
+
" cadence tickets log <ticket-id> --kind <intent|decision|rationale|action|verification|blocker|correction|note> --body <text> [--summary <text>] [--under <entry-id|ticket-last|session-last|last-decision|last-correction|last-action>] [--session <session-id>] [--changeset <changeset-id>] [--project <project-id>] [--json]",
|
|
1869
2111
|
" cadence tickets complete <ticket-id> --if-version <version> [--summary <summary>] [--project <project-id>] [--json]",
|
|
1870
|
-
" cadence sessions start --ticket <ticket-id> [--changeset <changeset-id>] [--project <project-id>] [--json]",
|
|
2112
|
+
" cadence sessions start --ticket <ticket-id> [--changeset <changeset-id>] [--actor <actor-id>] [--project <project-id>] [--json]",
|
|
1871
2113
|
" cadence sessions end <session-id> --summary <summary> [--project <project-id>] [--json]",
|
|
2114
|
+
" cadence sessions files <session-id> --file <path> [--file <path>] [--kind <added|modified|deleted|renamed|unknown>] [--changeset <changeset-id>] [--project <project-id>] [--json]",
|
|
1872
2115
|
" cadence changesets create --ticket <ticket-id> --branch <branch> [--base-branch <branch>] [--project <project-id>] [--json]",
|
|
1873
2116
|
" cadence intake dismiss <intake-id> --reason <reason> [--project <project-id>] [--json]",
|
|
1874
2117
|
" cadence sessions current [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--json]",
|
|
2118
|
+
" cadence changesets current [--branch current|<branch>] [--project <project-id>] [--json]",
|
|
1875
2119
|
" cadence changesets get <changeset-id> [--project <project-id>] [--json]",
|
|
1876
2120
|
" cadence changesets list [--project <project-id>] [--ticket <ticket-id>] [--status <status>] [--json]",
|
|
2121
|
+
" cadence changesets notes get [--changeset <id>|--branch current|<branch>] [--project <project-id>] [--json]",
|
|
2122
|
+
" cadence changesets notes put [--changeset <id>|--branch current|<branch>] --title <text> --body-file <path> [--head-sha <sha>] [--base-sha <sha>] [--pr-url <url>] [--pr-number <n>] [--project <project-id>] [--json]",
|
|
2123
|
+
" cadence changesets notes apply [--changeset <id>|--branch current|<branch>] --provider github --pr-number <n> --pr-url <url> [--project <project-id>] [--json]",
|
|
1877
2124
|
" cadence events list [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--session <session-id>] [--json]",
|
|
1878
2125
|
"",
|
|
1879
2126
|
"Global flags:",
|
|
1880
2127
|
" --project <id> Cadence project ID or org/project slug",
|
|
2128
|
+
" --server <url> Cadence API server override",
|
|
1881
2129
|
" --json Print stable JSON envelope",
|
|
1882
2130
|
"",
|
|
2131
|
+
"Work log parent selectors:",
|
|
2132
|
+
" --under ticket-last attaches under the latest work-log entry on the Ticket.",
|
|
2133
|
+
" --under session-last attaches under the latest entry in --session.",
|
|
2134
|
+
" --under last-decision|last-correction|last-action attaches under the latest matching kind.",
|
|
2135
|
+
" --under last is only valid with --session; without a session use ticket-last or a kind selector.",
|
|
2136
|
+
"",
|
|
1883
2137
|
"Auth options:",
|
|
1884
2138
|
" --web-base-url <url> Browser login base URL"
|
|
1885
2139
|
].join(`
|
|
1886
2140
|
`);
|
|
1887
2141
|
}
|
|
1888
|
-
|
|
2142
|
+
function credentialExpiresSoon(credential, now = new Date) {
|
|
2143
|
+
if (!credential.expiresAt) {
|
|
2144
|
+
return false;
|
|
2145
|
+
}
|
|
2146
|
+
const expiresAt = new Date(credential.expiresAt).getTime();
|
|
2147
|
+
if (Number.isNaN(expiresAt)) {
|
|
2148
|
+
return false;
|
|
2149
|
+
}
|
|
2150
|
+
return expiresAt <= now.getTime() + credentialRefreshSkewMs;
|
|
2151
|
+
}
|
|
2152
|
+
async function readFreshAccessToken(config, store, authClient) {
|
|
2153
|
+
const credential = await readStoredCredential(store, config.server);
|
|
2154
|
+
if (!credential) {
|
|
2155
|
+
return "";
|
|
2156
|
+
}
|
|
2157
|
+
if (!credentialExpiresSoon(credential)) {
|
|
2158
|
+
return credential.accessToken;
|
|
2159
|
+
}
|
|
2160
|
+
const lock = await acquireCredentialRefreshLock(config);
|
|
2161
|
+
try {
|
|
2162
|
+
const lockedCredential = await readStoredCredential(store, config.server);
|
|
2163
|
+
if (!lockedCredential) {
|
|
2164
|
+
return "";
|
|
2165
|
+
}
|
|
2166
|
+
if (!credentialExpiresSoon(lockedCredential)) {
|
|
2167
|
+
return lockedCredential.accessToken;
|
|
2168
|
+
}
|
|
2169
|
+
if (!lockedCredential.refreshToken) {
|
|
2170
|
+
throw new CliError("AUTH_REFRESH_REQUIRED", "Stored Cadence credential is expired and cannot be refreshed because no refresh token is stored.");
|
|
2171
|
+
}
|
|
2172
|
+
try {
|
|
2173
|
+
const refreshed = await authClient.auth.cli.refresh({
|
|
2174
|
+
refreshToken: lockedCredential.refreshToken
|
|
2175
|
+
});
|
|
2176
|
+
await store.setCredential(config.server, encodeCredential(refreshed));
|
|
2177
|
+
return refreshed.accessToken;
|
|
2178
|
+
} catch (error) {
|
|
2179
|
+
const recoveredCredential = await readStoredCredential(store, config.server);
|
|
2180
|
+
if (recoveredCredential && !credentialExpiresSoon(recoveredCredential)) {
|
|
2181
|
+
return recoveredCredential.accessToken;
|
|
2182
|
+
}
|
|
2183
|
+
throw new CliError("AUTH_REFRESH_FAILED", "Stored Cadence credential is expired and refresh failed. Run `cadence auth login` to reconnect.", {
|
|
2184
|
+
server: config.server,
|
|
2185
|
+
cause: error instanceof Error ? error.message : "Credential refresh failed."
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
} finally {
|
|
2189
|
+
await releaseCredentialRefreshLock(lock);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function createClient(config, options, useStoredCredential = true) {
|
|
1889
2193
|
if (options.client) {
|
|
1890
2194
|
return options.client;
|
|
1891
2195
|
}
|
|
1892
2196
|
const store = getCredentialStore(options);
|
|
1893
|
-
const credential = await readStoredCredential(store, config.server);
|
|
2197
|
+
const credential = useStoredCredential ? await readStoredCredential(store, config.server) : null;
|
|
2198
|
+
const authClient = createCadenceClient({
|
|
2199
|
+
baseUrl: config.server,
|
|
2200
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
2201
|
+
});
|
|
1894
2202
|
return createCadenceClient({
|
|
1895
2203
|
baseUrl: config.server,
|
|
1896
|
-
...credential ? { getAuthToken: async () => (
|
|
2204
|
+
...credential ? { getAuthToken: async () => readFreshAccessToken(config, store, authClient) } : {},
|
|
1897
2205
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
1898
2206
|
});
|
|
1899
2207
|
}
|
|
@@ -1901,6 +2209,7 @@ function commandMeta(parsed, config) {
|
|
|
1901
2209
|
return {
|
|
1902
2210
|
command: parsed.command.name,
|
|
1903
2211
|
server: config.server,
|
|
2212
|
+
...config.profile ? { profile: config.profile } : {},
|
|
1904
2213
|
projectId: config.projectId
|
|
1905
2214
|
};
|
|
1906
2215
|
}
|
|
@@ -1962,6 +2271,55 @@ function parseTicketStatus(value) {
|
|
|
1962
2271
|
}
|
|
1963
2272
|
return value;
|
|
1964
2273
|
}
|
|
2274
|
+
function parseSessionFileChangeKind(value) {
|
|
2275
|
+
if (!value) {
|
|
2276
|
+
return "unknown";
|
|
2277
|
+
}
|
|
2278
|
+
if (sessionFileChangeKinds.includes(value)) {
|
|
2279
|
+
return value;
|
|
2280
|
+
}
|
|
2281
|
+
throw new CliError("CLI_USAGE", "--kind must be one of added, modified, deleted, renamed, or unknown.");
|
|
2282
|
+
}
|
|
2283
|
+
function parseChangeSetPrNoteSource(value) {
|
|
2284
|
+
if (!value) {
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
if (changesetPrNoteSources.includes(value)) {
|
|
2288
|
+
return value;
|
|
2289
|
+
}
|
|
2290
|
+
throw new CliError("CLI_USAGE", "--source must be one of agent, human, system.");
|
|
2291
|
+
}
|
|
2292
|
+
function parseWorkLogEntryKind(value) {
|
|
2293
|
+
if (!value) {
|
|
2294
|
+
throw new CliError("CLI_USAGE", "--kind must be one of intent, decision, rationale, action, verification, blocker, correction, or note.");
|
|
2295
|
+
}
|
|
2296
|
+
if (workLogEntryKinds.includes(value)) {
|
|
2297
|
+
return value;
|
|
2298
|
+
}
|
|
2299
|
+
throw new CliError("CLI_USAGE", "--kind must be one of intent, decision, rationale, action, verification, blocker, correction, or note.");
|
|
2300
|
+
}
|
|
2301
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2302
|
+
function parseWorkLogParent(value) {
|
|
2303
|
+
if (!value) {
|
|
2304
|
+
return {};
|
|
2305
|
+
}
|
|
2306
|
+
if (uuidPattern.test(value)) {
|
|
2307
|
+
return { parentEntryId: value };
|
|
2308
|
+
}
|
|
2309
|
+
if (workLogParentSelectors.includes(value)) {
|
|
2310
|
+
return { parentSelector: value };
|
|
2311
|
+
}
|
|
2312
|
+
throw new CliError("CLI_USAGE", "--under must be a work-log entry UUID or one of ticket-last, session-last, last-decision, last-correction, last-action, or last.");
|
|
2313
|
+
}
|
|
2314
|
+
function parseSessionFilePaths(parsed) {
|
|
2315
|
+
const value = requireOption(parsed, "file");
|
|
2316
|
+
const paths = value.split(`
|
|
2317
|
+
`).map((path) => path.trim()).filter(Boolean);
|
|
2318
|
+
if (paths.length === 0) {
|
|
2319
|
+
throw new CliError("CLI_USAGE", "sessions files requires at least one --file.");
|
|
2320
|
+
}
|
|
2321
|
+
return paths;
|
|
2322
|
+
}
|
|
1965
2323
|
function leaseExpiresAt(ttlSeconds) {
|
|
1966
2324
|
return new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1967
2325
|
}
|
|
@@ -1974,20 +2332,73 @@ function commandMetadata() {
|
|
|
1974
2332
|
source: "cli"
|
|
1975
2333
|
};
|
|
1976
2334
|
}
|
|
2335
|
+
function notesCommandMetadata() {
|
|
2336
|
+
return {
|
|
2337
|
+
occurredAt: new Date().toISOString(),
|
|
2338
|
+
eventSource: "cli"
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
async function resolveCurrentBranch(options) {
|
|
2342
|
+
const injected = await options.resolveCurrentBranch?.();
|
|
2343
|
+
if (injected !== undefined) {
|
|
2344
|
+
const branch2 = injected.trim();
|
|
2345
|
+
if (!branch2) {
|
|
2346
|
+
throw new CliError("GIT_BRANCH_ERROR", "Current git branch is empty.");
|
|
2347
|
+
}
|
|
2348
|
+
return branch2;
|
|
2349
|
+
}
|
|
2350
|
+
const result = spawnSync("git", ["branch", "--show-current"], {
|
|
2351
|
+
cwd: options.cwd,
|
|
2352
|
+
encoding: "utf8"
|
|
2353
|
+
});
|
|
2354
|
+
if (result.status !== 0) {
|
|
2355
|
+
throw new CliError("GIT_BRANCH_ERROR", "Failed to resolve current git branch.", {
|
|
2356
|
+
stderr: result.stderr?.trim() ?? ""
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
const branch = result.stdout.trim();
|
|
2360
|
+
if (!branch) {
|
|
2361
|
+
throw new CliError("GIT_BRANCH_ERROR", "Current git branch is empty.");
|
|
2362
|
+
}
|
|
2363
|
+
return branch;
|
|
2364
|
+
}
|
|
2365
|
+
async function changesetLookupFromOptions(parsed, options) {
|
|
2366
|
+
if (parsed.options.changeset && parsed.options.branch) {
|
|
2367
|
+
throw new CliError("CLI_USAGE", "Use either --changeset or --branch, not both.");
|
|
2368
|
+
}
|
|
2369
|
+
if (parsed.options.changeset) {
|
|
2370
|
+
return {
|
|
2371
|
+
changesetId: parsed.options.changeset
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
const branchOption = parsed.options.branch ?? "current";
|
|
2375
|
+
const branchName = branchOption === "current" ? await resolveCurrentBranch(options) : branchOption;
|
|
2376
|
+
return {
|
|
2377
|
+
branchName
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
async function readBodyFile(path, options) {
|
|
2381
|
+
const resolvedPath = isAbsolute(path) ? path : join(options.cwd ?? process.cwd(), path);
|
|
2382
|
+
return readFile(resolvedPath, "utf8");
|
|
2383
|
+
}
|
|
1977
2384
|
async function runStatus(parsed, options) {
|
|
1978
2385
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
1979
2386
|
const store = getCredentialStore(options);
|
|
1980
2387
|
const credential = await readStoredCredential(store, config.server);
|
|
1981
2388
|
const client = await createClient(config, options);
|
|
1982
2389
|
const health = await client.health();
|
|
2390
|
+
const webBaseUrl = getCliWebBaseUrl(config, parsed, options);
|
|
1983
2391
|
const data = {
|
|
1984
2392
|
server: config.server,
|
|
2393
|
+
webBaseUrl,
|
|
2394
|
+
profile: config.profile,
|
|
1985
2395
|
projectId: config.projectId,
|
|
1986
2396
|
credentialConfigured: Boolean(credential),
|
|
1987
2397
|
authTokenConfigured: Boolean(credential),
|
|
1988
2398
|
health,
|
|
1989
2399
|
config: {
|
|
1990
2400
|
repoConfigPath: config.repoConfigPath,
|
|
2401
|
+
localConfigPath: config.localConfigPath,
|
|
1991
2402
|
globalConfigPath: config.globalConfigPath
|
|
1992
2403
|
}
|
|
1993
2404
|
};
|
|
@@ -2116,7 +2527,7 @@ async function runAuthCommand(parsed, options) {
|
|
|
2116
2527
|
switch (parsed.command.name) {
|
|
2117
2528
|
case "auth.login":
|
|
2118
2529
|
{
|
|
2119
|
-
const client = await createClient(config, options);
|
|
2530
|
+
const client = await createClient(config, options, false);
|
|
2120
2531
|
const challenge = await client.auth.cli.start({
|
|
2121
2532
|
loginBaseUrl: getCliWebBaseUrl(config, parsed, options)
|
|
2122
2533
|
});
|
|
@@ -2128,7 +2539,11 @@ async function runAuthCommand(parsed, options) {
|
|
|
2128
2539
|
});
|
|
2129
2540
|
}
|
|
2130
2541
|
await requireCredentialStore(store);
|
|
2542
|
+
await writeInteractiveStatus("Opening browser to approve Cadence CLI access.", options);
|
|
2543
|
+
await writeInteractiveStatus(`Verification code: ${challenge.deviceCode}`, options);
|
|
2544
|
+
await writeInteractiveStatus(`Login URL: ${challenge.loginUrl}`, options);
|
|
2131
2545
|
await openBrowser(challenge.loginUrl, options);
|
|
2546
|
+
await writeInteractiveStatus("Waiting for browser approval...", options);
|
|
2132
2547
|
let poll = await client.auth.cli.poll({
|
|
2133
2548
|
deviceId: challenge.deviceId,
|
|
2134
2549
|
deviceCode: challenge.deviceCode
|
|
@@ -2148,6 +2563,7 @@ async function runAuthCommand(parsed, options) {
|
|
|
2148
2563
|
}
|
|
2149
2564
|
await store.setCredential(config.server, encodeCredential(poll.credential));
|
|
2150
2565
|
await mergeConfigFile(config.globalConfigPath, { server: config.server });
|
|
2566
|
+
await writeInteractiveStatus("Cadence CLI login approved. Credential stored.", options);
|
|
2151
2567
|
data = {
|
|
2152
2568
|
server: config.server,
|
|
2153
2569
|
credentialStored: true,
|
|
@@ -2252,6 +2668,12 @@ async function runReadCommand(parsed, options) {
|
|
|
2252
2668
|
changesetId: requireArg(parsed, 0, "<changeset-id>")
|
|
2253
2669
|
});
|
|
2254
2670
|
break;
|
|
2671
|
+
case "changesets.current":
|
|
2672
|
+
data = await client.changesets.context({
|
|
2673
|
+
projectId,
|
|
2674
|
+
query: await changesetLookupFromOptions(parsed, options)
|
|
2675
|
+
});
|
|
2676
|
+
break;
|
|
2255
2677
|
case "changesets.list":
|
|
2256
2678
|
data = await client.changesets.list({
|
|
2257
2679
|
projectId,
|
|
@@ -2263,6 +2685,12 @@ async function runReadCommand(parsed, options) {
|
|
|
2263
2685
|
})
|
|
2264
2686
|
});
|
|
2265
2687
|
break;
|
|
2688
|
+
case "changesets.notes.get":
|
|
2689
|
+
data = await client.changesets.notes.get({
|
|
2690
|
+
projectId,
|
|
2691
|
+
query: await changesetLookupFromOptions(parsed, options)
|
|
2692
|
+
});
|
|
2693
|
+
break;
|
|
2266
2694
|
default:
|
|
2267
2695
|
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
2268
2696
|
}
|
|
@@ -2306,6 +2734,32 @@ async function runProjectCommand(parsed, options) {
|
|
|
2306
2734
|
exitCode: 0
|
|
2307
2735
|
};
|
|
2308
2736
|
}
|
|
2737
|
+
async function runActorCommand(parsed, options) {
|
|
2738
|
+
const config = await resolveCliConfig(parsed.flags, options);
|
|
2739
|
+
const client = await createClient(config, options);
|
|
2740
|
+
const meta = commandMeta(parsed, config);
|
|
2741
|
+
let data;
|
|
2742
|
+
switch (parsed.command.name) {
|
|
2743
|
+
case "actors.ensure-workspace-agent":
|
|
2744
|
+
data = await ensureWorkspaceAgentIdentity(parsed, config, client, options);
|
|
2745
|
+
break;
|
|
2746
|
+
default:
|
|
2747
|
+
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
2748
|
+
}
|
|
2749
|
+
if (parsed.flags.json) {
|
|
2750
|
+
return {
|
|
2751
|
+
stdout: formatJson(successEnvelope(data, meta)),
|
|
2752
|
+
stderr: "",
|
|
2753
|
+
exitCode: 0
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
return {
|
|
2757
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
2758
|
+
`,
|
|
2759
|
+
stderr: "",
|
|
2760
|
+
exitCode: 0
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2309
2763
|
async function runIntakeCommand(parsed, options) {
|
|
2310
2764
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2311
2765
|
const projectId = requireProjectId(config);
|
|
@@ -2402,6 +2856,7 @@ async function runIntakeCommand(parsed, options) {
|
|
|
2402
2856
|
ticketId: requireArg(parsed, 0, "<ticket-id>"),
|
|
2403
2857
|
sessionId: requireOption(parsed, "session"),
|
|
2404
2858
|
...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
|
|
2859
|
+
...parsed.options.actor ? { actorId: parsed.options.actor } : {},
|
|
2405
2860
|
expiresAt: leaseExpiresAt(parsePositiveInteger(parsed.options["ttl-seconds"], "--ttl-seconds") ?? defaultLeaseTtlSeconds),
|
|
2406
2861
|
...commandMetadata()
|
|
2407
2862
|
}
|
|
@@ -2418,6 +2873,21 @@ async function runIntakeCommand(parsed, options) {
|
|
|
2418
2873
|
}
|
|
2419
2874
|
});
|
|
2420
2875
|
break;
|
|
2876
|
+
case "tickets.log":
|
|
2877
|
+
data = await client.tickets.log({
|
|
2878
|
+
projectId,
|
|
2879
|
+
ticketId: requireArg(parsed, 0, "<ticket-id>"),
|
|
2880
|
+
entry: {
|
|
2881
|
+
entryKind: parseWorkLogEntryKind(parsed.options.kind),
|
|
2882
|
+
body: requireOption(parsed, "body"),
|
|
2883
|
+
...parsed.options.summary ? { summary: parsed.options.summary } : {},
|
|
2884
|
+
...parseWorkLogParent(parsed.options.under),
|
|
2885
|
+
...parsed.options.session ? { sessionId: parsed.options.session } : {},
|
|
2886
|
+
...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
|
|
2887
|
+
...commandMetadata()
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
break;
|
|
2421
2891
|
case "tickets.complete":
|
|
2422
2892
|
data = await client.tickets.complete({
|
|
2423
2893
|
projectId,
|
|
@@ -2435,6 +2905,7 @@ async function runIntakeCommand(parsed, options) {
|
|
|
2435
2905
|
session: {
|
|
2436
2906
|
ticketId: requireOption(parsed, "ticket"),
|
|
2437
2907
|
...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
|
|
2908
|
+
...parsed.options.actor ? { actorId: parsed.options.actor } : {},
|
|
2438
2909
|
...parsed.options["local-session-ref"] ? { localSessionRef: parsed.options["local-session-ref"] } : {},
|
|
2439
2910
|
...commandMetadata()
|
|
2440
2911
|
}
|
|
@@ -2450,6 +2921,23 @@ async function runIntakeCommand(parsed, options) {
|
|
|
2450
2921
|
}
|
|
2451
2922
|
});
|
|
2452
2923
|
break;
|
|
2924
|
+
case "sessions.files":
|
|
2925
|
+
{
|
|
2926
|
+
const changeKind = parseSessionFileChangeKind(parsed.options.kind);
|
|
2927
|
+
data = await client.sessions.files({
|
|
2928
|
+
projectId,
|
|
2929
|
+
sessionId: requireArg(parsed, 0, "<session-id>"),
|
|
2930
|
+
files: {
|
|
2931
|
+
...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
|
|
2932
|
+
files: parseSessionFilePaths(parsed).map((path) => ({
|
|
2933
|
+
path,
|
|
2934
|
+
changeKind
|
|
2935
|
+
})),
|
|
2936
|
+
...commandMetadata()
|
|
2937
|
+
}
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
break;
|
|
2453
2941
|
case "changesets.create":
|
|
2454
2942
|
data = await client.changesets.create({
|
|
2455
2943
|
projectId,
|
|
@@ -2462,6 +2950,41 @@ async function runIntakeCommand(parsed, options) {
|
|
|
2462
2950
|
}
|
|
2463
2951
|
});
|
|
2464
2952
|
break;
|
|
2953
|
+
case "changesets.notes.put":
|
|
2954
|
+
{
|
|
2955
|
+
const source = parseChangeSetPrNoteSource(parsed.options.source);
|
|
2956
|
+
data = await client.changesets.notes.put({
|
|
2957
|
+
projectId,
|
|
2958
|
+
query: await changesetLookupFromOptions(parsed, options),
|
|
2959
|
+
notes: {
|
|
2960
|
+
title: requireOption(parsed, "title"),
|
|
2961
|
+
body: await readBodyFile(requireOption(parsed, "body-file"), options),
|
|
2962
|
+
...source ? { source } : {},
|
|
2963
|
+
...parsed.options.provider ? { prProvider: parsed.options.provider } : {},
|
|
2964
|
+
...parsed.options["pr-number"] ? { prNumber: parseRequiredPositiveInteger(parsed.options["pr-number"], "--pr-number") } : {},
|
|
2965
|
+
...parsed.options["pr-url"] ? { prUrl: parsed.options["pr-url"] } : {},
|
|
2966
|
+
...parsed.options["base-branch"] ? { baseBranch: parsed.options["base-branch"] } : {},
|
|
2967
|
+
...parsed.options["head-branch"] ? { headBranch: parsed.options["head-branch"] } : {},
|
|
2968
|
+
...parsed.options["base-sha"] ? { baseSha: parsed.options["base-sha"] } : {},
|
|
2969
|
+
...parsed.options["head-sha"] ? { headSha: parsed.options["head-sha"] } : {},
|
|
2970
|
+
...notesCommandMetadata()
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
break;
|
|
2975
|
+
case "changesets.notes.apply":
|
|
2976
|
+
data = await client.changesets.notes.apply({
|
|
2977
|
+
projectId,
|
|
2978
|
+
query: await changesetLookupFromOptions(parsed, options),
|
|
2979
|
+
notes: {
|
|
2980
|
+
prProvider: requireOption(parsed, "provider"),
|
|
2981
|
+
prNumber: parseRequiredPositiveInteger(requireOption(parsed, "pr-number"), "--pr-number"),
|
|
2982
|
+
prUrl: requireOption(parsed, "pr-url"),
|
|
2983
|
+
...parsed.options["head-sha"] ? { headSha: parsed.options["head-sha"] } : {},
|
|
2984
|
+
...notesCommandMetadata()
|
|
2985
|
+
}
|
|
2986
|
+
});
|
|
2987
|
+
break;
|
|
2465
2988
|
default:
|
|
2466
2989
|
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
2467
2990
|
}
|
|
@@ -2523,16 +3046,19 @@ async function runCli(argv, options = {}) {
|
|
|
2523
3046
|
if (parsed.command.name === "init") {
|
|
2524
3047
|
return await runInit(parsed, options);
|
|
2525
3048
|
}
|
|
3049
|
+
if (parsed.command.name === "actors.ensure-workspace-agent") {
|
|
3050
|
+
return await runActorCommand(parsed, options);
|
|
3051
|
+
}
|
|
2526
3052
|
if (parsed.command.name === "auth.login" || parsed.command.name === "auth.status" || parsed.command.name === "auth.logout") {
|
|
2527
3053
|
return await runAuthCommand(parsed, options);
|
|
2528
3054
|
}
|
|
2529
|
-
if (parsed.command.name === "events.list" || parsed.command.name === "work.overview" || parsed.command.name === "tickets.get" || parsed.command.name === "tickets.list" || parsed.command.name === "sessions.current" || parsed.command.name === "changesets.get" || parsed.command.name === "changesets.list") {
|
|
3055
|
+
if (parsed.command.name === "events.list" || parsed.command.name === "work.overview" || parsed.command.name === "tickets.get" || parsed.command.name === "tickets.list" || parsed.command.name === "sessions.current" || parsed.command.name === "changesets.get" || parsed.command.name === "changesets.current" || parsed.command.name === "changesets.list" || parsed.command.name === "changesets.notes.get") {
|
|
2530
3056
|
return await runReadCommand(parsed, options);
|
|
2531
3057
|
}
|
|
2532
3058
|
if (parsed.command.name === "projects.list") {
|
|
2533
3059
|
return await runProjectCommand(parsed, options);
|
|
2534
3060
|
}
|
|
2535
|
-
if (parsed.command.name === "intake" || parsed.command.name === "intake.dismiss" || parsed.command.name === "tickets.attach" || parsed.command.name === "tickets.create" || parsed.command.name === "tickets.update" || parsed.command.name === "tickets.claim" || parsed.command.name === "tickets.release" || parsed.command.name === "tickets.complete" || parsed.command.name === "sessions.start" || parsed.command.name === "sessions.end" || parsed.command.name === "changesets.create") {
|
|
3061
|
+
if (parsed.command.name === "intake" || parsed.command.name === "intake.dismiss" || parsed.command.name === "tickets.attach" || parsed.command.name === "tickets.create" || parsed.command.name === "tickets.update" || parsed.command.name === "tickets.claim" || parsed.command.name === "tickets.release" || parsed.command.name === "tickets.log" || parsed.command.name === "tickets.complete" || parsed.command.name === "sessions.start" || parsed.command.name === "sessions.end" || parsed.command.name === "sessions.files" || parsed.command.name === "changesets.create" || parsed.command.name === "changesets.notes.put" || parsed.command.name === "changesets.notes.apply") {
|
|
2536
3062
|
return await runIntakeCommand(parsed, options);
|
|
2537
3063
|
}
|
|
2538
3064
|
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|