@mediamonks/canopy-cli 0.7.15 → 0.7.16
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/docs/cli-docs.md +10 -0
- package/dist/{docs-BlzdYmou.mjs → docs-D7Ey1ZMQ.mjs} +92 -2
- package/dist/{feedback-h_j6DBBZ.mjs → feedback-B0iX3iZ1.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{package-CF0dX7UA.mjs → package-BGDIsRxe.mjs} +1 -1
- package/dist/skill/canopy/SKILL.md +3 -2
- package/dist/{skill-DaaxgqEk.mjs → skill-Be_pqqdH.mjs} +1 -1
- package/dist/{upgrade-DG-7xwOs.mjs → upgrade-ChwehW_q.mjs} +1 -1
- package/package.json +1 -1
package/dist/docs/cli-docs.md
CHANGED
|
@@ -10,12 +10,14 @@ description: Read the Canopy docs from the terminal — live from the platform,
|
|
|
10
10
|
```bash
|
|
11
11
|
canopy docs # list all topics
|
|
12
12
|
canopy docs <topic> # print one topic
|
|
13
|
+
canopy docs search <terms…> # keyword search across all docs
|
|
13
14
|
canopy docs <topic> --refresh
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
## Arguments & flags
|
|
17
18
|
|
|
18
19
|
- `[topic]` — positional: the topic slug to print. Omit it to list every topic with a one-line summary, grouped by section. Guides keep bare slugs (`deploy`, `staging`); other sections prefix theirs (`sdk-database`, `cli-deploy`, `platform-schedules`).
|
|
20
|
+
- `search <terms…>` — search every doc for the given terms instead of printing one. Case-insensitive; a doc matches only when every term appears somewhere in its title, summary, or body. Results come back best-first (title and summary hits outrank body mentions), each with the matching lines, capped at ten.
|
|
19
21
|
- `--refresh` — re-fetch from the platform, bypassing the local cache. Default `false`.
|
|
20
22
|
|
|
21
23
|
## How it works
|
|
@@ -30,6 +32,8 @@ That last step is why `canopy docs` works with no network and no login: a fresh
|
|
|
30
32
|
|
|
31
33
|
Asking for an unknown topic errors with the list of available slugs. Everything prints as plain Markdown to stdout, so it pipes cleanly into a pager or an agent's context.
|
|
32
34
|
|
|
35
|
+
Search runs entirely on the local corpus — the cache plus the bundled snapshot — so it's instant and works offline. It's a locator, not a reader: find the topic, then print it with `canopy docs <topic>` (which fetches fresh content when it can).
|
|
36
|
+
|
|
33
37
|
## Examples
|
|
34
38
|
|
|
35
39
|
```bash
|
|
@@ -38,6 +42,12 @@ canopy docs
|
|
|
38
42
|
|
|
39
43
|
Lists every topic with its summary — the starting point when you're not sure which guide covers what you're doing.
|
|
40
44
|
|
|
45
|
+
```bash
|
|
46
|
+
canopy docs search egress allowlist
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Finds the docs that mention both terms — useful when the summaries don't tell you which doc covers a detail, like a specific error code or environment variable.
|
|
50
|
+
|
|
41
51
|
```bash
|
|
42
52
|
canopy docs deploy --refresh
|
|
43
53
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BGDIsRxe.mjs";
|
|
2
2
|
import { n as siteConfig } from "./site-BUlQR117.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
4
|
import { n as reportErrors } from "./ui-B1Px5y7q.mjs";
|
|
@@ -14,6 +14,10 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
* never goes stale, with a local cache and a bundled snapshot as offline
|
|
15
15
|
* fallbacks. The listing is grouped per docs-site section.
|
|
16
16
|
*
|
|
17
|
+
* `canopy docs search <terms…>` greps the locally available corpus (cache +
|
|
18
|
+
* bundled snapshot) — the corpus is small enough (~60 docs) that keyword
|
|
19
|
+
* matching beats any index, and it works fully offline.
|
|
20
|
+
*
|
|
17
21
|
* The platform requires a login for docs (internal platform), so fetches carry
|
|
18
22
|
* the CLI token when there is one; without it we simply fall back to the
|
|
19
23
|
* bundled snapshot, so `canopy docs` still works before sign-in.
|
|
@@ -140,6 +144,75 @@ function bodyFromFallback(slug) {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
}
|
|
147
|
+
const SEARCH_LIMIT = 10;
|
|
148
|
+
const LINE_LIMIT = 2;
|
|
149
|
+
const LINE_WIDTH = 120;
|
|
150
|
+
/** A matching body line, trimmed and windowed around the first term hit. */
|
|
151
|
+
function excerpt(line, matchIndex) {
|
|
152
|
+
const clean = line.replace(/\s+/g, " ").trim();
|
|
153
|
+
if (clean.length <= LINE_WIDTH) return clean;
|
|
154
|
+
const start = Math.max(0, Math.min(matchIndex - Math.floor(LINE_WIDTH / 3), clean.length - LINE_WIDTH));
|
|
155
|
+
const slice = clean.slice(start, start + LINE_WIDTH);
|
|
156
|
+
return `${start > 0 ? "…" : ""}${slice}${start + LINE_WIDTH < clean.length ? "…" : ""}`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Keyword search over the docs corpus: case-insensitive, every term must match
|
|
160
|
+
* somewhere in a doc (title, summary, or body). Title and summary hits weigh
|
|
161
|
+
* more than body occurrences; results come back best-first.
|
|
162
|
+
*/
|
|
163
|
+
function searchDocs(entries, query) {
|
|
164
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
165
|
+
if (terms.length === 0) return [];
|
|
166
|
+
const hits = [];
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
const title = entry.title.toLowerCase();
|
|
169
|
+
const summary = entry.summary.toLowerCase();
|
|
170
|
+
const body = entry.body.toLowerCase();
|
|
171
|
+
let score = 0;
|
|
172
|
+
let matchesAll = true;
|
|
173
|
+
for (const term of terms) {
|
|
174
|
+
let termScore = 0;
|
|
175
|
+
if (title.includes(term)) termScore += 10;
|
|
176
|
+
if (summary.includes(term)) termScore += 5;
|
|
177
|
+
let count = 0;
|
|
178
|
+
for (let i = body.indexOf(term); i !== -1 && count < 20; i = body.indexOf(term, i + term.length)) count++;
|
|
179
|
+
termScore += count;
|
|
180
|
+
if (termScore === 0) {
|
|
181
|
+
matchesAll = false;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
score += termScore;
|
|
185
|
+
}
|
|
186
|
+
if (!matchesAll) continue;
|
|
187
|
+
const lines = [];
|
|
188
|
+
for (const line of entry.body.split("\n")) {
|
|
189
|
+
const lower = line.toLowerCase();
|
|
190
|
+
const at = terms.map((t) => lower.indexOf(t)).find((i) => i !== -1);
|
|
191
|
+
if (at !== void 0 && line.trim()) {
|
|
192
|
+
lines.push(excerpt(line, at));
|
|
193
|
+
if (lines.length >= LINE_LIMIT) break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
hits.push({
|
|
197
|
+
slug: entry.slug,
|
|
198
|
+
title: entry.title,
|
|
199
|
+
score,
|
|
200
|
+
lines
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return hits.sort((a, b) => b.score - a.score || a.slug.localeCompare(b.slug));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Every topic paired with its locally available body (cache first, bundled
|
|
207
|
+
* snapshot second). A topic with no local body still gets searched by title
|
|
208
|
+
* and summary — search never hits the network per doc.
|
|
209
|
+
*/
|
|
210
|
+
function searchEntries(index) {
|
|
211
|
+
return index.topics.map((t) => ({
|
|
212
|
+
...t,
|
|
213
|
+
body: bodyFromFallback(t.slug) ?? ""
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
143
216
|
async function listIndex(refresh) {
|
|
144
217
|
const { apiUrl } = siteConfig();
|
|
145
218
|
if (refresh || !cacheFresh()) {
|
|
@@ -174,7 +247,7 @@ const docsCommand = defineCommand({
|
|
|
174
247
|
topic: {
|
|
175
248
|
type: "positional",
|
|
176
249
|
required: false,
|
|
177
|
-
description: "Topic to print (omit to list)"
|
|
250
|
+
description: "Topic to print (omit to list), or `search <terms…>`"
|
|
178
251
|
},
|
|
179
252
|
refresh: {
|
|
180
253
|
type: "boolean",
|
|
@@ -187,6 +260,23 @@ const docsCommand = defineCommand({
|
|
|
187
260
|
const topic = args.topic;
|
|
188
261
|
const latest = latestKnownVersion();
|
|
189
262
|
if (latest && compareSemver(version, latest) < 0) console.error(`Note: these docs describe CLI ${latest}; you have ${version} — commands or flags they mention may not exist here. Run \`canopy upgrade\`.\n`);
|
|
263
|
+
if (topic === "search") {
|
|
264
|
+
const query = args._.slice(1).join(" ").trim();
|
|
265
|
+
if (!query) throw new Error("Usage: canopy docs search <terms…>");
|
|
266
|
+
const hits = searchDocs(searchEntries(await listIndex(refresh)), query);
|
|
267
|
+
if (hits.length === 0) {
|
|
268
|
+
console.log(`No docs match "${query}". Run \`canopy docs\` to list all topics.`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const shown = hits.slice(0, SEARCH_LIMIT);
|
|
272
|
+
console.log(`${hits.length} doc${hits.length === 1 ? "" : "s"} match "${query}" — run \`canopy docs <topic>\` to read one:\n`);
|
|
273
|
+
for (const hit of shown) {
|
|
274
|
+
console.log(`${hit.slug} — ${hit.title}`);
|
|
275
|
+
for (const line of hit.lines) console.log(` ${line}`);
|
|
276
|
+
}
|
|
277
|
+
if (hits.length > shown.length) console.log(`\n…and ${hits.length - shown.length} more — add terms to narrow it down.`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
190
280
|
if (!topic) {
|
|
191
281
|
const index = await listIndex(refresh);
|
|
192
282
|
if (index.topics.length === 0) throw new Error("No docs available — check your connection, or reinstall the CLI.");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BGDIsRxe.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-BUlQR117.mjs";
|
|
3
3
|
import { c as apiRequest, l as apiUpload, n as reportErrors, s as apiDownload } from "./ui-B1Px5y7q.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-BGDIsRxe.mjs";
|
|
3
3
|
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-BUlQR117.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
@@ -76,13 +76,13 @@ const main = defineCommand({
|
|
|
76
76
|
token: () => import("./token-BUxgRJVA.mjs").then((m) => m.tokenCommand),
|
|
77
77
|
egress: () => import("./egress-PJp0SZy2.mjs").then((m) => m.egressCommand),
|
|
78
78
|
schedule: () => import("./schedule-Cq9K0rnE.mjs").then((m) => m.scheduleCommand),
|
|
79
|
-
feedback: () => import("./feedback-
|
|
79
|
+
feedback: () => import("./feedback-B0iX3iZ1.mjs").then((m) => m.feedbackCommand),
|
|
80
80
|
db: () => import("./db-DKnJQPm5.mjs").then((m) => m.dbCommand),
|
|
81
81
|
usage: () => import("./usage-DjNngyvy.mjs").then((m) => m.usageCommand),
|
|
82
82
|
open: () => import("./open-C5x3tX6I.mjs").then((m) => m.openCommand),
|
|
83
|
-
docs: () => import("./docs-
|
|
84
|
-
skill: () => import("./skill-
|
|
85
|
-
upgrade: () => import("./upgrade-
|
|
83
|
+
docs: () => import("./docs-D7Ey1ZMQ.mjs").then((m) => m.docsCommand),
|
|
84
|
+
skill: () => import("./skill-Be_pqqdH.mjs").then((m) => m.skillCommand),
|
|
85
|
+
upgrade: () => import("./upgrade-ChwehW_q.mjs").then((m) => m.upgradeCommand),
|
|
86
86
|
logs: () => import("./logs-C53y2eAS.mjs").then((m) => m.logsCommand)
|
|
87
87
|
}
|
|
88
88
|
});
|
|
@@ -71,8 +71,9 @@ The CLI ships the full documentation and keeps it fresh from the platform. List
|
|
|
71
71
|
the topics and read the relevant one _before_ running unfamiliar commands:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
canopy docs
|
|
75
|
-
canopy docs deploy
|
|
74
|
+
canopy docs # list all topics
|
|
75
|
+
canopy docs deploy # print one topic
|
|
76
|
+
canopy docs search egress allowlist # find the topics that mention all terms
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
The listing is grouped by section: guides (`getting-started`, `deploy`, `dev`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-BGDIsRxe.mjs";
|
|
2
2
|
import { n as reportErrors } from "./ui-B1Px5y7q.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-Be_pqqdH.mjs");
|
|
116
116
|
const refreshSkill = skillInstalled(process.cwd());
|
|
117
117
|
console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
|
|
118
118
|
await run(command, cmd);
|