@zoahhq/cli 0.1.0

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.
Files changed (4) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +348 -0
  3. package/dist/cli.mjs +101429 -0
  4. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2026 Zoah Inc.
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ <p align="center">
2
+ <img src="https://zoah.com/assets/images/opacity-mark.svg" alt="Zoah" width="84" />
3
+ </p>
4
+
5
+ # @zoahhq/cli
6
+
7
+ The Zoah CLI. Sign in from your terminal, import first-party React components into Zoah, and optionally rewrite local imports.
8
+
9
+ Published to npm as [`@zoahhq/cli`](https://www.npmjs.com/package/@zoahhq/cli). The package installs the `zoah` command.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install -g @zoahhq/cli
15
+ ```
16
+
17
+ Verify the installation with `zoah --version`. The package also provides `opacity` as a temporary compatibility alias.
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ zoah login # sign in via your browser
23
+ zoah import # scan ./src, import and publish your components
24
+ zoah import --swap # also rewrite local imports to the published package
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ ### `zoah login`
30
+
31
+ Opens your browser to the Zoah sign-in flow. It saves the credential with `chmod 600`. Other commands reuse its token and base URL.
32
+
33
+ Credentials are layered (same precedence as `git config local > global`):
34
+
35
+ - **Default, global:** writes `~/.zoah/credentials.json`. One sign-in covers every Zoah project on this machine.
36
+ - **`--local`:** writes `<cwd>/.zoah/credentials.json`. Use this for a project-specific account or instance. The CLI also writes `<cwd>/.zoah/.gitignore`.
37
+
38
+ Reads always check local first; falls back to global.
39
+
40
+ ```bash
41
+ zoah login # global default
42
+ zoah login --local # project-specific override
43
+ zoah login --base-url https://zoah.example.com
44
+ ```
45
+
46
+ The CLI uses `ZOAH_BASE_URL` when it is set. Otherwise, it uses `https://zoah.com`. `OPACITY_BASE_URL` remains a compatibility fallback.
47
+
48
+ ### `zoah logout`
49
+
50
+ By default, this removes the **active** credentials. It removes project-local credentials when they exist, and otherwise removes global credentials.
51
+
52
+ - `zoah logout`: clear the active layer only.
53
+ - `zoah logout --global`: remove `~/.zoah/credentials.json` even when a project-local override is also present.
54
+
55
+ To clear both, run logout twice.
56
+
57
+ ### `zoah whoami`
58
+
59
+ Prints the active user, the source credentials file, and the base URL. Use this when project credentials hide the global token.
60
+
61
+ ```bash
62
+ zoah whoami
63
+ # Signed in as you@example.com (global).
64
+ # Source: ~/.zoah/credentials.json
65
+ # Base URL: https://zoah.com
66
+ ```
67
+
68
+ ### `zoah mcp`
69
+
70
+ Serves the Zoah Action API over MCP (stdio) so a coding agent (Claude Code,
71
+ Codex, Cursor, or any MCP-capable harness) can read and edit a Zoah project
72
+ from your terminal session. The agent gets the same verbs the designer uses:
73
+ nodes, pages, components, slots, variants, variables, themes, breakpoints and
74
+ events, plus branch and pull-request tools.
75
+
76
+ #### Set it up
77
+
78
+ **1. Sign in once.** The MCP server reuses the credential `zoah login`
79
+ writes. It never asks the agent for one.
80
+
81
+ ```bash
82
+ npm i -g @zoahhq/cli
83
+ zoah login
84
+ ```
85
+
86
+ **2. Add the server to your harness.** The guided installer is the fast path:
87
+
88
+ ```bash
89
+ zoah mcp init
90
+ ```
91
+
92
+ It asks four things:
93
+
94
+ | Question | Options | Pick this when |
95
+ | ------------------ | ----------------------------------------------------------- | ------------------------------------------------------------- |
96
+ | Which harness | Claude Code, Cursor, VS Code, Codex, or print the snippet | Pick the harness that will use Zoah |
97
+ | How to launch | `npx -y @zoahhq/cli@latest`, or the global `zoah` binary | `npx` for a config you check in, so teammates need no install |
98
+ | What agents may do | full access, or `--read-only` | read-only serves the query verbs alone |
99
+ | Default project | the repo's linked project from `.zoah/config.json`, or none | baking it in means `connect` needs no ids |
100
+
101
+ It merges into an existing config without disturbing other servers. Every answer
102
+ is also a flag (`--harness`, `--global-command`, `--read-only`, `--project`,
103
+ `--print`) for scripted setup.
104
+
105
+ To write the entry by hand instead:
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "zoah": {
111
+ "command": "npx",
112
+ "args": ["-y", "@zoahhq/cli@latest", "mcp"]
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ **3. Point the agent at a project.** In the agent session:
119
+
120
+ ```
121
+ connect(project: "my-project")
122
+ ```
123
+
124
+ `connect` signs in when the credential is missing or expired. It returns a URL
125
+ for you to approve in a browser, then resolves the project by slug, name or id
126
+ and opens the session. Give it `branch` to pick one. With no branch it creates a
127
+ fresh `agent/<timestamp>` branch off main, so an agent's edits stay reviewable
128
+ rather than landing on main.
129
+
130
+ **4. Check it worked.** `status` reports the connected project, the branch, and
131
+ workspace role. It also reports credential health. `whoami` reports the
132
+ identity, role, and instance.
133
+
134
+ #### What the agent gets
135
+
136
+ The server exposes each Action API verb that can run in a headless project
137
+ session without direct human confirmation. Confirmation actions stay in the
138
+ Zoah app. The server replaces renderer-only and workspace-agent verbs with
139
+ the session tools below.
140
+
141
+ | Group | Tools |
142
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
143
+ | Session | `connect`, `status`, `pullEvents`, `disconnect`, `whoami`, `logout`, `describeProperties` |
144
+ | Workspaces and projects | `listWorkspaces`, `createWorkspace`, `listProjects`, `createProject`, `updateProject` |
145
+ | Branches | `listBranches`, `createBranch` |
146
+ | Pull requests | `createPullRequest`, `getPullRequest`, `updatePullRequest`, `commentOnPullRequest` |
147
+ | Canvas | one per applicable Action API verb for nodes, pages, components, slots, variants, variables, themes, breakpoints, events, and queries |
148
+
149
+ Two properties are worth knowing. Mutation tools return only after the sync
150
+ gateway acks the write, so a returned success means persisted, not queued. And
151
+ the server does not provide branch, project, or workspace deletion.
152
+ `pullEvents` returns structured rejection and collaborator action details. It
153
+ also returns merge summaries. Sync and cache signals mean the agent should
154
+ re-query after reconnect catch-up.
155
+ For a read role, the server hides mutation tools and connects to an existing
156
+ branch. It does not create an agent branch.
157
+ Deleting one is irreversible from here, and it is easy for an agent to reach for
158
+ while recovering from its own mistake. That stays in the web UI, where a person
159
+ can see what they are about to destroy.
160
+
161
+ #### Working against local dev
162
+
163
+ The published bundle targets `https://zoah.com`. For a local instance, link
164
+ the checkout instead of installing from npm, then sign in against it:
165
+
166
+ ```bash
167
+ zoah login --base-url http://localhost:5070
168
+ ```
169
+
170
+ `connect` accepts a `baseUrl` parameter, and it wins over the saved
171
+ credential. It exists for the clean-workspace case: with no credential file
172
+ yet, the built-in default is a guess, and on a multi-instance dev machine it
173
+ is usually the wrong port. The choice is saved, so later calls agree - a
174
+ session cannot silently straddle two instances, and `status` reports a
175
+ mismatch between the open session and the credential on disk
176
+ (`baseUrlMismatch`) rather than letting one develop unnoticed. Build and link
177
+ steps are in [DEVELOPMENT.md](./DEVELOPMENT.md).
178
+
179
+ Design and details: [docs/action-api-mcp.md](docs/action-api-mcp.md).
180
+
181
+ ### `zoah import [path]`
182
+
183
+ Scans first-party React components and sends an import payload to Zoah. It publishes a package and writes project state under `.zoah/`. The default path is `src`.
184
+
185
+ ```bash
186
+ zoah import # scans ./src
187
+ zoah import src/components # scope to a subdirectory
188
+ zoah import --swap # also rewrite local imports after publish
189
+ zoah import --project-name my-ui # override the first project name
190
+ zoah import --branch main # target a specific branch
191
+ zoah import --debug # also write .zoah/debug/payload.json
192
+ zoah import --print-issues warn # print issue details at or above this level
193
+ zoah import --dry-run # preview locally without an API call
194
+ ```
195
+
196
+ The first import creates a project and saves its IDs to `.zoah/config.json`. Later imports use the same project.
197
+
198
+ Notable flags:
199
+
200
+ - `--swap`: runs `zoah swap` after a successful import.
201
+ - `--project-name <name>`: sets the first project name. Later imports use the project ID in `.zoah/config.json`.
202
+ - `--branch <name>`: defaults to the project's saved branch (usually `main`).
203
+ - `--base-url <url>`: overrides the URL saved at login.
204
+ - `--print-issues <level>`: also prints full details for every issue at or above `info | warn | error`.
205
+ - `--dry-run`: runs the local pipeline without publishing. It still writes `.zoah/debug/issues.json`.
206
+
207
+ The CLI skips components imported directly by an entry file (`src/main.tsx`, `src/index.tsx`, etc.). Translating them would blank the running app.
208
+
209
+ ### `zoah preflight [path]`
210
+
211
+ Runs the import pipeline locally without publishing. It writes `.zoah/debug/issues.json` but does not call the API or change project state.
212
+
213
+ - preview what a real `zoah import` would do on a checkout you have not published,
214
+ - audit which codes a codebase trips before deciding whether to upgrade the CLI,
215
+ - regenerate the issues snapshot after a source change without re-publishing.
216
+
217
+ ```bash
218
+ zoah preflight # scans ./src
219
+ zoah preflight src/components # scope to a subdirectory
220
+ zoah preflight --print-issues warn # also print issue details
221
+ ```
222
+
223
+ The full code list, grouped by what the swap actually does (cleanly / visibly degrades / refuses), is documented in `docs/supported-shapes.md` in the repo.
224
+
225
+ ### `zoah swap [path]`
226
+
227
+ Installs the published Zoah package and wraps your local components. It reads `.zoah/components.json` and `.zoah/manifest.json` from `zoah import`.
228
+
229
+ ```bash
230
+ zoah swap # install the package and rewrite imports
231
+ zoah swap --dry-run # print diffs only
232
+ zoah swap --print-issues warn # print issue details
233
+ ```
234
+
235
+ The dry-run mode is the safe way to preview what swap will do before letting it touch your source tree.
236
+
237
+ #### Authenticated installs (`.npmrc` management)
238
+
239
+ Published packages carry the project's privacy. CLI imports are private by
240
+ default, so the install needs credentials. When the repo's `.npmrc` (and your
241
+ `~/.npmrc`) has no token for the Zoah registry host, swap mints a registry
242
+ API key for the project's organization and manages the file for you:
243
+
244
+ - The key is requested through the authenticated import API, requires write
245
+ access in the project's organization, and is **reused** on repeat runs (one
246
+ key per project + user, not one per invocation). It appears in Project
247
+ Settings → Package as `CLI import · <project>` and is revocable there.
248
+ - The token line (`//<host>/:_authToken=…`) is written to `<repo>/.npmrc`
249
+ with owner-only permissions (0600), and `.npmrc` is added to `.gitignore`.
250
+ - **If `.npmrc` is already tracked by git**, `.gitignore` cannot protect it.
251
+ swap prints a loud warning telling you to `git rm --cached .npmrc` before
252
+ your next commit.
253
+ - When signed out, the install continues without authentication. Public
254
+ packages still work. A private package install returns the registry's 401.
255
+
256
+ ## Reading the import output
257
+
258
+ After each `zoah import` run, the CLI prints a status table for each component:
259
+
260
+ ```
261
+ Component Import Swap
262
+ ──────────────────── ────── ────
263
+ Avatar █ █
264
+ Button █ █ 1 error
265
+ Card █ ░ █ 2 warnings, 1 info
266
+ ToastProvider █ █
267
+
268
+ File-level:
269
+ src/utils.ts █ 1 warning
270
+ src/App.tsx ░ 1 info
271
+
272
+ 1 error, 2 warnings, 1 info. See .zoah/debug/issues.json
273
+
274
+ View in Zoah: https://zoah.com/projects/<id>/design
275
+ ```
276
+
277
+ Each cell is two glyphs: the **severity slot** on the left summarises the worst outcome for that (row, phase) pair, and the **info-note slot** on the right lights up when any info-level notes accrued.
278
+
279
+ | Cell | Meaning |
280
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
281
+ | `█` green | Phase ran cleanly (component imported successfully, no issues). |
282
+ | `█` yellow | Phase ran with at least one warning. |
283
+ | `█` red | Phase ran with at least one error. |
284
+ | `░` dim | On a component row, phase didn't reach this row (e.g. swap blocked by an import error). On a File-level row, phase ran with only info-level notes (e.g. an entry file that was intentionally skipped). |
285
+ | blank | On a File-level row, the phase did not touch this file. Sparse rows leave unused phases empty. |
286
+ | trailing `░` | The cell has at least one info-level note beside a colored severity block. See the JSON for details. |
287
+
288
+ The trailing summary line counts errors, warnings, and info notes (zero-counts are omitted, so a clean run with only info notes still shows e.g. `3 info`). Full per-issue detail is in the JSON snapshot.
289
+
290
+ ### `.zoah/debug/issues.json`
291
+
292
+ Every run writes all issues to `.zoah/debug/issues.json`, grouped by phase and severity. Shape:
293
+
294
+ ```jsonc
295
+ {
296
+ "schemaVersion": 1,
297
+ "generatedAt": "2026-05-04T18:00:00.000Z",
298
+ "commitSha": "abc1234+dirty",
299
+ "summary": { "total": 12 },
300
+ "phases": {
301
+ "import": { "total": 11, "issues": { "error": [...], "warn": [...], "info": [...] } },
302
+ "package": { "total": 0, "issues": { "error": [], "warn": [], "info": [] } },
303
+ "swap": { "total": 1, "issues": { "error": [], "warn": [...], "info": [] } }
304
+ }
305
+ }
306
+ ```
307
+
308
+ Each issue carries a stable `code` (e.g. `DYNAMIC_CLASSNAME_UNRESOLVED`, `CODEMOD_FILE_REFUSED`), a `target` describing what it applies to (`node` / `component` / `file` / `project`), an optional `location` with a 3-line source snippet, and a `commitSha` stamp. The full code list with severity and phase mappings, grouped by outcome, is documented in `docs/supported-shapes.md` in the repo.
309
+
310
+ When the import fails (e.g. the server returns 400 because no components were extractable), the CLI still writes this file and prints the table so you can see exactly which components couldn't be imported and why. Pass `--print-issues warn` (or `info`/`error`) to also print snippet + caret detail inline:
311
+
312
+ ```
313
+ warn Card · import · DYNAMIC_CLASSNAME_UNRESOLVED src/Card.tsx:42:18
314
+ Dynamic className expression couldn't be resolved to styles
315
+ 41 | const cls = `card-${variant}`;
316
+ 42 | return <div className={cls}>...</div>;
317
+ ^^^
318
+ 43 | }
319
+ ```
320
+
321
+ ## What the CLI writes
322
+
323
+ **Global (per-machine, never inside a project):**
324
+
325
+ - `~/.zoah/credentials.json`: auth token and base URL (chmod 600). Written by `zoah login` unless a project overrides it.
326
+
327
+ **Project-local** (paths relative to the project where you run `zoah`). The CLI manages `.zoah/.gitignore` so secrets and debug output stay out of git.
328
+
329
+ Commit these files. They bind the checkout to the Zoah project:
330
+
331
+ - `.zoah/config.json`: workspace and project binding, written on the first import.
332
+ - `.zoah/components.json`: component IDs, package exports, and slot anchors.
333
+ - `.zoah/manifest.json`: published package name, version, and registry URL.
334
+
335
+ Ignored automatically through `.zoah/.gitignore`:
336
+
337
+ - `.zoah/credentials.json`: local credential from `login --local`.
338
+ - `.zoah/debug/issues.json`: issues from each run, grouped by phase and severity.
339
+ - `.zoah/debug/payload.json`: raw import payload from `--debug`.
340
+
341
+ The CLI reads old `.opacity` state when the matching `.zoah` file does not exist. New writes use `.zoah`.
342
+
343
+ ## Environment variables
344
+
345
+ - `ZOAH_BASE_URL`: default base URL when no flag or credential sets one.
346
+ - `ZOAH_BRANCH`: default branch for `import` when `--branch` is not passed.
347
+
348
+ The old `OPACITY_BASE_URL` and `OPACITY_BRANCH` variables remain compatibility fallbacks.