@isomorph.ai/cli 0.2.1 → 0.2.2-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,6 @@ Before 0.2.0 the CLI was published as `@fourier-labs/harbour` with the command `
50
50
 
51
51
  ## Development
52
52
 
53
- Built from the [governance control plane](https://github.com/Fourier-Labs-AI/harbour-governance-control-plane) repository: `npm ci`, `npm run build --prefix packages/harbour-cli`, tests in `tests/cli-kit.test.ts` and `tests/cli-packaging-contract.test.ts`. Releases are tagged `cli-v<version>` (a `-rc.N` candidate first, then the stable tag).
53
+ Built from the [governance control plane](https://github.com/Fourier-Labs-AI/harbour-governance-control-plane) repository: `npm ci`, `npm run build --prefix packages/harbour-cli`, tests in `tests/cli-kit.test.ts` and `tests/cli-packaging-contract.test.ts`. Releases are tagged `cli-v<version>` by the release automation the moment `packages/harbour-cli/package.json` changes on `main`, so bump to a candidate first (`X.Y.Z-rc.1`, published under the `candidate` dist-tag through npm trusted publishing) and promote it with the `CLI release automation` workflow's `candidate` input; a final version on `main` without an accepted candidate is refused by the release job. 0.2.0 and 0.2.1 (the rename) were published by hand from their merge commits before the trusted publisher existed for `@isomorph.ai/cli`, which is why their `cli-v` release runs show red.
54
54
 
55
55
  Connect with `isomorph connect name@getlokal.com`: the CLI tries `https://platform.isomorph.ai/start/getlokal.json` and validates the profile before saving it. This uses the first domain label as a guess, not a domain registry; subdomains or companies whose tenant name differs should use their company setup link. Only the guessed tenant name is sent, not the email. Company sign-in and access checks still apply.
@@ -85,10 +85,16 @@ export async function productionise(rootArg, client, output, tenantId, includePa
85
85
  const gate = options.kitGate === false ? undefined : options.kitGate ?? (options.integrations ? { bundle: options.integrations.bundle } : undefined);
86
86
  if (gate && !pending?.uploaded)
87
87
  await preflightKitGate(root, gate.bundle, output, gate.run);
88
+ // A `CliError` is Isomorph's own answer (`AUTH_REQUIRED` after two 401s, a
89
+ // JSON-RPC refusal) and reaches the builder as it is; only a plain Error from
90
+ // the transport — Isomorph out of reach — is "not started". Reporting a
91
+ // refused sign-in as unreachable sent an agent down a worse path (2026-09-14).
88
92
  try {
89
93
  await client.initialize();
90
94
  }
91
- catch {
95
+ catch (error) {
96
+ if (error instanceof CliError)
97
+ throw error;
92
98
  throw new CliError("NOT_STARTED", "Isomorph could not be reached before the operation started.");
93
99
  }
94
100
  let start;
@@ -37,6 +37,20 @@ function rpcRefusal(error, status) {
37
37
  const code = labelled?.[1] ?? (typeof error.code === "number" ? `ISOMORPH_RPC_${error.code}` : `ISOMORPH_HTTP_${status}`);
38
38
  return new CliError(code, (labelled?.[2] ?? text) || `Isomorph refused the request (HTTP ${status}).`);
39
39
  }
40
+ /**
41
+ * A second 401 in a row: the sign-in itself is gone, or this company does not
42
+ * admit the signed-in account. The server says which in `error_description`
43
+ * (plain English, no URL or token); an older server sends only the code, and
44
+ * the fixed sentence stands in. Both fixes are named because the builder cannot
45
+ * tell the two causes apart: on 2026-09-14 a `productionise` 14 seconds after a
46
+ * successful `isomorph login` was refused by a company the account was not a
47
+ * member of, and "run login again" would only have repeated it.
48
+ */
49
+ async function signInRefused(response) {
50
+ const body = await response.json().catch(() => undefined);
51
+ const said = typeof body?.error_description === "string" ? body.error_description.trim() : "";
52
+ return new CliError("AUTH_REQUIRED", `${said || "Isomorph sign-in expired or was revoked."} Run \`isomorph login\` again, or \`isomorph connect <work-email-or-start-url>\` if this is the wrong company.`);
53
+ }
40
54
  async function readRpc(response) {
41
55
  try {
42
56
  return await response.json();
@@ -76,11 +90,11 @@ export class RemoteMcpClient {
76
90
  let response = await this.post(body);
77
91
  // A 401 mid-command usually means the stored token was rotated by another
78
92
  // process; re-resolve (which reloads the store) and retry once. A second
79
- // 401 means the sign-in itself is gone.
93
+ // 401 is Isomorph refusing the sign-in, and `signInRefused` says why.
80
94
  if (response.status === 401)
81
95
  response = await this.post(body);
82
96
  if (response.status === 401)
83
- throw new CliError("AUTH_REQUIRED", "Isomorph sign-in expired or was revoked. Run `isomorph login` again.");
97
+ throw await signInRefused(response);
84
98
  if (!expectResponse)
85
99
  return undefined;
86
100
  const parsed = await readRpc(response);
@@ -1 +1 @@
1
- export const CLI_VERSION = "0.2.1";
1
+ export const CLI_VERSION = "0.2.2-rc.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2-rc.1",
4
4
  "description": "Isomorph development kit CLI",
5
5
  "type": "module",
6
6
  "bin": {