exponential-cli 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command14 } from "commander";
4
+ import { Command as Command18 } 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 = [
@@ -1582,6 +1703,28 @@ function createContactsCommand() {
1582
1703
  handleError(error, useJson);
1583
1704
  }
1584
1705
  });
1706
+ contacts.command("enrich").description("Queue a web-search enrichment job for an existing contact (runs regardless of the workspace auto-enrich setting)").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
1707
+ const globalOpts = cmd.optsWithGlobals();
1708
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1709
+ try {
1710
+ const client = getClient();
1711
+ const result = await client.contacts.enrich(id);
1712
+ if (useJson) {
1713
+ console.log(JSON.stringify(result, null, 2));
1714
+ } else if (result.enqueued) {
1715
+ console.log(`
1716
+ \u2713 Enrichment queued for contact ${id} (job ${result.enrichmentId})`);
1717
+ } else if (result.reason === "already-in-flight") {
1718
+ console.log(`
1719
+ \u2022 Enrichment already in progress for contact ${id} (job ${result.enrichmentId})`);
1720
+ } else {
1721
+ console.log(`
1722
+ \u2022 Not queued for contact ${id}${result.reason ? ` (${result.reason})` : ""}`);
1723
+ }
1724
+ } catch (error) {
1725
+ handleError(error, useJson);
1726
+ }
1727
+ });
1585
1728
  return contacts;
1586
1729
  }
1587
1730
 
@@ -1927,7 +2070,7 @@ function createEpicsCommand() {
1927
2070
  }
1928
2071
 
1929
2072
  // src/commands/features.ts
1930
- import { Command as Command7 } from "commander";
2073
+ import { Command as Command10 } from "commander";
1931
2074
 
1932
2075
  // src/commands/stories.ts
1933
2076
  import { Command as Command6 } from "commander";
@@ -2036,12 +2179,325 @@ function createStoriesCommand() {
2036
2179
  return stories;
2037
2180
  }
2038
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
+
2039
2494
  // src/commands/features.ts
2040
2495
  var FEATURE_STATUSES = [
2041
2496
  "IDEA",
2042
2497
  "DEFINED",
2043
2498
  "IN_PROGRESS",
2044
2499
  "SHIPPED",
2500
+ "DEPRECATED",
2045
2501
  "ARCHIVED"
2046
2502
  ];
2047
2503
  function validateFeatureStatus(value) {
@@ -2054,7 +2510,7 @@ function validateFeatureStatus(value) {
2054
2510
  return value;
2055
2511
  }
2056
2512
  function createFeaturesCommand() {
2057
- const features = new Command7("features").description(
2513
+ const features = new Command10("features").description(
2058
2514
  "Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
2059
2515
  );
2060
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(
@@ -2090,7 +2546,7 @@ function createFeaturesCommand() {
2090
2546
  handleError(error, useJson);
2091
2547
  }
2092
2548
  });
2093
- 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(
2094
2550
  async (options, cmd) => {
2095
2551
  const globalOpts = cmd.optsWithGlobals();
2096
2552
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2110,7 +2566,8 @@ function createFeaturesCommand() {
2110
2566
  vision: options.vision,
2111
2567
  status,
2112
2568
  priority: options.priority,
2113
- effort: options.effort
2569
+ effort: options.effort,
2570
+ areaId: options.area
2114
2571
  });
2115
2572
  if (useJson) outputFeatureJson(feature);
2116
2573
  else {
@@ -2122,7 +2579,7 @@ function createFeaturesCommand() {
2122
2579
  }
2123
2580
  }
2124
2581
  );
2125
- 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(
2126
2583
  async (options, cmd) => {
2127
2584
  const globalOpts = cmd.optsWithGlobals();
2128
2585
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2136,7 +2593,8 @@ function createFeaturesCommand() {
2136
2593
  vision: options.vision,
2137
2594
  status,
2138
2595
  priority: options.priority,
2139
- effort: options.effort
2596
+ effort: options.effort,
2597
+ areaId: options.area === "none" ? null : options.area
2140
2598
  });
2141
2599
  if (useJson) outputFeatureJson(feature);
2142
2600
  else {
@@ -2148,13 +2606,51 @@ function createFeaturesCommand() {
2148
2606
  }
2149
2607
  }
2150
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
+ );
2151
2644
  features.addCommand(createStoriesCommand());
2645
+ features.addCommand(createScopesCommand());
2646
+ features.addCommand(createRequirementsCommand());
2647
+ features.addCommand(createAreasCommand());
2152
2648
  return features;
2153
2649
  }
2154
2650
 
2155
2651
  // src/commands/labels.ts
2156
- import { Command as Command8 } from "commander";
2157
- import chalk4 from "chalk";
2652
+ import { Command as Command11 } from "commander";
2653
+ import chalk5 from "chalk";
2158
2654
  var TRIAGE_LABELS = [
2159
2655
  { slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
2160
2656
  { slug: "needs-info", name: "needs-info", color: "avatar-yellow" },
@@ -2168,22 +2664,22 @@ function looksLikeCuid(value) {
2168
2664
  }
2169
2665
  function outputTagsPretty(tags) {
2170
2666
  if (tags.length === 0) {
2171
- console.log(chalk4.dim("No labels found."));
2667
+ console.log(chalk5.dim("No labels found."));
2172
2668
  return;
2173
2669
  }
2174
2670
  console.log();
2175
2671
  for (const tag of tags) {
2176
- const scope = tag.workspaceId === null ? chalk4.cyan("global") : chalk4.magenta("workspace");
2177
- const category = tag.category === null ? chalk4.dim("(label)") : chalk4.dim(`(${tag.category})`);
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})`);
2178
2674
  console.log(
2179
- ` ${chalk4.bold(tag.slug)} ${tag.name} ${chalk4.gray(tag.color)} ${scope} ${category}`
2675
+ ` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)} ${scope} ${category}`
2180
2676
  );
2181
2677
  }
2182
2678
  console.log();
2183
2679
  }
2184
2680
  function outputTagPretty(tag, prefix) {
2185
2681
  if (prefix) console.log(prefix);
2186
- console.log(` ${chalk4.bold(tag.slug)} ${tag.name} ${chalk4.gray(tag.color)}`);
2682
+ console.log(` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)}`);
2187
2683
  }
2188
2684
  async function resolveLabelIds(client, workspaceId, values) {
2189
2685
  if (values.length === 0) return [];
@@ -2207,7 +2703,7 @@ async function resolveLabelIds(client, workspaceId, values) {
2207
2703
  return [...byId, ...resolvedFromSlugs];
2208
2704
  }
2209
2705
  function createLabelsCommand() {
2210
- const labels = new Command8("labels").description(
2706
+ const labels = new Command11("labels").description(
2211
2707
  "Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
2212
2708
  );
2213
2709
  labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
@@ -2255,7 +2751,7 @@ function createLabelsCommand() {
2255
2751
  if (useJson) {
2256
2752
  console.log(JSON.stringify(tag, null, 2));
2257
2753
  } else {
2258
- outputTagPretty(tag, chalk4.green("\n\u2713 Label created"));
2754
+ outputTagPretty(tag, chalk5.green("\n\u2713 Label created"));
2259
2755
  }
2260
2756
  } catch (error) {
2261
2757
  handleError(error, useJson);
@@ -2293,10 +2789,10 @@ function createLabelsCommand() {
2293
2789
  console.log(JSON.stringify(results, null, 2));
2294
2790
  } else {
2295
2791
  console.log();
2296
- console.log(chalk4.bold("Triage labels:"));
2792
+ console.log(chalk5.bold("Triage labels:"));
2297
2793
  for (const r of results) {
2298
- const tag = chalk4.bold(r.slug);
2299
- const status = r.status === "created" ? chalk4.green("created") : chalk4.dim("existed");
2794
+ const tag = chalk5.bold(r.slug);
2795
+ const status = r.status === "created" ? chalk5.green("created") : chalk5.dim("existed");
2300
2796
  console.log(` ${tag} ${status}`);
2301
2797
  }
2302
2798
  console.log();
@@ -2310,7 +2806,7 @@ function createLabelsCommand() {
2310
2806
  }
2311
2807
 
2312
2808
  // src/commands/organizations.ts
2313
- import { Command as Command9 } from "commander";
2809
+ import { Command as Command12 } from "commander";
2314
2810
  var VALID_SIZES = [
2315
2811
  "1-10",
2316
2812
  "11-50",
@@ -2320,7 +2816,7 @@ var VALID_SIZES = [
2320
2816
  "1000+"
2321
2817
  ];
2322
2818
  function createOrganizationsCommand() {
2323
- const organizations = new Command9("organizations").alias("orgs").description("Manage CRM organizations");
2819
+ const organizations = new Command12("organizations").alias("orgs").description("Manage CRM organizations");
2324
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) => {
2325
2821
  const globalOpts = cmd.optsWithGlobals();
2326
2822
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2387,10 +2883,101 @@ function createOrganizationsCommand() {
2387
2883
  return organizations;
2388
2884
  }
2389
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
+
2390
2977
  // src/commands/products.ts
2391
- import { Command as Command10 } from "commander";
2978
+ import { Command as Command14 } from "commander";
2392
2979
  function createProductsCommand() {
2393
- const products = new Command10("products").description(
2980
+ const products = new Command14("products").description(
2394
2981
  "Manage products. A workspace contains products; a product holds features and tickets."
2395
2982
  );
2396
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) => {
@@ -2453,9 +3040,9 @@ function createProductsCommand() {
2453
3040
  }
2454
3041
 
2455
3042
  // src/commands/projects.ts
2456
- import { Command as Command11 } from "commander";
3043
+ import { Command as Command15 } from "commander";
2457
3044
  function createProjectsCommand() {
2458
- const projects = new Command11("projects").description("Manage projects");
3045
+ const projects = new Command15("projects").description("Manage projects");
2459
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) => {
2460
3047
  const globalOpts = cmd.optsWithGlobals();
2461
3048
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2478,7 +3065,7 @@ function createProjectsCommand() {
2478
3065
  }
2479
3066
 
2480
3067
  // src/commands/tickets.ts
2481
- import { Command as Command12 } from "commander";
3068
+ import { Command as Command16 } from "commander";
2482
3069
  function collectRepeatable(value, previous) {
2483
3070
  return [...previous, value];
2484
3071
  }
@@ -2521,7 +3108,7 @@ function validateTicketType(value) {
2521
3108
  return value;
2522
3109
  }
2523
3110
  function createTicketsCommand() {
2524
- const tickets = new Command12("tickets").description(
3111
+ const tickets = new Command16("tickets").description(
2525
3112
  "Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
2526
3113
  );
2527
3114
  tickets.command("list").description(
@@ -2624,7 +3211,7 @@ function createTicketsCommand() {
2624
3211
  handleError(error, useJson);
2625
3212
  }
2626
3213
  });
2627
- 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(
3214
+ 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(
2628
3215
  "--label <slug-or-id>",
2629
3216
  "Label to attach (repeatable)",
2630
3217
  collectRepeatable,
@@ -2652,6 +3239,7 @@ function createTicketsCommand() {
2652
3239
  priority: options.priority,
2653
3240
  points: options.points,
2654
3241
  featureId: options.feature,
3242
+ scopeId: options.scope,
2655
3243
  epicId: options.epic,
2656
3244
  assigneeId: options.assignee,
2657
3245
  branchName: options.branch,
@@ -2834,7 +3422,7 @@ function createTicketsCommand() {
2834
3422
  handleError(error, useJson);
2835
3423
  }
2836
3424
  });
2837
- const comment = new Command12("comment").description("Manage comments on a ticket");
3425
+ const comment = new Command16("comment").description("Manage comments on a ticket");
2838
3426
  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) => {
2839
3427
  const globalOpts = cmd.optsWithGlobals();
2840
3428
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2873,10 +3461,10 @@ function createTicketsCommand() {
2873
3461
  }
2874
3462
 
2875
3463
  // src/commands/workspaces.ts
2876
- import { Command as Command13 } from "commander";
2877
- import chalk5 from "chalk";
3464
+ import { Command as Command17 } from "commander";
3465
+ import chalk6 from "chalk";
2878
3466
  function createWorkspacesCommand() {
2879
- const workspaces = new Command13("workspaces").description("Manage workspaces");
3467
+ const workspaces = new Command17("workspaces").description("Manage workspaces");
2880
3468
  workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
2881
3469
  const globalOpts = cmd.optsWithGlobals();
2882
3470
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2889,7 +3477,7 @@ function createWorkspacesCommand() {
2889
3477
  const config = getConfig();
2890
3478
  outputWorkspacesPretty(workspaces2);
2891
3479
  if (config.defaultWorkspaceSlug) {
2892
- console.log(chalk5.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
3480
+ console.log(chalk6.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
2893
3481
  }
2894
3482
  }
2895
3483
  } catch (error) {
@@ -2902,10 +3490,10 @@ function createWorkspacesCommand() {
2902
3490
  const workspaces2 = await client.workspaces.list();
2903
3491
  const workspace = workspaces2.find((w) => w.slug === slug);
2904
3492
  if (!workspace) {
2905
- console.error(chalk5.red(`Workspace with slug "${slug}" not found.`));
2906
- console.log(chalk5.gray("Available workspaces:"));
3493
+ console.error(chalk6.red(`Workspace with slug "${slug}" not found.`));
3494
+ console.log(chalk6.gray("Available workspaces:"));
2907
3495
  workspaces2.forEach((w) => {
2908
- console.log(chalk5.gray(` - ${w.slug} (${w.name})`));
3496
+ console.log(chalk6.gray(` - ${w.slug} (${w.name})`));
2909
3497
  });
2910
3498
  process.exit(1);
2911
3499
  }
@@ -2913,7 +3501,7 @@ function createWorkspacesCommand() {
2913
3501
  defaultWorkspaceId: workspace.id,
2914
3502
  defaultWorkspaceSlug: workspace.slug
2915
3503
  });
2916
- console.log(chalk5.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
3504
+ console.log(chalk6.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
2917
3505
  } catch (error) {
2918
3506
  handleError(error);
2919
3507
  }
@@ -2922,7 +3510,7 @@ function createWorkspacesCommand() {
2922
3510
  }
2923
3511
 
2924
3512
  // src/index.ts
2925
- var program = new Command14();
3513
+ var program = new Command18();
2926
3514
  program.name("exponential").description(
2927
3515
  [
2928
3516
  "CLI to interact with Exponential productivity app.",
@@ -2940,6 +3528,7 @@ program.addCommand(createEpicsCommand());
2940
3528
  program.addCommand(createFeaturesCommand());
2941
3529
  program.addCommand(createLabelsCommand());
2942
3530
  program.addCommand(createOrganizationsCommand());
3531
+ program.addCommand(createPagesCommand());
2943
3532
  program.addCommand(createProductsCommand());
2944
3533
  program.addCommand(createProjectsCommand());
2945
3534
  program.addCommand(createTicketsCommand());