Skip to content

feat(cli): agent-friendly CLI, docs, and Docus skill#751

Merged
HugoRCD merged 5 commits into
mainfrom
feat/cli-agent-friendly
May 30, 2026
Merged

feat(cli): agent-friendly CLI, docs, and Docus skill#751
HugoRCD merged 5 commits into
mainfrom
feat/cli-agent-friendly

Conversation

@HugoRCD
Copy link
Copy Markdown
Owner

@HugoRCD HugoRCD commented May 30, 2026

Summary

  • Add global CLI flags (--json, --quiet, --yes, --non-interactive, --debug) with auto-detection in CI/agent shells, structured JSON errors on stderr, and non-interactive guards across all commands.
  • Extend commands with machine-readable output, login --token, generate --type, push --yes, and fix silent fetch failures in shelve run.
  • Refresh shelve.cloud CLI documentation (v5 accuracy) and publish a Docus agent skill at /.well-known/skills/ (npx skills add https://shelve.cloud).

Test plan

  • pnpm --filter @shelve/cli test (61 tests)
  • pnpm play -- --json config — JSON output with redacted token
  • pnpm playshelve run end-to-end against fake API
  • After LP deploy: curl https://shelve.cloud/.well-known/skills/index.json lists shelve skill
  • Agent shell: shelve pull without --yes returns AGENT_BLOCKED
  • CI-style: SHELVE_TOKEN=… shelve --non-interactive run -- echo ok

Summary by CodeRabbit

  • New Features

    • Global CLI flags for automation: --json, --quiet, --yes, --non-interactive
    • New doctor command for preflight checks and CI validation
    • GitHub composite action for non-interactive shelve run usage
    • Published agent skill artifacts and skills catalog endpoint
  • Behavior Changes

    • Structured/machine-readable JSON output and errors (token redaction)
    • Agent/CI guards: non-interactive enforcement and pull blocked unless confirmed
  • Documentation

    • Expanded agents & automation, troubleshooting, command reference, and examples
  • Tests

    • Added tests for CLI context, error codes, and non-interactive flows

Review Change Stack

Introduce global CLI context (--json, --quiet, --yes, --non-interactive) with structured errors, non-interactive guards on all commands, and refreshed shelve.cloud documentation plus a publishable agent skill at /.well-known/skills/.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

Deployment failed with the following error:

There is no GitHub account connected to this Vercel account.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shelve-app Ready Ready Preview, Comment, Open in v0 May 30, 2026 6:29pm
shelve-lp Error Error May 30, 2026 6:29pm
shelve-vault Error Error May 30, 2026 6:29pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Warning

Review limit reached

@HugoRCD, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 859d8a60-5d90-4706-bc45-acfe57cb3ce1

📥 Commits

Reviewing files that changed from the base of the PR and between f8083ba and d9248c3.

📒 Files selected for processing (7)
  • apps/lp/content/docs/3.cli/10.agents-automation.md
  • packages/cli/src/commands/doctor.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/utils/cli-context.ts
  • packages/cli/src/utils/index.ts
  • packages/cli/src/utils/templates.ts
  • packages/cli/test/cli-context.test.ts
📝 Walkthrough

Walkthrough

This PR implements Shelve CLI v5 with agent-friendly infrastructure: adds global flags (--json, --quiet, --yes, --non-interactive) for structured/quiet/non-interactive operation, introduces CliError exception class with standardized error codes and JSON output helpers, refactors all commands to use new output system, introduces doctor validation command, enforces non-interactive guards blocking prompts in agent shells, and provides comprehensive documentation and published agent skills for CI/automation workflows.

Changes

Agent-Friendly CLI Modernization

Layer / File(s) Summary
CLI context and global flag foundation
packages/cli/src/utils/cli-context.ts, packages/cli/src/constants.ts, packages/cli/src/index.ts
Global flag parsing tracks --json, --quiet, --yes, --non-interactive, --debug and auto-enables non-interactive in CI/agent shells. Exported helpers (isJson, isQuiet, shouldSkipConfirm, isAgentShell, isNonInteractive) drive downstream behavior. CLI entry point initializes context early and appends error-code help to metadata.
Error and output infrastructure
packages/cli/src/services/api-error.ts, packages/cli/src/utils/error-codes.ts, packages/cli/src/utils/output.ts, packages/cli/src/utils/index.ts
New CliError class carries code/status/hint. CLI_ERROR_CODES and EXIT_CODES provide structured definitions. Output helpers (cliError, cliSuccess, cliInfo, cliWarn) and JSON writers (writeJsonSuccess, writeJsonError) switch between JSON and human modes. Spinner management via withSpinner respects quiet/JSON flags.
Prompt and input handling for non-interactive mode
packages/cli/src/utils/prompt.ts, packages/cli/src/utils/templates.ts
askBoolean, askSelect, askText, askPassword throw CliError in non-interactive mode instead of prompting. Added hint/default parameters and tokenFromFlag support. getEslintConfig respects quiet/JSON modes and computes conditional install decisions.
Service layer updates for error handling and output
packages/cli/src/services/base.ts, packages/cli/src/services/env.ts, packages/cli/src/services/environment.ts, packages/cli/src/services/project.ts, packages/cli/src/utils/config.ts
BaseService.withLoading delegates to withSpinner; whoAmI/getApi get HTTP debug hooks; getToken accepts tokenFromFlag. EnvironmentService/ProjectService throw CliError in non-interactive mode. EnvService uses cliWarn. createShelveConfig/validateConfig use CliError with explicit codes.
Refactor simple commands to new output system
packages/cli/src/commands/config.ts, packages/cli/src/commands/init.ts, packages/cli/src/commands/login.ts, packages/cli/src/commands/logout.ts, packages/cli/src/commands/me.ts, packages/cli/src/commands/upgrade.ts
Replaced @clack/prompts intro/outro with cliIntro/cliSuccess. Commands accept args destructuring and emit structured success payloads (e.g., { writtenFiles, skippedFiles, gitignoreUpdated } for init).
Refactor complex commands with new behavior
packages/cli/src/commands/create.ts, packages/cli/src/commands/push.ts, packages/cli/src/commands/pull.ts, packages/cli/src/commands/run.ts, packages/cli/src/commands/generate.ts
push/pull add --yes and compute effectiveConfirmChanges. pull blocks with AGENT_BLOCKED in agent shells unless --yes. run emits cliJsonEvent('child_spawned') and uses toCliError for API failures. generate adds --type with non-interactive validation. All report structured cliSuccess payloads.
New doctor validation command
packages/cli/src/commands/doctor.ts
New doctor command validates config/auth/environment/api/cache and agent/CI status, emits DoctorCheck records, outputs JSON or human-readable checks, and exits non-zero when unhealthy.
GitHub Actions composite action and workflow jobs
.github/actions/shelve-run/action.yml, .github/actions/shelve-run/README.md, .github/workflows/test.yml, package.json, playground/run/README.md
Composite shelve-run action injects SHELVE_* env vars and runs commands with --non-interactive; README documents inputs and doctor validation. Adds cli-playground and lp-agent-skills CI jobs and play:json script; playground README updated with JSON/non-interactive examples.
End-user and contributor documentation
apps/lp/content/docs/3.cli/*, apps/lp/skills/shelve/*, docs/agents/cli.md, packages/cli/README.md, .changeset/cli-agent-friendly.md, apps/lp/nuxt.config.ts
Comprehensive docs: CLI index with global flags; command guides updated for --json shapes and non-interactive behavior; new agents-automation page with workflows/flags/codes/examples; troubleshooting; published skill docs and site links.
Tests for CLI context, error codes, and output
packages/cli/test/cli-context.test.ts, packages/cli/test/error-codes.test.ts, packages/cli/test/non-interactive.test.ts, packages/cli/test/output.test.ts
Vitest suites validating argv/global-flag parsing, CI/AI_AGENT overrides, error-code content, non-interactive prompt rejection (CliError), redactConfig behavior, and JSON writer output correctness.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HugoRCD/shelve#749: Extends the playground fixture used as a foundation by this PR's CLI infrastructure tests and workflow validation jobs (cli-playground and lp-agent-skills).
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-agent-friendly

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 30, 2026

Thank you for following the naming conventions! 🙏

Extend agent automation with shelve doctor, structured error codes, run spawn JSON events, GitHub Action, playground CI, troubleshooting docs, and shelve-app skill — without local Cursor skills or global --token flags.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 30, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@shelve/cli@751

commit: d9248c3

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/shelve-run/action.yml:
- Around line 24-27: The action currently defaults the inputs.cli-version to
"latest", making CI non-deterministic; update the default value in action.yml
for the input key cli-version from 'latest' to a pinned semver (e.g., '1.0.0' or
a major-only pin like '1') while keeping the input override, and ensure the
runtime invocation that uses npx --yes "`@shelve/cli`@${{ inputs.cli-version }}"
continues to reference inputs.cli-version so callers can still override the
version when needed.

In @.github/workflows/test.yml:
- Around line 41-49: Replace mutable tag refs with immutable commit SHAs for all
third-party actions in the workflow: change occurrences of
"actions/checkout@v6", "pnpm/action-setup@v6", and "actions/setup-node@v4" to
their corresponding full commit SHA pins; and for each "actions/checkout" step,
add with: persist-credentials: false so the checkout does not persist repository
credentials to the runner. Locate the action usages by the action identifiers
(actions/checkout, pnpm/action-setup, actions/setup-node) and update the ref
strings to SHAs and add the persist-credentials key to the checkout steps.

In `@apps/lp/content/docs/3.cli/10.agents-automation.md`:
- Around line 25-29: The ordered list restarts at "2. Run **`shelve init`**"
which breaks markdownlint sequence; locate the list item that begins with "2.
Run **`shelve init`**" and change its leading numeral to continue the previous
sequence (i.e., set it to the previous list's last number + 1) so the sequence
flows correctly through "Run **`shelve init`**", "Inject secrets with **`shelve
run -- pnpm dev`**", "Use **`--json`**…" and "Use **`--non-interactive`**…".

In `@apps/lp/content/docs/3.cli/3.run.md`:
- Line 6: The doc overstates security by claiming secrets "never touch disk";
update the sentence about `shelve run` so it accurately states that `shelve run`
does not write a plaintext `.env` file (e.g., change the phrase "No `.env` file
is written — secrets never touch disk." to something like "No plaintext `.env`
file is written by `shelve run` — encrypted cache files (e.g., ~/.shelve/cache/)
may still be created."), keeping reference to `shelve run` and the encrypted
cache location to avoid misleading readers.

In `@apps/lp/content/docs/3.cli/8.generate.md`:
- Line 43: The sentence describing optional installs is backwards: update the
sentence that currently reads "Optionally installs `eslint` and
`@hrcd/eslint-config` when run interactively (skipped with `--yes` /
non-interactive)" to clarify that `--yes` auto-accepts the optional installs
while non-interactive mode skips them; specifically edit the line containing
"Optionally installs `eslint` and `@hrcd/eslint-config`" to say something like
installs occur when interactive or when `--yes` is passed (which auto-confirms),
and that only non-interactive runs will skip the installs.

In `@apps/lp/skills/shelve/agent-workflows.md`:
- Line 15: Replace the typo in the user guidance sentence that currently reads
"a on-disk `.env` file" with the grammatically correct phrase "an on-disk `.env`
file" in the docs (look for the exact text "a on-disk `.env` file" in
agent-workflows.md and update it to "an on-disk `.env` file").

In `@docs/agents/cli.md`:
- Around line 24-27: The ordered list restarts at "2." causing markdownlint
MD029; renumber the sequence so it starts at 1 and increments (change "2. Run
**`shelve init`**" to "1. Run **`shelve init`**" and adjust the subsequent items
"3. Prefer **`shelve run -- <cmd>`**", "4. Use **`--json`**", "5. Use
**`--non-interactive`**" to "2.", "3.", "4." respectively so the items are a
continuous ordered list.

In `@packages/cli/README.md`:
- Around line 59-74: Add the missing "doctor" command to the CLI docs: update
the USAGE line to include "doctor" (e.g., "USAGE shelve
run|push|pull|login|logout|me|init|create|config|generate|upgrade|doctor") and
insert a new command entry under COMMANDS to match the existing style (e.g., " 
doctor    Run diagnostic checks for the Shelve CLI and environment"), ensuring
alignment and formatting consistent with the other command descriptions.

In `@packages/cli/src/commands/config.ts`:
- Line 16: The current console.log(config) call prints Node's default
inspection; replace it with a pretty JSON serialization for human-readable
output by calling JSON.stringify(config, null, 2) (use the same conditional that
checks the --json flag if present) so the object returned by redactConfig(...)
is displayed consistently and legibly; update the output in the command handler
where console.log(config) is used.

In `@packages/cli/src/commands/doctor.ts`:
- Around line 60-69: Early returns call finishDoctor(checks) before
pushAgentChecks(checks), causing agent/CI diagnostics to be skipped; move or add
calls to pushAgentChecks(checks) so they always run prior to any
finishDoctor(checks) return. Specifically, ensure that after
pushConfigChecks(config, checks) you invoke pushAgentChecks(checks)
unconditionally (and likewise before the other early-return branches that
currently call finishDoctor(checks)), so pushAgentChecks is executed regardless
of config.token presence or other failing conditions before calling
finishDoctor(checks).
- Around line 256-271: The JSON branch currently always calls cliSuccess(data,
...) even when healthy is false; change it so that when isJson() is true and
healthy is false you do not emit a success-shaped payload: either call the
corresponding failure emitter (cliError or similar) with the same data/context
(e.g., cliError(data, undefined, 'doctor')) or explicitly set data.ok = false
(or equivalent failure flag) before emitting, so machines receive a failing JSON
result when !healthy; update the isJson() branch around the cliSuccess(...) call
to handle both healthy and !healthy cases accordingly.

In `@packages/cli/src/commands/generate.ts`:
- Around line 80-81: The default branch in the switch (calling
cliCancel('Invalid option')) is dead code because the variable mapped is
validated earlier (lines validating mapped before runGenerate). Remove the
unreachable default case from the switch in generate.ts (the switch that
dispatches on mapped before calling runGenerate) so only the concrete cases
remain; ensure no other code relies on that default path and run tests/TS
compile to confirm no fallthrough errors.

In `@packages/cli/src/commands/me.ts`:
- Around line 13-20: The current me command assumes both config.username and
config.email exist when logging in; change the logic so that when either field
is present it still sets loggedIn: true, and build the success message from only
the present fields (e.g., create a displayParts array that adds config.username
and `<${config.email}>` only if they exist, then join with a space or
separator). Update the cliSuccess call (the payload passed to cliSuccess and its
message) to use these conditional values instead of interpolating potentially
undefined config.username/config.email so the printed message never contains
"undefined".

In `@packages/cli/src/commands/push.ts`:
- Around line 33-35: The current logic in push.ts treats a non-interactive
environment (shouldSkipConfirm()) as implicit approval by setting skipConfirm
and computing effectiveConfirmChanges; instead, change the behavior so that if
args.yes is not set and shouldSkipConfirm() returns true, the command throws a
structured error (e.g., "cannot prompt for confirmation in non-interactive mode;
pass --yes to confirm") rather than assuming consent; locate and update the code
that computes skipConfirm/effectiveConfirmChanges (references: args.yes,
shouldSkipConfirm, skipConfirm, effectiveConfirmChanges, confirmChanges) to
perform this check and throw the error before proceeding with any mutating
action.

In `@packages/cli/src/commands/upgrade.ts`:
- Around line 21-22: The JSON payload currently sets current: 'latest', which is
misleading; change the call to use the actual installed version returned by
installLatest() (e.g., let installed = await installLatest() and pass current:
installed) or, if installLatest() cannot return a version yet, remove the
current field from the cliSuccess call; update the code around installLatest()
and the cliSuccess invocation so the structured output contains a real resolved
version (or omits current) instead of the literal 'latest'.

In `@packages/cli/src/utils/cli-context.ts`:
- Around line 87-97: Both helpers mistakenly continue processing after an option
terminator ("--"); update getCommandFromArgv and stripGlobalFlags so they stop
parsing when encountering "--". In getCommandFromArgv (function name) iterate
argv.slice(2) and if you see arg === "--" then break and return undefined (or
leave the remainder alone) so subsequent args aren't treated as global flags or
commands; in stripGlobalFlags (function name) scan arguments and when you
encounter "--" stop filtering and append the rest of the argv slice (including
the "--" and following items) unchanged to the result so child process flags are
preserved.

In `@packages/cli/src/utils/output.ts`:
- Around line 135-141: The spinner error is being rendered twice on
unrecoverable failures because s.cancel(formatCliError(error, message)) prints
the error and then handleThrownError(error, message) prints/exits again;
instead, on the unrecoverable path remove the direct spinner render and only
call handleThrownError so the spinner is stopped/cancelled without duplicating
output—e.g., call s.cancel() or s.stop() to clear the spinner and then invoke
handleThrownError(error, message), preserving the existing recoverable branch
that calls s.stop(message) and rethrows.
- Around line 46-50: The JSON path in cliError currently concatenates hint into
message before calling writeJsonError, causing duplicate human text; change
cliError so when isJson() is true it calls writeJsonError with the original
input (or with separate message and hint fields) instead of the combined
message, while preserving the existing concatenated message only for the
non-JSON (human) branch; update the code in cliError to use the computed
combined message for the non-JSON branch but pass input.message and input.hint
(or input unchanged) to writeJsonError so JSON consumers get separate fields.

In `@packages/cli/src/utils/templates.ts`:
- Around line 33-38: The logic that computes shouldInstall is inverted:
shouldSkipConfirm() means “auto-confirm”, so when it returns true we should set
shouldInstall to true instead of false; update the expression that defines
shouldInstall (referenced by the variable name shouldInstall and functions
shouldSkipConfirm(), isNonInteractive(), askBoolean()) so that
shouldSkipConfirm() yields true (auto-accept), keep isNonInteractive()
short-circuiting to false, and otherwise call await askBoolean('Do you want to
install ESLint and `@hrcd/eslint-config`?') to prompt the user.
- Around line 23-24: The code fetches templates.eslint into response and
immediately reads text and writes it out; add a check for response.ok after the
fetch (before calling response.text()) and handle non-2xx responses by throwing
or returning an error so you don't write an HTML error page into
eslint.config.js. Specifically, after the fetch(templates.eslint) check
response.ok and if false, throw an Error including
response.status/response.statusText (or log and abort), otherwise await
response.text() and proceed with writing; update the code paths that currently
use response/text to rely on this check so generation fails cleanly on 404/500.

In `@packages/cli/test/cli-context.test.ts`:
- Around line 15-18: The afterEach teardown in the test sets
process.env.AI_AGENT and process.env.CI directly from
ORIGINAL_AI_AGENT/ORIGINAL_CI which can assign the literal string "undefined"
when those originals were not set; change the restore logic in the afterEach
(around the afterEach block that calls initCliContextFromArgv) to check each
saved value (ORIGINAL_AI_AGENT and ORIGINAL_CI) and if it is undefined use
delete process.env.AI_AGENT / delete process.env.CI, otherwise reassign the
saved string to process.env.AI_AGENT / process.env.CI so unset envs are truly
removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 68765c5d-4566-43c3-ad45-68d81d03dc31

📥 Commits

Reviewing files that changed from the base of the PR and between e64d2ec and c0110f6.

📒 Files selected for processing (56)
  • .changeset/cli-agent-friendly.md
  • .github/actions/shelve-run/README.md
  • .github/actions/shelve-run/action.yml
  • .github/workflows/test.yml
  • AGENTS.md
  • apps/lp/content/docs/3.cli/1.init.md
  • apps/lp/content/docs/3.cli/10.agents-automation.md
  • apps/lp/content/docs/3.cli/11.troubleshooting.md
  • apps/lp/content/docs/3.cli/2.index.md
  • apps/lp/content/docs/3.cli/3.run.md
  • apps/lp/content/docs/3.cli/4.login-logout.md
  • apps/lp/content/docs/3.cli/5.push-pull.md
  • apps/lp/content/docs/3.cli/6.create.md
  • apps/lp/content/docs/3.cli/7.config.md
  • apps/lp/content/docs/3.cli/8.generate.md
  • apps/lp/content/docs/3.cli/9.upgrade.md
  • apps/lp/nuxt.config.ts
  • apps/lp/skills/shelve-app/SKILL.md
  • apps/lp/skills/shelve/SKILL.md
  • apps/lp/skills/shelve/agent-workflows.md
  • apps/lp/skills/shelve/cli-commands.md
  • docs/agents/cli.md
  • docs/agents/docs-links.md
  • package.json
  • packages/cli/README.md
  • packages/cli/src/commands/config.ts
  • packages/cli/src/commands/create.ts
  • packages/cli/src/commands/doctor.ts
  • packages/cli/src/commands/generate.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/commands/login.ts
  • packages/cli/src/commands/logout.ts
  • packages/cli/src/commands/me.ts
  • packages/cli/src/commands/pull.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/commands/upgrade.ts
  • packages/cli/src/constants.ts
  • packages/cli/src/index.ts
  • packages/cli/src/services/api-error.ts
  • packages/cli/src/services/base.ts
  • packages/cli/src/services/env.ts
  • packages/cli/src/services/environment.ts
  • packages/cli/src/services/project.ts
  • packages/cli/src/utils/cli-context.ts
  • packages/cli/src/utils/config.ts
  • packages/cli/src/utils/error-codes.ts
  • packages/cli/src/utils/index.ts
  • packages/cli/src/utils/output.ts
  • packages/cli/src/utils/prompt.ts
  • packages/cli/src/utils/templates.ts
  • packages/cli/test/cli-context.test.ts
  • packages/cli/test/error-codes.test.ts
  • packages/cli/test/non-interactive.test.ts
  • packages/cli/test/output.test.ts
  • playground/run/README.md

Comment thread .github/actions/shelve-run/action.yml Outdated
Comment thread .github/workflows/test.yml Outdated
Comment thread apps/lp/content/docs/3.cli/10.agents-automation.md Outdated
Comment thread apps/lp/content/docs/3.cli/3.run.md Outdated
Comment thread apps/lp/content/docs/3.cli/8.generate.md Outdated
Comment thread packages/cli/src/utils/output.ts
Comment thread packages/cli/src/utils/output.ts
Comment thread packages/cli/src/utils/templates.ts Outdated
Comment thread packages/cli/src/utils/templates.ts Outdated
Comment thread packages/cli/test/cli-context.test.ts
Pin CI action SHAs, harden doctor/push/json output, fix flag parsing after `--`, and correct docs for run security, generate installs, and list numbering.
The playground fail script intentionally exits with 7 to verify propagation through shelve run, not 1.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/cli/src/utils/cli-context.ts (1)

48-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop reading global flags after the -- separator during context init.

hasFlag() scans the full argv, so initCliContextFromArgv() still treats child-process args as Shelve globals. For example, shelve run -- echo --json will incorrectly flip JSON mode on Line 53 even though --json belongs to the spawned command. Slice argv at the first -- before computing jsonMode/quietMode/yesMode/nonInteractiveFlag.

Proposed fix
 export function initCliContextFromArgv(argv: string[] = process.argv): void {
-  jsonMode = hasFlag(argv, '--json')
-  quietMode = hasFlag(argv, '--quiet', '-q')
-  yesMode = hasFlag(argv, '--yes', '-y')
-  nonInteractiveFlag = hasFlag(argv, '--non-interactive')
+  const separatorIndex = argv.indexOf('--')
+  const cliArgv = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex)
+
+  jsonMode = hasFlag(cliArgv, '--json')
+  quietMode = hasFlag(cliArgv, '--quiet', '-q')
+  yesMode = hasFlag(cliArgv, '--yes', '-y')
+  nonInteractiveFlag = hasFlag(cliArgv, '--non-interactive')
 
   if (process.env.CI === 'true' || process.env.CI === '1') {
     nonInteractiveFlag = true
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/utils/cli-context.ts` around lines 48 - 56, The CLI context
initialization currently scans the entire argv (via hasFlag) which incorrectly
picks up flags after a `--` separator; update initCliContextFromArgv to first
locate the first `--` in the provided argv and slice argv to argv.slice(0,
indexOfDoubleDash) (or use the full argv if no `--`), then call hasFlag against
that sliced array when setting jsonMode, quietMode, yesMode and
nonInteractiveFlag so flags intended for spawned commands are ignored; reference
initCliContextFromArgv and hasFlag to make the change.
packages/cli/src/utils/index.ts (1)

38-59: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always clean up the spinner on failure paths.

Both flows start the shared spinner and then await network/package-manager calls without a finally. If either call throws, the spinner is never stopped and the terminal can be left in a broken-looking state. Wrap the start/stop pair in try/finally so cleanup happens on both success and error paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/utils/index.ts` around lines 38 - 59, Both isLatestVersion
and installLatest start the shared spinner before awaiting network/package calls
but don't stop it on exceptions; wrap the spinner lifecycle in a try/finally in
each function so s.stop(...) always runs even if fetchLatestCliVersion() or
addDependency(...) throws. Specifically, in isLatestVersion and installLatest,
call s.start(...) as you currently do, then perform the awaited work inside a
try block and call s.stop('...') in the finally block (guarding the stop with
the same !isQuiet() && !isJson() check), ensuring the spinner is cleaned up on
both success and error paths.
packages/cli/src/utils/templates.ts (1)

22-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t report every failure here as a fetch error.

This catch now covers the download, file write, and both addDevDependency() calls, but it always emits “Failed to fetch ESLint config”. If installation fails after Line 29 writes the file, users get the wrong diagnosis and lose the partial-success context. Narrow the try/catch to the fetch step or surface the original error message instead of replacing it unconditionally.

Proposed fix
-  } catch {
-    cliCancel('Failed to fetch ESLint config')
+  } catch (error) {
+    cliCancel(error instanceof Error ? error.message : 'Failed to generate ESLint config')
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/utils/templates.ts` around lines 22 - 52, The catch
currently hides the real failure cause by always calling cliCancel('Failed to
fetch ESLint config') for errors from fetch, FileService.write, askBoolean, or
addDevDependency; either narrow the try/catch to only surround the
fetch/response.text() (the fetch of templates.eslint and response.ok check) and
let later steps throw normally, or change the catch to capture the real error
(catch (err)) and call cliCancel with a message that includes
err.message/context (e.g., mention whether the failure happened during fetch,
file write via FileService.write, or dependency installation via
addDevDependency) so callers can see partial successes and accurate failure
reasons.
♻️ Duplicate comments (3)
packages/cli/src/commands/push.ts (1)

35-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not let shouldSkipConfirm() become implicit consent for push.

confirmed = args.yes || shouldSkipConfirm() still auto-approves pushes in agent/CI shells, so the guard on Line 36 never fires for the case it is meant to block. Keep auto-skip separate from explicit --yes, throw before mutating, and only disable confirmation when the caller actually passed --yes.

Suggested fix
-    const confirmed = args.yes || shouldSkipConfirm()
-    if (confirmChanges && !confirmed && isNonInteractive()) {
+    const autoSkipConfirm = shouldSkipConfirm()
+    if (confirmChanges && !args.yes && autoSkipConfirm && isNonInteractive()) {
       throw new CliError(
         'Push confirmation is required.',
         'CONFIRMATION_REQUIRED',
         undefined,
         'Pass --yes to confirm pushing variables in non-interactive mode.',
       )
     }
-    const effectiveConfirmChanges = confirmed ? false : confirmChanges
+    const effectiveConfirmChanges = args.yes ? false : confirmChanges
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/push.ts` around lines 35 - 44, The current code
treats shouldSkipConfirm() as explicit consent by setting confirmed = args.yes
|| shouldSkipConfirm(), which bypasses the non-interactive guard; change this so
confirmed is only based on args.yes (e.g., const confirmed = !!args.yes), call
shouldSkipConfirm() into a separate variable (e.g., const autoSkip =
shouldSkipConfirm()) if you still need it for prompt-skipping, perform the throw
for non-interactive when confirmChanges && !confirmed && isNonInteractive(), and
compute effectiveConfirmChanges solely from confirmed (const
effectiveConfirmChanges = confirmed ? false : confirmChanges) so autoSkip does
not implicitly disable confirmation.
apps/lp/content/docs/3.cli/10.agents-automation.md (1)

25-28: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

This list still fails MD029 because the code fence above breaks the sequence.

Markdownlint expects these items to restart at 1. unless the fenced block under item 2 is indented as part of the same list item. Either indent the code block above or renumber this block as a new list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/lp/content/docs/3.cli/10.agents-automation.md` around lines 25 - 28, The
markdown list triggers MD029 because the fenced code block under item 2 isn't
indented as part of that list item; to fix, either indent the fenced block so it
is nested under item 2 (so items 3-6 continue the same numbered list) or treat
items 3-6 as a new list by renumbering them to start at 1; locate the list
containing "Run `shelve init`", the preceding code fence under item 2, and the
flags `--json` and `--non-interactive` and apply one of the two fixes to resolve
the lint error.
packages/cli/src/commands/doctor.ts (1)

257-262: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the standard JSON error envelope on failing doctor runs.

The unhealthy branch now emits { ok: false, command, data }, but every other JSON failure path is documented and implemented as an error object with stable code/message fields. That makes doctor --json a special case that automation has to parse differently on failure.

Suggested fix
   if (isJson()) {
     if (healthy) {
       cliSuccess(data, undefined, 'doctor')
     } else {
-      console.error(JSON.stringify({ ok: false, command: 'doctor', data }))
+      console.error(JSON.stringify({
+        ok: false,
+        command: 'doctor',
+        error: {
+          code: 'DOCTOR_FAILED',
+          message: 'Doctor checks failed',
+        },
+        data,
+      }))
     }
   } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/doctor.ts` around lines 257 - 262, The failing JSON
branch in the doctor command currently prints JSON.stringify({ ok: false,
command: 'doctor', data }) which breaks the standard error envelope used
elsewhere; change that branch to emit the documented error envelope with an
error object containing stable code and message fields (and include the original
data as nested details if needed), e.g. produce JSON.stringify({ ok: false,
command: 'doctor', error: { code: '<SOME_CODE>', message: '<human-readable
message>', details: data }}) instead of the current shape; update the unhealthy
path in the isJson() block (where cliSuccess is used for healthy) to construct
and console.error this standard error object so automated consumers receive a
consistent {error: {code, message}} envelope.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/cli/src/utils/cli-context.ts`:
- Around line 48-56: The CLI context initialization currently scans the entire
argv (via hasFlag) which incorrectly picks up flags after a `--` separator;
update initCliContextFromArgv to first locate the first `--` in the provided
argv and slice argv to argv.slice(0, indexOfDoubleDash) (or use the full argv if
no `--`), then call hasFlag against that sliced array when setting jsonMode,
quietMode, yesMode and nonInteractiveFlag so flags intended for spawned commands
are ignored; reference initCliContextFromArgv and hasFlag to make the change.

In `@packages/cli/src/utils/index.ts`:
- Around line 38-59: Both isLatestVersion and installLatest start the shared
spinner before awaiting network/package calls but don't stop it on exceptions;
wrap the spinner lifecycle in a try/finally in each function so s.stop(...)
always runs even if fetchLatestCliVersion() or addDependency(...) throws.
Specifically, in isLatestVersion and installLatest, call s.start(...) as you
currently do, then perform the awaited work inside a try block and call
s.stop('...') in the finally block (guarding the stop with the same !isQuiet()
&& !isJson() check), ensuring the spinner is cleaned up on both success and
error paths.

In `@packages/cli/src/utils/templates.ts`:
- Around line 22-52: The catch currently hides the real failure cause by always
calling cliCancel('Failed to fetch ESLint config') for errors from fetch,
FileService.write, askBoolean, or addDevDependency; either narrow the try/catch
to only surround the fetch/response.text() (the fetch of templates.eslint and
response.ok check) and let later steps throw normally, or change the catch to
capture the real error (catch (err)) and call cliCancel with a message that
includes err.message/context (e.g., mention whether the failure happened during
fetch, file write via FileService.write, or dependency installation via
addDevDependency) so callers can see partial successes and accurate failure
reasons.

---

Duplicate comments:
In `@apps/lp/content/docs/3.cli/10.agents-automation.md`:
- Around line 25-28: The markdown list triggers MD029 because the fenced code
block under item 2 isn't indented as part of that list item; to fix, either
indent the fenced block so it is nested under item 2 (so items 3-6 continue the
same numbered list) or treat items 3-6 as a new list by renumbering them to
start at 1; locate the list containing "Run `shelve init`", the preceding code
fence under item 2, and the flags `--json` and `--non-interactive` and apply one
of the two fixes to resolve the lint error.

In `@packages/cli/src/commands/doctor.ts`:
- Around line 257-262: The failing JSON branch in the doctor command currently
prints JSON.stringify({ ok: false, command: 'doctor', data }) which breaks the
standard error envelope used elsewhere; change that branch to emit the
documented error envelope with an error object containing stable code and
message fields (and include the original data as nested details if needed), e.g.
produce JSON.stringify({ ok: false, command: 'doctor', error: { code:
'<SOME_CODE>', message: '<human-readable message>', details: data }}) instead of
the current shape; update the unhealthy path in the isJson() block (where
cliSuccess is used for healthy) to construct and console.error this standard
error object so automated consumers receive a consistent {error: {code,
message}} envelope.

In `@packages/cli/src/commands/push.ts`:
- Around line 35-44: The current code treats shouldSkipConfirm() as explicit
consent by setting confirmed = args.yes || shouldSkipConfirm(), which bypasses
the non-interactive guard; change this so confirmed is only based on args.yes
(e.g., const confirmed = !!args.yes), call shouldSkipConfirm() into a separate
variable (e.g., const autoSkip = shouldSkipConfirm()) if you still need it for
prompt-skipping, perform the throw for non-interactive when confirmChanges &&
!confirmed && isNonInteractive(), and compute effectiveConfirmChanges solely
from confirmed (const effectiveConfirmChanges = confirmed ? false :
confirmChanges) so autoSkip does not implicitly disable confirmation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae150301-220d-4457-a9df-8f98ebaa68be

📥 Commits

Reviewing files that changed from the base of the PR and between c0110f6 and f8083ba.

📒 Files selected for processing (20)
  • .github/actions/shelve-run/README.md
  • .github/actions/shelve-run/action.yml
  • .github/workflows/test.yml
  • apps/lp/content/docs/3.cli/10.agents-automation.md
  • apps/lp/content/docs/3.cli/3.run.md
  • apps/lp/content/docs/3.cli/8.generate.md
  • apps/lp/skills/shelve/agent-workflows.md
  • docs/agents/cli.md
  • packages/cli/README.md
  • packages/cli/src/commands/config.ts
  • packages/cli/src/commands/doctor.ts
  • packages/cli/src/commands/generate.ts
  • packages/cli/src/commands/me.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/commands/upgrade.ts
  • packages/cli/src/utils/cli-context.ts
  • packages/cli/src/utils/index.ts
  • packages/cli/src/utils/output.ts
  • packages/cli/src/utils/templates.ts
  • packages/cli/test/cli-context.test.ts

Ignore post-`--` flags in context init, harden spinner cleanup, narrow ESLint fetch errors, standardize doctor JSON errors, and require push --yes for non-interactive confirmation.
@HugoRCD HugoRCD merged commit f9a0848 into main May 30, 2026
13 of 15 checks passed
@HugoRCD HugoRCD deleted the feat/cli-agent-friendly branch May 30, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant