canopy-engine 0.1.4 → 0.1.7

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/LICENSE CHANGED
@@ -15,8 +15,9 @@ terms:
15
15
  3. Commercial use. Use inside a company or for paid work needs a
16
16
  separate written agreement. Contact: canopy.spillitlabs@gmail.com.
17
17
  4. Your code stays yours. Canopy runs locally and does not send
18
- your source code anywhere. The only network feature is the
19
- optional update check (canopy update).
18
+ your source code anywhere. The only network features are the
19
+ optional update check (canopy update) and optional, opt-in
20
+ anonymous usage statistics described in PRIVACY.md.
20
21
  5. No warranty. Canopy is provided "as is", without warranty of
21
22
  any kind. The author is not liable for any damages arising from
22
23
  its use.
package/PRIVACY.md ADDED
@@ -0,0 +1,72 @@
1
+ # Privacy
2
+
3
+ Short version: Canopy runs on your computer. Your code is never sent anywhere by Canopy. There is one optional network check (the update notice) and one optional, opt-in feature (anonymous usage counts) that is **off until you say yes**.
4
+
5
+ ## What Canopy does locally
6
+
7
+ - It reads your project's JavaScript / TypeScript files on your computer and builds a map. The map, the search index, the history and your choices are stored in a `.canopy/` folder inside your project (kept out of git automatically). Nothing in it is uploaded.
8
+ - It serves the viewer on `127.0.0.1` (your own computer only), protected by a random per-session key.
9
+ - **Ask AI** starts the Claude Code program that is already installed on your computer (for example the one inside the VS Code extension), with your own login and plan. Canopy never calls an AI service itself, never sees or stores your credentials, and never reads Claude's login or session files. What Claude Code does with your question, and what it sends to Anthropic, is governed by Anthropic's terms and your own account, not by Canopy. The questions and answers are not kept by Canopy; only the id of the Claude conversation is remembered in `.canopy/ai-session.json` so follow-up questions work, and the path of the Claude Code program you allowed is saved in `.canopy/prefs.json`.
10
+ - **Send feedback** and the hint at the end of `canopy sync` are `mailto:` links. They open your own mail program with the subject filled in and an empty message. Canopy sends nothing itself.
11
+
12
+ ## The update check
13
+
14
+ The viewer can ask `https://registry.npmjs.org/canopy-engine/latest` once when it loads, to tell you if a newer version exists, and `canopy update` uses npm. The request carries no cookies, no identifiers and no information about you or your project. Turn the viewer's check off with `CANOPY_NO_UPDATE_CHECK=1`.
15
+
16
+ ## Optional anonymous usage counts
17
+
18
+ **Off by default. Opt-in only.** The first time you run Canopy in an interactive terminal, it asks once:
19
+
20
+ > Help improve Canopy? Share anonymous usage counts (no code, no file or project names, no questions). You can change this any time with `canopy telemetry off`. [y/N]
21
+
22
+ Pressing Enter means **No**. Commands, scripts, CI, `--quiet` runs and the commit hook never ask and never send. `DO_NOT_TRACK=1`, `CANOPY_TELEMETRY=0` or a `CI` environment variable switch it off no matter what was chosen earlier.
23
+
24
+ Control it any time: `canopy telemetry status`, `on`, `off` (which also deletes your random id), and `show` (prints exactly what would be sent). The choice and the random id live in your user config folder (Windows `%APPDATA%\canopy\config.json`, otherwise `~/.config/canopy/config.json`), not inside any project.
25
+
26
+ ### Exactly what can be sent
27
+
28
+ Every event carries: the event name, a random install id (a UUID made only after you say yes, not linked to you), a UTC timestamp, the Canopy version, the operating system family (`win32`, `darwin` or `linux`), the CPU architecture, the Node major version, and the editor you started Canopy from if known (`vscode`, `cursor`, `other`, `none`).
29
+
30
+ Events and their only extra fields (each a fixed word or a whole number):
31
+
32
+ | event | extra fields |
33
+ |---|---|
34
+ | `first_run` | none |
35
+ | `sync_completed` | duration bucket (`<5s`, `5-30s`, `30s-2m`, `2-10m`, `10m+`), number-of-files bucket (`<500`, `500-2k`, `2k-10k`, `10k+`), number-of-services bucket (`1`, `2-3`, `4-10`, `10+`), `ok` / `error`, an error code from a fixed list |
36
+ | `viewer_opened` | none |
37
+ | `search_used` | a count |
38
+ | `workflow_opened` | a count |
39
+ | `ask_ai_used` | a count, result (`ok`, `error`, `cancelled`), where Claude Code was found (`vscode`, `path`, `env`), answer-time bucket (`<10s`, `10-30s`, `30-90s`, `90s+`) |
40
+ | `feedback_link_clicked` | a count |
41
+ | `update_toast_shown` | a count |
42
+ | `error` | an error code from a fixed list (never a message) |
43
+
44
+ A single function in the code (`src/telemetry/events.js`) enforces this list: any other field is dropped, and a value that is not one of the listed words or a whole number is dropped too.
45
+
46
+ ### What is never collected
47
+
48
+ Your code; file paths; repository, project, function, node or workflow names; search text; your questions to Ask AI or the answers; error messages; environment variables or their values; user names, computer names or host names; your country. Requests ask the server not to store an IP address, and no person profile is created.
49
+
50
+ ### How it is sent
51
+
52
+ Counters are kept in memory and sent as one HTTPS request when Canopy exits and every 5 minutes while the viewer runs. The request has a 2-second limit, is never retried, and nothing is queued on disk. If it fails, nothing happens and Canopy carries on. Nothing is sent unless this build of Canopy has a statistics address configured (it is empty in development builds).
53
+
54
+ ### Where it goes
55
+
56
+ To a PostHog project in the EU region (`eu.i.posthog.com`) run by the maker of Canopy. Processor: PostHog (EU cloud region).
57
+
58
+ ### How long it is kept
59
+
60
+ *[Founder to state before launch: the retention period, for example "26 months, then deleted".]*
61
+
62
+ ### Your rights
63
+
64
+ Because the counts are anonymous and carry only a random id, they cannot be tied to you. If you want the data for your install id removed, run `canopy telemetry status`, note the choice file, and email the id from that file to the contact below. After `canopy telemetry off` the id is deleted from your computer and no new data is sent.
65
+
66
+ ## Contact
67
+
68
+ t.gandigoutham@gmail.com
69
+
70
+ ---
71
+
72
+ *This document describes how Canopy behaves. It has not yet been reviewed by a lawyer; do that before a public launch.*
package/README.md CHANGED
@@ -1,201 +1,241 @@
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.4.tgz`, install that: `npm install -g ./canopy-engine-0.1.4.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
-
98
- ## Links between services
99
-
100
- 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:
101
-
102
- - **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.
103
- - **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).
104
-
105
- 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).
106
-
107
- 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.
108
-
109
- 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.
110
-
111
- 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`).
112
-
113
- ## Hook on and off
114
-
115
- 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.
116
-
117
- - Turn it on: answer Y in `canopy` (or use `-y`), or run `canopy hook install` (safe to repeat; your own hook content is kept).
118
- - Turn it off: `canopy hook uninstall` (removes only Canopy's block). If you answered n once, `canopy` will not ask again.
119
-
120
- ## When it doesn't work
121
-
122
- Run `canopy doctor` and send its output with your feedback. Healthy output (the hook line is not critical):
123
-
124
- ```
125
- canopy 0.1.4 doctor (C:\work\my-project)
126
-
127
- OK Node version >= 18 - v22.12.0
128
- OK tree-sitter grammars (wasm) - 5 files
129
- OK query files (.scm) - 17 files
130
- OK write access to <path>/.canopy - C:\work\my-project\.canopy
131
- OK git available - git version 2.45.1.windows.1
132
- OK map viewer present
133
- FAIL post-commit hook installed
134
- fix: canopy hook install "C:\work\my-project" (not critical)
135
-
136
- all critical checks passed
137
- ```
138
-
139
- A critical problem looks like this, and `canopy doctor` then exits with 1:
140
-
141
- ```
142
- FAIL write access to <path>/.canopy - EEXIST: file already exists, mkdir 'C:\work\my-project\.canopy'
143
- fix: make C:\work\my-project writable for your user, or run canopy on a folder you own
144
-
145
- 1 critical problem(s) - fix them and run canopy doctor again
146
- ```
147
-
148
- Exit codes of `canopy`: 0 ok, 1 a problem (the message says what to do), 3 no JavaScript / TypeScript files.
149
-
150
- ## What is in `.canopy/`
151
-
152
- | file | what it is |
153
- |---|---|
154
- | `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) |
155
- | `output/systemGraph.js` | links between workflows and between services |
156
- | `output/services.js` | the services found (frontend, backend, ...) with counts |
157
- | `output/meta.js`, `meta.json` | repo root and the coverage report (files analyzed, skipped, with parse errors) |
158
- | `output/history.js` | the version list and recent diffs shown in the viewer's Changes panel |
159
- | `history/*.json.gz` | one compressed snapshot per version (total size capped at 200 MB, `CANOPY_SNAPSHOT_MAX_MB`) |
160
- | `sync.log` | log of background syncs started by the hook |
161
- | `meta.json` | the project's random id, when it was first set up, which version you last looked at (checksummed) |
162
- | `files.json` | size, time and hash of each analysed file, so an unchanged project is recognised without re-reading it |
163
- | `server.json` | the address of the viewer while it runs (so a second `canopy` reuses it) |
164
- | `last-error.txt` | only after a crash: time, version, stage, exit code, memory setting |
165
-
166
- ## The viewer in five bullets
167
-
168
- - First screen: the **services** of the project and the links between them; click one to list its **workflows**.
169
- - 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.
170
- - 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).
171
- - The `</>` button on any card shows the exact source lines.
172
- - 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.
173
- - **Runtime (predicted)** draws a workflow end to end across the services it calls, one band per service.
174
-
175
- ## Known limits
176
-
177
- Each line is backed by a measurement (the numbers are quoted in the lines below).
178
-
179
- - JavaScript and TypeScript only; other languages are silently skipped (a Python-only folder gives an empty map).
180
- - 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".
181
- - Method calls on objects mostly stay unresolved (`obj.method()`: 1.3% resolved in express); plain function calls and imports resolve better.
182
- - Flow type annotations are not supported (parse-server: 47 of 370 files have syntax errors).
183
- - Anonymous exports have no node, so calls into them are not followed.
184
- - Repositories with 5,000+ files take minutes (strapi, 5,451 files: 219 s the first time).
185
- - Vue and Svelte files are skipped (see "Which files are analysed").
186
-
187
- ## Troubleshooting
188
-
189
- - `canopy doctor` first. It exits non-zero only when a critical check fails (Node, grammars, queries, write access).
190
- - "no map yet": run `canopy sync <path>` (or just `canopy`) before `canopy open <path>`.
191
- - 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`.
192
- - 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.
193
- - 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.
194
-
195
- ## License
196
-
197
- 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.
198
-
199
- ## Feedback
200
-
201
- 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
+ - **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).