canopy-engine 0.1.8 → 0.1.9

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
@@ -1,241 +1,242 @@
1
- # Canopy
2
-
3
- > **Beta. JS/TS only.** Canopy is an early release for testers. It reads JavaScript and TypeScript projects; other languages are not read.
4
-
5
- Canopy reads a **JavaScript or TypeScript** codebase and draws a map of what it does: where execution starts (routes, event listeners, socket handlers, the app's main file), what each of those calls, in order, and how the parts of a project (services) fit together. The map is built from the code itself.
6
-
7
- **JavaScript and TypeScript only.** Exactly which files are read is listed in "Which files are analysed" below.
8
-
9
- ## Install (needs Node 18 or newer)
10
-
11
- **1. Try it, always the latest, nothing to install.** Run this inside your project folder:
12
-
13
- ```sh
14
- npx -y canopy-engine@latest # needs the network
15
- ```
16
-
17
- **2. Install for regular use.** Once, then use the short name:
18
-
19
- ```sh
20
- npm install -g canopy-engine # needs the network
21
- canopy
22
- ```
23
-
24
- Without installing anything, `npx canopy-engine` runs the same program (it downloads it the first time). **Do not type `npx install -g canopy-engine`**: `npx install` is not a command. The install command is `npm install -g canopy-engine`, and then you run `canopy`.
25
-
26
- (The two lines marked "needs the network" download from the npm registry. If you were given a file instead, `canopy-engine-0.1.5.tgz`, install that: `npm install -g ./canopy-engine-0.1.5.tgz`.)
27
-
28
- **If the `canopy` command clashes with another tool** on your computer, use `canopy-engine` instead. It is the same program under a second name: `canopy-engine --version`, `canopy-engine doctor`, and so on.
29
-
30
- ## What `canopy` does
31
-
32
- `canopy` does these steps in order and prints a plain line for each; if one fails it stops with one line saying what happened and what to do:
33
-
34
- 1. **Checks this computer** - Node 18+, a folder you can write to, free disk space (300 MB), git, a free port (the same checks as `canopy doctor`).
35
- 2. **Looks at your project** - counts the JavaScript / TypeScript files. A folder with none (for example a Python-only project) stops here: "Canopy currently supports JavaScript and TypeScript".
36
- 3. **Prepares `.canopy/`** - the only folder Canopy writes inside your project; it adds `.canopy/` to `.gitignore` (creating the file if needed, never removing a line).
37
- 4. **Asks about the commit hook** - "Update the map on every commit? [Y/n]" (default yes; `-y` skips the question; with no terminal it answers yes). See "Hook on and off" below.
38
- 5. **Builds the map** - the first time takes seconds to minutes depending on size (a project of ten thousand files takes some minutes); a later run on an unchanged project answers "up to date" at once, and after an edit it re-reads only the changed files and links the whole project again. It ends with a short summary: services, workflows, files inside a service, files with parse errors.
39
- 6. **Opens the map** - starts a viewer on a free local port (127.0.0.1 only) and opens your browser. Ctrl+C stops it.
40
-
41
- Then it prints "Next time just run `canopy`." and the feedback link. Options: `-y` / `--yes` (no questions; installs the hook), `--no-hook` (no hook, no question), `--no-open` (build the map, do not start the viewer; for scripts), `--port=N`, `--for=SECONDS` (stop the viewer by itself), `--full` (ignore everything remembered and analyse from scratch), `--quiet` (print only problems and the address), `--no-color` (or set `NO_COLOR=1`; also used automatically when the output is not a terminal), and a folder to look at instead of the current one: `canopy path/to/project`.
42
-
43
- Supported languages: JavaScript and TypeScript only (see "Which files are analysed" below).
44
-
45
- ## Which files are analysed
46
-
47
- Read from the source of the engine, not guessed:
48
-
49
- - **Parsed as JavaScript:** `.js .jsx .mjs .cjs`. **Parsed as TypeScript:** `.ts .mts .cts` (and `.tsx` with the TSX grammar).
50
- - **Found but not analysed:** declaration files (`.d.ts .d.mts .d.cts`: they only describe types), `*.min.js`, `*.bundle.js`, files over 1 MB and files with a line over 10,000 characters (minified or bundled code), and everything inside `node_modules`, `.git`, `dist`, `build`, `out`, `coverage`, `vendor`, `.next`, `.nuxt`, `.turbo`, `.cache`, `.yarn`, `.pnp`, `bower_components` and anything your `.gitignore` lists. They are counted in the coverage summary.
51
- - **`.html`** is read only to find the page's `<script src>` (the start of a browser app); its other contents are not analysed.
52
- - **`.vue` and `.svelte` files are skipped, not parsed**: they are counted ("not read yet: N files in languages Canopy does not support"), they do not fail the run, and their code does not appear on the map.
53
- - Every other file type (`.py`, `.go`, `.css`, `.json`, ...) is ignored.
54
-
55
- ## Large projects, memory and what happens when it still fails
56
-
57
- - A whole analysis runs in a second Node process that is given about **75% of this computer's memory** (at least 2 GB, at most 8 GB). Set `CANOPY_MAX_MEMORY_MB=6144` (any number from 256) to choose it yourself; an existing `--max-old-space-size` in `NODE_OPTIONS` is respected. `CANOPY_DEBUG_MEM=1` prints the memory use at each stage (to the terminal only).
58
- - If Canopy still runs out of memory, or the process ends abnormally, it says so in plain words with what to try next (analyse one package or app at a time with `canopy <folder>`, give it more memory, close other programs) and exits with a non-zero code. It never ends silently. `.canopy/last-error.txt` then holds the time, the Canopy version, the stage it was in, the exit code and the memory setting, and nothing else (no code, no secrets).
59
- - Nothing is lost by a crash: every file already read is remembered, so the next run starts where the last one stopped. A run that was stopped leaves no stale lock behind (a lock whose process has ended is removed, and the run says so).
60
-
61
- ## What Canopy remembers
62
-
63
- Inside the project's `.canopy/` folder, on this computer only: the versions of your code it has analysed, which files it has already read, and **which version you last looked at**. That is what lets the Changes view start with "Since you last opened Canopy, 2 days ago (12 commits)" instead of only the last commit. The "last looked at" mark moves only after the page has loaded and stayed open for a minute, or when you stop Canopy normally; a crash does not move it. Delete `.canopy/` to start over; a damaged store is moved aside (`.canopy/state.corrupt-<time>`) and rebuilt, and Canopy says so. If you run `canopy` again in the same project while its viewer is open, it opens the running one instead of starting a second.
64
-
65
- ## Updating
66
-
67
- ```sh
68
- canopy update --check # needs the network
69
- ```
70
-
71
- - `canopy update --check` says whether a newer version is on npm and changes nothing: "up to date (x.y.z)" or "x.y.z available (you have a.b.c). Run: canopy update".
72
- - `canopy update` installs the newest version with `npm install -g canopy-engine@latest`, shows npm's output, then runs the new `canopy --version` to confirm it. If anything goes wrong it prints the exact command to run yourself. If you run Canopy with `npx -y canopy-engine@latest`, you always get the newest version and there is nothing to update.
73
- - **No silent updates, ever.** Canopy never downloads, installs or changes itself on its own. The engine makes no network calls at all, except these two explicit commands (`canopy update --check` and `canopy update`), which ask registry.npmjs.org for the newest version number and nothing else.
74
- - When the map opens in your browser, the page makes **one** request to registry.npmjs.org (the same version number, nothing about you or your project) and, if a newer version exists, shows a small notice at the bottom left: "Canopy x.y.z is available. In your terminal run: canopy update". You can close it; it stays closed for that version. To turn the request off completely, start Canopy with the environment variable `CANOPY_NO_UPDATE_CHECK=1` (the page then gets no address to ask and makes no request).
75
-
76
- ## Commands
77
-
78
- ```sh
79
- canopy --version
80
- canopy --help
81
- canopy doctor
82
- canopy sync .
83
- canopy history
84
- canopy diff
85
- canopy open --for=3
86
- canopy hook install
87
- canopy hook uninstall
88
- ```
89
-
90
- - `canopy update [--check]` - see "Updating" above.
91
- - `canopy [path]` - the one-command journey above.
92
- - `canopy sync [path]` - analyze, and store a new version only if the code changed.
93
- - `canopy open [path]` - serve the viewer on the latest output. `--port=N` picks the port; `--for=SECONDS` stops by itself after that time (for scripts).
94
- - `canopy doctor [path]` - OK / FAIL per check (Node version, grammars, queries, write access, git, viewer, hook), each with the fix.
95
- - `canopy history`, `canopy diff [from] [to] [--json]` - the stored versions and what changed between two of them.
96
- - `canopy hook install` / `canopy hook uninstall` - see below.
97
- - `canopy ai [path]` - which Claude Code the Ask AI panel would use (found automatically).
98
- - `canopy telemetry on|off|status|show` - anonymous, opt-in usage counts (see Privacy).
99
- - `canopy mcp [path]` - a read-only server your own AI tool can use (see "Search, and help from your own AI tool").
100
- - `canopy install [path] [--yes]` / `canopy uninstall [path]` - connect your AI tool to it, and remove that again.
101
-
102
- ## Search, and help from your own AI tool
103
-
104
- **Search** (the box at the top of the viewer; press `/`). It answers from an index kept in `.canopy/search/`, the same way every time, with nothing sent anywhere. Try:
105
-
106
- | type | finds |
107
- |---|---|
108
- | `post v1 events trigger` | the route `POST /events/trigger` (a leading `v1` or `api` is optional: frameworks add it) |
109
- | `GET /api/users/:id` or `get users {id}` | that route (`:id`, `{id}` and `[id]` are the same thing) |
110
- | `sendEmail` or `send email` | the function (camelCase, snake_case and acronyms are split into words) |
111
- | `"hooks.example.com"` | a quoted phrase must appear exactly: in a string, URL, queue name or comment |
112
- | `create order` | also finds `post`, `add`, `send` (a short fixed list of synonyms), one typo in a longer word (`evnts`), and the start of a word (`trig`) |
113
- | `STRIPE_KEY` | the file that reads that environment variable (names only; values are never stored) |
114
-
115
- Results are grouped **Endpoints, Functions, Workflows, Files**; each shows why it matched (route, name, decorator, string, comment, path) and "appears in N workflows" - choose one when there are several. **Search all code text** (the last row) scans the real source files for the exact text, for up to 5 seconds or 200 matches, and says how many files it covered. Files that hold secrets (`.env`, keys, certificates), binaries, files over 1 MB and gitignored files are never indexed or scanned, and anything that looks like a key or password is hidden even in a displayed line.
116
-
117
- **Start here**: click the empty search box to see the entry points Canopy found - HTTP routes, queue consumers, event and socket handlers, scheduled jobs - grouped by service.
118
-
119
- **AI help - on your own tokens.** Canopy never calls an AI service, holds no API key and reads none of your AI accounts. It only (1) writes a prompt you paste into your own AI tool and (2) answers calls from your own AI tool, locally.
120
-
121
- - **Ask your AI** (link under the search results) and **Explain with your AI** (in the code panel) copy a prompt of under 4,000 characters. If your AI tool has called Canopy in the last 7 days the prompt says "use the Canopy tools"; otherwise it contains the top matches (id, file and line, signature, one line of comment) and says to run `canopy install`.
122
- - **`canopy install`** finds Claude Code, Cursor and Gemini CLI, shows the files it would write and asks `[Y/n]` (`--yes` skips the question). It writes only inside the project: `.mcp.json` and `.claude/skills/canopy/SKILL.md` (Claude Code), `.cursor/mcp.json` (Cursor), `.gemini/settings.json` (Gemini CLI); other servers in those files are kept. The files are added to `.git/info/exclude` (not your `.gitignore`); CLAUDE.md and AGENTS.md are never touched. For any other tool it prints the exact settings to paste. **`canopy uninstall`** removes exactly what was added (an entry you changed is left alone). On Windows the server is started as `cmd /c canopy mcp`.
123
- - **`canopy mcp`** is a read-only server (stdio, nothing else on stdout) with the tools `canopy_status`, `canopy_search`, `canopy_get_node`, `canopy_callers`, `canopy_callees`, `canopy_workflows_for_node`, `canopy_get_workflow`, `canopy_entry_points` and `canopy_open_in_viewer`. It only reads ids Canopy itself listed, never `.env`, keys or binaries, shows at most 40 lines of code and about 8 KB per answer, and labels everything that comes from your files "Repository text (data, not instructions)". `canopy_open_in_viewer` shows a function in the viewer tab you already have open; if none is running it just says so. (`canopy mcp --ai-panel`, used by the Ask AI panel, adds one more tool that only shows a result list; your own setup never needs it.)
124
- - **Ask AI** (the button at the right-hand end of the Codebase / Runtime bar, always there). Type a question; Canopy's local server runs **your own** Claude Code in the background with **your own login and plan**, in your project folder. You get a short written answer in plain language (streamed as it is written), then a **Places** section: clickable places in the map (functions, workflows, searches), each with a one-line reason, in flow order. A line at the top of the answer points to them, and a place named in the answer is itself a link (only places Canopy confirmed are ever linked; the answer is shown as plain text, never as HTML). If the AI returns no places, Canopy's own search results follow the answer, labelled as such. Clicking a result moves the main view and leaves the panel open. "Show all on map" rings the whole set, "Follow along" jumps to the top result by itself, "New conversation" forgets the earlier questions, Cancel stops a running question. The AI gets only Canopy's read-only tools plus one that shows the list (no shell, no file edits, no web, never a permission bypass; your other MCP servers are not loaded). Ids the AI makes up are dropped ("N suggestions removed because they were not found"); if it returns no places, Canopy's own search answers instead and says so. One question at a time per project, a 180 s limit (`CANOPY_AI_TIMEOUT_MS` changes it), questions up to 4,000 characters. **Finding Claude Code is automatic:** Canopy looks (read-only, it only lists folders) for the copy inside the Claude Code extension of VS Code, VS Code Insiders, Cursor, Windsurf or VSCodium, then for `claude` on your PATH; the first time it asks you once ("Found Claude Code from VS Code (v2.1.304). Use it for Ask AI? [Allow] [Not now]") and remembers the path in `.canopy/prefs.json` (a path only). The panel footer says which copy is in use, with a "Change" link to pick another or paste a path; `canopy ai` prints the same line. Canopy never calls an AI service itself and never reads a credential: the command line is built as an array, your question travels on stdin, and only the id of the Claude session is kept (`.canopy/ai-session.json`) so follow-up questions remember earlier ones. If no Claude Code is found (or it is too old) the panel says so, links to the install page and offers "Copy prompt instead". `CANOPY_CLAUDE_BIN` (a full path) overrides everything. If Claude is not signed in, sign in to Claude Code in VS Code and try again. Only Claude Code is supported today.
125
- - **Experimental deep links.** Put `{"experimentalDeepLinks": true}` in `.canopy/prefs.json` to add two buttons to the Ask panel, "Open in Claude Code" and "Open in VS Code" (they open your own tool with the prompt filled in). They are labelled "experimental, may not work on every setup" and do nothing Canopy controls.
126
-
127
- ## Links between services
128
-
129
- When a project has more than one service (a frontend and a backend, a server and a realtime server), Canopy links the calls that cross between them:
130
-
131
- - **HTTP**: a `fetch`, `axios` (also `axios.create({ baseURL })` instances), `ky`, `got` or `XMLHttpRequest` call with a method and a path is matched to a route of another service. Paths with parameters (`/users/${id}` and `/users/:id`), a base URL in a variable (`${API_URL}/users`) and wrapper functions (`request("POST", "/friends/request")`, where the link starts at the function that calls the wrapper) are followed.
132
- - **WebSocket**: `new WebSocket(url)` / `io(url)` is linked to the service that runs a WebSocket server (a socket.io client only to a socket.io server), a `socket.send(...)` whose message is built at run time to that server's `message` listener, and, the other way round, what the server sends over the socket to the message handler of the client that connects to it (always "inferred": the message itself is not read).
133
-
134
- Every link says how sure Canopy is: **exact** means the client URL is written out in full (no variable base URL, no parameters), the route is written out in full, and the method and path are equal on both sides; **resolved** means the base URL was followed to the service that owns it, using the project's own configuration files (an environment variable read from `.env`, a `docker-compose` service name or port, a dev-server proxy, or a server that listens on that port), and a route there matches; the link says which variable and which file; **inferred** is everything else (a base URL it had to guess, parameters matched loosely, a leading `/api` ignored, only the path matched).
135
-
136
- Canopy reads .env and deploy-config files locally to resolve service URLs; it never stores or transmits secret values. The files are `.env` (and `.env.local`, `.env.development`, `.env.production`, `.env.example`), `docker-compose*.yml`, `render.yaml`, `fly.toml`, `railway.json` / `railway.toml`, `vercel.json` (rewrites and redirects), `netlify.toml` (redirects), `app.json` (Heroku), `Dockerfile` (`EXPOSE` and `ENV`), the dev-server proxy of `vite.config` / `next.config`, and the `proxy` field of `package.json`. They are read as plain text and never run. Only URL-like and port-like values are looked at (anything else is dropped as it is read), and only the variable name, the file name and the address without user name, password or query string can appear in the output. The links are in `.canopy/output/systemGraph.js` (`crossServiceEdges`, with `confidence`, `method`, `url`, `reason`, and for resolved links `resolvedFrom`), the service-to-service counts in `serviceLinks` (`exact` / `resolved` / `inferred`), and the steps that cross carry `crossesInto` in the viewer.
137
-
138
- In the viewer, a step that calls into another service is joined by an edge to a small box in that service (what is called, which function receives it). The edge is solid for exact and resolved links and dashed for inferred ones; hover it for the tag and the reason (for a resolved link, the variable and the file). "View in {service}" on the box opens the receiving workflow, and the start of that workflow shows "Called from {service}: {file}" (click to go back to the caller). Back returns to the step you left.
139
-
140
- A `package.json` `bin` or `main` that points at build output (`dist/cli.js`) now starts the map at the source file it was built from, when the repository itself says which one (a sourcemap, `tsconfig` `outDir`/`rootDir`, a `dist` -> `src` layout, or a build script that names the source such as `esbuild src/cli.js`).
141
-
142
- ## Hook on and off
143
-
144
- The hook starts `canopy sync` in the background after every commit and merge so the map stays current. It never blocks or fails a commit and prints nothing when it works. When it runs it looks for Canopy in this order: `canopy` on your PATH; else `npx --yes canopy-engine@<the version you installed>`; else it prints one line, `canopy not found; skipping map update`, and carries on.
145
-
146
- - Turn it on: answer Y in `canopy` (or use `-y`), or run `canopy hook install` (safe to repeat; your own hook content is kept).
147
- - Turn it off: `canopy hook uninstall` (removes only Canopy's block). If you answered n once, `canopy` will not ask again.
148
-
149
- ## When it doesn't work
150
-
151
- Run `canopy doctor` and send its output with your feedback. Healthy output (the hook line is not critical):
152
-
153
- ```
154
- canopy 0.1.5 doctor (C:\work\my-project)
155
-
156
- OK Node version >= 18 - v22.12.0
157
- OK tree-sitter grammars (wasm) - 5 files
158
- OK query files (.scm) - 17 files
159
- OK write access to <path>/.canopy - C:\work\my-project\.canopy
160
- OK git available - git version 2.45.1.windows.1
161
- OK map viewer present
162
- FAIL post-commit hook installed
163
- fix: canopy hook install "C:\work\my-project" (not critical)
164
-
165
- all critical checks passed
166
- ```
167
-
168
- A critical problem looks like this, and `canopy doctor` then exits with 1:
169
-
170
- ```
171
- FAIL write access to <path>/.canopy - EEXIST: file already exists, mkdir 'C:\work\my-project\.canopy'
172
- fix: make C:\work\my-project writable for your user, or run canopy on a folder you own
173
-
174
- 1 critical problem(s) - fix them and run canopy doctor again
175
- ```
176
-
177
- Exit codes of `canopy`: 0 ok, 1 a problem (the message says what to do), 3 no JavaScript / TypeScript files.
178
-
179
- **Queue links are inferred.** When one function adds a job to a queue (`queue.add`, BullMQ or Bull, `@InjectQueue`, or a wrapper class) and another processes the same queue (`new Worker`, `queue.process`, a NestJS `@Processor` class), Canopy draws a dashed link, "queue (inferred)", between them - but only when the queue's name is known for sure (a string, a constant, an enum member). A name built at run time gives no link. These links do not change any workflow.
180
-
181
- ## What is in `.canopy/`
182
-
183
- | file | what it is |
184
- |---|---|
185
- | `output/workflows.js` | every traced workflow with its steps, calls and source - what the viewer reads (`workflows.json` is the same data as plain JSON when it fits under 25 MB) |
186
- | `output/systemGraph.js` | links between workflows and between services |
187
- | `output/services.js` | the services found (frontend, backend, ...) with counts |
188
- | `output/meta.js`, `meta.json` | repo root and the coverage report (files analyzed, skipped, with parse errors) |
189
- | `output/history.js` | the version list and recent diffs shown in the viewer's Changes panel |
190
- | `history/*.json.gz` | one compressed snapshot per version (total size capped at 200 MB, `CANOPY_SNAPSHOT_MAX_MB`) |
191
- | `sync.log` | log of background syncs started by the hook |
192
- | `meta.json` | the project's random id, when it was first set up, which version you last looked at (checksummed) |
193
- | `files.json` | size, time and hash of each analysed file, so an unchanged project is recognised without re-reading it |
194
- | `server.json` | the address of the viewer while it runs (so a second `canopy` reuses it), and a key only `canopy mcp` can use to ask it to show something |
195
- | `search/` | the search index (`index.json`, rebuilt after every sync; `files/` holds one small cache entry per file) |
196
- | `ai-session.json` | the id of the Claude Code conversation the Ask AI panel continues (an id only; "New conversation" deletes it) |
197
- | `install.json`, `mcp-seen.json` | what `canopy install` wrote (so `uninstall` can remove exactly that); when your AI tool last called `canopy mcp` |
198
- | `last-error.txt` | only after a crash: time, version, stage, exit code, memory setting |
199
-
200
- ## The viewer in five bullets
201
-
202
- - First screen: the **services** of the project and the links between them; click one to list its **workflows**.
203
- - A workflow opens as a chain of cards: the entry point (a route, a listener, the main file) and the functions it calls, in source order, with branches (`if`, `switch`, `try`) shown side by side.
204
- - Click a call to open it as its own card; **Trace from root** opens everything beneath the entry point (it pauses at 3,000 rows and offers Continue).
205
- - The `</>` button on any card shows the exact source lines.
206
- - The **Changes** panel starts with what changed since you last opened Canopy, and can also step through one commit at a time; click a changed function for its line-by-line diff.
207
- - **Runtime (predicted)** draws a workflow end to end across the services it calls, one band per service.
208
-
209
- ## Privacy
210
-
211
- Canopy runs on your computer and never sends your code anywhere. The only network features are the update check and **optional, opt-in anonymous usage counts** (off until you say yes; counts only: never code, paths, names, searches or questions). See [PRIVACY.md](PRIVACY.md) for the exact list, and `canopy telemetry status | on | off | show`.
212
-
213
- ## Known limits
214
-
215
- Each line is backed by a measurement (the numbers are quoted in the lines below).
216
-
217
- - JavaScript and TypeScript only; other languages are silently skipped (a Python-only folder gives an empty map).
218
- - Links between services are found for HTTP calls with a readable method and path and for WebSocket connections; links are "resolved" only when the project's own files say which service a base URL belongs to, otherwise "inferred" (the base URL is a variable). Calls whose URL is built at run time are not linked; a message a server sends to a browser is linked to the client's message handler only when that handler calls into the project, and the link is always "inferred".
219
- - Method calls on objects mostly stay unresolved (`obj.method()`: 1.3% resolved in express); plain function calls and imports resolve better.
220
- - Flow type annotations are not supported (parse-server: 47 of 370 files have syntax errors).
221
- - Anonymous exports have no node, so calls into them are not followed.
222
- - Repositories with 5,000+ files take minutes (strapi, 5,451 files: 219 s the first time).
223
- - Vue and Svelte files are skipped (see "Which files are analysed").
224
-
225
- ## Troubleshooting
226
-
227
- - `canopy doctor` first. It exits non-zero only when a critical check fails (Node, grammars, queries, write access).
228
- - "no map yet": run `canopy sync <path>` (or just `canopy`) before `canopy open <path>`.
229
- - A commit prints `canopy not found; skipping map update`: neither `canopy` nor `npx` is on the PATH git uses; install Canopy globally (`npm i -g canopy-engine`) or run `canopy hook uninstall`.
230
- - Parse errors in a few files are normal; the coverage block printed by `canopy sync` lists them and the rest of each file is still analyzed.
231
- - The viewer answers "forbidden" (403): it only accepts a `127.0.0.1` or `localhost` address. A forwarded port opened under another host name (Codespaces, dev containers, `ssh -L` with a custom name) is refused on purpose; open `http://127.0.0.1:<port>/` instead.
232
-
233
- ## License
234
-
235
- Canopy is free for personal, educational and evaluation use on your own computer; commercial use (inside a company or for paid work) needs a separate written agreement. The full terms are in the `LICENSE` file in this package.
236
-
237
- ## Feedback
238
-
239
- The viewer has a small **Send feedback** link, and `canopy sync` ends with a one-line hint: both open a `mailto:` link in your own mail program with the subject "Canopy feedback (v<version>, <platform>)" and an empty message. Canopy collects and sends nothing; you decide whether to send. (`--quiet` leaves the hint out.)
240
-
241
- For a longer report please use [FEEDBACK.md](FEEDBACK.md) (about 20 minutes) and send it back, with the output of `canopy doctor`, to whoever gave you this package. `canopy` prints the feedback link from `canopyFeedbackUrl` in package.json (a placeholder until the owner sets it).
1
+ # Canopy
2
+
3
+ > **Beta. JS/TS only.** Canopy is an early release for testers. It reads JavaScript and TypeScript projects; other languages are not read.
4
+
5
+ Canopy reads a **JavaScript or TypeScript** codebase and draws a map of what it does: where execution starts (routes, event listeners, socket handlers, the app's main file), what each of those calls, in order, and how the parts of a project (services) fit together. The map is built from the code itself.
6
+
7
+ **JavaScript and TypeScript only.** Exactly which files are read is listed in "Which files are analysed" below.
8
+
9
+ ## Install (needs Node 18 or newer)
10
+
11
+ **1. Try it, always the latest, nothing to install.** Run this inside your project folder:
12
+
13
+ ```sh
14
+ npx -y canopy-engine@latest # needs the network
15
+ ```
16
+
17
+ **2. Install for regular use.** Once, then use the short name:
18
+
19
+ ```sh
20
+ npm install -g canopy-engine # needs the network
21
+ canopy
22
+ ```
23
+
24
+ Without installing anything, `npx canopy-engine` runs the same program (it downloads it the first time). **Do not type `npx install -g canopy-engine`**: `npx install` is not a command. The install command is `npm install -g canopy-engine`, and then you run `canopy`.
25
+
26
+ (The two lines marked "needs the network" download from the npm registry. If you were given a file instead, `canopy-engine-0.1.5.tgz`, install that: `npm install -g ./canopy-engine-0.1.5.tgz`.)
27
+
28
+ **If the `canopy` command clashes with another tool** on your computer, use `canopy-engine` instead. It is the same program under a second name: `canopy-engine --version`, `canopy-engine doctor`, and so on.
29
+
30
+ ## What `canopy` does
31
+
32
+ `canopy` does these steps in order and prints a plain line for each; if one fails it stops with one line saying what happened and what to do:
33
+
34
+ 1. **Checks this computer** - Node 18+, a folder you can write to, free disk space (300 MB), git, a free port (the same checks as `canopy doctor`).
35
+ 2. **Looks at your project** - counts the JavaScript / TypeScript files. A folder with none (for example a Python-only project) stops here: "Canopy currently supports JavaScript and TypeScript".
36
+ 3. **Prepares `.canopy/`** - the only folder Canopy writes inside your project; it adds `.canopy/` to `.gitignore` (creating the file if needed, never removing a line).
37
+ 4. **Asks about the commit hook** - "Update the map on every commit? [Y/n]" (default yes; `-y` skips the question; with no terminal it answers yes). See "Hook on and off" below.
38
+ 5. **Builds the map** - the first time takes seconds to minutes depending on size (a project of ten thousand files takes some minutes); a later run on an unchanged project answers "up to date" at once, and after an edit it re-reads only the changed files and links the whole project again. It ends with a short summary: services, workflows, files inside a service, files with parse errors.
39
+ 6. **Opens the map** - starts a viewer on a free local port (127.0.0.1 only) and opens your browser. Ctrl+C stops it.
40
+
41
+ Then it prints "Next time just run `canopy`." and the feedback link. Options: `-y` / `--yes` (no questions; installs the hook), `--no-hook` (no hook, no question), `--no-open` (build the map, do not start the viewer; for scripts), `--port=N`, `--for=SECONDS` (stop the viewer by itself), `--full` (ignore everything remembered and analyse from scratch), `--quiet` (print only problems and the address), `--no-color` (or set `NO_COLOR=1`; also used automatically when the output is not a terminal), and a folder to look at instead of the current one: `canopy path/to/project`.
42
+
43
+ Supported languages: JavaScript and TypeScript only (see "Which files are analysed" below).
44
+
45
+ ## Which files are analysed
46
+
47
+ Read from the source of the engine, not guessed:
48
+
49
+ - **Parsed as JavaScript:** `.js .jsx .mjs .cjs`. **Parsed as TypeScript:** `.ts .mts .cts` (and `.tsx` with the TSX grammar).
50
+ - **Found but not analysed:** declaration files (`.d.ts .d.mts .d.cts`: they only describe types), `*.min.js`, `*.bundle.js`, files over 1 MB and files with a line over 10,000 characters (minified or bundled code), and everything inside `node_modules`, `.git`, `dist`, `build`, `out`, `coverage`, `vendor`, `.next`, `.nuxt`, `.turbo`, `.cache`, `.yarn`, `.pnp`, `bower_components` and anything your `.gitignore` lists. They are counted in the coverage summary.
51
+ - **`.html`** is read only to find the page's `<script src>` (the start of a browser app); its other contents are not analysed.
52
+ - **`.vue` and `.svelte` files are skipped, not parsed**: they are counted ("not read yet: N files in languages Canopy does not support"), they do not fail the run, and their code does not appear on the map.
53
+ - Every other file type (`.py`, `.go`, `.css`, `.json`, ...) is ignored.
54
+
55
+ ## Large projects, memory and what happens when it still fails
56
+
57
+ - A whole analysis runs in a second Node process that is given about **75% of this computer's memory** (at least 2 GB, at most 8 GB). Set `CANOPY_MAX_MEMORY_MB=6144` (any number from 256) to choose it yourself; an existing `--max-old-space-size` in `NODE_OPTIONS` is respected. `CANOPY_DEBUG_MEM=1` prints the memory use at each stage (to the terminal only).
58
+ - If Canopy still runs out of memory, or the process ends abnormally, it says so in plain words with what to try next (analyse one package or app at a time with `canopy <folder>`, give it more memory, close other programs) and exits with a non-zero code. It never ends silently. `.canopy/last-error.txt` then holds the time, the Canopy version, the stage it was in, the exit code and the memory setting, and nothing else (no code, no secrets).
59
+ - Nothing is lost by a crash: every file already read is remembered, so the next run starts where the last one stopped. A run that was stopped leaves no stale lock behind (a lock whose process has ended is removed, and the run says so).
60
+
61
+ ## What Canopy remembers
62
+
63
+ Inside the project's `.canopy/` folder, on this computer only: the versions of your code it has analysed, which files it has already read, and **which version you last looked at**. That is what lets the Changes view start with "Since you last opened Canopy, 2 days ago (12 commits)" instead of only the last commit. The "last looked at" mark moves only after the page has loaded and stayed open for a minute, or when you stop Canopy normally; a crash does not move it. Delete `.canopy/` to start over; a damaged store is moved aside (`.canopy/state.corrupt-<time>`) and rebuilt, and Canopy says so. If you run `canopy` again in the same project while its viewer is open, it opens the running one instead of starting a second.
64
+
65
+ ## Updating
66
+
67
+ ```sh
68
+ canopy update --check # needs the network
69
+ ```
70
+
71
+ - `canopy update --check` says whether a newer version is on npm and changes nothing: "up to date (x.y.z)" or "x.y.z available (you have a.b.c). Run: canopy update".
72
+ - `canopy update` installs the newest version with `npm install -g canopy-engine@latest`, shows npm's output, then runs the new `canopy --version` to confirm it. If anything goes wrong it prints the exact command to run yourself. If you run Canopy with `npx -y canopy-engine@latest`, you always get the newest version and there is nothing to update.
73
+ - **A one-line notice.** When a newer version is known, an interactive command prints one line to stderr before its output: "A newer Canopy is available: 0.1.8 -> 0.1.9. Update with: canopy update". It is read from a small file in your user config folder (`update-check.json`); when that file is more than a day old, Canopy refreshes it in the background (2-second limit, silent on any failure) by asking registry.npmjs.org for the public package metadata of canopy-engine, sending no identifiers. Nothing is printed with `--quiet`, in CI, when output is not a terminal, from a source checkout, or by `canopy mcp` and the commit hook. `CANOPY_NO_UPDATE_CHECK=1` turns the check and the notice off. `canopy --version` shows `0.1.9 (latest: x.y.z)` from the same file.
74
+ - **No silent updates, ever.** Canopy never downloads, installs or changes itself on its own: `canopy update` shows the current and the newest version, asks you to confirm (`--yes` skips the question), then runs npm.
75
+ - When the map opens in your browser, the page makes **one** request to registry.npmjs.org (the same version number, nothing about you or your project) and, if a newer version exists, shows a small notice at the bottom left: "Canopy x.y.z is available. In your terminal run: canopy update". You can close it; it stays closed for the rest of that browser session. To turn the request off completely, start Canopy with the environment variable `CANOPY_NO_UPDATE_CHECK=1` (the page then gets no address to ask and makes no request).
76
+
77
+ ## Commands
78
+
79
+ ```sh
80
+ canopy --version
81
+ canopy --help
82
+ canopy doctor
83
+ canopy sync .
84
+ canopy history
85
+ canopy diff
86
+ canopy open --for=3
87
+ canopy hook install
88
+ canopy hook uninstall
89
+ ```
90
+
91
+ - `canopy update [--check]` - see "Updating" above.
92
+ - `canopy [path]` - the one-command journey above.
93
+ - `canopy sync [path]` - analyze, and store a new version only if the code changed.
94
+ - `canopy open [path]` - serve the viewer on the latest output. `--port=N` picks the port; `--for=SECONDS` stops by itself after that time (for scripts).
95
+ - `canopy doctor [path]` - OK / FAIL per check (Node version, grammars, queries, write access, git, viewer, hook), each with the fix.
96
+ - `canopy history`, `canopy diff [from] [to] [--json]` - the stored versions and what changed between two of them.
97
+ - `canopy hook install` / `canopy hook uninstall` - see below.
98
+ - `canopy ai [path]` - which Claude Code the Ask AI panel would use (found automatically). Ask AI can also use Codex CLI (experimental, off until you turn it on under Change in the panel); Antigravity CLI is listed but stays off because it cannot be limited to Canopy's read-only tools for a single run.
99
+ - `canopy telemetry on|off|status|show` - anonymous, opt-in usage counts (see Privacy).
100
+ - `canopy mcp [path]` - a read-only server your own AI tool can use (see "Search, and help from your own AI tool").
101
+ - `canopy install [path] [--yes]` / `canopy uninstall [path]` - connect your AI tool to it, and remove that again.
102
+
103
+ ## Search, and help from your own AI tool
104
+
105
+ **Search** (the box at the top of the viewer; press `/`). It answers from an index kept in `.canopy/search/`, the same way every time, with nothing sent anywhere. Try:
106
+
107
+ | type | finds |
108
+ |---|---|
109
+ | `post v1 events trigger` | the route `POST /events/trigger` (a leading `v1` or `api` is optional: frameworks add it) |
110
+ | `GET /api/users/:id` or `get users {id}` | that route (`:id`, `{id}` and `[id]` are the same thing) |
111
+ | `sendEmail` or `send email` | the function (camelCase, snake_case and acronyms are split into words) |
112
+ | `"hooks.example.com"` | a quoted phrase must appear exactly: in a string, URL, queue name or comment |
113
+ | `create order` | also finds `post`, `add`, `send` (a short fixed list of synonyms), one typo in a longer word (`evnts`), and the start of a word (`trig`) |
114
+ | `STRIPE_KEY` | the file that reads that environment variable (names only; values are never stored) |
115
+
116
+ Results are grouped **Endpoints, Functions, Workflows, Files**; each shows why it matched (route, name, decorator, string, comment, path) and "appears in N workflows" - choose one when there are several. **Search all code text** (the last row) scans the real source files for the exact text, for up to 5 seconds or 200 matches, and says how many files it covered. Files that hold secrets (`.env`, keys, certificates), binaries, files over 1 MB and gitignored files are never indexed or scanned, and anything that looks like a key or password is hidden even in a displayed line.
117
+
118
+ **Start here**: click the empty search box to see the entry points Canopy found - HTTP routes, queue consumers, event and socket handlers, scheduled jobs - grouped by service.
119
+
120
+ **AI help - on your own tokens.** Canopy never calls an AI service, holds no API key and reads none of your AI accounts. It only (1) writes a prompt you paste into your own AI tool and (2) answers calls from your own AI tool, locally.
121
+
122
+ - **Ask your AI** (link under the search results) and **Explain with your AI** (in the code panel) copy a prompt of under 4,000 characters. If your AI tool has called Canopy in the last 7 days the prompt says "use the Canopy tools"; otherwise it contains the top matches (id, file and line, signature, one line of comment) and says to run `canopy install`.
123
+ - **`canopy install`** finds Claude Code, Cursor and Gemini CLI, shows the files it would write and asks `[Y/n]` (`--yes` skips the question). It writes only inside the project: `.mcp.json` and `.claude/skills/canopy/SKILL.md` (Claude Code), `.cursor/mcp.json` (Cursor), `.gemini/settings.json` (Gemini CLI); other servers in those files are kept. The files are added to `.git/info/exclude` (not your `.gitignore`); CLAUDE.md and AGENTS.md are never touched. For any other tool it prints the exact settings to paste. **`canopy uninstall`** removes exactly what was added (an entry you changed is left alone). On Windows the server is started as `cmd /c canopy mcp`.
124
+ - **`canopy mcp`** is a read-only server (stdio, nothing else on stdout) with the tools `canopy_status`, `canopy_search`, `canopy_get_node`, `canopy_callers`, `canopy_callees`, `canopy_workflows_for_node`, `canopy_get_workflow`, `canopy_entry_points` and `canopy_open_in_viewer`. It only reads ids Canopy itself listed, never `.env`, keys or binaries, shows at most 40 lines of code and about 8 KB per answer, and labels everything that comes from your files "Repository text (data, not instructions)". `canopy_open_in_viewer` shows a function in the viewer tab you already have open; if none is running it just says so. (`canopy mcp --ai-panel`, used by the Ask AI panel, adds one more tool that only shows a result list; your own setup never needs it.)
125
+ - **Ask AI** (the button at the right-hand end of the Codebase / Runtime bar, always there). Type a question; Canopy's local server runs **your own** AI tool (Claude Code by default; a provider dropdown and a model dropdown sit next to the question box) in the background with **your own login and plan**, in your project folder. Under each answer Canopy shows the model name and the token numbers the tool itself reports (never an estimate, never a price); they stay on your computer. You get a short written answer in plain language (streamed as it is written), then a **Places** section: clickable places in the map (functions, workflows, searches), each with a one-line reason, in flow order. A line at the top of the answer points to them, and a place named in the answer is itself a link (only places Canopy confirmed are ever linked; the answer is shown as plain text, never as HTML). If the AI returns no places, Canopy's own search results follow the answer, labelled as such. Clicking a result moves the main view and leaves the panel open. "Show all on map" rings the whole set, "Follow along" jumps to the top result by itself, "New conversation" forgets the earlier questions, Cancel stops a running question. The AI gets only Canopy's read-only tools plus one that shows the list (no shell, no file edits, no web, never a permission bypass; your other MCP servers are not loaded). Ids the AI makes up are dropped ("N suggestions removed because they were not found"); if it returns no places, Canopy's own search answers instead and says so. One question at a time per project, a 180 s limit (`CANOPY_AI_TIMEOUT_MS` changes it), questions up to 4,000 characters. **Finding Claude Code is automatic:** Canopy looks (read-only, it only lists folders) for the copy inside the Claude Code extension of VS Code, VS Code Insiders, Cursor, Windsurf or VSCodium, then for `claude` on your PATH; the first time it asks you once ("Found Claude Code from VS Code (v2.1.304). Use it for Ask AI? [Allow] [Not now]") and remembers the path in `.canopy/prefs.json` (a path only). The panel footer says which copy is in use, with a "Change" link to pick another or paste a path; `canopy ai` prints the same line. Canopy never calls an AI service itself and never reads a credential: the command line is built as an array, your question travels on stdin, and only the id of the Claude session is kept (`.canopy/ai-session.json`) so follow-up questions remember earlier ones. If no Claude Code is found (or it is too old) the panel says so, links to the install page and offers "Copy prompt instead". `CANOPY_CLAUDE_BIN` (a full path) overrides everything. If Claude is not signed in, sign in to Claude Code in VS Code and try again. Only Claude Code is supported today.
126
+ - **Experimental deep links.** Put `{"experimentalDeepLinks": true}` in `.canopy/prefs.json` to add two buttons to the Ask panel, "Open in Claude Code" and "Open in VS Code" (they open your own tool with the prompt filled in). They are labelled "experimental, may not work on every setup" and do nothing Canopy controls.
127
+
128
+ ## Links between services
129
+
130
+ When a project has more than one service (a frontend and a backend, a server and a realtime server), Canopy links the calls that cross between them:
131
+
132
+ - **HTTP**: a `fetch`, `axios` (also `axios.create({ baseURL })` instances), `ky`, `got` or `XMLHttpRequest` call with a method and a path is matched to a route of another service. Paths with parameters (`/users/${id}` and `/users/:id`), a base URL in a variable (`${API_URL}/users`) and wrapper functions (`request("POST", "/friends/request")`, where the link starts at the function that calls the wrapper) are followed.
133
+ - **WebSocket**: `new WebSocket(url)` / `io(url)` is linked to the service that runs a WebSocket server (a socket.io client only to a socket.io server), a `socket.send(...)` whose message is built at run time to that server's `message` listener, and, the other way round, what the server sends over the socket to the message handler of the client that connects to it (always "inferred": the message itself is not read).
134
+
135
+ Every link says how sure Canopy is: **exact** means the client URL is written out in full (no variable base URL, no parameters), the route is written out in full, and the method and path are equal on both sides; **resolved** means the base URL was followed to the service that owns it, using the project's own configuration files (an environment variable read from `.env`, a `docker-compose` service name or port, a dev-server proxy, or a server that listens on that port), and a route there matches; the link says which variable and which file; **inferred** is everything else (a base URL it had to guess, parameters matched loosely, a leading `/api` ignored, only the path matched).
136
+
137
+ Canopy reads .env and deploy-config files locally to resolve service URLs; it never stores or transmits secret values. The files are `.env` (and `.env.local`, `.env.development`, `.env.production`, `.env.example`), `docker-compose*.yml`, `render.yaml`, `fly.toml`, `railway.json` / `railway.toml`, `vercel.json` (rewrites and redirects), `netlify.toml` (redirects), `app.json` (Heroku), `Dockerfile` (`EXPOSE` and `ENV`), the dev-server proxy of `vite.config` / `next.config`, and the `proxy` field of `package.json`. They are read as plain text and never run. Only URL-like and port-like values are looked at (anything else is dropped as it is read), and only the variable name, the file name and the address without user name, password or query string can appear in the output. The links are in `.canopy/output/systemGraph.js` (`crossServiceEdges`, with `confidence`, `method`, `url`, `reason`, and for resolved links `resolvedFrom`), the service-to-service counts in `serviceLinks` (`exact` / `resolved` / `inferred`), and the steps that cross carry `crossesInto` in the viewer.
138
+
139
+ In the viewer, a step that calls into another service is joined by an edge to a small box in that service (what is called, which function receives it). The edge is solid for exact and resolved links and dashed for inferred ones; hover it for the tag and the reason (for a resolved link, the variable and the file). "View in {service}" on the box opens the receiving workflow, and the start of that workflow shows "Called from {service}: {file}" (click to go back to the caller). Back returns to the step you left.
140
+
141
+ A `package.json` `bin` or `main` that points at build output (`dist/cli.js`) now starts the map at the source file it was built from, when the repository itself says which one (a sourcemap, `tsconfig` `outDir`/`rootDir`, a `dist` -> `src` layout, or a build script that names the source such as `esbuild src/cli.js`).
142
+
143
+ ## Hook on and off
144
+
145
+ The hook starts `canopy sync` in the background after every commit and merge so the map stays current. It never blocks or fails a commit and prints nothing when it works. When it runs it looks for Canopy in this order: `canopy` on your PATH; else `npx --yes canopy-engine@<the version you installed>`; else it prints one line, `canopy not found; skipping map update`, and carries on.
146
+
147
+ - Turn it on: answer Y in `canopy` (or use `-y`), or run `canopy hook install` (safe to repeat; your own hook content is kept).
148
+ - Turn it off: `canopy hook uninstall` (removes only Canopy's block). If you answered n once, `canopy` will not ask again.
149
+
150
+ ## When it doesn't work
151
+
152
+ Run `canopy doctor` and send its output with your feedback. Healthy output (the hook line is not critical):
153
+
154
+ ```
155
+ canopy 0.1.5 doctor (C:\work\my-project)
156
+
157
+ OK Node version >= 18 - v22.12.0
158
+ OK tree-sitter grammars (wasm) - 5 files
159
+ OK query files (.scm) - 17 files
160
+ OK write access to <path>/.canopy - C:\work\my-project\.canopy
161
+ OK git available - git version 2.45.1.windows.1
162
+ OK map viewer present
163
+ FAIL post-commit hook installed
164
+ fix: canopy hook install "C:\work\my-project" (not critical)
165
+
166
+ all critical checks passed
167
+ ```
168
+
169
+ A critical problem looks like this, and `canopy doctor` then exits with 1:
170
+
171
+ ```
172
+ FAIL write access to <path>/.canopy - EEXIST: file already exists, mkdir 'C:\work\my-project\.canopy'
173
+ fix: make C:\work\my-project writable for your user, or run canopy on a folder you own
174
+
175
+ 1 critical problem(s) - fix them and run canopy doctor again
176
+ ```
177
+
178
+ Exit codes of `canopy`: 0 ok, 1 a problem (the message says what to do), 3 no JavaScript / TypeScript files.
179
+
180
+ **Queue links are inferred.** When one function adds a job to a queue (`queue.add`, BullMQ or Bull, `@InjectQueue`, or a wrapper class) and another processes the same queue (`new Worker`, `queue.process`, a NestJS `@Processor` class), Canopy draws a dashed link, "queue (inferred)", between them - but only when the queue's name is known for sure (a string, a constant, an enum member). A name built at run time gives no link. These links do not change any workflow.
181
+
182
+ ## What is in `.canopy/`
183
+
184
+ | file | what it is |
185
+ |---|---|
186
+ | `output/workflows.js` | every traced workflow with its steps, calls and source - what the viewer reads (`workflows.json` is the same data as plain JSON when it fits under 25 MB) |
187
+ | `output/systemGraph.js` | links between workflows and between services |
188
+ | `output/services.js` | the services found (frontend, backend, ...) with counts |
189
+ | `output/meta.js`, `meta.json` | repo root and the coverage report (files analyzed, skipped, with parse errors) |
190
+ | `output/history.js` | the version list and recent diffs shown in the viewer's Changes panel |
191
+ | `history/*.json.gz` | one compressed snapshot per version (total size capped at 200 MB, `CANOPY_SNAPSHOT_MAX_MB`) |
192
+ | `sync.log` | log of background syncs started by the hook |
193
+ | `meta.json` | the project's random id, when it was first set up, which version you last looked at (checksummed) |
194
+ | `files.json` | size, time and hash of each analysed file, so an unchanged project is recognised without re-reading it |
195
+ | `server.json` | the address of the viewer while it runs (so a second `canopy` reuses it), and a key only `canopy mcp` can use to ask it to show something |
196
+ | `search/` | the search index (`index.json`, rebuilt after every sync; `files/` holds one small cache entry per file) |
197
+ | `ai-session.json` | the id of the Claude Code conversation the Ask AI panel continues (an id only; "New conversation" deletes it) |
198
+ | `install.json`, `mcp-seen.json` | what `canopy install` wrote (so `uninstall` can remove exactly that); when your AI tool last called `canopy mcp` |
199
+ | `last-error.txt` | only after a crash: time, version, stage, exit code, memory setting |
200
+
201
+ ## The viewer in five bullets
202
+
203
+ - First screen: the **services** of the project and the links between them; click one to list its **workflows**.
204
+ - A workflow opens as a chain of cards: the entry point (a route, a listener, the main file) and the functions it calls, in source order, with branches (`if`, `switch`, `try`) shown side by side.
205
+ - Click a call to open it as its own card; **Trace from root** opens everything beneath the entry point (it pauses at 3,000 rows and offers Continue).
206
+ - The `</>` button on any card shows the exact source lines.
207
+ - The **Changes** panel starts with what changed since you last opened Canopy, and can also step through one commit at a time; click a changed function for its line-by-line diff.
208
+ - **Runtime (predicted)** draws a workflow end to end across the services it calls, one band per service.
209
+
210
+ ## Privacy
211
+
212
+ Canopy runs on your computer and never sends your code anywhere. The only network features are the update check and **optional, opt-in anonymous usage counts** (off until you say yes; counts only: never code, paths, names, searches or questions). See [PRIVACY.md](PRIVACY.md) for the exact list, and `canopy telemetry status | on | off | show`.
213
+
214
+ ## Known limits
215
+
216
+ Each line is backed by a measurement (the numbers are quoted in the lines below).
217
+
218
+ - JavaScript and TypeScript only; other languages are silently skipped (a Python-only folder gives an empty map).
219
+ - Links between services are found for HTTP calls with a readable method and path and for WebSocket connections; links are "resolved" only when the project's own files say which service a base URL belongs to, otherwise "inferred" (the base URL is a variable). Calls whose URL is built at run time are not linked; a message a server sends to a browser is linked to the client's message handler only when that handler calls into the project, and the link is always "inferred".
220
+ - Method calls on objects mostly stay unresolved (`obj.method()`: 1.3% resolved in express); plain function calls and imports resolve better.
221
+ - Flow type annotations are not supported (parse-server: 47 of 370 files have syntax errors).
222
+ - Anonymous exports have no node, so calls into them are not followed.
223
+ - Repositories with 5,000+ files take minutes (strapi, 5,451 files: 219 s the first time).
224
+ - Vue and Svelte files are skipped (see "Which files are analysed").
225
+
226
+ ## Troubleshooting
227
+
228
+ - `canopy doctor` first. It exits non-zero only when a critical check fails (Node, grammars, queries, write access).
229
+ - "no map yet": run `canopy sync <path>` (or just `canopy`) before `canopy open <path>`.
230
+ - A commit prints `canopy not found; skipping map update`: neither `canopy` nor `npx` is on the PATH git uses; install Canopy globally (`npm i -g canopy-engine`) or run `canopy hook uninstall`.
231
+ - Parse errors in a few files are normal; the coverage block printed by `canopy sync` lists them and the rest of each file is still analyzed.
232
+ - The viewer answers "forbidden" (403): it only accepts a `127.0.0.1` or `localhost` address. A forwarded port opened under another host name (Codespaces, dev containers, `ssh -L` with a custom name) is refused on purpose; open `http://127.0.0.1:<port>/` instead.
233
+
234
+ ## License
235
+
236
+ Canopy is free for personal, educational and evaluation use on your own computer; commercial use (inside a company or for paid work) needs a separate written agreement. The full terms are in the `LICENSE` file in this package.
237
+
238
+ ## Feedback
239
+
240
+ The viewer has a small **Send feedback** link, and `canopy sync` ends with a one-line hint: both open a `mailto:` link in your own mail program with the subject "Canopy feedback (v<version>, <platform>)" and an empty message. Canopy collects and sends nothing; you decide whether to send. (`--quiet` leaves the hint out.)
241
+
242
+ For a longer report please use [FEEDBACK.md](FEEDBACK.md) (about 20 minutes) and send it back, with the output of `canopy doctor`, to canopy.spillitlabs@gmail.com. `canopy` also prints the feedback link from `canopyFeedbackUrl` in package.json.