exponential-cli 1.2.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command12 } from "commander";
4
+ import { Command as Command14 } from "commander";
5
5
 
6
6
  // src/commands/auth.ts
7
7
  import { Command } from "commander";
@@ -170,6 +170,29 @@ function createAuthCommand() {
170
170
  handleError(error);
171
171
  }
172
172
  });
173
+ auth.command("show").description(
174
+ "Print parts of the current credentials. Use --token to print the raw JWT (for piping into other tools, e.g. `gh secret set EXPONENTIAL_TOKEN < <(exponential auth show --token)`)."
175
+ ).option("--token", "Print the raw JWT to stdout with no trailing newline").action((options) => {
176
+ const config = getConfig();
177
+ if (options.token) {
178
+ if (!config.token) {
179
+ console.error("Not authenticated. Run `exponential auth login` first.");
180
+ process.exit(1);
181
+ }
182
+ process.stdout.write(config.token);
183
+ return;
184
+ }
185
+ console.log(chalk2.bold("\nCredentials"));
186
+ console.log(chalk2.gray("\u2500".repeat(40)));
187
+ console.log(`API URL: ${config.apiUrl || chalk2.gray("(not set)")}`);
188
+ console.log(
189
+ `Token: ${config.token ? chalk2.gray(`${config.token.substring(0, 20)}...`) : chalk2.gray("(not set)")}`
190
+ );
191
+ console.log(
192
+ chalk2.gray("Use `exponential auth show --token` to print the raw JWT.")
193
+ );
194
+ console.log();
195
+ });
173
196
  auth.command("status").description("Show detailed authentication status").action(() => {
174
197
  const config = getConfig();
175
198
  console.log(chalk2.bold("\nAuthentication Status"));
@@ -476,6 +499,60 @@ function outputInteractionPretty(interaction) {
476
499
  if (interaction.notes) console.log(` ${chalk3.gray("Notes:")} ${interaction.notes}`);
477
500
  console.log();
478
501
  }
502
+ function transformOrganization(org) {
503
+ return {
504
+ id: org.id,
505
+ workspaceId: org.workspaceId,
506
+ name: org.name,
507
+ websiteUrl: org.websiteUrl,
508
+ logoUrl: org.logoUrl,
509
+ description: org.description,
510
+ industry: org.industry,
511
+ size: org.size,
512
+ contactCount: org._count?.contacts ?? null,
513
+ createdAt: new Date(org.createdAt).toISOString(),
514
+ updatedAt: new Date(org.updatedAt).toISOString()
515
+ };
516
+ }
517
+ function outputOrganizationJson(org) {
518
+ console.log(JSON.stringify(transformOrganization(org), null, 2));
519
+ }
520
+ function outputOrganizationPretty(org) {
521
+ console.log(chalk3.gray("\u2500".repeat(50)));
522
+ console.log(`
523
+ ${chalk3.bold(org.name)}`);
524
+ console.log(chalk3.gray(` ID: ${org.id}`));
525
+ if (org.industry) console.log(` ${chalk3.magenta("Industry:")} ${org.industry}`);
526
+ if (org.size) console.log(` ${chalk3.cyan("Size:")} ${org.size}`);
527
+ if (org.websiteUrl) console.log(` ${chalk3.cyan("Website:")} ${org.websiteUrl}`);
528
+ if (org.description) console.log(` ${chalk3.gray("About:")} ${org.description.substring(0, 120)}${org.description.length > 120 ? "..." : ""}`);
529
+ if (org._count?.contacts != null) console.log(` ${chalk3.green("Contacts:")} ${org._count.contacts}`);
530
+ console.log();
531
+ }
532
+ function outputOrganizationsJson(organizations, nextCursor) {
533
+ console.log(JSON.stringify({
534
+ organizations: organizations.map(transformOrganization),
535
+ total: organizations.length,
536
+ nextCursor: nextCursor ?? null
537
+ }, null, 2));
538
+ }
539
+ function outputOrganizationsPretty(organizations) {
540
+ if (organizations.length === 0) {
541
+ console.log(chalk3.gray("No organizations found."));
542
+ return;
543
+ }
544
+ console.log(chalk3.bold(`
545
+ Organizations (${organizations.length} total)`));
546
+ console.log(chalk3.gray("\u2500".repeat(50)));
547
+ for (const org of organizations) {
548
+ console.log(`
549
+ ${chalk3.bold(org.name)}`);
550
+ console.log(chalk3.gray(` ID: ${org.id}`));
551
+ if (org.industry) console.log(` ${chalk3.magenta("Industry:")} ${org.industry}`);
552
+ if (org._count?.contacts != null) console.log(` ${chalk3.green("Contacts:")} ${org._count.contacts}`);
553
+ }
554
+ console.log();
555
+ }
479
556
  function outputDealJson(deal) {
480
557
  console.log(JSON.stringify(transformDeal(deal), null, 2));
481
558
  }
@@ -768,6 +845,87 @@ function getFeatureStatusColor(status) {
768
845
  return "gray";
769
846
  }
770
847
  }
848
+ function transformUserStory(story) {
849
+ return {
850
+ id: story.id,
851
+ featureId: story.featureId,
852
+ scopeId: story.scopeId,
853
+ asA: story.asA,
854
+ iWant: story.iWant,
855
+ soThat: story.soThat,
856
+ acceptanceCriteria: story.acceptanceCriteria,
857
+ displayOrder: story.displayOrder,
858
+ createdAt: new Date(story.createdAt).toISOString(),
859
+ updatedAt: new Date(story.updatedAt).toISOString()
860
+ };
861
+ }
862
+ function outputUserStoryJson(story) {
863
+ console.log(JSON.stringify(transformUserStory(story), null, 2));
864
+ }
865
+ function outputUserStoryPretty(story) {
866
+ console.log(chalk3.gray("\u2500".repeat(50)));
867
+ const order = chalk3.gray(`#${story.displayOrder}`);
868
+ console.log(`
869
+ ${order} ${chalk3.bold(story.iWant ?? '(no "I want")')}`);
870
+ console.log(chalk3.gray(` ID: ${story.id}`));
871
+ if (story.asA) console.log(` ${chalk3.cyan("As a:")} ${story.asA}`);
872
+ if (story.iWant) console.log(` ${chalk3.cyan("I want:")} ${story.iWant}`);
873
+ if (story.soThat) console.log(` ${chalk3.cyan("So that:")} ${story.soThat}`);
874
+ if (story.acceptanceCriteria) {
875
+ console.log(` ${chalk3.yellow("Acceptance:")} ${story.acceptanceCriteria}`);
876
+ }
877
+ if (story.scopeId) console.log(` ${chalk3.magenta("Scope:")} ${story.scopeId}`);
878
+ console.log();
879
+ }
880
+ function outputUserStoriesJson(stories) {
881
+ console.log(JSON.stringify({
882
+ userStories: stories.map(transformUserStory),
883
+ total: stories.length
884
+ }, null, 2));
885
+ }
886
+ function outputUserStoriesPretty(stories) {
887
+ if (stories.length === 0) {
888
+ console.log(chalk3.gray("No user stories found."));
889
+ return;
890
+ }
891
+ console.log(chalk3.bold(`
892
+ User stories (${stories.length} total)`));
893
+ console.log(chalk3.gray("\u2500".repeat(50)));
894
+ for (const story of stories) {
895
+ const order = chalk3.gray(`#${story.displayOrder}`);
896
+ const parts = [
897
+ story.asA ? `As a ${story.asA},` : null,
898
+ story.iWant ? `I want ${story.iWant},` : null,
899
+ story.soThat ? `so that ${story.soThat}.` : null
900
+ ].filter(Boolean).join(" ");
901
+ console.log(` ${order} ${parts || chalk3.gray("(empty)")}`);
902
+ console.log(chalk3.gray(` ID: ${story.id}`));
903
+ }
904
+ console.log();
905
+ }
906
+ function outputBatchResultsJson(results) {
907
+ const succeeded = results.filter((r) => r.ok).length;
908
+ console.log(JSON.stringify({
909
+ results,
910
+ total: results.length,
911
+ succeeded,
912
+ failed: results.length - succeeded
913
+ }, null, 2));
914
+ }
915
+ function outputBatchResultsPretty(results) {
916
+ const succeeded = results.filter((r) => r.ok).length;
917
+ console.log(chalk3.bold(`
918
+ Added ${succeeded}/${results.length} user stories`));
919
+ console.log(chalk3.gray("\u2500".repeat(50)));
920
+ for (const r of results) {
921
+ if (r.ok) {
922
+ console.log(` ${chalk3.green("\u2713")} [${r.index}] ${chalk3.gray(r.id ?? "")}`);
923
+ } else {
924
+ console.log(` ${chalk3.red("\u2717")} [${r.index}] ${r.error ?? "failed"}`);
925
+ }
926
+ }
927
+ console.log();
928
+ }
771
929
  function outputTicketJson(ticket) {
772
930
  console.log(JSON.stringify(transformTicket(ticket), null, 2));
773
931
  }
@@ -1313,7 +1471,7 @@ function createContactsCommand() {
1313
1471
  handleError(error, useJson);
1314
1472
  }
1315
1473
  });
1316
- contacts.command("create").description("Create a new contact").requiredOption("--workspace <id>", "Workspace ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", "Email address").option("--phone <phone>", "Phone number").option("--linkedin <url>", "LinkedIn URL").option("--telegram <handle>", "Telegram handle").option("--twitter <handle>", "Twitter/X handle").option("--github <handle>", "GitHub username").option("--bluesky <handle>", "Bluesky handle").option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", "Organization ID").action(async (options, cmd) => {
1474
+ contacts.command("create").description("Create a new contact").requiredOption("--workspace <id>", "Workspace ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", "Email address").option("--phone <phone>", "Phone number").option("--linkedin <url>", "LinkedIn URL").option("--telegram <handle>", "Telegram handle").option("--twitter <handle>", "Twitter/X handle").option("--github <handle>", "GitHub username").option("--bluesky <handle>", "Bluesky handle").option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", "Organization ID").option("--organization-name <name>", "Organization name (find-or-create; ignored if --organization is set)").action(async (options, cmd) => {
1317
1475
  const globalOpts = cmd.optsWithGlobals();
1318
1476
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1319
1477
  try {
@@ -1333,7 +1491,8 @@ function createContactsCommand() {
1333
1491
  profileType: options.profileType,
1334
1492
  skills: options.skills?.split(",").map((s) => s.trim()),
1335
1493
  tags: options.tags?.split(",").map((t) => t.trim()),
1336
- organizationId: options.organization
1494
+ organizationId: options.organization,
1495
+ organizationName: options.organizationName
1337
1496
  });
1338
1497
  if (useJson) {
1339
1498
  outputContactJson(contact);
@@ -1345,7 +1504,7 @@ function createContactsCommand() {
1345
1504
  handleError(error, useJson);
1346
1505
  }
1347
1506
  });
1348
- contacts.command("update").description("Update an existing contact").requiredOption("--id <id>", "Contact ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", 'Email (use "null" to clear)').option("--phone <phone>", 'Phone (use "null" to clear)').option("--linkedin <url>", 'LinkedIn URL (use "null" to clear)').option("--telegram <handle>", 'Telegram handle (use "null" to clear)').option("--twitter <handle>", 'Twitter/X handle (use "null" to clear)').option("--github <handle>", 'GitHub username (use "null" to clear)').option("--bluesky <handle>", 'Bluesky handle (use "null" to clear)').option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", 'Organization ID (use "null" to clear)').action(async (options, cmd) => {
1507
+ contacts.command("update").description("Update an existing contact").requiredOption("--id <id>", "Contact ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", 'Email (use "null" to clear)').option("--phone <phone>", 'Phone (use "null" to clear)').option("--linkedin <url>", 'LinkedIn URL (use "null" to clear)').option("--telegram <handle>", 'Telegram handle (use "null" to clear)').option("--twitter <handle>", 'Twitter/X handle (use "null" to clear)').option("--github <handle>", 'GitHub username (use "null" to clear)').option("--bluesky <handle>", 'Bluesky handle (use "null" to clear)').option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", 'Organization ID (use "null" to clear)').option("--organization-name <name>", "Organization name (find-or-create; ignored if --organization is set)").action(async (options, cmd) => {
1349
1508
  const globalOpts = cmd.optsWithGlobals();
1350
1509
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1351
1510
  const nullable = (v) => v === "null" ? null : v;
@@ -1366,7 +1525,8 @@ function createContactsCommand() {
1366
1525
  profileType: options.profileType,
1367
1526
  skills: options.skills?.split(",").map((s) => s.trim()),
1368
1527
  tags: options.tags?.split(",").map((t) => t.trim()),
1369
- organizationId: nullable(options.organization)
1528
+ organizationId: nullable(options.organization),
1529
+ organizationName: options.organizationName
1370
1530
  });
1371
1531
  if (useJson) {
1372
1532
  outputContactJson(contact);
@@ -1767,7 +1927,116 @@ function createEpicsCommand() {
1767
1927
  }
1768
1928
 
1769
1929
  // src/commands/features.ts
1930
+ import { Command as Command7 } from "commander";
1931
+
1932
+ // src/commands/stories.ts
1770
1933
  import { Command as Command6 } from "commander";
1934
+ async function readStdin() {
1935
+ const chunks = [];
1936
+ for await (const chunk of process.stdin) {
1937
+ chunks.push(chunk);
1938
+ }
1939
+ return Buffer.concat(chunks).toString("utf-8");
1940
+ }
1941
+ function createStoriesCommand() {
1942
+ const stories = new Command6("stories").description(
1943
+ "Manage a feature's native, structured user stories (As a / I want / So that)."
1944
+ );
1945
+ stories.command("add").description(
1946
+ [
1947
+ "Add user stories to a feature.",
1948
+ "",
1949
+ "Flag mode: pass --as-a/--i-want/--so-that (plus optional --acceptance/--scope) to add one story.",
1950
+ "Batch mode: pipe a JSON array of { asA?, iWant?, soThat?, acceptanceCriteria?, scopeId? } on stdin",
1951
+ " to add many at once (per-item success/failure is reported)."
1952
+ ].join("\n")
1953
+ ).requiredOption("--feature <id>", "Feature CUID").option("--as-a <text>", '"As a ..." actor').option("--i-want <text>", '"I want ..." capability').option("--so-that <text>", '"So that ..." outcome').option("--acceptance <text>", "Acceptance criteria").option("--scope <id>", "Feature scope CUID to group the story under").action(
1954
+ async (options, cmd) => {
1955
+ const globalOpts = cmd.optsWithGlobals();
1956
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1957
+ const hasFlags = options.asA != null || options.iWant != null || options.soThat != null || options.acceptance != null;
1958
+ const isBatch = !hasFlags && !process.stdin.isTTY;
1959
+ try {
1960
+ const client = getClient();
1961
+ if (isBatch) {
1962
+ const raw = await readStdin();
1963
+ let items;
1964
+ try {
1965
+ const parsed = JSON.parse(raw);
1966
+ if (!Array.isArray(parsed)) {
1967
+ throw new Error("Expected a JSON array of story objects on stdin");
1968
+ }
1969
+ items = parsed;
1970
+ } catch (parseError) {
1971
+ throw new Error(
1972
+ `Could not parse stdin as a JSON array: ${parseError instanceof Error ? parseError.message : String(parseError)}`
1973
+ );
1974
+ }
1975
+ const results = [];
1976
+ for (let index = 0; index < items.length; index++) {
1977
+ const item = items[index];
1978
+ try {
1979
+ const story2 = await client.userStories.create({
1980
+ featureId: options.feature,
1981
+ asA: item.asA,
1982
+ iWant: item.iWant,
1983
+ soThat: item.soThat,
1984
+ acceptanceCriteria: item.acceptanceCriteria,
1985
+ scopeId: item.scopeId
1986
+ });
1987
+ results.push({ index, ok: true, id: story2.id });
1988
+ } catch (itemError) {
1989
+ results.push({
1990
+ index,
1991
+ ok: false,
1992
+ error: itemError instanceof Error ? itemError.message : String(itemError)
1993
+ });
1994
+ }
1995
+ }
1996
+ if (useJson) outputBatchResultsJson(results);
1997
+ else outputBatchResultsPretty(results);
1998
+ if (results.length > 0 && results.every((r) => !r.ok)) {
1999
+ process.exit(1);
2000
+ }
2001
+ return;
2002
+ }
2003
+ const input = {
2004
+ featureId: options.feature,
2005
+ asA: options.asA,
2006
+ iWant: options.iWant,
2007
+ soThat: options.soThat,
2008
+ acceptanceCriteria: options.acceptance,
2009
+ scopeId: options.scope
2010
+ };
2011
+ const story = await client.userStories.create(input);
2012
+ if (useJson) outputUserStoryJson(story);
2013
+ else {
2014
+ console.log("\n\u2713 User story added");
2015
+ outputUserStoryPretty(story);
2016
+ }
2017
+ } catch (error) {
2018
+ handleError(error, useJson);
2019
+ }
2020
+ }
2021
+ );
2022
+ stories.command("list").description("List a feature's user stories in display order").requiredOption("--feature <id>", "Feature CUID").action(
2023
+ async (options, cmd) => {
2024
+ const globalOpts = cmd.optsWithGlobals();
2025
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2026
+ try {
2027
+ const client = getClient();
2028
+ const list = await client.userStories.list({ featureId: options.feature });
2029
+ if (useJson) outputUserStoriesJson(list);
2030
+ else outputUserStoriesPretty(list);
2031
+ } catch (error) {
2032
+ handleError(error, useJson);
2033
+ }
2034
+ }
2035
+ );
2036
+ return stories;
2037
+ }
2038
+
2039
+ // src/commands/features.ts
1771
2040
  var FEATURE_STATUSES = [
1772
2041
  "IDEA",
1773
2042
  "DEFINED",
@@ -1785,7 +2054,7 @@ function validateFeatureStatus(value) {
1785
2054
  return value;
1786
2055
  }
1787
2056
  function createFeaturesCommand() {
1788
- const features = new Command6("features").description(
2057
+ const features = new Command7("features").description(
1789
2058
  "Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
1790
2059
  );
1791
2060
  features.command("list").description("List features in a product").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${FEATURE_STATUSES.join(", ")}`).action(
@@ -1879,11 +2148,12 @@ function createFeaturesCommand() {
1879
2148
  }
1880
2149
  }
1881
2150
  );
2151
+ features.addCommand(createStoriesCommand());
1882
2152
  return features;
1883
2153
  }
1884
2154
 
1885
2155
  // src/commands/labels.ts
1886
- import { Command as Command7 } from "commander";
2156
+ import { Command as Command8 } from "commander";
1887
2157
  import chalk4 from "chalk";
1888
2158
  var TRIAGE_LABELS = [
1889
2159
  { slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
@@ -1937,7 +2207,7 @@ async function resolveLabelIds(client, workspaceId, values) {
1937
2207
  return [...byId, ...resolvedFromSlugs];
1938
2208
  }
1939
2209
  function createLabelsCommand() {
1940
- const labels = new Command7("labels").description(
2210
+ const labels = new Command8("labels").description(
1941
2211
  "Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
1942
2212
  );
1943
2213
  labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
@@ -2039,10 +2309,88 @@ function createLabelsCommand() {
2039
2309
  return labels;
2040
2310
  }
2041
2311
 
2312
+ // src/commands/organizations.ts
2313
+ import { Command as Command9 } from "commander";
2314
+ var VALID_SIZES = [
2315
+ "1-10",
2316
+ "11-50",
2317
+ "51-200",
2318
+ "201-500",
2319
+ "501-1000",
2320
+ "1000+"
2321
+ ];
2322
+ function createOrganizationsCommand() {
2323
+ const organizations = new Command9("organizations").alias("orgs").description("Manage CRM organizations");
2324
+ organizations.command("list").description("List organizations").requiredOption("--workspace <id>", "Workspace ID").option("--search <query>", "Search by name or description").option("--industry <industry>", "Filter by industry").option("--limit <n>", "Max results to return", parseInt).option("--cursor <cursor>", "Pagination cursor").action(async (options, cmd) => {
2325
+ const globalOpts = cmd.optsWithGlobals();
2326
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2327
+ try {
2328
+ const client = getClient();
2329
+ const result = await client.organizations.list({
2330
+ workspaceId: options.workspace,
2331
+ search: options.search,
2332
+ industry: options.industry,
2333
+ limit: options.limit,
2334
+ cursor: options.cursor
2335
+ });
2336
+ if (useJson) {
2337
+ outputOrganizationsJson(result.organizations, result.nextCursor);
2338
+ } else {
2339
+ outputOrganizationsPretty(result.organizations);
2340
+ }
2341
+ } catch (error) {
2342
+ handleError(error, useJson);
2343
+ }
2344
+ });
2345
+ organizations.command("get").description("Get an organization by ID").argument("<id>", "Organization ID").action(async (id, _options, cmd) => {
2346
+ const globalOpts = cmd.optsWithGlobals();
2347
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2348
+ try {
2349
+ const client = getClient();
2350
+ const org = await client.organizations.get(id);
2351
+ if (useJson) {
2352
+ outputOrganizationJson(org);
2353
+ } else {
2354
+ outputOrganizationPretty(org);
2355
+ }
2356
+ } catch (error) {
2357
+ handleError(error, useJson);
2358
+ }
2359
+ });
2360
+ organizations.command("create").description("Create a new organization").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--name <name>", "Organization name").option("--website-url <url>", "Website URL").option("--logo-url <url>", "Logo URL").option("--description <text>", "Description").option("--industry <industry>", "Industry").option("--size <size>", `Company size (${VALID_SIZES.join(", ")})`).action(async (options, cmd) => {
2361
+ const globalOpts = cmd.optsWithGlobals();
2362
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2363
+ try {
2364
+ if (options.size && !VALID_SIZES.includes(options.size)) {
2365
+ throw new Error(`Invalid size "${options.size}". Valid: ${VALID_SIZES.join(", ")}`);
2366
+ }
2367
+ const client = getClient();
2368
+ const org = await client.organizations.create({
2369
+ workspaceId: options.workspace,
2370
+ name: options.name,
2371
+ websiteUrl: options.websiteUrl,
2372
+ logoUrl: options.logoUrl,
2373
+ description: options.description,
2374
+ industry: options.industry,
2375
+ size: options.size
2376
+ });
2377
+ if (useJson) {
2378
+ outputOrganizationJson(org);
2379
+ } else {
2380
+ console.log("\n\u2713 Organization created successfully");
2381
+ outputOrganizationPretty(org);
2382
+ }
2383
+ } catch (error) {
2384
+ handleError(error, useJson);
2385
+ }
2386
+ });
2387
+ return organizations;
2388
+ }
2389
+
2042
2390
  // src/commands/products.ts
2043
- import { Command as Command8 } from "commander";
2391
+ import { Command as Command10 } from "commander";
2044
2392
  function createProductsCommand() {
2045
- const products = new Command8("products").description(
2393
+ const products = new Command10("products").description(
2046
2394
  "Manage products. A workspace contains products; a product holds features and tickets."
2047
2395
  );
2048
2396
  products.command("list").description("List products in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID (defaults to configured workspace)").action(async (options, cmd) => {
@@ -2105,9 +2453,9 @@ function createProductsCommand() {
2105
2453
  }
2106
2454
 
2107
2455
  // src/commands/projects.ts
2108
- import { Command as Command9 } from "commander";
2456
+ import { Command as Command11 } from "commander";
2109
2457
  function createProjectsCommand() {
2110
- const projects = new Command9("projects").description("Manage projects");
2458
+ const projects = new Command11("projects").description("Manage projects");
2111
2459
  projects.command("list").description("List all projects").option("--workspace <id>", "Filter by workspace ID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
2112
2460
  const globalOpts = cmd.optsWithGlobals();
2113
2461
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2130,7 +2478,7 @@ function createProjectsCommand() {
2130
2478
  }
2131
2479
 
2132
2480
  // src/commands/tickets.ts
2133
- import { Command as Command10 } from "commander";
2481
+ import { Command as Command12 } from "commander";
2134
2482
  function collectRepeatable(value, previous) {
2135
2483
  return [...previous, value];
2136
2484
  }
@@ -2173,10 +2521,21 @@ function validateTicketType(value) {
2173
2521
  return value;
2174
2522
  }
2175
2523
  function createTicketsCommand() {
2176
- const tickets = new Command10("tickets").description(
2524
+ const tickets = new Command12("tickets").description(
2177
2525
  "Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
2178
2526
  );
2179
- tickets.command("list").description("List tickets in a product").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${TICKET_STATUSES.join(", ")}`).option("--type <type>", `Filter by type: ${TICKET_TYPES.join(", ")}`).option("--feature <id>", "Filter by feature CUID").option("--epic <id>", "Filter by epic CUID").option("--assignee <id>", "Filter by assignee user ID").option(
2527
+ tickets.command("list").description(
2528
+ "List tickets in a product, or workspace-wide when --pr or --branch is supplied"
2529
+ ).option(
2530
+ "--product <slug|id>",
2531
+ "Product slug or CUID (required unless --pr or --branch is supplied)"
2532
+ ).option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${TICKET_STATUSES.join(", ")}`).option("--type <type>", `Filter by type: ${TICKET_TYPES.join(", ")}`).option("--feature <id>", "Filter by feature CUID").option("--epic <id>", "Filter by epic CUID").option("--assignee <id>", "Filter by assignee user ID").option(
2533
+ "--pr <url>",
2534
+ "Workspace-wide lookup by Ticket.prUrl (exact match). Makes --product optional."
2535
+ ).option(
2536
+ "--branch <name>",
2537
+ "Workspace-wide lookup by Ticket.branchName (exact match). Makes --product optional."
2538
+ ).option(
2180
2539
  "--label <slug-or-id>",
2181
2540
  "Filter to tickets carrying this label (repeatable; AND semantics)",
2182
2541
  collectRepeatable,
@@ -2188,20 +2547,32 @@ function createTicketsCommand() {
2188
2547
  try {
2189
2548
  const status = validateTicketStatus(options.status);
2190
2549
  const type = validateTicketType(options.type);
2550
+ const hasWorkspaceFilter = Boolean(options.pr || options.branch);
2551
+ if (!hasWorkspaceFilter && !options.product) {
2552
+ throw new Error(
2553
+ "--product is required unless --pr or --branch is supplied"
2554
+ );
2555
+ }
2191
2556
  const client = getClient();
2192
- const workspaceId = await resolveWorkspaceId(client, options.workspace);
2193
- const productId = await resolveProductId(
2194
- client,
2195
- workspaceId,
2196
- options.product
2197
- );
2557
+ let workspaceId;
2558
+ let productId;
2559
+ if (options.product) {
2560
+ workspaceId = await resolveWorkspaceId(client, options.workspace);
2561
+ productId = await resolveProductId(
2562
+ client,
2563
+ workspaceId,
2564
+ options.product
2565
+ );
2566
+ }
2198
2567
  const list = await client.tickets.list({
2199
2568
  productId,
2200
2569
  status,
2201
2570
  type,
2202
2571
  featureId: options.feature,
2203
2572
  epicId: options.epic,
2204
- assigneeId: options.assignee
2573
+ assigneeId: options.assignee,
2574
+ prUrl: options.pr,
2575
+ branchName: options.branch
2205
2576
  });
2206
2577
  let filtered = list;
2207
2578
  if (options.label.length > 0) {
@@ -2463,7 +2834,7 @@ function createTicketsCommand() {
2463
2834
  handleError(error, useJson);
2464
2835
  }
2465
2836
  });
2466
- const comment = new Command10("comment").description("Manage comments on a ticket");
2837
+ const comment = new Command12("comment").description("Manage comments on a ticket");
2467
2838
  comment.command("add").description("Add a comment to a ticket").requiredOption("--id <id>", "Ticket CUID").requiredOption("-m, --message <text>", "Comment content (markdown supported)").action(async (options, cmd) => {
2468
2839
  const globalOpts = cmd.optsWithGlobals();
2469
2840
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2502,10 +2873,10 @@ function createTicketsCommand() {
2502
2873
  }
2503
2874
 
2504
2875
  // src/commands/workspaces.ts
2505
- import { Command as Command11 } from "commander";
2876
+ import { Command as Command13 } from "commander";
2506
2877
  import chalk5 from "chalk";
2507
2878
  function createWorkspacesCommand() {
2508
- const workspaces = new Command11("workspaces").description("Manage workspaces");
2879
+ const workspaces = new Command13("workspaces").description("Manage workspaces");
2509
2880
  workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
2510
2881
  const globalOpts = cmd.optsWithGlobals();
2511
2882
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2551,7 +2922,7 @@ function createWorkspacesCommand() {
2551
2922
  }
2552
2923
 
2553
2924
  // src/index.ts
2554
- var program = new Command12();
2925
+ var program = new Command14();
2555
2926
  program.name("exponential").description(
2556
2927
  [
2557
2928
  "CLI to interact with Exponential productivity app.",
@@ -2568,6 +2939,7 @@ program.addCommand(createDealsCommand());
2568
2939
  program.addCommand(createEpicsCommand());
2569
2940
  program.addCommand(createFeaturesCommand());
2570
2941
  program.addCommand(createLabelsCommand());
2942
+ program.addCommand(createOrganizationsCommand());
2571
2943
  program.addCommand(createProductsCommand());
2572
2944
  program.addCommand(createProjectsCommand());
2573
2945
  program.addCommand(createTicketsCommand());