@trycadence/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cadence +190 -34
- package/package.json +1 -1
package/dist/cadence
CHANGED
|
@@ -1447,7 +1447,12 @@ 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
|
+
}
|
|
1451
1456
|
},
|
|
1452
1457
|
events: {
|
|
1453
1458
|
list: (input) => rpc.events.list.query(input)
|
|
@@ -1481,6 +1486,11 @@ function createCadenceClient(options = {}) {
|
|
|
1481
1486
|
create: (input) => rpc.changesets.create.mutate(input),
|
|
1482
1487
|
get: (input) => rpc.changesets.get.query(input),
|
|
1483
1488
|
list: (input) => rpc.changesets.list.query(input)
|
|
1489
|
+
},
|
|
1490
|
+
projects: {
|
|
1491
|
+
default: () => rpc.projects.default.query(),
|
|
1492
|
+
list: () => rpc.projects.list.query(),
|
|
1493
|
+
resolve: (input) => rpc.projects.resolve.query(input)
|
|
1484
1494
|
}
|
|
1485
1495
|
};
|
|
1486
1496
|
}
|
|
@@ -1494,6 +1504,7 @@ var ticketPriorities = ["low", "normal", "high", "urgent"];
|
|
|
1494
1504
|
var ticketStatuses = ["backlog", "ready", "in_progress", "blocked", "review", "done", "abandoned"];
|
|
1495
1505
|
var defaultLeaseTtlSeconds = 5 * 60 * 60;
|
|
1496
1506
|
var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
|
|
1507
|
+
var defaultCliWebBaseUrl = "https://cadence.deploy.lvl8studios.com";
|
|
1497
1508
|
|
|
1498
1509
|
class CliError extends Error {
|
|
1499
1510
|
code;
|
|
@@ -1534,6 +1545,7 @@ var knownCommandPaths = [
|
|
|
1534
1545
|
["tickets", "get"],
|
|
1535
1546
|
["events", "list"],
|
|
1536
1547
|
["work", "overview"],
|
|
1548
|
+
["projects", "list"],
|
|
1537
1549
|
["init"],
|
|
1538
1550
|
["status"],
|
|
1539
1551
|
["help"]
|
|
@@ -1724,8 +1736,44 @@ function getCredentialStore(options) {
|
|
|
1724
1736
|
async function readPromptText(options, message) {
|
|
1725
1737
|
return options.readText ? options.readText(message) : promptText(message);
|
|
1726
1738
|
}
|
|
1727
|
-
|
|
1728
|
-
return options.
|
|
1739
|
+
function isInteractive(options) {
|
|
1740
|
+
return options.isInteractive ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
1741
|
+
}
|
|
1742
|
+
function getCliWebBaseUrl(config, parsed, options) {
|
|
1743
|
+
return parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL ?? deriveWebBaseUrl(config.server);
|
|
1744
|
+
}
|
|
1745
|
+
function deriveWebBaseUrl(server) {
|
|
1746
|
+
try {
|
|
1747
|
+
const url = new URL(server);
|
|
1748
|
+
if ((url.hostname === "localhost" || url.hostname === "127.0.0.1") && url.port === "3000") {
|
|
1749
|
+
url.port = "3001";
|
|
1750
|
+
return url.toString().replace(/\/$/, "");
|
|
1751
|
+
}
|
|
1752
|
+
} catch {
|
|
1753
|
+
return defaultCliWebBaseUrl;
|
|
1754
|
+
}
|
|
1755
|
+
return defaultCliWebBaseUrl;
|
|
1756
|
+
}
|
|
1757
|
+
async function openBrowser(url, options) {
|
|
1758
|
+
if (options.openBrowser) {
|
|
1759
|
+
await options.openBrowser(url);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if (process.platform === "darwin") {
|
|
1763
|
+
const result = spawnSync("open", [url]);
|
|
1764
|
+
if (result.status !== 0) {
|
|
1765
|
+
throw new CliError("BROWSER_OPEN_FAILED", "Could not open the browser for Cadence login.", {
|
|
1766
|
+
loginUrl: url
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
async function sleep2(milliseconds, options) {
|
|
1772
|
+
if (options.sleep) {
|
|
1773
|
+
await options.sleep(milliseconds);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1729
1777
|
}
|
|
1730
1778
|
function encodeCredential(credential) {
|
|
1731
1779
|
return JSON.stringify(credential);
|
|
@@ -1773,19 +1821,6 @@ async function promptText(message) {
|
|
|
1773
1821
|
readline.close();
|
|
1774
1822
|
}
|
|
1775
1823
|
}
|
|
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
1824
|
function successEnvelope(data, meta) {
|
|
1790
1825
|
return {
|
|
1791
1826
|
success: true,
|
|
@@ -1816,11 +1851,12 @@ function helpText() {
|
|
|
1816
1851
|
"Cadence CLI",
|
|
1817
1852
|
"",
|
|
1818
1853
|
"Usage:",
|
|
1819
|
-
" cadence init --project <project-id> [--json]",
|
|
1854
|
+
" cadence init [--project <project-id|org/project>] [--json]",
|
|
1820
1855
|
" cadence auth login [--json]",
|
|
1821
1856
|
" cadence auth status [--json]",
|
|
1822
1857
|
" cadence auth logout [--json]",
|
|
1823
1858
|
" cadence status [--project <project-id>] [--json]",
|
|
1859
|
+
" cadence projects list [--json]",
|
|
1824
1860
|
" cadence work overview [--project <project-id>] [--json]",
|
|
1825
1861
|
" cadence tickets get <ticket-id> [--project <project-id>] [--json]",
|
|
1826
1862
|
" cadence tickets list [--project <project-id>] [--status <status>] [--json]",
|
|
@@ -1841,11 +1877,11 @@ function helpText() {
|
|
|
1841
1877
|
" cadence events list [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--session <session-id>] [--json]",
|
|
1842
1878
|
"",
|
|
1843
1879
|
"Global flags:",
|
|
1844
|
-
" --project <id> Cadence project ID",
|
|
1880
|
+
" --project <id> Cadence project ID or org/project slug",
|
|
1845
1881
|
" --json Print stable JSON envelope",
|
|
1846
1882
|
"",
|
|
1847
1883
|
"Auth options:",
|
|
1848
|
-
" --
|
|
1884
|
+
" --web-base-url <url> Browser login base URL"
|
|
1849
1885
|
].join(`
|
|
1850
1886
|
`);
|
|
1851
1887
|
}
|
|
@@ -1973,25 +2009,28 @@ async function runStatus(parsed, options) {
|
|
|
1973
2009
|
async function runInit(parsed, options) {
|
|
1974
2010
|
const cwd = options.cwd ?? process.cwd();
|
|
1975
2011
|
const repoConfigPath = join(cwd, ".cadence", "config.json");
|
|
1976
|
-
const
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
}
|
|
2012
|
+
const config = await resolveCliConfig(parsed.flags, {
|
|
2013
|
+
...options,
|
|
2014
|
+
cwd
|
|
2015
|
+
});
|
|
2016
|
+
const client = await createClient(config, options);
|
|
2017
|
+
const project = await selectProject(parsed, client, options);
|
|
2018
|
+
const projectId = project.id;
|
|
1980
2019
|
const updates = {
|
|
1981
2020
|
projectId,
|
|
1982
2021
|
...parsed.flags.server ? { server: parsed.flags.server } : {}
|
|
1983
2022
|
};
|
|
1984
2023
|
await mergeConfigFile(repoConfigPath, updates);
|
|
1985
|
-
const config = await resolveCliConfig(parsed.flags, {
|
|
1986
|
-
...options,
|
|
1987
|
-
cwd
|
|
1988
|
-
});
|
|
1989
2024
|
const data = {
|
|
1990
2025
|
repoConfigPath,
|
|
1991
2026
|
projectId,
|
|
2027
|
+
project,
|
|
1992
2028
|
...parsed.flags.server ? { server: parsed.flags.server } : {}
|
|
1993
2029
|
};
|
|
1994
|
-
const meta = commandMeta(parsed,
|
|
2030
|
+
const meta = commandMeta(parsed, {
|
|
2031
|
+
...config,
|
|
2032
|
+
projectId
|
|
2033
|
+
});
|
|
1995
2034
|
if (parsed.flags.json) {
|
|
1996
2035
|
return {
|
|
1997
2036
|
stdout: formatJson(successEnvelope(data, meta)),
|
|
@@ -2006,6 +2045,69 @@ async function runInit(parsed, options) {
|
|
|
2006
2045
|
exitCode: 0
|
|
2007
2046
|
};
|
|
2008
2047
|
}
|
|
2048
|
+
function isUuid(value) {
|
|
2049
|
+
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);
|
|
2050
|
+
}
|
|
2051
|
+
async function resolveProjectReference(projectReference, client) {
|
|
2052
|
+
if (isUuid(projectReference)) {
|
|
2053
|
+
return {
|
|
2054
|
+
id: projectReference,
|
|
2055
|
+
orgSlug: null,
|
|
2056
|
+
projectSlug: null,
|
|
2057
|
+
name: null
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
const [orgSlug, projectSlug, extra] = projectReference.split("/");
|
|
2061
|
+
if (!orgSlug || !projectSlug || extra) {
|
|
2062
|
+
throw new CliError("CLI_USAGE", "--project must be a project ID or org/project slug.");
|
|
2063
|
+
}
|
|
2064
|
+
return await client.projects.resolve({
|
|
2065
|
+
orgSlug,
|
|
2066
|
+
projectSlug
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
function formatProjectChoice(project, index) {
|
|
2070
|
+
const slug = project.orgSlug && project.projectSlug ? `${project.orgSlug}/${project.projectSlug}` : project.id;
|
|
2071
|
+
const name = project.name ? ` ${project.name}` : "";
|
|
2072
|
+
return `${index + 1}. ${slug}${name}`;
|
|
2073
|
+
}
|
|
2074
|
+
async function selectProject(parsed, client, options) {
|
|
2075
|
+
if (parsed.flags.project) {
|
|
2076
|
+
return await resolveProjectReference(parsed.flags.project, client);
|
|
2077
|
+
}
|
|
2078
|
+
const projects = await client.projects.list();
|
|
2079
|
+
if (projects.length === 1) {
|
|
2080
|
+
return projects[0];
|
|
2081
|
+
}
|
|
2082
|
+
if (parsed.flags.json || !isInteractive(options)) {
|
|
2083
|
+
throw new CliError("PROJECT_REQUIRED", "Choose a Cadence project with --project.", {
|
|
2084
|
+
projects
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
if (projects.length === 0) {
|
|
2088
|
+
throw new CliError("PROJECT_REQUIRED", "No accessible Cadence projects were found.");
|
|
2089
|
+
}
|
|
2090
|
+
const message = [
|
|
2091
|
+
"Choose a Cadence project:",
|
|
2092
|
+
...projects.map(formatProjectChoice),
|
|
2093
|
+
"",
|
|
2094
|
+
"Project number, ID, or org/project: "
|
|
2095
|
+
].join(`
|
|
2096
|
+
`);
|
|
2097
|
+
const answer = (await readPromptText(options, message)).trim();
|
|
2098
|
+
const selectedIndex = Number(answer);
|
|
2099
|
+
const selected = Number.isInteger(selectedIndex) && selectedIndex > 0 ? projects[selectedIndex - 1] : undefined;
|
|
2100
|
+
if (selected) {
|
|
2101
|
+
return selected;
|
|
2102
|
+
}
|
|
2103
|
+
const matching = projects.find((project) => project.id === answer || `${project.orgSlug}/${project.projectSlug}` === answer);
|
|
2104
|
+
if (!matching) {
|
|
2105
|
+
throw new CliError("PROJECT_REQUIRED", "Selected project was not in the accessible project list.", {
|
|
2106
|
+
projects
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
return matching;
|
|
2110
|
+
}
|
|
2009
2111
|
async function runAuthCommand(parsed, options) {
|
|
2010
2112
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2011
2113
|
const store = getCredentialStore(options);
|
|
@@ -2014,18 +2116,43 @@ async function runAuthCommand(parsed, options) {
|
|
|
2014
2116
|
switch (parsed.command.name) {
|
|
2015
2117
|
case "auth.login":
|
|
2016
2118
|
{
|
|
2017
|
-
await requireCredentialStore(store);
|
|
2018
2119
|
const client = await createClient(config, options);
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2120
|
+
const challenge = await client.auth.cli.start({
|
|
2121
|
+
loginBaseUrl: getCliWebBaseUrl(config, parsed, options)
|
|
2122
|
+
});
|
|
2123
|
+
if (parsed.flags.json || !isInteractive(options)) {
|
|
2124
|
+
throw new CliError("HUMAN_AUTH_REQUIRED", "Cadence login must be completed by a human in the browser.", {
|
|
2125
|
+
loginUrl: challenge.loginUrl,
|
|
2126
|
+
deviceCode: challenge.deviceCode,
|
|
2127
|
+
expiresAt: challenge.expiresAt
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
await requireCredentialStore(store);
|
|
2131
|
+
await openBrowser(challenge.loginUrl, options);
|
|
2132
|
+
let poll = await client.auth.cli.poll({
|
|
2133
|
+
deviceId: challenge.deviceId,
|
|
2134
|
+
deviceCode: challenge.deviceCode
|
|
2022
2135
|
});
|
|
2023
|
-
|
|
2136
|
+
while (poll.status === "pending") {
|
|
2137
|
+
await sleep2(poll.pollIntervalSeconds * 1000, options);
|
|
2138
|
+
poll = await client.auth.cli.poll({
|
|
2139
|
+
deviceId: challenge.deviceId,
|
|
2140
|
+
deviceCode: challenge.deviceCode
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
if (poll.status === "expired") {
|
|
2144
|
+
throw new CliError("AUTH_LOGIN_EXPIRED", "Cadence browser login expired.");
|
|
2145
|
+
}
|
|
2146
|
+
if (poll.status === "denied") {
|
|
2147
|
+
throw new CliError("AUTH_LOGIN_DENIED", "Cadence browser login was denied.");
|
|
2148
|
+
}
|
|
2149
|
+
await store.setCredential(config.server, encodeCredential(poll.credential));
|
|
2024
2150
|
await mergeConfigFile(config.globalConfigPath, { server: config.server });
|
|
2025
2151
|
data = {
|
|
2026
2152
|
server: config.server,
|
|
2027
2153
|
credentialStored: true,
|
|
2028
|
-
globalConfigPath: config.globalConfigPath
|
|
2154
|
+
globalConfigPath: config.globalConfigPath,
|
|
2155
|
+
loginUrl: challenge.loginUrl
|
|
2029
2156
|
};
|
|
2030
2157
|
}
|
|
2031
2158
|
break;
|
|
@@ -2153,6 +2280,32 @@ async function runReadCommand(parsed, options) {
|
|
|
2153
2280
|
exitCode: 0
|
|
2154
2281
|
};
|
|
2155
2282
|
}
|
|
2283
|
+
async function runProjectCommand(parsed, options) {
|
|
2284
|
+
const config = await resolveCliConfig(parsed.flags, options);
|
|
2285
|
+
const client = await createClient(config, options);
|
|
2286
|
+
const meta = commandMeta(parsed, config);
|
|
2287
|
+
let data;
|
|
2288
|
+
switch (parsed.command.name) {
|
|
2289
|
+
case "projects.list":
|
|
2290
|
+
data = await client.projects.list();
|
|
2291
|
+
break;
|
|
2292
|
+
default:
|
|
2293
|
+
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
2294
|
+
}
|
|
2295
|
+
if (parsed.flags.json) {
|
|
2296
|
+
return {
|
|
2297
|
+
stdout: formatJson(successEnvelope(data, meta)),
|
|
2298
|
+
stderr: "",
|
|
2299
|
+
exitCode: 0
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
return {
|
|
2303
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
2304
|
+
`,
|
|
2305
|
+
stderr: "",
|
|
2306
|
+
exitCode: 0
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2156
2309
|
async function runIntakeCommand(parsed, options) {
|
|
2157
2310
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2158
2311
|
const projectId = requireProjectId(config);
|
|
@@ -2376,6 +2529,9 @@ async function runCli(argv, options = {}) {
|
|
|
2376
2529
|
if (parsed.command.name === "events.list" || parsed.command.name === "work.overview" || parsed.command.name === "tickets.get" || parsed.command.name === "tickets.list" || parsed.command.name === "sessions.current" || parsed.command.name === "changesets.get" || parsed.command.name === "changesets.list") {
|
|
2377
2530
|
return await runReadCommand(parsed, options);
|
|
2378
2531
|
}
|
|
2532
|
+
if (parsed.command.name === "projects.list") {
|
|
2533
|
+
return await runProjectCommand(parsed, options);
|
|
2534
|
+
}
|
|
2379
2535
|
if (parsed.command.name === "intake" || parsed.command.name === "intake.dismiss" || parsed.command.name === "tickets.attach" || parsed.command.name === "tickets.create" || parsed.command.name === "tickets.update" || parsed.command.name === "tickets.claim" || parsed.command.name === "tickets.release" || parsed.command.name === "tickets.complete" || parsed.command.name === "sessions.start" || parsed.command.name === "sessions.end" || parsed.command.name === "changesets.create") {
|
|
2380
2536
|
return await runIntakeCommand(parsed, options);
|
|
2381
2537
|
}
|