Commit Graph

10 Commits

Author SHA1 Message Date
AlexYue 5e2e1dfe60 fix(plugin): detect symlinked monorepo sub-plugins in discoverPlugins (#487)
* fix(plugin): detect symlinked monorepo sub-plugins in discoverPlugins

discoverPlugins() used entry.isDirectory() to filter plugin directories,
but monorepo sub-plugins are installed as symlinks pointing into
~/.opencli/monorepos/. On most Node.js versions, isDirectory() returns
false for symlinks, causing monorepo plugin commands to be silently
skipped during discovery.

Add entry.isSymbolicLink() check so symlinked plugin directories are
properly discovered and their commands registered.

* fix(plugin): skip broken symlink discovery

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 14:12:54 +08:00
aresbit 806b358c0e fix windows chatwise connect (#405)
* Add

* test(chatwise): cover missing cdp endpoint guard

* refactor(chatwise): replace site special-case with command metadata

---------

Co-authored-by: ericyangbit <yangyang581@huawei.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-25 13:13:57 +08:00
AlexYue 4eeed2d4d7 ci: add cross-platform CI matrix (Linux/macOS/Windows) (#402)
* ci: add cross-platform matrix (Linux/macOS/Windows) to build, unit-test, adapter-test

Add OS matrix with ubuntu-latest, macos-latest, and windows-latest to
the build, unit-test, and adapter-test CI jobs. This ensures cross-
platform compatibility is verified on every push and PR.

Smoke tests remain Linux-only due to xvfb dependency.

Relates to #392 (Windows plugin path issues).

* test: replace hardcoded /tmp with os.tmpdir() for Windows compatibility

Fix Windows CI failures caused by hardcoded '/tmp' paths that don't
exist on Windows. Use os.tmpdir() which returns the correct platform-
specific temp directory on all operating systems.

Files fixed:
- src/engine.test.ts: 3 occurrences (mkdtemp, discoverClis path)
- src/plugin.test.ts: 2 occurrences (getCommitHash test, mock condition)

* test: fix remaining Windows path issues in test files

- engine.test.ts: use pathToFileURL().href for dynamic import paths
  (path.join produces backslashes on Windows, breaking ES module imports)
- download.test.ts: replace hardcoded '/tmp' with os.tmpdir() + path.join
2026-03-25 10:44:33 +08:00
AlexYue 1512016967 feat(plugin): add lifecycle hooks API (onStartup, onBeforeExecute, onAfterExecute) (#376)
* feat(plugin): add lifecycle hooks API (onStartup, onBeforeExecute, onAfterExecute)

Introduce a hooks system that allows plugins to tap into opencli's
execution lifecycle without modifying core code.

New files:
- src/hooks.ts: hook registration, emission, and globalThis singleton
- src/hooks.test.ts: 10 unit tests covering registration, ordering,
  error isolation, async support, and globalThis sharing

Modified files:
- src/execution.ts: emit onBeforeExecute/onAfterExecute around command execution
- src/main.ts: emit onStartup after discoverPlugins()
- src/registry-api.ts: export hooks API for plugin consumption

Example plugin: https://github.com/ByteYue/opencli-plugin-audit-log

* fix(discovery): load plugin files that register lifecycle hooks

The isCliModule() check only matched files containing 'cli(' calls,
silently skipping hook-only files like audit-hooks.ts that register
onBeforeExecute/onAfterExecute without any cli() command registration.

Renamed CLI_MODULE_PATTERN → PLUGIN_MODULE_PATTERN and extended the
regex to also match onStartup(, onBeforeExecute(, onAfterExecute(.

* fix(plugin): tighten lifecycle hook semantics

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-25 00:09:43 +08:00
jakevin 53699eb807 fix: harden security-sensitive execution paths (#335)
* fix(security): harden against command injection and sandbox escape

1. cli.ts: Remove auto-discover of arbitrary system binaries via denylist.
   Unknown commands now require explicit registration via `opencli register`.
   The previous denylist approach was trivially bypassable (bash, curl, etc.).

2. template.ts: Protect evalJsExpr against prototype chain escape.
   Block expressions containing constructor/prototype/__proto__/process/etc.
   Deep-copy context objects to sever prototype chains before passing to
   new Function().

3. external.ts: Expand shell operator detection in parseCommand to cover
   $(), $, #, \n, \r — preventing command substitution and comment injection.

4. fetch.ts: Use JSON.stringify for HTTP method in browser evaluate() instead
   of raw string interpolation, preventing JS injection via crafted method values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden security-sensitive execution paths

* chore: tighten template sandbox guard

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 11:28:22 +08:00
jakevin 7776db83d7 docs: consolidate adapter docs and discovery loading (#212) 2026-03-22 00:24:45 +08:00
AlexYue 1d39295f4b feat: plugin system (Stage 0-2)
* feat: plugin system (Stage 0-2)

- Stage 0: discoverPlugins() scans ~/.opencli/plugins/ at startup
- Stage 1: demo plugin repos (github-trending, hot-digest)
- Stage 2: opencli plugin install/uninstall/list commands
- package.json exports ./registry for TS plugin peerDep support
- 17 new/updated tests, tsc --noEmit clean

* fix: CDPBridge connect timeout unit mismatch (seconds vs ms)

opts.timeout is passed in seconds from runtime.ts but CDPBridge
was using it as milliseconds, causing instant timeout (30ms).

* feat: add registry-api public entry point for TS plugin peerDep support

- Add src/registry-api.ts: re-exports core registration API (cli, Strategy,
  getRegistry) without transitive side-effects, safe for plugin imports
- Update package.json exports: './registry' -> './dist/registry-api.js'
- Update src/registry.ts: use globalThis shared registry to ensure single
  instance across npm-linked plugin modules
- Update .gitignore for plugin-related artifacts

* fix: symlink host opencli into plugin node_modules on install

After npm install, replace the npm-installed @jackwener/opencli
with a symlink to the running host's package root. This ensures
TS plugins always resolve '@jackwener/opencli/registry' against
the host installation, avoiding version mismatches when the
published npm package lags behind.

* fix: transpile TS plugins to JS on install, deduplicate .ts/.js discovery

- installPlugin: after symlinking host opencli, transpile any .ts files
  to .js using esbuild from the host's node_modules/.bin/
- discoverPluginDir: skip .ts files when a .js sibling exists (production
  node cannot load .ts directly)
- scanPluginCommands: deduplicate basenames via Set to avoid showing
  'aggregate, aggregate' when both .ts and .js exist

* docs: add plugin system user guide

- New docs/guide/plugins.md covering:
  - Installation/uninstallation commands
  - Creating YAML plugins (zero-dep)
  - Creating TS plugins (with peerDep)
  - TS plugin install lifecycle (clone → deps → symlink → transpile)
  - Example plugins and troubleshooting
- Add Plugins to VitePress sidebar (EN + ZH)
- Link from getting-started.md Next Steps

* fix: address review issues in plugin system

- Security: replace execSync with execFileSync to prevent shell injection
- Replace deprecated npm --production with --omit=dev
- Tighten parseSource regex to [\w.-]+ to reject special chars
- Fix ZH sidebar plugin link (/guide/plugins → /zh/guide/plugins)
- Return plugin name from installPlugin() to avoid duplicated logic
- Use execFileSync for esbuild transpilation
- Fix misleading comment in linkHostOpencli

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-21 19:41:15 +08:00
jakevin d556eeb512 refactor: deep CLI layer architecture improvements (#152)
1. execution.ts: executeCommand auto-manages browser sessions
   - Signature simplified: (cmd, kwargs, debug) — callers dont handle browser
   - Internal runCommand() does lazy-loading, func/pipeline dispatch
   - shouldUseBrowserSession + domain pre-nav moved here from adapter

2. runtime.ts: add getBrowserFactory()
   - Eliminates 4x duplicate CDPBridge/BrowserBridge selection

3. serialization.ts: new module (79 LOC)
   - serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText
   - registry.ts re-exports for backward compat (160 -> 96 LOC)

4. cli.ts: format all built-in commands
   - Un-compressed explore/generate/cascade from 300-char single lines
   - Extracted inferHost() helper
   - Uses getBrowserFactory() instead of inline CDPBridge selection
   - Clear section comments

5. commanderAdapter.ts: pure thin adapter (113 LOC)
   - Only does: arg collection → executeCommand → renderOutput
   - Zero browser/session/strategy logic

All 244 tests pass. No behavioral changes.
2026-03-21 02:09:46 +08:00
jackwener 43b753fa02 fix(xiaohongshu): repair command args and request capture 2026-03-18 22:47:39 +08:00
jackwener 34a9bff2b3 refactor: eliminate code duplication, improve type safety, add tests
- NEW: src/interceptor.ts — unified XHR/Fetch interceptor (was duplicated 3x)
- NEW: src/version.ts — centralized PKG_VERSION (was duplicated 2x)
- NEW: src/constants.ts — shared VOLATILE_PARAMS, FIELD_ROLES etc.
- NEW: src/engine.test.ts, src/registry.test.ts — 14 new unit tests

- browser.ts: use shared normalizeEval, interceptor, withTimeoutMs, PKG_VERSION
- intercept.ts, tap.ts: use shared interceptor generators
- cascade.ts: extract shared buildFetchProbeJs (90% dedup)
- engine.ts: use InternalCliCommand (no more 'as any' casts)
- executor.ts: remove all 15 'as StepHandler' type casts
- runtime.ts: add withTimeoutMs, IBrowserFactory interface
- registry.ts: add InternalCliCommand type for internal fields
- validate.ts: add pipeline step name validation
- output.ts: remove 'null as any' and '.filter(() => true)' hacks
- explore.ts, synthesize.ts: use shared constants
- docs: fix V2EX commands (3→6), SKILL.md version, verify example

Tests: 88 passed (was 74), tsc --noEmit: 0 errors
2026-03-16 13:22:12 +08:00