Skip to content

feat(cli): auto-detect GitHub shorthand (owner/repo) in positional arguments#1628

Open
serhiizghama wants to merge 4 commits into
yamadashy:mainfrom
serhiizghama:feat/auto-detect-github-shorthand
Open

feat(cli): auto-detect GitHub shorthand (owner/repo) in positional arguments#1628
serhiizghama wants to merge 4 commits into
yamadashy:mainfrom
serhiizghama:feat/auto-detect-github-shorthand

Conversation

@serhiizghama

Copy link
Copy Markdown
Contributor

Motivation

Follow-up to #1120. The explicit URL part shipped in v1.12.0 (#1145), and the remaining piece is the owner/repo shorthand — held back by a fair concern:

a typo like src/utilssrc/uitls could match the pattern and trigger an unintended clone

This PR is a concrete proposal for that remaining piece, designed so the typo scenario can never reach a clone attempt.

What this changes

repomix yamadashy/repomix now works without --remote. The shorthand is treated as remote only when all three hold:

  1. The argument does not exist as a local path — any existing file/directory always wins, so current local workflows are untouched.
  2. The argument matches the strict owner/repo pattern (same isValidShorthand used by --remote).
  3. The repository is confirmed reachable on GitHub via a HEAD-only git ls-remote probe before any clone/download starts.

A mistyped local path like src/uitls fails the probe and falls through to the regular "Target path does not exist" error — exactly what happens today. When detection does kick in, it prints an explicit notice with an escape hatch:

Detected GitHub repository shorthand: sindresorhus/is-plain-obj (prefix with ./ to treat it as a local path)

The probe only runs in the case that would otherwise be guaranteed to fail (single arg, shorthand-shaped, no such local path), so no network cost is added to any currently-working invocation. Detection is skipped in --stdin mode, which rejects positional directory arguments.

Why this approach

  • The probe uses git ls-remote -- <url> HEAD (new execLsRemoteHead) rather than the existing --heads --tags variant, so the response stays tiny even for repositories with thousands of refs.
  • Probe failures (non-existent, private without credentials, offline) are treated as "not a remote", never as an error — GIT_TERMINAL_PROMPT=0 is already set for remote git commands, so there is no interactive hang. Private-repo users keep using explicit --remote owner/repo.
  • isValidShorthand moved into the lightweight gitRemoteUrl.ts module (same pattern as isExplicitRemoteUrl, re-exported from gitRemoteParse.ts) so the CLI entrypoint doesn't pull in git-url-parse at startup. Public API is unchanged.
  • An alternative was probing the GitHub REST API, but ls-remote avoids rate limits and extra HTTP plumbing, and respects existing git credential helpers.

Testing

  • Unit tests for the full decision matrix in cliRun.test.ts: reachable shorthand → remote action; unreachable shorthand → local handling; existing local path that matches the pattern (src/cli) → local handling with no probe; --stdin → no probe.
  • Unit tests for execLsRemoteHead (HEAD-only args, error propagation, dangerous-URL rejection) and checkRemoteRepoExists (true/false/validation).
  • Manually verified with the built CLI: repomix sindresorhus/is-plain-obj detects and packs via archive download; a non-existent owner/repo and an existing local src/utils directory both behave exactly as before.
  • Full suite: 131 files / 1341 tests passing; biome, oxlint and tsgo clean.

Closes #1120

…dule

Keeps shorthand pattern matching available to the CLI entrypoint without
pulling in the heavy git-url-parse dependency, following the same approach
as isExplicitRemoteUrl. Re-exported from gitRemoteParse to preserve the
public API.
…guments

Allow running `repomix owner/repo` without the --remote flag.

Shorthand is ambiguous with relative local paths, so it is only treated
as remote when the argument does not exist as a local path and the
repository is confirmed reachable on GitHub via a HEAD-only
`git ls-remote` probe. A mistyped local path such as `src/uitls` fails
the probe and falls through to the regular local-path error instead of
triggering an unintended clone attempt.
@serhiizghama serhiizghama requested a review from yamadashy as a code owner June 6, 2026 08:28
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Worried about impact? Review this PR in Change Stack to explore blast radius before you approve or request changes.

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f1b137d-a5c4-4ef3-81e1-fadb73cb489f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds GitHub shorthand (owner/repo) auto-detection to the CLI. When a positional argument matches the shorthand pattern, the CLI now checks if it exists locally; if not, it probes GitHub reachability and routes to remote action if found, otherwise defaults to local handling. Tests validate all code paths.

Changes

GitHub Shorthand Auto-Detection for CLI Arguments

Layer / File(s) Summary
Shorthand validation utility
src/core/git/gitRemoteUrl.ts, src/core/git/gitRemoteParse.ts
gitRemoteUrl.ts introduces isValidShorthand with an owner/repo regex pattern. gitRemoteParse.ts moves from inline validation to importing and re-exporting the validator from gitRemoteUrl.ts, consolidating shorthand detection in one location.
Remote repository reachability check
src/core/git/gitCommand.ts, src/core/git/gitRemoteHandle.ts, tests/core/git/gitCommand.test.ts, tests/core/git/gitRemoteHandle.test.ts
execLsRemoteHead in gitCommand.ts validates Git URLs and runs git ls-remote HEAD to probe repository existence. checkRemoteRepoExists in gitRemoteHandle.ts wraps this with error handling and trace logging, returning boolean. Both functions are tested for success/failure paths and security validation (rejecting dangerous URL parameters).
CLI shorthand auto-detection integration
src/cli/cliRun.ts, tests/cli/cliRun.test.ts
cliRun.ts imports shorthand validation and now detects owner/repo positional arguments. After checking local path existence and outside stdin mode, it probes GitHub via checkRemoteRepoExists and routes to remote action if reachable; otherwise defaults to local handling. Tests verify all outcomes: remote execution when reachable, local fallback when unreachable, local-path precedence, and no probing in stdin mode.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • yamadashy/repomix#1145: Introduces the foundation for positional-argument remote auto-detection via explicit URL detection in cliRun.ts and gitRemoteParse.ts; this PR builds on that pattern to add shorthand probing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: auto-detection of GitHub shorthand (owner/repo) format in positional arguments.
Description check ✅ Passed The description is comprehensive, covering motivation, implementation details, testing approach, and design rationale. However, it does not explicitly address the checklist template items (npm run test, npm run lint).
Linked Issues check ✅ Passed The PR fully addresses issue #1120 by implementing auto-detection of GitHub shorthand (owner/repo) with proper precedence: explicit --remote flag, local path existence check, shorthand pattern validation, and remote reachability probe via git ls-remote.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing GitHub shorthand auto-detection: CLI detection logic, git ls-remote helpers, shorthand validation consolidation, and corresponding test coverage. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces automatic detection of GitHub repository shorthands (owner/repo) in CLI positional arguments. When a shorthand is detected and does not exist as a local path, a lightweight HEAD-only git ls-remote probe is used to verify its existence on GitHub before triggering a remote action. The reviewer feedback focuses on improving robustness: disabling interactive Git Credential Manager prompts and reducing the probe timeout to 5 seconds, ensuring absolute paths (especially on Windows) are not misidentified as shorthands, and explicitly checking for ENOENT errors during local path verification to prevent permission errors from triggering remote probes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/core/git/gitCommand.ts Outdated
validateGitUrl(url);

try {
const result = await deps.execFileAsync('git', ['ls-remote', '--', url, 'HEAD'], gitRemoteOpts);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

When probing for repository existence, we should ensure that Git Credential Manager (GCM) does not trigger interactive GUI prompts/popups if the repository is private or does not exist. This is especially important for auto-detection, as a simple typo in a local path (e.g., repomix src/uitls) could otherwise trigger an annoying login popup.\n\nAdditionally, the default gitRemoteOpts timeout is 30 seconds. For an automatic background probe triggered by a potential typo, waiting 30 seconds on a slow or offline network before falling back to the local path error is a poor user experience. Reducing the timeout to 5 seconds keeps the CLI responsive.

    const result = await deps.execFileAsync('git', ['ls-remote', '--', url, 'HEAD'], {\n      ...gitRemoteOpts,\n      timeout: 5000,\n      env: { ...gitRemoteOpts.env, GCM_INTERACTIVE: 'never' },\n    });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — addressed in 3f65dad. The probe now uses dedicated opts: 5s timeout and GCM_INTERACTIVE: 'never' on top of the existing GIT_TERMINAL_PROMPT: '0', so a mistyped path never hangs on slow networks or triggers credential manager popups.

Comment thread src/cli/cliRun.ts
// A mistyped local path (e.g. `src/uitls`) fails the probe and falls through to the
// regular local-path handling instead of triggering an unintended clone attempt.
// Skipped in stdin mode, where positional directory arguments are rejected.
if (directories.length === 1 && !options.stdin && isValidShorthand(directories[0])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

On Windows, absolute paths can be specified using forward slashes (e.g., C:/project). Such paths will match the isValidShorthand regex (since C is a valid owner pattern and project is a valid repo pattern). If the specified absolute path does not exist locally, the CLI would incorrectly attempt to probe it as a GitHub shorthand (e.g., querying https://github.com/C/project.git).\n\nTo prevent this, we should explicitly ensure the argument is not an absolute path before performing shorthand auto-detection.

Suggested change
if (directories.length === 1 && !options.stdin && isValidShorthand(directories[0])) {
if (directories.length === 1 && !options.stdin && !path.isAbsolute(directories[0]) && isValidShorthand(directories[0])) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

C:/project can't actually match the shorthand pattern: VALID_NAME_PATTERN ([a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?) doesn't allow :, so the owner part C: fails the regex — same for POSIX absolute paths, which start with /. Added a regression test (should not treat Windows-style absolute path as shorthand) in 3f65dad to lock this in.

Comment thread src/cli/cliRun.ts
Comment on lines +309 to +312
const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(
() => true,
() => false,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If a local path exists but is inaccessible due to permission restrictions (e.g., EACCES or EPERM), fs.access will reject. Currently, any rejection is treated as "path does not exist", which would cause the CLI to incorrectly perform a remote GitHub probe for a local path that actually exists.\n\nWe should explicitly check if the error code is ENOENT to ensure we only fall back to the remote probe when the path is truly missing.

    const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(\n      () => true,\n      (err: any) => err?.code !== 'ENOENT',\n    );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3f65dadfs.access rejection is now only treated as "missing" for ENOENT/ENOTDIR; EACCES/EPERM keep local-path precedence. Covered by a new test with a mocked EACCES rejection.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/cli/cliRun.ts`:
- Around line 309-312: The current fs.access usage treats any error as "missing"
which misclassifies permission-denied errors (EACCES/EPERM); update the local
path existence check around fs.access(path.resolve(cwd, directories[0])) so that
it distinguishes error.code === 'EACCES' || error.code === 'EPERM' and returns
true (treat as local) for those cases while still returning false for ENOENT;
adjust the Promise.then/.catch or switch to try/catch around await fs.access in
the same block that sets localPathExists to ensure permission errors are
considered existing paths.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d51b476-79ba-4f72-b36f-62bda0ae313a

📥 Commits

Reviewing files that changed from the base of the PR and between edbe25e and e455809.

📒 Files selected for processing (8)
  • src/cli/cliRun.ts
  • src/core/git/gitCommand.ts
  • src/core/git/gitRemoteHandle.ts
  • src/core/git/gitRemoteParse.ts
  • src/core/git/gitRemoteUrl.ts
  • tests/cli/cliRun.test.ts
  • tests/core/git/gitCommand.test.ts
  • tests/core/git/gitRemoteHandle.test.ts

Comment thread src/cli/cliRun.ts
Comment on lines +309 to +312
const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(
() => true,
() => false,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat permission-denied paths as local, not “missing”.

On Line 309, any fs.access failure is treated as non-existent. EACCES/EPERM means the path exists but is inaccessible, and this can incorrectly trigger GitHub probing and remote execution, breaking local-path precedence.

Suggested fix
-    const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(
-      () => true,
-      () => false,
-    );
+    const resolvedInputPath = path.resolve(cwd, directories[0]);
+    const localPathExists = await fs.access(resolvedInputPath).then(
+      () => true,
+      (error: NodeJS.ErrnoException) => !['ENOENT', 'ENOTDIR'].includes(error.code ?? ''),
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(
() => true,
() => false,
);
const resolvedInputPath = path.resolve(cwd, directories[0]);
const localPathExists = await fs.access(resolvedInputPath).then(
() => true,
(error: NodeJS.ErrnoException) => !['ENOENT', 'ENOTDIR'].includes(error.code ?? ''),
);
🤖 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 `@src/cli/cliRun.ts` around lines 309 - 312, The current fs.access usage treats
any error as "missing" which misclassifies permission-denied errors
(EACCES/EPERM); update the local path existence check around
fs.access(path.resolve(cwd, directories[0])) so that it distinguishes error.code
=== 'EACCES' || error.code === 'EPERM' and returns true (treat as local) for
those cases while still returning false for ENOENT; adjust the
Promise.then/.catch or switch to try/catch around await fs.access in the same
block that sets localPathExists to ensure permission errors are considered
existing paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3f65dad — rejection is now treated as "missing" only for ENOENT/ENOTDIR, so permission-denied paths keep local precedence. Test added.

…robe fail fast

Review follow-up: EACCES/EPERM from fs.access means the path exists but is
inaccessible, so it must keep local-path precedence instead of triggering a
remote probe. The probe itself now uses a 5s timeout and GCM_INTERACTIVE=never
so a mistyped path never hangs on slow networks or credential manager popups.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-detect URLs and treat as --remote for CLI

1 participant