@mediamonks/canopy-cli 0.0.1 → 0.2.0

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 (66) hide show
  1. package/dist/api-CyJ-wBZh.mjs +40 -0
  2. package/dist/config-C-z7st1X.mjs +33 -0
  3. package/dist/{db-BBcgT1md.mjs → db-CtUG4vfG.mjs} +3 -3
  4. package/dist/{deploy-BfhAlb91.mjs → deploy-N88l4Kt3.mjs} +7 -6
  5. package/dist/{dev-BB7CwqoI.mjs → dev-DTGW0lm0.mjs} +6 -5
  6. package/dist/dev-D_GlUULk.mjs +2 -0
  7. package/dist/docs/ci.md +48 -0
  8. package/dist/docs/cli.md +67 -0
  9. package/dist/docs/data.md +49 -0
  10. package/dist/docs/deploy.md +50 -0
  11. package/dist/docs/dev.md +44 -0
  12. package/dist/docs/getting-started.md +100 -0
  13. package/dist/docs/troubleshooting.md +61 -0
  14. package/dist/docs-C3aOAx_A.mjs +179 -0
  15. package/dist/index.mjs +26 -16
  16. package/dist/{local-DhufX0eJ.mjs → local-951zc9qJ.mjs} +2 -2
  17. package/dist/{login-DrXKOEmr.mjs → login-Dif9Y32q.mjs} +38 -10
  18. package/dist/{open-DFWvrLFc.mjs → open-xLiGUz_W.mjs} +2 -2
  19. package/dist/package-OcWG7JAW.mjs +4 -0
  20. package/dist/{project-CrrpLEPs.mjs → project-CpiPLNby.mjs} +1 -1
  21. package/dist/projects-DJT_mJDR.mjs +228 -0
  22. package/dist/skill/canopy/SKILL.md +80 -0
  23. package/dist/skill-jbmERP1X.mjs +84 -0
  24. package/dist/templates/spa/.github/workflows/canopy-deploy.yml +34 -0
  25. package/dist/templates/spa/AGENTS.md +43 -0
  26. package/dist/templates/spa/README.md +14 -0
  27. package/dist/templates/spa/_gitignore +4 -0
  28. package/dist/templates/spa/canopy.json +5 -0
  29. package/dist/templates/spa/index.html +12 -0
  30. package/dist/templates/spa/package.json +21 -0
  31. package/dist/templates/spa/src/app.tsx +25 -0
  32. package/dist/templates/spa/src/canopy.ts +5 -0
  33. package/dist/templates/spa/src/main.tsx +10 -0
  34. package/dist/templates/spa/src/style.css +29 -0
  35. package/dist/templates/spa/tsconfig.json +16 -0
  36. package/dist/templates/spa/vite.config.ts +7 -0
  37. package/dist/templates/static/AGENTS.md +41 -0
  38. package/dist/templates/static/README.md +9 -0
  39. package/dist/templates/static/_gitignore +2 -0
  40. package/dist/templates/static/canopy.json +4 -0
  41. package/dist/templates/static/index.html +21 -0
  42. package/dist/templates/static/main.js +9 -0
  43. package/dist/templates/static/style.css +29 -0
  44. package/dist/templates/worker/.github/workflows/canopy-deploy.yml +34 -0
  45. package/dist/templates/worker/AGENTS.md +51 -0
  46. package/dist/templates/worker/README.md +18 -0
  47. package/dist/templates/worker/_gitignore +4 -0
  48. package/dist/templates/worker/canopy.json +6 -0
  49. package/dist/templates/worker/index.html +12 -0
  50. package/dist/templates/worker/package.json +22 -0
  51. package/dist/templates/worker/src/app.tsx +25 -0
  52. package/dist/templates/worker/src/canopy.ts +5 -0
  53. package/dist/templates/worker/src/main.tsx +10 -0
  54. package/dist/templates/worker/src/style.css +29 -0
  55. package/dist/templates/worker/tsconfig.json +16 -0
  56. package/dist/templates/worker/vite.config.ts +7 -0
  57. package/dist/templates/worker/worker/index.ts +29 -0
  58. package/dist/token-rTD9jxPZ.mjs +75 -0
  59. package/dist/ui-C8VuPzGp.mjs +40 -0
  60. package/dist/upgrade-Y8ejvoeg.mjs +99 -0
  61. package/dist/version-check-CoWgQs2h.mjs +128 -0
  62. package/package.json +2 -2
  63. package/dist/api-BEY4O4ap.mjs +0 -36
  64. package/dist/dev-Bvy21srH.mjs +0 -2
  65. package/dist/projects-f_KocDae.mjs +0 -295
  66. package/dist/ui-Cl5P_yQ6.mjs +0 -72
@@ -0,0 +1,84 @@
1
+ import { t as version } from "./package-OcWG7JAW.mjs";
2
+ import { n as reportErrors } from "./ui-C8VuPzGp.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
+ import { join, relative } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ //#region src/skill.ts
8
+ /**
9
+ * canopy skill install — write the Canopy agent skill into the current repo.
10
+ *
11
+ * To avoid vendor lock-in the skill lives in the tool-neutral `.agents/skills/`
12
+ * tree, and `.claude/skills/canopy` is a symlink to it (so Claude Code picks it
13
+ * up while other agents can read `.agents/`). The skill files are bundled with
14
+ * the CLI (dist/skill in the published package, packages/cli/skill in dev), so
15
+ * `canopy upgrade` re-runs this to keep the installed skill in sync.
16
+ */
17
+ const SKILL_NAME = "canopy";
18
+ const AGENTS_REL = join(".agents", "skills", SKILL_NAME);
19
+ const CLAUDE_REL = join(".claude", "skills", SKILL_NAME);
20
+ const STAMP = ".canopy-skill-version";
21
+ /** The bundled skill source: dist/skill/canopy published, packages/cli/skill in dev. */
22
+ function bundledSkillDir() {
23
+ const base = fileURLToPath(new URL(".", import.meta.url));
24
+ for (const candidate of [join(base, "skill", SKILL_NAME), join(base, "..", "skill", SKILL_NAME)]) if (existsSync(join(candidate, "SKILL.md"))) return candidate;
25
+ throw new Error("Skill files not found — the CLI build is incomplete (missing skill/).");
26
+ }
27
+ /** Point `.claude/skills/canopy` at the `.agents` copy; copy as a fallback. */
28
+ function linkClaude(root) {
29
+ const linkPath = join(root, CLAUDE_REL);
30
+ const target = relative(join(root, ".claude", "skills"), join(root, AGENTS_REL));
31
+ mkdirSync(join(root, ".claude", "skills"), { recursive: true });
32
+ rmSync(linkPath, {
33
+ recursive: true,
34
+ force: true
35
+ });
36
+ try {
37
+ symlinkSync(target, linkPath, "dir");
38
+ return "symlink";
39
+ } catch {
40
+ cpSync(join(root, AGENTS_REL), linkPath, { recursive: true });
41
+ return "copy";
42
+ }
43
+ }
44
+ /** Install/refresh the skill under `root`. Returns the absolute `.agents` path. */
45
+ function installSkill(root) {
46
+ const source = bundledSkillDir();
47
+ const agentsDir = join(root, AGENTS_REL);
48
+ rmSync(agentsDir, {
49
+ recursive: true,
50
+ force: true
51
+ });
52
+ mkdirSync(agentsDir, { recursive: true });
53
+ cpSync(source, agentsDir, { recursive: true });
54
+ writeFileSync(join(agentsDir, STAMP), `${version}\n`);
55
+ return {
56
+ agentsDir,
57
+ claudeMode: linkClaude(root)
58
+ };
59
+ }
60
+ /** Is a Canopy skill already installed under `root`? (Used by `canopy upgrade`.) */
61
+ function skillInstalled(root) {
62
+ return existsSync(join(root, AGENTS_REL, "SKILL.md"));
63
+ }
64
+ const skillCommand = defineCommand({
65
+ meta: {
66
+ name: "skill",
67
+ description: "Manage the Canopy agent skill in this repo"
68
+ },
69
+ subCommands: { install: () => defineCommand({
70
+ meta: {
71
+ name: "install",
72
+ description: "Install/refresh the Canopy skill (.agents + .claude)"
73
+ },
74
+ run: reportErrors("skill install", async () => {
75
+ const { claudeMode } = installSkill(process.cwd());
76
+ console.log(`Installed the Canopy skill (v${version}):`);
77
+ console.log(` .agents/skills/${SKILL_NAME}/ (source of truth)`);
78
+ console.log(claudeMode === "symlink" ? ` .claude/skills/${SKILL_NAME} → symlink to .agents` : ` .claude/skills/${SKILL_NAME} (copy — symlinks unavailable here)`);
79
+ console.log("\nYour agent can now use Canopy. Try: canopy docs");
80
+ })
81
+ }) }
82
+ });
83
+ //#endregion
84
+ export { skillCommand, skillInstalled };
@@ -0,0 +1,34 @@
1
+ # Deploy to Canopy on every push to main. GitHub's runners are the build
2
+ # farm — the platform owns no build infrastructure.
3
+ #
4
+ # Needs a CANOPY_DEPLOY_TOKEN repo secret: create a deploy token in the
5
+ # dashboard (project → Settings → Deploy tokens) or with `canopy token
6
+ # create`, then add it via `gh secret set CANOPY_DEPLOY_TOKEN` or the repo's
7
+ # Settings → Secrets. Until the secret is set, this workflow skips itself
8
+ # instead of failing the build.
9
+ name: Deploy to Canopy
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ env:
19
+ CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: 22
25
+ - name: Install dependencies
26
+ if: env.CANOPY_DEPLOY_TOKEN != ''
27
+ run: |
28
+ if [ -f package-lock.json ]; then npm ci; else npm install; fi
29
+ - name: Deploy
30
+ if: env.CANOPY_DEPLOY_TOKEN != ''
31
+ run: npm exec --yes @mediamonks/canopy-cli deploy
32
+ - name: Skipped (no deploy token)
33
+ if: env.CANOPY_DEPLOY_TOKEN == ''
34
+ run: echo "CANOPY_DEPLOY_TOKEN secret not set — skipping deploy."
@@ -0,0 +1,43 @@
1
+ # {{name}} — a Canopy React SPA
2
+
3
+ A Vite + React single-page app on [Canopy](https://dashboard.thanarie.dev),
4
+ Monks' managed platform for internal projects. Everything is served behind
5
+ company login; there is no infrastructure to manage and none should be added.
6
+
7
+ ## Canopy skill
8
+
9
+ The full SDK reference (auth, db, blob, ai) and platform guardrails ship as an
10
+ agent skill, kept out of this scaffold so it stays current. Install it before
11
+ building features:
12
+
13
+ ```
14
+ npx skills add mediamonks/canopy
15
+ ```
16
+
17
+ If that fails the skill isn't published yet — the SDK surface is documented on
18
+ the [Canopy dashboard](https://dashboard.thanarie.dev).
19
+
20
+ ## Working on this project
21
+
22
+ - A standard Vite + React + TypeScript app; `npm install` once, then develop
23
+ with `canopy dev` (it runs Vite with this project's `vite.config.ts` and
24
+ proxies `/_api` to the platform).
25
+ - The SDK comes from npm (`@mediamonks/canopy`) — the shared instance lives in
26
+ `src/canopy.ts`.
27
+ - `canopy dev` uses LIVE project data: read freely, but take a
28
+ `canopy db snapshot` before risky data changes.
29
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead;
30
+ writes never reach the platform. Prefer it for destructive experiments.
31
+ - `canopy deploy` — builds (`npm run build`) and publishes `dist/`; live
32
+ (behind company login) at https://{{slug}}.thanarie.dev.
33
+
34
+ ## Boundaries
35
+
36
+ - Do not add auth — the platform authenticates every request before it
37
+ reaches this app.
38
+ - Do not store secrets anywhere in this project, and never call external APIs
39
+ that need them from the browser — all backend needs go through the Canopy
40
+ SDK (`canopy.auth`, `canopy.db`, `canopy.blob`, `canopy.ai`).
41
+ - Frontend only: no SSR, no custom servers, no databases outside `canopy.db`.
42
+ (Need backend code? Recreate on the `worker` template, or ask the Canopy
43
+ team.)
@@ -0,0 +1,14 @@
1
+ # {{name}}
2
+
3
+ A [Canopy](https://dashboard.thanarie.dev) project — Vite + React. Live
4
+ (behind company login) at https://{{slug}}.thanarie.dev after the first
5
+ `canopy deploy`.
6
+
7
+ - `npm install` — once, then:
8
+ - `canopy dev` — develop locally against the live platform (real data!)
9
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead
10
+ - `canopy deploy` — build and publish `dist/`
11
+ - `AGENTS.md` — project guardrails and skill setup for coding agents
12
+
13
+ Push-to-main deploys are prepared in `.github/workflows/canopy-deploy.yml`
14
+ (needs a `CANOPY_DEPLOY_TOKEN` repo secret — see the workflow file).
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ .canopy/
4
+ .DS_Store
@@ -0,0 +1,5 @@
1
+ {
2
+ "slug": "{{slug}}",
3
+ "build": "npm run build",
4
+ "dist": "dist"
5
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{name}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "{{slug}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "canopy dev",
7
+ "build": "tsc && vite build"
8
+ },
9
+ "dependencies": {
10
+ "@mediamonks/canopy": "^0.0.1",
11
+ "react": "^19.1.0",
12
+ "react-dom": "^19.1.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/react": "^19.1.0",
16
+ "@types/react-dom": "^19.1.0",
17
+ "@vitejs/plugin-react": "^5.0.0",
18
+ "typescript": "^5.8.0",
19
+ "vite": "^7.0.0"
20
+ }
21
+ }
@@ -0,0 +1,25 @@
1
+ import { useEffect, useState } from "react";
2
+ import { canopy } from "./canopy.ts";
3
+
4
+ export function App() {
5
+ const [status, setStatus] = useState("connecting to Canopy…");
6
+
7
+ useEffect(() => {
8
+ canopy.auth
9
+ .user()
10
+ .then((user) => setStatus(`signed in as ${user.email}`))
11
+ .catch(() => setStatus("could not reach Canopy — is `canopy dev` running?"));
12
+ }, []);
13
+
14
+ return (
15
+ <main>
16
+ <h1>{"{{name}}"}</h1>
17
+ <p className="status">{status}</p>
18
+ <section>
19
+ <p className="placeholder">
20
+ Start building — edit <code>src/app.tsx</code>, then run <code>canopy deploy</code>.
21
+ </p>
22
+ </section>
23
+ </main>
24
+ );
25
+ }
@@ -0,0 +1,5 @@
1
+ // One SDK instance for the whole app. Auth (auth), data (db), files (blob)
2
+ // and AI (ai) are all live — see AGENTS.md for how to get the full reference.
3
+ import { createCanopy } from "@mediamonks/canopy";
4
+
5
+ export const canopy = createCanopy();
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./app.tsx";
4
+ import "./style.css";
5
+
6
+ createRoot(document.getElementById("root") as HTMLElement).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,29 @@
1
+ :root {
2
+ font-family: system-ui, sans-serif;
3
+ color-scheme: light dark;
4
+ }
5
+
6
+ body {
7
+ margin: 0;
8
+ display: grid;
9
+ place-items: center;
10
+ min-height: 100vh;
11
+ }
12
+
13
+ main {
14
+ max-width: 40rem;
15
+ padding: 2rem;
16
+ text-align: center;
17
+ }
18
+
19
+ .status {
20
+ font-size: 0.9rem;
21
+ opacity: 0.7;
22
+ }
23
+
24
+ .placeholder {
25
+ border: 1px dashed color-mix(in srgb, currentColor 30%, transparent);
26
+ border-radius: 8px;
27
+ padding: 1.5rem;
28
+ opacity: 0.6;
29
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "isolatedModules": true,
12
+ "allowImportingTsExtensions": true,
13
+ "noUncheckedIndexedAccess": true
14
+ },
15
+ "include": ["src", "vite.config.ts"]
16
+ }
@@ -0,0 +1,7 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+
4
+ // `canopy dev` picks this config up and adds its own /_api proxy on top.
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ });
@@ -0,0 +1,41 @@
1
+ # {{name}} — a Canopy static app
2
+
3
+ A no-build web app on [Canopy](https://dashboard.thanarie.dev), Monks' managed
4
+ platform for internal projects. Everything is served behind company login;
5
+ there is no infrastructure to manage and none should be added.
6
+
7
+ ## Canopy skill
8
+
9
+ The full SDK reference (auth, db, blob, ai) and platform guardrails ship as an
10
+ agent skill, kept out of this scaffold so it stays current. Install it before
11
+ building features:
12
+
13
+ ```
14
+ npx skills add mediamonks/canopy
15
+ ```
16
+
17
+ If that fails the skill isn't published yet — the SDK surface is documented on
18
+ the [Canopy dashboard](https://dashboard.thanarie.dev).
19
+
20
+ ## Working on this project
21
+
22
+ - Plain html/css/js in this directory — no bundler, no install step. The
23
+ Canopy SDK is imported straight from the platform, so it always matches the
24
+ API version: `import { createCanopy } from "/_api/v1/sdk.js";`
25
+ - `canopy dev` — local dev server; its `/_api` proxies to the real platform.
26
+ That is LIVE project data: read freely, but take a `canopy db snapshot`
27
+ before risky data changes.
28
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead;
29
+ writes never reach the platform. Prefer it for destructive experiments.
30
+ - `canopy deploy` — publish; live (behind company login) at
31
+ https://{{slug}}.thanarie.dev.
32
+
33
+ ## Boundaries
34
+
35
+ - Do not add auth — the platform authenticates every request before it
36
+ reaches this app.
37
+ - Do not store secrets anywhere in this project, and never call external APIs
38
+ that need them from the browser — all backend needs go through the Canopy
39
+ SDK (`canopy.auth`, `canopy.db`, `canopy.blob`, `canopy.ai`).
40
+ - Keep it a static site: no SSR, no custom servers, no databases outside
41
+ `canopy.db`.
@@ -0,0 +1,9 @@
1
+ # {{name}}
2
+
3
+ A [Canopy](https://dashboard.thanarie.dev) project. Live (behind company
4
+ login) at https://{{slug}}.thanarie.dev after the first `canopy deploy`.
5
+
6
+ - `canopy dev` — develop locally against the live platform (real data!)
7
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead
8
+ - `canopy deploy` — publish the current directory
9
+ - `AGENTS.md` — project guardrails and skill setup for coding agents
@@ -0,0 +1,2 @@
1
+ .canopy/
2
+ .DS_Store
@@ -0,0 +1,4 @@
1
+ {
2
+ "slug": "{{slug}}",
3
+ "dist": "."
4
+ }
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{name}}</title>
7
+ <link rel="stylesheet" href="./style.css" />
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>{{name}}</h1>
12
+ <p class="status" id="status">connecting to Canopy…</p>
13
+ <section id="app">
14
+ <p class="placeholder">
15
+ Start building — edit <code>main.js</code>, then run <code>canopy deploy</code>.
16
+ </p>
17
+ </section>
18
+ </main>
19
+ <script type="module" src="./main.js"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,9 @@
1
+ // The Canopy SDK is served by the platform itself — nothing to install.
2
+ // Auth (auth), data (db), files (blob) and AI (ai) are all live — see
3
+ // AGENTS.md for how to get the full SDK reference.
4
+ import { createCanopy } from "/_api/v1/sdk.js";
5
+
6
+ const canopy = createCanopy();
7
+
8
+ const user = await canopy.auth.user();
9
+ document.getElementById("status").textContent = `signed in as ${user.email}`;
@@ -0,0 +1,29 @@
1
+ :root {
2
+ font-family: system-ui, sans-serif;
3
+ color-scheme: light dark;
4
+ }
5
+
6
+ body {
7
+ margin: 0;
8
+ display: grid;
9
+ place-items: center;
10
+ min-height: 100vh;
11
+ }
12
+
13
+ main {
14
+ max-width: 40rem;
15
+ padding: 2rem;
16
+ text-align: center;
17
+ }
18
+
19
+ .status {
20
+ font-size: 0.9rem;
21
+ opacity: 0.7;
22
+ }
23
+
24
+ .placeholder {
25
+ border: 1px dashed color-mix(in srgb, currentColor 30%, transparent);
26
+ border-radius: 8px;
27
+ padding: 1.5rem;
28
+ opacity: 0.6;
29
+ }
@@ -0,0 +1,34 @@
1
+ # Deploy to Canopy on every push to main. GitHub's runners are the build
2
+ # farm — the platform owns no build infrastructure.
3
+ #
4
+ # Needs a CANOPY_DEPLOY_TOKEN repo secret: create a deploy token in the
5
+ # dashboard (project → Settings → Deploy tokens) or with `canopy token
6
+ # create`, then add it via `gh secret set CANOPY_DEPLOY_TOKEN` or the repo's
7
+ # Settings → Secrets. Until the secret is set, this workflow skips itself
8
+ # instead of failing the build.
9
+ name: Deploy to Canopy
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ env:
19
+ CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: 22
25
+ - name: Install dependencies
26
+ if: env.CANOPY_DEPLOY_TOKEN != ''
27
+ run: |
28
+ if [ -f package-lock.json ]; then npm ci; else npm install; fi
29
+ - name: Deploy
30
+ if: env.CANOPY_DEPLOY_TOKEN != ''
31
+ run: npm exec --yes @mediamonks/canopy-cli deploy
32
+ - name: Skipped (no deploy token)
33
+ if: env.CANOPY_DEPLOY_TOKEN == ''
34
+ run: echo "CANOPY_DEPLOY_TOKEN secret not set — skipping deploy."
@@ -0,0 +1,51 @@
1
+ # {{name}} — a Canopy React SPA with a backend Worker
2
+
3
+ A Vite + React single-page app plus a per-project backend Worker on
4
+ [Canopy](https://dashboard.thanarie.dev), Monks' managed platform for internal
5
+ projects. Everything is served behind company login; there is no
6
+ infrastructure to manage and none should be added.
7
+
8
+ > **Phase 2 preview:** per-project Workers are not deployable yet.
9
+ > `canopy deploy` publishes the frontend only; `worker/` is picked up
10
+ > automatically once the platform supports it.
11
+
12
+ ## Canopy skill
13
+
14
+ The full SDK reference (auth, db, blob, ai) and platform guardrails ship as an
15
+ agent skill, kept out of this scaffold so it stays current. Install it before
16
+ building features:
17
+
18
+ ```
19
+ npx skills add mediamonks/canopy
20
+ ```
21
+
22
+ If that fails the skill isn't published yet — the SDK surface is documented on
23
+ the [Canopy dashboard](https://dashboard.thanarie.dev).
24
+
25
+ ## Working on this project
26
+
27
+ - Frontend: a standard Vite + React + TypeScript app in `src/`; `npm install`
28
+ once, then develop with `canopy dev` (it runs Vite with this project's
29
+ `vite.config.ts` and proxies `/_api` to the platform).
30
+ - Backend: Hono-style handlers in `worker/index.ts`, serving `/api/*`. It runs
31
+ behind the Canopy gateway (requests arrive authenticated) and reaches the
32
+ platform through a service binding — same SDK, same budgets and data
33
+ isolation as the browser.
34
+ - The SDK comes from npm (`@mediamonks/canopy`) — the browser's shared
35
+ instance lives in `src/canopy.ts`.
36
+ - `canopy dev` uses LIVE project data: read freely, but take a
37
+ `canopy db snapshot` before risky data changes.
38
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead;
39
+ writes never reach the platform. Prefer it for destructive experiments.
40
+ - `canopy deploy` — builds (`npm run build`) and publishes; live (behind
41
+ company login) at https://{{slug}}.thanarie.dev.
42
+
43
+ ## Boundaries
44
+
45
+ - Do not add auth — the platform authenticates every request before it
46
+ reaches this app or the Worker.
47
+ - Do not store secrets anywhere in this project — platform access goes
48
+ through the Canopy SDK (`canopy.auth`, `canopy.db`, `canopy.blob`,
49
+ `canopy.ai`) in both the browser and the Worker.
50
+ - The Worker is the only backend: no SSR, no long-running processes, no
51
+ databases outside `canopy.db`.
@@ -0,0 +1,18 @@
1
+ # {{name}}
2
+
3
+ A [Canopy](https://dashboard.thanarie.dev) project — Vite + React with a
4
+ backend Worker (`worker/`). Live (behind company login) at
5
+ https://{{slug}}.thanarie.dev after the first `canopy deploy`.
6
+
7
+ > Per-project Workers are a Canopy Phase 2 capability: `canopy deploy`
8
+ > publishes the frontend only for now — `worker/` deploys automatically once
9
+ > the platform supports it.
10
+
11
+ - `npm install` — once, then:
12
+ - `canopy dev` — develop locally against the live platform (real data!)
13
+ - `canopy dev --local [--pull]` — offline sandbox with local data instead
14
+ - `canopy deploy` — build and publish `dist/`
15
+ - `AGENTS.md` — project guardrails and skill setup for coding agents
16
+
17
+ Push-to-main deploys are prepared in `.github/workflows/canopy-deploy.yml`
18
+ (needs a `CANOPY_DEPLOY_TOKEN` repo secret — see the workflow file).
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ .canopy/
4
+ .DS_Store
@@ -0,0 +1,6 @@
1
+ {
2
+ "slug": "{{slug}}",
3
+ "build": "npm run build",
4
+ "dist": "dist",
5
+ "worker": "worker/index.ts"
6
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{name}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "{{slug}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "canopy dev",
7
+ "build": "tsc && vite build"
8
+ },
9
+ "dependencies": {
10
+ "@mediamonks/canopy": "^0.0.1",
11
+ "hono": "^4.8.0",
12
+ "react": "^19.1.0",
13
+ "react-dom": "^19.1.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^19.1.0",
17
+ "@types/react-dom": "^19.1.0",
18
+ "@vitejs/plugin-react": "^5.0.0",
19
+ "typescript": "^5.8.0",
20
+ "vite": "^7.0.0"
21
+ }
22
+ }
@@ -0,0 +1,25 @@
1
+ import { useEffect, useState } from "react";
2
+ import { canopy } from "./canopy.ts";
3
+
4
+ export function App() {
5
+ const [status, setStatus] = useState("connecting to Canopy…");
6
+
7
+ useEffect(() => {
8
+ canopy.auth
9
+ .user()
10
+ .then((user) => setStatus(`signed in as ${user.email}`))
11
+ .catch(() => setStatus("could not reach Canopy — is `canopy dev` running?"));
12
+ }, []);
13
+
14
+ return (
15
+ <main>
16
+ <h1>{"{{name}}"}</h1>
17
+ <p className="status">{status}</p>
18
+ <section>
19
+ <p className="placeholder">
20
+ Start building — edit <code>src/app.tsx</code>, then run <code>canopy deploy</code>.
21
+ </p>
22
+ </section>
23
+ </main>
24
+ );
25
+ }
@@ -0,0 +1,5 @@
1
+ // One SDK instance for the whole app. Auth (auth), data (db), files (blob)
2
+ // and AI (ai) are all live — see AGENTS.md for how to get the full reference.
3
+ import { createCanopy } from "@mediamonks/canopy";
4
+
5
+ export const canopy = createCanopy();
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./app.tsx";
4
+ import "./style.css";
5
+
6
+ createRoot(document.getElementById("root") as HTMLElement).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );