@mediamonks/canopy-cli 0.7.46 → 0.7.47

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.
@@ -0,0 +1,121 @@
1
+ import { c as projectDirArgs, d as resolveProject, l as projectFlagArgs } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { r as reportErrors } from "./ui-26ETk1n8.mjs";
4
+ import { defineCommand } from "citty";
5
+ import * as p from "@clack/prompts";
6
+ //#region src/audience.ts
7
+ /**
8
+ * Project audience (OpenSpec change project-audience): `canopy audience
9
+ * show|set|draft|ready`. Who a project is for and whether it's still a draft
10
+ * are labels on the dashboard's project list, stored on the platform (never
11
+ * in canopy.json) and set through the project update route.
12
+ */
13
+ const KINDS = [
14
+ "everyone",
15
+ "personal",
16
+ "client",
17
+ "department"
18
+ ];
19
+ const NAME_MAX = 40;
20
+ /** `set <kind> [name]` → the update body, or a sentence saying what's wrong. */
21
+ function parseAudience(kind, name) {
22
+ const k = kind.trim().toLowerCase();
23
+ if (!KINDS.includes(k)) return `"${kind}" is not an audience. Use everyone, personal, client <name> or department <name>.`;
24
+ if (k === "client" || k === "department") {
25
+ const n = name?.trim() ?? "";
26
+ if (!n) return `Name the ${k}: canopy audience set ${k} "<name>"`;
27
+ if (n.length > NAME_MAX) return `Keep the ${k} name to ${NAME_MAX} characters.`;
28
+ return {
29
+ kind: k,
30
+ name: n
31
+ };
32
+ }
33
+ if (name?.trim()) return `${k === "everyone" ? "Everyone" : "Personal"} takes no name.`;
34
+ return { kind: k };
35
+ }
36
+ /** One line for people: who it's for, and whether it's a draft. */
37
+ function describeAudience(project) {
38
+ const a = project.audience ?? null;
39
+ const who = a === null ? "is for everyone" : a.kind === "personal" ? "is a personal project" : `is for the ${a.name} ${a.kind === "client" ? "client team" : "department"}`;
40
+ return `${project.slug} ${who}${project.draft ? ", and is still a draft" : ""}.`;
41
+ }
42
+ const FIXES = { invalid_audience: "Use everyone, personal, client <name> or department <name> (names up to 40 characters)." };
43
+ function update(slug, body) {
44
+ return withContext(apiRequest("PATCH", `/projects/${slug}`, body).then((r) => r.project), {
45
+ action: `update ${slug}`,
46
+ fixes: FIXES
47
+ });
48
+ }
49
+ const audienceCommand = defineCommand({
50
+ meta: {
51
+ name: "audience",
52
+ description: "Who the project is for, and whether it's a draft (show, set, draft, ready)"
53
+ },
54
+ subCommands: {
55
+ show: defineCommand({
56
+ meta: {
57
+ name: "show",
58
+ description: "Show who the project is for and whether it's a draft"
59
+ },
60
+ args: { ...projectDirArgs },
61
+ run: reportErrors("audience show", async ({ args }) => {
62
+ const { slug } = resolveProject(args);
63
+ const { project } = await withContext(apiRequest("GET", `/projects/${slug}`), { action: `read ${slug}` });
64
+ console.log(describeAudience(project));
65
+ })
66
+ }),
67
+ set: defineCommand({
68
+ meta: {
69
+ name: "set",
70
+ description: "Say who it's for: everyone, personal, client <name> or department <name>"
71
+ },
72
+ args: {
73
+ kind: {
74
+ type: "positional",
75
+ description: "everyone | personal | client | department",
76
+ required: true
77
+ },
78
+ name: {
79
+ type: "positional",
80
+ description: "The client team or department (client and department only)",
81
+ required: false
82
+ },
83
+ ...projectFlagArgs
84
+ },
85
+ run: reportErrors("audience set", async ({ args }) => {
86
+ const audience = parseAudience(args.kind, args.name);
87
+ if (typeof audience === "string") {
88
+ console.error(audience);
89
+ process.exit(1);
90
+ }
91
+ const { slug } = resolveProject({ project: args.project });
92
+ p.log.success(describeAudience(await update(slug, { audience })));
93
+ p.log.info("It's a label on the project list; who can open the project is unchanged.");
94
+ })
95
+ }),
96
+ draft: defineCommand({
97
+ meta: {
98
+ name: "draft",
99
+ description: "Mark it as not ready yet (greyed out on the list)"
100
+ },
101
+ args: { ...projectDirArgs },
102
+ run: reportErrors("audience draft", async ({ args }) => {
103
+ const { slug } = resolveProject(args);
104
+ p.log.success(describeAudience(await update(slug, { draft: true })));
105
+ })
106
+ }),
107
+ ready: defineCommand({
108
+ meta: {
109
+ name: "ready",
110
+ description: "Mark it as ready (no longer a draft)"
111
+ },
112
+ args: { ...projectDirArgs },
113
+ run: reportErrors("audience ready", async ({ args }) => {
114
+ const { slug } = resolveProject(args);
115
+ p.log.success(describeAudience(await update(slug, { draft: false })));
116
+ })
117
+ })
118
+ }
119
+ });
120
+ //#endregion
121
+ export { audienceCommand };
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: canopy audience
3
+ description: Say who a project is for and whether it's still a draft.
4
+ ---
5
+
6
+ `canopy audience` sets the labels people see on the dashboard's project list:
7
+ who the project was made for, and whether it's still a draft. They never
8
+ change who can open the project. Read
9
+ [Saying who it's for](https://docs.canopy.website/platform/members-and-access#saying-who-its-for).
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ canopy audience show
15
+ canopy audience set <everyone|personal>
16
+ canopy audience set <client|department> <name>
17
+ canopy audience draft
18
+ canopy audience ready
19
+ ```
20
+
21
+ ## Arguments & flags
22
+
23
+ Each subcommand works on the project in the current folder (`canopy.json`),
24
+ or the one you name with `--project <slug>`. All of them honour the global
25
+ `--profile <site>` / `CANOPY_PROFILE`. Owners, managers and editors can change
26
+ them.
27
+
28
+ - `show` — who the project is for, and whether it's a draft.
29
+ - `set <kind> [name]` — `everyone` (the default), `personal`, or `client` /
30
+ `department` with a name of up to 40 characters. Quote names with spaces.
31
+ - `draft` — mark it as not ready yet. It shows greyed out on the project list
32
+ but stays listed and usable.
33
+ - `ready` — it's no longer a draft.
34
+
35
+ ## Examples
36
+
37
+ ```bash
38
+ canopy audience set client "Uniqlo"
39
+ ```
40
+
41
+ Marks the project as made for the Uniqlo client team.
42
+
43
+ ```bash
44
+ canopy audience draft --project retro-board
45
+ ```
46
+
47
+ Marks `retro-board` as a draft without being in its folder.
@@ -246,6 +246,11 @@
246
246
  "title": "canopy rename",
247
247
  "summary": "Change a project's slug — its address — and update canopy.json."
248
248
  },
249
+ {
250
+ "slug": "cli-audience",
251
+ "title": "canopy audience",
252
+ "summary": "Say who a project is for and whether it's still a draft."
253
+ },
249
254
  {
250
255
  "slug": "cli-dev",
251
256
  "title": "canopy dev",
@@ -113,6 +113,26 @@ Turning it off again is the same toggle.
113
113
  > **Note:** Members-only gates the **live app**. Management pages (settings, data, deploys) are always for the
114
114
  > owner, managers and editors only, whatever this toggle says.
115
115
 
116
+ ## Saying who it's for
117
+
118
+ Not every project is meant for everyone who scrolls past it. In **Settings →
119
+ Audience** you can say who it was made for:
120
+
121
+ - **Everyone**, the default. Nothing extra shows on the project list.
122
+ - **Just me**, for a personal tool or experiment.
123
+ - **A client team** or **a department**, with its name (up to 40 characters).
124
+
125
+ Other people see the audience as a small badge next to **Visit** on the
126
+ project list, and personal projects sort below the ones meant for everyone.
127
+ Every Monk can read the name, the same as the project's name and description.
128
+
129
+ Turn on **Still a draft** while it isn't ready. The project shows greyed out
130
+ and sorts lower, but it stays listed and anyone who could open it still can.
131
+
132
+ Both are labels only. They never change who can open the project; that's
133
+ what members-only and the client password are for. From the CLI, use
134
+ [`canopy audience`](https://docs.canopy.website/cli/audience).
135
+
116
136
  ## Platform admins and the Admin page
117
137
 
118
138
  Platform admins get an extra **Admin** page in the dashboard. Its **Users**
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BdNHq8QV.mjs";
1
+ import { t as version } from "./package-BksVeJZB.mjs";
2
2
  import { r as siteConfig } from "./site-BL5Ik-yj.mjs";
3
3
  import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
4
4
  import { n as agentHeaders } from "./api-DmJrIsE7.mjs";
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BdNHq8QV.mjs";
1
+ import { t as version } from "./package-BksVeJZB.mjs";
2
2
  import { c as projectDirArgs, d as resolveProject, h as tryResolveProject, m as tryReadCanopyJson, r as siteConfig } from "./site-BL5Ik-yj.mjs";
3
3
  import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-DmJrIsE7.mjs";
4
4
  import { r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-BdNHq8QV.mjs";
2
+ import { t as version } from "./package-BksVeJZB.mjs";
3
3
  import { i as siteHeaderLine, n as setInvocationProfile } from "./site-BL5Ik-yj.mjs";
4
4
  import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
5
5
  import { defineCommand, runMain } from "citty";
@@ -83,19 +83,20 @@ const main = defineCommand({
83
83
  roles: () => import("./roles-CrYf_8wk.mjs").then((m) => m.rolesCommand),
84
84
  members: () => import("./roles-CrYf_8wk.mjs").then((m) => m.membersCommand),
85
85
  schedule: () => import("./schedule-DvHSSzfR.mjs").then((m) => m.scheduleCommand),
86
- feedback: () => import("./feedback-BXhbxyXI.mjs").then((m) => m.feedbackCommand),
86
+ feedback: () => import("./feedback-C6kiYRro.mjs").then((m) => m.feedbackCommand),
87
87
  db: () => import("./db-DpCMc0uz.mjs").then((m) => m.dbCommand),
88
88
  usage: () => import("./usage-Bpz5B1N1.mjs").then((m) => m.usageCommand),
89
89
  ai: () => import("./ai-CWPQiUft.mjs").then((m) => m.aiCommand),
90
90
  open: () => import("./open-DcQSdfx5.mjs").then((m) => m.openCommand),
91
91
  browse: () => import("./browse-BPLWhoFI.mjs").then((m) => m.browseCommand),
92
- docs: () => import("./docs-CtMrMpt1.mjs").then((m) => m.docsCommand),
93
- skill: () => import("./skill-vzcCD1b4.mjs").then((m) => m.skillCommand),
94
- upgrade: () => import("./upgrade-8_pFNTo5.mjs").then((m) => m.upgradeCommand),
92
+ docs: () => import("./docs-D5gmZRJk.mjs").then((m) => m.docsCommand),
93
+ skill: () => import("./skill-Bz39NhTN.mjs").then((m) => m.skillCommand),
94
+ upgrade: () => import("./upgrade-BjDaghNf.mjs").then((m) => m.upgradeCommand),
95
95
  logs: () => import("./logs-dZMPTJLu.mjs").then((m) => m.logsCommand),
96
- mcp: () => import("./mcp-CGmWkGN8.mjs").then((m) => m.mcpCommand),
96
+ mcp: () => import("./mcp-DuCxfb85.mjs").then((m) => m.mcpCommand),
97
97
  repo: () => import("./repo-C7AXDtFZ.mjs").then((m) => m.repoCommand),
98
- template: () => import("./project-template-Dw11151p.mjs").then((m) => m.templateCommand)
98
+ template: () => import("./project-template-Dw11151p.mjs").then((m) => m.templateCommand),
99
+ audience: () => import("./audience-B68YZBGy.mjs").then((m) => m.audienceCommand)
99
100
  }
100
101
  });
101
102
  const quiet = process.argv[2] === "mcp" && process.argv[3] === "serve" || process.argv[2] === "repo" && process.argv[3] === "credential";
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BdNHq8QV.mjs";
1
+ import { t as version } from "./package-BksVeJZB.mjs";
2
2
  import { h as tryResolveProject, p as stagingUrl, r as siteConfig, u as projectUrl } from "./site-BL5Ik-yj.mjs";
3
3
  import { a as apiRequest, f as noticeRenamesOnly, l as currentSlug, t as ApiError } from "./api-DmJrIsE7.mjs";
4
4
  import { t as liveDevServer } from "./dev-server-file-Dk9pk4AR.mjs";
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.46";
2
+ var version = "0.7.47";
3
3
  //#endregion
4
4
  export { version as t };
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-BdNHq8QV.mjs";
1
+ import { t as version } from "./package-BksVeJZB.mjs";
2
2
  import { r as reportErrors } from "./ui-26ETk1n8.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
@@ -112,7 +112,7 @@ const upgradeCommand = defineCommand({
112
112
  const spec = `${PACKAGE_NAME}@${args.version ?? "latest"}`;
113
113
  const command = pmCommand(pm);
114
114
  const cmd = installArgs(pm, spec);
115
- const { skillInstalled } = await import("./skill-vzcCD1b4.mjs");
115
+ const { skillInstalled } = await import("./skill-Bz39NhTN.mjs");
116
116
  const { homedir } = await import("node:os");
117
117
  const refreshGlobal = skillInstalled(homedir());
118
118
  const refreshRepo = process.cwd() !== homedir() && skillInstalled(process.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.46",
3
+ "version": "0.7.47",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"