@mediamonks/canopy-cli 0.7.29 → 0.7.30
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-fAvjdjVX.mjs +54 -0
- package/dist/docs/cli-ai.md +43 -0
- package/dist/docs/cli.md +2 -0
- package/dist/docs/index.json +5 -0
- package/dist/docs/platform-ai-usage.md +6 -4
- package/dist/docs/sdk-ai.md +41 -10
- package/dist/{docs-ZQnhFSKT.mjs → docs-Dg0UJugG.mjs} +1 -1
- package/dist/{feedback-CNfUjjNw.mjs → feedback-Dvb6CdlP.mjs} +1 -1
- package/dist/index.mjs +7 -5
- package/dist/{package-DzU_2IJi.mjs → package-CnS450Yc.mjs} +1 -1
- package/dist/skill/canopy/SKILL.md +3 -0
- package/dist/{skill-CQ4a__Ga.mjs → skill-CuES7kPj.mjs} +1 -1
- package/dist/{upgrade-BzmIrnjs.mjs → upgrade-C8YdSKKd.mjs} +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-CeupNHNa.mjs";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
//#region src/ai.ts
|
|
4
|
+
/**
|
|
5
|
+
* canopy ai models — the site's AI model list (OpenSpec change
|
|
6
|
+
* ai-model-catalog): the auto/* aliases with what they resolve to, the
|
|
7
|
+
* default, available models with prices, and legacy models with their
|
|
8
|
+
* replacements. Site-wide, so it needs no linked project; `--profile` picks
|
|
9
|
+
* the site. Agents should check this before naming a model in code.
|
|
10
|
+
*/
|
|
11
|
+
function perMTok(n) {
|
|
12
|
+
return `$${Number.isInteger(n) ? n : n.toFixed(2).replace(/0$/, "")}`;
|
|
13
|
+
}
|
|
14
|
+
function renderModels(site) {
|
|
15
|
+
const lines = ["Aliases (prefer these over model ids)"];
|
|
16
|
+
for (const alias of site.aliases) {
|
|
17
|
+
const target = alias.resolvesTo ?? "— (no model on this site)";
|
|
18
|
+
const tag = alias.id === site.default ? " (default)" : "";
|
|
19
|
+
lines.push(` ${alias.id.padEnd(12)} → ${target}${tag}`);
|
|
20
|
+
}
|
|
21
|
+
lines.push("", "Models (USD per 1M tokens, in / out)");
|
|
22
|
+
if (site.models.length === 0) lines.push(" none — AI is not enabled on this site");
|
|
23
|
+
const width = Math.max(0, ...site.models.map((m) => m.id.length));
|
|
24
|
+
for (const model of site.models) lines.push(` ${model.id.padEnd(width)} ${perMTok(model.inputPerMTok)} / ${perMTok(model.outputPerMTok)}`);
|
|
25
|
+
if (site.legacy.length > 0) {
|
|
26
|
+
lines.push("", "Legacy (still answered, by the replacement)");
|
|
27
|
+
const legacyWidth = Math.max(...site.legacy.map((m) => m.id.length));
|
|
28
|
+
for (const model of site.legacy) lines.push(` ${model.id.padEnd(legacyWidth)} → ${model.replacement}`);
|
|
29
|
+
}
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
32
|
+
const aiCommand = defineCommand({
|
|
33
|
+
meta: {
|
|
34
|
+
name: "ai",
|
|
35
|
+
description: "AI models available to projects on this site"
|
|
36
|
+
},
|
|
37
|
+
subCommands: { models: defineCommand({
|
|
38
|
+
meta: {
|
|
39
|
+
name: "models",
|
|
40
|
+
description: "List the AI models and auto/* aliases this site offers"
|
|
41
|
+
},
|
|
42
|
+
args: { json: {
|
|
43
|
+
type: "boolean",
|
|
44
|
+
description: "Print raw JSON (same shape as GET /control/v1/ai/models)",
|
|
45
|
+
default: false
|
|
46
|
+
} },
|
|
47
|
+
run: reportErrors("ai models", async ({ args }) => {
|
|
48
|
+
const site = await withContext(apiRequest("GET", "/ai/models"), { action: "list the site's AI models" });
|
|
49
|
+
console.log(args.json ? JSON.stringify(site, null, 2) : renderModels(site));
|
|
50
|
+
})
|
|
51
|
+
}) }
|
|
52
|
+
});
|
|
53
|
+
//#endregion
|
|
54
|
+
export { aiCommand };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: canopy ai models
|
|
3
|
+
description: List the AI models and auto/* aliases your site offers, with prices.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`canopy ai models` shows which AI models projects on your site can use. Run it before you name a model in code: only names on this list work.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
canopy ai models [--json]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Arguments & flags
|
|
15
|
+
|
|
16
|
+
- `--json`: print the raw JSON instead of the formatted list. Default `false`.
|
|
17
|
+
- `--profile <id>`: which site to ask, as with every command.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
The list belongs to the site, not to a project, so the command works from any directory. It prints three parts:
|
|
22
|
+
|
|
23
|
+
- **Aliases.** `auto/low`, `auto/mid` and `auto/high`, each with the model it answers with right now. The default (`auto/low`) is marked. Aliases follow newer models as they arrive, so prefer them over fixed model ids.
|
|
24
|
+
- **Models.** Every model you can name, with its price per million input and output tokens.
|
|
25
|
+
- **Legacy.** Retired models that still work, and the model that now answers for each.
|
|
26
|
+
|
|
27
|
+
It's read-only. With `--json`, the output is ready for scripts and agents.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
canopy ai models
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Shows the aliases, models and prices for your active site.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
canopy ai models --json | jq -r '.aliases[] | "\(.id) \(.resolvesTo)"'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Prints what each alias answers with right now.
|
|
42
|
+
|
|
43
|
+
See also [AI](https://docs.canopy.website/sdk/ai) for calling models from your project, and [AI access & usage](https://docs.canopy.website/platform/ai-usage) for budgets and spend.
|
package/dist/docs/cli.md
CHANGED
|
@@ -60,6 +60,8 @@ has a full page per command.
|
|
|
60
60
|
`--snapshot <key>`. `--yes` skips the confirmation. **Destructive.**
|
|
61
61
|
- `canopy usage` — AI spend and storage for the project (same data as the
|
|
62
62
|
dashboard Usage tab). `--project <slug>` / `--json` for agents.
|
|
63
|
+
- `canopy ai models` — the AI models and `auto/*` aliases this site offers,
|
|
64
|
+
with prices. Works from any directory; `--json` for agents.
|
|
63
65
|
|
|
64
66
|
## CI / deploy tokens
|
|
65
67
|
|
package/dist/docs/index.json
CHANGED
|
@@ -276,6 +276,11 @@
|
|
|
276
276
|
"title": "canopy usage",
|
|
277
277
|
"summary": "Show a project's AI spend, storage footprint, and deploy count from the terminal."
|
|
278
278
|
},
|
|
279
|
+
{
|
|
280
|
+
"slug": "cli-ai",
|
|
281
|
+
"title": "canopy ai models",
|
|
282
|
+
"summary": "List the AI models and auto/* aliases your site offers, with prices."
|
|
283
|
+
},
|
|
279
284
|
{
|
|
280
285
|
"slug": "cli-feedback",
|
|
281
286
|
"title": "canopy feedback",
|
|
@@ -21,10 +21,12 @@ const reply = await canopy.ai.chat({
|
|
|
21
21
|
|
|
22
22
|
- **No keys in your code.** Requests authenticate as the project; the
|
|
23
23
|
platform holds the provider credentials.
|
|
24
|
-
- **
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
- **Pick a tier, not a model.** Name `auto/low` (the default), `auto/mid`, or
|
|
25
|
+
`auto/high` and the platform answers with the best current model for it.
|
|
26
|
+
You can also name a specific model from the site's list. `canopy ai models`
|
|
27
|
+
and the Models section on your project's Usage tab show it, with prices.
|
|
28
|
+
Names that aren't on the list are refused, and retired models keep working,
|
|
29
|
+
answered by their replacement, until you switch.
|
|
28
30
|
- **Streaming** via `canopy.ai.chatStream()` for chat-style UIs.
|
|
29
31
|
- **Documents and images.** Messages can attach files — reference something
|
|
30
32
|
in the project's [blob storage](https://docs.canopy.website/sdk/blob) by key, or inline base64 — see
|
package/dist/docs/sdk-ai.md
CHANGED
|
@@ -8,6 +8,29 @@ Every request authenticates as the project and is metered against hard
|
|
|
8
8
|
per-project budgets — spend, limits, and how prompts are handled by the
|
|
9
9
|
model providers are covered in [AI access & usage](https://docs.canopy.website/platform/ai-usage).
|
|
10
10
|
|
|
11
|
+
## Picking a model
|
|
12
|
+
|
|
13
|
+
Name a quality tier instead of a model id, and the platform answers with the
|
|
14
|
+
best current model for it:
|
|
15
|
+
|
|
16
|
+
- `auto/low` — fast and cheap. The **default** when you leave `model` out.
|
|
17
|
+
- `auto/mid` — a good balance for most product features.
|
|
18
|
+
- `auto/high` — the most capable model, for hard reasoning and long documents.
|
|
19
|
+
|
|
20
|
+
Each alias moves to newer models as they arrive, without a code change. You
|
|
21
|
+
can also name a specific model from the site's list. Run
|
|
22
|
+
`canopy ai models` to see the aliases, what each answers with right now, and
|
|
23
|
+
every model with its price:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
canopy ai models
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Only names on that list work: anything else is refused with a `400` that lists
|
|
30
|
+
what is available. When a model is retired, calls naming it keep working,
|
|
31
|
+
answered by its replacement: `res.model` names the model that answered, and
|
|
32
|
+
the response carries a `deprecation` notice (below) until you switch.
|
|
33
|
+
|
|
11
34
|
## ai.chat(options)
|
|
12
35
|
|
|
13
36
|
One-shot chat completion. Takes a `ChatOptions` object, resolves to a
|
|
@@ -19,7 +42,7 @@ const res = await canopy.ai.chat({
|
|
|
19
42
|
{ role: "system", content: "You are terse." },
|
|
20
43
|
{ role: "user", content: "Say hi in five words." },
|
|
21
44
|
],
|
|
22
|
-
model: "
|
|
45
|
+
model: "auto/mid", // optional — defaults to auto/low
|
|
23
46
|
temperature: 0.7, // optional, 0–2
|
|
24
47
|
maxTokens: 256, // optional
|
|
25
48
|
});
|
|
@@ -34,10 +57,10 @@ res.usage; // { inputTokens: number, outputTokens: number }
|
|
|
34
57
|
- `messages` — required, non-empty array of `{ role, content }`. `role` is
|
|
35
58
|
`"system" | "user" | "assistant"`; `content` is a non-empty string, or an
|
|
36
59
|
array of content parts to attach documents and images (below).
|
|
37
|
-
- `model` — optional `
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
- `model` — optional: an alias (`auto/low`, `auto/mid`, `auto/high`) or a
|
|
61
|
+
`"{provider}/{model}"` id from the site's list (`canopy ai models`). Omit it
|
|
62
|
+
and `auto/low` answers. A name that isn't on the list fails with a message
|
|
63
|
+
listing what _is_ available.
|
|
41
64
|
- `temperature` — optional number between 0 and 2. Reasoning models (the
|
|
42
65
|
GPT-5 family) only accept their default temperature, so the platform drops
|
|
43
66
|
this setting for them rather than failing the call — it has effect on
|
|
@@ -52,7 +75,11 @@ res.usage; // { inputTokens: number, outputTokens: number }
|
|
|
52
75
|
|
|
53
76
|
**`ChatResponse`:** `{ message, model, usage: { inputTokens, outputTokens } }`.
|
|
54
77
|
`message.role` is `"assistant"` and `message.content` is always a plain
|
|
55
|
-
string, whatever went in.
|
|
78
|
+
string, whatever went in. `model` is the model that actually answered. When
|
|
79
|
+
you named a retired model, the response also has
|
|
80
|
+
`deprecation: { requested, replacement }`. Switch to the replacement or an
|
|
81
|
+
alias. Streams and the endpoints below say the same with a `Deprecation:
|
|
82
|
+
true` response header.
|
|
56
83
|
|
|
57
84
|
## Attaching documents and images
|
|
58
85
|
|
|
@@ -143,8 +170,10 @@ The compat endpoint speaks OpenAI's chat-completions dialect to every model —
|
|
|
143
170
|
portable, but the lowest common denominator. When you want a provider's own
|
|
144
171
|
protocol — Anthropic prompt caching, PDFs with citations, or OpenAI's
|
|
145
172
|
Responses API — use the native base URLs from `endpoint()`. Requests pass
|
|
146
|
-
through to the provider unchanged; auth, the model
|
|
147
|
-
|
|
173
|
+
through to the provider unchanged; auth, the model list, the daily budget,
|
|
174
|
+
and metering all still apply. Aliases work here too: on the Anthropic endpoint
|
|
175
|
+
`auto/mid` picks the best Claude model for that tier, on the OpenAI endpoint
|
|
176
|
+
the best OpenAI one.
|
|
148
177
|
|
|
149
178
|
```ts
|
|
150
179
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -152,7 +181,7 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
152
181
|
const { native } = await canopy.ai.endpoint();
|
|
153
182
|
const anthropic = new Anthropic({ baseURL: native.anthropic, apiKey: "unused" });
|
|
154
183
|
const msg = await anthropic.messages.create({
|
|
155
|
-
model: "
|
|
184
|
+
model: "auto/mid", // an alias, or a model id from `canopy ai models` (bare or "anthropic/…")
|
|
156
185
|
max_tokens: 1024,
|
|
157
186
|
messages: [{ role: "user", content: "Hello" }],
|
|
158
187
|
});
|
|
@@ -188,7 +217,9 @@ as the `message`. Two worth handling:
|
|
|
188
217
|
or after a maintainer raises it). Everything else in your app keeps
|
|
189
218
|
working.
|
|
190
219
|
- **Invalid request** — status `400`, body contains `"invalid_chat"` and a
|
|
191
|
-
detail explaining which option was rejected.
|
|
220
|
+
detail explaining which option was rejected. A model that isn't on the
|
|
221
|
+
site's list lands here too, with the available aliases and models in the
|
|
222
|
+
detail.
|
|
192
223
|
|
|
193
224
|
> **Note:** Budgets are enforced before every call, so a runaway loop becomes a capped bill — see [AI access
|
|
194
225
|
> & usage](https://docs.canopy.website/platform/ai-usage) for how spend is tracked and displayed.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CnS450Yc.mjs";
|
|
2
2
|
import { n as siteConfig } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
4
|
import { r as reportErrors } from "./ui-CeupNHNa.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CnS450Yc.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-CeupNHNa.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "./package-
|
|
2
|
+
import { t as version } from "./package-CnS450Yc.mjs";
|
|
3
3
|
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-DM2J10CB.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
@@ -17,6 +17,7 @@ import { defineCommand, runMain } from "citty";
|
|
|
17
17
|
* db pull|snapshot|restore [phase 2]
|
|
18
18
|
* logs [--follow] read the project's worker logs [phase 2]
|
|
19
19
|
* usage AI spend + storage for a project [phase 1]
|
|
20
|
+
* ai models the site's AI models and auto/* aliases
|
|
20
21
|
* open open the deployed project / dashboard [phase 1]
|
|
21
22
|
*/
|
|
22
23
|
if (process.env["CANOPY_UPDATE_REFRESH"] === "1") {
|
|
@@ -77,13 +78,14 @@ const main = defineCommand({
|
|
|
77
78
|
egress: () => import("./egress-Cn9zDfDT.mjs").then((m) => m.egressCommand),
|
|
78
79
|
secrets: () => import("./secrets-Bl3uOx4l.mjs").then((m) => m.secretsCommand),
|
|
79
80
|
schedule: () => import("./schedule-CLuksxSM.mjs").then((m) => m.scheduleCommand),
|
|
80
|
-
feedback: () => import("./feedback-
|
|
81
|
+
feedback: () => import("./feedback-Dvb6CdlP.mjs").then((m) => m.feedbackCommand),
|
|
81
82
|
db: () => import("./db-KTLn8J1u.mjs").then((m) => m.dbCommand),
|
|
82
83
|
usage: () => import("./usage-CV7TjTO8.mjs").then((m) => m.usageCommand),
|
|
84
|
+
ai: () => import("./ai-fAvjdjVX.mjs").then((m) => m.aiCommand),
|
|
83
85
|
open: () => import("./open-BJgd0R61.mjs").then((m) => m.openCommand),
|
|
84
|
-
docs: () => import("./docs-
|
|
85
|
-
skill: () => import("./skill-
|
|
86
|
-
upgrade: () => import("./upgrade-
|
|
86
|
+
docs: () => import("./docs-Dg0UJugG.mjs").then((m) => m.docsCommand),
|
|
87
|
+
skill: () => import("./skill-CuES7kPj.mjs").then((m) => m.skillCommand),
|
|
88
|
+
upgrade: () => import("./upgrade-C8YdSKKd.mjs").then((m) => m.upgradeCommand),
|
|
87
89
|
logs: () => import("./logs-D5RWqsWl.mjs").then((m) => m.logsCommand)
|
|
88
90
|
}
|
|
89
91
|
});
|
|
@@ -98,6 +98,9 @@ and never go stale.
|
|
|
98
98
|
- Roll back: `canopy rollback`
|
|
99
99
|
- Open the live site: `canopy open`
|
|
100
100
|
- Manage data: `canopy db pull | snapshot | snapshots | restore | migrate`
|
|
101
|
+
- Use AI (`canopy.ai`): run `canopy ai models` before naming a model in code.
|
|
102
|
+
Prefer the aliases `auto/low` (default) / `auto/mid` / `auto/high` over
|
|
103
|
+
hard-coded ids — model ids outside that list are refused.
|
|
101
104
|
- Work bug reports & feedback: `canopy feedback list | show | comment | status | report`
|
|
102
105
|
— see [Working feedback reports](#working-feedback-reports-issues) below
|
|
103
106
|
- CI credentials: `canopy token create | list | revoke`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CnS450Yc.mjs";
|
|
2
2
|
import { r as reportErrors } from "./ui-CeupNHNa.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-
|
|
115
|
+
const { skillInstalled } = await import("./skill-CuES7kPj.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.
|
|
3
|
+
"version": "0.7.30",
|
|
4
4
|
"description": "Canopy CLI — create, develop and deploy Canopy projects.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"canopy": "./dist/index.mjs"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"esbuild": "^0.28.1",
|
|
21
21
|
"hono": "^4.8.0",
|
|
22
22
|
"vite-server": "npm:vite@^7",
|
|
23
|
-
"@mediamonks/canopy": "0.0.
|
|
23
|
+
"@mediamonks/canopy": "0.0.10",
|
|
24
24
|
"@mediamonks/canopy-data-plane": "0.0.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|