exponential-cli 1.6.0 → 1.8.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 +684 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 Command19 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -1176,6 +1176,127 @@ Pipeline Stages (${stages.length})`));
|
|
|
1176
1176
|
}
|
|
1177
1177
|
console.log();
|
|
1178
1178
|
}
|
|
1179
|
+
function outputPageJson(page) {
|
|
1180
|
+
console.log(JSON.stringify(page, null, 2));
|
|
1181
|
+
}
|
|
1182
|
+
function outputPagePretty(page) {
|
|
1183
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1184
|
+
console.log(`
|
|
1185
|
+
${chalk3.bold(page.title)}`);
|
|
1186
|
+
console.log(chalk3.gray(` ID: ${page.id}`));
|
|
1187
|
+
if (page.project) {
|
|
1188
|
+
console.log(` ${chalk3.gray("Project:")} ${page.project.name}`);
|
|
1189
|
+
}
|
|
1190
|
+
if (page.body) {
|
|
1191
|
+
console.log(`
|
|
1192
|
+
${page.body}
|
|
1193
|
+
`);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function outputPagesJson(pages) {
|
|
1197
|
+
console.log(JSON.stringify({ pages, total: pages.length }, null, 2));
|
|
1198
|
+
}
|
|
1199
|
+
function outputPagesPretty(pages) {
|
|
1200
|
+
if (pages.length === 0) {
|
|
1201
|
+
console.log(chalk3.gray("No pages found."));
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
console.log(chalk3.bold(`
|
|
1205
|
+
Pages (${pages.length} total)`));
|
|
1206
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1207
|
+
for (const p of pages) {
|
|
1208
|
+
console.log(` ${chalk3.bold(p.title)}`);
|
|
1209
|
+
console.log(chalk3.gray(` ID: ${p.id}`));
|
|
1210
|
+
}
|
|
1211
|
+
console.log();
|
|
1212
|
+
}
|
|
1213
|
+
function outputRequirementJson(requirement) {
|
|
1214
|
+
console.log(JSON.stringify(requirement, null, 2));
|
|
1215
|
+
}
|
|
1216
|
+
function outputRequirementPretty(requirement) {
|
|
1217
|
+
const met = requirement.checkedAt ? chalk3.green("[met]") : chalk3.gray("[unmet]");
|
|
1218
|
+
const kind = requirement.kind ? chalk3.gray(` (${requirement.kind})`) : "";
|
|
1219
|
+
console.log(` ${met} ${requirement.statement}${kind}`);
|
|
1220
|
+
console.log(chalk3.gray(` ID: ${requirement.id}`));
|
|
1221
|
+
}
|
|
1222
|
+
function outputRequirementsJson(requirements) {
|
|
1223
|
+
console.log(JSON.stringify({ requirements, total: requirements.length }, null, 2));
|
|
1224
|
+
}
|
|
1225
|
+
function outputRequirementsPretty(requirements) {
|
|
1226
|
+
if (requirements.length === 0) {
|
|
1227
|
+
console.log(chalk3.gray("No requirements found."));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const metCount = requirements.filter((r) => r.checkedAt).length;
|
|
1231
|
+
console.log(chalk3.bold(`
|
|
1232
|
+
Requirements (${metCount}/${requirements.length} met)`));
|
|
1233
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1234
|
+
for (const r of requirements) {
|
|
1235
|
+
outputRequirementPretty(r);
|
|
1236
|
+
}
|
|
1237
|
+
console.log();
|
|
1238
|
+
}
|
|
1239
|
+
function outputRequirementBatchJson(results) {
|
|
1240
|
+
console.log(JSON.stringify({
|
|
1241
|
+
results,
|
|
1242
|
+
total: results.length,
|
|
1243
|
+
succeeded: results.filter((r) => r.success).length,
|
|
1244
|
+
failed: results.filter((r) => !r.success).length
|
|
1245
|
+
}, null, 2));
|
|
1246
|
+
}
|
|
1247
|
+
function outputRequirementBatchPretty(results) {
|
|
1248
|
+
for (const r of results) {
|
|
1249
|
+
if (r.success) {
|
|
1250
|
+
console.log(` ${chalk3.green("\u2713")} #${r.index}: ${r.requirement?.statement ?? ""}`);
|
|
1251
|
+
} else {
|
|
1252
|
+
console.log(` ${chalk3.red("\u2717")} #${r.index}: ${r.error ?? "failed"}`);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const ok = results.filter((r) => r.success).length;
|
|
1256
|
+
console.log(chalk3.bold(`
|
|
1257
|
+
${ok}/${results.length} requirements added`));
|
|
1258
|
+
}
|
|
1259
|
+
function getScopeStatusColor(status) {
|
|
1260
|
+
switch (status) {
|
|
1261
|
+
case "PLANNED":
|
|
1262
|
+
return "blue";
|
|
1263
|
+
case "IN_PROGRESS":
|
|
1264
|
+
return "yellow";
|
|
1265
|
+
case "SHIPPED":
|
|
1266
|
+
return "green";
|
|
1267
|
+
case "DEPRECATED":
|
|
1268
|
+
return "red";
|
|
1269
|
+
default:
|
|
1270
|
+
return "gray";
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function outputScopeJson(scope) {
|
|
1274
|
+
console.log(JSON.stringify(scope, null, 2));
|
|
1275
|
+
}
|
|
1276
|
+
function outputScopePretty(scope) {
|
|
1277
|
+
const badge = chalk3[getScopeStatusColor(scope.status)](`[${scope.status}]`);
|
|
1278
|
+
console.log(` ${badge} ${chalk3.bold(scope.version)}`);
|
|
1279
|
+
console.log(chalk3.gray(` ID: ${scope.id}`));
|
|
1280
|
+
if (scope.description) {
|
|
1281
|
+
console.log(` ${scope.description.substring(0, 120)}${scope.description.length > 120 ? "..." : ""}`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function outputScopesJson(scopes) {
|
|
1285
|
+
console.log(JSON.stringify({ scopes, total: scopes.length }, null, 2));
|
|
1286
|
+
}
|
|
1287
|
+
function outputScopesPretty(scopes) {
|
|
1288
|
+
if (scopes.length === 0) {
|
|
1289
|
+
console.log(chalk3.gray("No scopes found."));
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
console.log(chalk3.bold(`
|
|
1293
|
+
Scopes (${scopes.length} total)`));
|
|
1294
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1295
|
+
for (const s of scopes) {
|
|
1296
|
+
outputScopePretty(s);
|
|
1297
|
+
}
|
|
1298
|
+
console.log();
|
|
1299
|
+
}
|
|
1179
1300
|
|
|
1180
1301
|
// src/commands/actions.ts
|
|
1181
1302
|
var VALID_PRIORITIES = [
|
|
@@ -1949,7 +2070,7 @@ function createEpicsCommand() {
|
|
|
1949
2070
|
}
|
|
1950
2071
|
|
|
1951
2072
|
// src/commands/features.ts
|
|
1952
|
-
import { Command as
|
|
2073
|
+
import { Command as Command10 } from "commander";
|
|
1953
2074
|
|
|
1954
2075
|
// src/commands/stories.ts
|
|
1955
2076
|
import { Command as Command6 } from "commander";
|
|
@@ -2058,12 +2179,325 @@ function createStoriesCommand() {
|
|
|
2058
2179
|
return stories;
|
|
2059
2180
|
}
|
|
2060
2181
|
|
|
2182
|
+
// src/commands/scopes.ts
|
|
2183
|
+
import { Command as Command7 } from "commander";
|
|
2184
|
+
var SCOPE_STATUSES = [
|
|
2185
|
+
"PLANNED",
|
|
2186
|
+
"IN_PROGRESS",
|
|
2187
|
+
"SHIPPED",
|
|
2188
|
+
"DEPRECATED"
|
|
2189
|
+
];
|
|
2190
|
+
function validateScopeStatus(value) {
|
|
2191
|
+
if (!value) return void 0;
|
|
2192
|
+
if (!SCOPE_STATUSES.includes(value)) {
|
|
2193
|
+
throw new Error(
|
|
2194
|
+
`Invalid status "${value}". Valid: ${SCOPE_STATUSES.join(", ")}`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
return value;
|
|
2198
|
+
}
|
|
2199
|
+
function createScopesCommand() {
|
|
2200
|
+
const scopes = new Command7("scopes").description(
|
|
2201
|
+
`Manage a feature's scopes: shippable increments ("V1", "V2"), each with its own lifecycle.`
|
|
2202
|
+
);
|
|
2203
|
+
scopes.command("list").description("List a feature's scopes").requiredOption("--feature <id>", "Feature CUID").action(
|
|
2204
|
+
async (options, cmd) => {
|
|
2205
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2206
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2207
|
+
try {
|
|
2208
|
+
const client = getClient();
|
|
2209
|
+
const list = await client.scopes.list({ featureId: options.feature });
|
|
2210
|
+
if (useJson) outputScopesJson(list);
|
|
2211
|
+
else outputScopesPretty(list);
|
|
2212
|
+
} catch (error) {
|
|
2213
|
+
handleError(error, useJson);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
scopes.command("add").description("Add a scope to a feature").requiredOption("--feature <id>", "Feature CUID").requiredOption("--version <label>", 'Short label for the increment, e.g. "V1"').requiredOption("-d, --description <text>", "What this scope delivers (markdown supported)").option("--status <status>", `Status: ${SCOPE_STATUSES.join(", ")} (default PLANNED)`).action(
|
|
2218
|
+
async (options, cmd) => {
|
|
2219
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2220
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2221
|
+
try {
|
|
2222
|
+
const status = validateScopeStatus(options.status);
|
|
2223
|
+
const client = getClient();
|
|
2224
|
+
const scope = await client.scopes.create({
|
|
2225
|
+
featureId: options.feature,
|
|
2226
|
+
version: options.version,
|
|
2227
|
+
description: options.description,
|
|
2228
|
+
status
|
|
2229
|
+
});
|
|
2230
|
+
if (useJson) outputScopeJson(scope);
|
|
2231
|
+
else {
|
|
2232
|
+
console.log("\n\u2713 Scope added");
|
|
2233
|
+
outputScopePretty(scope);
|
|
2234
|
+
}
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
handleError(error, useJson);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
);
|
|
2240
|
+
scopes.command("update").description("Update a scope (setting SHIPPED stamps shippedAt and rolls the feature status up)").requiredOption("--id <id>", "Scope CUID").option("--version <label>", "New version label").option("-d, --description <text>", "New description").option("--status <status>", `Status: ${SCOPE_STATUSES.join(", ")}`).action(
|
|
2241
|
+
async (options, cmd) => {
|
|
2242
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2243
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2244
|
+
try {
|
|
2245
|
+
const status = validateScopeStatus(options.status);
|
|
2246
|
+
const client = getClient();
|
|
2247
|
+
const scope = await client.scopes.update({
|
|
2248
|
+
id: options.id,
|
|
2249
|
+
version: options.version,
|
|
2250
|
+
description: options.description,
|
|
2251
|
+
status
|
|
2252
|
+
});
|
|
2253
|
+
if (useJson) outputScopeJson(scope);
|
|
2254
|
+
else {
|
|
2255
|
+
console.log("\n\u2713 Scope updated");
|
|
2256
|
+
outputScopePretty(scope);
|
|
2257
|
+
}
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
handleError(error, useJson);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
);
|
|
2263
|
+
scopes.command("rm").description("Delete a scope").requiredOption("--id <id>", "Scope CUID").action(async (options, cmd) => {
|
|
2264
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2265
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2266
|
+
try {
|
|
2267
|
+
const client = getClient();
|
|
2268
|
+
const result = await client.scopes.delete(options.id);
|
|
2269
|
+
if (useJson) console.log(JSON.stringify(result, null, 2));
|
|
2270
|
+
else console.log("\n\u2713 Scope deleted");
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
handleError(error, useJson);
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
return scopes;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// src/commands/requirements.ts
|
|
2279
|
+
import { Command as Command8 } from "commander";
|
|
2280
|
+
var REQUIREMENT_KINDS = [
|
|
2281
|
+
"FUNCTIONAL",
|
|
2282
|
+
"NON_FUNCTIONAL",
|
|
2283
|
+
"CONSTRAINT"
|
|
2284
|
+
];
|
|
2285
|
+
function validateKind(value) {
|
|
2286
|
+
if (!value) return void 0;
|
|
2287
|
+
if (!REQUIREMENT_KINDS.includes(value)) {
|
|
2288
|
+
throw new Error(
|
|
2289
|
+
`Invalid kind "${value}". Valid: ${REQUIREMENT_KINDS.join(", ")}`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
return value;
|
|
2293
|
+
}
|
|
2294
|
+
async function readStdin2() {
|
|
2295
|
+
const chunks = [];
|
|
2296
|
+
for await (const chunk of process.stdin) {
|
|
2297
|
+
chunks.push(chunk);
|
|
2298
|
+
}
|
|
2299
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
2300
|
+
}
|
|
2301
|
+
function createRequirementsCommand() {
|
|
2302
|
+
const requirements = new Command8("requirements").description(
|
|
2303
|
+
`Manage a feature's requirements: atomic, testable EARS-style "shall" statements, checkable met/unmet.`
|
|
2304
|
+
);
|
|
2305
|
+
requirements.command("add").description(
|
|
2306
|
+
[
|
|
2307
|
+
"Add requirements to a feature.",
|
|
2308
|
+
"",
|
|
2309
|
+
"Flag mode: pass -s/--statement (plus optional --kind/--scope) to add one requirement.",
|
|
2310
|
+
"Batch mode: pipe a JSON array of { statement, kind?, scopeId? } on stdin",
|
|
2311
|
+
" to add many at once (per-item success/failure is reported)."
|
|
2312
|
+
].join("\n")
|
|
2313
|
+
).requiredOption("--feature <id>", "Feature CUID").option("-s, --statement <text>", 'One EARS-style "shall" statement').option("--kind <kind>", `Kind: ${REQUIREMENT_KINDS.join(", ")}`).option("--scope <id>", "Feature scope CUID to pin the requirement to").action(
|
|
2314
|
+
async (options, cmd) => {
|
|
2315
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2316
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2317
|
+
try {
|
|
2318
|
+
const client = getClient();
|
|
2319
|
+
if (options.statement) {
|
|
2320
|
+
const requirement = await client.requirements.create({
|
|
2321
|
+
featureId: options.feature,
|
|
2322
|
+
statement: options.statement,
|
|
2323
|
+
kind: validateKind(options.kind),
|
|
2324
|
+
scopeId: options.scope
|
|
2325
|
+
});
|
|
2326
|
+
if (useJson) outputRequirementJson(requirement);
|
|
2327
|
+
else {
|
|
2328
|
+
console.log("\n\u2713 Requirement added");
|
|
2329
|
+
outputRequirementPretty(requirement);
|
|
2330
|
+
}
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (process.stdin.isTTY) {
|
|
2334
|
+
throw new Error(
|
|
2335
|
+
"Pass -s/--statement, or pipe a JSON array of requirements on stdin."
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
const raw = await readStdin2();
|
|
2339
|
+
let batch;
|
|
2340
|
+
try {
|
|
2341
|
+
const parsed = JSON.parse(raw);
|
|
2342
|
+
if (!Array.isArray(parsed)) throw new Error("not an array");
|
|
2343
|
+
batch = parsed;
|
|
2344
|
+
} catch {
|
|
2345
|
+
throw new Error(
|
|
2346
|
+
"stdin must be a JSON array of { statement, kind?, scopeId? } objects."
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
const results = [];
|
|
2350
|
+
for (const [index, item] of batch.entries()) {
|
|
2351
|
+
try {
|
|
2352
|
+
if (!item.statement || typeof item.statement !== "string") {
|
|
2353
|
+
throw new Error('missing "statement"');
|
|
2354
|
+
}
|
|
2355
|
+
const input = {
|
|
2356
|
+
featureId: options.feature,
|
|
2357
|
+
statement: item.statement,
|
|
2358
|
+
kind: validateKind(item.kind),
|
|
2359
|
+
scopeId: item.scopeId
|
|
2360
|
+
};
|
|
2361
|
+
const requirement = await client.requirements.create(input);
|
|
2362
|
+
results.push({ index, success: true, requirement });
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
results.push({
|
|
2365
|
+
index,
|
|
2366
|
+
success: false,
|
|
2367
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (useJson) outputRequirementBatchJson(results);
|
|
2372
|
+
else outputRequirementBatchPretty(results);
|
|
2373
|
+
if (results.some((r) => !r.success)) process.exitCode = 1;
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
handleError(error, useJson);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
);
|
|
2379
|
+
requirements.command("list").description("List a feature's requirements").requiredOption("--feature <id>", "Feature CUID").option("--scope <id>", "Only requirements pinned to this scope").action(
|
|
2380
|
+
async (options, cmd) => {
|
|
2381
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2382
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2383
|
+
try {
|
|
2384
|
+
const client = getClient();
|
|
2385
|
+
const list = await client.requirements.list({
|
|
2386
|
+
featureId: options.feature,
|
|
2387
|
+
scopeId: options.scope
|
|
2388
|
+
});
|
|
2389
|
+
if (useJson) outputRequirementsJson(list);
|
|
2390
|
+
else outputRequirementsPretty(list);
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
handleError(error, useJson);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
);
|
|
2396
|
+
requirements.command("check").description("Mark a requirement met (or unmet with --unmet)").requiredOption("--id <id>", "Requirement CUID").option("--unmet", "Mark unmet instead of met").action(
|
|
2397
|
+
async (options, cmd) => {
|
|
2398
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2399
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2400
|
+
try {
|
|
2401
|
+
const client = getClient();
|
|
2402
|
+
const requirement = await client.requirements.setChecked(
|
|
2403
|
+
options.id,
|
|
2404
|
+
!options.unmet
|
|
2405
|
+
);
|
|
2406
|
+
if (useJson) outputRequirementJson(requirement);
|
|
2407
|
+
else {
|
|
2408
|
+
console.log(`
|
|
2409
|
+
\u2713 Requirement marked ${options.unmet ? "unmet" : "met"}`);
|
|
2410
|
+
outputRequirementPretty(requirement);
|
|
2411
|
+
}
|
|
2412
|
+
} catch (error) {
|
|
2413
|
+
handleError(error, useJson);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
);
|
|
2417
|
+
requirements.command("rm").description("Delete a requirement").requiredOption("--id <id>", "Requirement CUID").action(async (options, cmd) => {
|
|
2418
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2419
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2420
|
+
try {
|
|
2421
|
+
const client = getClient();
|
|
2422
|
+
const result = await client.requirements.delete(options.id);
|
|
2423
|
+
if (useJson) console.log(JSON.stringify(result, null, 2));
|
|
2424
|
+
else console.log("\n\u2713 Requirement deleted");
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
handleError(error, useJson);
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2429
|
+
return requirements;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// src/commands/areas.ts
|
|
2433
|
+
import { Command as Command9 } from "commander";
|
|
2434
|
+
import chalk4 from "chalk";
|
|
2435
|
+
function outputAreasPretty(areas) {
|
|
2436
|
+
if (areas.length === 0) {
|
|
2437
|
+
console.log(chalk4.gray("No areas found."));
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
console.log(chalk4.bold(`
|
|
2441
|
+
Areas (${areas.length} total)`));
|
|
2442
|
+
console.log(chalk4.gray("\u2500".repeat(50)));
|
|
2443
|
+
for (const a of areas) {
|
|
2444
|
+
const count = a._count?.features != null ? chalk4.gray(` (${a._count.features} features)`) : "";
|
|
2445
|
+
console.log(` ${chalk4.bold(a.name)}${count}`);
|
|
2446
|
+
console.log(chalk4.gray(` ID: ${a.id}`));
|
|
2447
|
+
}
|
|
2448
|
+
console.log();
|
|
2449
|
+
}
|
|
2450
|
+
function createAreasCommand() {
|
|
2451
|
+
const areas = new Command9("areas").description(
|
|
2452
|
+
"Manage a product's areas: the buckets features are filed under (a feature has exactly one or none)."
|
|
2453
|
+
);
|
|
2454
|
+
areas.command("list").description("List a product's areas").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
2455
|
+
async (options, cmd) => {
|
|
2456
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2457
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2458
|
+
try {
|
|
2459
|
+
const client = getClient();
|
|
2460
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2461
|
+
const productId = await resolveProductId(client, workspaceId, options.product);
|
|
2462
|
+
const list = await client.areas.list({ productId });
|
|
2463
|
+
if (useJson) console.log(JSON.stringify({ areas: list, total: list.length }, null, 2));
|
|
2464
|
+
else outputAreasPretty(list);
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
handleError(error, useJson);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
);
|
|
2470
|
+
areas.command("create").description("Create an area in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-n, --name <name>", "Area name (unique per product)").option("-d, --description <text>", "What belongs in this area").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
2471
|
+
async (options, cmd) => {
|
|
2472
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2473
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2474
|
+
try {
|
|
2475
|
+
const client = getClient();
|
|
2476
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2477
|
+
const productId = await resolveProductId(client, workspaceId, options.product);
|
|
2478
|
+
const area = await client.areas.create({
|
|
2479
|
+
productId,
|
|
2480
|
+
name: options.name,
|
|
2481
|
+
description: options.description
|
|
2482
|
+
});
|
|
2483
|
+
if (useJson) console.log(JSON.stringify(area, null, 2));
|
|
2484
|
+
else console.log(`
|
|
2485
|
+
\u2713 Area created: ${area.name} (${area.id})`);
|
|
2486
|
+
} catch (error) {
|
|
2487
|
+
handleError(error, useJson);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
);
|
|
2491
|
+
return areas;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2061
2494
|
// src/commands/features.ts
|
|
2062
2495
|
var FEATURE_STATUSES = [
|
|
2063
2496
|
"IDEA",
|
|
2064
2497
|
"DEFINED",
|
|
2065
2498
|
"IN_PROGRESS",
|
|
2066
2499
|
"SHIPPED",
|
|
2500
|
+
"DEPRECATED",
|
|
2067
2501
|
"ARCHIVED"
|
|
2068
2502
|
];
|
|
2069
2503
|
function validateFeatureStatus(value) {
|
|
@@ -2076,7 +2510,7 @@ function validateFeatureStatus(value) {
|
|
|
2076
2510
|
return value;
|
|
2077
2511
|
}
|
|
2078
2512
|
function createFeaturesCommand() {
|
|
2079
|
-
const features = new
|
|
2513
|
+
const features = new Command10("features").description(
|
|
2080
2514
|
"Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
|
|
2081
2515
|
);
|
|
2082
2516
|
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(
|
|
@@ -2112,7 +2546,7 @@ function createFeaturesCommand() {
|
|
|
2112
2546
|
handleError(error, useJson);
|
|
2113
2547
|
}
|
|
2114
2548
|
});
|
|
2115
|
-
features.command("create").description("Create a new feature in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-n, --name <name>", "Feature name").option("-d, --description <text>", "Description (markdown supported)").option("--vision <text>", "Vision / target outcome").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
2549
|
+
features.command("create").description("Create a new feature in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-n, --name <name>", "Feature name").option("-d, --description <text>", "Description (markdown supported)").option("--vision <text>", "Vision / target outcome").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--area <id>", "Area CUID (per-product bucket) to file the feature under").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
2116
2550
|
async (options, cmd) => {
|
|
2117
2551
|
const globalOpts = cmd.optsWithGlobals();
|
|
2118
2552
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2132,7 +2566,8 @@ function createFeaturesCommand() {
|
|
|
2132
2566
|
vision: options.vision,
|
|
2133
2567
|
status,
|
|
2134
2568
|
priority: options.priority,
|
|
2135
|
-
effort: options.effort
|
|
2569
|
+
effort: options.effort,
|
|
2570
|
+
areaId: options.area
|
|
2136
2571
|
});
|
|
2137
2572
|
if (useJson) outputFeatureJson(feature);
|
|
2138
2573
|
else {
|
|
@@ -2144,7 +2579,7 @@ function createFeaturesCommand() {
|
|
|
2144
2579
|
}
|
|
2145
2580
|
}
|
|
2146
2581
|
);
|
|
2147
|
-
features.command("update").description("Update an existing feature").requiredOption("--id <id>", "Feature CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--vision <text>", "New vision text").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4", parseInt).option("--effort <n>", "Effort estimate", parseFloat).action(
|
|
2582
|
+
features.command("update").description("Update an existing feature").requiredOption("--id <id>", "Feature CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--vision <text>", "New vision text").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--area <id>", 'Area CUID ("none" to clear)').action(
|
|
2148
2583
|
async (options, cmd) => {
|
|
2149
2584
|
const globalOpts = cmd.optsWithGlobals();
|
|
2150
2585
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2158,7 +2593,8 @@ function createFeaturesCommand() {
|
|
|
2158
2593
|
vision: options.vision,
|
|
2159
2594
|
status,
|
|
2160
2595
|
priority: options.priority,
|
|
2161
|
-
effort: options.effort
|
|
2596
|
+
effort: options.effort,
|
|
2597
|
+
areaId: options.area === "none" ? null : options.area
|
|
2162
2598
|
});
|
|
2163
2599
|
if (useJson) outputFeatureJson(feature);
|
|
2164
2600
|
else {
|
|
@@ -2170,13 +2606,51 @@ function createFeaturesCommand() {
|
|
|
2170
2606
|
}
|
|
2171
2607
|
}
|
|
2172
2608
|
);
|
|
2609
|
+
features.command("link-page").description("Link a Knowledge page (PRD, spec, research) to a feature").requiredOption("--feature <id>", "Feature CUID").requiredOption("--page <id>", "Page CUID").option("--scope <id>", "Feature scope CUID to pin the page to").action(
|
|
2610
|
+
async (options, cmd) => {
|
|
2611
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2612
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2613
|
+
try {
|
|
2614
|
+
const client = getClient();
|
|
2615
|
+
const link = await client.features.linkPage({
|
|
2616
|
+
featureId: options.feature,
|
|
2617
|
+
pageId: options.page,
|
|
2618
|
+
scopeId: options.scope
|
|
2619
|
+
});
|
|
2620
|
+
if (useJson) console.log(JSON.stringify(link, null, 2));
|
|
2621
|
+
else console.log("\n\u2713 Page linked");
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
handleError(error, useJson);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
);
|
|
2627
|
+
features.command("unlink-page").description("Unlink a Knowledge page from a feature").requiredOption("--feature <id>", "Feature CUID").requiredOption("--page <id>", "Page CUID").action(
|
|
2628
|
+
async (options, cmd) => {
|
|
2629
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2630
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2631
|
+
try {
|
|
2632
|
+
const client = getClient();
|
|
2633
|
+
const result = await client.features.unlinkPage(
|
|
2634
|
+
options.feature,
|
|
2635
|
+
options.page
|
|
2636
|
+
);
|
|
2637
|
+
if (useJson) console.log(JSON.stringify(result, null, 2));
|
|
2638
|
+
else console.log("\n\u2713 Page unlinked");
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
handleError(error, useJson);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2173
2644
|
features.addCommand(createStoriesCommand());
|
|
2645
|
+
features.addCommand(createScopesCommand());
|
|
2646
|
+
features.addCommand(createRequirementsCommand());
|
|
2647
|
+
features.addCommand(createAreasCommand());
|
|
2174
2648
|
return features;
|
|
2175
2649
|
}
|
|
2176
2650
|
|
|
2177
2651
|
// src/commands/labels.ts
|
|
2178
|
-
import { Command as
|
|
2179
|
-
import
|
|
2652
|
+
import { Command as Command11 } from "commander";
|
|
2653
|
+
import chalk5 from "chalk";
|
|
2180
2654
|
var TRIAGE_LABELS = [
|
|
2181
2655
|
{ slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
|
|
2182
2656
|
{ slug: "needs-info", name: "needs-info", color: "avatar-yellow" },
|
|
@@ -2190,22 +2664,22 @@ function looksLikeCuid(value) {
|
|
|
2190
2664
|
}
|
|
2191
2665
|
function outputTagsPretty(tags) {
|
|
2192
2666
|
if (tags.length === 0) {
|
|
2193
|
-
console.log(
|
|
2667
|
+
console.log(chalk5.dim("No labels found."));
|
|
2194
2668
|
return;
|
|
2195
2669
|
}
|
|
2196
2670
|
console.log();
|
|
2197
2671
|
for (const tag of tags) {
|
|
2198
|
-
const scope = tag.workspaceId === null ?
|
|
2199
|
-
const category = tag.category === null ?
|
|
2672
|
+
const scope = tag.workspaceId === null ? chalk5.cyan("global") : chalk5.magenta("workspace");
|
|
2673
|
+
const category = tag.category === null ? chalk5.dim("(label)") : chalk5.dim(`(${tag.category})`);
|
|
2200
2674
|
console.log(
|
|
2201
|
-
` ${
|
|
2675
|
+
` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)} ${scope} ${category}`
|
|
2202
2676
|
);
|
|
2203
2677
|
}
|
|
2204
2678
|
console.log();
|
|
2205
2679
|
}
|
|
2206
2680
|
function outputTagPretty(tag, prefix) {
|
|
2207
2681
|
if (prefix) console.log(prefix);
|
|
2208
|
-
console.log(` ${
|
|
2682
|
+
console.log(` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)}`);
|
|
2209
2683
|
}
|
|
2210
2684
|
async function resolveLabelIds(client, workspaceId, values) {
|
|
2211
2685
|
if (values.length === 0) return [];
|
|
@@ -2229,7 +2703,7 @@ async function resolveLabelIds(client, workspaceId, values) {
|
|
|
2229
2703
|
return [...byId, ...resolvedFromSlugs];
|
|
2230
2704
|
}
|
|
2231
2705
|
function createLabelsCommand() {
|
|
2232
|
-
const labels = new
|
|
2706
|
+
const labels = new Command11("labels").description(
|
|
2233
2707
|
"Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
|
|
2234
2708
|
);
|
|
2235
2709
|
labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
|
|
@@ -2277,7 +2751,7 @@ function createLabelsCommand() {
|
|
|
2277
2751
|
if (useJson) {
|
|
2278
2752
|
console.log(JSON.stringify(tag, null, 2));
|
|
2279
2753
|
} else {
|
|
2280
|
-
outputTagPretty(tag,
|
|
2754
|
+
outputTagPretty(tag, chalk5.green("\n\u2713 Label created"));
|
|
2281
2755
|
}
|
|
2282
2756
|
} catch (error) {
|
|
2283
2757
|
handleError(error, useJson);
|
|
@@ -2315,10 +2789,10 @@ function createLabelsCommand() {
|
|
|
2315
2789
|
console.log(JSON.stringify(results, null, 2));
|
|
2316
2790
|
} else {
|
|
2317
2791
|
console.log();
|
|
2318
|
-
console.log(
|
|
2792
|
+
console.log(chalk5.bold("Triage labels:"));
|
|
2319
2793
|
for (const r of results) {
|
|
2320
|
-
const tag =
|
|
2321
|
-
const status = r.status === "created" ?
|
|
2794
|
+
const tag = chalk5.bold(r.slug);
|
|
2795
|
+
const status = r.status === "created" ? chalk5.green("created") : chalk5.dim("existed");
|
|
2322
2796
|
console.log(` ${tag} ${status}`);
|
|
2323
2797
|
}
|
|
2324
2798
|
console.log();
|
|
@@ -2332,7 +2806,7 @@ function createLabelsCommand() {
|
|
|
2332
2806
|
}
|
|
2333
2807
|
|
|
2334
2808
|
// src/commands/organizations.ts
|
|
2335
|
-
import { Command as
|
|
2809
|
+
import { Command as Command12 } from "commander";
|
|
2336
2810
|
var VALID_SIZES = [
|
|
2337
2811
|
"1-10",
|
|
2338
2812
|
"11-50",
|
|
@@ -2342,7 +2816,7 @@ var VALID_SIZES = [
|
|
|
2342
2816
|
"1000+"
|
|
2343
2817
|
];
|
|
2344
2818
|
function createOrganizationsCommand() {
|
|
2345
|
-
const organizations = new
|
|
2819
|
+
const organizations = new Command12("organizations").alias("orgs").description("Manage CRM organizations");
|
|
2346
2820
|
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) => {
|
|
2347
2821
|
const globalOpts = cmd.optsWithGlobals();
|
|
2348
2822
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2409,10 +2883,101 @@ function createOrganizationsCommand() {
|
|
|
2409
2883
|
return organizations;
|
|
2410
2884
|
}
|
|
2411
2885
|
|
|
2886
|
+
// src/commands/pages.ts
|
|
2887
|
+
import { readFileSync } from "fs";
|
|
2888
|
+
import { Command as Command13 } from "commander";
|
|
2889
|
+
function readBody(body, bodyFile) {
|
|
2890
|
+
if (body !== void 0) return body;
|
|
2891
|
+
if (bodyFile === void 0) return void 0;
|
|
2892
|
+
if (bodyFile === "-") return readFileSync(0, "utf-8");
|
|
2893
|
+
return readFileSync(bodyFile, "utf-8");
|
|
2894
|
+
}
|
|
2895
|
+
function createPagesCommand() {
|
|
2896
|
+
const pages = new Command13("pages").description(
|
|
2897
|
+
'Manage Knowledge pages (PRDs, research, technical specs). Link one to a feature with "features link-page".'
|
|
2898
|
+
);
|
|
2899
|
+
pages.command("list").description("List pages in a workspace").option("--workspace <slug|id>", "Workspace (defaults to the configured default)").option("--search <text>", "Filter by title/content").action(
|
|
2900
|
+
async (options, cmd) => {
|
|
2901
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2902
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2903
|
+
try {
|
|
2904
|
+
const client = getClient();
|
|
2905
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2906
|
+
const list = await client.pages.list({
|
|
2907
|
+
workspaceId,
|
|
2908
|
+
search: options.search
|
|
2909
|
+
});
|
|
2910
|
+
if (useJson) outputPagesJson(list);
|
|
2911
|
+
else outputPagesPretty(list);
|
|
2912
|
+
} catch (error) {
|
|
2913
|
+
handleError(error, useJson);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
);
|
|
2917
|
+
pages.command("get <id>").description("Get a page by CUID (body is Markdown)").action(async (id, _options, cmd) => {
|
|
2918
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2919
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2920
|
+
try {
|
|
2921
|
+
const client = getClient();
|
|
2922
|
+
const page = await client.pages.get(id);
|
|
2923
|
+
if (useJson) outputPageJson(page);
|
|
2924
|
+
else outputPagePretty(page);
|
|
2925
|
+
} catch (error) {
|
|
2926
|
+
handleError(error, useJson);
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
pages.command("create").description("Create a page. Body: -b text, --body-file <path>, or --body-file - for stdin.").option("--workspace <slug|id>", "Workspace (defaults to the configured default)").requiredOption("-t, --title <title>", "Page title").option("-b, --body <markdown>", "Markdown body").option("--body-file <path>", 'Read the Markdown body from a file ("-" = stdin)').action(
|
|
2930
|
+
async (options, cmd) => {
|
|
2931
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2932
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2933
|
+
try {
|
|
2934
|
+
const client = getClient();
|
|
2935
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2936
|
+
const page = await client.pages.create({
|
|
2937
|
+
workspaceId,
|
|
2938
|
+
title: options.title,
|
|
2939
|
+
body: readBody(options.body, options.bodyFile)
|
|
2940
|
+
});
|
|
2941
|
+
if (useJson) outputPageJson(page);
|
|
2942
|
+
else {
|
|
2943
|
+
console.log("\n\u2713 Page created");
|
|
2944
|
+
outputPagePretty(page);
|
|
2945
|
+
}
|
|
2946
|
+
} catch (error) {
|
|
2947
|
+
handleError(error, useJson);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
);
|
|
2951
|
+
pages.command("update").description(
|
|
2952
|
+
"Update a page. Sending a body is a Markdown-source write: the Markdown becomes canonical."
|
|
2953
|
+
).requiredOption("--id <id>", "Page CUID").option("-t, --title <title>", "New title").option("-b, --body <markdown>", "New Markdown body (replaces the whole body)").option("--body-file <path>", 'Read the new Markdown body from a file ("-" = stdin)').action(
|
|
2954
|
+
async (options, cmd) => {
|
|
2955
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2956
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2957
|
+
try {
|
|
2958
|
+
const client = getClient();
|
|
2959
|
+
const page = await client.pages.update({
|
|
2960
|
+
id: options.id,
|
|
2961
|
+
title: options.title,
|
|
2962
|
+
body: readBody(options.body, options.bodyFile)
|
|
2963
|
+
});
|
|
2964
|
+
if (useJson) outputPageJson(page);
|
|
2965
|
+
else {
|
|
2966
|
+
console.log("\n\u2713 Page updated");
|
|
2967
|
+
outputPagePretty(page);
|
|
2968
|
+
}
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
handleError(error, useJson);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
return pages;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2412
2977
|
// src/commands/products.ts
|
|
2413
|
-
import { Command as
|
|
2978
|
+
import { Command as Command14 } from "commander";
|
|
2414
2979
|
function createProductsCommand() {
|
|
2415
|
-
const products = new
|
|
2980
|
+
const products = new Command14("products").description(
|
|
2416
2981
|
"Manage products. A workspace contains products; a product holds features and tickets."
|
|
2417
2982
|
);
|
|
2418
2983
|
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) => {
|
|
@@ -2475,9 +3040,9 @@ function createProductsCommand() {
|
|
|
2475
3040
|
}
|
|
2476
3041
|
|
|
2477
3042
|
// src/commands/projects.ts
|
|
2478
|
-
import { Command as
|
|
3043
|
+
import { Command as Command15 } from "commander";
|
|
2479
3044
|
function createProjectsCommand() {
|
|
2480
|
-
const projects = new
|
|
3045
|
+
const projects = new Command15("projects").description("Manage projects");
|
|
2481
3046
|
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) => {
|
|
2482
3047
|
const globalOpts = cmd.optsWithGlobals();
|
|
2483
3048
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2499,8 +3064,86 @@ function createProjectsCommand() {
|
|
|
2499
3064
|
return projects;
|
|
2500
3065
|
}
|
|
2501
3066
|
|
|
3067
|
+
// src/commands/search.ts
|
|
3068
|
+
import { Command as Command16 } from "commander";
|
|
3069
|
+
import chalk6 from "chalk";
|
|
3070
|
+
var TYPE_LABELS = {
|
|
3071
|
+
workspace: "Workspaces",
|
|
3072
|
+
project: "Projects",
|
|
3073
|
+
action: "Actions",
|
|
3074
|
+
goal: "Goals",
|
|
3075
|
+
keyResult: "Key Results",
|
|
3076
|
+
outcome: "Outcomes",
|
|
3077
|
+
ticket: "Tickets",
|
|
3078
|
+
feature: "Features",
|
|
3079
|
+
epic: "Epics",
|
|
3080
|
+
page: "Pages",
|
|
3081
|
+
meeting: "Meetings",
|
|
3082
|
+
contact: "Contacts",
|
|
3083
|
+
organization: "Organizations",
|
|
3084
|
+
product: "Products"
|
|
3085
|
+
};
|
|
3086
|
+
function createSearchCommand() {
|
|
3087
|
+
const search = new Command16("search").description(
|
|
3088
|
+
"Global search across projects, actions, goals, workspaces and more \u2014 same coverage as the app's Cmd+K palette"
|
|
3089
|
+
).argument("<query>", "Text to search for").option("-w, --workspace <slug>", "Restrict results to one workspace (slug or ID)").option("-l, --limit <n>", "Max results per entity type (1-25, default 10)").action(async (query, options, cmd) => {
|
|
3090
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3091
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3092
|
+
try {
|
|
3093
|
+
const client = getClient();
|
|
3094
|
+
let workspaceId;
|
|
3095
|
+
if (options.workspace) {
|
|
3096
|
+
const workspaces = await client.workspaces.list();
|
|
3097
|
+
const match = workspaces.find(
|
|
3098
|
+
(w) => w.slug === options.workspace || w.id === options.workspace
|
|
3099
|
+
);
|
|
3100
|
+
if (!match) {
|
|
3101
|
+
console.error(chalk6.red(`Workspace "${options.workspace}" not found.`));
|
|
3102
|
+
console.log(chalk6.gray("Available workspaces:"));
|
|
3103
|
+
workspaces.forEach((w) => {
|
|
3104
|
+
console.log(chalk6.gray(` - ${w.slug} (${w.name})`));
|
|
3105
|
+
});
|
|
3106
|
+
process.exit(1);
|
|
3107
|
+
}
|
|
3108
|
+
workspaceId = match.id;
|
|
3109
|
+
}
|
|
3110
|
+
const limit = options.limit ? Number(options.limit) : void 0;
|
|
3111
|
+
const result = await client.search.global({ query, workspaceId, limit });
|
|
3112
|
+
if (useJson) {
|
|
3113
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
if (result.results.length === 0) {
|
|
3117
|
+
console.log(chalk6.gray(`No matches for "${result.query}".`));
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
const byType = /* @__PURE__ */ new Map();
|
|
3121
|
+
for (const r of result.results) {
|
|
3122
|
+
const bucket = byType.get(r.type) ?? [];
|
|
3123
|
+
bucket.push(r);
|
|
3124
|
+
byType.set(r.type, bucket);
|
|
3125
|
+
}
|
|
3126
|
+
for (const [type, rows] of byType) {
|
|
3127
|
+
console.log(chalk6.bold(`
|
|
3128
|
+
${TYPE_LABELS[type] ?? type}`));
|
|
3129
|
+
for (const row of rows) {
|
|
3130
|
+
const parts = [chalk6.white(row.title)];
|
|
3131
|
+
if (row.subtitle) parts.push(chalk6.gray(row.subtitle));
|
|
3132
|
+
if (row.workspace) parts.push(chalk6.cyan(`@${row.workspace.slug}`));
|
|
3133
|
+
console.log(` ${parts.join(chalk6.gray(" \xB7 "))}`);
|
|
3134
|
+
console.log(chalk6.gray(` ${row.id}${row.url ? ` ${row.url}` : ""}`));
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
console.log();
|
|
3138
|
+
} catch (error) {
|
|
3139
|
+
handleError(error, useJson);
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
return search;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
2502
3145
|
// src/commands/tickets.ts
|
|
2503
|
-
import { Command as
|
|
3146
|
+
import { Command as Command17 } from "commander";
|
|
2504
3147
|
function collectRepeatable(value, previous) {
|
|
2505
3148
|
return [...previous, value];
|
|
2506
3149
|
}
|
|
@@ -2543,7 +3186,7 @@ function validateTicketType(value) {
|
|
|
2543
3186
|
return value;
|
|
2544
3187
|
}
|
|
2545
3188
|
function createTicketsCommand() {
|
|
2546
|
-
const tickets = new
|
|
3189
|
+
const tickets = new Command17("tickets").description(
|
|
2547
3190
|
"Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
|
|
2548
3191
|
);
|
|
2549
3192
|
tickets.command("list").description(
|
|
@@ -2646,7 +3289,7 @@ function createTicketsCommand() {
|
|
|
2646
3289
|
handleError(error, useJson);
|
|
2647
3290
|
}
|
|
2648
3291
|
});
|
|
2649
|
-
tickets.command("create").description("Create a new ticket in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-t, --title <title>", "Ticket title").option("-b, --body <text>", "Body (markdown supported)").option("--type <type>", `Ticket type: ${TICKET_TYPES.join(", ")}`).option("--status <status>", `Initial status: ${TICKET_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--points <n>", "Story points", parseFloat).option("--feature <id>", "Feature CUID to attach to").option("--epic <id>", "Epic CUID to attach to").option("--assignee <id>", "Assignee user ID").option("--branch <name>", "Branch name").option("--pr <url>", "Pull request URL").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option(
|
|
3292
|
+
tickets.command("create").description("Create a new ticket in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-t, --title <title>", "Ticket title").option("-b, --body <text>", "Body (markdown supported)").option("--type <type>", `Ticket type: ${TICKET_TYPES.join(", ")}`).option("--status <status>", `Initial status: ${TICKET_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--points <n>", "Story points", parseFloat).option("--feature <id>", "Feature CUID to attach to").option("--scope <id>", "Feature scope CUID to attach to (requires --feature)").option("--epic <id>", "Epic CUID to attach to").option("--assignee <id>", "Assignee user ID").option("--branch <name>", "Branch name").option("--pr <url>", "Pull request URL").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option(
|
|
2650
3293
|
"--label <slug-or-id>",
|
|
2651
3294
|
"Label to attach (repeatable)",
|
|
2652
3295
|
collectRepeatable,
|
|
@@ -2674,6 +3317,7 @@ function createTicketsCommand() {
|
|
|
2674
3317
|
priority: options.priority,
|
|
2675
3318
|
points: options.points,
|
|
2676
3319
|
featureId: options.feature,
|
|
3320
|
+
scopeId: options.scope,
|
|
2677
3321
|
epicId: options.epic,
|
|
2678
3322
|
assigneeId: options.assignee,
|
|
2679
3323
|
branchName: options.branch,
|
|
@@ -2856,7 +3500,7 @@ function createTicketsCommand() {
|
|
|
2856
3500
|
handleError(error, useJson);
|
|
2857
3501
|
}
|
|
2858
3502
|
});
|
|
2859
|
-
const comment = new
|
|
3503
|
+
const comment = new Command17("comment").description("Manage comments on a ticket");
|
|
2860
3504
|
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) => {
|
|
2861
3505
|
const globalOpts = cmd.optsWithGlobals();
|
|
2862
3506
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2895,10 +3539,10 @@ function createTicketsCommand() {
|
|
|
2895
3539
|
}
|
|
2896
3540
|
|
|
2897
3541
|
// src/commands/workspaces.ts
|
|
2898
|
-
import { Command as
|
|
2899
|
-
import
|
|
3542
|
+
import { Command as Command18 } from "commander";
|
|
3543
|
+
import chalk7 from "chalk";
|
|
2900
3544
|
function createWorkspacesCommand() {
|
|
2901
|
-
const workspaces = new
|
|
3545
|
+
const workspaces = new Command18("workspaces").description("Manage workspaces");
|
|
2902
3546
|
workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
|
|
2903
3547
|
const globalOpts = cmd.optsWithGlobals();
|
|
2904
3548
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2911,7 +3555,7 @@ function createWorkspacesCommand() {
|
|
|
2911
3555
|
const config = getConfig();
|
|
2912
3556
|
outputWorkspacesPretty(workspaces2);
|
|
2913
3557
|
if (config.defaultWorkspaceSlug) {
|
|
2914
|
-
console.log(
|
|
3558
|
+
console.log(chalk7.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
|
|
2915
3559
|
}
|
|
2916
3560
|
}
|
|
2917
3561
|
} catch (error) {
|
|
@@ -2924,10 +3568,10 @@ function createWorkspacesCommand() {
|
|
|
2924
3568
|
const workspaces2 = await client.workspaces.list();
|
|
2925
3569
|
const workspace = workspaces2.find((w) => w.slug === slug);
|
|
2926
3570
|
if (!workspace) {
|
|
2927
|
-
console.error(
|
|
2928
|
-
console.log(
|
|
3571
|
+
console.error(chalk7.red(`Workspace with slug "${slug}" not found.`));
|
|
3572
|
+
console.log(chalk7.gray("Available workspaces:"));
|
|
2929
3573
|
workspaces2.forEach((w) => {
|
|
2930
|
-
console.log(
|
|
3574
|
+
console.log(chalk7.gray(` - ${w.slug} (${w.name})`));
|
|
2931
3575
|
});
|
|
2932
3576
|
process.exit(1);
|
|
2933
3577
|
}
|
|
@@ -2935,7 +3579,7 @@ function createWorkspacesCommand() {
|
|
|
2935
3579
|
defaultWorkspaceId: workspace.id,
|
|
2936
3580
|
defaultWorkspaceSlug: workspace.slug
|
|
2937
3581
|
});
|
|
2938
|
-
console.log(
|
|
3582
|
+
console.log(chalk7.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
|
|
2939
3583
|
} catch (error) {
|
|
2940
3584
|
handleError(error);
|
|
2941
3585
|
}
|
|
@@ -2944,7 +3588,7 @@ function createWorkspacesCommand() {
|
|
|
2944
3588
|
}
|
|
2945
3589
|
|
|
2946
3590
|
// src/index.ts
|
|
2947
|
-
var program = new
|
|
3591
|
+
var program = new Command19();
|
|
2948
3592
|
program.name("exponential").description(
|
|
2949
3593
|
[
|
|
2950
3594
|
"CLI to interact with Exponential productivity app.",
|
|
@@ -2962,8 +3606,10 @@ program.addCommand(createEpicsCommand());
|
|
|
2962
3606
|
program.addCommand(createFeaturesCommand());
|
|
2963
3607
|
program.addCommand(createLabelsCommand());
|
|
2964
3608
|
program.addCommand(createOrganizationsCommand());
|
|
3609
|
+
program.addCommand(createPagesCommand());
|
|
2965
3610
|
program.addCommand(createProductsCommand());
|
|
2966
3611
|
program.addCommand(createProjectsCommand());
|
|
3612
|
+
program.addCommand(createSearchCommand());
|
|
2967
3613
|
program.addCommand(createTicketsCommand());
|
|
2968
3614
|
program.addCommand(createWorkspacesCommand());
|
|
2969
3615
|
program.parse();
|