@isomorph.ai/cli 0.7.2 → 0.7.4

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.
@@ -109,7 +109,7 @@ Rules live beside this file: \`core.md\` before the first edit; \`integrations.m
109
109
  ## Getting ready
110
110
 
111
111
  - No \`.isomorph/\` yet: \`isomorph init --app-root .\` (empty folder → starter; an existing app → three choices to relay). Node 22+, no Docker.
112
- - Sign-in (company systems and shipping only): \`isomorph connect <work-email>\` (refused? run the command it prints), then \`isomorph login\` — the browser opens and the person finishes there (their one step; say so).
112
+ - Sign-in, only when a command answers \`CONFIG_REQUIRED\` or \`AUTH_REQUIRED\` (most machines already are): \`isomorph connect <work-email>\`, then \`isomorph login\` — the browser opens and the person finishes there (their one step; say so).
113
113
 
114
114
  ## Intent → command
115
115
 
@@ -87,6 +87,15 @@ export async function connect(startUrl, path = configPath(), fetchImpl = fetch)
87
87
  .catch((error) => { throw new CliError("PLATFORM_UNREACHABLE", `Isomorph at ${url.host} could not be reached: ${transportFailure(error)}.`); });
88
88
  const body = await response.json().catch(() => undefined);
89
89
  if (!response.ok && isRecord(body) && typeof body.error_description === "string") {
90
+ // A person in two companies who is already connected to one of them is
91
+ // not asking a question: every builder on a connected machine ran
92
+ // `connect <email>` on the skill's say-so and got this refusal (bench,
93
+ // 9 of 21 builds). The saved connection stands; a start link switches.
94
+ if (body.error === "tenant_ambiguous" && Array.isArray(body.tenants)) {
95
+ const saved = await loadConfig(path).catch(() => undefined);
96
+ if (saved && body.tenants.some(tenant => isRecord(tenant) && tenant.tenantId === saved.tenantId))
97
+ return saved;
98
+ }
90
99
  // The server's sentence already names the fix. Its links are printed without
91
100
  // their scheme: the failure printer strips every full URL (output.ts), and
92
101
  // `connect` accepts a link without one. `tenants` (with the full links) rides
@@ -67,7 +67,7 @@ Never guess a connection, channel, table, view, mailbox or column: a guess is re
67
67
 
68
68
  ## Request
69
69
 
70
- \`isomorph integrations request <connection> --reason "<why>" --app-root . --json\` files the one request IT approves once for every environment (\`isomorph dev\` and \`isomorph deploy\` file it too); submit it yourself, in the same turn. \`isomorph integrations status --app-root . --json\`: READY, use it; PENDING, say "IT has to approve this; the app works meanwhile". \`RESOURCE_NOT_APPROVED\`: IT adds the channel, view or mailbox in the console (Controls & integrations); then run the same request again.
70
+ \`isomorph integrations request <connection> --reason "<why>" --app-root . --json\` files the one request IT approves once for every environment; run it once \`isomorph check\` has read the call (\`dev\`/\`deploy\` file it too). \`isomorph integrations status --app-root . --json\`: READY, use it; PENDING, say "IT has to approve this; the app works meanwhile". \`RESOURCE_NOT_APPROVED\`: IT adds the channel, view or mailbox in the console (Controls & integrations); then run the same request again.
71
71
 
72
72
  ## Consent
73
73
 
@@ -48,6 +48,17 @@ export async function runJob(root, name, scheduledAt, run = runCommand, real = f
48
48
  const gatewayUrl = real ? lock.origin : `http://127.0.0.1:${lock.ports.gateway}`;
49
49
  const result = await run(process.execPath, ["--experimental-strip-types", "--input-type=module", "--eval", runner], { cwd: root, env: { ...env, ISOMORPH_GATEWAY_URL: gatewayUrl, ISOMORPH_SCHEDULED_AT: timestamp } });
50
50
  if (result.code !== 0)
51
- throw new CliError("JOB_FAILED", `Job ${name} failed: ${result.stderr.trim().split("\n").at(-1) ?? "process exited non-zero"}`);
51
+ throw new CliError("JOB_FAILED", `Job ${name} failed: ${jobFailureReason(result.stderr)}`, undefined, "Fix what the error names in jobs/<name>.ts, then run this again; `details.stderr` carries the last lines.", undefined, { details: { stderr: result.stderr.trim().split("\n").slice(-20) } });
52
52
  return { name, schedule: job.schedule, scheduledAt: timestamp, mode: real ? "real" : "local" };
53
53
  }
54
+ /**
55
+ * The line of a failed job's stderr that says what went wrong. Node prints
56
+ * the stack first and its own version last, so the last line — what this
57
+ * used to report — was `Node.js v24.19.0` ten times in one build (bench
58
+ * s19, 2026-09-16). The first line naming an error wins; failing that, the
59
+ * first non-empty line.
60
+ */
61
+ export function jobFailureReason(stderr) {
62
+ const lines = stderr.split("\n").map(line => line.trim()).filter(Boolean);
63
+ return lines.find(line => /\b(?:error|exception|failed|cannot|unexpected|invalid|denied|refused)\b/i.test(line)) ?? lines[0] ?? "process exited non-zero";
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/cli",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Isomorph development kit CLI",
5
5
  "type": "module",
6
6
  "bin": {