Publish the npm packages under @smbcloud again

The scope moved to @getsigit in 1.5.5 and @smbcloud was left on the registry at 1.5.2 with nothing pointing anywhere, so anyone who had installed it before the rename stopped getting releases and got no explanation from npm update. All seven packages now publish under both scopes from the same build. The scope is a render-time argument rather than a second copy of the packaging: render-platform-package.cjs takes it as a fifth argument, and the base package gets it from package-main.json.tmpl or the new package-compat.json.tmpl. @smbcloud/sigit carries its own platform binaries, so it is a full install and not a stub that depends on the new scope. The launcher is built once and shipped under both names. It reads its own package.json at runtime to work out which scope to resolve the platform binary from, and prints a line on stderr about where the package moved only when it finds itself running as @smbcloud/sigit. SIGIT_SUPPRESS_SCOPE_NOTICE=1 turns that off. The notice goes to stderr because in ACP mode stdout is the JSON-RPC pipe to the editor. @getsigit/sigit stays canonical. This is a migration path, and the README, the Homebrew tap and the ACP registry entry all still point at the new name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Seto Elkahfi committed Sep 6, 2026 at 08:57 UTC 0d8411ac5e7d23cd4702d5e34d50e04ad3a780a1
9 files changed +172 -60
.agents/skills/sigit-code-release/SKILL.md
+3
@@ -28,6 +28,8 @@ Use this skill when preparing a release for this repository.
28 - Update the root `sigit` package entry in `Cargo.lock` when the crate version changes.
29 - Add or update the top changelog entry in `CHANGELOG.md` for the release being cut.
30 - Do not treat `npm/sigit/package.json` `0.0.0-dev` as a bug by default. The npm release workflow rewrites it at publish time using `npm/scripts/render-main-package.cjs` and the release tag.
31 +- Every npm package publishes twice, under `@getsigit` and under `@smbcloud`. `@smbcloud` is the pre-1.5.5 name, mirrored so an install predating the rename keeps updating; `@getsigit` is canonical and is what the README, the Homebrew tap, and the ACP registry entry point at. All fourteen names (two base packages, twelve platform packages) publish over OIDC and each needs its own Trusted Publisher on npm pointing at `release-npm.yml`.
32 +- The scope is a render-time argument, not a second copy of the packaging: `render-platform-package.cjs` takes it as a fifth argument, and the base package picks it up from `package-main.json.tmpl` versus `package-compat.json.tmpl`. `npm/sigit/src/index.ts` is built once and shipped under both names — it reads its own `package.json` at runtime to decide which scope to resolve the platform binary from, and prints the migration notice only when it finds itself running as `@smbcloud/sigit`.
33 - Do not add a hardcoded version to `pypi/pyproject.toml` for normal releases. PyPI uses `maturin` with `dynamic = ["version"]` and derives the published package version from `Cargo.toml`.
34 - Release workflows are tag-driven. `release-github.yml`, `release-npm.yml`, `release-pypi.yml`, `release-crates.yml`, `release-homebrew.yml`, `release-nuget.yml`, and `release-mcp-registry.yml` all derive `RELEASE_VERSION` from a `v*.*.*` tag or a manually supplied tag input.
35 - The crate is published to crates.io (`release-crates.yml`) and the Homebrew tap is updated (`release-homebrew.yml`) as part of the tag-driven flow. Per the siGit release flow, Homebrew is auto-triggered — do not dispatch it manually.
@@ -58,6 +60,7 @@ Pushing the `v*.*.*` tag is what fires every release workflow, so create and pus
60 - `CHANGELOG.md`
61 - `README.md`
62 - `npm/sigit/package.json`
63 +- `npm/package-main.json.tmpl`, `npm/package-compat.json.tmpl`, `npm/package.json.tmpl` (publish-time package metadata for both scopes)
64 - `npm/scripts/render-main-package.cjs`
65 - `npm/`
66 - `pypi/`
.github/workflows/release-npm.yml
+67 -47
@@ -116,8 +116,10 @@ jobs:
116 shell: bash
117 run: npm install -g npm@latest
118
119 - # Authentication is OIDC: each @getsigit/* package has a Trusted
120 - # Publisher pointing at this workflow, so there is no token to leak.
119 + # Authentication is OIDC: each @getsigit/* and @smbcloud/* package has a
120 + # Trusted Publisher pointing at this workflow, so there is no token to
121 + # leak. @smbcloud is the pre-1.5.5 scope, published alongside @getsigit
122 + # so that an install made before the rename keeps updating.
123 - name: Publish platform package to NPM
124 shell: bash
125 run: |
@@ -149,13 +151,6 @@ jobs:
151 # Create the package directory
152 mkdir -p "${node_pkg}/bin"
153
152 - # Generate package.json + README for this platform slice
153 - node ./scripts/render-platform-package.cjs \
154 - "${node_pkg}" \
155 - "${node_version}" \
156 - "${node_os}" \
157 - "${node_arch}"
158 -
154 # Windows binaries carry a .exe extension
155 if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
156 cargo_bin="${cargo_bin}.exe"
@@ -163,27 +158,36 @@ jobs:
158
159 cp "../target/${{ matrix.build.TARGET }}/release/${cargo_bin}" "${node_pkg}/bin"
160
166 - cd "${node_pkg}"
167 -
168 - npm_package_name="@getsigit/${node_pkg}"
169 -
170 - # `npm view` exits non-zero both when the version is genuinely
171 - # unpublished and when the registry could not be reached, so treat
172 - # only an E404 as "not published yet". Anything else stops the job
173 - # instead of publishing over a version that is already there.
174 - if view_output=$(npm view "${npm_package_name}@${node_version}" version 2>&1); then
175 - echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
176 - exit 0
177 - elif ! grep -q "E404" <<<"${view_output}"; then
178 - echo "npm view failed while checking ${npm_package_name}@${node_version}:" >&2
179 - echo "${view_output}" >&2
180 - exit 1
181 - fi
182 -
183 - npm publish --access public
161 + # One binary, published under both scopes: re-render package.json and
162 + # README for each, from the same directory.
163 + for scope in "@getsigit" "@smbcloud"; do
164 + node ./scripts/render-platform-package.cjs \
165 + "${node_pkg}" \
166 + "${node_version}" \
167 + "${node_os}" \
168 + "${node_arch}" \
169 + "${scope}"
170 +
171 + npm_package_name="${scope}/${node_pkg}"
172 +
173 + # `npm view` exits non-zero both when the version is genuinely
174 + # unpublished and when the registry could not be reached, so treat
175 + # only an E404 as "not published yet". Anything else stops the job
176 + # instead of publishing over a version that is already there.
177 + if view_output=$(npm view "${npm_package_name}@${node_version}" version 2>&1); then
178 + echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
179 + continue
180 + elif ! grep -q "E404" <<<"${view_output}"; then
181 + echo "npm view failed while checking ${npm_package_name}@${node_version}:" >&2
182 + echo "${view_output}" >&2
183 + exit 1
184 + fi
185 +
186 + (cd "${node_pkg}" && npm publish --access public)
187 + done
188
189 publish-npm-base:
186 - name: Publish base NPM package (@getsigit/sigit)
190 + name: Publish base NPM packages (@getsigit/sigit, @smbcloud/sigit)
191 needs: publish-npm-binaries
192 runs-on: ubuntu-latest
193
@@ -217,31 +221,47 @@ jobs:
221 shell: bash
222 run: npm install -g npm@latest
223
220 - - name: Publish base package to NPM
224 + - name: Publish base packages to NPM
225 shell: bash
226 run: |
227 cd npm/sigit
228
225 - # Rewrite package.json with the correct release version and
226 - # matching optionalDependency versions
227 - node ../scripts/render-main-package.cjs \
228 - "./package.json" \
229 - "${{ env.RELEASE_VERSION }}"
230 -
231 - npm_package_name="@getsigit/sigit"
229 npm_package_version="${{ env.RELEASE_VERSION }}"
230
234 - # Same E404-only reading as the platform packages above: a registry
235 - # failure must not look like an unpublished version.
236 - if view_output=$(npm view "${npm_package_name}@${npm_package_version}" version 2>&1); then
237 - echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
238 - exit 0
239 - elif ! grep -q "E404" <<<"${view_output}"; then
240 - echo "npm view failed while checking ${npm_package_name}@${npm_package_version}:" >&2
241 - echo "${view_output}" >&2
242 - exit 1
243 - fi
231 + # Rewrite package.json with the correct release version and matching
232 + # optionalDependency versions, then build. The compiled launcher is
233 + # scope-agnostic — it reads its own package.json at runtime to decide
234 + # which scope to resolve the platform binary from — so both packages
235 + # ship the same lib/ and it only needs building once.
236 + node ../scripts/render-main-package.cjs \
237 + "./package.json" \
238 + "${npm_package_version}"
239
240 npm install
241 npm run build
247 - npm publish --access public
242 +
243 + # @getsigit/sigit is the canonical package; @smbcloud/sigit is the
244 + # pre-1.5.5 name, kept publishing so installs predating the rename
245 + # keep updating. Its template points optionalDependencies at the
246 + # @smbcloud platform packages.
247 + for template in "package-main.json.tmpl" "package-compat.json.tmpl"; do
248 + node ../scripts/render-main-package.cjs \
249 + "./package.json" \
250 + "${npm_package_version}" \
251 + "${template}"
252 +
253 + npm_package_name=$(node -p "require('./package.json').name")
254 +
255 + # Same E404-only reading as the platform packages above: a registry
256 + # failure must not look like an unpublished version.
257 + if view_output=$(npm view "${npm_package_name}@${npm_package_version}" version 2>&1); then
258 + echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
259 + continue
260 + elif ! grep -q "E404" <<<"${view_output}"; then
261 + echo "npm view failed while checking ${npm_package_name}@${npm_package_version}:" >&2
262 + echo "${view_output}" >&2
263 + exit 1
264 + fi
265 +
266 + npm publish --access public
267 + done
npm/README.md.tmpl
+1 -1
@@ -15,7 +15,7 @@
15
16 ## `${node_pkg}`
17
18 -This package is one of the platform-specific binaries behind [`@getsigit/sigit`](https://www.npmjs.com/package/@getsigit/sigit). You usually do not need to install it directly.
18 +This package is one of the platform-specific binaries behind [`${node_scope}/sigit`](https://www.npmjs.com/package/${node_scope}/sigit). You usually do not need to install it directly.
19
20 Install the main package and npm will pull in the right binary for your OS and architecture:
21
npm/package-compat.json.tmpl new
+41
@@ -0,0 +1,41 @@
1 +{
2 + "name": "@smbcloud/sigit",
3 + "version": "${release_version}",
4 + "keywords": [
5 + "sigit",
6 + "cli",
7 + "ai",
8 + "coding-agent",
9 + "llm",
10 + "on-device",
11 + "smbcloud",
12 + "acp"
13 + ],
14 + "bin": {
15 + "sigit": "lib/index.js"
16 + },
17 + "files": ["lib", "README.md"],
18 + "description": "Previous name for @getsigit/sigit. AI coding agent powered by local LLM via Onde Inference.",
19 + "license": "Apache-2.0",
20 + "repository": {
21 + "type": "git",
22 + "url": "git+https://github.com/getsigit/sigit.git"
23 + },
24 + "scripts": {
25 + "typecheck": "tsc --noEmit",
26 + "build": "tsc",
27 + "dev": "npm run build && node lib/index.js"
28 + },
29 + "devDependencies": {
30 + "@types/node": "^18.15.11",
31 + "typescript": "^5.0.0"
32 + },
33 + "optionalDependencies": {
34 + "@smbcloud/sigit-darwin-arm64": "${release_version}",
35 + "@smbcloud/sigit-darwin-x64": "${release_version}",
36 + "@smbcloud/sigit-linux-arm64": "${release_version}",
37 + "@smbcloud/sigit-linux-x64": "${release_version}",
38 + "@smbcloud/sigit-windows-arm64": "${release_version}",
39 + "@smbcloud/sigit-windows-x64": "${release_version}"
40 + }
41 +}
npm/package.json.tmpl
+1 -1
@@ -1,5 +1,5 @@
1 {
2 - "name": "@getsigit/${node_pkg}",
2 + "name": "${node_scope}/${node_pkg}",
3 "version": "${node_version}",
4 "description": "Platform binary for siGit Code.",
5 "license": "Apache-2.0",
npm/scripts/render-main-package.cjs
+8 -7
@@ -3,10 +3,13 @@
3 const fs = require("fs");
4 const path = require("path");
5
6 -const [outputPath, version] = process.argv.slice(2);
6 +const [outputPath, version, templateName = "package-main.json.tmpl"] =
7 + process.argv.slice(2);
8
9 if (!outputPath || !version) {
9 - throw new Error("Usage: render-main-package.cjs <output-path> <version>");
10 + throw new Error(
11 + "Usage: render-main-package.cjs <output-path> <version> [template-name]",
12 + );
13 }
14
15 const npmRoot = path.resolve(__dirname, "..");
@@ -25,10 +28,8 @@ function interpolate(template, variables) {
28 });
29 }
30
28 -// Read and interpolate package-main.json.tmpl
29 -const template = fs.readFileSync(
30 - path.join(npmRoot, "package-main.json.tmpl"),
31 - "utf-8",
32 -);
31 +// Read and interpolate the requested template: package-main.json.tmpl for
32 +// @getsigit/sigit, package-compat.json.tmpl for the @smbcloud/sigit copy.
33 +const template = fs.readFileSync(path.join(npmRoot, templateName), "utf-8");
34
35 fs.writeFileSync(path.resolve(outputPath), interpolate(template, vars));
npm/scripts/render-platform-package.cjs
+11 -3
@@ -3,12 +3,19 @@
3 const fs = require("fs");
4 const path = require("path");
5
6 -const [packageName, version, operatingSystem, architecture] =
7 - process.argv.slice(2);
6 +const [
7 + packageName,
8 + version,
9 + operatingSystem,
10 + architecture,
11 + // `@smbcloud` is the pre-1.5.5 scope, still published alongside `@getsigit`
12 + // so installs made before the rename keep updating.
13 + scope = "@getsigit",
14 +] = process.argv.slice(2);
15
16 if (!packageName || !version || !operatingSystem || !architecture) {
17 throw new Error(
11 - "Usage: render-platform-package.cjs <package-name> <version> <os> <arch>",
18 + "Usage: render-platform-package.cjs <package-name> <version> <os> <arch> [scope]",
19 );
20 }
21
@@ -19,6 +26,7 @@ fs.mkdirSync(packageDirectory, { recursive: true });
26
27 // Variable map for template interpolation
28 const vars = {
29 + node_scope: scope,
30 node_pkg: packageName,
31 node_version: version,
32 node_os: operatingSystem,
npm/sigit/README.md
+8
@@ -21,6 +21,14 @@ npm install -g @getsigit/sigit
21
22 npm pulls in the right binary for your platform automatically.
23
24 +If you installed this before 1.5.5, you have it under its old name,
25 +`@smbcloud/sigit`. That name still gets every release, so nothing is broken,
26 +but `@getsigit/sigit` is the one to move to:
27 +
28 +```sh
29 +npm uninstall -g @smbcloud/sigit && npm install -g @getsigit/sigit
30 +```
31 +
32 Supported targets:
33
34 - macOS, Apple Silicon and Intel
npm/sigit/src/index.ts
+32 -1
@@ -2,6 +2,18 @@
2
3 import { spawnSync } from "child_process";
4
5 +/**
6 + * The same launcher ships under two names: `@getsigit/sigit`, and
7 + * `@smbcloud/sigit`, which is what the package was called before 1.5.5. Each
8 + * one carries its own set of platform packages, so which scope to look the
9 + * binary up in is whichever scope this copy was published under.
10 + */
11 +function getScope() {
12 + const selfName = require("../package.json").name as string;
13 + const scope = selfName.split("/")[0];
14 + return scope.startsWith("@") ? scope : "@getsigit";
15 +}
16 +
17 /**
18 * Returns the executable path which is located inside node_modules
19 * The naming convention is cli-OS-ARCH
@@ -22,7 +34,7 @@ function getExePath() {
34 try {
35 // Since the binary will be located inside node_modules, we can simply call require.resolve
36 return require.resolve(
25 - `@getsigit/sigit-${os}-${arch}/bin/sigit${extension}`,
37 + `${getScope()}/sigit-${os}-${arch}/bin/sigit${extension}`,
38 );
39 } catch (e) {
40 throw new Error(
@@ -31,10 +43,29 @@ function getExePath() {
43 }
44 }
45
46 +/**
47 + * `@smbcloud/sigit` is kept publishing so that an install predating the rename
48 + * keeps updating instead of sitting on 1.5.2, but `@getsigit/sigit` is the
49 + * name the project actually goes by. Say so, once per run, on stderr — in ACP
50 + * mode stdout is the JSON-RPC pipe to the editor.
51 + */
52 +function warnIfDeprecatedName() {
53 + if (getScope() !== "@smbcloud") return;
54 + if (process.env.SIGIT_SUPPRESS_SCOPE_NOTICE) return;
55 +
56 + process.stderr.write(
57 + "@smbcloud/sigit is the old name for @getsigit/sigit. Both names get the " +
58 + "same release for now; the new one is where siGit Code lives:\n" +
59 + " npm uninstall -g @smbcloud/sigit && npm install -g @getsigit/sigit\n" +
60 + "Set SIGIT_SUPPRESS_SCOPE_NOTICE=1 to silence this.\n",
61 + );
62 +}
63 +
64 /**
65 * Runs the application with args using nodejs spawn
66 */
67 function run() {
68 + warnIfDeprecatedName();
69 const args = process.argv.slice(2);
70 const processResult = spawnSync(getExePath(), args, { stdio: "inherit" });
71 process.exit(processResult.status ?? 0);