feat: add wmill docs CLI command for querying documentation (#8114)

* feat: add wmill docs CLI command for querying documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show loading message before fetch, include error body, clarify --json description

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
centdix
2026-02-26 18:05:29 +01:00
committed by GitHub
parent cf7f704a91
commit 01c7270cda
2 changed files with 117 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
import { Command } from "@cliffy/command";
import { colors } from "@cliffy/ansi/colors";
import * as log from "../../core/log.ts";
import { requireLogin } from "../../core/auth.ts";
import { resolveWorkspace } from "../../core/context.ts";
import { GlobalOptions } from "../../types.ts";
interface DocContentItem {
title: string;
url: string;
source?: {
content?: Array<{ text: string }>;
};
}
interface InkeepResponse {
choices?: Array<{
message?: {
content?: string;
};
}>;
}
interface ParsedContent {
content?: DocContentItem[];
}
async function docs(
opts: GlobalOptions & { json?: boolean },
query: string
) {
await requireLogin(opts);
const workspace = await resolveWorkspace(opts);
const url = `${workspace.remote}api/inkeep`;
console.log(colors.bold(`\nSearching Windmill docs...\n`));
let res: Response;
try {
res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${workspace.token}`,
},
body: JSON.stringify({ query }),
});
} catch (e) {
throw new Error(`Network error connecting to ${workspace.remote}: ${e}`);
}
if (res.status === 403) {
log.info(
"Windmill documentation search is an Enterprise Edition feature. Please upgrade to use this command."
);
return;
}
if (!res.ok) {
throw new Error(
`Documentation search failed: ${res.status} ${res.statusText}\n${await res.text()}`
);
}
const data = (await res.json()) as InkeepResponse;
const raw = data.choices?.[0]?.message?.content;
if (!raw) {
log.info("No documentation found for this query.");
return;
}
let parsed: ParsedContent;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("Failed to parse documentation response.");
}
const items = parsed.content ?? [];
if (items.length === 0) {
log.info("No documentation found for this query.");
return;
}
if (opts.json) {
console.log(JSON.stringify(items, null, 2));
return;
}
for (const item of items) {
console.log(colors.bold(colors.cyan(`📄 ${item.title}`)));
if (item.url) {
console.log(` ${colors.underline(item.url)}`);
}
const text = item.source?.content?.[0]?.text;
if (text) {
const snippet = text.length > 500 ? text.slice(0, 500) + "..." : text;
console.log(` ${snippet}`);
}
console.log();
}
}
const command = new Command()
.name("docs")
.description("Search Windmill documentation. Requires Enterprise Edition.")
.arguments("<query:string>")
.option("--json", "Output results as JSON.")
.action(docs as any);
export default command;
+3
View File
@@ -39,6 +39,7 @@ import queues from "./commands/queues/queues.ts";
import dependencies from "./commands/dependencies/dependencies.ts";
import init from "./commands/init/init.ts";
import jobs from "./commands/jobs/jobs.ts";
import docs from "./commands/docs/docs.ts";
import { fetchVersion } from "./core/context.ts";
export {
@@ -59,6 +60,7 @@ export {
gitsyncSettings,
instance,
dev,
docs,
hubPull,
pull,
push,
@@ -127,6 +129,7 @@ const command = new Command()
.command("queues", queues)
.command("dependencies", dependencies)
.command("jobs", jobs)
.command("docs", docs)
.command("version --version", "Show version information")
.action(async (opts: any) => {
console.log("CLI version: " + VERSION);