@trycadence/cli 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/cadence +736 -54
  2. package/package.json +1 -1
package/dist/cadence CHANGED
@@ -1447,7 +1447,16 @@ function createCadenceClient(options = {}) {
1447
1447
  rpc,
1448
1448
  health: () => getCadenceHealth(healthOptions),
1449
1449
  auth: {
1450
- login: (input) => rpc.auth.login.mutate(input)
1450
+ login: (input) => rpc.auth.login.mutate(input),
1451
+ cli: {
1452
+ start: (input) => rpc.auth.cli.start.mutate(input),
1453
+ poll: (input) => rpc.auth.cli.poll.query(input),
1454
+ complete: (input) => rpc.auth.cli.complete.mutate(input),
1455
+ refresh: (input) => rpc.auth.cli.refresh.mutate(input)
1456
+ }
1457
+ },
1458
+ actors: {
1459
+ ensureWorkspaceAgent: (input) => rpc.actors.ensureWorkspaceAgent.mutate(input)
1451
1460
  },
1452
1461
  events: {
1453
1462
  list: (input) => rpc.events.list.query(input)
@@ -1462,7 +1471,8 @@ function createCadenceClient(options = {}) {
1462
1471
  attach: (input) => rpc.tickets.attach.mutate(input),
1463
1472
  update: (input) => rpc.tickets.update.mutate(input),
1464
1473
  changeStatus: (input) => rpc.tickets.changeStatus.mutate(input),
1465
- complete: (input) => rpc.tickets.complete.mutate(input)
1474
+ complete: (input) => rpc.tickets.complete.mutate(input),
1475
+ log: (input) => rpc.tickets.log.mutate(input)
1466
1476
  },
1467
1477
  intake: {
1468
1478
  create: (input) => rpc.intake.create.mutate(input),
@@ -1471,6 +1481,7 @@ function createCadenceClient(options = {}) {
1471
1481
  sessions: {
1472
1482
  start: (input) => rpc.sessions.start.mutate(input),
1473
1483
  end: (input) => rpc.sessions.end.mutate(input),
1484
+ files: (input) => rpc.sessions.files.mutate(input),
1474
1485
  current: (input) => rpc.sessions.current.query(input),
1475
1486
  leases: {
1476
1487
  create: (input) => rpc.sessions.leases.create.mutate(input),
@@ -1480,20 +1491,41 @@ function createCadenceClient(options = {}) {
1480
1491
  changesets: {
1481
1492
  create: (input) => rpc.changesets.create.mutate(input),
1482
1493
  get: (input) => rpc.changesets.get.query(input),
1483
- list: (input) => rpc.changesets.list.query(input)
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
+ }
1501
+ },
1502
+ projects: {
1503
+ default: () => rpc.projects.default.query(),
1504
+ list: () => rpc.projects.list.query(),
1505
+ resolve: (input) => rpc.projects.resolve.query(input)
1484
1506
  }
1485
1507
  };
1486
1508
  }
1487
1509
 
1488
1510
  // src/index.ts
1489
1511
  import { spawnSync } from "child_process";
1490
- import { mkdir, writeFile } from "fs/promises";
1491
- import { dirname, join, parse } from "path";
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";
1492
1515
  import { createInterface } from "readline/promises";
1493
1516
  var ticketPriorities = ["low", "normal", "high", "urgent"];
1494
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"];
1495
1522
  var defaultLeaseTtlSeconds = 5 * 60 * 60;
1496
1523
  var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
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;
1497
1529
 
1498
1530
  class CliError extends Error {
1499
1531
  code;
@@ -1513,14 +1545,20 @@ function readFlagValue(argv, index, flag) {
1513
1545
  return value;
1514
1546
  }
1515
1547
  var knownCommandPaths = [
1548
+ ["actors", "ensure-workspace-agent"],
1516
1549
  ["auth", "login"],
1517
1550
  ["auth", "status"],
1518
1551
  ["auth", "logout"],
1519
1552
  ["changesets", "create"],
1520
1553
  ["changesets", "list"],
1521
1554
  ["changesets", "get"],
1555
+ ["changesets", "current"],
1556
+ ["changesets", "notes", "get"],
1557
+ ["changesets", "notes", "put"],
1558
+ ["changesets", "notes", "apply"],
1522
1559
  ["sessions", "start"],
1523
1560
  ["sessions", "end"],
1561
+ ["sessions", "files"],
1524
1562
  ["sessions", "current"],
1525
1563
  ["intake", "dismiss"],
1526
1564
  ["intake"],
@@ -1528,12 +1566,14 @@ var knownCommandPaths = [
1528
1566
  ["tickets", "complete"],
1529
1567
  ["tickets", "release"],
1530
1568
  ["tickets", "claim"],
1569
+ ["tickets", "log"],
1531
1570
  ["tickets", "update"],
1532
1571
  ["tickets", "create"],
1533
1572
  ["tickets", "list"],
1534
1573
  ["tickets", "get"],
1535
1574
  ["events", "list"],
1536
1575
  ["work", "overview"],
1576
+ ["projects", "list"],
1537
1577
  ["init"],
1538
1578
  ["status"],
1539
1579
  ["help"]
@@ -1591,7 +1631,10 @@ function parseCliArgs(argv) {
1591
1631
  continue;
1592
1632
  }
1593
1633
  if (arg.startsWith("--")) {
1594
- options[arg.slice(2)] = readFlagValue(argv, index, arg);
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;
1595
1638
  index += 1;
1596
1639
  continue;
1597
1640
  }
@@ -1620,12 +1663,40 @@ function safeJsonParse(source, filePath) {
1620
1663
  }
1621
1664
  const record = parsed;
1622
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;
1623
1668
  const projectId = typeof record.projectId === "string" ? record.projectId : typeof record.project === "string" ? record.project : undefined;
1669
+ const profiles = parseProfiles(record.profiles, filePath);
1624
1670
  return {
1625
1671
  ...server ? { server } : {},
1626
- ...projectId ? { projectId } : {}
1672
+ ...webBaseUrl ? { webBaseUrl } : {},
1673
+ ...projectId ? { projectId } : {},
1674
+ ...profile ? { profile } : {},
1675
+ ...profiles ? { profiles } : {}
1627
1676
  };
1628
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
+ }
1629
1700
  async function readOptionalConfig(filePath) {
1630
1701
  const file = Bun.file(filePath);
1631
1702
  if (!await file.exists()) {
@@ -1645,12 +1716,104 @@ async function mergeConfigFile(filePath, updates) {
1645
1716
  ...updates
1646
1717
  });
1647
1718
  }
1648
- async function findRepoConfigPath(cwd) {
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) {
1649
1810
  let current = cwd;
1650
1811
  while (true) {
1651
- const candidate = join(current, ".cadence", "config.json");
1652
- if (await Bun.file(candidate).exists()) {
1653
- return candidate;
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;
1654
1817
  }
1655
1818
  const parent = dirname(current);
1656
1819
  if (parent === current) {
@@ -1666,18 +1829,34 @@ async function resolveCliConfig(flags, options = {}) {
1666
1829
  const env = options.env ?? process.env;
1667
1830
  const cwd = options.cwd ?? process.cwd();
1668
1831
  const globalConfigPath = join(getConfigHome(env), "config.json");
1669
- const repoConfigPath = await findRepoConfigPath(cwd);
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;
1670
1835
  const repoConfigPromise = repoConfigPath ? readOptionalConfig(repoConfigPath) : Promise.resolve({});
1671
- const [globalConfig, repoConfig] = await Promise.all([
1836
+ const localConfigPromise = localConfigPath ? readOptionalConfig(localConfigPath) : Promise.resolve({});
1837
+ const [globalConfig, repoConfig, localConfig] = await Promise.all([
1672
1838
  readOptionalConfig(globalConfigPath),
1673
- repoConfigPromise
1839
+ repoConfigPromise,
1840
+ localConfigPromise
1674
1841
  ]);
1675
- const server = flags.server ?? repoConfig.server ?? globalConfig.server ?? defaultCliApiBaseUrl;
1676
- const projectId = flags.project ?? repoConfig.projectId ?? globalConfig.projectId ?? null;
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;
1677
1853
  return {
1678
1854
  server,
1855
+ webBaseUrl,
1856
+ profile,
1679
1857
  projectId,
1680
1858
  repoConfigPath,
1859
+ localConfigPath,
1681
1860
  globalConfigPath
1682
1861
  };
1683
1862
  }
@@ -1724,8 +1903,55 @@ function getCredentialStore(options) {
1724
1903
  async function readPromptText(options, message) {
1725
1904
  return options.readText ? options.readText(message) : promptText(message);
1726
1905
  }
1727
- async function readPromptSecret(options, message) {
1728
- return options.readSecret ? options.readSecret(message) : promptSecret(message);
1906
+ function isInteractive(options) {
1907
+ return options.isInteractive ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
1908
+ }
1909
+ function getCliWebBaseUrl(config, parsed, options) {
1910
+ return parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL ?? config.webBaseUrl;
1911
+ }
1912
+ function deriveWebBaseUrl(server) {
1913
+ try {
1914
+ const url = new URL(server);
1915
+ if ((url.hostname === "localhost" || url.hostname === "127.0.0.1") && url.port === "3000") {
1916
+ url.port = "3001";
1917
+ return url.toString().replace(/\/$/, "");
1918
+ }
1919
+ } catch {
1920
+ return defaultCliWebBaseUrl;
1921
+ }
1922
+ return defaultCliWebBaseUrl;
1923
+ }
1924
+ async function openBrowser(url, options) {
1925
+ if (options.openBrowser) {
1926
+ await options.openBrowser(url);
1927
+ return;
1928
+ }
1929
+ if (process.platform === "darwin") {
1930
+ const result = spawnSync("open", [url]);
1931
+ if (result.status !== 0) {
1932
+ throw new CliError("BROWSER_OPEN_FAILED", "Could not open the browser for Cadence login.", {
1933
+ loginUrl: url
1934
+ });
1935
+ }
1936
+ }
1937
+ }
1938
+ async function sleep2(milliseconds, options) {
1939
+ if (options.sleep) {
1940
+ await options.sleep(milliseconds);
1941
+ return;
1942
+ }
1943
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
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
+ `);
1729
1955
  }
1730
1956
  function encodeCredential(credential) {
1731
1957
  return JSON.stringify(credential);
@@ -1754,6 +1980,68 @@ async function readStoredCredential(store, server) {
1754
1980
  const rawCredential = await store.getCredential(server);
1755
1981
  return rawCredential ? decodeCredential(rawCredential) : null;
1756
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
+ }
1757
2045
  async function requireCredentialStore(store) {
1758
2046
  if (!await store.isAvailable()) {
1759
2047
  throw new CliError("CREDENTIAL_STORE_UNAVAILABLE", "OS secure credential storage is unavailable.");
@@ -1773,19 +2061,6 @@ async function promptText(message) {
1773
2061
  readline.close();
1774
2062
  }
1775
2063
  }
1776
- async function promptSecret(message) {
1777
- if (!process.stdin.isTTY || !process.stderr.isTTY) {
1778
- throw new CliError("AUTH_INTERACTIVE_REQUIRED", "Interactive auth requires a TTY.");
1779
- }
1780
- spawnSync("stty", ["-echo"], { stdio: ["inherit", "ignore", "ignore"] });
1781
- try {
1782
- return await promptText(message);
1783
- } finally {
1784
- spawnSync("stty", ["echo"], { stdio: ["inherit", "ignore", "ignore"] });
1785
- process.stderr.write(`
1786
- `);
1787
- }
1788
- }
1789
2064
  function successEnvelope(data, meta) {
1790
2065
  return {
1791
2066
  success: true,
@@ -1816,11 +2091,13 @@ function helpText() {
1816
2091
  "Cadence CLI",
1817
2092
  "",
1818
2093
  "Usage:",
1819
- " cadence init --project <project-id> [--json]",
2094
+ " cadence init [--project <project-id|org/project>] [--json]",
1820
2095
  " cadence auth login [--json]",
1821
2096
  " cadence auth status [--json]",
1822
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]",
1823
2099
  " cadence status [--project <project-id>] [--json]",
2100
+ " cadence projects list [--json]",
1824
2101
  " cadence work overview [--project <project-id>] [--json]",
1825
2102
  " cadence tickets get <ticket-id> [--project <project-id>] [--json]",
1826
2103
  " cadence tickets list [--project <project-id>] [--status <status>] [--json]",
@@ -1828,36 +2105,103 @@ function helpText() {
1828
2105
  " cadence tickets attach <ticket-id> --from-intake <intake-id> --if-version <version> [--project <project-id>] [--json]",
1829
2106
  " cadence tickets create --title <text> [--from-intake <intake-id>] [--project <project-id>] [--json]",
1830
2107
  " cadence tickets update <ticket-id> --if-version <version> [--title <text>] [--description <text>] [--priority <priority>] [--status <status>] [--project <project-id>] [--json]",
1831
- " 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]",
1832
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]",
1833
2111
  " cadence tickets complete <ticket-id> --if-version <version> [--summary <summary>] [--project <project-id>] [--json]",
1834
- " 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]",
1835
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]",
1836
2115
  " cadence changesets create --ticket <ticket-id> --branch <branch> [--base-branch <branch>] [--project <project-id>] [--json]",
1837
2116
  " cadence intake dismiss <intake-id> --reason <reason> [--project <project-id>] [--json]",
1838
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]",
1839
2119
  " cadence changesets get <changeset-id> [--project <project-id>] [--json]",
1840
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]",
1841
2124
  " cadence events list [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--session <session-id>] [--json]",
1842
2125
  "",
1843
2126
  "Global flags:",
1844
- " --project <id> Cadence project ID",
2127
+ " --project <id> Cadence project ID or org/project slug",
2128
+ " --server <url> Cadence API server override",
1845
2129
  " --json Print stable JSON envelope",
1846
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
+ "",
1847
2137
  "Auth options:",
1848
- " --email <email> Email for auth login"
2138
+ " --web-base-url <url> Browser login base URL"
1849
2139
  ].join(`
1850
2140
  `);
1851
2141
  }
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
+ }
1852
2192
  async function createClient(config, options) {
1853
2193
  if (options.client) {
1854
2194
  return options.client;
1855
2195
  }
1856
2196
  const store = getCredentialStore(options);
1857
2197
  const credential = await readStoredCredential(store, config.server);
2198
+ const authClient = createCadenceClient({
2199
+ baseUrl: config.server,
2200
+ ...options.fetch ? { fetch: options.fetch } : {}
2201
+ });
1858
2202
  return createCadenceClient({
1859
2203
  baseUrl: config.server,
1860
- ...credential ? { getAuthToken: async () => (await readStoredCredential(store, config.server))?.accessToken ?? "" } : {},
2204
+ ...credential ? { getAuthToken: async () => readFreshAccessToken(config, store, authClient) } : {},
1861
2205
  ...options.fetch ? { fetch: options.fetch } : {}
1862
2206
  });
1863
2207
  }
@@ -1865,6 +2209,7 @@ function commandMeta(parsed, config) {
1865
2209
  return {
1866
2210
  command: parsed.command.name,
1867
2211
  server: config.server,
2212
+ ...config.profile ? { profile: config.profile } : {},
1868
2213
  projectId: config.projectId
1869
2214
  };
1870
2215
  }
@@ -1926,6 +2271,55 @@ function parseTicketStatus(value) {
1926
2271
  }
1927
2272
  return value;
1928
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
+ }
1929
2323
  function leaseExpiresAt(ttlSeconds) {
1930
2324
  return new Date(Date.now() + ttlSeconds * 1000).toISOString();
1931
2325
  }
@@ -1938,20 +2332,73 @@ function commandMetadata() {
1938
2332
  source: "cli"
1939
2333
  };
1940
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
+ }
1941
2384
  async function runStatus(parsed, options) {
1942
2385
  const config = await resolveCliConfig(parsed.flags, options);
1943
2386
  const store = getCredentialStore(options);
1944
2387
  const credential = await readStoredCredential(store, config.server);
1945
2388
  const client = await createClient(config, options);
1946
2389
  const health = await client.health();
2390
+ const webBaseUrl = getCliWebBaseUrl(config, parsed, options);
1947
2391
  const data = {
1948
2392
  server: config.server,
2393
+ webBaseUrl,
2394
+ profile: config.profile,
1949
2395
  projectId: config.projectId,
1950
2396
  credentialConfigured: Boolean(credential),
1951
2397
  authTokenConfigured: Boolean(credential),
1952
2398
  health,
1953
2399
  config: {
1954
2400
  repoConfigPath: config.repoConfigPath,
2401
+ localConfigPath: config.localConfigPath,
1955
2402
  globalConfigPath: config.globalConfigPath
1956
2403
  }
1957
2404
  };
@@ -1973,25 +2420,28 @@ async function runStatus(parsed, options) {
1973
2420
  async function runInit(parsed, options) {
1974
2421
  const cwd = options.cwd ?? process.cwd();
1975
2422
  const repoConfigPath = join(cwd, ".cadence", "config.json");
1976
- const projectId = parsed.flags.project;
1977
- if (!projectId) {
1978
- throw new CliError("CLI_USAGE", "init requires --project.");
1979
- }
2423
+ const config = await resolveCliConfig(parsed.flags, {
2424
+ ...options,
2425
+ cwd
2426
+ });
2427
+ const client = await createClient(config, options);
2428
+ const project = await selectProject(parsed, client, options);
2429
+ const projectId = project.id;
1980
2430
  const updates = {
1981
2431
  projectId,
1982
2432
  ...parsed.flags.server ? { server: parsed.flags.server } : {}
1983
2433
  };
1984
2434
  await mergeConfigFile(repoConfigPath, updates);
1985
- const config = await resolveCliConfig(parsed.flags, {
1986
- ...options,
1987
- cwd
1988
- });
1989
2435
  const data = {
1990
2436
  repoConfigPath,
1991
2437
  projectId,
2438
+ project,
1992
2439
  ...parsed.flags.server ? { server: parsed.flags.server } : {}
1993
2440
  };
1994
- const meta = commandMeta(parsed, config);
2441
+ const meta = commandMeta(parsed, {
2442
+ ...config,
2443
+ projectId
2444
+ });
1995
2445
  if (parsed.flags.json) {
1996
2446
  return {
1997
2447
  stdout: formatJson(successEnvelope(data, meta)),
@@ -2006,6 +2456,69 @@ async function runInit(parsed, options) {
2006
2456
  exitCode: 0
2007
2457
  };
2008
2458
  }
2459
+ function isUuid(value) {
2460
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
2461
+ }
2462
+ async function resolveProjectReference(projectReference, client) {
2463
+ if (isUuid(projectReference)) {
2464
+ return {
2465
+ id: projectReference,
2466
+ orgSlug: null,
2467
+ projectSlug: null,
2468
+ name: null
2469
+ };
2470
+ }
2471
+ const [orgSlug, projectSlug, extra] = projectReference.split("/");
2472
+ if (!orgSlug || !projectSlug || extra) {
2473
+ throw new CliError("CLI_USAGE", "--project must be a project ID or org/project slug.");
2474
+ }
2475
+ return await client.projects.resolve({
2476
+ orgSlug,
2477
+ projectSlug
2478
+ });
2479
+ }
2480
+ function formatProjectChoice(project, index) {
2481
+ const slug = project.orgSlug && project.projectSlug ? `${project.orgSlug}/${project.projectSlug}` : project.id;
2482
+ const name = project.name ? ` ${project.name}` : "";
2483
+ return `${index + 1}. ${slug}${name}`;
2484
+ }
2485
+ async function selectProject(parsed, client, options) {
2486
+ if (parsed.flags.project) {
2487
+ return await resolveProjectReference(parsed.flags.project, client);
2488
+ }
2489
+ const projects = await client.projects.list();
2490
+ if (projects.length === 1) {
2491
+ return projects[0];
2492
+ }
2493
+ if (parsed.flags.json || !isInteractive(options)) {
2494
+ throw new CliError("PROJECT_REQUIRED", "Choose a Cadence project with --project.", {
2495
+ projects
2496
+ });
2497
+ }
2498
+ if (projects.length === 0) {
2499
+ throw new CliError("PROJECT_REQUIRED", "No accessible Cadence projects were found.");
2500
+ }
2501
+ const message = [
2502
+ "Choose a Cadence project:",
2503
+ ...projects.map(formatProjectChoice),
2504
+ "",
2505
+ "Project number, ID, or org/project: "
2506
+ ].join(`
2507
+ `);
2508
+ const answer = (await readPromptText(options, message)).trim();
2509
+ const selectedIndex = Number(answer);
2510
+ const selected = Number.isInteger(selectedIndex) && selectedIndex > 0 ? projects[selectedIndex - 1] : undefined;
2511
+ if (selected) {
2512
+ return selected;
2513
+ }
2514
+ const matching = projects.find((project) => project.id === answer || `${project.orgSlug}/${project.projectSlug}` === answer);
2515
+ if (!matching) {
2516
+ throw new CliError("PROJECT_REQUIRED", "Selected project was not in the accessible project list.", {
2517
+ projects
2518
+ });
2519
+ }
2520
+ return matching;
2521
+ }
2009
2522
  async function runAuthCommand(parsed, options) {
2010
2523
  const config = await resolveCliConfig(parsed.flags, options);
2011
2524
  const store = getCredentialStore(options);
@@ -2014,18 +2527,48 @@ async function runAuthCommand(parsed, options) {
2014
2527
  switch (parsed.command.name) {
2015
2528
  case "auth.login":
2016
2529
  {
2017
- await requireCredentialStore(store);
2018
2530
  const client = await createClient(config, options);
2019
- const credential = await client.auth.login({
2020
- email: parsed.options.email ?? await readPromptText(options, "Email: "),
2021
- password: await readPromptSecret(options, "Password: ")
2531
+ const challenge = await client.auth.cli.start({
2532
+ loginBaseUrl: getCliWebBaseUrl(config, parsed, options)
2022
2533
  });
2023
- await store.setCredential(config.server, encodeCredential(credential));
2534
+ if (parsed.flags.json || !isInteractive(options)) {
2535
+ throw new CliError("HUMAN_AUTH_REQUIRED", "Cadence login must be completed by a human in the browser.", {
2536
+ loginUrl: challenge.loginUrl,
2537
+ deviceCode: challenge.deviceCode,
2538
+ expiresAt: challenge.expiresAt
2539
+ });
2540
+ }
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);
2545
+ await openBrowser(challenge.loginUrl, options);
2546
+ await writeInteractiveStatus("Waiting for browser approval...", options);
2547
+ let poll = await client.auth.cli.poll({
2548
+ deviceId: challenge.deviceId,
2549
+ deviceCode: challenge.deviceCode
2550
+ });
2551
+ while (poll.status === "pending") {
2552
+ await sleep2(poll.pollIntervalSeconds * 1000, options);
2553
+ poll = await client.auth.cli.poll({
2554
+ deviceId: challenge.deviceId,
2555
+ deviceCode: challenge.deviceCode
2556
+ });
2557
+ }
2558
+ if (poll.status === "expired") {
2559
+ throw new CliError("AUTH_LOGIN_EXPIRED", "Cadence browser login expired.");
2560
+ }
2561
+ if (poll.status === "denied") {
2562
+ throw new CliError("AUTH_LOGIN_DENIED", "Cadence browser login was denied.");
2563
+ }
2564
+ await store.setCredential(config.server, encodeCredential(poll.credential));
2024
2565
  await mergeConfigFile(config.globalConfigPath, { server: config.server });
2566
+ await writeInteractiveStatus("Cadence CLI login approved. Credential stored.", options);
2025
2567
  data = {
2026
2568
  server: config.server,
2027
2569
  credentialStored: true,
2028
- globalConfigPath: config.globalConfigPath
2570
+ globalConfigPath: config.globalConfigPath,
2571
+ loginUrl: challenge.loginUrl
2029
2572
  };
2030
2573
  }
2031
2574
  break;
@@ -2125,6 +2668,12 @@ async function runReadCommand(parsed, options) {
2125
2668
  changesetId: requireArg(parsed, 0, "<changeset-id>")
2126
2669
  });
2127
2670
  break;
2671
+ case "changesets.current":
2672
+ data = await client.changesets.context({
2673
+ projectId,
2674
+ query: await changesetLookupFromOptions(parsed, options)
2675
+ });
2676
+ break;
2128
2677
  case "changesets.list":
2129
2678
  data = await client.changesets.list({
2130
2679
  projectId,
@@ -2136,6 +2685,64 @@ async function runReadCommand(parsed, options) {
2136
2685
  })
2137
2686
  });
2138
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;
2694
+ default:
2695
+ throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
2696
+ }
2697
+ if (parsed.flags.json) {
2698
+ return {
2699
+ stdout: formatJson(successEnvelope(data, meta)),
2700
+ stderr: "",
2701
+ exitCode: 0
2702
+ };
2703
+ }
2704
+ return {
2705
+ stdout: `${JSON.stringify(data, null, 2)}
2706
+ `,
2707
+ stderr: "",
2708
+ exitCode: 0
2709
+ };
2710
+ }
2711
+ async function runProjectCommand(parsed, options) {
2712
+ const config = await resolveCliConfig(parsed.flags, options);
2713
+ const client = await createClient(config, options);
2714
+ const meta = commandMeta(parsed, config);
2715
+ let data;
2716
+ switch (parsed.command.name) {
2717
+ case "projects.list":
2718
+ data = await client.projects.list();
2719
+ break;
2720
+ default:
2721
+ throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
2722
+ }
2723
+ if (parsed.flags.json) {
2724
+ return {
2725
+ stdout: formatJson(successEnvelope(data, meta)),
2726
+ stderr: "",
2727
+ exitCode: 0
2728
+ };
2729
+ }
2730
+ return {
2731
+ stdout: `${JSON.stringify(data, null, 2)}
2732
+ `,
2733
+ stderr: "",
2734
+ exitCode: 0
2735
+ };
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;
2139
2746
  default:
2140
2747
  throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
2141
2748
  }
@@ -2249,6 +2856,7 @@ async function runIntakeCommand(parsed, options) {
2249
2856
  ticketId: requireArg(parsed, 0, "<ticket-id>"),
2250
2857
  sessionId: requireOption(parsed, "session"),
2251
2858
  ...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
2859
+ ...parsed.options.actor ? { actorId: parsed.options.actor } : {},
2252
2860
  expiresAt: leaseExpiresAt(parsePositiveInteger(parsed.options["ttl-seconds"], "--ttl-seconds") ?? defaultLeaseTtlSeconds),
2253
2861
  ...commandMetadata()
2254
2862
  }
@@ -2265,6 +2873,21 @@ async function runIntakeCommand(parsed, options) {
2265
2873
  }
2266
2874
  });
2267
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;
2268
2891
  case "tickets.complete":
2269
2892
  data = await client.tickets.complete({
2270
2893
  projectId,
@@ -2282,6 +2905,7 @@ async function runIntakeCommand(parsed, options) {
2282
2905
  session: {
2283
2906
  ticketId: requireOption(parsed, "ticket"),
2284
2907
  ...parsed.options.changeset ? { changesetId: parsed.options.changeset } : {},
2908
+ ...parsed.options.actor ? { actorId: parsed.options.actor } : {},
2285
2909
  ...parsed.options["local-session-ref"] ? { localSessionRef: parsed.options["local-session-ref"] } : {},
2286
2910
  ...commandMetadata()
2287
2911
  }
@@ -2297,6 +2921,23 @@ async function runIntakeCommand(parsed, options) {
2297
2921
  }
2298
2922
  });
2299
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;
2300
2941
  case "changesets.create":
2301
2942
  data = await client.changesets.create({
2302
2943
  projectId,
@@ -2309,6 +2950,41 @@ async function runIntakeCommand(parsed, options) {
2309
2950
  }
2310
2951
  });
2311
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;
2312
2988
  default:
2313
2989
  throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
2314
2990
  }
@@ -2370,13 +3046,19 @@ async function runCli(argv, options = {}) {
2370
3046
  if (parsed.command.name === "init") {
2371
3047
  return await runInit(parsed, options);
2372
3048
  }
3049
+ if (parsed.command.name === "actors.ensure-workspace-agent") {
3050
+ return await runActorCommand(parsed, options);
3051
+ }
2373
3052
  if (parsed.command.name === "auth.login" || parsed.command.name === "auth.status" || parsed.command.name === "auth.logout") {
2374
3053
  return await runAuthCommand(parsed, options);
2375
3054
  }
2376
- 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") {
2377
3056
  return await runReadCommand(parsed, options);
2378
3057
  }
2379
- 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") {
3058
+ if (parsed.command.name === "projects.list") {
3059
+ return await runProjectCommand(parsed, options);
3060
+ }
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") {
2380
3062
  return await runIntakeCommand(parsed, options);
2381
3063
  }
2382
3064
  throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trycadence/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {