Skip to content

refactor(cli): migrate remaining CJS modules to TypeScript#1306

Merged
cv merged 9 commits intomainfrom
refactor/migrate-cjs-to-ts-1298
Apr 2, 2026
Merged

refactor(cli): migrate remaining CJS modules to TypeScript#1306
cv merged 9 commits intomainfrom
refactor/migrate-cjs-to-ts-1298

Conversation

@prekshivyas
Copy link
Copy Markdown
Contributor

@prekshivyas prekshivyas commented Apr 2, 2026

Summary

Closes #1298. Part of #924 shell consolidation.

  • resolve-openshell.js → TS migration with ResolveOpenshellOptions interface; bin/lib shim re-exports from compiled dist
  • version.ts → new module reading root package.json + git describe; wired into bin/nemoclaw.js for --version and help (output unchanged — uses plain semver)
  • chat-filter.ts → new utility extracting ALLOWED_CHAT_IDS parsing from telegram-bridge; wired into scripts/telegram-bridge.js

Each module follows the established pattern:

  1. TS source in nemoclaw/src/lib/ with flat interfaces
  2. Thin CJS re-export shim in bin/lib/require("../../nemoclaw/dist/lib/...")
  3. Co-located tests in src/lib/*.test.ts importing from ../../dist/lib/ for coverage attribution

Test plan

  • npx vitest run --project plugin — 216 tests pass (includes new lib tests)
  • npx vitest run --project cli — 336 tests pass (shims work transparently)
  • npx eslint nemoclaw/src/lib/ — clean
  • node bin/nemoclaw.js --version — output unchanged (nemoclaw v0.1.0)
  • Pre-commit hooks pass (prettier, eslint, gitleaks, vitest, tsc)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added utilities for chat allowlist handling, openshell binary resolution, and robust version detection.
  • Refactor

    • Consolidated runtime implementations into the compiled distribution to streamline module surfaces.
  • Tests

    • Added comprehensive tests covering chat filtering, shell resolution fallbacks, and version-detection behavior.

Migrate resolve-openshell.js to TypeScript and create two new TS modules
(version.ts, chat-filter.ts), completing the "everything not blocked is
converted" goal for #1298 / #924 shell consolidation.

For each module:
- TS implementation in nemoclaw/src/lib/ with typed interfaces
- Thin CJS re-export shim in bin/lib/ pointing to compiled dist/
- Co-located tests importing from ../../dist/lib/ for coverage attribution

Consumers updated:
- bin/nemoclaw.js uses getVersion() for --version and help output
- scripts/telegram-bridge.js uses parseAllowedChatIds/isChatAllowed

Closes #1298

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 268735f9-2156-41a7-91df-b9712eebe11b

📥 Commits

Reviewing files that changed from the base of the PR and between 564d4dc and f66875d.

📒 Files selected for processing (1)
  • src/lib/resolve-openshell.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/resolve-openshell.test.ts

📝 Walkthrough

Walkthrough

Three CLI utility modules (chat-filter, resolve-openshell, version) were implemented in TypeScript under src/lib/ with tests; the original bin/lib/*.js files were replaced by thin CommonJS shims that re-export the compiled dist/lib/* modules.

Changes

Cohort / File(s) Summary
TypeScript Implementations
src/lib/chat-filter.ts, src/lib/resolve-openshell.ts, src/lib/version.ts
Added TS implementations: chat ID parsing/allowlist, openshell binary resolution with DI-friendly options and executable checks, and version resolution (git → .version → package.json). Review: resolve-openshell exec/fs logic and version's git/file fallbacks.
Test Suites
src/lib/chat-filter.test.ts, src/lib/resolve-openshell.test.ts, src/lib/version.test.ts
New Vitest suites covering parsing, allowlist checks, openshell resolution/fallbacks with injected mocks, and version source prioritization.
CommonJS Shims
bin/lib/chat-filter.js, bin/lib/resolve-openshell.js, bin/lib/version.js
Replaced inline implementations with thin re-export shims: module.exports = require("../../dist/lib/..."), delegating runtime behavior to compiled outputs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code, neat and spry,
Types and tests beneath the sky.
Shims now point where builds reside,
Paths and versions safe to guide.
A little hop — the CLI’s spry!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: migrating three remaining CommonJS modules to TypeScript as part of CLI refactoring.
Linked Issues check ✅ Passed All objectives from #1298 are met: three CJS modules migrated to TS with flat interfaces, thin re-export shims in bin/lib/, co-located tests added, and existing consumers updated.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #1298 requirements: TS implementations (src/lib/), re-export shims (bin/lib/), and co-located tests. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/migrate-cjs-to-ts-1298

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

Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (2)
nemoclaw/src/lib/resolve-openshell.test.ts (1)

85-93: Consider consolidating with the similar test case above.

This test appears functionally identical to "returns null when openshell not found anywhere" (lines 66-73). Both invoke resolveOpenshell with commandVResult: null and checkExecutable: () => false; passing home: undefined explicitly has no effect since opts.home ?? process.env.HOME treats missing and explicit undefined identically.

If the intent is to document the explicit undefined case, a brief comment would clarify. Otherwise, this could be removed to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/lib/resolve-openshell.test.ts` around lines 85 - 93, The two
tests in resolve-openshell.test.ts are duplicated: both call resolveOpenshell
with commandVResult: null and checkExecutable: () => false (the second also
passes home: undefined which is redundant). Remove the redundant test ("returns
null for null commandVResult with no executable found") or consolidate by
keeping a single test (e.g., "returns null when openshell not found anywhere")
and, if you want to document the explicit undefined case, add a one-line comment
in that retained test noting that passing home: undefined is equivalent to
omitting the option; ensure the retained test still calls resolveOpenshell and
asserts toBeNull so resolveOpenshell behavior is covered once.
bin/nemoclaw.js (1)

403-406: Skip git probing in plain semver CLI output paths.

Line 403 and Line 475 only need version, but getVersion() default behavior executes a synchronous git describe --tags --always --dirty call. Pass gitDescribeResult: null at these call sites to keep help/version output fast and avoid unnecessary subprocess execution.

Proposed fix
 function help() {
-  const { version } = getVersion();
+  const { version } = getVersion({ gitDescribeResult: null });
   console.log(`
   ${B}${G}NemoClaw${R}  ${D}v${version}${R}
@@
       case "--version":
       case "-v": {
-        const { version } = getVersion();
+        const { version } = getVersion({ gitDescribeResult: null });
         console.log(`nemoclaw v${version}`);
         break;
       }

Also applies to: 475-476

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/nemoclaw.js` around lines 403 - 406, The CLI prints plain semver output
but calls getVersion() which runs a synchronous git describe subprocess; to
avoid git probing in these fast paths, call getVersion({ gitDescribeResult: null
}) when extracting version (e.g., the lines using const { version } =
getVersion() around the help/version output and the second occurrence near line
475) so only the version is returned without executing git describe; update both
call sites to pass the gitDescribeResult: null option.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nemoclaw/src/lib/version.test.ts`:
- Around line 4-5: Tests use hardcoded POSIX path strings like
'/test-dir/package.json' which breaks cross-platform behavior; update the mocks
in version.test.ts to construct expected storage keys using node:path join
(e.g., import { join } from "node:path") and use join("test-dir",
"package.json") wherever the test currently uses '/test-dir/package.json' so the
mocked keys match the implementation that uses join() (refer to the test file
and the getVersion import for where these mocks are set).

---

Nitpick comments:
In `@bin/nemoclaw.js`:
- Around line 403-406: The CLI prints plain semver output but calls getVersion()
which runs a synchronous git describe subprocess; to avoid git probing in these
fast paths, call getVersion({ gitDescribeResult: null }) when extracting version
(e.g., the lines using const { version } = getVersion() around the help/version
output and the second occurrence near line 475) so only the version is returned
without executing git describe; update both call sites to pass the
gitDescribeResult: null option.

In `@nemoclaw/src/lib/resolve-openshell.test.ts`:
- Around line 85-93: The two tests in resolve-openshell.test.ts are duplicated:
both call resolveOpenshell with commandVResult: null and checkExecutable: () =>
false (the second also passes home: undefined which is redundant). Remove the
redundant test ("returns null for null commandVResult with no executable found")
or consolidate by keeping a single test (e.g., "returns null when openshell not
found anywhere") and, if you want to document the explicit undefined case, add a
one-line comment in that retained test noting that passing home: undefined is
equivalent to omitting the option; ensure the retained test still calls
resolveOpenshell and asserts toBeNull so resolveOpenshell behavior is covered
once.
🪄 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: 80b5dc42-8876-4e4d-91e4-0f6a18bc9db2

📥 Commits

Reviewing files that changed from the base of the PR and between cb668d7 and ef21237.

📒 Files selected for processing (11)
  • bin/lib/chat-filter.js
  • bin/lib/resolve-openshell.js
  • bin/lib/version.js
  • bin/nemoclaw.js
  • nemoclaw/src/lib/chat-filter.test.ts
  • nemoclaw/src/lib/chat-filter.ts
  • nemoclaw/src/lib/resolve-openshell.test.ts
  • nemoclaw/src/lib/resolve-openshell.ts
  • nemoclaw/src/lib/version.test.ts
  • nemoclaw/src/lib/version.ts
  • scripts/telegram-bridge.js

Copy link
Copy Markdown
Contributor

@cv cv left a comment

Choose a reason for hiding this comment

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

Thanks for picking this up @prekshivyas — the scope and approach are right.

One structural issue to fix, then this is close to ready:

TS files are in the wrong directory. All three modules are in nemoclaw/src/lib/ — that is the OpenClaw plugin directory (Commander-based, separate tsconfig, different runtime). CLI modules belong in src/lib/ at the repo root, compiled via tsconfig.src.jsondist/lib/. The bin/lib/ shims point at ../../dist/lib/, not into the plugin tree.

See any of the recently merged migrations for the pattern:

  • src/lib/preflight.ts (#1262)
  • src/lib/inference-config.ts (#1265)
  • src/lib/nim.ts (#1271)

The pattern is:

src/lib/foo.ts          ← TS source
src/lib/foo.test.ts     ← co-located test (imports from ../../dist/lib/foo)
bin/lib/foo.js          ← thin shim: module.exports = require("../../dist/lib/foo")

Also needs a rebase — there are merge conflicts with main.

The actual module implementations look correct. Once the directory is fixed and conflicts resolved, this should be straightforward to land.

Prekshi Vyas and others added 4 commits April 2, 2026 10:05
Migrate resolve-openshell.js to TypeScript and create two new TS modules
(version.ts, chat-filter.ts), completing the "everything not blocked is
converted" goal for #1298 / #924 shell consolidation.

For each module:
- TS implementation in nemoclaw/src/lib/ with typed interfaces
- Thin CJS re-export shim in bin/lib/ pointing to compiled dist/
- Co-located tests importing from ../../dist/lib/ for coverage attribution

Consumers updated:
- bin/nemoclaw.js uses getVersion() for --version and help output
- scripts/telegram-bridge.js uses parseAllowedChatIds/isChatAllowed

Closes #1298

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback from @cv and CodeRabbit:

- Move TS files from nemoclaw/src/lib/ to src/lib/ (repo root),
  compiled via tsconfig.src.json to CJS in dist/lib/
- Preserve getVersion() string return type (matching existing API)
- Preserve isChatAllowed(allowedChats, chatId) array-based signature
- Update bin/lib/ shims to point to dist/lib/ (not nemoclaw/dist/)
- Use platform-neutral paths in version tests (real temp dirs)
- Restore bin/nemoclaw.js and telegram-bridge.js to main versions
  (no consumer changes needed — API is backward-compatible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
src/lib/version.test.ts (1)

13-35: Cover the git-first branch with a regression test.

Every case here bypasses git by construction, so tag-format regressions in getVersion() can ship without this suite failing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/version.test.ts` around lines 13 - 35, Add a regression test that
exercises the git-first branch of getVersion by initializing a real git repo in
the existing testDir, committing the package.json, creating an annotated tag
like "v2.0.0" (set user.name/user.email locally so commit succeeds), ensure no
.version file is present, call getVersion({ rootDir: testDir }) and assert it
returns "2.0.0" (or the tag without "v" if getVersion strips it), then clean up
the .git directory; reference getVersion and the test file version.test.ts so
the new test sits alongside the other cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/version.ts`:
- Around line 25-30: The current call to git describe strips the leading "v"
from any output, which turns a non-exact describe like "v1.2.3-4-gHASH" into
"1.2.3-4-gHASH" and changes development commit output; modify getVersion() so
you first run git describe --tags --match "v*" --exact-match (store e.g.
rawExact) and if that returns a value return rawExact.replace(/^v/, "");
otherwise run the fallback git describe --tags --match "v*" (the existing
command that may return the -N-gHASH form) and return that raw string unchanged;
update the execFileSync calls that produce raw to implement this two-step logic
(refer to getVersion, rawExact/raw variables).

---

Nitpick comments:
In `@src/lib/version.test.ts`:
- Around line 13-35: Add a regression test that exercises the git-first branch
of getVersion by initializing a real git repo in the existing testDir,
committing the package.json, creating an annotated tag like "v2.0.0" (set
user.name/user.email locally so commit succeeds), ensure no .version file is
present, call getVersion({ rootDir: testDir }) and assert it returns "2.0.0" (or
the tag without "v" if getVersion strips it), then clean up the .git directory;
reference getVersion and the test file version.test.ts so the new test sits
alongside the other cases.
🪄 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: ebd67ddc-049e-46a1-a880-1e09862abe5b

📥 Commits

Reviewing files that changed from the base of the PR and between ef21237 and 564d4dc.

📒 Files selected for processing (9)
  • bin/lib/chat-filter.js
  • bin/lib/resolve-openshell.js
  • bin/lib/version.js
  • src/lib/chat-filter.test.ts
  • src/lib/chat-filter.ts
  • src/lib/resolve-openshell.test.ts
  • src/lib/resolve-openshell.ts
  • src/lib/version.test.ts
  • src/lib/version.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/lib/resolve-openshell.js

Comment on lines +25 to +30
const raw = execFileSync("git", ["describe", "--tags", "--match", "v*"], {
cwd: root,
encoding: "utf-8",
stdio: ["ignore", "pipe", "ignore"],
}).trim();
if (raw) return raw.replace(/^v/, "");
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

Keep the git path on the plain release version.

git describe --tags --match "v*" starts returning vX.Y.Z-N-gHASH as soon as HEAD moves past the last tag, so this branch changes getVersion() output on normal development commits.

Proposed fix
-    const raw = execFileSync("git", ["describe", "--tags", "--match", "v*"], {
+    const raw = execFileSync("git", ["describe", "--tags", "--match", "v*", "--abbrev=0"], {
#!/bin/bash
set -euo pipefail

printf 'git describe --exact-match: '
git describe --tags --match 'v*' --exact-match || true

printf 'git describe: '
git describe --tags --match 'v*' || true

printf 'git describe --abbrev=0: '
git describe --tags --match 'v*' --abbrev=0 || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/version.ts` around lines 25 - 30, The current call to git describe
strips the leading "v" from any output, which turns a non-exact describe like
"v1.2.3-4-gHASH" into "1.2.3-4-gHASH" and changes development commit output;
modify getVersion() so you first run git describe --tags --match "v*"
--exact-match (store e.g. rawExact) and if that returns a value return
rawExact.replace(/^v/, ""); otherwise run the fallback git describe --tags
--match "v*" (the existing command that may return the -N-gHASH form) and return
that raw string unchanged; update the execFileSync calls that produce raw to
implement this two-step logic (refer to getVersion, rawExact/raw variables).

The "null commandVResult with home: undefined" test was functionally
identical to "returns null when openshell not found anywhere" since
opts.home ?? process.env.HOME treats undefined identically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@prekshivyas prekshivyas requested a review from cv April 2, 2026 18:33
Copy link
Copy Markdown
Contributor

@cv cv left a comment

Choose a reason for hiding this comment

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

Directory fixed, shims correct, CI green. The version format CodeRabbit concern is pre-existing behavior from the original JS, not a regression.

@cv cv enabled auto-merge (squash) April 2, 2026 20:38
@cv cv merged commit 82ce32c into main Apr 2, 2026
8 checks passed
cjagwani pushed a commit to fdzdev/NemoClaw that referenced this pull request Apr 3, 2026
## Summary

Closes NVIDIA#1298. Part of NVIDIA#924 shell consolidation.

- **resolve-openshell.js** → TS migration with `ResolveOpenshellOptions`
interface; bin/lib shim re-exports from compiled dist
- **version.ts** → new module reading root package.json + `git
describe`; wired into `bin/nemoclaw.js` for `--version` and help (output
unchanged — uses plain semver)
- **chat-filter.ts** → new utility extracting `ALLOWED_CHAT_IDS` parsing
from telegram-bridge; wired into `scripts/telegram-bridge.js`

Each module follows the established pattern:
1. TS source in `nemoclaw/src/lib/` with flat interfaces
2. Thin CJS re-export shim in `bin/lib/` →
`require("../../nemoclaw/dist/lib/...")`
3. Co-located tests in `src/lib/*.test.ts` importing from
`../../dist/lib/` for coverage attribution

## Test plan

- [x] `npx vitest run --project plugin` — 216 tests pass (includes new
lib tests)
- [x] `npx vitest run --project cli` — 336 tests pass (shims work
transparently)
- [x] `npx eslint nemoclaw/src/lib/` — clean
- [x] `node bin/nemoclaw.js --version` — output unchanged (`nemoclaw
v0.1.0`)
- [x] Pre-commit hooks pass (prettier, eslint, gitleaks, vitest, tsc)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added utilities for chat allowlist handling, openshell binary
resolution, and robust version detection.

* **Refactor**
* Consolidated runtime implementations into the compiled distribution to
streamline module surfaces.

* **Tests**
* Added comprehensive tests covering chat filtering, shell resolution
fallbacks, and version-detection behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Prekshi Vyas <prekshivyas@nvidia.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
lakamsani pushed a commit to lakamsani/NemoClaw that referenced this pull request Apr 4, 2026
## Summary

Closes NVIDIA#1298. Part of NVIDIA#924 shell consolidation.

- **resolve-openshell.js** → TS migration with `ResolveOpenshellOptions`
interface; bin/lib shim re-exports from compiled dist
- **version.ts** → new module reading root package.json + `git
describe`; wired into `bin/nemoclaw.js` for `--version` and help (output
unchanged — uses plain semver)
- **chat-filter.ts** → new utility extracting `ALLOWED_CHAT_IDS` parsing
from telegram-bridge; wired into `scripts/telegram-bridge.js`

Each module follows the established pattern:
1. TS source in `nemoclaw/src/lib/` with flat interfaces
2. Thin CJS re-export shim in `bin/lib/` →
`require("../../nemoclaw/dist/lib/...")`
3. Co-located tests in `src/lib/*.test.ts` importing from
`../../dist/lib/` for coverage attribution

## Test plan

- [x] `npx vitest run --project plugin` — 216 tests pass (includes new
lib tests)
- [x] `npx vitest run --project cli` — 336 tests pass (shims work
transparently)
- [x] `npx eslint nemoclaw/src/lib/` — clean
- [x] `node bin/nemoclaw.js --version` — output unchanged (`nemoclaw
v0.1.0`)
- [x] Pre-commit hooks pass (prettier, eslint, gitleaks, vitest, tsc)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added utilities for chat allowlist handling, openshell binary
resolution, and robust version detection.

* **Refactor**
* Consolidated runtime implementations into the compiled distribution to
streamline module surfaces.

* **Tests**
* Added comprehensive tests covering chat filtering, shell resolution
fallbacks, and version-detection behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Prekshi Vyas <prekshivyas@nvidia.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
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.

refactor(cli): migrate remaining unblocked CJS modules to TypeScript

2 participants