@mediamonks/canopy-cli 0.7.42 → 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.
- package/dist/{ai-DpQ1nLka.mjs → ai-FBHTSCLZ.mjs} +2 -2
- package/dist/{api-Do9c8tZ_.mjs → api-Bl6SG8Oq.mjs} +2 -2
- package/dist/{browse-Dp5HytqK.mjs → browse-B8-0dweq.mjs} +2 -2
- package/dist/{db-BNsX7sXo.mjs → db-BJJGydgL.mjs} +3 -3
- package/dist/{deploy-CXMLABvH.mjs → deploy-ExrIeX_m.mjs} +22 -3
- package/dist/dev-CUJKgaqJ.mjs +2 -0
- package/dist/{dev-CPR-9slK.mjs → dev-Cq98yblV.mjs} +98 -16
- package/dist/{dev-worker-D78leKBJ.mjs → dev-worker-CcYwQjxz.mjs} +23 -10
- package/dist/docs/agent-browsing.md +2 -2
- package/dist/docs/agent-tools.md +2 -2
- package/dist/docs/ci.md +1 -1
- package/dist/docs/cli-browse.md +1 -1
- package/dist/docs/cli-dev.md +1 -0
- package/dist/docs/cli-mcp.md +1 -1
- package/dist/docs/cli-members.md +41 -0
- package/dist/docs/cli-roles.md +39 -0
- package/dist/docs/cli-token.md +1 -1
- package/dist/docs/developers-architecture.md +12 -0
- package/dist/docs/index.json +16 -1
- package/dist/docs/overview-faq.md +6 -4
- package/dist/docs/overview-how-it-works.md +3 -2
- package/dist/docs/overview-use-cases.md +1 -1
- package/dist/docs/overview-what-is-canopy.md +1 -1
- package/dist/docs/platform-client-access.md +1 -1
- package/dist/docs/platform-egress.md +14 -7
- package/dist/docs/platform-members-and-access.md +66 -42
- package/dist/docs/roles.md +100 -0
- package/dist/docs/sdk-auth.md +24 -10
- package/dist/docs/sdk-realtime.md +1 -1
- package/dist/docs/static-serving.md +14 -5
- package/dist/{docs-CkJGcCpR.mjs → docs-KmN6jD1S.mjs} +3 -3
- package/dist/{egress-D6qyaaAn.mjs → egress-C2amvqmE.mjs} +2 -2
- package/dist/{feedback-DMgUUl0V.mjs → feedback-B5-95neo.mjs} +3 -3
- package/dist/index.mjs +34 -32
- package/dist/{local-w2wXfE1d.mjs → local-CIkL7tXP.mjs} +5 -4
- package/dist/{login-CeNvHLUv.mjs → login-CREcZfLH.mjs} +2 -2
- package/dist/{logs-BzgJfWNP.mjs → logs-BnrNZvLc.mjs} +2 -2
- package/dist/{mcp-DFLaXiY_.mjs → mcp-DZdMNTWr.mjs} +3 -3
- package/dist/{migrations-Bsu4quya.mjs → migrations-D8NqjC5I.mjs} +1 -1
- package/dist/{open-D71Ms1nG.mjs → open-Bpz4C2Sr.mjs} +1 -1
- package/dist/{package-CVDpzGgA.mjs → package-Bvly8-rB.mjs} +1 -1
- package/dist/project-template-715H_lnu.mjs +2 -0
- package/dist/{project-template-BXfHpsI8.mjs → project-template-Cdr1uwwE.mjs} +2 -2
- package/dist/{projects-BWTIk80a.mjs → projects-BRG2HnBs.mjs} +4 -4
- package/dist/{rename-BdIFrqvG.mjs → rename-BGKa25Rq.mjs} +2 -2
- package/dist/{repo-LSOxnmUk.mjs → repo-CwT2RDoW.mjs} +2 -2
- package/dist/repo-DmFs6H9m.mjs +2 -0
- package/dist/roles-BDZaN3vi.mjs +2 -0
- package/dist/roles-UM0Iiws6.mjs +379 -0
- package/dist/{schedule-DpMIpCGC.mjs → schedule-BcqBspn3.mjs} +2 -2
- package/dist/{secrets-BVx8L0Gj.mjs → secrets-BjbIi7Gp.mjs} +2 -2
- package/dist/skill/canopy/SKILL.md +9 -0
- package/dist/{skill-D58yV6K8.mjs → skill-DRLniv-I.mjs} +2 -2
- package/dist/{src-RUf3Gfuh.mjs → src-8vxOBOxW.mjs} +763 -2
- package/dist/{staging-ynPlMiHW.mjs → staging-_2Js0Fkc.mjs} +2 -2
- package/dist/{token-CxiPrKn_.mjs → token-7vSNuz-X.mjs} +2 -2
- package/dist/{ui-CDUJq-X-.mjs → ui-CLnICOne.mjs} +1 -1
- package/dist/{upgrade-Dll5rhOQ.mjs → upgrade-D_yrD7zp.mjs} +2 -2
- package/dist/{usage-BtPsb7Iu.mjs → usage-DGGmTwjJ.mjs} +2 -2
- package/package.json +2 -2
- package/dist/dev-Dj3wFKHe.mjs +0 -2
- package/dist/project-template-BbmSmraT.mjs +0 -2
- package/dist/repo-JBKJqyoS.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as projectUrl, d as stagingUrl, l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, f as withContext } from "./api-
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
/** Plain words for the API's template errors. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { c as projectUrl, f as tryReadCanopyJson, r as siteConfig } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, f as withContext } from "./api-
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
4
|
-
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-
|
|
5
|
-
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
4
|
+
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-Cdr1uwwE.mjs";
|
|
5
|
+
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-CwT2RDoW.mjs";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as projectUrl, l as resolveProject, o as projectDirArgs, u as rewriteCanopyJsonSlug } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, f as withContext } from "./api-
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/rename.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, r as siteConfig } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, d as noticeRenamesOnly, f as withContext, n as agentHeaders, u as followRename } from "./api-
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, d as noticeRenamesOnly, f as withContext, n as agentHeaders, u as followRename } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
|
|
4
4
|
import { a as parseCredentialInput, i as helperCommand, o as runCredentialHelper, r as fileTokenCache, t as RepoCredentialError } from "./repo-credential-CNDRM-tG.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { existsSync, realpathSync } from "node:fs";
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
//#region src/roles.ts
|
|
6
|
+
/**
|
|
7
|
+
* canopy roles / canopy members — a project's app roles and its roster from
|
|
8
|
+
* the terminal (OpenSpec project-roles), so an agent can look up the real
|
|
9
|
+
* role slugs before it writes `roles.includes(...)`, and set people up
|
|
10
|
+
* without the dashboard.
|
|
11
|
+
*
|
|
12
|
+
* Canopy roles (owner / editor / viewer) decide who may manage the project
|
|
13
|
+
* and, when it is members-only, who may open it. App roles are the project's
|
|
14
|
+
* own labels, delivered to the app as `roles` and never enforced by Canopy.
|
|
15
|
+
*/
|
|
16
|
+
const ROLE_FIXES = {
|
|
17
|
+
not_found: "The project or role doesn't exist, or you can't manage this project — only its owner and editors can.",
|
|
18
|
+
invalid_role_slug: "Slugs are 1–32 lowercase letters, digits and dashes, starting with a letter. Pass one with --slug.",
|
|
19
|
+
role_exists: "That slug is taken on this project — `canopy roles list` shows them.",
|
|
20
|
+
too_many_roles: "A project has at most 20 app roles — remove one first.",
|
|
21
|
+
default_role: "Make another role the default first (`canopy roles default <slug>`), then remove this one.",
|
|
22
|
+
unknown_role: "Use a slug from `canopy roles list`, or create it with `canopy roles add`."
|
|
23
|
+
};
|
|
24
|
+
/** One line per role, default marked — what `canopy roles list` prints. */
|
|
25
|
+
function formatRoles(roles) {
|
|
26
|
+
if (roles.length === 0) return [];
|
|
27
|
+
const width = Math.max(...roles.map((r) => r.slug.length));
|
|
28
|
+
return roles.map((r) => `${r.slug.padEnd(width)} ${r.name}${r.default ? " (default)" : ""}`);
|
|
29
|
+
}
|
|
30
|
+
/** One line per person on the roster — what `canopy members list` prints. */
|
|
31
|
+
function formatRoster(members, invites) {
|
|
32
|
+
const rows = [...members.map((m) => ({
|
|
33
|
+
who: m.email,
|
|
34
|
+
role: m.role,
|
|
35
|
+
apps: m.app_roles,
|
|
36
|
+
note: ""
|
|
37
|
+
})), ...invites.map((i) => ({
|
|
38
|
+
who: i.email,
|
|
39
|
+
role: i.role,
|
|
40
|
+
apps: i.app_roles,
|
|
41
|
+
note: " (invited, not signed in yet)"
|
|
42
|
+
}))];
|
|
43
|
+
if (rows.length === 0) return [];
|
|
44
|
+
const whoWidth = Math.max(...rows.map((r) => r.who.length));
|
|
45
|
+
const roleWidth = Math.max(...rows.map((r) => r.role.length));
|
|
46
|
+
return rows.map((r) => `${r.who.padEnd(whoWidth)} ${r.role.padEnd(roleWidth)} ${r.apps.length > 0 ? r.apps.join(", ") : "-"}${r.note}`);
|
|
47
|
+
}
|
|
48
|
+
/** `--app-role a --app-role b,c` → ["a", "b", "c"]; undefined when the flag is absent. */
|
|
49
|
+
function parseAppRoleFlag(value) {
|
|
50
|
+
if (value === void 0) return void 0;
|
|
51
|
+
const list = (Array.isArray(value) ? value : [value]).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
52
|
+
return [...new Set(list)];
|
|
53
|
+
}
|
|
54
|
+
/** The Canopy role named by `--editor` / `--viewer`, refusing both at once. */
|
|
55
|
+
function canopyRoleFlag(args) {
|
|
56
|
+
if (args.editor && args.viewer) throw new Error("Pass --editor or --viewer, not both.");
|
|
57
|
+
if (args.viewer) return "viewer";
|
|
58
|
+
if (args.editor) return "editor";
|
|
59
|
+
}
|
|
60
|
+
const appRoleArg = { "app-role": {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "An app role slug to give them (repeatable, or comma-separated)"
|
|
63
|
+
} };
|
|
64
|
+
/** citty hands a repeated string flag over as an array at runtime. */
|
|
65
|
+
const appRolesOf = (args) => parseAppRoleFlag(args["app-role"]);
|
|
66
|
+
const rolesCommand = defineCommand({
|
|
67
|
+
meta: {
|
|
68
|
+
name: "roles",
|
|
69
|
+
description: "Manage the project's app roles (list / add / rename / default / remove)"
|
|
70
|
+
},
|
|
71
|
+
subCommands: {
|
|
72
|
+
list: defineCommand({
|
|
73
|
+
meta: {
|
|
74
|
+
name: "list",
|
|
75
|
+
description: "List the project's app roles (the default is marked)"
|
|
76
|
+
},
|
|
77
|
+
args: {
|
|
78
|
+
...projectDirArgs,
|
|
79
|
+
json: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
default: false,
|
|
82
|
+
description: "Print the roles as JSON"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
run: reportErrors("roles list", async ({ args }) => {
|
|
86
|
+
const { slug } = resolveProject(args);
|
|
87
|
+
const { roles } = await withContext(apiRequest("GET", `/projects/${slug}/roles`), {
|
|
88
|
+
action: `list app roles of ${slug}`,
|
|
89
|
+
fixes: ROLE_FIXES
|
|
90
|
+
});
|
|
91
|
+
if (args.json) {
|
|
92
|
+
console.log(JSON.stringify({ roles }, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (roles.length === 0) {
|
|
96
|
+
console.log(`${slug} has no app roles — add one with \`canopy roles add "Approver"\`.`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const line of formatRoles(roles)) console.log(line);
|
|
100
|
+
})
|
|
101
|
+
}),
|
|
102
|
+
add: defineCommand({
|
|
103
|
+
meta: {
|
|
104
|
+
name: "add",
|
|
105
|
+
description: "Create an app role (the first one becomes the default)"
|
|
106
|
+
},
|
|
107
|
+
args: {
|
|
108
|
+
name: {
|
|
109
|
+
type: "positional",
|
|
110
|
+
description: "Display name, e.g. \"Approver\"",
|
|
111
|
+
required: true
|
|
112
|
+
},
|
|
113
|
+
slug: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "The slug your code checks (default: derived from the name; never changes)"
|
|
116
|
+
},
|
|
117
|
+
...projectDirArgs
|
|
118
|
+
},
|
|
119
|
+
run: reportErrors("roles add", async ({ args }) => {
|
|
120
|
+
const { slug } = resolveProject(args);
|
|
121
|
+
const { role } = await withContext(apiRequest("POST", `/projects/${slug}/roles`, {
|
|
122
|
+
name: args.name,
|
|
123
|
+
...args.slug && { slug: args.slug }
|
|
124
|
+
}), {
|
|
125
|
+
action: `add app role "${args.name}" to ${slug}`,
|
|
126
|
+
fixes: ROLE_FIXES
|
|
127
|
+
});
|
|
128
|
+
console.log(`Added ${role.slug} ("${role.name}")${role.default ? " — the default role, so every visitor has it" : ""}.`);
|
|
129
|
+
})
|
|
130
|
+
}),
|
|
131
|
+
rename: defineCommand({
|
|
132
|
+
meta: {
|
|
133
|
+
name: "rename",
|
|
134
|
+
description: "Change an app role's display name (the slug stays)"
|
|
135
|
+
},
|
|
136
|
+
args: {
|
|
137
|
+
role: {
|
|
138
|
+
type: "positional",
|
|
139
|
+
description: "Role slug",
|
|
140
|
+
required: true
|
|
141
|
+
},
|
|
142
|
+
name: {
|
|
143
|
+
type: "positional",
|
|
144
|
+
description: "New display name",
|
|
145
|
+
required: true
|
|
146
|
+
},
|
|
147
|
+
...projectDirArgs
|
|
148
|
+
},
|
|
149
|
+
run: reportErrors("roles rename", async ({ args }) => {
|
|
150
|
+
const { slug } = resolveProject(args);
|
|
151
|
+
await withContext(apiRequest("PATCH", `/projects/${slug}/roles/${args.role}`, { name: args.name }), {
|
|
152
|
+
action: `rename app role ${args.role} on ${slug}`,
|
|
153
|
+
fixes: ROLE_FIXES
|
|
154
|
+
});
|
|
155
|
+
console.log(`Renamed ${args.role} to "${args.name}".`);
|
|
156
|
+
})
|
|
157
|
+
}),
|
|
158
|
+
default: defineCommand({
|
|
159
|
+
meta: {
|
|
160
|
+
name: "default",
|
|
161
|
+
description: "Make an app role the default — every visitor gets it on top of their own"
|
|
162
|
+
},
|
|
163
|
+
args: {
|
|
164
|
+
role: {
|
|
165
|
+
type: "positional",
|
|
166
|
+
description: "Role slug",
|
|
167
|
+
required: true
|
|
168
|
+
},
|
|
169
|
+
...projectDirArgs
|
|
170
|
+
},
|
|
171
|
+
run: reportErrors("roles default", async ({ args }) => {
|
|
172
|
+
const { slug } = resolveProject(args);
|
|
173
|
+
await withContext(apiRequest("PATCH", `/projects/${slug}/roles/${args.role}`, { default: true }), {
|
|
174
|
+
action: `make ${args.role} the default on ${slug}`,
|
|
175
|
+
fixes: ROLE_FIXES
|
|
176
|
+
});
|
|
177
|
+
console.log(`${args.role} is now the default role on ${slug}.`);
|
|
178
|
+
})
|
|
179
|
+
}),
|
|
180
|
+
remove: defineCommand({
|
|
181
|
+
meta: {
|
|
182
|
+
name: "remove",
|
|
183
|
+
description: "Delete an app role and take it away from everyone"
|
|
184
|
+
},
|
|
185
|
+
args: {
|
|
186
|
+
role: {
|
|
187
|
+
type: "positional",
|
|
188
|
+
description: "Role slug",
|
|
189
|
+
required: true
|
|
190
|
+
},
|
|
191
|
+
...projectDirArgs
|
|
192
|
+
},
|
|
193
|
+
run: reportErrors("roles remove", async ({ args }) => {
|
|
194
|
+
const { slug } = resolveProject(args);
|
|
195
|
+
await withContext(apiRequest("DELETE", `/projects/${slug}/roles/${args.role}`), {
|
|
196
|
+
action: `remove app role ${args.role} from ${slug}`,
|
|
197
|
+
fixes: ROLE_FIXES
|
|
198
|
+
});
|
|
199
|
+
console.log(`Removed ${args.role} from ${slug}.`);
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const MEMBER_FIXES = {
|
|
205
|
+
...ROLE_FIXES,
|
|
206
|
+
not_found: "No such person on the project, or you can't manage it — only the owner adds and removes people.",
|
|
207
|
+
forbidden: "Only the project's owner changes who is an editor or a viewer.",
|
|
208
|
+
already_member: "They're already on the project — change them with `canopy members set`.",
|
|
209
|
+
already_invited: "They already have a pending invite — change it with `canopy members set`.",
|
|
210
|
+
email_domain_not_allowed: "Only colleagues who can sign in to Canopy can be added.",
|
|
211
|
+
cannot_change_owner: "The owner's role is fixed.",
|
|
212
|
+
cannot_remove_owner: "The owner can't be removed — delete the project instead."
|
|
213
|
+
};
|
|
214
|
+
const roster = (slug) => withContext(apiRequest("GET", `/projects/${slug}/members`), {
|
|
215
|
+
action: `read the members of ${slug}`,
|
|
216
|
+
fixes: MEMBER_FIXES
|
|
217
|
+
});
|
|
218
|
+
const membersCommand = defineCommand({
|
|
219
|
+
meta: {
|
|
220
|
+
name: "members",
|
|
221
|
+
description: "Manage who is on the project and their roles (list / add / set / remove)"
|
|
222
|
+
},
|
|
223
|
+
subCommands: {
|
|
224
|
+
list: defineCommand({
|
|
225
|
+
meta: {
|
|
226
|
+
name: "list",
|
|
227
|
+
description: "List members and pending invites with their roles"
|
|
228
|
+
},
|
|
229
|
+
args: {
|
|
230
|
+
...projectDirArgs,
|
|
231
|
+
json: {
|
|
232
|
+
type: "boolean",
|
|
233
|
+
default: false,
|
|
234
|
+
description: "Print the roster as JSON"
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
run: reportErrors("members list", async ({ args }) => {
|
|
238
|
+
const { slug } = resolveProject(args);
|
|
239
|
+
const { members, invites } = await roster(slug);
|
|
240
|
+
if (args.json) {
|
|
241
|
+
console.log(JSON.stringify({
|
|
242
|
+
members,
|
|
243
|
+
invites
|
|
244
|
+
}, null, 2));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for (const line of formatRoster(members, invites)) console.log(line);
|
|
248
|
+
})
|
|
249
|
+
}),
|
|
250
|
+
add: defineCommand({
|
|
251
|
+
meta: {
|
|
252
|
+
name: "add",
|
|
253
|
+
description: "Add someone as an editor (default) or viewer; invites them if they haven't signed in yet"
|
|
254
|
+
},
|
|
255
|
+
args: {
|
|
256
|
+
email: {
|
|
257
|
+
type: "positional",
|
|
258
|
+
description: "Their company email",
|
|
259
|
+
required: true
|
|
260
|
+
},
|
|
261
|
+
viewer: {
|
|
262
|
+
type: "boolean",
|
|
263
|
+
description: "May open the app, not manage the project"
|
|
264
|
+
},
|
|
265
|
+
editor: {
|
|
266
|
+
type: "boolean",
|
|
267
|
+
description: "May manage the project (the default)"
|
|
268
|
+
},
|
|
269
|
+
...appRoleArg,
|
|
270
|
+
...projectDirArgs
|
|
271
|
+
},
|
|
272
|
+
run: reportErrors("members add", async ({ args }) => {
|
|
273
|
+
const { slug } = resolveProject(args);
|
|
274
|
+
const role = canopyRoleFlag(args) ?? "editor";
|
|
275
|
+
const appRoles = appRolesOf(args);
|
|
276
|
+
const res = await withContext(apiRequest("POST", `/projects/${slug}/members`, {
|
|
277
|
+
email: args.email,
|
|
278
|
+
role,
|
|
279
|
+
...appRoles && { app_roles: appRoles }
|
|
280
|
+
}), {
|
|
281
|
+
action: `add ${args.email} to ${slug}`,
|
|
282
|
+
fixes: MEMBER_FIXES
|
|
283
|
+
});
|
|
284
|
+
const roles = appRoles?.length ? ` with ${appRoles.join(", ")}` : "";
|
|
285
|
+
console.log(res.invite ? `Invited ${args.email} as ${role}${roles} — it takes effect when they first sign in. Canopy sends no email, so let them know.` : `Added ${args.email} as ${role}${roles}.`);
|
|
286
|
+
})
|
|
287
|
+
}),
|
|
288
|
+
set: defineCommand({
|
|
289
|
+
meta: {
|
|
290
|
+
name: "set",
|
|
291
|
+
description: "Change someone's Canopy role (owner only) and/or app roles (editors too)"
|
|
292
|
+
},
|
|
293
|
+
args: {
|
|
294
|
+
email: {
|
|
295
|
+
type: "positional",
|
|
296
|
+
description: "Their email, as `members list` shows it",
|
|
297
|
+
required: true
|
|
298
|
+
},
|
|
299
|
+
viewer: {
|
|
300
|
+
type: "boolean",
|
|
301
|
+
description: "Make them a viewer"
|
|
302
|
+
},
|
|
303
|
+
editor: {
|
|
304
|
+
type: "boolean",
|
|
305
|
+
description: "Make them an editor"
|
|
306
|
+
},
|
|
307
|
+
...appRoleArg,
|
|
308
|
+
"clear-app-roles": {
|
|
309
|
+
type: "boolean",
|
|
310
|
+
description: "Take all their app roles away"
|
|
311
|
+
},
|
|
312
|
+
...projectDirArgs
|
|
313
|
+
},
|
|
314
|
+
run: reportErrors("members set", async ({ args }) => {
|
|
315
|
+
const { slug } = resolveProject(args);
|
|
316
|
+
const role = canopyRoleFlag(args);
|
|
317
|
+
const appRoles = args["clear-app-roles"] ? [] : appRolesOf(args);
|
|
318
|
+
if (role === void 0 && appRoles === void 0) throw new Error("Nothing to change — pass --editor, --viewer, --app-role or --clear-app-roles.");
|
|
319
|
+
const body = {
|
|
320
|
+
...role && { role },
|
|
321
|
+
...appRoles && { app_roles: appRoles }
|
|
322
|
+
};
|
|
323
|
+
const email = args.email.trim().toLowerCase();
|
|
324
|
+
const { members, invites } = await roster(slug);
|
|
325
|
+
const member = members.find((m) => m.email.toLowerCase() === email);
|
|
326
|
+
const invite = invites.find((i) => i.email === email);
|
|
327
|
+
if (!member && !invite) throw new Error(`${args.email} is not on ${slug} — add them with \`canopy members add\`.`);
|
|
328
|
+
await withContext(apiRequest("PATCH", member ? `/projects/${slug}/members/${encodeURIComponent(member.user_id)}` : `/projects/${slug}/invites/${encodeURIComponent(email)}`, body), {
|
|
329
|
+
action: `update ${args.email} on ${slug}`,
|
|
330
|
+
fixes: MEMBER_FIXES
|
|
331
|
+
});
|
|
332
|
+
console.log([
|
|
333
|
+
`Updated ${args.email}`,
|
|
334
|
+
role && `now ${role}`,
|
|
335
|
+
appRoles && (appRoles.length ? `app roles ${appRoles.join(", ")}` : "no app roles")
|
|
336
|
+
].filter(Boolean).join(" — ") + ".");
|
|
337
|
+
})
|
|
338
|
+
}),
|
|
339
|
+
remove: defineCommand({
|
|
340
|
+
meta: {
|
|
341
|
+
name: "remove",
|
|
342
|
+
description: "Remove someone from the project, or withdraw their invite"
|
|
343
|
+
},
|
|
344
|
+
args: {
|
|
345
|
+
email: {
|
|
346
|
+
type: "positional",
|
|
347
|
+
description: "Their email, as `members list` shows it",
|
|
348
|
+
required: true
|
|
349
|
+
},
|
|
350
|
+
...projectDirArgs
|
|
351
|
+
},
|
|
352
|
+
run: reportErrors("members remove", async ({ args }) => {
|
|
353
|
+
const { slug } = resolveProject(args);
|
|
354
|
+
const email = args.email.trim().toLowerCase();
|
|
355
|
+
const { members, invites } = await roster(slug);
|
|
356
|
+
const member = members.find((m) => m.email.toLowerCase() === email);
|
|
357
|
+
if (member) {
|
|
358
|
+
await withContext(apiRequest("DELETE", `/projects/${slug}/members/${encodeURIComponent(member.user_id)}`), {
|
|
359
|
+
action: `remove ${args.email} from ${slug}`,
|
|
360
|
+
fixes: MEMBER_FIXES
|
|
361
|
+
});
|
|
362
|
+
console.log(`Removed ${args.email} from ${slug}.`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (invites.some((i) => i.email === email)) {
|
|
366
|
+
await withContext(apiRequest("DELETE", `/projects/${slug}/invites/${encodeURIComponent(email)}`), {
|
|
367
|
+
action: `withdraw the invite for ${args.email}`,
|
|
368
|
+
fixes: MEMBER_FIXES
|
|
369
|
+
});
|
|
370
|
+
console.log(`Withdrew the invite for ${args.email}.`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw new Error(`${args.email} is not on ${slug}.`);
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
//#endregion
|
|
379
|
+
export { parseAppRoleFlag as a, membersCommand as i, formatRoles as n, rolesCommand as o, formatRoster as r, canopyRoleFlag as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, f as withContext } from "./api-
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/schedule.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
-
import { a as apiRequest, f as withContext } from "./api-
|
|
3
|
-
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
|
|
3
|
+
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/secrets.ts
|
|
@@ -109,6 +109,15 @@ and never go stale.
|
|
|
109
109
|
hard-coded ids — model ids outside that list are refused.
|
|
110
110
|
- Work bug reports & feedback: `canopy feedback list | show | comment | note | status | report | watch`
|
|
111
111
|
— see [Working feedback reports](#working-feedback-reports-issues) below
|
|
112
|
+
- **People and permissions in the app** (`canopy docs roles`): app roles are
|
|
113
|
+
the project's own labels, read as `roles` on `auth.user()` / `env.CALLER`.
|
|
114
|
+
Run `canopy roles list --json` before writing a role check and use only those
|
|
115
|
+
slugs; create missing ones with `canopy roles add "<Name>"` and assign them
|
|
116
|
+
with `canopy members set <email> --app-role <slug>`. **Enforce roles in the
|
|
117
|
+
backend worker** (`env.CALLER.roles`), never only in the page; keep data
|
|
118
|
+
only some roles may see in `private_` tables. Try other roles locally with
|
|
119
|
+
`canopy dev --as-role <slug>`. Adding or removing people is the owner's call
|
|
120
|
+
(`canopy members add|remove`) — confirm with the user first.
|
|
112
121
|
- CI credentials: `canopy token create | list | revoke`
|
|
113
122
|
- Git: `canopy repo show --json` (is there a repository, and its remote) ·
|
|
114
123
|
`canopy repo create` · `canopy repo clone <slug>` · `canopy repo connect`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-Bvly8-rB.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-CLnICOne.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|