@trycadence/cli 0.1.4 → 0.1.7

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 +174 -30
  2. package/package.json +1 -1
package/dist/cadence CHANGED
@@ -1466,6 +1466,9 @@ function createCadenceClient(options = {}) {
1466
1466
  },
1467
1467
  tickets: {
1468
1468
  get: (input) => rpc.tickets.get.query(input),
1469
+ detail: (input) => rpc.tickets.detail.query(input),
1470
+ activity: (input) => rpc.tickets.activity.query(input),
1471
+ workLog: (input) => rpc.tickets.workLog.query(input),
1469
1472
  list: (input) => rpc.tickets.list.query(input),
1470
1473
  create: (input) => rpc.tickets.create.mutate(input),
1471
1474
  attach: (input) => rpc.tickets.attach.mutate(input),
@@ -1552,6 +1555,7 @@ var knownCommandPaths = [
1552
1555
  ["changesets", "create"],
1553
1556
  ["changesets", "list"],
1554
1557
  ["changesets", "get"],
1558
+ ["changesets", "context"],
1555
1559
  ["changesets", "current"],
1556
1560
  ["changesets", "notes", "get"],
1557
1561
  ["changesets", "notes", "put"],
@@ -1574,6 +1578,7 @@ var knownCommandPaths = [
1574
1578
  ["events", "list"],
1575
1579
  ["work", "overview"],
1576
1580
  ["projects", "list"],
1581
+ ["projects", "use"],
1577
1582
  ["init"],
1578
1583
  ["status"],
1579
1584
  ["help"]
@@ -1822,6 +1827,39 @@ async function findRepoCadenceDirectory(cwd) {
1822
1827
  current = parent;
1823
1828
  }
1824
1829
  }
1830
+ async function findRepoConfigDirectory(cwd) {
1831
+ let current = cwd;
1832
+ while (true) {
1833
+ const repoConfig = join(current, ".cadence", "config.json");
1834
+ if (await Bun.file(repoConfig).exists()) {
1835
+ return join(current, ".cadence");
1836
+ }
1837
+ const parent = dirname(current);
1838
+ if (parent === current) {
1839
+ return null;
1840
+ }
1841
+ current = parent;
1842
+ }
1843
+ }
1844
+ function resolveGitRootFromCommand(cwd) {
1845
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
1846
+ cwd,
1847
+ encoding: "utf8"
1848
+ });
1849
+ if (result.status !== 0) {
1850
+ return null;
1851
+ }
1852
+ return result.stdout.trim() || null;
1853
+ }
1854
+ async function repoConfigPathForWrite(options) {
1855
+ const cwd = options.cwd ?? process.cwd();
1856
+ const existingConfigDirectory = await findRepoConfigDirectory(cwd);
1857
+ if (existingConfigDirectory) {
1858
+ return join(existingConfigDirectory, "config.json");
1859
+ }
1860
+ const gitRoot = options.resolveGitRoot ? await options.resolveGitRoot() : resolveGitRootFromCommand(cwd);
1861
+ return join(gitRoot ?? cwd, ".cadence", "config.json");
1862
+ }
1825
1863
  function getConfigHome(env) {
1826
1864
  return env.CADENCE_CONFIG_HOME ?? (env.HOME ? join(env.HOME, ".config", "cadence") : join(parse(process.cwd()).root, ".config", "cadence"));
1827
1865
  }
@@ -1839,16 +1877,13 @@ async function resolveCliConfig(flags, options = {}) {
1839
1877
  repoConfigPromise,
1840
1878
  localConfigPromise
1841
1879
  ]);
1842
- const profiles = {
1843
- ...globalConfig.profiles ?? {},
1844
- ...repoConfig.profiles ?? {},
1845
- ...localConfig.profiles ?? {}
1846
- };
1847
1880
  const envProfile = env.CADENCE_PROFILE;
1848
1881
  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);
1882
+ const globalProfileConfig = profile ? globalConfig.profiles?.[profile] : undefined;
1883
+ const repoProfileConfig = profile ? repoConfig.profiles?.[profile] : undefined;
1884
+ const localProfileConfig = profile ? localConfig.profiles?.[profile] : undefined;
1885
+ const server = flags.server ?? env.CADENCE_SERVER ?? localConfig.server ?? localProfileConfig?.server ?? repoConfig.server ?? repoProfileConfig?.server ?? globalProfileConfig?.server ?? globalConfig.server ?? defaultCliApiBaseUrl;
1886
+ const webBaseUrl = env.CADENCE_WEB_BASE_URL ?? localConfig.webBaseUrl ?? localProfileConfig?.webBaseUrl ?? repoConfig.webBaseUrl ?? repoProfileConfig?.webBaseUrl ?? globalProfileConfig?.webBaseUrl ?? globalConfig.webBaseUrl ?? deriveWebBaseUrl(server);
1852
1887
  const projectId = flags.project ?? localConfig.projectId ?? repoConfig.projectId ?? globalConfig.projectId ?? null;
1853
1888
  return {
1854
1889
  server,
@@ -1977,16 +2012,46 @@ async function readStoredCredential(store, server) {
1977
2012
  if (!await store.isAvailable()) {
1978
2013
  return null;
1979
2014
  }
1980
- const rawCredential = await store.getCredential(server);
1981
- return rawCredential ? decodeCredential(rawCredential) : null;
2015
+ for (const key of credentialStoreKeysForServer(server)) {
2016
+ const rawCredential = await store.getCredential(key);
2017
+ if (rawCredential) {
2018
+ return decodeCredential(rawCredential);
2019
+ }
2020
+ }
2021
+ return null;
1982
2022
  }
1983
- function normalizeServerForCredentialLock(server) {
2023
+ async function writeStoredCredential(store, server, credential) {
2024
+ await store.setCredential(normalizeServerForCredentialStore(server), encodeCredential(credential));
2025
+ }
2026
+ async function deleteStoredCredential(store, server) {
2027
+ for (const key of credentialStoreKeysForServer(server)) {
2028
+ await store.deleteCredential(key);
2029
+ }
2030
+ }
2031
+ function normalizeServerForCredentialStore(server) {
1984
2032
  try {
1985
2033
  return new URL(server).toString().replace(/\/$/, "");
1986
2034
  } catch {
1987
2035
  return server.trim();
1988
2036
  }
1989
2037
  }
2038
+ function credentialStoreKeysForServer(server) {
2039
+ const keys = [normalizeServerForCredentialStore(server)];
2040
+ const trimmed = server.trim();
2041
+ if (trimmed && !keys.includes(trimmed)) {
2042
+ keys.push(trimmed);
2043
+ }
2044
+ try {
2045
+ const serialized = new URL(trimmed).toString();
2046
+ if (!keys.includes(serialized)) {
2047
+ keys.push(serialized);
2048
+ }
2049
+ } catch {}
2050
+ return keys;
2051
+ }
2052
+ function normalizeServerForCredentialLock(server) {
2053
+ return normalizeServerForCredentialStore(server);
2054
+ }
1990
2055
  function credentialRefreshLockPath(config) {
1991
2056
  const normalizedServer = normalizeServerForCredentialLock(config.server);
1992
2057
  const serverHash = createHash("sha256").update(normalizedServer).digest("hex").slice(0, 24);
@@ -2098,6 +2163,7 @@ function helpText() {
2098
2163
  " cadence actors ensure-workspace-agent --agent-kind <kind> [--workspace-name <name>] [--workspace-ref <ref>] [--display-name <name>] [--project <project-id>] [--json]",
2099
2164
  " cadence status [--project <project-id>] [--json]",
2100
2165
  " cadence projects list [--json]",
2166
+ " cadence projects use <project-id|org/project> [--server <url>] [--json]",
2101
2167
  " cadence work overview [--project <project-id>] [--json]",
2102
2168
  " cadence tickets get <ticket-id> [--project <project-id>] [--json]",
2103
2169
  " cadence tickets list [--project <project-id>] [--status <status>] [--json]",
@@ -2112,10 +2178,11 @@ function helpText() {
2112
2178
  " cadence sessions start --ticket <ticket-id> [--changeset <changeset-id>] [--actor <actor-id>] [--project <project-id>] [--json]",
2113
2179
  " cadence sessions end <session-id> --summary <summary> [--project <project-id>] [--json]",
2114
2180
  " cadence sessions files <session-id> --file <path> [--file <path>] [--kind <added|modified|deleted|renamed|unknown>] [--changeset <changeset-id>] [--project <project-id>] [--json]",
2115
- " cadence changesets create --ticket <ticket-id> --branch <branch> [--base-branch <branch>] [--project <project-id>] [--json]",
2181
+ " cadence changesets create --ticket <ticket-id> --branch <branch> --base-branch <branch> [--project <project-id>] [--json]",
2116
2182
  " cadence intake dismiss <intake-id> --reason <reason> [--project <project-id>] [--json]",
2117
2183
  " cadence sessions current [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--json]",
2118
2184
  " cadence changesets current [--branch current|<branch>] [--project <project-id>] [--json]",
2185
+ " cadence changesets context <changeset-id> [--project <project-id>] [--json]",
2119
2186
  " cadence changesets get <changeset-id> [--project <project-id>] [--json]",
2120
2187
  " cadence changesets list [--project <project-id>] [--ticket <ticket-id>] [--status <status>] [--json]",
2121
2188
  " cadence changesets notes get [--changeset <id>|--branch current|<branch>] [--project <project-id>] [--json]",
@@ -2124,7 +2191,7 @@ function helpText() {
2124
2191
  " cadence events list [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--session <session-id>] [--json]",
2125
2192
  "",
2126
2193
  "Global flags:",
2127
- " --project <id> Cadence project ID or org/project slug",
2194
+ " --project <id|org/project> Cadence project ID or org/project slug",
2128
2195
  " --server <url> Cadence API server override",
2129
2196
  " --json Print stable JSON envelope",
2130
2197
  "",
@@ -2173,7 +2240,7 @@ async function readFreshAccessToken(config, store, authClient) {
2173
2240
  const refreshed = await authClient.auth.cli.refresh({
2174
2241
  refreshToken: lockedCredential.refreshToken
2175
2242
  });
2176
- await store.setCredential(config.server, encodeCredential(refreshed));
2243
+ await writeStoredCredential(store, config.server, refreshed);
2177
2244
  return refreshed.accessToken;
2178
2245
  } catch (error) {
2179
2246
  const recoveredCredential = await readStoredCredential(store, config.server);
@@ -2410,8 +2477,21 @@ async function runStatus(parsed, options) {
2410
2477
  exitCode: 0
2411
2478
  };
2412
2479
  }
2480
+ const projectLabel = config.projectId ?? "not configured";
2481
+ const profileLabel = config.profile ?? "default";
2482
+ const credentialLabel = credential ? "configured" : "not configured";
2413
2483
  return {
2414
- stdout: `Cadence API ${health.ok ? "ok" : "unavailable"} at ${config.server}
2484
+ stdout: [
2485
+ `Cadence API: ${health.ok ? "ok" : "unavailable"} at ${config.server}`,
2486
+ `Cadence web: ${webBaseUrl}`,
2487
+ `Profile: ${profileLabel}`,
2488
+ `Project: ${projectLabel}`,
2489
+ `Credential: ${credentialLabel}`,
2490
+ `Repo config: ${config.repoConfigPath ?? "not found"}`,
2491
+ `Local config: ${config.localConfigPath ?? "not found"}`,
2492
+ `Global config: ${config.globalConfigPath}`
2493
+ ].join(`
2494
+ `) + `
2415
2495
  `,
2416
2496
  stderr: "",
2417
2497
  exitCode: 0
@@ -2419,7 +2499,7 @@ async function runStatus(parsed, options) {
2419
2499
  }
2420
2500
  async function runInit(parsed, options) {
2421
2501
  const cwd = options.cwd ?? process.cwd();
2422
- const repoConfigPath = join(cwd, ".cadence", "config.json");
2502
+ const repoConfigPath = await repoConfigPathForWrite(options);
2423
2503
  const config = await resolveCliConfig(parsed.flags, {
2424
2504
  ...options,
2425
2505
  cwd
@@ -2477,6 +2557,15 @@ async function resolveProjectReference(projectReference, client) {
2477
2557
  projectSlug
2478
2558
  });
2479
2559
  }
2560
+ async function resolveRequiredProject(config, client) {
2561
+ return await resolveProjectReference(requireProjectId(config), client);
2562
+ }
2563
+ function configWithProject(config, project) {
2564
+ return {
2565
+ ...config,
2566
+ projectId: project.id
2567
+ };
2568
+ }
2480
2569
  function formatProjectChoice(project, index) {
2481
2570
  const slug = project.orgSlug && project.projectSlug ? `${project.orgSlug}/${project.projectSlug}` : project.id;
2482
2571
  const name = project.name ? ` ${project.name}` : "";
@@ -2561,14 +2650,20 @@ async function runAuthCommand(parsed, options) {
2561
2650
  if (poll.status === "denied") {
2562
2651
  throw new CliError("AUTH_LOGIN_DENIED", "Cadence browser login was denied.");
2563
2652
  }
2564
- await store.setCredential(config.server, encodeCredential(poll.credential));
2653
+ await writeStoredCredential(store, config.server, poll.credential);
2565
2654
  await mergeConfigFile(config.globalConfigPath, { server: config.server });
2566
2655
  await writeInteractiveStatus("Cadence CLI login approved. Credential stored.", options);
2656
+ const projectConfigured = Boolean(config.projectId);
2657
+ if (!projectConfigured) {
2658
+ await writeInteractiveStatus("No Cadence project is configured for this repo. Run cadence init to choose one.", options);
2659
+ }
2567
2660
  data = {
2568
2661
  server: config.server,
2569
2662
  credentialStored: true,
2663
+ projectConfigured,
2570
2664
  globalConfigPath: config.globalConfigPath,
2571
- loginUrl: challenge.loginUrl
2665
+ loginUrl: challenge.loginUrl,
2666
+ ...!projectConfigured ? { nextCommand: "cadence init" } : {}
2572
2667
  };
2573
2668
  }
2574
2669
  break;
@@ -2590,7 +2685,7 @@ async function runAuthCommand(parsed, options) {
2590
2685
  break;
2591
2686
  case "auth.logout":
2592
2687
  await requireCredentialStore(store);
2593
- await store.deleteCredential(config.server);
2688
+ await deleteStoredCredential(store, config.server);
2594
2689
  data = {
2595
2690
  server: config.server,
2596
2691
  credentialRemoved: true
@@ -2615,9 +2710,11 @@ async function runAuthCommand(parsed, options) {
2615
2710
  }
2616
2711
  async function runReadCommand(parsed, options) {
2617
2712
  const config = await resolveCliConfig(parsed.flags, options);
2618
- const projectId = requireProjectId(config);
2619
2713
  const client = await createClient(config, options);
2620
- const meta = commandMeta(parsed, config);
2714
+ const project = await resolveRequiredProject(config, client);
2715
+ const resolvedConfig = configWithProject(config, project);
2716
+ const projectId = project.id;
2717
+ const meta = commandMeta(parsed, resolvedConfig);
2621
2718
  let data;
2622
2719
  switch (parsed.command.name) {
2623
2720
  case "events.list":
@@ -2674,6 +2771,14 @@ async function runReadCommand(parsed, options) {
2674
2771
  query: await changesetLookupFromOptions(parsed, options)
2675
2772
  });
2676
2773
  break;
2774
+ case "changesets.context":
2775
+ data = await client.changesets.context({
2776
+ projectId,
2777
+ query: {
2778
+ changesetId: requireArg(parsed, 0, "<changeset-id>")
2779
+ }
2780
+ });
2781
+ break;
2677
2782
  case "changesets.list":
2678
2783
  data = await client.changesets.list({
2679
2784
  projectId,
@@ -2711,18 +2816,47 @@ async function runReadCommand(parsed, options) {
2711
2816
  async function runProjectCommand(parsed, options) {
2712
2817
  const config = await resolveCliConfig(parsed.flags, options);
2713
2818
  const client = await createClient(config, options);
2714
- const meta = commandMeta(parsed, config);
2715
2819
  let data;
2820
+ let outputConfig = config;
2716
2821
  switch (parsed.command.name) {
2717
2822
  case "projects.list":
2718
2823
  data = await client.projects.list();
2719
2824
  break;
2825
+ case "projects.use":
2826
+ {
2827
+ const project = await resolveProjectReference(requireArg(parsed, 0, "<project-id|org/project>"), client);
2828
+ const repoConfigPath = await repoConfigPathForWrite(options);
2829
+ const updates = {
2830
+ projectId: project.id,
2831
+ ...parsed.flags.server ? { server: parsed.flags.server } : {}
2832
+ };
2833
+ await mergeConfigFile(repoConfigPath, updates);
2834
+ outputConfig = configWithProject(config, project);
2835
+ data = {
2836
+ repoConfigPath,
2837
+ projectId: project.id,
2838
+ project,
2839
+ ...parsed.flags.server ? { server: parsed.flags.server } : {}
2840
+ };
2841
+ }
2842
+ break;
2720
2843
  default:
2721
2844
  throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
2722
2845
  }
2723
2846
  if (parsed.flags.json) {
2724
2847
  return {
2725
- stdout: formatJson(successEnvelope(data, meta)),
2848
+ stdout: formatJson(successEnvelope(data, commandMeta(parsed, outputConfig))),
2849
+ stderr: "",
2850
+ exitCode: 0
2851
+ };
2852
+ }
2853
+ if (parsed.command.name === "projects.use") {
2854
+ const project = data;
2855
+ const slug = project.project?.orgSlug && project.project.projectSlug ? `${project.project.orgSlug}/${project.project.projectSlug}` : project.projectId;
2856
+ const name = project.project?.name ? ` (${project.project.name})` : "";
2857
+ return {
2858
+ stdout: `Using Cadence project ${slug}${name}.
2859
+ `,
2726
2860
  stderr: "",
2727
2861
  exitCode: 0
2728
2862
  };
@@ -2737,11 +2871,13 @@ async function runProjectCommand(parsed, options) {
2737
2871
  async function runActorCommand(parsed, options) {
2738
2872
  const config = await resolveCliConfig(parsed.flags, options);
2739
2873
  const client = await createClient(config, options);
2740
- const meta = commandMeta(parsed, config);
2874
+ const project = await resolveRequiredProject(config, client);
2875
+ const resolvedConfig = configWithProject(config, project);
2876
+ const meta = commandMeta(parsed, resolvedConfig);
2741
2877
  let data;
2742
2878
  switch (parsed.command.name) {
2743
2879
  case "actors.ensure-workspace-agent":
2744
- data = await ensureWorkspaceAgentIdentity(parsed, config, client, options);
2880
+ data = await ensureWorkspaceAgentIdentity(parsed, resolvedConfig, client, options);
2745
2881
  break;
2746
2882
  default:
2747
2883
  throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
@@ -2762,9 +2898,11 @@ async function runActorCommand(parsed, options) {
2762
2898
  }
2763
2899
  async function runIntakeCommand(parsed, options) {
2764
2900
  const config = await resolveCliConfig(parsed.flags, options);
2765
- const projectId = requireProjectId(config);
2766
2901
  const client = await createClient(config, options);
2767
- const meta = commandMeta(parsed, config);
2902
+ const project = await resolveRequiredProject(config, client);
2903
+ const resolvedConfig = configWithProject(config, project);
2904
+ const projectId = project.id;
2905
+ const meta = commandMeta(parsed, resolvedConfig);
2768
2906
  let data;
2769
2907
  const ifVersion = () => parseRequiredPositiveInteger(requireOption(parsed, "if-version"), "--if-version");
2770
2908
  switch (parsed.command.name) {
@@ -2944,7 +3082,7 @@ async function runIntakeCommand(parsed, options) {
2944
3082
  changeset: {
2945
3083
  ticketId: requireOption(parsed, "ticket"),
2946
3084
  branchName: requireOption(parsed, "branch"),
2947
- baseBranch: parsed.options["base-branch"] ?? "main",
3085
+ baseBranch: requireOption(parsed, "base-branch"),
2948
3086
  ...parsed.options.session ? { sessionId: parsed.options.session } : {},
2949
3087
  ...commandMetadata()
2950
3088
  }
@@ -3009,6 +3147,12 @@ function normalizeError(error) {
3009
3147
  if (error.message === "INTAKE_ALREADY_DECIDED" || error.message.includes("INTAKE_ALREADY_DECIDED")) {
3010
3148
  return new CliError("INTAKE_ALREADY_DECIDED", "INTAKE_ALREADY_DECIDED");
3011
3149
  }
3150
+ if (typeof error === "object" && error !== null && "data" in error) {
3151
+ const data = error.data;
3152
+ if (data?.serviceDetails) {
3153
+ return new CliError("API_ERROR", error.message || "Unexpected CLI error.", data.serviceDetails);
3154
+ }
3155
+ }
3012
3156
  return new CliError("API_ERROR", error.message || "Unexpected CLI error.");
3013
3157
  }
3014
3158
  function commandNameFromArgv(argv) {
@@ -3052,10 +3196,10 @@ async function runCli(argv, options = {}) {
3052
3196
  if (parsed.command.name === "auth.login" || parsed.command.name === "auth.status" || parsed.command.name === "auth.logout") {
3053
3197
  return await runAuthCommand(parsed, options);
3054
3198
  }
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") {
3199
+ 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.context" || parsed.command.name === "changesets.current" || parsed.command.name === "changesets.list" || parsed.command.name === "changesets.notes.get") {
3056
3200
  return await runReadCommand(parsed, options);
3057
3201
  }
3058
- if (parsed.command.name === "projects.list") {
3202
+ if (parsed.command.name === "projects.list" || parsed.command.name === "projects.use") {
3059
3203
  return await runProjectCommand(parsed, options);
3060
3204
  }
3061
3205
  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") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trycadence/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {