@isomorph.ai/cli 0.3.4 → 0.4.0-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
@@ -12,7 +12,7 @@ npx -y @isomorph.ai/cli agent-setup
12
12
 
13
13
  Paste that line into Claude Code or Codex (or a terminal). It teaches both agents the kit by installing the `isomorph` skill for each (`~/.claude/skills/isomorph/SKILL.md`, `~/.codex/skills/isomorph/SKILL.md`, with the kit's rules beside it as `core.md`, `integrations.md`, `ai.md` and `jobs.md`) and never touches your other skills or instructions. A skill is read only when the task matches it — a folder with `.isomorph/`, an app you ask for on Isomorph — so the agent works exactly as before on everything else. (Releases before 0.1.34 put the guide in your global Codex instructions, `~/.codex/AGENTS.md`, where every Codex session read it; running the line again takes that block out and leaves the rest of the file as it was.)
14
14
 
15
- That `npx` line runs the current release, but `isomorph init`, `dev` and `check` afterwards run whatever `isomorph` is installed on this machine. So `agent-setup` also compares the two and says, in its output and in `result.cli` of `--json` (`state`, `upgradeRequired`, `remediation`), when the installed CLI is missing or behind — a stale one carries an old kit bundle and builds an app the deployment pipeline refuses. The fix is always `npm i -g @isomorph.ai/cli`.
15
+ That `npx` line runs the current release; `isomorph init`, `dev`, `check` and `deploy` afterwards run whatever `isomorph` is installed on this machine. A CLI older than what your company's Isomorph platform accepts is refused by every company command in under a second (`CLI_UPGRADE_REQUIRED`), with the fix: `npm i -g @isomorph.ai/cli`.
16
16
 
17
17
  Then, in an empty folder:
18
18
 
@@ -27,28 +27,26 @@ The only step you do yourself is the company sign-in: when the agent runs `isomo
27
27
  ## Commands
28
28
 
29
29
  ```
30
- isomorph agent-setup install the `isomorph` skill for Claude Code and Codex; idempotent; reports a missing or stale installed CLI
31
- isomorph init --app-root <path> [--upgrade] starter app in an empty folder, or kit files in a Vite + React app (also runs agent-setup); --upgrade re-pins the kit bundle
32
- isomorph dev --app-root <path> [--reset] run the app locally on one loopback origin
30
+ isomorph agent-setup install the `isomorph` skill for Claude Code and Codex; idempotent
31
+ isomorph init --app-root <path> [--upgrade] starter app in an empty folder, or kit files in a Vite + React app (also runs agent-setup); --upgrade re-pins the kit bundle and runs the checks
32
+ isomorph dev --app-root <path> [--reset] [--detach] [--json] run the app locally on one loopback origin; --detach starts it in the background and prints {origin, pid} once it answers
33
33
  isomorph stop --app-root <path> stop local services, keep data
34
- isomorph check --app-root <path> [--integrations] [--json] declaration, types, build, migrations, journeys; report in .isomorph/local/check-report.json
34
+ isomorph check --app-root <path> [--integrations] [--json] types, build, migrations, database gate, write probe, journeys, coverage; --json prints the whole report
35
35
  isomorph integrations catalog --app-root <path> [--json] the company's connections as .isomorph/integrations.json names them, with approved channels/views per environment
36
- isomorph integrations request <connection> --app-root <path> [--reason <text>] one request per connection; IT approves it once for every environment (`isomorph dev` and `isomorph productionise` file it for you)
36
+ isomorph integrations request <connection> --app-root <path> [--reason <text>] one request per connection; IT approves it once for every environment (`isomorph dev` and `isomorph deploy` file it for you)
37
37
  isomorph integrations status --app-root <path> [--json]
38
38
  isomorph connect <work-email | company-start-url> once per company; then isomorph login | logout
39
- isomorph productionise --app-root <path> [--confirm-save] [--json] save + preview deployment, prints the protected link
40
- isomorph status | retry | promote | setup | profile | audience | secrets … --operation <reference> (promote: [--confirm-tested])
39
+ isomorph deploy --app-root <path> [--json] checks (when not already passed for this code), one pre-flight, save + preview deployment; prints the protected link
40
+ isomorph status | retry | promote --operation <reference> (promote: [--app-root <path>] [--confirm-tested])
41
41
  ```
42
42
 
43
- `productionise`, `status --wait`, `retry` and `promote` follow the deployment for up to `--max-wait <seconds>` (default 30 minutes). Past that they exit 0 with `status: "RUNNING"` and the `isomorph status --operation <reference> --wait --max-wait 120 --json` that continues the same deployment — a bounded wait is not a failure, and never a reason to start another deploy.
43
+ `deploy` reads the app's name, description and audience from `.isomorph/app.json` (written by `init`; edit it before shipping) and prints the three values before it starts. It then asks Isomorph once whether the app can deploy — the access request for every declared connection, the company's AI setup when the app calls governed AI, and this CLI's version — and refuses with exit 2 naming every blocker and its fix (`DEPLOY_BLOCKED`, or `INTEGRATIONS_NOT_READY` / `AI_NOT_READY` / `CLI_UPGRADE_REQUIRED` when the blockers are all of one kind).
44
44
 
45
- `productionise` and `promote` each stop for a typed reply — `Approved.` before the app files are copied to the company code system, `Tested.` before the preview is released. `--confirm-save` and `--confirm-tested` give that same reply on the command line when nobody can type it.
45
+ `deploy`, `status --wait`, `retry` and `promote` follow the deployment for up to `--max-wait <seconds>` (default 30 minutes). Past that they exit 0 with `status: "RUNNING"` and the `isomorph status --operation <reference> --wait --max-wait 120 --json` that continues the same deployment — a bounded wait is not a failure, and never a reason to start another deploy.
46
46
 
47
- `isomorph --help` prints the full usage. Local commands need no company sign-in; integrations and shipping do.
48
-
49
- ## Apps set up by the old `harbour` CLI
47
+ `promote` never prompts: `--confirm-tested` is the one flag that means the person has opened the preview and it works. `productionise` is still accepted as an unlisted alias of `deploy` for one release.
50
48
 
51
- Before 0.2.0 the CLI was published as `@fourier-labs/harbour` with the command `harbour`, and it kept the kit's files under `.harbour/`. That layout is not read any more — not by this CLI and not by the deployment pipeline. Run `isomorph init --upgrade --app-root .` once in each such app: it renames `.harbour/` to `.isomorph/` (git records renames), moves the two committed schema ids with it, rewrites the managed block in CLAUDE.md / AGENTS.md, deletes the project skill older CLIs wrote (`.claude/skills/harbour-kit/SKILL.md` up to 0.1.x, `.claude/skills/isomorph-kit/SKILL.md` up to 0.2.2 — when it is the kit's, by the frontmatter name and the opening line every release wrote; one whose body you rewrote is kept, and reported as kept; the rules now live beside the user-level skill and no project skill replaces it), deletes the checks the old kit generated (the next `isomorph check` regenerates them) and rewrites the SDK package name, its exported type names and the retired `HARBOUR_*` environment names (`HARBOUR_APP_URL` → `ISOMORPH_APP_URL` and the rest) in `package.json`, `vite.config.*`, `src/`, `jobs/` and the checks you wrote yourself. Run it again after updating the CLI: an app already moved by 0.2.0 loses its old skill and old environment names the same way. Every other command refuses an un-upgraded app and names that command. The old CLI can stay installed; it no longer deploys.
49
+ `isomorph --help` prints the full usage. Local commands need no company sign-in; integrations and shipping do.
52
50
 
53
51
  ## Development
54
52
 
@@ -1,21 +1,10 @@
1
- import { execFile } from "node:child_process";
2
1
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { promisify } from "node:util";
7
- import { CLI_VERSION } from "./version.js";
8
4
  import { GUIDE_MODULES } from "./guide.js";
9
5
  import { continueCommand } from "./operations.js";
10
- const execFileAsync = promisify(execFile);
11
6
  export const MANAGED_START = "<!-- isomorph:kit:start -->";
12
7
  export const MANAGED_END = "<!-- isomorph:kit:end -->";
13
- /**
14
- * The one command that installs or upgrades the CLI. It carries no dist-tag: npm resolves an
15
- * untagged name through `latest`, which is what a release publishes, so every instruction in
16
- * the kit names the package the same way a person typing it from memory would (LLD §15.5).
17
- */
18
- export const CLI_INSTALL_COMMAND = "npm i -g @isomorph.ai/cli";
19
8
  /**
20
9
  * Where the two agents read the user-level guide from; both honour the tools' own
21
10
  * override variables. Both are skills — a file each tool lists by name and description
@@ -40,13 +29,11 @@ export function agentPaths(env = process.env) {
40
29
  * workflow) and the four rule modules beside it, every file wholly owned by the kit —
41
30
  * and takes the guide back out of Codex's global AGENTS.md where an older release put
42
31
  * it, keeping everything else in that file. Idempotent:
43
- * unchanged files are reported as kept. Also reports which `isomorph` the agent's next
44
- * command will run (`result.cli`), because this command is routinely reached through
45
- * `npx` while everything after it is not.
32
+ * unchanged files are reported as kept.
46
33
  */
47
- export async function agentSetup(env = process.env, lookups = {}) {
34
+ export async function agentSetup(env = process.env) {
48
35
  const paths = agentPaths(env);
49
- const result = { created: [], updated: [], kept: [], removed: [], cli: await checkCliVersion(lookups) };
36
+ const result = { created: [], updated: [], kept: [], removed: [] };
50
37
  for (const skill of [paths.claudeSkill, paths.codexSkill])
51
38
  for (const file of skillFiles(skill))
52
39
  result[await upsertManagedBlock(file.path, file.text, {})].push(file.path);
@@ -58,94 +45,6 @@ export async function agentSetup(env = process.env, lookups = {}) {
58
45
  export function skillFiles(skill) {
59
46
  return [{ path: skill, text: `${SKILL_FRONTMATTER}\n${AGENT_GUIDE}` }, ...Object.entries(GUIDE_MODULES).map(([name, text]) => ({ path: join(dirname(skill), `${name}.md`), text }))];
60
47
  }
61
- /** Compares the globally resolvable `isomorph` with the package this process runs from. */
62
- export async function checkCliVersion(lookups = {}) {
63
- const running = await (lookups.runningCliVersion ?? runningPackageVersion)();
64
- // No `isomorph` on PATH, output with no version in it, a non-zero exit or a hang all
65
- // mean the same thing: there is no installed CLI whose version can be trusted.
66
- const installed = parseVersion(await (lookups.installedCliVersion ?? installedCliVersion)() ?? "");
67
- const consequence = "carries an old kit bundle, so the app it creates passes every local check and is then refused by the deployment pipeline (kit_bundle_incompatible)";
68
- if (!installed) {
69
- // The CLI used to be published under another name with another command; a machine that still has it needs to know that it no longer deploys.
70
- const legacy = await (lookups.legacyCliInstalled ?? legacyCliInstalled)() ? " The old `harbour` CLI is installed; it no longer deploys — install @isomorph.ai/cli." : "";
71
- return { running, state: "missing", upgradeRequired: true, remediation: CLI_INSTALL_COMMAND, message: `No \`isomorph\` command is installed on this machine; this package is ${running}. \`isomorph init\`, \`dev\` and \`check\` run the installed CLI, not this one, and a missing or stale CLI ${consequence}.${legacy}` };
72
- }
73
- const order = compareCliVersions(installed, running);
74
- if (order < 0)
75
- return { running, installed, state: "stale", upgradeRequired: true, remediation: CLI_INSTALL_COMMAND, message: `The installed \`isomorph\` command is ${installed}, older than this package (${running}). \`isomorph init\`, \`dev\` and \`check\` run the installed CLI, not this one, and a stale CLI ${consequence}.` };
76
- if (order > 0)
77
- return { running, installed, state: "ahead", upgradeRequired: false, message: `The installed \`isomorph\` command is ${installed}, newer than this package (${running}); the installed one is what runs.` };
78
- return { running, installed, state: "current", upgradeRequired: false, message: `The installed \`isomorph\` command is ${installed}, the same version as this package.` };
79
- }
80
- /** What `agent-setup` and `init` print about the installed CLI; an upgrade is impossible to miss in a scrolling log. */
81
- export function cliVersionLines(check) {
82
- if (!check.upgradeRequired)
83
- return [check.message];
84
- return ["", "!!! UPGRADE THE ISOMORPH CLI BEFORE `isomorph init` OR ANY OTHER ISOMORPH COMMAND !!!", check.message, `Run: ${check.remediation}`, ""];
85
- }
86
- /** Numeric-core semver order; a prerelease sorts before its release. An unparseable version compares equal, so nothing is called stale on a guess. */
87
- export function compareCliVersions(left, right) {
88
- const parse = (value) => {
89
- const match = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(value);
90
- return match ? { core: [Number(match[1]), Number(match[2]), Number(match[3])], pre: match[4] } : undefined;
91
- };
92
- const a = parse(left);
93
- const b = parse(right);
94
- if (!a || !b)
95
- return 0;
96
- for (let index = 0; index < 3; index += 1)
97
- if (a.core[index] !== b.core[index])
98
- return a.core[index] < b.core[index] ? -1 : 1;
99
- if (a.pre === b.pre)
100
- return 0;
101
- if (a.pre === undefined)
102
- return 1;
103
- if (b.pre === undefined)
104
- return -1;
105
- return a.pre < b.pre ? -1 : 1;
106
- }
107
- /** Whatever the globally resolvable `isomorph` prints for `--version`; undefined when running it is not possible at all. */
108
- async function installedCliVersion() {
109
- try {
110
- const { stdout } = await execFileAsync("isomorph", ["--version"], { timeout: 20_000, windowsHide: true });
111
- return stdout;
112
- }
113
- catch (error) {
114
- return error.stdout;
115
- }
116
- }
117
- /** Whether the CLI's previous command name is still on PATH (whatever it answers). */
118
- async function legacyCliInstalled() {
119
- try {
120
- await execFileAsync("harbour", ["--version"], { timeout: 20_000, windowsHide: true });
121
- return true;
122
- }
123
- catch (error) {
124
- return error.code !== "ENOENT";
125
- }
126
- }
127
- function parseVersion(text) {
128
- return /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(text.trim())?.[1];
129
- }
130
- /**
131
- * The version of the package this process runs from: the nearest package.json named
132
- * `@isomorph.ai/cli`, walking up from this file. That is `packages/harbour-cli/`
133
- * in the repository and the package root once published (the entry point is
134
- * `dist/packages/harbour-cli/src/cli.js`), so no environment variable is involved.
135
- */
136
- async function runningPackageVersion() {
137
- let directory = dirname(fileURLToPath(import.meta.url));
138
- for (let depth = 0; depth < 12; depth += 1) {
139
- const manifest = await readFile(join(directory, "package.json"), "utf8").then(text => JSON.parse(text), () => undefined);
140
- if (manifest?.name === "@isomorph.ai/cli" && typeof manifest.version === "string")
141
- return manifest.version;
142
- const parent = dirname(directory);
143
- if (parent === directory)
144
- break;
145
- directory = parent;
146
- }
147
- return CLI_VERSION;
148
- }
149
48
  /**
150
49
  * Writes `block` to `file`: creates the file, replaces the text between the markers
151
50
  * when both are present, or appends the block after the existing content. Without
@@ -201,34 +100,35 @@ description: ${SKILL_DESCRIPTION}
201
100
  /** One guide, shared by the Claude Code and Codex skills: the workflow only, written for an agent working with a non-developer. Every rule lives in a module (`guide.ts`) beside it and is pointed at, not restated. */
202
101
  export const AGENT_GUIDE = `# Isomorph development kit
203
102
 
204
- This guide is for one job: an app that runs on Isomorph — a folder with \`.isomorph/\`, or a new app the person asks for on Isomorph. Anything else is not this job: ignore every rule below and work as you always do. Never turn an existing project into an Isomorph app unless the person asks for that by name.
103
+ This guide is for one job: an app that runs on Isomorph (a folder with \`.isomorph/\`, or a new app asked for on Isomorph). Anything else: ignore every rule below and work as you always do. Never turn an existing project into an Isomorph app unless asked by name.
205
104
 
206
- The person may not be a developer. They say what they want in plain English; you build it and run every command yourself. Never ask them to type a terminal command (the one exception is sign-in, below). Prefer \`--json\` output and read it yourself; never paste JSON, logs, stack traces or file contents at them.
105
+ The person may not be a developer: they say what they want in plain English; you build it and run every command (they type none but the sign-in below). Use \`--json\`; never paste JSON, logs or file contents at them.
207
106
 
208
- The kit's rules are the files beside this one: \`core.md\` before the first edit; \`integrations.md\`, \`ai.md\` and \`jobs.md\` before the work each names.
107
+ Rules live beside this file: \`core.md\` before the first edit; \`integrations.md\`, \`ai.md\`, \`jobs.md\` before the work each names.
209
108
 
210
- ## Getting ready (once per machine and folder)
109
+ ## Getting ready
211
110
 
212
- 1. CLI, before \`isomorph init\` or anything else: the installed CLI must match the current release. \`isomorph agent-setup\` prints both versions and says when the installed one is missing or older (\`--json\`: \`cli.upgradeRequired\`, \`cli.remediation\`); otherwise compare \`isomorph --version\` with \`npx -y @isomorph.ai/cli --version\`. If it is missing or older, run \`npm i -g @isomorph.ai/cli\` and confirm \`isomorph --version\` now matches: every later command runs the installed CLI, and a stale one builds an app the deployment pipeline refuses (\`kit_bundle_incompatible\`). Node 22+, no Docker.
213
- 2. Folder: if it has no \`.isomorph/\` directory, run \`isomorph init --app-root .\` — an empty folder gets a small starter app, an existing Vite + React app gets the kit files and nothing overwritten.
214
- 3. Sign-in, needed only for company systems and shipping: run \`isomorph login\`. It opens the browser and the person finishes the sign-in there — the one step they do themselves; tell them so in one line. It then prints the company and what IT has set up. If it says the company is not connected yet, ask for their work email and run \`isomorph connect <work-email>\` — Isomorph looks the company up from the email's domain. Only if that fails use a link: if it answers that more than one company admits the domain, ask which one and run the command it printed; if it says no company admits the domain, their IT admin has to add it or give them a link (their Isomorph page \`https://platform.isomorph.ai/t/<company>\`, or the setup link), and then run \`isomorph connect <link>\`.
111
+ - No \`.isomorph/\` yet: \`isomorph init --app-root .\` (empty folder → starter; Vite + React app → kit files only). 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).
215
113
 
216
- ## What they say → what you do
114
+ ## Intent → command
217
115
 
218
- - "run it", "show me", "let me try it" → start \`isomorph dev --app-root .\` in the background (the first start downloads the native runtime; a minute or two). Wait for \`Isomorph dev is running: http://127.0.0.1:<port>\` and give them that link — unasked as soon as the first check is green. Locally they are a fixture user; no company sign-in is needed.
219
- - "check it", "is it ok?", "is it ready?" → with dev running, \`isomorph check --app-root . --json\`, then read \`.isomorph/local/check-report.json\`. Failures in the app's code are yours: fix, then check again until it is clean — after every change and before every ship, without being asked and without offering them as a choice.
220
- - "does it work?", and before you report anything as working → open the dev link in your own browser, press the control you built or changed, and read what the app shows. A green \`isomorph check\` is not that proof: fixtures answer AI and company systems, so their refusals appear only when the control is really pressed. Test rendering, navigation, fixtures and approved reads automatically; a Send in development is real: reuse explicit authorization for that bounded test, or ask once if none exists (IT approval alone is not permission to send). If you have no browser, say that the button itself is untested.
221
- - "I need Slack / Gmail / the warehouse / company data" → read \`integrations.md\` beside this skill first; then, in the same turn, read the catalog, declare only what the app calls, submit the access request yourself, and say READY or PENDING in one line.
222
- - "summarise", "draft", "explain", "AI" → read \`ai.md\` beside this skill first; then one call behind a control they press.
223
- - "ship it", "put it online", "let my team try it" → \`isomorph check --app-root . --json\` first, unasked; fix everything it finds: the same gates rerun in the cloud, minutes per attempt. Before sharing or deploying the private preview, show the exact proposed app name, description and audience that will be passed to Isomorph (say "only you" when the audience is empty); ask for one confirmation or correction covering all three; do not run \`productionise\` until they confirm. \`isomorph productionise --app-root . --name "<app name>" --description "<description>" --emails "<comma-separated audience>" --confirm-save --json\` (omit \`--emails\` for "only you"; \`--confirm-save\` is the approval they just gave). It records the confirmed setup before uploading or deploying the app, files the access request for each declared connection itself and refuses with \`INTEGRATIONS_NOT_READY\` naming what IT still has to approve, or \`AI_NOT_READY\` and the one IT step: relay that line only; the app works without them until then. Pass \`--session-notes\`: what in Isomorph cost you time, what you believed, what was true; \`""\` if nothing did. \`result.deployment.protectedUrl\` is the private preview, for them and the confirmed audience after company sign-in. If your tool cuts it off, \`${continueCommand("<ref>")}\` continues the same deployment; never start another one to find out what happened; \`TRANSFORMING\` means building and checking.
224
- - "make it live for everyone", "go to production" → only after they have tried the preview, which \`--confirm-tested\` states. Before \`isomorph promote --operation <ref> --confirm-tested --json\`, show the exact app name, description and production audience again; ask for one confirmation or correction covering all three; never promote a profile or audience the person has not just seen and confirmed. Report the production link, or that operator approval is pending.
225
- - "stop it" → \`isomorph stop --app-root .\` (data kept); \`isomorph dev --reset --app-root .\` deletes local data, only when they ask to start over.
226
- - "every day at 2pm", "run this on a schedule", "send this automatically" → read \`jobs.md\` beside this skill first; then write the job, run it once with \`isomorph jobs run\`, and say the sentence it gives you.
116
+ | They say | You do |
117
+ |---|---|
118
+ | "run it", "show me" | \`isomorph dev --app-root . --detach --json\`; give them \`result.origin\` (the first start takes a minute or two). Locally they are a fixture user. |
119
+ | "check it", "is it ready?" | \`isomorph check --app-root . --json\` (the output is the report). Failures in the app's code are yours: fix and re-check until clean, after every change and before every ship, unasked and never offered as a choice. |
120
+ | "does it work?" (and before reporting anything as working) | open the dev link in your own browser, press the control you built or changed, read what the app shows; a green \`isomorph check\` is not that proof (fixtures answer AI and company systems). A Send in development is real: reuse explicit authorization or ask once. No browser: say the button is untested. |
121
+ | "I need Slack / Gmail / the warehouse" | read \`integrations.md\` beside this skill first; then, in the same turn: catalog, declare only what the app calls, submit the request yourself, say READY or PENDING in one line. |
122
+ | "summarise", "draft", "AI" | read \`ai.md\` beside this skill first; then one call behind a control they press. |
123
+ | "ship it", "let my team try it" | show the person the three values in \`.isomorph/app.json\` (name, description, audience — "only you" when empty) and correct the file as they say; then \`isomorph deploy --app-root . --json\` (it runs the checks when needed and refuses once naming every blocker and its fix: relay that line; the app works meanwhile). Give them \`result.deployment.protectedUrl\`. \`--session-notes\`: what cost you time in Isomorph, or \`""\`. |
124
+ | "make it live" | only after they have tried the preview: \`isomorph promote --operation <ref> --app-root . --confirm-tested --json\`. Report the production link, or that operator approval is pending. |
125
+ | "stop it" | \`isomorph stop --app-root .\` (data kept); \`isomorph dev --app-root . --reset\` only when they ask to start over. |
126
+ | "every day at 2pm", "send this automatically" | read \`jobs.md\` beside this skill first; then write the job, run it once with \`isomorph jobs run\`, and say the sentence it gives you. |
227
127
 
228
- When a command refuses, the refusal names its own reason and its own fix: change that one thing, then run it again. A failed app needs its reported fix; a wait timeout means work is still running, so continue the saved operation — and never start a second deploy of an app while one is running, because concurrent deploys of one app cancel each other.
128
+ A refusal names its layer, its reason and its fix: change that one thing, then run it again. A wait timeout is not a failure — work is still running: run the command it prints (\`${continueCommand("<ref>")}\`); never start a second deploy while one is running.
229
129
 
230
130
  ## Talking to the person
231
131
 
232
- - Plain words, short: "Your app is running at <link>.", "IT has to approve Slack; the app works without it until then." Say what happens next and roughly how long it takes; report only what you observed, and say when something is unknown.
233
- - Keep reports natural and brief: what you verified, any remaining blocker and who can resolve it, any material scope decision. No forced headings, no repeated status. A platform check is not proof that the main task worked in the browser.
234
- - End a turn with at most one question, and only when a decision is genuinely theirs and you cannot go on without it. Never offer to do something this guide already tells you to do unasked — do it and report what happened.`;
132
+ - Plain words, short: "IT has to approve Slack; the app works meanwhile." Say what happens next and how long; report only what you observed; say when something is unknown.
133
+ - Reports: brief and natural — what you verified, any remaining blocker and who resolves it, any material scope decision; no forced headings or repeated status.
134
+ - End a turn with at most one question, only when a decision is genuinely theirs and you cannot go on without it. Never offer to do something this guide already tells you to do unasked — do it and report.`;
@@ -83,7 +83,7 @@ const REFRESH_MARGIN_MS = 5 * 60_000;
83
83
  /**
84
84
  * Returns a usable access token, refreshing only when the stored one is about
85
85
  * to expire. Refresh tokens are single-use, so every CLI process refreshing on
86
- * start meant two processes at once (a `productionise` still polling and a
86
+ * start meant two processes at once (a `deploy` still polling and a
87
87
  * `status` beside it) spent the same refresh token and one was told to sign in
88
88
  * again. A refresh that fails now re-reads the store once: if a sibling
89
89
  * process rotated the pair in the meantime, its newer token is used instead.
@@ -5,16 +5,17 @@ import { readAppSchema } from "./app-schema.js";
5
5
  import { describeRetainedChecks, syncRetainedChecks } from "./retained-checks.js";
6
6
  import { CliError } from "./output.js";
7
7
  import { DEPENDENT_READ_OPERATIONS, READ_OPERATIONS, kitPaths, readDeclaration, readKitLock, resourceNames, sourceDigest } from "./kit.js";
8
- import { LocalRuntime, allocatePorts, ensureSdk, freePort, nodePackageCommand, readDevLock, runCommand, runningOrigin } from "./local-runtime.js";
8
+ import { LocalRuntime, allocatePorts, ensureSdk, freePort, nodePackageCommand, readDevLock, runningOrigin } from "./local-runtime.js";
9
9
  import { CLI_VERSION } from "./version.js";
10
10
  /**
11
11
  * What actually failed, in the refusal itself.
12
12
  *
13
13
  * The report is already in hand at the call site, and a refusal that spends its
14
14
  * message on a file path costs the reader a whole turn to learn anything —
15
- * `productionise` already names the failing checks when it refuses to deploy
16
- * (`nameList(failed)`), and `check` did not. The detail of the first failure is
17
- * carried too, capped, because "which check" is a smaller answer than "why".
15
+ * `deploy` already names the failing checks when it refuses to deploy
16
+ * and `check` did not. Every failing check is named, in full — names are never
17
+ * elided — and the detail of the first failure is carried too, capped, because
18
+ * "which check" is a smaller answer than "why".
18
19
  */
19
20
  export function checksFailedMessage(report) {
20
21
  const failed = report.checks.filter(check => check.status === "fail");
@@ -93,7 +94,7 @@ export async function runChecks(root, options) {
93
94
  // After the gate, because `.isomorph/checks/*.mjs` is part of the deployed
94
95
  // tree (kit.ts `sourceDigest`) and the gate may have regenerated it: the
95
96
  // report has to pin the tree the journeys actually ran against, or
96
- // `isomorph productionise` would refuse the very tree this check just passed
97
+ // `isomorph deploy` would refuse the very tree this check just passed
97
98
  // as CHECKS_STALE.
98
99
  const source = await sourceDigest(root);
99
100
  const report = {
@@ -101,7 +102,7 @@ export async function runChecks(root, options) {
101
102
  createdAt: new Date().toISOString(),
102
103
  sourceDigest: source.digest,
103
104
  sourceFiles: source.entries,
104
- toolchain: { node: process.version, cliVersion: CLI_VERSION, bundle: { kitVersion: bundle.kitVersion, sdkTarballSha256: bundle.sdk.tarballSha256, appGateway: bundle.images.appGateway, sessionFixture: bundle.images.sessionFixture, briefFingerprint: bundle.brief.fingerprint } },
105
+ toolchain: { node: process.version, cliVersion: CLI_VERSION, bundle: { kitVersion: bundle.kitVersion, sdkTarballSha256: bundle.sdk.tarballSha256, briefFingerprint: bundle.brief.fingerprint } },
105
106
  checks,
106
107
  gate,
107
108
  integrations,
@@ -255,34 +256,6 @@ async function ensureSession(root, runtime, bundle, output) {
255
256
  await runtime.sessionEnv();
256
257
  return true;
257
258
  }
258
- /**
259
- * `productionise` pre-flight for a kit app: the same gate the pipeline runs,
260
- * here, before any operation exists. A failed check is refused with the
261
- * pipeline's own wording (kit.check-failed: <check>); a local runtime that
262
- * cannot start is reported and skipped — the pipeline's gate
263
- * still runs.
264
- */
265
- export async function preflightKitGate(root, bundle, output, run = runCommand, fetchImpl, runtime) {
266
- const lock = await readKitLock(root).catch(() => undefined);
267
- if (!lock)
268
- return "skipped";
269
- let report;
270
- try {
271
- report = await runKitGate(root, { run, bundle, output, ...(fetchImpl ? { fetch: fetchImpl } : {}), ...(runtime ? { runtime } : {}) });
272
- }
273
- catch (error) {
274
- if (error instanceof CliError && ["LOCAL_RUNTIME_FAILED", "KIT_IMAGES_UNAVAILABLE", "KIT_RUNTIME_UNAVAILABLE"].includes(error.code)) {
275
- output(`The kit gate was not run locally (${error.message}); the pipeline's gate will run in CodeBuild.`);
276
- return "skipped";
277
- }
278
- throw error;
279
- }
280
- const failed = report.checks.filter(check => check.status === "fail");
281
- if (failed.length)
282
- throw new CliError("KIT_GATE_FAILED", `The kit gate refused this app (${failed.length} check(s) failed):\n${failed.map(check => ` - ${check.name}: ${check.detail ?? ""}`).join("\n")}\nFix the app and run \`isomorph check\`; the pipeline would refuse this deployment as kit.check-failed: ${failed[0].name}.`);
283
- output(`The kit gate passed locally (${report.checks.length} checks; ${report.inventory.journeys.length} journey(s), ${report.inventory.migrations} migration(s)).`);
284
- return "passed";
285
- }
286
259
  // ---- Real integrations ----------------------------------------------------------------
287
260
  /** Explicit read operations only (READ_OPERATIONS) against READY development grants; sends never run. A dependent read (gmail.message.read) is exercised through its list: the newest message of the first thread, when there is one. */
288
261
  async function testIntegrationReads(appId, client, declaration, output) {