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 +398 -26
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2391
|
+
import { Command as Command10 } from "commander";
|
|
2044
2392
|
function createProductsCommand() {
|
|
2045
|
-
const products = new
|
|
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
|
|
2456
|
+
import { Command as Command11 } from "commander";
|
|
2109
2457
|
function createProjectsCommand() {
|
|
2110
|
-
const projects = new
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
workspaceId,
|
|
2196
|
-
|
|
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
|
|
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
|
|
2876
|
+
import { Command as Command13 } from "commander";
|
|
2506
2877
|
import chalk5 from "chalk";
|
|
2507
2878
|
function createWorkspacesCommand() {
|
|
2508
|
-
const workspaces = new
|
|
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
|
|
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());
|