Skip to content

feat(runtime): add project-local adapter and plugin discovery under ./.opencli/#1426

Open
Benjamin-eecs wants to merge 1 commit into
jackwener:mainfrom
Benjamin-eecs:feat/disable-builtins-project-isolation
Open

feat(runtime): add project-local adapter and plugin discovery under ./.opencli/#1426
Benjamin-eecs wants to merge 1 commit into
jackwener:mainfrom
Benjamin-eecs:feat/disable-builtins-project-isolation

Conversation

@Benjamin-eecs
Copy link
Copy Markdown
Contributor

@Benjamin-eecs Benjamin-eecs commented May 9, 2026

Description

Adds a project-local discovery step that loads adapters from ./.opencli/clis/<site>/<command>.js and plugins from ./.opencli/plugins/<plugin>/ when opencli runs inside a project. Mirrors the existing user-level layer at ~/.opencli/ (same import surface, same compat shim mechanism), so a repo can check project-specific adapters into VCS and they override built-ins for that working directory.

Closes #1423.

Discovery order

Last wins on collision (mirrors existing registerCommand semantics in src/registry.ts):

built-in
  -> ~/.opencli/clis
  -> ./.opencli/clis
  -> ~/.opencli/plugins
  -> ./.opencli/plugins

A project adapter shadows a user adapter, which shadows a built-in. External CLIs (gh, ntn, tg, discord, wx, ...) are passthrough binaries on a separate registration path (src/external.ts / external-clis.yaml) and are unaffected.

Scope

Implements the project-isolation half of #1423 only. The disable-builtins half is left out; external-CLI replacement (#1559, #1544) is already cutting the built-in surface, so a runtime flag can wait for a follow-up if it's still useful after that migration settles.

Type of Change

  • ✨ New feature
  • 🐛 Bug fix
  • 🌐 New site adapter
  • 📝 Documentation
  • ♻️ Refactor
  • 🔧 CI / build / tooling

Checklist

  • I ran the checks relevant to this PR
  • I updated tests or docs if needed
  • I included output or screenshots when useful

Implementation

src/discovery.ts

  • projectOpenCliDir(cwd?) / projectClisDir(cwd?) / projectPluginsDir(cwd?): path helpers, default to process.cwd(). Lazy resolution so they pick up the actual working directory at invocation time.
  • ensureProjectCliCompatShims(cwd?): thin wrapper that calls existing ensureUserCliCompatShims(baseDir) with a project-local baseDir, but only when ./.opencli/ already exists. Silent no-op when the project has no .opencli/ directory, so projects that don't use the feature pay zero startup cost.
  • discoverPlugins(dir = PLUGINS_DIR): widen the existing signature with an optional dir parameter so project-local plugins can be loaded from ./.opencli/plugins/ using the same flat-file scan.

src/main.ts

  • PROJECT_CLIS / PROJECT_PLUGINS constants.
  • ensureProjectCliCompatShims() added to the parallel startup block alongside the existing user-level setup.
  • await discoverClis(PROJECT_CLIS) after the user-CLI pass.
  • await discoverPlugins(PROJECT_PLUGINS) after the default plugins pass.

src/discovery-project.test.ts

Three tests covering:

  • Path helpers resolve relative to a given cwd.
  • discoverClis(projectDir) registers an adapter dropped into a project-local site directory.
  • discoverPlugins(projectDir) registers a plugin dropped into a project-local plugin directory.

docs/guide/extending-opencli.md

Adds a "Project-local adapters and plugins" section documenting the ./.opencli/ layout and the discovery order. Adds a row to the extension-paths table at the top.

Screenshots / Output

$ mkdir -p my-project/.opencli/clis/hello
$ cat > my-project/.opencli/clis/hello/world.js <<'JS'
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
  site: 'hello', name: 'world', access: 'read',
  description: 'project-local greeting',
  strategy: Strategy.PUBLIC, browser: false,
  func: async () => [{ msg: 'hi from ./.opencli' }],
});
JS

$ cd my-project && opencli hello world -f json
[{ "msg": "hi from ./.opencli" }]

A project adapter with the same site/command as a built-in overrides the built-in for the duration of cd my-project; leave the directory and the built-in is back.

Test plan

  • npx vitest run src/discovery-project.test.ts: 3 passed
  • npm run build: manifest compiles, 813 entries, no path leaks
  • npx tsc --noEmit: clean
  • Project-local adapter registers and runs when ./.opencli/clis/<site>/<cmd>.js is present
  • Project without ./.opencli/ directory pays zero startup cost (early-return in ensureProjectCliCompatShims)
  • Reviewer to confirm precedence behaviour on a project that shadows a built-in (./.opencli/clis/twitter/search.js should win over the bundled twitter/search.js).

Copilot AI review requested due to automatic review settings May 9, 2026 08:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds runtime gating for built-in adapter discovery (disable all or filter by site) and introduces project-local discovery under ./.opencli/ so repositories can ship adapters/plugins alongside source.

Changes:

  • Load ~/.opencli/config.json + env (OPENCLI_DISABLE_BUILTINS, OPENCLI_DISABLED_SITES) to skip built-ins entirely or filter specific built-in sites.
  • Add project-local discovery directories (./.opencli/clis, ./.opencli/plugins) and a compat shim for project-local package-export imports.
  • Add unit tests covering config resolution, discovery filtering (manifest + FS scan), and custom plugin dir discovery; update docs with new extension path and config.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/main.ts Loads runtime config, conditionally discovers built-ins with filtering, and adds project-local CLI/plugin discovery to the startup order.
src/discovery.ts Adds project-local path helpers + compat shim; updates discoverClis API and implements disabledSites filtering; allows discoverPlugins(dir) for project-local plugins.
src/discovery-filter.test.ts Tests site filtering for both FS and manifest fast paths, plus project-local discovery helper behavior.
src/config.ts Implements config/env parsing and normalization for disableBuiltins and disabledSites.
src/config.test.ts Verifies config/env precedence and parsing behaviors (truthy env, additive disabled sites, malformed JSON handling).
docs/guide/extending-opencli.md Documents project-local adapters/plugins and built-in disabling/filtering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config.ts Outdated
return null;
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === 'ENOENT') return null;
Comment on lines +141 to +146
```text
my-project/
.opencli/
clis/<site>/<command>.js # adapters local to this project
plugins/<plugin>/<file>.js # plugins local to this project
```
plugins/<plugin>/<file>.js # plugins local to this project
```

Project-local commands are discovered when `opencli` runs from a directory that contains `./.opencli/`. The discovery order is `built-in → user (~/.opencli) → project (./.opencli)`, so a project adapter overrides a user adapter, which in turn overrides a built-in adapter with the same `site/command`.
@Benjamin-eecs Benjamin-eecs force-pushed the feat/disable-builtins-project-isolation branch from 55c5f38 to e4cfbf8 Compare May 10, 2026 07:39
@Benjamin-eecs Benjamin-eecs changed the title feat(runtime): disable built-in adapters + project-local CLI isolation (#1423) feat(runtime): disable built-in adapters + project-local CLI isolation May 10, 2026
@Benjamin-eecs Benjamin-eecs force-pushed the feat/disable-builtins-project-isolation branch 17 times, most recently from 8e80705 to 3baa379 Compare May 14, 2026 11:24
@Benjamin-eecs Benjamin-eecs changed the title feat(runtime): disable built-in adapters + project-local CLI isolation feat(runtime): add project-local adapter and plugin discovery under ./.opencli/ May 14, 2026
@Benjamin-eecs Benjamin-eecs force-pushed the feat/disable-builtins-project-isolation branch 2 times, most recently from cf7b422 to fc8668f Compare May 14, 2026 11:33
@Benjamin-eecs Benjamin-eecs force-pushed the feat/disable-builtins-project-isolation branch 2 times, most recently from 6bfca56 to 853e1fd Compare May 14, 2026 15:46
…/.opencli/

Adds a project-local discovery step that loads adapters from
`./.opencli/clis/<site>/<command>.js` and plugins from
`./.opencli/plugins/<plugin>/` when opencli runs inside a project.
Mirrors the existing user-level layer at `~/.opencli/`: same import
surface (`@jackwener/opencli/registry`), same compat shim mechanism,
so a repo can check project-specific adapters into VCS and they
override built-ins for that working directory.

Discovery order (last wins on collision):
  built-in
    -> ~/.opencli/clis
      -> ./.opencli/clis
        -> ~/.opencli/plugins
          -> ./.opencli/plugins

Pure addition. No flags, env vars, or config file. External CLIs
(gh, ntn, tg, ...) are passthrough binaries on a separate
registration path and are unaffected.

Closes the project-isolation half of jackwener#1423. The "disable built-ins"
half is intentionally deferred: recent work (jackwener#1559 notion -> ntn
migration, jackwener#1544 -cli suffix drop) suggests built-in adapter scope
will shrink organically via external-CLI replacement, which
addresses the underlying AI-agent tool-list concern more directly
than a runtime flag.

Changes:
- src/discovery.ts: add projectOpenCliDir / projectClisDir /
  projectPluginsDir helpers, add ensureProjectCliCompatShims
  (reuses ensureUserCliCompatShims with a project root), widen
  discoverPlugins to take an optional dir parameter so project
  plugins can be discovered from ./.opencli/plugins/.
- src/main.ts: thread the new helpers, call
  ensureProjectCliCompatShims in the parallel startup block, run
  discoverClis(PROJECT_CLIS) after USER_CLIS, and
  discoverPlugins(PROJECT_PLUGINS) after the default plugins pass.
- docs/guide/extending-opencli.md: document the project-local
  layout and discovery order.
- src/discovery-project.test.ts: 3 tests covering the path helpers,
  project-local adapter discovery, and project-local plugin
  discovery.
@Benjamin-eecs Benjamin-eecs force-pushed the feat/disable-builtins-project-isolation branch from 853e1fd to fb62bef Compare May 15, 2026 08:29
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.

Feature: disable built-in adapters + project-level CLI isolation

2 participants