@goodtek/vibeops 1.0.0 → 1.1.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +51 -60
  3. package/dist/bootstrap/installer.js +38 -4
  4. package/dist/bootstrap/manifest.js +23 -3
  5. package/dist/cli.js +64 -15
  6. package/dist/commands/init.js +279 -114
  7. package/dist/commands/llm.js +177 -0
  8. package/dist/commands/status.js +37 -1
  9. package/dist/commands/task-add.js +33 -6
  10. package/dist/commands/task-done.js +27 -31
  11. package/dist/lib/config.js +84 -4
  12. package/dist/lib/env.js +32 -0
  13. package/dist/lib/git-config.js +21 -0
  14. package/dist/lib/git-host.js +12 -0
  15. package/dist/lib/git-policy.js +67 -0
  16. package/dist/lib/git-remote.js +108 -0
  17. package/dist/lib/git.js +43 -0
  18. package/dist/lib/init-clients.js +55 -0
  19. package/dist/lib/llm-complete.js +18 -19
  20. package/dist/lib/llm-connect.js +150 -0
  21. package/dist/lib/llm-preference.js +102 -0
  22. package/dist/lib/llm-status.js +19 -0
  23. package/dist/lib/pr-create.js +66 -0
  24. package/dist/lib/task-add-llm.js +8 -1
  25. package/dist/lib/task-done-llm.js +8 -1
  26. package/dist/lib/task-pr-llm.js +50 -0
  27. package/dist/lib/task-push-pr.js +131 -0
  28. package/dist/lib/task-start.js +46 -18
  29. package/dist/lib/task.js +15 -0
  30. package/package.json +13 -11
  31. package/templates/.cursor/rules/02-git-safety.mdc +5 -4
  32. package/templates/AGENTS.md +2 -1
  33. package/templates/clients/claude/CLAUDE.md +28 -0
  34. package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +11 -0
  35. package/templates/core/AGENTS.md +44 -0
  36. package/templates/{.cursor/skills → skills-shared}/implement-task/SKILL.md +1 -1
  37. package/templates/{.cursor/skills → skills-shared}/plan-task/SKILL.md +3 -3
  38. package/dist/lib/task-merge.js +0 -115
  39. /package/templates/{.cursor → clients/cursor/.cursor}/rules/01-task-source-of-truth.mdc +0 -0
  40. /package/templates/{.cursor → clients/cursor/.cursor}/rules/03-docs-on-done.mdc +0 -0
  41. /package/templates/{docs → core/docs}/logs/README.md +0 -0
  42. /package/templates/{docs → core/docs}/project/03-architecture.md +0 -0
  43. /package/templates/{docs → core/docs}/project/05-current-state.md +0 -0
  44. /package/templates/{docs → core/docs}/project/06-decisions.md +0 -0
  45. /package/templates/{docs → core/docs}/tasks/TASK-000-template.md +0 -0
package/CHANGELOG.md CHANGED
@@ -2,8 +2,39 @@
2
2
 
3
3
  All notable changes to VibeOps are documented here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ### Fixed
8
+
9
+ - Removed dead `task-merge.ts` (local merge no longer used).
10
+ - `task add` checks out integration branch from `origin/<branch>` when only remote exists.
11
+ - `task done` sets Status **Done** only after push/MR succeed; skips duplicate PR when URL exists.
12
+ - Re-init creates missing integration branch on existing repos.
13
+ - `status` shows branch policy and MR/PR URL.
14
+
15
+ ## 1.1.0 - 2026-05-27
16
+
17
+ ### Breaking
18
+
19
+ - **`task done` no longer merges locally.** It pushes the task branch and opens a **pull/merge request** to the integration branch (`gh` / `glab`). Merge and deploy happen on the host (CI).
20
+ - **`init` requires a git remote** (`origin`) unless `--allow-no-remote` (smoke/CI).
21
+ - **`.vibeops.json` gains a `git` block** (integration/production branches, host). Re-run `vibeops init` on older projects.
22
+
23
+ ### Added
24
+
25
+ - **Branch policy at init:** GitFlow lite (`develop` + `main`) or trunk (`main` only); `--git-policy`, `--integration-branch`, `--production-branch`.
26
+ - **`task add`** branches from the configured **integration** branch, not arbitrary HEAD.
27
+ - **LLM-generated MR/PR** title and body on `task done`; URL stored in TASK **Git Context**.
28
+ - **`--no-pr`** on `task done` to push only.
29
+
5
30
  ## 1.0.0 - 2026-05-27
6
31
 
32
+ ### Added
33
+
34
+ - **`vibeops init --clients`**: choose **cursor**, **claude**, **codex** (≥1 required). Installs core + per-client rules/skills (`CLAUDE.md`, `.agents/skills/`, etc.).
35
+ - **Re-init guard**: existing projects prompt before overwriting templates; `docs/tasks/TASK-*.md` are preserved. Use `--yes` for non-interactive re-init.
36
+ - **`vibeops llm`**: `connect`, `status`, `use` for task add/done LLM providers.
37
+
7
38
  ### Breaking
8
39
 
9
40
  - **Four commands only:** `init`, `task add`, `task done`, `status`.
package/README.md CHANGED
@@ -1,104 +1,95 @@
1
1
  # VibeOps
2
2
 
3
- > Minimal CLI rails for Cursor-based TASK-driven vibe coding.
3
+ > Minimal CLI rails for TASK-driven vibe coding (Cursor, Claude Code, Codex).
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@goodtek/vibeops.svg)](https://www.npmjs.com/package/@goodtek/vibeops)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
- VibeOps bootstraps a **Cursor-friendly repo**, starts numbered **TASK** files on Git branches, and closes them while updating **project memory** in Git. Cursor plans and implements; the CLI handles files, Git, and short LLM assists.
8
+ VibeOps bootstraps an **agent-friendly repo**, starts numbered **TASK** files on Git branches, and closes them while updating **project memory** in Git. You plan and implement in your chosen agent; the CLI handles files, Git, and short LLM assists.
9
9
 
10
- ## Four commands
10
+ ## Commands
11
11
 
12
12
  | Command | Purpose |
13
13
  |---------|---------|
14
- | `vibeops init` | Install AGENTS.md, Cursor rules/skills, docs layout, `.vibeops.json` |
15
- | `vibeops task add` | New `TASK-NNN` file + task branch (blocks if another TASK is In Progress) |
16
- | `vibeops task done` | LLM fills Result/Test + updates project docs; commit + merge |
17
- | `vibeops status` | Briefing: active TASK, branch, doc health, next hint |
14
+ | `vibeops init` | Core docs + agent packs (cursor / claude / codex) |
15
+ | `vibeops task add` | New `TASK-NNN` file + task branch |
16
+ | `vibeops task done` | LLM summary, push branch, open MR/PR (no local merge) |
17
+ | `vibeops status` | Briefing: TASK, Git, LLM, clients |
18
+ | `vibeops llm` | Connect LLM providers (`connect` · `status` · `use`) |
18
19
 
19
- ## Workflow
20
+ ## Init
20
21
 
21
22
  ```bash
22
- vibeops init --git --initial-commit # once per repo
23
+ # Interactive: pick agents (≥1), then Git
24
+ vibeops init
23
25
 
24
- vibeops task add # interactive: idea → TASK file + branch
25
- # Plan in Cursor Ask: @docs/tasks/TASK-NNN-….md
26
- # Build in Cursor Agent: same file + Scope / Acceptance Criteria
26
+ # Non-interactive (GitFlow: develop + main, origin required unless CI)
27
+ vibeops init --clients cursor,claude,codex --git --initial-commit --git-policy gitflow
27
28
 
28
- vibeops task done # close TASK, update memory, merge branch
29
- vibeops status # anytime snapshot
29
+ # Re-init templates on an existing project
30
+ vibeops init --clients cursor --yes
30
31
  ```
31
32
 
32
- If a TASK is already **In Progress**, `task add` prints a guide and exits with code 1 — it does **not** auto-run `task done`.
33
+ | Pack | Installed |
34
+ |------|-----------|
35
+ | **core** (always) | `AGENTS.md`, `docs/tasks`, `docs/project`, `docs/logs` |
36
+ | **cursor** | `.cursor/rules/`, `.cursor/skills/` |
37
+ | **claude** | `CLAUDE.md`, `.claude/skills/` |
38
+ | **codex** | `.agents/skills/` |
39
+
40
+ Re-init **overwrites** templates (rules, skills, doc stubs). **`docs/tasks/TASK-*.md` are kept.**
33
41
 
34
- ## What `init` creates
42
+ ## Workflow
35
43
 
36
- | Path | Role |
37
- |------|------|
38
- | `AGENTS.md` | Source of truth, read order, four CLI commands |
39
- | `.cursor/rules/` | Task source of truth, Git safety, docs on done |
40
- | `.cursor/skills/` | Optional `plan-task` and `implement-task` skills |
41
- | `docs/tasks/TASK-000-template.md` | Section template for new TASKs |
42
- | `docs/project/05-current-state.md` | Facts: stage, active TASK, next step |
43
- | `docs/project/06-decisions.md` | Append-only decision log |
44
- | `docs/project/03-architecture.md` | Living architecture |
45
- | `docs/logs/README.md` | Daily log convention |
46
- | `.vibeops.json` | Project metadata (no Notion) |
44
+ ```bash
45
+ vibeops init --clients cursor --git --initial-commit
47
46
 
48
- ## Source of truth
47
+ vibeops task add
48
+ # Plan / build using your agent and the current TASK file
49
49
 
50
- | What | Where |
51
- |------|--------|
52
- | Current work | `docs/tasks/TASK-NNN-*.md` |
53
- | Project memory | `docs/project/05-current-state.md`, `03-architecture.md`, `06-decisions.md` |
54
- | Change history | Git commits on task branches |
55
- | **Not** trusted | Cursor chat, Slack |
50
+ vibeops task done
51
+ vibeops status
52
+ ```
56
53
 
57
- ## LLM (optional)
54
+ If a TASK is **In Progress**, `task add` exits with a guide — it does not auto-run `task done`.
58
55
 
59
- Used only for **`task add`** (scaffold title/slug/sections) and **`task done`** (Result, Test Result, project doc patches).
56
+ ## LLM (optional)
60
57
 
61
- Providers (first match wins): Codex OAuth (`~/.codex/auth.json`), Cursor Agent CLI, `OPENAI_API_KEY` in `.vibeops.env`.
58
+ For **`task add`** / **`task done`** only (not for coding in the IDE):
62
59
 
63
- Without LLM, `task add` falls back to a minimal template; `task done` uses a git-diff fallback for Result sections.
60
+ ```bash
61
+ vibeops llm connect
62
+ vibeops llm use auto # auto | codex-oauth | cursor-agent | openai
63
+ ```
64
64
 
65
65
  ## Installation
66
66
 
67
- Node.js 20+ required.
67
+ Node.js 20+.
68
68
 
69
69
  ```bash
70
70
  npm install -g @goodtek/vibeops
71
- vibeops --help
72
71
  ```
73
72
 
74
- Local development:
73
+ Development:
75
74
 
76
75
  ```bash
77
- pnpm install
78
- pnpm build
79
- pnpm smoke # typecheck + build + smoke cases
80
- node dist/cli.js --help
76
+ pnpm install && pnpm build && pnpm smoke
81
77
  ```
82
78
 
83
79
  ## Flags (common)
84
80
 
85
- - **`init`**: `--dry-run`, `--cwd`, `--name`, `--git`, `--no-git`, `--initial-commit`, `--force`
86
- - **`task add`**: `--dry-run`, `--non-interactive --idea "…"` (CI/smoke)
87
- - **`task done`**: `--dry-run`, `--no-merge`, `--allow-dirty`, `--yes`, `--cwd`
88
- - **`status`**: `--json`, `--cwd`
89
-
90
- ## Git conventions
91
-
92
- - Task branch: `task/<slug>` from current HEAD
93
- - Commit message: `feat(task-nnn): <short title>`
94
- - Merge via `task done` (confirm in TTY; `--yes` to skip prompt)
95
- - No force-push
96
-
97
- ## Breaking change: 1.0.0
81
+ - **`init`**: `--clients`, `--yes`, `--dry-run`, `--force`, `--git`, `--initial-commit`, `--git-policy gitflow|trunk`, `--integration-branch`, `--production-branch`, `--allow-no-remote`, `--cwd`
82
+ - **`task add`**: `--dry-run`, `--non-interactive --idea "…"`
83
+ - **`task done`**: `--dry-run`, `--no-pr` (push only)
84
+ - **`status`**: `--json`
98
85
 
99
- v1.0 removes `plan`, `start`, `next`, `rollback`, `notion`, `github`, and legacy task subcommands. Pin `@goodtek/vibeops@0.7` if you still need them.
86
+ ## Git
100
87
 
101
- See [CHANGELOG.md](CHANGELOG.md) and [docs/project/v3-scope.md](docs/project/v3-scope.md).
88
+ - **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`).
89
+ - **`task add`**: branch `task/<slug>` from the **integration** branch (e.g. `develop`).
90
+ - **`task done`**: commit if needed → `git push` → `gh pr create` / `glab mr create` (LLM title/body). **You merge on GitHub/GitLab**; CI deploys.
91
+ - Commit messages: `feat(task-nnn): …`
92
+ - No force-push to shared branches
102
93
 
103
94
  ## License
104
95
 
@@ -1,8 +1,10 @@
1
+ import { readdir } from "node:fs/promises";
1
2
  import { join } from "node:path";
2
- import { ensureDir, pathExists, readText, readTextOrNull, writeText } from "../lib/filesystem.js";
3
+ import { ensureDir, pathExists, readText, readTextOrNull, writeText, } from "../lib/filesystem.js";
4
+ import { CLIENT_SKILL_ROOT } from "../lib/init-clients.js";
3
5
  import { dim, green, log, yellow } from "../lib/logger.js";
4
- import { projectPaths, VIBEOPS_ENV_FILE } from "../lib/paths.js";
5
- import { loadManifest, resolveDestination } from "./manifest.js";
6
+ import { projectPaths, TEMPLATES_ROOT, VIBEOPS_ENV_FILE } from "../lib/paths.js";
7
+ import { loadInstallManifest, resolveDestination } from "./manifest.js";
6
8
  import { applySubstitutions, buildSubstitutions, isTextPath, } from "./substitute.js";
7
9
  import { configToJson } from "../lib/config.js";
8
10
  function envExampleContents() {
@@ -74,19 +76,51 @@ async function ensureGitignoreEntry(projectRoot, dryRun) {
74
76
  relativePath: ".gitignore",
75
77
  };
76
78
  }
79
+ async function listSharedSkillNames() {
80
+ const sharedRoot = join(TEMPLATES_ROOT, "skills-shared");
81
+ if (!(await pathExists(sharedRoot)))
82
+ return [];
83
+ const entries = await readdir(sharedRoot, { withFileTypes: true });
84
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
85
+ }
86
+ async function installSharedSkills(options, subs) {
87
+ const outcomes = [];
88
+ const skillNames = await listSharedSkillNames();
89
+ const sharedRoot = join(TEMPLATES_ROOT, "skills-shared");
90
+ for (const client of options.clients) {
91
+ const skillRoot = CLIENT_SKILL_ROOT[client];
92
+ for (const skillName of skillNames) {
93
+ const sourceAbs = join(sharedRoot, skillName, "SKILL.md");
94
+ if (!(await pathExists(sourceAbs)))
95
+ continue;
96
+ const relativePath = join(skillRoot, skillName, "SKILL.md");
97
+ const dest = join(options.projectRoot, relativePath);
98
+ const entry = { relativePath, sourceAbs };
99
+ if (!options.dryRun) {
100
+ await ensureDir(join(dest, ".."));
101
+ }
102
+ outcomes.push(await copyOne(entry, dest, subs, options));
103
+ }
104
+ }
105
+ return outcomes;
106
+ }
77
107
  export async function install(options) {
78
108
  const paths = projectPaths(options.projectRoot);
79
109
  const subs = buildSubstitutions(options.config);
80
110
  if (!options.dryRun) {
81
111
  await ensureDir(paths.root);
82
112
  }
83
- const manifest = await loadManifest();
113
+ const manifest = await loadInstallManifest(options.clients);
84
114
  const outcomes = [];
85
115
  for (const entry of manifest) {
86
116
  const dest = resolveDestination(entry, paths.root);
117
+ if (!options.dryRun) {
118
+ await ensureDir(join(dest, ".."));
119
+ }
87
120
  const outcome = await copyOne(entry, dest, subs, options);
88
121
  outcomes.push(outcome);
89
122
  }
123
+ outcomes.push(...(await installSharedSkills(options, subs)));
90
124
  const configOutcome = await writeBlobIfNeeded(paths.config, ".vibeops.json", configToJson(options.config), options);
91
125
  outcomes.push(configOutcome);
92
126
  const envExampleOutcome = await writeBlobIfNeeded(paths.envExample, ".vibeops.env.example", envExampleContents(), options);
@@ -1,6 +1,29 @@
1
1
  import { join, relative } from "node:path";
2
2
  import { walk } from "../lib/filesystem.js";
3
+ import { pathExists } from "../lib/filesystem.js";
3
4
  import { TEMPLATES_ROOT } from "../lib/paths.js";
5
+ async function loadManifestFromDirectory(rootDir) {
6
+ if (!(await pathExists(rootDir)))
7
+ return [];
8
+ const files = await walk(rootDir);
9
+ return files.map((abs) => ({
10
+ relativePath: relative(rootDir, abs),
11
+ sourceAbs: abs,
12
+ }));
13
+ }
14
+ /** Core tree + selected client packs (not shared skills — those are copied separately). */
15
+ export async function loadInstallManifest(clients) {
16
+ const entries = [];
17
+ entries.push(...(await loadManifestFromDirectory(join(TEMPLATES_ROOT, "core"))));
18
+ for (const id of clients) {
19
+ entries.push(...(await loadManifestFromDirectory(join(TEMPLATES_ROOT, "clients", id))));
20
+ }
21
+ return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
22
+ }
23
+ export function resolveDestination(entry, projectRoot) {
24
+ return join(projectRoot, entry.relativePath);
25
+ }
26
+ /** @deprecated Use loadInstallManifest — walks entire templates/ (legacy). */
4
27
  export async function loadManifest() {
5
28
  const files = await walk(TEMPLATES_ROOT);
6
29
  return files
@@ -10,6 +33,3 @@ export async function loadManifest() {
10
33
  }))
11
34
  .sort((a, b) => a.relativePath.localeCompare(b.relativePath));
12
35
  }
13
- export function resolveDestination(entry, projectRoot) {
14
- return join(projectRoot, entry.relativePath);
15
- }
package/dist/cli.js CHANGED
@@ -1,30 +1,57 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { initCommand } from "./commands/init.js";
4
+ import { llmConnectCommand, llmStatusCommand, llmUseCommand } from "./commands/llm.js";
4
5
  import { statusCommand } from "./commands/status.js";
5
6
  import { taskAddCommand } from "./commands/task-add.js";
6
7
  import { taskDoneCommand } from "./commands/task-done.js";
8
+ import { loadVibeopsEnv } from "./lib/env.js";
7
9
  import { VERSION } from "./version.js";
8
10
  const program = new Command();
9
11
  program
10
12
  .name("vibeops")
11
- .description("VibeOps v3 — Cursor-friendly TASK workflow (init · task add · task done · status)")
13
+ .description("VibeOps v3 — Cursor-friendly TASK workflow (init · task · status · llm)")
12
14
  .version(VERSION, "-v, --version", "Print the VibeOps version");
15
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
16
+ const opts = actionCommand.opts();
17
+ const cwd = typeof opts.cwd === "string" ? opts.cwd : process.cwd();
18
+ await loadVibeopsEnv(cwd);
19
+ });
13
20
  program
14
21
  .command("init")
15
- .description("Install the VibeOps v3 project tree")
22
+ .description("Install core + agent client packs (cursor, claude, codex)")
16
23
  .option("--dry-run", "Show what would be created")
17
- .option("--force", "Overwrite existing files")
24
+ .option("--force", "Overwrite existing template files (also implied when re-initializing with --yes)")
25
+ .option("--yes", "Re-init an existing project without prompting")
18
26
  .option("--cwd <path>", "Target directory")
19
27
  .option("--name <projectName>", "Project name in .vibeops.json")
20
- .option("--git", "Initialize Git")
21
- .option("--no-git", "Skip Git")
28
+ .option("--clients <list>", "Agent packs: comma-separated cursor, claude, codex (required in non-interactive mode)")
29
+ .option("--git", "Initialize Git (always on; flag kept for scripts)")
22
30
  .option("--initial-commit", "Create initial commit")
23
31
  .option("--no-initial-commit", "Skip initial commit")
24
- .option("--default-branch <name>", "Default branch (default main)")
25
32
  .option("--commit-message <message>", "Initial commit message")
33
+ .option("--git-policy <preset>", "Branch policy: gitflow (develop+main) or trunk (main only)")
34
+ .option("--integration-branch <name>", "Integration branch (with --production-branch)")
35
+ .option("--production-branch <name>", "Production branch")
36
+ .option("--git-host <host>", "github or gitlab (default: detect from remote)")
37
+ .option("--allow-no-remote", "Smoke/CI: skip origin requirement")
26
38
  .action(async (opts) => {
27
- await initCommand(opts);
39
+ await initCommand({
40
+ dryRun: Boolean(opts.dryRun),
41
+ force: Boolean(opts.force),
42
+ yes: Boolean(opts.yes),
43
+ cwd: opts.cwd,
44
+ name: opts.name,
45
+ clients: opts.clients,
46
+ git: opts.git,
47
+ initialCommit: opts.initialCommit,
48
+ commitMessage: opts.commitMessage,
49
+ allowNoRemote: Boolean(opts.allowNoRemote),
50
+ gitPolicy: opts.gitPolicy,
51
+ integrationBranch: opts.integrationBranch,
52
+ productionBranch: opts.productionBranch,
53
+ gitHost: opts.gitHost,
54
+ });
28
55
  });
29
56
  const task = program.command("task").description("TASK lifecycle");
30
57
  task
@@ -44,24 +71,20 @@ task
44
71
  });
45
72
  task
46
73
  .command("done [taskRef]")
47
- .description("Close TASK: LLM summary, project memory, merge")
74
+ .description("Close TASK: LLM summary, push branch, open MR/PR (no local merge)")
48
75
  .option("--dry-run", "Plan only")
49
- .option("--no-merge", "Skip merge")
50
- .option("--allow-dirty", "Allow dirty tree during merge")
51
- .option("--yes", "Merge without prompting")
76
+ .option("--no-pr", "Push only; skip creating MR/PR")
52
77
  .option("--cwd <path>", "Target directory")
53
78
  .action(async (taskRef, opts) => {
54
79
  await taskDoneCommand(taskRef, {
55
80
  dryRun: Boolean(opts.dryRun),
56
- noMerge: Boolean(opts.noMerge),
57
- allowDirty: Boolean(opts.allowDirty),
58
- yes: Boolean(opts.yes),
81
+ noPr: Boolean(opts.noPr),
59
82
  cwd: opts.cwd,
60
83
  });
61
84
  });
62
85
  program
63
86
  .command("status")
64
- .description("Briefing: active TASK, Git, next hint")
87
+ .description("Briefing: active TASK, Git, LLM, next hint")
65
88
  .option("--json", "JSON output")
66
89
  .option("--cwd <path>", "Target directory")
67
90
  .action(async (opts) => {
@@ -70,6 +93,32 @@ program
70
93
  cwd: opts.cwd,
71
94
  });
72
95
  });
96
+ const llm = program.command("llm").description("LLM providers for task add / task done");
97
+ llm
98
+ .command("connect")
99
+ .description("Interactive menu: connect providers and choose default")
100
+ .option("--cwd <path>", "Target directory")
101
+ .action(async (opts) => {
102
+ await llmConnectCommand({ cwd: opts.cwd });
103
+ });
104
+ llm
105
+ .command("status")
106
+ .description("Show connected providers and project preference")
107
+ .option("--json", "JSON output")
108
+ .option("--cwd <path>", "Target directory")
109
+ .action(async (opts) => {
110
+ await llmStatusCommand({
111
+ json: Boolean(opts.json),
112
+ cwd: opts.cwd,
113
+ });
114
+ });
115
+ llm
116
+ .command("use [provider]")
117
+ .description("Set default provider: auto | codex-oauth | cursor-agent | openai")
118
+ .option("--cwd <path>", "Target directory")
119
+ .action(async (provider, opts) => {
120
+ await llmUseCommand(provider, { cwd: opts.cwd });
121
+ });
73
122
  program.parseAsync(process.argv).catch((err) => {
74
123
  console.error("[vibeops] error:", err);
75
124
  process.exitCode = 1;