@mediamonks/canopy-cli 0.7.43 → 0.7.44

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 (38) hide show
  1. package/dist/{browse-dyOThTyf.mjs → browse-B8-0dweq.mjs} +1 -1
  2. package/dist/dev-CUJKgaqJ.mjs +2 -0
  3. package/dist/{dev-adLLSY1F.mjs → dev-Cq98yblV.mjs} +95 -13
  4. package/dist/{dev-worker-DJekKSdb.mjs → dev-worker-CcYwQjxz.mjs} +23 -10
  5. package/dist/docs/agent-browsing.md +2 -2
  6. package/dist/docs/agent-tools.md +2 -2
  7. package/dist/docs/ci.md +1 -1
  8. package/dist/docs/cli-browse.md +1 -1
  9. package/dist/docs/cli-dev.md +1 -0
  10. package/dist/docs/cli-mcp.md +1 -1
  11. package/dist/docs/cli-members.md +41 -0
  12. package/dist/docs/cli-roles.md +39 -0
  13. package/dist/docs/cli-token.md +1 -1
  14. package/dist/docs/index.json +16 -1
  15. package/dist/docs/overview-faq.md +6 -4
  16. package/dist/docs/overview-how-it-works.md +3 -2
  17. package/dist/docs/overview-use-cases.md +1 -1
  18. package/dist/docs/overview-what-is-canopy.md +1 -1
  19. package/dist/docs/platform-client-access.md +1 -1
  20. package/dist/docs/platform-members-and-access.md +66 -42
  21. package/dist/docs/roles.md +100 -0
  22. package/dist/docs/sdk-auth.md +24 -10
  23. package/dist/docs/sdk-realtime.md +1 -1
  24. package/dist/docs/static-serving.md +1 -1
  25. package/dist/{docs-CaAgJWK3.mjs → docs-KmN6jD1S.mjs} +1 -1
  26. package/dist/{feedback-CMnG2Zvs.mjs → feedback-B5-95neo.mjs} +1 -1
  27. package/dist/index.mjs +10 -8
  28. package/dist/{local-2xtxtoUd.mjs → local-CIkL7tXP.mjs} +4 -3
  29. package/dist/{mcp-BVIU4j9N.mjs → mcp-DZdMNTWr.mjs} +2 -2
  30. package/dist/{package-XOtqboS1.mjs → package-Bvly8-rB.mjs} +1 -1
  31. package/dist/roles-BDZaN3vi.mjs +2 -0
  32. package/dist/roles-UM0Iiws6.mjs +379 -0
  33. package/dist/skill/canopy/SKILL.md +9 -0
  34. package/dist/{skill-Ct-aCOzg.mjs → skill-DRLniv-I.mjs} +1 -1
  35. package/dist/{src-CScXotBB.mjs → src-8vxOBOxW.mjs} +38 -3
  36. package/dist/{upgrade-DJogeXAF.mjs → upgrade-D_yrD7zp.mjs} +1 -1
  37. package/package.json +2 -2
  38. package/dist/dev-BmI5kwoG.mjs +0 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Members & access
3
- description: Everything on Canopy sits behind Monks sign-in. Collaborators manage a project; other Monks can view the live app — unless you tighten it.
3
+ description: Everything on Canopy sits behind Monks sign-in. Editors manage a project, viewers open it, and your app can hand out its own roles on top.
4
4
  ---
5
5
 
6
6
  Nothing on Canopy is reachable by the anonymous internet. Every project URL
@@ -23,52 +23,76 @@ nobody outside the team can touch its data or settings.
23
23
 
24
24
  ## Roles
25
25
 
26
- - **Owner** — the person who created the project. Owners govern it: they
27
- invite and remove collaborators, change access settings, and can delete
28
- the project.
29
- - **Collaborator** — invited teammates. They can do day-to-day work — deploy,
30
- roll back, browse and edit data, manage schedules and secrets — but not
31
- delete the project or manage its roster.
32
- - **Platform admin** — can manage any project from the dashboard. Admins keep
33
- governance access, but they do not silently bypass a collaborators-only
34
- live app (see below).
35
-
36
- ## Inviting collaborators
37
-
38
- Open the project's **Settings** tab and use the **Collaborators** card. Start
39
- typing a name or email: colleagues who have signed in to Canopy before show up
40
- as suggestions, and picking one adds them immediately.
41
-
42
- Someone who has never opened Canopy can be added too — type their full
43
- company email and choose **Invite by email**. They appear on the roster as
44
- **invited** and become a collaborator automatically the first time they sign
45
- in, whether that's opening the project's URL or running `canopy login`. Until
46
- then the invite grants nothing. You can withdraw a pending invite from the
47
- same card. Canopy doesn't send them a message, so let them know yourself.
48
-
49
- Only company addresses that can sign in to Canopy can be invited. Owners (and
50
- admins) can also remove people from the same card.
51
-
52
- ## Collaborators-only mode
53
-
54
- Sometimes "any Monk can look" is too open — payroll prototypes, sensitive
55
- client work, unfinished things you'd rather not demo by accident. Flip on
56
- **Settings → Who at Monks can open it → Restrict to collaborators** and the
57
- live app is limited to the project's roster:
58
-
59
- - Other Monks who open the URL get a clear "limited to its collaborators"
60
- page instead of the app.
61
- - The project still appears in the company list — it isn't hidden, just
26
+ Everyone on a project's roster has one Canopy role:
27
+
28
+ - **Owner**: the person who created the project. Owners govern it: they add
29
+ and remove members, decide who is an editor or a viewer, change access
30
+ settings, and can delete the project.
31
+ - **Editor**: does the day-to-day work. Editors deploy, roll back, browse and
32
+ edit data, and manage schedules, secrets and app roles. They can't delete
33
+ the project or change who is on it.
34
+ - **Viewer**: can open the app, and nothing else. Viewers don't see the
35
+ project's management pages. Adding someone as a viewer only makes a
36
+ difference when the project is limited to members, or when you give them
37
+ [app roles](#app-roles).
38
+ - **Platform admin**: can manage any project from the dashboard. Admins don't
39
+ quietly get past a members-only app (see below).
40
+
41
+ ## Adding members
42
+
43
+ Open the project's **Settings → Access** page and use the **Members** card.
44
+ Start typing a name or email: colleagues who have signed in to Canopy before
45
+ show up as suggestions. Pick **Editor** or **Viewer** and add them.
46
+
47
+ Someone who has never opened Canopy can be added too. Type their full company
48
+ email and choose **Invite by email**. They show up as **invited** and join
49
+ automatically the first time they sign in, whether that's opening the
50
+ project's URL or running `canopy login`. Until then the invite grants nothing.
51
+ Canopy doesn't send them a message, so let them know yourself.
52
+
53
+ The owner can switch anyone between editor and viewer, remove people, or
54
+ withdraw an invite from the same card. From a terminal,
55
+ [`canopy members`](https://docs.canopy.website/cli/members) does the same.
56
+
57
+ ## App roles
58
+
59
+ Many apps need their own permissions: "everyone can look, but only a few
60
+ people approve", or "only these people get in, each with their own role".
61
+ App roles cover that without a table of emails in your code.
62
+
63
+ Under **Settings → Access → App roles**, create the roles your app needs. Each
64
+ has a name and a slug, like `approver`. Then give members their roles from the
65
+ Members card. Your app reads them as `roles` on the signed-in user, from
66
+ [`auth.user()`](https://docs.canopy.website/sdk/auth) or `env.CALLER` in a backend worker.
67
+
68
+ One role is always the **default**. Everyone who can open the app has it,
69
+ client-password visitors included, so you don't have to hand it out. The first
70
+ role you create becomes the default, and you can make another one the default
71
+ at any time.
72
+
73
+ Canopy hands roles to your app but never checks them for you. Read
74
+ [Roles and permissions](https://docs.canopy.website/guides/roles) for how to use them safely.
75
+
76
+ ## Members-only mode
77
+
78
+ Sometimes "any Monk can look" is too open: payroll prototypes, sensitive
79
+ client work, unfinished things you'd rather not demo by accident. Turn on
80
+ **Settings → Access → Who at Monks can open it → Restrict to members** and the
81
+ live app is limited to the project's roster, editors and viewers alike:
82
+
83
+ - Other Monks who open the URL get a "limited to its members" page instead of
84
+ the app.
85
+ - The project still appears in the company list. It isn't hidden, just
62
86
  closed.
63
87
  - Platform admins can still manage the project in the dashboard, but they
64
- must add themselves as collaborators if they need to open the live app.
65
- - [Client password access](https://docs.canopy.website/platform/client-access), if you've enabled
66
- it, is unaffected — the two controls are independent.
88
+ must add themselves as members to open the live app.
89
+ - [Client password access](https://docs.canopy.website/platform/client-access), if you've turned it on,
90
+ is unaffected. The two controls are independent.
67
91
 
68
92
  Turning it off again is the same toggle.
69
93
 
70
- > **Note:** Collaborators-only gates the **live app**. Management access (settings, data, deploys) is always
71
- > members-only, regardless of this toggle.
94
+ > **Note:** Members-only gates the **live app**. Management pages (settings, data, deploys) are always for the
95
+ > owner and editors only, whatever this toggle says.
72
96
 
73
97
  ## Platform admins and the Admin page
74
98
 
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Roles and permissions
3
+ description: Give people different powers inside your app with app roles, and check them where it counts.
4
+ ---
5
+
6
+ Canopy decides who can open your app. App roles decide what each person can
7
+ do once they're in. You create the roles, give them to people, and your code
8
+ reads them. Canopy never checks them for you.
9
+
10
+ ## Two kinds of role
11
+
12
+ - **Canopy roles** (owner, editor, viewer) are about the project itself: who
13
+ can deploy, see the data, change settings. Viewers can only open the app.
14
+ See [Members and access](https://docs.canopy.website/platform/members-and-access).
15
+ - **App roles** are your app's own labels, like `approver` or `admin`. They
16
+ mean whatever your code makes them mean.
17
+
18
+ The two are independent. A viewer can be your app's `admin`, and an editor
19
+ can have no app roles at all.
20
+
21
+ ## Set roles up
22
+
23
+ In the dashboard, open **Settings → Access**:
24
+
25
+ 1. Under **App roles**, add a role. Give it a name ("Approver") and check the
26
+ slug (`approver`). The slug is what your code checks, and it can't change
27
+ later. The name can.
28
+ 2. Under **Members**, open the tag button next to someone and tick their
29
+ roles. To give roles to a colleague who only needs to use the app, add
30
+ them as a **Viewer** first.
31
+
32
+ Or from a terminal, which is also how an agent sets them up:
33
+
34
+ ```bash
35
+ canopy roles add "Approver"
36
+ canopy members add jane@monks.com --viewer --app-role approver
37
+ canopy roles list
38
+ ```
39
+
40
+ ## The default role
41
+
42
+ The first role you create is the **default**. Everyone who can open the app
43
+ has it: members, any other Monk on an open project, and client-password
44
+ visitors. That lets your code treat everyone the same way ("everyone is at
45
+ least a `user`") without listing every person. Make another role the default
46
+ whenever you like; everyone's roles change with it. The default role can't be
47
+ deleted while other roles exist.
48
+
49
+ ## Read roles in your code
50
+
51
+ `roles` is a list of slugs, the default first:
52
+
53
+ ```ts
54
+ const user = await canopy.auth.user();
55
+ user.roles; // ["user", "approver"]
56
+ ```
57
+
58
+ In a backend worker, `env.CALLER.roles` holds the same list without a round
59
+ trip, and so does `env.USER.roles` for a signed-in Monk.
60
+
61
+ ## Check roles where it counts
62
+
63
+ A check that only runs in the browser hides buttons. It doesn't protect
64
+ anything, because a visitor can call your data and routes directly. Put the
65
+ check that matters in your backend worker:
66
+
67
+ ```ts
68
+ app.post("/api/approve/:id", async (c) => {
69
+ if (!c.env.CALLER?.roles?.includes("approver")) {
70
+ return c.json({ error: "approvers only" }, 403);
71
+ }
72
+ // only approvers get here
73
+ });
74
+ ```
75
+
76
+ Keep data that only some roles may see in tables or collections whose names
77
+ start with `private_`. Pages can't reach those, only your worker can. See
78
+ [Database](https://docs.canopy.website/sdk/database).
79
+
80
+ ## Try another role locally
81
+
82
+ `canopy dev` runs with your own roles. To see the app the way someone else
83
+ does, pass `--as-role`:
84
+
85
+ ```bash
86
+ canopy dev --as-role reviewer
87
+ ```
88
+
89
+ You keep the project's default role, as a real visitor would, and get
90
+ `reviewer` instead of your own roles. It only changes your local session.
91
+ Nothing changes on the platform.
92
+
93
+ ## Good to know
94
+
95
+ - Role changes reach your app within a few seconds.
96
+ - A backend deployed before app roles existed sees no `roles` on
97
+ `env.CALLER` until you redeploy it. `auth.user()` has them right away.
98
+ - Client-password visitors all share one password, so they only ever get the
99
+ default role.
100
+ - Scheduled runs have no caller and no roles.
@@ -15,15 +15,16 @@ Returns the authenticated user for the current session as a `CanopyUser`:
15
15
 
16
16
  ```ts
17
17
  const user = await canopy.auth.user();
18
- // { id: "…", email: "ada@monks.com", name: "Ada Lovelace", kind: "monk" }
18
+ // { id: "…", email: "ada@monks.com", name: "Ada Lovelace", kind: "monk", roles: ["user"] }
19
19
  ```
20
20
 
21
- | Field | Type | Meaning |
22
- | ------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
23
- | `id` | `string` | Stable subject from OneLogin — safe to key user data on. |
24
- | `email` | `string` | The user's email address. |
25
- | `name` | `string` | Display name. |
26
- | `kind` | `"monk" \| "external"` | `"monk"` = signed in through OneLogin; `"external"` = a visitor who got in with the project's client password instead. |
21
+ | Field | Type | Meaning |
22
+ | ------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `id` | `string` | Stable subject from OneLogin — safe to key user data on. |
24
+ | `email` | `string` | The user's email address. |
25
+ | `name` | `string` | Display name. |
26
+ | `kind` | `"monk" \| "external"` | `"monk"` = signed in through OneLogin; `"external"` = a visitor who got in with the project's client password instead. |
27
+ | `roles` | `string[]` | The project's [app roles](https://docs.canopy.website/guides/roles) this person has: the default role first, then their own. Empty when the project has none. |
27
28
 
28
29
  `kind: "external"` sessions come from client-password access — clients viewing
29
30
  a shared project without a Monks account. Branch on it when externals should
@@ -46,8 +47,8 @@ if (user.kind === "external") {
46
47
  ### In a backend worker
47
48
 
48
49
  A worker route already knows who is calling, without asking: `env.CALLER`
49
- holds the same `id`, `email`, `name` and `kind` that `auth.user()` returns,
50
- plus `agent`, which is `true` when an AI agent is acting for a Monk.
50
+ holds the same `id`, `email`, `name`, `kind` and `roles` that `auth.user()`
51
+ returns, plus `agent`, which is `true` when an AI agent is acting for a Monk.
51
52
 
52
53
  ```ts
53
54
  import type { CanopyCaller } from "@mediamonks/canopy";
@@ -67,7 +68,20 @@ A client-password visitor has no email address and is named `"Client"`. Its
67
68
  `id` changes with every password unlock, so don't key data on it that has to
68
69
  last.
69
70
 
70
- A backend gets `env.CALLER` from its next deploy. Until it is redeployed,
71
+ This is the place to check app roles, because a visitor can't change what
72
+ your worker sees:
73
+
74
+ ```ts
75
+ app.post("/api/approve/:id", (c) => {
76
+ if (!c.env.CALLER?.roles?.includes("approver")) {
77
+ return c.json({ error: "approvers only" }, 403);
78
+ }
79
+ // …
80
+ });
81
+ ```
82
+
83
+ A backend gets `env.CALLER` from its next deploy, and `roles` on it from its
84
+ first deploy after app roles shipped. Until it is redeployed,
71
85
  `auth.user()` through the `CANOPY` binding gives the same answer:
72
86
 
73
87
  ```ts
@@ -121,7 +121,7 @@ right away and `onOpen` runs, so the page catches up from the database. A
121
121
  quick switch away and back keeps the connection.
122
122
 
123
123
  Every connection also renews itself about every quarter of an hour, and
124
- whenever access to the project changes (the client password, collaborators
124
+ whenever access to the project changes (the client password, members
125
125
  only, someone removed). Each renewal runs `onOpen` again, which is what
126
126
  keeps the reload-on-open pattern correct.
127
127
 
@@ -17,7 +17,7 @@ Every request passes the sign-in wall before a single byte of your project is
17
17
  read. A visitor arrives with a company login, or — if you've turned on
18
18
  [client access](https://docs.canopy.website/platform/client-access) — the project's password. There is no
19
19
  public URL, no "unlisted" link, and no way to opt a single file out. A project
20
- is readable by any signed-in colleague unless you set it to collaborators only,
20
+ is readable by any signed-in colleague unless you set it to members only,
21
21
  in [members and access](https://docs.canopy.website/platform/members-and-access).
22
22
 
23
23
  ## Deep links work — routing is yours
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-XOtqboS1.mjs";
1
+ import { t as version } from "./package-Bvly8-rB.mjs";
2
2
  import { r as siteConfig } from "./site-0i7wAJqg.mjs";
3
3
  import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
4
4
  import { n as agentHeaders } from "./api-Bl6SG8Oq.mjs";
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-XOtqboS1.mjs";
1
+ import { t as version } from "./package-Bvly8-rB.mjs";
2
2
  import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
3
3
  import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-Bl6SG8Oq.mjs";
4
4
  import { r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-XOtqboS1.mjs";
2
+ import { t as version } from "./package-Bvly8-rB.mjs";
3
3
  import { i as siteHeaderLine, n as setInvocationProfile } from "./site-0i7wAJqg.mjs";
4
4
  import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
5
5
  import { defineCommand, runMain } from "citty";
@@ -71,7 +71,7 @@ const main = defineCommand({
71
71
  projects: () => import("./projects-BRG2HnBs.mjs").then((m) => m.projectsCommand),
72
72
  delete: () => import("./projects-BRG2HnBs.mjs").then((m) => m.deleteCommand),
73
73
  rename: () => import("./rename-BGKa25Rq.mjs").then((m) => m.renameCommand),
74
- dev: () => import("./dev-BmI5kwoG.mjs").then((m) => m.devCommand),
74
+ dev: () => import("./dev-CUJKgaqJ.mjs").then((m) => m.devCommand),
75
75
  deploy: () => import("./deploy-ExrIeX_m.mjs").then((m) => m.deployCommand),
76
76
  promote: () => import("./deploy-ExrIeX_m.mjs").then((m) => m.promoteCommand),
77
77
  staging: () => import("./staging-_2Js0Fkc.mjs").then((m) => m.stagingCommand),
@@ -80,18 +80,20 @@ const main = defineCommand({
80
80
  token: () => import("./token-7vSNuz-X.mjs").then((m) => m.tokenCommand),
81
81
  egress: () => import("./egress-C2amvqmE.mjs").then((m) => m.egressCommand),
82
82
  secrets: () => import("./secrets-BjbIi7Gp.mjs").then((m) => m.secretsCommand),
83
+ roles: () => import("./roles-BDZaN3vi.mjs").then((m) => m.rolesCommand),
84
+ members: () => import("./roles-BDZaN3vi.mjs").then((m) => m.membersCommand),
83
85
  schedule: () => import("./schedule-BcqBspn3.mjs").then((m) => m.scheduleCommand),
84
- feedback: () => import("./feedback-CMnG2Zvs.mjs").then((m) => m.feedbackCommand),
86
+ feedback: () => import("./feedback-B5-95neo.mjs").then((m) => m.feedbackCommand),
85
87
  db: () => import("./db-BJJGydgL.mjs").then((m) => m.dbCommand),
86
88
  usage: () => import("./usage-DGGmTwjJ.mjs").then((m) => m.usageCommand),
87
89
  ai: () => import("./ai-FBHTSCLZ.mjs").then((m) => m.aiCommand),
88
90
  open: () => import("./open-Bpz4C2Sr.mjs").then((m) => m.openCommand),
89
- browse: () => import("./browse-dyOThTyf.mjs").then((m) => m.browseCommand),
90
- docs: () => import("./docs-CaAgJWK3.mjs").then((m) => m.docsCommand),
91
- skill: () => import("./skill-Ct-aCOzg.mjs").then((m) => m.skillCommand),
92
- upgrade: () => import("./upgrade-DJogeXAF.mjs").then((m) => m.upgradeCommand),
91
+ browse: () => import("./browse-B8-0dweq.mjs").then((m) => m.browseCommand),
92
+ docs: () => import("./docs-KmN6jD1S.mjs").then((m) => m.docsCommand),
93
+ skill: () => import("./skill-DRLniv-I.mjs").then((m) => m.skillCommand),
94
+ upgrade: () => import("./upgrade-D_yrD7zp.mjs").then((m) => m.upgradeCommand),
93
95
  logs: () => import("./logs-BnrNZvLc.mjs").then((m) => m.logsCommand),
94
- mcp: () => import("./mcp-BVIU4j9N.mjs").then((m) => m.mcpCommand),
96
+ mcp: () => import("./mcp-DZdMNTWr.mjs").then((m) => m.mcpCommand),
95
97
  repo: () => import("./repo-DmFs6H9m.mjs").then((m) => m.repoCommand),
96
98
  template: () => import("./project-template-715H_lnu.mjs").then((m) => m.templateCommand)
97
99
  }
@@ -1,6 +1,6 @@
1
1
  import { r as apiDataRequest } from "./api-Bl6SG8Oq.mjs";
2
- import { a as realtimeRouter, c as META_COLUMNS, l as blobRouter, n as WORKER_ATTEST_HEADER, o as dbRouter, s as hasPrivateNames } from "./src-CScXotBB.mjs";
3
- import { a as rewriteApiPath } from "./dev-adLLSY1F.mjs";
2
+ import { a as realtimeRouter, c as META_COLUMNS, l as blobRouter, n as WORKER_ATTEST_HEADER, o as dbRouter, s as hasPrivateNames } from "./src-8vxOBOxW.mjs";
3
+ import { a as rewriteApiPath } from "./dev-Cq98yblV.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join, relative, sep } from "node:path";
@@ -942,7 +942,8 @@ async function createSandbox(options) {
942
942
  id: "local-dev",
943
943
  email: options.email ?? "dev@localhost",
944
944
  name: "Local Dev",
945
- kind: "monk"
945
+ kind: "monk",
946
+ roles: options.roles ?? []
946
947
  }));
947
948
  app.all("/data/v1/ai/*", (c) => c.json({
948
949
  error: "not_available_in_local_dev",
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-XOtqboS1.mjs";
1
+ import { t as version } from "./package-Bvly8-rB.mjs";
2
2
  import { c as projectUrl, d as stagingUrl, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
3
3
  import { a as apiRequest, d as noticeRenamesOnly, l as currentSlug, t as ApiError } from "./api-Bl6SG8Oq.mjs";
4
4
  import { t as liveDevServer } from "./dev-server-file-Dk9pk4AR.mjs";
@@ -171,7 +171,7 @@ function explainRefusal(status, body, project) {
171
171
  case "agent_token_required":
172
172
  case "agent_token_invalid": return "Canopy refused the sign-in for this agent session — run `canopy login` and restart the MCP server.";
173
173
  case "agent_token_scope": return `The agent credential does not cover ${project} on this channel.`;
174
- case "members_only": return `${project} is limited to its collaborators — ask the owner to add you.`;
174
+ case "members_only": return `${project} is limited to its members — ask the owner to add you.`;
175
175
  case "project_not_found": return `There is no project ${project} on this site — check the slug and --profile.`;
176
176
  case "project_paused": return `${project} is paused.`;
177
177
  case "project_moved": return `${project} has moved${body.moved_to ? ` to ${body.moved_to}` : ""}.`;
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.43";
2
+ var version = "0.7.44";
3
3
  //#endregion
4
4
  export { version as t };
@@ -0,0 +1,2 @@
1
+ import { i as membersCommand, o as rolesCommand } from "./roles-UM0Iiws6.mjs";
2
+ export { membersCommand, rolesCommand };