@mediamonks/canopy-cli 0.7.20 → 0.7.22
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/dist/{dev-DtEwiNYi.mjs → dev-CNqx_53I.mjs} +3 -3
- package/dist/dev-D7v7WK9O.mjs +2 -0
- package/dist/{dev-worker-IWnHvQ1f.mjs → dev-worker-Bij9OUMk.mjs} +1 -1
- package/dist/docs/cli-dev.md +1 -1
- package/dist/docs/cli-logs.md +1 -1
- package/dist/docs/data.md +2 -2
- package/dist/docs/dev.md +1 -1
- package/dist/docs/developers-architecture.md +26 -18
- package/dist/docs/index.json +21 -11
- package/dist/docs/migrations.md +17 -14
- package/dist/docs/platform-assistant.md +50 -0
- package/dist/docs/platform-egress.md +2 -2
- package/dist/docs/platform-feedback.md +3 -0
- package/dist/docs/{notifications.md → platform-notifications.md} +6 -2
- package/dist/docs/platform-project-data.md +3 -3
- package/dist/docs/platform-schedules.md +1 -1
- package/dist/docs/platform-sites-and-profiles.md +1 -1
- package/dist/docs/platform-slack.md +85 -0
- package/dist/docs/sdk-files.md +11 -1
- package/dist/docs/sdk-slack.md +132 -0
- package/dist/docs/static-serving.md +58 -300
- package/dist/{docs-C2D7__Iz.mjs → docs-c0CdeBxh.mjs} +1 -1
- package/dist/{feedback-BvkCjS-g.mjs → feedback-QhD8k6Ph.mjs} +8 -3
- package/dist/index.mjs +10 -10
- package/dist/{local-CKRZur_5.mjs → local-CsSP4Vpt.mjs} +2 -2
- package/dist/login-BrEDKrb3.mjs +285 -0
- package/dist/{package-B-aXVcQu.mjs → package-BuwV_mje.mjs} +1 -1
- package/dist/{skill-CqiSkac0.mjs → skill-BvoYCIKk.mjs} +1 -1
- package/dist/{src-Bu4Ze32I.mjs → src-BKKAEkbH.mjs} +25 -2
- package/dist/templates/spa/package.json +1 -1
- package/dist/templates/worker/AGENTS.md +3 -0
- package/dist/templates/worker/package.json +1 -1
- package/dist/templates/worker/worker/index.ts +20 -3
- package/dist/{upgrade-CpUCVTrN.mjs → upgrade-0lmdjvom.mjs} +1 -1
- package/dist/{usage-CRpVLLi6.mjs → usage-C1-cvS2-.mjs} +13 -1
- package/package.json +2 -2
- package/dist/dev-DjkK-50R.mjs +0 -2
- package/dist/docs/infrastructure.md +0 -406
- package/dist/login-D-Ac_kFW.mjs +0 -285
|
@@ -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-
|
|
40
|
+
const { missingSecretNames, readDevVars, workerDevPlugin } = await import("./dev-worker-Bij9OUMk.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-
|
|
168
|
+
const { fetchProjectDump } = await import("./local-CsSP4Vpt.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-
|
|
183
|
+
const { createSandbox } = await import("./local-CsSP4Vpt.mjs");
|
|
184
184
|
const sandbox = await createSandbox({
|
|
185
185
|
slug,
|
|
186
186
|
persistDir: join(flags.dir, ".canopy", "local"),
|
package/dist/docs/cli-dev.md
CHANGED
|
@@ -29,7 +29,7 @@ canopy dev --local --pull # sandbox hydrated from a fresh prod export
|
|
|
29
29
|
|
|
30
30
|
**Local sandbox (`--local`).** Database, file, and auth requests are answered entirely on your machine, from a sandbox stored under `.canopy/local/` in the project directory — no database to install, and writes never reach the platform. The sandbox behaves the same as the real API, so code that works locally works deployed. Two things still go remote: `ai` and `services` calls go through to the real platform (metered as usual) when you're logged in — fully offline, they return errors until you log in and restart.
|
|
31
31
|
|
|
32
|
-
**Matching your site's SQL dialect.** The document API (`canopy.db.collection(…)`) behaves identically everywhere, but raw `canopy.db.sql()` speaks
|
|
32
|
+
**Matching your site's SQL dialect.** The document API (`canopy.db.collection(…)`) behaves identically everywhere, but raw `canopy.db.sql()` speaks your site's dialect — SQLite or Postgres (see [dialect()](https://docs.canopy.website/guides/data)). The sandbox defaults to Postgres. If your project uses raw SQL written for a SQLite site, lock the dialect in `canopy.json`:
|
|
33
33
|
|
|
34
34
|
```json
|
|
35
35
|
{ "slug": "my-project", "dialect": "sqlite" }
|
package/dist/docs/cli-logs.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Read your project worker's logs (console output) from the terminal
|
|
|
5
5
|
|
|
6
6
|
`canopy logs` prints what your project's worker wrote to `console.log` / `console.error`. Reach for it when the worker misbehaves — a 500, an unexpected 403, a schedule that "did nothing" — instead of adding diagnostic endpoints and redeploying. The same output lives in the dashboard under the project's **Logs** tab.
|
|
7
7
|
|
|
8
|
-
Logs are available on every site that runs
|
|
8
|
+
Logs are available on every site that runs backends. On a site where the platform can't read your backend's output, the command says so honestly instead of guessing. One caveat: on some sites log capture starts with a project's next deploy after 2026-08-19, so a backend that hasn't been redeployed since then has nothing to show yet.
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
package/dist/docs/data.md
CHANGED
|
@@ -3,8 +3,8 @@ title: Working with project data
|
|
|
3
3
|
description: Pulling, snapshotting, and restoring your project database.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Each Canopy project gets its own database and
|
|
7
|
-
|
|
6
|
+
Each Canopy project gets its own database and its own file storage. Your app
|
|
7
|
+
reads and writes them
|
|
8
8
|
through the Canopy SDK at runtime; the CLI gives you tools to manage the data
|
|
9
9
|
around it.
|
|
10
10
|
|
package/dist/docs/dev.md
CHANGED
|
@@ -39,7 +39,7 @@ canopy dev --local --pull
|
|
|
39
39
|
By default the sandbox runs PGlite (embedded Postgres), so raw
|
|
40
40
|
`canopy.db.sql()` speaks the postgres dialect there, while your deployed
|
|
41
41
|
project speaks whatever its platform target speaks (`canopy.db.dialect()`
|
|
42
|
-
tells you
|
|
42
|
+
tells you). Collections (`canopy.db.collection(...)`)
|
|
43
43
|
behave the same in both, so this only matters for hand-written SQL — see
|
|
44
44
|
`canopy docs data`. If your project's raw SQL targets a sqlite site, lock the
|
|
45
45
|
dialect in `canopy.json` (`"dialect": "sqlite"`) and the sandbox runs an
|
|
@@ -42,8 +42,8 @@ fall back to `index.html`, so client-side routes like `/settings` work on a
|
|
|
42
42
|
hard refresh without any configuration. Rollback just repoints "live" at an
|
|
43
43
|
earlier version — instant, nothing rebuilt.
|
|
44
44
|
|
|
45
|
-
The full
|
|
46
|
-
|
|
45
|
+
The full picture — caching rules, deploy limits, and version-pinned
|
|
46
|
+
`v12-dot-<slug>` preview hosts — is in
|
|
47
47
|
[How static files are served](https://docs.canopy.website/guides/static-serving).
|
|
48
48
|
|
|
49
49
|
## The optional backend worker
|
|
@@ -63,44 +63,52 @@ a backend, declared in `canopy.json`:
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Requests under `/api/*` reach the worker (Hono-style handlers); everything
|
|
66
|
-
else is still static serving.
|
|
66
|
+
else is still static serving. A few things to know:
|
|
67
67
|
|
|
68
68
|
- **Requests arrive authenticated.** The worker sits behind the same gateway
|
|
69
69
|
wall as the frontend.
|
|
70
|
-
- **
|
|
71
|
-
|
|
70
|
+
- **The caller is already identified.** `c.env.USER` is the signed-in person
|
|
71
|
+
(`null` during a scheduled run) — no round-trip needed:
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
|
-
app.get("/api/hello",
|
|
75
|
-
const
|
|
76
|
-
|
|
74
|
+
app.get("/api/hello", (c) => {
|
|
75
|
+
const user = c.env.USER;
|
|
76
|
+
if (!user) return c.json({ error: "unauthenticated" }, 401);
|
|
77
77
|
return c.json({ message: `Hello, ${user.name}!` });
|
|
78
78
|
});
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
- **Platform access goes through the same SDK**, via the `CANOPY` service
|
|
82
|
+
binding — same budgets, same data isolation as the browser:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const canopy = createCanopy({ fetch: c.env.CANOPY.fetch.bind(c.env.CANOPY) });
|
|
86
|
+
const notes = await canopy.db.collection("notes").find();
|
|
87
|
+
```
|
|
88
|
+
|
|
81
89
|
- **Secrets and schedules come from the dashboard.** Secrets (Settings →
|
|
82
90
|
Secrets) land as Workers-shaped bindings (`c.env.MY_API_KEY`, not
|
|
83
91
|
`process.env`); dashboard Schedules invoke an exported
|
|
84
92
|
`scheduled(event, env)` — switch on `event.name`.
|
|
85
93
|
|
|
86
|
-
> **
|
|
87
|
-
>
|
|
88
|
-
> deploy everywhere.
|
|
94
|
+
> **Note:** Backends run on the `main` and `gcp-pilot` sites. On a site without backend support, a deploy that
|
|
95
|
+
> includes one answers with a clear "not supported here" rather than failing obscurely;
|
|
96
|
+
> frontend-only projects deploy everywhere.
|
|
89
97
|
|
|
90
98
|
## Where your data lives
|
|
91
99
|
|
|
92
100
|
Every project gets its own database and its own blob-storage prefix. No other
|
|
93
101
|
project can read them, and deleting the project tears everything down. The
|
|
94
|
-
document API
|
|
102
|
+
document API works the same on every site; raw SQL speaks your site's dialect
|
|
95
103
|
(`sqlite` or `postgres` — ask `canopy.db.dialect()`). Dumps, snapshots, and
|
|
96
104
|
restores are CLI commands — see the [project data guide](https://docs.canopy.website/guides/data).
|
|
97
105
|
|
|
98
106
|
## Sites
|
|
99
107
|
|
|
100
108
|
A _site_ is one installation of the whole platform. The CLI ships three
|
|
101
|
-
profiles — `main`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
profiles — `main`, `staging`, and `gcp-pilot` — and always shows which one
|
|
110
|
+
you're on. Projects pin theirs in `canopy.json`; `--profile` or
|
|
111
|
+
`CANOPY_PROFILE` overrides per command. Most projects only ever use `main`;
|
|
112
|
+
see [sites and profiles](https://docs.canopy.website/platform/sites-and-profiles) for what the other two
|
|
113
|
+
are. Where sites differ (SQL dialect, full-text search, backend support) it
|
|
114
|
+
surfaces as a capability, not a separate API.
|
package/dist/docs/index.json
CHANGED
|
@@ -77,20 +77,10 @@
|
|
|
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",
|
|
88
|
-
"summary": "
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"slug": "infrastructure",
|
|
92
|
-
"title": "Infrastructure as code",
|
|
93
|
-
"summary": "Terraform for the account-level Cloudflare pieces Canopy needs."
|
|
83
|
+
"summary": "Why every page is a file, and what that means for routing, caching, and deploys."
|
|
94
84
|
},
|
|
95
85
|
{
|
|
96
86
|
"slug": "troubleshooting",
|
|
@@ -134,6 +124,11 @@
|
|
|
134
124
|
"title": "Google",
|
|
135
125
|
"summary": "Let your project work with Google data — Sheets, Docs, Calendar, Drive, Gmail — without handling any OAuth yourself."
|
|
136
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
|
+
},
|
|
137
132
|
{
|
|
138
133
|
"slug": "platform-members-and-access",
|
|
139
134
|
"title": "Members & access",
|
|
@@ -144,6 +139,16 @@
|
|
|
144
139
|
"title": "Feedback & bug reports",
|
|
145
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."
|
|
146
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
|
+
},
|
|
147
152
|
{
|
|
148
153
|
"slug": "platform-client-access",
|
|
149
154
|
"title": "Client access",
|
|
@@ -328,6 +333,11 @@
|
|
|
328
333
|
"title": "Google",
|
|
329
334
|
"summary": "Call Google APIs as the project's connected account or as the visitor's own — the platform holds the OAuth."
|
|
330
335
|
},
|
|
336
|
+
{
|
|
337
|
+
"slug": "sdk-slack",
|
|
338
|
+
"title": "Slack",
|
|
339
|
+
"summary": "Handle your bot's events and slash commands in your worker, and post to Slack as the bot — no tokens in project code."
|
|
340
|
+
},
|
|
331
341
|
{
|
|
332
342
|
"slug": "sdk-services",
|
|
333
343
|
"title": "Services",
|
package/dist/docs/migrations.md
CHANGED
|
@@ -65,9 +65,9 @@ data).
|
|
|
65
65
|
|
|
66
66
|
Pin your dialect first — a Drizzle schema is written for one SQL flavor. Check
|
|
67
67
|
what your project's site runs on (`await canopy.db.dialect()` or the
|
|
68
|
-
[data guide](https://docs.canopy.website/guides/data)) and set `"dialect"` in `canopy.json
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
[data guide](https://docs.canopy.website/guides/data)) and set `"dialect"` in `canopy.json` to match —
|
|
69
|
+
`"sqlite"` or `"postgres"`. That same field makes `canopy dev --local` run a
|
|
70
|
+
matching local engine.
|
|
71
71
|
|
|
72
72
|
Install the tools and describe your schema:
|
|
73
73
|
|
|
@@ -113,24 +113,30 @@ This is fully local — it diffs your schema files and writes `.sql` into
|
|
|
113
113
|
Canopy never hands out connection strings — Drizzle talks to your data through
|
|
114
114
|
the handles Canopy already provides.
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Both handles are query functions, so both fit Drizzle's **proxy driver** —
|
|
117
|
+
`drizzle-orm/sqlite-proxy` on a sqlite site, `drizzle-orm/pg-proxy` on a
|
|
118
|
+
postgres one. Match it to the `"dialect"` you pinned in `canopy.json`.
|
|
119
|
+
|
|
120
|
+
**In your backend**, `env.DB` takes the statement and its parameters:
|
|
118
121
|
|
|
119
122
|
```ts title="worker/index.ts"
|
|
120
|
-
import { drizzle } from "drizzle-orm/
|
|
123
|
+
import { drizzle } from "drizzle-orm/sqlite-proxy";
|
|
121
124
|
import { users } from "../src/schema.ts";
|
|
122
125
|
|
|
123
126
|
export default {
|
|
124
127
|
async fetch(request, env) {
|
|
125
|
-
const db = drizzle(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
const db = drizzle(async (sql, params, method) => {
|
|
129
|
+
const { rows } = await env.DB.query(sql, params);
|
|
130
|
+
const values = rows.map((row) => Object.values(row));
|
|
131
|
+
return { rows: method === "get" ? (values[0] ?? []) : values };
|
|
132
|
+
});
|
|
133
|
+
return Response.json(await db.select().from(users));
|
|
128
134
|
},
|
|
129
135
|
};
|
|
130
136
|
```
|
|
131
137
|
|
|
132
|
-
**In the browser (or anywhere the SDK runs)**, wrap `canopy.db.sql()`
|
|
133
|
-
|
|
138
|
+
**In the browser (or anywhere the SDK runs)**, wrap `canopy.db.sql()` the same
|
|
139
|
+
way:
|
|
134
140
|
|
|
135
141
|
```ts title="src/db.ts"
|
|
136
142
|
import { drizzle } from "drizzle-orm/sqlite-proxy";
|
|
@@ -143,9 +149,6 @@ export const db = drizzle(async (sql, params, method) => {
|
|
|
143
149
|
});
|
|
144
150
|
```
|
|
145
151
|
|
|
146
|
-
(On a postgres site use `drizzle-orm/pg-proxy` the same way; in a GCP project
|
|
147
|
-
worker, `env.DB.query(text, params)` fits `pg-proxy` directly.)
|
|
148
|
-
|
|
149
152
|
## Rehearsing on staging
|
|
150
153
|
|
|
151
154
|
The safest way to try a migration is on the project's staging channel — a
|
|
@@ -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.
|
|
@@ -13,8 +13,8 @@ That default is deliberate: a thousand idle vibecoded projects should not be
|
|
|
13
13
|
able to call arbitrary internet hosts. When your project genuinely needs an
|
|
14
14
|
external API, you allow that host yourself — no platform-team ticket.
|
|
15
15
|
|
|
16
|
-
> **Note:** Enforcement
|
|
17
|
-
>
|
|
16
|
+
> **Note:** Enforcement happens outside your code, on every site — so the rule holds even if your project
|
|
17
|
+
> tries to route around it. Same rule, same error, one allowlist.
|
|
18
18
|
|
|
19
19
|
## Allowing a host
|
|
20
20
|
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description: Canopy DMs you about your reports and projects — replies, new reports, blocked fetches, budget hits, failing schedules
|
|
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
|
|
@@ -69,8 +69,8 @@ then run `canopy dev --local --pull` for a throwaway sandbox seeded with it.
|
|
|
69
69
|
|
|
70
70
|
The SDK's document API (`canopy.db.collection(...)`) behaves the same
|
|
71
71
|
everywhere Canopy runs. Raw SQL via `canopy.db.sql()` passes straight through
|
|
72
|
-
to the underlying database
|
|
73
|
-
|
|
74
|
-
`canopy.db.dialect()
|
|
72
|
+
to the underlying database, so anything that database supports works. Which
|
|
73
|
+
one your site runs — SQLite or Postgres — your code can ask with
|
|
74
|
+
`canopy.db.dialect()`.
|
|
75
75
|
|
|
76
76
|
- [Working with project data](https://docs.canopy.website/guides/data) — Collection naming rules, dialects and raw SQL, dumps, and restore workflows in depth.
|
|
@@ -100,4 +100,4 @@ stable contract between the dashboard and your handler.
|
|
|
100
100
|
- **Failures are visible.** A run that throws records its error on the
|
|
101
101
|
schedule row in Settings, so silent breakage doesn't stay silent.
|
|
102
102
|
|
|
103
|
-
- [
|
|
103
|
+
- [How the platform serves your project](https://docs.canopy.website/developers/architecture) — Where your backend runs, and what it can reach — the same environment a scheduled run gets.
|
|
@@ -15,7 +15,7 @@ the CLI targets them.
|
|
|
15
15
|
| ----------- | --------------------- | ------------------------------------------------- |
|
|
16
16
|
| `main` | `*.canopy.website` | The real platform. Your projects live here. |
|
|
17
17
|
| `staging` | `*.thanarie.dev` | The platform team's testbed for platform changes. |
|
|
18
|
-
| `gcp-pilot` | `*.canopy.monks.zone` | A pilot
|
|
18
|
+
| `gcp-pilot` | `*.canopy.monks.zone` | A pilot installation, used for evaluation work. |
|
|
19
19
|
|
|
20
20
|
Sites are fully separate: a project on `main` doesn't exist on `staging`, and
|
|
21
21
|
signing in to one doesn't create anything on another. They can also differ in
|
|
@@ -0,0 +1,85 @@
|
|
|
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), come back and click install again.
|
|
60
|
+
|
|
61
|
+
The same button reappears whenever you change features in a way that needs
|
|
62
|
+
new permissions — the card shows a clear warning when saved changes are
|
|
63
|
+
waiting on a reinstall. Feature changes that don't widen permissions apply
|
|
64
|
+
immediately.
|
|
65
|
+
|
|
66
|
+
## Day to day
|
|
67
|
+
|
|
68
|
+
- **The status line** shows where the app is installed and when Slack last
|
|
69
|
+
delivered something — the quickest "is it wired up" check.
|
|
70
|
+
- **Enabled** is the kill switch: off, Slack traffic is accepted and
|
|
71
|
+
dropped, nothing reaches your code, and outbound sends are refused. The
|
|
72
|
+
app stays installed; flip it back anytime.
|
|
73
|
+
- **Remove Slack app** deletes the app from the workspace too — bot,
|
|
74
|
+
commands, history access, all of it.
|
|
75
|
+
|
|
76
|
+
Every message your project sends through the bot is counted per day, so a
|
|
77
|
+
runaway loop is visible.
|
|
78
|
+
|
|
79
|
+
## Local development
|
|
80
|
+
|
|
81
|
+
Slack needs a public URL to deliver to, so `canopy dev` can't receive
|
|
82
|
+
events or commands. Test inbound handling on a deployed project — a scratch
|
|
83
|
+
project with a scratch bot works well — and unit-test your handler directly.
|
|
84
|
+
Outbound (`canopy.slack.api`) works from `canopy dev` like any other SDK
|
|
85
|
+
call.
|
package/dist/docs/sdk-files.md
CHANGED
|
@@ -37,8 +37,9 @@ Every upload (and every `list` entry) is described by a `BlobObject`:
|
|
|
37
37
|
const blob = await canopy.blob.get("notes.txt");
|
|
38
38
|
const text = await blob?.text();
|
|
39
39
|
|
|
40
|
-
// getUrl(key) → Promise<{ url: string; expiresAt: string }>
|
|
40
|
+
// getUrl(key, options?) → Promise<{ url: string; expiresAt: string }>
|
|
41
41
|
const { url, expiresAt } = await canopy.blob.getUrl("avatars/arjan.png");
|
|
42
|
+
const long = await canopy.blob.getUrl("packs/brand.zip", { expiresIn: 3600 });
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
`get` returns the content as a standard `Blob`, typed with the MIME type it
|
|
@@ -52,6 +53,15 @@ links, media elements — and prefer `get` when your code needs the content.
|
|
|
52
53
|
Request a fresh URL when you render rather than storing one: it expires at
|
|
53
54
|
`expiresAt`.
|
|
54
55
|
|
|
56
|
+
A URL lasts **10 minutes** by default. Pass `expiresIn` (seconds) when a link
|
|
57
|
+
has to outlive that — a download a person starts later, or a sync tool that
|
|
58
|
+
works through a batch of files without coming back for a new link each time.
|
|
59
|
+
The platform caps a lifetime at **24 hours**; ask for more and you get 24
|
|
60
|
+
hours rather than an error, so trust `expiresAt` over what you asked for. A
|
|
61
|
+
lifetime that isn't a whole number of seconds above zero is rejected. Keep the
|
|
62
|
+
window as short as the job needs — anyone holding the URL can download the
|
|
63
|
+
file until it expires.
|
|
64
|
+
|
|
55
65
|
## Listing and deleting
|
|
56
66
|
|
|
57
67
|
```ts
|