@braingrid/cli 0.2.12 → 0.2.15

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/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.15] - 2025-12-15
11
+
12
+ ### Changed
13
+
14
+ - **CI/CD: Switch to OIDC-based npm publishing**
15
+ - GitHub Actions workflow now uses OpenID Connect (Trusted Publishing) for npm authentication
16
+ - No longer requires NPM_TOKEN secret - authentication is handled via OIDC
17
+ - More secure and eliminates token expiration issues
18
+
19
+ ## [0.2.14] - 2025-12-15
20
+
21
+ ### Added
22
+
23
+ - **Requirement auto-detection for task commands**
24
+ - Task commands now auto-detect the requirement ID from git branch names (e.g., `feature/REQ-123-something` → `REQ-123`)
25
+ - Matches the existing behavior of requirement commands
26
+ - Applies to all task commands: `list`, `summary`, `show`, `create`, `update`, `delete`
27
+ - Explicit `-r/--requirement` flag still works and takes precedence over auto-detection
28
+
29
+ ## [0.2.13] - 2025-12-08
30
+
31
+ ### Added
32
+
33
+ - **Shell tab completion support**
34
+ - Added `braingrid completion` command to generate shell completion scripts
35
+ - Support for bash and zsh shells (fish support planned for future release)
36
+ - Automated setup with `braingrid completion --setup` flag
37
+ - Auto-detects shell from `$SHELL` environment variable
38
+ - Completes commands, subcommands, options, and values
39
+ - Includes status values (IDEA, PLANNED, IN_PROGRESS, REVIEW, COMPLETED, CANCELLED)
40
+ - Includes format options (table, json, xml, markdown)
41
+ - Comprehensive documentation in README.md with installation instructions
42
+ - Uses omelette library for cross-shell compatibility
43
+
8
44
  ## [0.2.12] - 2025-12-05
9
45
 
10
46
  ### Changed
package/README.md CHANGED
@@ -258,6 +258,67 @@ braingrid --help # Show help information
258
258
 
259
259
  ---
260
260
 
261
+ ## Shell Completion
262
+
263
+ BrainGrid CLI supports tab completion for bash and zsh shells, making it faster to type commands and discover available options.
264
+
265
+ ### Quick Setup (Recommended)
266
+
267
+ Automatically install completion for your current shell:
268
+
269
+ ```bash
270
+ braingrid completion --setup
271
+ ```
272
+
273
+ Then restart your terminal or source your shell config:
274
+
275
+ ```bash
276
+ # For bash
277
+ source ~/.bashrc
278
+
279
+ # For zsh
280
+ source ~/.zshrc
281
+ ```
282
+
283
+ ### Manual Installation
284
+
285
+ #### Bash
286
+
287
+ Add to your `~/.bashrc`:
288
+
289
+ ```bash
290
+ # Option 1: Add to shell config
291
+ braingrid completion bash >> ~/.bashrc
292
+
293
+ # Option 2: Eval in current session (temporary)
294
+ eval "$(braingrid completion bash)"
295
+ ```
296
+
297
+ #### Zsh
298
+
299
+ Add to your `~/.zshrc`:
300
+
301
+ ```bash
302
+ # Option 1: Add to shell config
303
+ braingrid completion zsh >> ~/.zshrc
304
+
305
+ # Option 2: Eval in current session (temporary)
306
+ eval "$(braingrid completion zsh)"
307
+ ```
308
+
309
+ ### What Gets Completed
310
+
311
+ - **Commands**: `login`, `logout`, `project`, `requirement`, `task`, etc.
312
+ - **Subcommands**: `list`, `show`, `create`, `update`, `delete`, `breakdown`, `build`
313
+ - **Options**: `--help`, `--format`, `--status`, `--project`, `--requirement`
314
+ - **Values**: Status values (`IDEA`, `PLANNED`, `IN_PROGRESS`, etc.), format options (`table`, `json`, `xml`, `markdown`)
315
+
316
+ ### Fish Shell
317
+
318
+ Fish shell support is planned for a future release.
319
+
320
+ ---
321
+
261
322
  ## Updating
262
323
 
263
324
  Update to the latest version:
package/dist/cli.js CHANGED
@@ -422,7 +422,7 @@ import axios3, { AxiosError as AxiosError2 } from "axios";
422
422
 
423
423
  // src/build-config.ts
424
424
  var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
425
- var CLI_VERSION = true ? "0.2.12" : "0.0.0-test";
425
+ var CLI_VERSION = true ? "0.2.15" : "0.0.0-test";
426
426
  var PRODUCTION_CONFIG = {
427
427
  apiUrl: "https://app.braingrid.ai",
428
428
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -4498,12 +4498,6 @@ async function handleTaskList(opts) {
4498
4498
  message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
4499
4499
  };
4500
4500
  }
4501
- if (!opts.requirement) {
4502
- return {
4503
- success: false,
4504
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan("braingrid task list -r REQ-456") + chalk8.dim(" (if initialized) or\n") + chalk8.cyan("braingrid task list -r REQ-456 -p PROJ-123")
4505
- };
4506
- }
4507
4501
  const workspace = await workspaceManager.getProject(opts.project);
4508
4502
  if (!workspace.success) {
4509
4503
  return {
@@ -4512,7 +4506,14 @@ async function handleTaskList(opts) {
4512
4506
  };
4513
4507
  }
4514
4508
  const projectId = workspace.projectId;
4515
- const requirementId = normalizeRequirementId(opts.requirement);
4509
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
4510
+ if (!requirementResult.success) {
4511
+ return {
4512
+ success: false,
4513
+ message: requirementResult.error
4514
+ };
4515
+ }
4516
+ const requirementId = requirementResult.requirementId;
4516
4517
  stopSpinner = showSpinner("Loading tasks", chalk8.gray);
4517
4518
  const response = await taskService.listTasks(projectId, requirementId);
4518
4519
  stopSpinner();
@@ -4525,7 +4526,7 @@ async function handleTaskList(opts) {
4525
4526
  };
4526
4527
  }
4527
4528
  const projectShortId = projectId;
4528
- const requirementShortId = opts.requirement;
4529
+ const requirementShortId = opts.requirement || requirementId;
4529
4530
  const format = opts.format || "markdown";
4530
4531
  const config = getConfig();
4531
4532
  const output = formatTasksListOutput(response.tasks, format, true, {
@@ -4560,12 +4561,6 @@ async function handleTaskSummary(opts) {
4560
4561
  message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
4561
4562
  };
4562
4563
  }
4563
- if (!opts.requirement) {
4564
- return {
4565
- success: false,
4566
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan("braingrid task summary -r REQ-456") + chalk8.dim(" (if initialized) or\n") + chalk8.cyan("braingrid task summary -r REQ-456 -p PROJ-123")
4567
- };
4568
- }
4569
4564
  const workspace = await workspaceManager.getProject(opts.project);
4570
4565
  if (!workspace.success) {
4571
4566
  return {
@@ -4574,7 +4569,14 @@ async function handleTaskSummary(opts) {
4574
4569
  };
4575
4570
  }
4576
4571
  const projectId = workspace.projectId;
4577
- const requirementId = normalizeRequirementId(opts.requirement);
4572
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
4573
+ if (!requirementResult.success) {
4574
+ return {
4575
+ success: false,
4576
+ message: requirementResult.error
4577
+ };
4578
+ }
4579
+ const requirementId = requirementResult.requirementId;
4578
4580
  stopSpinner = showSpinner("Loading tasks", chalk8.gray);
4579
4581
  const response = await taskService.listTasks(projectId, requirementId);
4580
4582
  stopSpinner();
@@ -4587,7 +4589,7 @@ async function handleTaskSummary(opts) {
4587
4589
  };
4588
4590
  }
4589
4591
  const projectShortId = projectId;
4590
- const requirementShortId = opts.requirement;
4592
+ const requirementShortId = opts.requirement || requirementId;
4591
4593
  const config = getConfig();
4592
4594
  const output = formatTasksListOutput(response.tasks, "table", false, {
4593
4595
  requirementId,
@@ -4621,21 +4623,22 @@ async function handleTaskShow(id, opts) {
4621
4623
  message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
4622
4624
  };
4623
4625
  }
4624
- if (!opts?.requirement) {
4626
+ const workspace = await workspaceManager.getProject(opts?.project);
4627
+ if (!workspace.success) {
4625
4628
  return {
4626
4629
  success: false,
4627
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan("braingrid task show TASK-789 -r REQ-456") + chalk8.dim(" (if initialized) or\n") + chalk8.cyan("braingrid task show TASK-789 -r REQ-456 -p PROJ-123")
4630
+ message: workspace.error
4628
4631
  };
4629
4632
  }
4630
- const workspace = await workspaceManager.getProject(opts.project);
4631
- if (!workspace.success) {
4633
+ const projectId = workspace.projectId;
4634
+ const requirementResult = await workspaceManager.getRequirement(opts?.requirement);
4635
+ if (!requirementResult.success) {
4632
4636
  return {
4633
4637
  success: false,
4634
- message: workspace.error
4638
+ message: requirementResult.error
4635
4639
  };
4636
4640
  }
4637
- const projectId = workspace.projectId;
4638
- const requirementId = normalizeRequirementId(opts.requirement);
4641
+ const requirementId = requirementResult.requirementId;
4639
4642
  const taskId = normalizeTaskId(id);
4640
4643
  const config = getConfig();
4641
4644
  stopSpinner = showSpinner("Loading task", chalk8.gray);
@@ -4646,7 +4649,7 @@ async function handleTaskShow(id, opts) {
4646
4649
  showContent: true,
4647
4650
  apiUrl: config.apiUrl,
4648
4651
  projectShortId: projectId,
4649
- requirementShortId: opts.requirement,
4652
+ requirementShortId: opts?.requirement || requirementId,
4650
4653
  requirementId
4651
4654
  });
4652
4655
  return {
@@ -4675,12 +4678,6 @@ async function handleTaskCreate(opts) {
4675
4678
  message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
4676
4679
  };
4677
4680
  }
4678
- if (!opts.requirement) {
4679
- return {
4680
- success: false,
4681
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan('braingrid task create -r REQ-456 --title "..."') + chalk8.dim(" (if initialized) or\n") + chalk8.cyan('braingrid task create -r REQ-456 -p PROJ-123 --title "..."')
4682
- };
4683
- }
4684
4681
  const workspace = await workspaceManager.getProject(opts.project);
4685
4682
  if (!workspace.success) {
4686
4683
  return {
@@ -4689,9 +4686,16 @@ async function handleTaskCreate(opts) {
4689
4686
  };
4690
4687
  }
4691
4688
  const projectId = workspace.projectId;
4692
- const requirementId = normalizeRequirementId(opts.requirement);
4689
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
4690
+ if (!requirementResult.success) {
4691
+ return {
4692
+ success: false,
4693
+ message: requirementResult.error
4694
+ };
4695
+ }
4696
+ const requirementId = requirementResult.requirementId;
4693
4697
  const projectShortId = projectId;
4694
- const requirementShortId = opts.requirement;
4698
+ const requirementShortId = opts.requirement || requirementId;
4695
4699
  const config = getConfig();
4696
4700
  stopSpinner = showSpinner("Creating task", chalk8.gray);
4697
4701
  const task2 = await taskService.createTask(projectId, requirementId, {
@@ -4740,12 +4744,6 @@ async function handleTaskUpdate(id, opts) {
4740
4744
  message: chalk8.red("\u274C Please provide at least one field to update (--status or --title)")
4741
4745
  };
4742
4746
  }
4743
- if (!opts.requirement) {
4744
- return {
4745
- success: false,
4746
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan("braingrid task update TASK-789 -r REQ-456 --status COMPLETED") + chalk8.dim(" (if initialized) or\n") + chalk8.cyan("braingrid task update TASK-789 -r REQ-456 -p PROJ-123 --status COMPLETED")
4747
- };
4748
- }
4749
4747
  const workspace = await workspaceManager.getProject(opts.project);
4750
4748
  if (!workspace.success) {
4751
4749
  return {
@@ -4754,7 +4752,14 @@ async function handleTaskUpdate(id, opts) {
4754
4752
  };
4755
4753
  }
4756
4754
  const projectId = workspace.projectId;
4757
- const requirementId = normalizeRequirementId(opts.requirement);
4755
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
4756
+ if (!requirementResult.success) {
4757
+ return {
4758
+ success: false,
4759
+ message: requirementResult.error
4760
+ };
4761
+ }
4762
+ const requirementId = requirementResult.requirementId;
4758
4763
  const taskId = normalizeTaskId(id);
4759
4764
  const config = getConfig();
4760
4765
  stopSpinner = showSpinner("Updating task", chalk8.gray);
@@ -4769,7 +4774,7 @@ async function handleTaskUpdate(id, opts) {
4769
4774
  successMessage: `Updated task ${task2.number}`,
4770
4775
  apiUrl: config.apiUrl,
4771
4776
  projectShortId: projectId,
4772
- requirementShortId: opts.requirement,
4777
+ requirementShortId: opts.requirement || requirementId,
4773
4778
  requirementId
4774
4779
  });
4775
4780
  return {
@@ -4804,12 +4809,6 @@ async function handleTaskDelete(id, opts) {
4804
4809
  message: chalk8.yellow("\u26A0\uFE0F Deleting a task is permanent. Use --force to confirm deletion.")
4805
4810
  };
4806
4811
  }
4807
- if (!opts.requirement) {
4808
- return {
4809
- success: false,
4810
- message: chalk8.red("\u274C No requirement specified.\n\n") + chalk8.dim("Please provide a requirement ID:\n") + chalk8.cyan("braingrid task delete TASK-789 -r REQ-456 --force") + chalk8.dim(" (if initialized) or\n") + chalk8.cyan("braingrid task delete TASK-789 -r REQ-456 -p PROJ-123 --force")
4811
- };
4812
- }
4813
4812
  const workspace = await workspaceManager.getProject(opts.project);
4814
4813
  if (!workspace.success) {
4815
4814
  return {
@@ -4818,7 +4817,14 @@ async function handleTaskDelete(id, opts) {
4818
4817
  };
4819
4818
  }
4820
4819
  const projectId = workspace.projectId;
4821
- const requirementId = normalizeRequirementId(opts.requirement);
4820
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
4821
+ if (!requirementResult.success) {
4822
+ return {
4823
+ success: false,
4824
+ message: requirementResult.error
4825
+ };
4826
+ }
4827
+ const requirementId = requirementResult.requirementId;
4822
4828
  const taskId = normalizeTaskId(id);
4823
4829
  stopSpinner = showSpinner("Deleting task", chalk8.gray);
4824
4830
  await taskService.deleteTask(projectId, requirementId, taskId);
@@ -6670,9 +6676,162 @@ async function handleUpdate(opts) {
6670
6676
  }
6671
6677
  }
6672
6678
 
6679
+ // src/handlers/completion.handlers.ts
6680
+ import chalk15 from "chalk";
6681
+
6682
+ // src/completion/index.ts
6683
+ import omelette from "omelette";
6684
+ import { homedir } from "os";
6685
+ import { join as join3 } from "path";
6686
+ var SUPPORTED_SHELLS = ["bash", "zsh"];
6687
+ function createCompletion() {
6688
+ const completion = omelette("braingrid <command> <subcommand>");
6689
+ completion.on("command", ({ reply }) => {
6690
+ reply([
6691
+ "login",
6692
+ "logout",
6693
+ "whoami",
6694
+ "status",
6695
+ "init",
6696
+ "update",
6697
+ "specify",
6698
+ "setup",
6699
+ "project",
6700
+ "requirement",
6701
+ "task",
6702
+ "completion"
6703
+ ]);
6704
+ });
6705
+ completion.on("setup", ({ reply }) => {
6706
+ reply(["claude-code", "cursor"]);
6707
+ });
6708
+ completion.on("project", ({ reply }) => {
6709
+ reply(["list", "show", "create", "update", "delete"]);
6710
+ });
6711
+ completion.on("requirement", ({ reply }) => {
6712
+ reply(["list", "show", "create", "update", "delete", "breakdown", "build"]);
6713
+ });
6714
+ completion.on("task", ({ reply }) => {
6715
+ reply(["list", "summary", "show", "create", "update", "delete"]);
6716
+ });
6717
+ completion.on("completion", ({ reply }) => {
6718
+ reply(["bash", "zsh"]);
6719
+ });
6720
+ return completion;
6721
+ }
6722
+ function initCompletion() {
6723
+ const completion = createCompletion();
6724
+ completion.init();
6725
+ }
6726
+ function detectShell() {
6727
+ const shellPath = process.env.SHELL || "";
6728
+ const shellName = shellPath.split("/").pop()?.toLowerCase();
6729
+ if (shellName === "bash" || shellName === "zsh") {
6730
+ return shellName;
6731
+ }
6732
+ return null;
6733
+ }
6734
+ function getShellConfigPath(shell) {
6735
+ const home = homedir();
6736
+ switch (shell) {
6737
+ case "bash":
6738
+ return join3(home, ".bashrc");
6739
+ case "zsh":
6740
+ return join3(home, ".zshrc");
6741
+ }
6742
+ }
6743
+ function generateCompletionScript(shell) {
6744
+ const completion = createCompletion();
6745
+ return completion.setupShellInitFile(shell);
6746
+ }
6747
+ function setupCompletion(shell) {
6748
+ const completion = createCompletion();
6749
+ completion.setupShellInitFile(shell);
6750
+ }
6751
+
6752
+ // src/handlers/completion.handlers.ts
6753
+ function getUnsupportedShellError() {
6754
+ return {
6755
+ success: false,
6756
+ message: chalk15.red("\u274C Could not detect shell type.\n\n") + chalk15.dim("Please specify a shell:\n") + chalk15.cyan(" braingrid completion bash\n") + chalk15.cyan(" braingrid completion zsh\n\n") + chalk15.dim("Supported shells: bash, zsh")
6757
+ };
6758
+ }
6759
+ async function handleCompletion(shellArg, opts) {
6760
+ try {
6761
+ let shell = null;
6762
+ if (shellArg) {
6763
+ if (SUPPORTED_SHELLS.includes(shellArg)) {
6764
+ shell = shellArg;
6765
+ } else {
6766
+ return getUnsupportedShellError();
6767
+ }
6768
+ } else if (opts?.shell) {
6769
+ if (SUPPORTED_SHELLS.includes(opts.shell)) {
6770
+ shell = opts.shell;
6771
+ } else {
6772
+ return getUnsupportedShellError();
6773
+ }
6774
+ } else {
6775
+ shell = detectShell();
6776
+ }
6777
+ if (!shell) {
6778
+ return getUnsupportedShellError();
6779
+ }
6780
+ if (opts?.setup) {
6781
+ try {
6782
+ setupCompletion(shell);
6783
+ const configPath = getShellConfigPath(shell);
6784
+ return {
6785
+ success: true,
6786
+ message: chalk15.green(`\u2705 Completion installed for ${shell}
6787
+
6788
+ `) + chalk15.dim("To activate in your current shell, run:\n") + chalk15.cyan(` source ${configPath}
6789
+
6790
+ `) + chalk15.dim("Or restart your terminal.")
6791
+ };
6792
+ } catch (error) {
6793
+ return {
6794
+ success: false,
6795
+ message: chalk15.red("\u274C Failed to install completion\n\n") + chalk15.dim("Error: ") + formatError(error)
6796
+ };
6797
+ }
6798
+ }
6799
+ try {
6800
+ const script = generateCompletionScript(shell);
6801
+ const configPath = getShellConfigPath(shell);
6802
+ const instructions = chalk15.blue(`# BrainGrid CLI completion for ${shell}
6803
+ `) + chalk15.dim("# To install, run one of the following:\n\n") + chalk15.cyan(`# 1. Automatic installation (recommended):
6804
+ `) + chalk15.cyan(` braingrid completion --setup
6805
+
6806
+ `) + chalk15.cyan(`# 2. Add to your shell config:
6807
+ `) + chalk15.cyan(` braingrid completion ${shell} >> ${configPath}
6808
+
6809
+ `) + chalk15.cyan(`# 3. Source directly (temporary, current session only):
6810
+ `) + chalk15.cyan(` eval "$(braingrid completion ${shell})"
6811
+
6812
+ `) + chalk15.dim("# Completion script:\n\n");
6813
+ return {
6814
+ success: true,
6815
+ message: instructions + script
6816
+ };
6817
+ } catch (error) {
6818
+ return {
6819
+ success: false,
6820
+ message: chalk15.red("\u274C Failed to generate completion script\n\n") + chalk15.dim("Error: ") + formatError(error)
6821
+ };
6822
+ }
6823
+ } catch (error) {
6824
+ return {
6825
+ success: false,
6826
+ message: formatError(error)
6827
+ };
6828
+ }
6829
+ }
6830
+
6673
6831
  // src/cli.ts
6674
6832
  var require2 = createRequire(import.meta.url);
6675
6833
  var packageJson = require2("../package.json");
6834
+ initCompletion();
6676
6835
  var program = new Command();
6677
6836
  program.name("braingrid").description("BrainGrid CLI - Manage projects, requirements, and tasks").version(packageJson.version, "-v, --version", "output the current version").showHelpAfterError("(use --help for usage)");
6678
6837
  program.command("login").description("Authenticate with BrainGrid").action(async () => {
@@ -6896,5 +7055,12 @@ task.command("delete <id>").description("Delete a task").option("-r, --requireme
6896
7055
  process.exit(1);
6897
7056
  }
6898
7057
  });
7058
+ program.command("completion [shell]").description("Generate shell completion script (bash, zsh)").option("--setup", "automatically install completion for current shell").option("-s, --shell <shell>", "shell type (bash, zsh)").action(async (shell, opts) => {
7059
+ const result = await handleCompletion(shell, opts);
7060
+ console.log(result.message);
7061
+ if (!result.success) {
7062
+ process.exit(1);
7063
+ }
7064
+ });
6899
7065
  program.parse();
6900
7066
  //# sourceMappingURL=cli.js.map