@mediamonks/canopy-cli 0.7.21 → 0.7.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/dev-BFPlhkzp.mjs +2 -0
  2. package/dist/{dev-CNqx_53I.mjs → dev-XULukupE.mjs} +3 -3
  3. package/dist/{dev-worker-Bij9OUMk.mjs → dev-worker-BDMjExps.mjs} +35 -1
  4. package/dist/docs/cli-login.md +2 -0
  5. package/dist/docs/cli-logout.md +9 -6
  6. package/dist/docs/index.json +27 -7
  7. package/dist/docs/platform-ai-usage.md +36 -10
  8. package/dist/docs/platform-assistant.md +50 -0
  9. package/dist/docs/platform-client-access.md +5 -2
  10. package/dist/docs/platform-feedback.md +3 -0
  11. package/dist/docs/platform-members-and-access.md +29 -6
  12. package/dist/docs/{notifications.md → platform-notifications.md} +6 -2
  13. package/dist/docs/platform-security-headers.md +53 -0
  14. package/dist/docs/platform-slack.md +87 -0
  15. package/dist/docs/sdk-ai.md +102 -11
  16. package/dist/docs/sdk-files.md +6 -0
  17. package/dist/docs/sdk-slack.md +132 -0
  18. package/dist/{docs-fiC6q9Ce.mjs → docs-BDPL0TI3.mjs} +1 -1
  19. package/dist/{feedback-CAwUHwWa.mjs → feedback-B3pnUPLf.mjs} +8 -3
  20. package/dist/index.mjs +10 -10
  21. package/dist/{local-CsSP4Vpt.mjs → local-BjsuI1IB.mjs} +37 -8
  22. package/dist/login-By4y5O5-.mjs +336 -0
  23. package/dist/{package-C_DLlB96.mjs → package-C5rZwlw8.mjs} +1 -1
  24. package/dist/{skill-CmgMU2Iq.mjs → skill-CgToW_Ac.mjs} +1 -1
  25. package/dist/{src-BKKAEkbH.mjs → src-BPKfY9IN.mjs} +53 -1
  26. package/dist/{upgrade-D1j0WV4l.mjs → upgrade-Bl_NJ_m5.mjs} +1 -1
  27. package/dist/{usage-CRpVLLi6.mjs → usage-C1-cvS2-.mjs} +13 -1
  28. package/package.json +3 -3
  29. package/dist/dev-D7v7WK9O.mjs +0 -2
  30. package/dist/login-DRYaJcfT.mjs +0 -285
@@ -0,0 +1,2 @@
1
+ import { t as devCommand } from "./dev-XULukupE.mjs";
2
+ export { devCommand };
@@ -37,7 +37,7 @@ async function workerPlugin(flags, slug) {
37
37
  console.log(`note: canopy.json points "worker" at ${entry}, which does not exist — skipping.`);
38
38
  return;
39
39
  }
40
- const { missingSecretNames, readDevVars, workerDevPlugin } = await import("./dev-worker-Bij9OUMk.mjs");
40
+ const { missingSecretNames, readDevVars, workerDevPlugin } = await import("./dev-worker-BDMjExps.mjs");
41
41
  const secrets = readDevVars(flags.dir);
42
42
  const email = siteConfig().email;
43
43
  const plugin = workerDevPlugin({
@@ -165,7 +165,7 @@ async function devRemote(flags, slug) {
165
165
  async function devLocal(flags, slug) {
166
166
  let dump;
167
167
  if (flags.pull) {
168
- const { fetchProjectDump } = await import("./local-CsSP4Vpt.mjs");
168
+ const { fetchProjectDump } = await import("./local-BjsuI1IB.mjs");
169
169
  console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
170
170
  dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
171
171
  }
@@ -180,7 +180,7 @@ async function devLocal(flags, slug) {
180
180
  const reason = err instanceof ApiError ? err.explain().why : String(err);
181
181
  console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
182
182
  }
183
- const { createSandbox } = await import("./local-CsSP4Vpt.mjs");
183
+ const { createSandbox } = await import("./local-BjsuI1IB.mjs");
184
184
  const sandbox = await createSandbox({
185
185
  slug,
186
186
  persistDir: join(flags.dir, ".canopy", "local"),
@@ -1,4 +1,4 @@
1
- import "./src-BKKAEkbH.mjs";
1
+ import "./src-BPKfY9IN.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { relative, resolve, sep } from "node:path";
4
4
  //#region ../data-plane/src/worker-request.ts
@@ -25,6 +25,39 @@ const IDENTITY_STAMP_HEADERS = [
25
25
  "x-canopy-user-email",
26
26
  "x-canopy-user-name"
27
27
  ];
28
+ /** Cookies that authenticate to Canopy itself — the monk session and the client-page session. */
29
+ const CANOPY_COOKIE_NAMES = /* @__PURE__ */ new Set(["canopy_session", "canopy_client"]);
30
+ /**
31
+ * Strip Canopy's own credentials from a request before untrusted project code
32
+ * sees it (R-01).
33
+ *
34
+ * The visitor's session cookie arrives with every request the gateway
35
+ * dispatches, and a bundle that can read it can replay it against the platform
36
+ * as that person — against the control plane, and against other projects' data
37
+ * on the api host. It is a credential, so the host keeps it in closure scope
38
+ * like the raw DB and blob handles, and hands the bundle a redacted request.
39
+ *
40
+ * Redaction is surgical on purpose: an app's own cookies and its own
41
+ * Authorization scheme survive, because those belong to the app. Only
42
+ * `canopy_session` / `canopy_client` and a `cnpy_` bearer are removed.
43
+ *
44
+ * `env.CANOPY.fetch` still authenticates as the visitor — the host passes it
45
+ * the unredacted headers — so nothing about the platform-facing behaviour
46
+ * changes; the credential simply stops being reachable from bundle code.
47
+ */
48
+ function redactCanopyCredentials(headers) {
49
+ const cookie = headers.get("cookie");
50
+ if (cookie !== null) {
51
+ const kept = cookie.split(";").filter((part) => {
52
+ const name = part.split("=")[0]?.trim() ?? "";
53
+ return name !== "" && !CANOPY_COOKIE_NAMES.has(name);
54
+ }).join(";").trim();
55
+ if (kept === "") headers.delete("cookie");
56
+ else headers.set("cookie", kept);
57
+ }
58
+ if (headers.get("authorization")?.startsWith("Bearer cnpy_")) headers.delete("authorization");
59
+ return headers;
60
+ }
28
61
  /**
29
62
  * Strip what must never travel upstream and stamp who is calling. Common to
30
63
  * every host; says nothing about credentials.
@@ -331,6 +364,7 @@ function workerDevPlugin(options) {
331
364
  }));
332
365
  return;
333
366
  }
367
+ redactCanopyCredentials(request.headers);
334
368
  await sendResponse(res, await worker.fetch(request, env));
335
369
  })().catch((err) => {
336
370
  if (err instanceof Error) server.ssrFixStacktrace(err);
@@ -24,6 +24,8 @@ canopy login --json # machine-readable status for agents/CI
24
24
  2. You finish signing in **in the browser** — your password never passes through the CLI. When OneLogin hands control back, the browser shows a small "You're signed in" page and you can return to the terminal. The sign-in waits up to 5 minutes; the hand-back uses local port `8976`, so if another program holds that port the login fails with a clear message.
25
25
  3. The CLI stores a token for **every** site (`main`, `staging`, `gcp-pilot`), not just the one you picked, in `~/.canopy/config.json` — readable only by your user account.
26
26
 
27
+ Each token is labelled with your machine's hostname and expires after 30 days. The dashboard's **Account** page (your avatar → Account) lists every machine you have signed in from and lets you revoke any of them; `canopy logout` revokes this machine's tokens for you.
28
+
27
29
  One login therefore covers all sites; `--profile` only decides which one commands target by default. If minting fails for a non-active site you get a warning but login still succeeds; if it fails for your **active** profile the command errors and asks you to re-run.
28
30
 
29
31
  ## Examples
@@ -1,25 +1,28 @@
1
1
  ---
2
2
  title: canopy logout
3
- description: Sign out of Canopy by removing all locally stored CLI tokens.
3
+ description: Sign out of Canopy — revoke your CLI tokens on every site and forget them locally.
4
4
  ---
5
5
 
6
- `canopy logout` signs you out on this machine by forgetting every stored CLI token. Use it on a shared machine, or when you want a clean slate before logging in as someone else.
6
+ `canopy logout` signs you out on this machine: it revokes the CLI token stored for every site on the platform, then forgets them in `~/.canopy/config.json`. Use it on a shared machine, or when you want a clean slate before logging in as someone else.
7
7
 
8
8
  ## Usage
9
9
 
10
10
  ```bash
11
11
  canopy logout
12
+ canopy logout --local # forget the tokens here, but leave them valid on the platform
12
13
  ```
13
14
 
14
15
  ## Arguments & flags
15
16
 
16
- None. Logout is deliberately all-or-nothing: it clears the tokens for **every** site (`main`, `staging`, `gcp-pilot`) at once — there is no per-profile logout, so the global `--profile` flag has no effect here.
17
+ - `--local` — only remove the tokens from this machine; nothing is called on the server. The tokens stay valid until they expire (30 days after login), and you can still revoke them from the dashboard's **Account** page.
18
+
19
+ Logout is deliberately all-or-nothing: it signs out of **every** site (`main`, `staging`, `gcp-pilot`) at once — there is no per-profile logout, so the global `--profile` flag has no effect here.
17
20
 
18
21
  ## Details
19
22
 
20
- The command is local-only: it removes every stored token from `~/.canopy/config.json` and prints a confirmation. Nothing is called on the server, so the tokens themselves aren't revoked — they simply age out at their normal expiry. Your active-site choice stays in the file, so the next `canopy login` picks up where you left off.
23
+ For each site with a stored token, the command asks the platform to revoke that token, so it stops working everywhere immediately — not just on this machine. Then the file is cleaned up. If a site cannot be reached, the local copy is still removed and the command tells you which tokens it could not revoke; those keep working until they expire, or until you revoke them from the dashboard's **Account** page (your avatar → Account), which lists every machine you have signed in from.
21
24
 
22
- If no token was stored to begin with, it prints `Not logged in.` and exits successfully.
25
+ Your active-site choice stays in the file, so the next `canopy login` picks up where you left off. If no token was stored to begin with, it prints `Not logged in.` and exits successfully.
23
26
 
24
27
  ## Examples
25
28
 
@@ -27,6 +30,6 @@ If no token was stored to begin with, it prints `Not logged in.` and exits succe
27
30
  canopy logout
28
31
  ```
29
32
 
30
- Removes all site tokens from `~/.canopy/config.json`; subsequent commands that need the API will ask you to run `canopy login`.
33
+ Revokes all site tokens on the platform and removes them from `~/.canopy/config.json`; subsequent commands that need the API will ask you to run `canopy login`.
31
34
 
32
35
  See also [canopy login](https://docs.canopy.website/cli/login) and [canopy whoami](https://docs.canopy.website/cli/whoami).
@@ -77,11 +77,6 @@
77
77
  "title": "CLI command reference",
78
78
  "summary": "Every canopy command, its flags, and what it does."
79
79
  },
80
- {
81
- "slug": "notifications",
82
- "title": "Slack notifications",
83
- "summary": "Canopy DMs you about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules — and answers questions when you ask."
84
- },
85
80
  {
86
81
  "slug": "static-serving",
87
82
  "title": "How static files are served",
@@ -122,13 +117,18 @@
122
117
  {
123
118
  "slug": "platform-ai-usage",
124
119
  "title": "AI access & usage",
125
- "summary": "Projects talk to AI models through the platform — no API keys to manage, a hard daily budget per project, and full visibility of spend."
120
+ "summary": "Projects talk to AI models through the platform — no API keys to manage, hard per-project budgets, and full visibility of spend."
126
121
  },
127
122
  {
128
123
  "slug": "platform-google",
129
124
  "title": "Google",
130
125
  "summary": "Let your project work with Google data — Sheets, Docs, Calendar, Drive, Gmail — without handling any OAuth yourself."
131
126
  },
127
+ {
128
+ "slug": "platform-slack",
129
+ "title": "Slack",
130
+ "summary": "Give your project its own Slack bot — Canopy creates and manages the app, your code handles the events."
131
+ },
132
132
  {
133
133
  "slug": "platform-members-and-access",
134
134
  "title": "Members & access",
@@ -139,11 +139,26 @@
139
139
  "title": "Feedback & bug reports",
140
140
  "summary": "A built-in feedback widget on every project, a ticket-lite inbox behind it, and a CLI that lets agents work the backlog."
141
141
  },
142
+ {
143
+ "slug": "platform-notifications",
144
+ "title": "Notifications",
145
+ "summary": "Canopy DMs you on Slack about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules."
146
+ },
147
+ {
148
+ "slug": "platform-assistant",
149
+ "title": "The Canopy assistant",
150
+ "summary": "Ask questions about your projects and the platform in plain language — in Slack, or from the button in the corner of the docs and the dashboard."
151
+ },
142
152
  {
143
153
  "slug": "platform-client-access",
144
154
  "title": "Client access",
145
155
  "summary": "Share a project with clients who have no Monks account — a shared password unlocks that one project's URL and nothing else."
146
156
  },
157
+ {
158
+ "slug": "platform-security-headers",
159
+ "title": "Browser security",
160
+ "summary": "The security headers the platform adds to your app's pages and files, what they block, and the two switches in Settings."
161
+ },
147
162
  {
148
163
  "slug": "platform-sites-and-profiles",
149
164
  "title": "Sites & profiles",
@@ -174,7 +189,7 @@
174
189
  {
175
190
  "slug": "cli-logout",
176
191
  "title": "canopy logout",
177
- "summary": "Sign out of Canopy by removing all locally stored CLI tokens."
192
+ "summary": "Sign out of Canopy — revoke your CLI tokens on every site and forget them locally."
178
193
  },
179
194
  {
180
195
  "slug": "cli-create",
@@ -323,6 +338,11 @@
323
338
  "title": "Google",
324
339
  "summary": "Call Google APIs as the project's connected account or as the visitor's own — the platform holds the OAuth."
325
340
  },
341
+ {
342
+ "slug": "sdk-slack",
343
+ "title": "Slack",
344
+ "summary": "Handle your bot's events and slash commands in your worker, and post to Slack as the bot — no tokens in project code."
345
+ },
326
346
  {
327
347
  "slug": "sdk-services",
328
348
  "title": "Services",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: AI access & usage
3
- description: Projects talk to AI models through the platform — no API keys to manage, a hard daily budget per project, and full visibility of spend.
3
+ description: Projects talk to AI models through the platform — no API keys to manage, hard per-project budgets, and full visibility of spend.
4
4
  ---
5
5
 
6
6
  Canopy projects can call AI models without you ever handling an API key. The
7
7
  platform routes every request through its own metered gateway, tracks what
8
- each project spends, and enforces a hard daily budget — so a runaway loop in
8
+ each project spends, and enforces hard budgets — so a runaway loop in
9
9
  a vibecoded app becomes a capped bill and a visible chart, not a surprise
10
10
  invoice.
11
11
 
@@ -26,9 +26,26 @@ const reply = await canopy.ai.chat({
26
26
  default — ask for a model outside the list and the API tells you what _is_
27
27
  available.
28
28
  - **Streaming** via `canopy.ai.chatStream()` for chat-style UIs.
29
+ - **Documents and images.** Messages can attach files — reference something
30
+ in the project's [blob storage](https://docs.canopy.website/sdk/blob) by key, or inline base64 — see
31
+ [the AI SDK page](https://docs.canopy.website/sdk/ai). Models that read PDFs and images get the real
32
+ document, not a text extraction.
29
33
  - **An OpenAI-compatible endpoint.** `canopy.ai.endpoint()` returns a base
30
34
  URL and model list you can plug into ecosystem tools (like the Vercel AI
31
35
  SDK) — same key handling, same budget, same tracking.
36
+ - **Provider-native endpoints.** For provider-specific capabilities —
37
+ Anthropic prompt caching and PDF citations, OpenAI's Responses API — point
38
+ an official SDK at the native base URLs from `canopy.ai.endpoint()`. Same
39
+ budget and tracking as everything else; see [the AI SDK page](https://docs.canopy.website/sdk/ai).
40
+
41
+ ## Request size
42
+
43
+ A single chat request — prompt, inline base64 attachments, and referenced
44
+ blobs together — can be up to **8 MB**. Base64 makes files about a third
45
+ larger than they are on disk, so that fits roughly a 6 MB document per
46
+ request. Bigger requests answer with a `chat_too_large` error (HTTP 413)
47
+ before anything is sent to a model. Providers also have their own per-file
48
+ limits, which are typically above this.
32
49
 
33
50
  ## Your data and the model providers
34
51
 
@@ -41,16 +58,25 @@ This applies to every way your project reaches AI — `canopy.ai.chat()`,
41
58
  streaming, and the OpenAI-compatible endpoint all go through the same gateway
42
59
  to the same accounts.
43
60
 
44
- ## The daily budget
61
+ ## Budgets: a daily limit and a monthly cap
62
+
63
+ Every project has two AI budgets in USD, enforced from the same spend ledger
64
+ the dashboard displays — so what you see and what's enforced can never
65
+ disagree:
66
+
67
+ - **A daily limit** (default $5) catches runaway loops. Cross it and AI
68
+ calls answer `ai_budget_exceeded` until midnight UTC; everything resets
69
+ the next day.
70
+ - **A monthly cap** (default $20) is the real spending ceiling. Cross it and
71
+ AI calls pause until the calendar month rolls over.
45
72
 
46
- Every project has a daily AI budget in USD. When a project's spend for the
47
- day crosses it, further AI calls answer with an `ai_budget_exceeded` error
48
- until the day rolls over. The budget is enforced from the same spend ledger
49
- the dashboard displays, so what you see and what's enforced can never
50
- disagree.
73
+ The error names which one tripped. The Usage tab shows spend against both.
74
+ If a project genuinely needs more headroom, a Canopy admin can raise either
75
+ budget from that same tab — ask in the project's feedback or reach out to
76
+ the platform team.
51
77
 
52
- > **Note:** Hitting the cap doesn't break the rest of your app — pages, data, and files keep working. Only AI
53
- > calls pause until the next day.
78
+ > **Note:** Hitting a budget doesn't break the rest of your app — pages, data, and files keep working. Only AI
79
+ > calls pause until the window rolls over.
54
80
 
55
81
  ## The Usage tab
56
82
 
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: The Canopy assistant
3
+ description: Ask questions about your projects and the platform in plain language — in Slack, or from the button in the corner of the docs and the dashboard.
4
+ ---
5
+
6
+ Canopy has an assistant that answers questions about **your projects** and
7
+ **how the platform works**. It reads the same things you can see — your
8
+ projects, their deploys, reports, AI spend, egress log and schedules — plus
9
+ these docs, and answers with a link to the guide it answered from.
10
+
11
+ It can only read. It never deploys, rolls back, changes settings or touches
12
+ your data; asked to, it points you at the dashboard or the `canopy` CLI.
13
+
14
+ ## Where to ask
15
+
16
+ **In the docs and the dashboard.** The floating button in the bottom-right
17
+ corner opens a choice: send feedback, or ask the assistant. Pick the second
18
+ and type your question. The page you're on sets the context, so on a project's
19
+ dashboard page "how many deploys today?" already knows which project you mean.
20
+
21
+ Your conversation survives a reload — come back to the tab an hour later and
22
+ it's still there. **New chat** clears it and starts over. Only one
23
+ conversation is kept, and it lives in your own browser.
24
+
25
+ **In Slack.** DM **Canopy**, or mention **@Canopy** in a channel. Same
26
+ assistant, same answers. In a channel linked to a project it knows which
27
+ project you mean; replying in a report's notification thread, it knows which
28
+ report. See [Notifications](https://docs.canopy.website/platform/notifications).
29
+
30
+ ## What it's good at
31
+
32
+ - **"Why is my fetch failing?"** — it checks the egress log and tells you
33
+ which host was blocked, and where to allow it.
34
+ - **"What happened on my project today?"** — deploys, new reports, status
35
+ changes.
36
+ - **"How do I promote staging to live?"** — it looks it up in these docs
37
+ rather than guessing.
38
+ - **"What's the context on this report?"** — the body, the trail, and the
39
+ triage notes you're allowed to see.
40
+
41
+ ## What it can't do
42
+
43
+ - **Change anything.** Read-only, by design.
44
+ - **See projects you can't.** Every lookup runs as you: a project you're not
45
+ on simply doesn't resolve.
46
+ - **Answer questions about your app's own content.** It knows Canopy and your
47
+ projects on it — not what's inside them.
48
+
49
+ It runs on a small daily budget shared by everyone. On the rare day that runs
50
+ out, it says so and comes back the next.
@@ -36,8 +36,11 @@ it** card:
36
36
  2. Send the client the project URL and the password.
37
37
 
38
38
  To change the password, set a new one — previously handed-out passwords stop
39
- working for new sign-ins. To turn client access off entirely, remove the
40
- password from the same card; visitors are back to Monks-only immediately.
39
+ working, and every browser that unlocked the project with the old one is
40
+ signed out on its next visit and asked for the new password. To turn client
41
+ access off entirely, remove the password from the same card, or switch the
42
+ mode back to Monks only; either way, visitors are back to Monks-only
43
+ immediately, unlocked browsers included.
41
44
 
42
45
  > **Warning:** A shared password is deliberately lightweight protection — right for work-in-progress previews,
43
46
  > wrong for anything sensitive. Anyone who has it can view the app and share it further, so rotate
@@ -18,6 +18,9 @@ boards.
18
18
 
19
19
  - Appears only for **signed-in Monks**. Client-password visitors never see it —
20
20
  the platform doesn't even send them the script.
21
+ - On Canopy's own pages (these docs and the dashboard) the same button also
22
+ opens [the assistant](https://docs.canopy.website/platform/assistant); on your project it is a feedback
23
+ widget and nothing else.
21
24
  - On by default for every project. Turn it off per project in
22
25
  **Settings → Feedback widget**; the change applies on the next page load.
23
26
  - Submissions are always identified (your company sign-in), so there's no
@@ -61,11 +61,34 @@ Turning it off again is the same toggle.
61
61
  > **Note:** Collaborators-only gates the **live app**. Management access (settings, data, deploys) is always
62
62
  > members-only, regardless of this toggle.
63
63
 
64
- ## Platform admins and the Users page
65
-
66
- Platform admins get an extra **Users** page in the dashboard listing everyone
67
- on Canopy — name, email, and when they first signed in — with a one-click way
68
- to grant or remove admin. Accounts are created automatically on first
69
- OneLogin sign-in; there is nothing to provision.
64
+ ## Platform admins and the Admin page
65
+
66
+ Platform admins get an extra **Admin** page in the dashboard. Its **Users**
67
+ tab lists everyone on Canopy — name, email, when they first and last signed
68
+ in — with a one-click way to grant or remove admin and to sign someone out
69
+ everywhere. Its **Sessions** tab lists every live CLI sign-in on the site:
70
+ who, from which machine, and when it was last used, each revocable on the
71
+ spot. Accounts are created automatically on first OneLogin sign-in; there is
72
+ nothing to provision.
73
+
74
+ ## Your sign-ins, and signing out everywhere
75
+
76
+ A Canopy sign-in lives in two places: the browser session from OneLogin, and
77
+ the CLI tokens `canopy login` stores on each machine (one per site). Your
78
+ **Account** page — open the avatar menu in the dashboard header — lists the
79
+ machines you have signed in from, with when each token was last used, and lets
80
+ you revoke any of them. Revoking a token is what `canopy logout` does for the
81
+ machine it runs on.
82
+
83
+ **Sign out everywhere** on the same page ends every browser session and every
84
+ CLI sign-in of yours on that site at once, this one included. Reach for it
85
+ when a laptop goes missing or you signed in somewhere you shouldn't have; the
86
+ next request from any of those places meets the sign-in page. Deploy tokens
87
+ used by CI belong to their projects, not to you, and keep working — revoke
88
+ those from the project's Deploys tab if they were on the lost machine too.
89
+
90
+ Platform admins have the same button for any user on the **Admin** page, which
91
+ is how someone who leaves the company is signed out of Canopy before their
92
+ open sessions would have expired on their own.
70
93
 
71
94
  - [Client access](https://docs.canopy.website/platform/client-access) — Share work-in-progress with clients who don't have a Monks account.
@@ -1,6 +1,6 @@
1
1
  ---
2
- title: Slack notifications
3
- description: Canopy DMs you about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules — and answers questions when you ask.
2
+ title: Notifications
3
+ description: Canopy DMs you on Slack about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules.
4
4
  ---
5
5
 
6
6
  Canopy sends you Slack DMs when something on the platform needs your
@@ -51,6 +51,10 @@ for that it points you at the dashboard or the CLI. In a channel that's
51
51
  linked to a project, it already knows which project you mean; replying in a
52
52
  report's notification thread, it knows which report.
53
53
 
54
+ The same assistant answers from the button in the corner of these docs and
55
+ the dashboard, if you'd rather not switch to Slack — see
56
+ [The Canopy assistant](https://docs.canopy.website/platform/assistant).
57
+
54
58
  ## If DMs don't arrive
55
59
 
56
60
  The bot finds you by your work email. If your Slack account uses a different
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: Browser security
3
+ description: The security headers the platform adds to your app's pages and files, what they block, and the two switches in Settings.
4
+ ---
5
+
6
+ Every project on Canopy shares one sign-in, so a script that manages to run
7
+ inside one app is a script running next to every other app a visitor can open.
8
+ To keep that from mattering, the platform adds a small set of browser security
9
+ headers to the pages and files it serves for you. You don't configure anything
10
+ to get them, and most projects never notice they're there.
11
+
12
+ ## What your pages get
13
+
14
+ - **A Content-Security-Policy.** Scripts may load from your own app and from
15
+ any `https://` address, so CDNs and hosted libraries keep working. Inline
16
+ `<script>` blocks that are part of your deployed HTML keep working too — the
17
+ platform reads them at serve time and allows exactly those. What's blocked:
18
+ inline event handlers such as `onclick="…"`, `javascript:` links, plugins
19
+ (`<object>`, `<embed>`), scripts loaded over plain `http://`, and inline
20
+ scripts that were _not_ in your deploy — which is what an injected script
21
+ usually is.
22
+ - **No embedding by other sites.** Your pages can only be shown in an iframe by
23
+ your own app.
24
+ - **A referrer policy and HTTPS pinning.** Other sites see your origin but not
25
+ your full URLs, and browsers remember to always use HTTPS.
26
+
27
+ ## The two switches
28
+
29
+ Both live on the project's **Settings → Security** page, and both take effect
30
+ on the next page load — no redeploy.
31
+
32
+ - **Content-Security-Policy** — on by default. Turn it off if your app depends
33
+ on something the policy blocks and you can't change it: an inline `onclick`,
34
+ a `javascript:` link, a library that writes `<script>` tags into the page at
35
+ runtime, or a backend route that renders HTML with its own inline scripts.
36
+ Check the browser console first — a blocked script shows up there with the
37
+ directive that stopped it.
38
+ - **Allow embedding in other sites** — off by default. Turn it on when another
39
+ site needs to show this app in an iframe.
40
+
41
+ Backend-rendered pages (a worker route that returns HTML) receive the framing
42
+ and referrer headers but no script policy, because the platform only hashes
43
+ inline scripts it knows came from a deploy.
44
+
45
+ ## Files you store
46
+
47
+ Files saved with `canopy.blob` are served from your app's address, with the
48
+ content type you gave them. Images, video, audio, fonts, plain text, CSV, JSON
49
+ and PDF display normally. SVGs display too, but sandboxed — fine in an
50
+ `<img>`, and harmless if someone opens the URL directly. Anything a browser
51
+ would treat as a page — HTML, XML, scripts, or files with an unknown type —
52
+ downloads instead of rendering. If you want to show user-uploaded HTML, render
53
+ it inside your own page rather than linking to the stored file.
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Slack
3
+ description: Give your project its own Slack bot — Canopy creates and manages the app, your code handles the events.
4
+ ---
5
+
6
+ Your project can have its own Slack app: its own name and bot, in whatever
7
+ workspace you choose. Canopy creates the app, keeps its settings in sync,
8
+ holds every credential, and routes Slack's traffic to your backend. Your
9
+ code never sees a bot token, a signing secret, or a webhook — it exports a
10
+ handler and calls `canopy.slack`, both covered in
11
+ [the SDK reference](https://docs.canopy.website/sdk/slack).
12
+
13
+ Everything on this page happens on the project's **Services** tab.
14
+
15
+ ## One-time: your configuration token
16
+
17
+ Creating Slack apps requires a Slack _app configuration token_, which is
18
+ personal — it belongs to you, in one workspace. You set it up once and every
19
+ project you manage shares it:
20
+
21
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and pick the
22
+ workspace your bots should live in — **that choice is baked into the
23
+ token**, and it's where every app you create will land.
24
+ 2. Under "Your App Configuration Tokens", generate a token and copy the
25
+ **refresh token** (the one starting `xoxe-1-`). The access token isn't
26
+ needed.
27
+ 3. Paste it into the token section of any project's Services tab and save.
28
+
29
+ Canopy validates it against Slack, stores it encrypted, and keeps it fresh
30
+ from then on — this is genuinely one-time. If it ever stops working (for
31
+ example, it was revoked), existing bots keep running; only app-settings
32
+ changes pause until you paste a fresh one.
33
+
34
+ To create bots in a different workspace — a client's, say — generate a token
35
+ for that workspace and save it. One workspace at a time per person.
36
+
37
+ ## Creating the app
38
+
39
+ Name the bot, pick its features, create. Every app comes with a bot that can
40
+ be invited to channels, post messages, and send people direct messages — the
41
+ notification baseline, no options needed. On top of that:
42
+
43
+ - **AI assistant** — people can chat with the bot: by @-mentioning it in
44
+ channels, in direct messages, and in Slack's assistant pane. Without it
45
+ the bot only sends; nobody can talk back. One caveat from Slack's side:
46
+ once on, it can't be turned off again.
47
+ - **Slash commands** — each becomes a workspace command handled by your
48
+ backend. Command names aren't reserved per app, so pick ones another app
49
+ in the workspace isn't already using.
50
+
51
+ ## Installing to the workspace
52
+
53
+ Creating the app doesn't put it in the workspace — installing does, and
54
+ that's a human step by design: you approve the bot's permissions on Slack's
55
+ own screen. Click **Install to workspace** and follow it through.
56
+
57
+ If your workspace requires admin approval for new apps, the first attempt
58
+ becomes an approval request instead. Once the admin approves (Slack DMs you
59
+ about it), either come back and click install again, or use the install
60
+ link Slack itself offers — both complete the same way. A Slack-side install
61
+ only works into the workspace the app already belongs to.
62
+
63
+ The same button reappears whenever you change features in a way that needs
64
+ new permissions — the card shows a clear warning when saved changes are
65
+ waiting on a reinstall. Feature changes that don't widen permissions apply
66
+ immediately.
67
+
68
+ ## Day to day
69
+
70
+ - **The status line** shows where the app is installed and when Slack last
71
+ delivered something — the quickest "is it wired up" check.
72
+ - **Enabled** is the kill switch: off, Slack traffic is accepted and
73
+ dropped, nothing reaches your code, and outbound sends are refused. The
74
+ app stays installed; flip it back anytime.
75
+ - **Remove Slack app** deletes the app from the workspace too — bot,
76
+ commands, history access, all of it.
77
+
78
+ Every message your project sends through the bot is counted per day, so a
79
+ runaway loop is visible.
80
+
81
+ ## Local development
82
+
83
+ Slack needs a public URL to deliver to, so `canopy dev` can't receive
84
+ events or commands. Test inbound handling on a deployed project — a scratch
85
+ project with a scratch bot works well — and unit-test your handler directly.
86
+ Outbound (`canopy.slack.api`) works from `canopy dev` like any other SDK
87
+ call.