DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

genius-alray /

genius-alray/dsh-plugin-forge-cnb

Verified

CNB (cnb.cool) merge request provider for the DeepSeek Harness forge panel

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@d0061784

dsh-plugin-forge-cnb

English | 中文

CNB (cnb.cool) provider for the DSH forge registry. It registers a ForgeProvider with ctx.forge, so the change request and working tree panel can show CNB merge requests: state, additions/deletions, mergeability, checks, reviewers, diff, file contents, review publication, and merge.

This is a host-side bundle with zero runtime dependencies: global fetch and Node built-ins only, Node.js >= 22. No client or DSH package change is needed — registering the provider is enough.

What it adds

contract method CNB source
repositoryFromRemote / changeRequestFromUrl / changeRequestUrl remote-URL and MR-URL parsing for https, ssh://, and scp-like remotes, nested groups (a/b/c), .git suffixes, ports; the two URL functions are exact inverses
fetch GET /{repo}/-/pulls/{n} + GET /{repo}/-/pulls/{n}/commit-statuses, plus per-head-commit totals (…/files), head commit date (…/git/commits/{sha}), and cached repository merge settings
fetchChecks the same reads, always with the complete check list (no 50-row polling cap)
findByHead GET /{repo}/-/pulls?state=open&page_size=100&page=N, filtered client-side (CNB has no head-branch filter)
diff GET /{repo}/-/pulls/{n}/files, assembled into one Git-style unified patch
fileText / fileUrl GET /{repo}/-/git/raw/{commit}/{path} (falls back to the base64 contents endpoint on HTTP 413), and the /-/blob/{commit}/{path} web URL
merge re-reads the MR, compares head.sha with the confirmed commit, then PUT /{repo}/-/pulls/{n}/merge with merge_style; never sends force
markReady no API exists; returns a clear failure telling the user to edit the WIP title in the web UI
submitReview POST /{repo}/-/pulls/{n}/reviews with event and inline comments
readiness GET /user; an expired or missing token reads as unauthenticated and tells the user to run cnb login

Install

This is a DSH bundle distributed from GitHub rather than npm. Clone it, then install the checked-out directory as a bundle in your profile (for example with the Plugin Manager's install-bundle flow, or the plugin_manager tool with action: install_bundle):

git clone https://github.com/genius-alray/dsh-plugin-forge-cnb

The bundle ships its own cordis.patch.yml, which inserts the provider row; you pick the bundle, not the row. It needs Node.js >= 22 on the host and a valid cnb login session (~/.cnb/token, re-read whenever the CLI refreshes it).

Enable it

The bundle ships a Cordis patch, so enabling it in the profile is one entry:

- insert:
    - id: forge-cnb
      name: 'dsh-plugin-forge-cnb'
      config:
        hosts: ['cnb.cool']
        tokenFile: '~/.cnb/token'
        timeoutMs: 10000
        maxPages: 3
config field default meaning
hosts ['cnb.cool'] web hosts this provider serves; repositories on other hosts are ignored
tokenFile ~/.cnb/token CNB CLI credential file; CNB_TOKEN in the environment overrides it
timeoutMs 10000 deadline of one HTTP request, combined with the caller's cancellation
maxPages 3 pages of 100 open MRs findByHead reads per lookup

Nothing is written to any repository unless you explicitly merge or submit a review.

How a merge request binds to a session

The forge session layer discovers a session's change request by running git in the session checkout: it maps the checkout's remote to a repository (repositoryFromRemote) and then asks findByHead(repository, branch) for open MRs whose head is the current branch. Pasting a CNB MR URL into the session binds it directly (changeRequestFromUrl), and bindings are re-resolved through changeRequestUrl → changeRequestFromUrl, which is why those two are exact inverses.

gh command output parsing does not apply here — that is GitHub-CLI specific. For CNB the only binding paths are the git remote/branch discovery and a pasted MR URL.

Credentials

The provider reads ~/.cnb/token (written by the official cnb CLI) and takes .auths["cnb.cool"].access_token. The token is never printed or logged. A token lives ~8 hours:

  • the file is re-read when its mtime/size changes, so a cnb login refresh is picked up without a restart;
  • a 401 / errcode: 16 response is reported as unauthenticated with a message telling the user to run cnb login;
  • CNB_TOKEN can supply a token for one process instead of the file.

Scopes used: repo-pr:r (MRs, statuses, files), repo-code:r (commit, raw file), repo-manage:r (merge settings), repo-pr:rw (merge), repo-notes:rw (review). Missing scopes surface as HTTP 403 (errcode 10023) and are reported as unavailable/failed rather than crashing the panel.

Mapping decisions

  • updatedAt: CNB's MR detail carries no timestamps. It is the head commit's committer date (GET /{repo}/-/git/commits/{headSha}), cached per commit; when that read fails it is the first time this head commit was observed (stable across polls, not the time of every poll).
  • closedAt: always null. CNB exposes no closed_at/merged_at on merge requests at all.
  • unresolvedThreads: always 0. CNB has no resolved/unresolved flag on review comments anywhere in the API.
  • additions/deletions: sum of the MR's changed files. CNB caps that list (see limitations), so a very large MR's totals can read low; there is no MR-level statistics endpoint.
  • mergeability: conflict/no-merge-base (or blocked_on = code_conflict/no_merge_base) is conflicting; mergeable/merging/merged is mergeable; everything else is unknown.
  • mergeReady: state == open && !is_wip && !is_merged && mergeable_state == mergeable && blocked_on == unblocked — mergeable_state alone is not enough (a mergeable MR can still wait for review or CI).
  • reviewDecision: changes_requested on any reviewer wins, then all-approved is approved, otherwise review-required; no reviewers is none.
  • checksRollup: CNB's aggregate state of commit-statuses is used directly (success/failure/ error/pending); individual success|pending|failure|error|skipped items become check rows with created_at/updated_at as start/completion times, capped at 50 in polling snapshots (checksTruncated) and complete in fetchChecks.
  • mergeMethods: from GET /{repo}/-/settings/pull-request (allow_merge_commit_merge, allow_squash_merge, allow_rebase_merge), cached per repository for 5 minutes; if it cannot be read (for example a token without repo-manage:r), the default set [merge, squash, rebase] is offered so the merge button does not disappear.
  • review body: POST /pulls/{n}/reviews takes event + inline comments; an empty review body is omitted instead of sent as "". Inline comments use the same full repository paths as diff().

Diff format

The panel parses diff() with jsdiff 9.0.0 (parsePatch), which resolves a file's path from diff --git / --- / +++ headers and silently drops a section that has only hunks. CNB's /pulls/{n}/files returns hunks only and reports the file basename in filename, so this provider:

  1. recovers the full repository-relative path from blob_url/raw_url/contents_url (…/-/blob/{sha}/{path}), never from filename;
  2. emits per file: diff --git a/<path> b/<path>, new file mode 100644/deleted file mode 100644, --- a/<path> + +++ b/<path> (or /dev/null on the missing side), then the recorded hunks byte-for-byte, with LF line endings and a trailing newline;
  3. renders an empty patch (add/delete/modify) as Binary files … differ, which jsdiff records as isBinary, instead of an empty change;
  4. keeps rename origins when a payload exposes previous_filename; a CNB rename otherwise renders as a change of the new path.

This is covered by an offline unit test that parses the assembled patch with the real jsdiff copy when it is discoverable and skips otherwise.

Known limitations

  • WIP cannot be cleared through the API: is_wip is read-only on CNB, so markReady always fails with a message asking the user to edit the title in the web UI. The provider never PATCHes the title.
  • The changed-file list is hard-capped at 300 entries even though CNB documents it as unpaginated (a verified 490-file MR returns 300). additions/deletions and diff() therefore cover at most 300 files for a very large MR; there is no truncation flag to expose and no workaround endpoint.
  • Rename origin is not recoverable from the MR file list; a rename renders as a change of the new path (no rename from header) unless the payload happens to carry previous_filename. Only GET /{repo}/-/git/compare/{base}...{head} exposes it, and that endpoint shares the 300-file cap.
  • No close/merge timestamp (see closedAt), so the panel cannot show when an MR was merged or closed.
  • Resolve state of review threads is unavailable (see unresolvedThreads).
  • Merge refusal shape is unverified: CNB documents 403 (die.WebError) but may answer 200 with merged:false; both are handled as failed with the host message. fast_forward is a valid CNB merge_style but not part of the forge contract's method set, so it is not offered.
  • Fork merge requests: CNB bakes the base repository path plus the head commit into the file URLs, and the base repository refuses a raw read of a foreign commit (HTTP 400 errcode 3). The provider detects a fork via head.repo.id !== base.repo.id (or differing paths), caches the head repository per change request, and reads fileText/fileUrl from head.repo.path. Before the first poll it learns the head repository from one MR detail read when the base read fails.
  • The diff/hunks are whatever the host returns: this provider does not re-number hunks or trim lines, because a count mismatch would make the panel's parser throw.
  • Custom hosts: only cnb.cool is tested; for another host the API base is derived as https://api.<host> and the token must exist under that host key in the token file.
  • No rate-limit headers are exposed by CNB; the provider does no adaptive throttling.

Development

npm install        # dev dependency: jsdiff, the same parser the panel host uses
npm test           # 85 offline unit tests (fixtures + fake fetch); live test skipped
npm run check      # node --check on every source and test file

The unit tests never touch the network: they load the synthetic responses under tests/fixtures/ and inject a fake fetch. Two format tests parse the assembled patch with the panel's own jsdiff copy; they skip when the dev dependency is not installed. Write paths (merge, review) are covered only by fake-fetch tests; this package was never used to write to a real repository.

An optional read-only smoke test runs against any repository and merge request you can read:

CNB_LIVE=1 CNB_LIVE_REPO=owner/repo CNB_LIVE_MR=12 node --test tests/live.test.js

—/ 5

No ratings yet

Verified DSH bundle

Commit d0061784ce3e

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout