Introduction¶
The DSH forge registry defaults to regular code hosting services. If a team is hosted on CNB (cnb.cool), the status, checks, diff, review, and merge information for change requests (merge requests) cannot be read by the panel because it doesn’t have access to the corresponding data source. Consequently, the change request and working tree panels will not function.
genius-alray/dsh-plugin-forge-cnb does exactly this: it registers a ForgeProvider to ctx.forge, allowing the DSH forge panel to directly read CNB merge requests. The plugin is categorized as “Network Tools”, maintained by genius-alray, has 1 GitHub star, version 0.1.0 in package.json, and the license is MIT.
Below is an introduction to its positioning, capabilities, and how to install and enable it.
What is it¶
One-sentence positioning: A DSH bundle distributed on GitHub, providing a CNB (cnb.cool) provider for the DSH forge registry.
It registers the provider via ctx.forge, overriding the reading and operation of merge requests: status, additions/deletions, mergeability, checks, reviewers, diff, file contents, review submission, and merging.
There are no third-party dependencies at runtime; it only uses the global fetch and Node.js built-in modules, requiring the host Node.js >= 22. Neither the client nor the DSH package itself requires changes—registering the provider is sufficient.
The code repository provides both an English README.md and a Chinese README.zh.md.
Core Capabilities¶
Comparing with the contract method table in the README, the capabilities are as follows:
| Contract Method | CNB Source |
|---|---|
repositoryFromRemote / changeRequestFromUrl / changeRequestUrl |
Parse remote URL and MR URL, supporting https, ssh://, scp-style remote, nested groups (a/b/c), .git suffix, ports; the two URL functions are strict inverse functions of each other |
fetch |
GET /{repo}/-/pulls/{n} + GET /{repo}/-/pulls/{n}/commit-statuses, plus additions/deletions statistics for each head commit (.../files), head commit date (.../git/commits/{sha}), and cached repository merge settings |
fetchChecks |
Same reading as fetch, but always returns the full list of checks, with no 50-item polling limit |
findByHead |
GET /{repo}/-/pulls?state=open&page_size=100&page=N, client-side filtering (CNB has no interface to filter by head branch) |
diff |
GET /{repo}/-/pulls/{n}/files, assembled into a Git-style unified patch |
fileText / fileUrl |
GET /{repo}/-/git/raw/{commit}/{path} (falls back to base64 contents interface when HTTP 413), and the web address /-/blob/{commit}/{path} |
merge |
Re-reads the MR, compares head.sha with the confirmed commit, then calls PUT /{repo}/-/pulls/{n}/merge with merge_style; never sends force |
markReady |
CNB has no corresponding interface, returns explicit failure and prompts 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; token expired or missing is read as unauthenticated, prompting the user to execute cnb login |
Notable Mapping Limitations¶
Some of CNB’s data models are inconsistent with what the panel expects. The plugin handles these according to fixed rules; you should be aware of these differences before using them:
updatedAt: CNB’s MR details do not have a timestamp. The plugin uses the committer date of the head commit (GET /{repo}/-/git/commits/{headSha}, cached by commit); if this read fails, it takes the time of the first observation of that head commit.closedAt: Alwaysnull. CNB’s MR interface does not haveclosed_at/merged_at.unresolvedThreads: Always0. CNB’s review comments do not have resolved / unresolved markers.- additions/deletions: Are the total of changed files in the MR. CNB truncates this list, so the total for very large MRs may be low; there is no MR-level statistics interface.
- mergeability:
conflict/no-merge-base(orblocked_oniscode_conflict/no_merge_base) maps toconflicting;mergeable/merging/mergedmaps tomergeable; the rest map tounknown. - mergeReady: Requires
state == open && !is_wip && !is_merged && mergeable_state == mergeable && blocked_on == unblocked. Just looking atmergeable_stateis not enough—amergeableMR may still be waiting for a review or CI. - reviewDecision: Takes the value if any reviewer gives
changes_requested,approvedif all pass, otherwisereview-required;noneif there are no reviewers. - mergeMethods: Comes from
GET /{repo}/-/settings/pull-request, cached by repository for 5 minutes. If you lack permission to read (e.g., token missingrepo-manage:r), a default collection[merge, squash, rebase]is provided to prevent the merge button from disappearing. - checksRollup: Directly uses the aggregated
stateof CNB’scommit-statuses; individual checks usecreated_at/updated_atas the start and end time. The polling snapshot limit is 50 items and markedchecksTruncated,fetchChecksreturns the full list.
Diff Assembly Method¶
This detail determines whether the panel can correctly display changes. The panel uses parsePatch from jsdiff 9.0.0 to parse diff(), which parses paths based on diff --git / --- / +++ headers and silently discards paragraphs that only have hunks but no file headers. CNB’s /pulls/{n}/files only returns hunks, and the filename field contains only the filename itself. Therefore, the plugin:
- Restores the repository’s relative full path from
blob_url/raw_url/contents_url(.../-/blob/{sha}/{path}), never usingfilename; - For each file, outputs
diff --git a/<path> b/<path>,new file mode 100644/deleted file mode 100644,--- a/<path>++++ b/<path>(missing side uses/dev/null), then concatenates the recorded hunks as-is, with LF line endings, and a trailing newline; - Empty patches (
add/delete/modify) are rendered asBinary files ... differ, which jsdiff records asisBinary, rather than being treated as empty changes; - When the payload exposes
previous_filename, it preserves the rename source.
Additionally, review bodies are submitted via POST /pulls/{n}/reviews with event + inline comments; empty review bodies are omitted rather than sending "". Inline comments use the repository’s full path, the same as diff().
Installation and Enablement¶
The plugin is a DSH bundle, distributed via GitHub rather than npm. The installation steps given in the README are in two parts: first, retrieve the repository, then install the extracted directory as a bundle to the profile (you can use 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 comes with cordis.patch.yml, responsible for inserting the provider line; during installation, select the bundle, no need to manually pick lines. The host needs Node.js >= 22, and a valid cnb login session (~/.cnb/token, the plugin re-reads after CLI refresh).
Enabling only requires one record in the profile:
- insert:
- id: forge-cnb
name: 'dsh-plugin-forge-cnb'
config:
hosts: ['cnb.cool']
tokenFile: '~/.cnb/token'
timeoutMs: 10000
maxPages: 3
Configuration field description:
| Configuration Item | Default Value | Meaning |
|---|---|---|
hosts |
['cnb.cool'] |
The web host of the provider service; repositories on other hosts are ignored |
tokenFile |
~/.cnb/token |
CNB CLI credential file; CNB_TOKEN in the environment can override it |
timeoutMs |
10000 |
The deadline for a single HTTP request, stacked with the caller’s cancellation mechanism |
maxPages |
3 |
The number of open MR pages read by findByHead each time, 100 items per page |
The plugin only writes data to the repository when explicitly performing a merge or submitting a review; all other operations are read-only.
Typical Usage¶
There are two paths for binding MRs to sessions, neither requiring manual configuration of repository information.
The first is automatic discovery. The forge session layer executes git in the session checkout, first using repositoryFromRemote to map the remote to a specific repository, then calling findByHead(repository, branch) to find the open MR that the head points to the current branch.
The second is pasting the MR address. Paste the CNB MR URL directly into the session, and changeRequestFromUrl will bind it immediately. The binding relationship is re-parsed via changeRequestUrl -> changeRequestFromUrl, which is why these two URL functions are strict inverse functions.
It is worth noting that gh command output parsing is not applicable here; that is specific to GitHub-CLI. CNB only has git remote / branch discovery and pasting MR URL as two binding paths.
Regarding credentials, the provider reads ~/.cnb/token (written by the official cnb CLI), extracts .auths["cnb.cool"].access_token, and the token is not printed or logged. The CNB token expires in about 8 hours, and the plugin’s handling is:
- Re-read when the file’s mtime or size changes;
cnb loginrefresh doesn’t require a restart; - 401 or
errcode: 16responses are reported asunauthenticated, prompting the user to executecnb login; CNB_TOKENcan provide a token for a single process, replacing the file.
Used scopes: repo-pr:r (MR, status, files), repo-code:r (commit, raw files), repo-manage:r (merge settings), repo-pr:rw (merge), repo-notes:rw (review). Missing scopes appear as HTTP 403 (errcode 10023), reported as unavailable / failed, and will not crash the panel.
Applicable Scenarios and Notes¶
Suitable for teams or individuals hosted on cnb.cool who want to directly view CNB MR status, checks, diff, and perform merges in the DSH forge panel.
A few reminders:
- The plugin runs with the permissions of the current dsh process and can read files and credentials that the host can read. Before installing, you should check the source code and MIT license yourself to confirm the source is trustworthy.
- It modifies the profile via a bundle; installation and enablement require a restart or profile reload to take effect. It is recommended to back up the current configuration before making changes.
- With 1 star and version 0.1.0, it indicates that both the maintainer and user base are very small. If you encounter problems, it is better to look at the source code or issues rather than waiting for a fix.
- The CNB data model limitations listed above (
closedAt,unresolvedThreads, truncation of additions/deletions, etc.) are determined by the upstream interface and will not change in the short term; don’t treat panel values as authoritative.
Conclusion¶
The value of this plugin lies in filling the gap in DSH forge panel support for CNB: no need to modify the client, and after registering the provider, you can read/write CNB merge requests, checks, diffs, reviews, and merges. It is small in code size, has zero runtime dependencies, has very detailed mapping rules, is suitable as a reference implementation to read, and is also suitable to use directly.
Directory page: https://www.skillhub.cn/plugins/genius-alray/dsh-plugin-forge-cnb
Source code: https://github.com/genius-alray/dsh-plugin-forge-cnb