@awesomate/hosting-mcp 0.19.2 → 0.20.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.
package/dist/index.js
CHANGED
|
@@ -11593,7 +11593,7 @@ var require_formdata = __commonJS({
|
|
|
11593
11593
|
var { File: NativeFile } = __require("node:buffer");
|
|
11594
11594
|
var nodeUtil = __require("node:util");
|
|
11595
11595
|
var File2 = globalThis.File ?? NativeFile;
|
|
11596
|
-
var
|
|
11596
|
+
var FormData2 = class _FormData {
|
|
11597
11597
|
constructor(form) {
|
|
11598
11598
|
webidl.util.markAsUncloneable(this);
|
|
11599
11599
|
if (form !== void 0) {
|
|
@@ -11695,8 +11695,8 @@ var require_formdata = __commonJS({
|
|
|
11695
11695
|
return `FormData ${output.slice(output.indexOf("]") + 2)}`;
|
|
11696
11696
|
}
|
|
11697
11697
|
};
|
|
11698
|
-
iteratorMixin("FormData",
|
|
11699
|
-
Object.defineProperties(
|
|
11698
|
+
iteratorMixin("FormData", FormData2, kState, "name", "value");
|
|
11699
|
+
Object.defineProperties(FormData2.prototype, {
|
|
11700
11700
|
append: kEnumerableProperty,
|
|
11701
11701
|
delete: kEnumerableProperty,
|
|
11702
11702
|
get: kEnumerableProperty,
|
|
@@ -11724,7 +11724,7 @@ var require_formdata = __commonJS({
|
|
|
11724
11724
|
}
|
|
11725
11725
|
return { name, value };
|
|
11726
11726
|
}
|
|
11727
|
-
module.exports = { FormData, makeEntry };
|
|
11727
|
+
module.exports = { FormData: FormData2, makeEntry };
|
|
11728
11728
|
}
|
|
11729
11729
|
});
|
|
11730
11730
|
|
|
@@ -11994,7 +11994,7 @@ var require_body = __commonJS({
|
|
|
11994
11994
|
extractMimeType,
|
|
11995
11995
|
utf8DecodeBytes
|
|
11996
11996
|
} = require_util3();
|
|
11997
|
-
var { FormData } = require_formdata();
|
|
11997
|
+
var { FormData: FormData2 } = require_formdata();
|
|
11998
11998
|
var { kState } = require_symbols2();
|
|
11999
11999
|
var { webidl } = require_webidl();
|
|
12000
12000
|
var { Blob: Blob2 } = __require("node:buffer");
|
|
@@ -12214,13 +12214,13 @@ Content-Type: ${value.type || "application/octet-stream"}\r
|
|
|
12214
12214
|
if (parsed === "failure") {
|
|
12215
12215
|
throw new TypeError("Failed to parse body as FormData.");
|
|
12216
12216
|
}
|
|
12217
|
-
const fd = new
|
|
12217
|
+
const fd = new FormData2();
|
|
12218
12218
|
fd[kState] = parsed;
|
|
12219
12219
|
return fd;
|
|
12220
12220
|
}
|
|
12221
12221
|
case "application/x-www-form-urlencoded": {
|
|
12222
12222
|
const entries = new URLSearchParams(value.toString());
|
|
12223
|
-
const fd = new
|
|
12223
|
+
const fd = new FormData2();
|
|
12224
12224
|
for (const [name, value2] of entries) {
|
|
12225
12225
|
fd.append(name, value2);
|
|
12226
12226
|
}
|
|
@@ -18891,7 +18891,7 @@ var require_response = __commonJS({
|
|
|
18891
18891
|
} = require_constants3();
|
|
18892
18892
|
var { kState, kHeaders } = require_symbols2();
|
|
18893
18893
|
var { webidl } = require_webidl();
|
|
18894
|
-
var { FormData } = require_formdata();
|
|
18894
|
+
var { FormData: FormData2 } = require_formdata();
|
|
18895
18895
|
var { URLSerializer } = require_data_url();
|
|
18896
18896
|
var { kConstruct } = require_symbols();
|
|
18897
18897
|
var assert2 = __require("node:assert");
|
|
@@ -19204,7 +19204,7 @@ var require_response = __commonJS({
|
|
|
19204
19204
|
ReadableStream
|
|
19205
19205
|
);
|
|
19206
19206
|
webidl.converters.FormData = webidl.interfaceConverter(
|
|
19207
|
-
|
|
19207
|
+
FormData2
|
|
19208
19208
|
);
|
|
19209
19209
|
webidl.converters.URLSearchParams = webidl.interfaceConverter(
|
|
19210
19210
|
URLSearchParams
|
|
@@ -39924,6 +39924,56 @@ async function hubRequest(config3, method, path, jsonBody, opts = {}) {
|
|
|
39924
39924
|
}
|
|
39925
39925
|
return body;
|
|
39926
39926
|
}
|
|
39927
|
+
async function hubUploadFile(config3, path, file) {
|
|
39928
|
+
let blob;
|
|
39929
|
+
try {
|
|
39930
|
+
const { openAsBlob } = await import("node:fs");
|
|
39931
|
+
if (typeof openAsBlob === "function") {
|
|
39932
|
+
blob = await openAsBlob(file.localPath, { type: file.contentType });
|
|
39933
|
+
} else {
|
|
39934
|
+
throw new Error("openAsBlob unavailable");
|
|
39935
|
+
}
|
|
39936
|
+
} catch {
|
|
39937
|
+
const { readFile } = await import("node:fs/promises");
|
|
39938
|
+
blob = new Blob([await readFile(file.localPath)], { type: file.contentType });
|
|
39939
|
+
}
|
|
39940
|
+
const form = new FormData();
|
|
39941
|
+
form.append("file", blob, file.filename);
|
|
39942
|
+
const controller = new AbortController();
|
|
39943
|
+
const timer = setTimeout(() => controller.abort(), 6e5);
|
|
39944
|
+
let res;
|
|
39945
|
+
try {
|
|
39946
|
+
res = await (0, import_undici.fetch)(`${config3.apiBase}${path}`, {
|
|
39947
|
+
method: "POST",
|
|
39948
|
+
headers: { Authorization: `Bearer ${config3.pat}` },
|
|
39949
|
+
body: form,
|
|
39950
|
+
signal: controller.signal,
|
|
39951
|
+
dispatcher: proxyDispatcher
|
|
39952
|
+
});
|
|
39953
|
+
} catch (err) {
|
|
39954
|
+
const aborted2 = err instanceof Error && err.name === "AbortError";
|
|
39955
|
+
throw new HubApiError(
|
|
39956
|
+
aborted2 ? "The upload timed out after 10 minutes. Large media can exceed this \u2014 try a smaller file, or upload it in the hub at Knowledge \u2192 Sources." : `Could not reach the Awesomate hub at ${config3.apiBase} to upload. This is a connectivity issue, not an auth problem.`,
|
|
39957
|
+
0,
|
|
39958
|
+
null
|
|
39959
|
+
);
|
|
39960
|
+
} finally {
|
|
39961
|
+
clearTimeout(timer);
|
|
39962
|
+
}
|
|
39963
|
+
const text = await res.text();
|
|
39964
|
+
let body;
|
|
39965
|
+
try {
|
|
39966
|
+
body = text ? JSON.parse(text) : null;
|
|
39967
|
+
} catch {
|
|
39968
|
+
body = text;
|
|
39969
|
+
}
|
|
39970
|
+
if (!res.ok) {
|
|
39971
|
+
const serverMsg = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : `Upload failed (${res.status})`;
|
|
39972
|
+
const hint = res.status === 413 ? " The file exceeds the 100 MB limit. Split it, compress it, or upload it in the hub at Knowledge \u2192 Sources." : res.status === 402 ? " NOTHING was ingested and nothing was purchased \u2014 the ingestion allowance is exhausted. State the pack price (1 credit = $100) and let the user decide in the hub." : "";
|
|
39973
|
+
throw new HubApiError(`${/[.!?]$/.test(serverMsg) ? serverMsg : `${serverMsg}.`}${hint}`, res.status, body);
|
|
39974
|
+
}
|
|
39975
|
+
return body;
|
|
39976
|
+
}
|
|
39927
39977
|
function hubGet(config3, path) {
|
|
39928
39978
|
return hubRequest(config3, "GET", path);
|
|
39929
39979
|
}
|
|
@@ -40165,7 +40215,7 @@ async function knowledgeSources(config3, args) {
|
|
|
40165
40215
|
if (!args.url && !args.sitemap) {
|
|
40166
40216
|
return {
|
|
40167
40217
|
error: "invalid_request",
|
|
40168
|
-
note: "add needs url or sitemap. For a local FILE
|
|
40218
|
+
note: "add needs url or sitemap. For a local FILE on this machine, use awesomate_knowledge_upload \u2014 it reads the file off disk and streams it, so the bytes never pass through the conversation."
|
|
40169
40219
|
};
|
|
40170
40220
|
}
|
|
40171
40221
|
const body = args.url ? { url: args.url } : { sitemap: args.sitemap, ...args.since ? { since: args.since } : {} };
|
|
@@ -40221,9 +40271,24 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
40221
40271
|
locator: s.locator ?? s.section_path ?? null,
|
|
40222
40272
|
url: s.url ?? null
|
|
40223
40273
|
}));
|
|
40274
|
+
const answer = typeof meta.answer_plain === "string" && meta.answer_plain ? meta.answer_plain : streamedAnswer;
|
|
40275
|
+
if (sources.length === 0) {
|
|
40276
|
+
return {
|
|
40277
|
+
status: "ok",
|
|
40278
|
+
grounded: false,
|
|
40279
|
+
answer,
|
|
40280
|
+
sources: [],
|
|
40281
|
+
not_in_verified_content: true,
|
|
40282
|
+
configured_fallback: fallback,
|
|
40283
|
+
...typeof meta.score === "number" ? { score: meta.score } : {},
|
|
40284
|
+
...session,
|
|
40285
|
+
note: "UNGROUNDED: the platform returned ok but ZERO sources, which means this answer came from the model, NOT from the verified content. Do NOT present it as an answer from their knowledge base, do not quote it as fact, and do not build on it. Tell the user their content does not cover this. If they need it to refuse instead of improvising, the agent's grounding setting is not strict \u2014 a purpose-built agent (awesomate_knowledge_agents) can be scoped and set strict, and that is what should be wired to anything customer-facing."
|
|
40286
|
+
};
|
|
40287
|
+
}
|
|
40224
40288
|
return {
|
|
40225
40289
|
status: "ok",
|
|
40226
|
-
|
|
40290
|
+
grounded: true,
|
|
40291
|
+
answer,
|
|
40227
40292
|
sources,
|
|
40228
40293
|
...typeof meta.score === "number" ? { score: meta.score } : {},
|
|
40229
40294
|
...session,
|
|
@@ -41466,7 +41531,7 @@ server.registerTool(
|
|
|
41466
41531
|
server.registerTool(
|
|
41467
41532
|
"awesomate_knowledge_sources",
|
|
41468
41533
|
{
|
|
41469
|
-
description: "The knowledge base's content sources. action 'list' \u2014 ONE page of sources, most recently ingested first (default 50, max 200 via limit): read `page.has_more`/`next_cursor` and pass cursor to continue \u2014 a page is never the whole library. 'summary' \u2014 exact whole-library counts {total, by_kind, chunks, indexed_chunks, failed_sources}: use THIS to say what the knowledge base contains. 'jobs' \u2014 ingest job statuses (optional status filter: queued|running|succeeded|failed). 'add' \u2014 ingest a public page {url} or a whole site {sitemap, since?}; ALWAYS get explicit approval first (ingest costs money and counts against quota),
|
|
41534
|
+
description: "The knowledge base's content sources. action 'list' \u2014 ONE page of sources, most recently ingested first (default 50, max 200 via limit): read `page.has_more`/`next_cursor` and pass cursor to continue \u2014 a page is never the whole library. 'summary' \u2014 exact whole-library counts {total, by_kind, chunks, indexed_chunks, failed_sources}: use THIS to say what the knowledge base contains. 'jobs' \u2014 ingest job statuses (optional status filter: queued|running|succeeded|failed). 'add' \u2014 ingest a public page {url} or a whole site {sitemap, since?}; ALWAYS get explicit approval first (ingest costs money and counts against quota), for a local FILE on the user's machine use awesomate_knowledge_upload instead (it streams the file from disk; this tool takes URLs only). A pack_required response means the allowance is exhausted: NOTHING was purchased \u2014 present the pack price (1 credit = $100) and let the user buy from the hub if they want it. 'remove' {sourceId} \u2014 deletes the source AND its indexed content; explicit approval required.",
|
|
41470
41535
|
inputSchema: {
|
|
41471
41536
|
action: external_exports.enum(["list", "summary", "add", "remove", "jobs"]),
|
|
41472
41537
|
url: external_exports.string().url().optional().describe("add: one public page / blog post / YouTube link"),
|
|
@@ -41492,7 +41557,7 @@ server.registerTool(
|
|
|
41492
41557
|
"awesomate_knowledge_search",
|
|
41493
41558
|
{
|
|
41494
41559
|
annotations: READ_ONLY,
|
|
41495
|
-
description: "Instant search over the knowledge library with live facet counts: the fastest way to see WHAT is in there and to find the exact video moment, book page, dataset or web section. Returns hits (title, kind, locator like t=612-640 or p.42, snippet with **matched words**, score) plus facets {kind, year, category, author, people, places, topics} whose counts describe the current filters: repeat a facet value to OR within it, combine facets to AND. include_media adds presigned url/poster_url to hits: they expire in minutes, use immediately, never store. Keyword-only and free (no answer quota); for a verified ANSWER use awesomate_knowledge_ask, optionally with the same filters.",
|
|
41560
|
+
description: "Instant search over the knowledge library with live facet counts: the fastest way to see WHAT is in there and to find the exact video moment, book page, dataset or web section. Returns hits (title, kind, locator like t=612-640 or p.42, snippet with **matched words**, score) plus facets {kind, year, category, author, people, places, topics} whose counts describe the current filters: repeat a facet value to OR within it, combine facets to AND. include_media adds presigned url/poster_url to hits: they expire in minutes, use immediately, never store. Keyword-only and free (no answer quota); for a verified ANSWER use awesomate_knowledge_ask, optionally with the same filters. Citations from awesomate_knowledge_ask carry NO media URLs \u2014 when a cited source is an image/video and the user wants to SEE it, re-query here with include_media (ideally filtered by its doc id). The returned url/poster_url expire in minutes: fine to show in chat, never safe to embed in a page \u2014 see the awesomate-knowledge skill's showing-media.md.",
|
|
41496
41561
|
inputSchema: {
|
|
41497
41562
|
q: external_exports.string().max(2e3).optional().describe("Search words; empty lists the library filtered by the facets"),
|
|
41498
41563
|
kind: external_exports.enum(["book", "document", "web", "image", "video", "audio", "post", "dataset"]).optional(),
|
|
@@ -41842,6 +41907,91 @@ server.registerTool(
|
|
|
41842
41907
|
}
|
|
41843
41908
|
}
|
|
41844
41909
|
);
|
|
41910
|
+
var KB_UPLOAD_TYPES = {
|
|
41911
|
+
".pdf": "application/pdf",
|
|
41912
|
+
".doc": "application/msword",
|
|
41913
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
41914
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
41915
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
41916
|
+
".xls": "application/vnd.ms-excel",
|
|
41917
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
41918
|
+
".csv": "text/csv",
|
|
41919
|
+
".txt": "text/plain",
|
|
41920
|
+
".md": "text/markdown",
|
|
41921
|
+
".rtf": "application/rtf",
|
|
41922
|
+
".html": "text/html",
|
|
41923
|
+
".htm": "text/html",
|
|
41924
|
+
".json": "application/json",
|
|
41925
|
+
".epub": "application/epub+zip",
|
|
41926
|
+
".mp3": "audio/mpeg",
|
|
41927
|
+
".m4a": "audio/mp4",
|
|
41928
|
+
".wav": "audio/wav",
|
|
41929
|
+
".mp4": "video/mp4",
|
|
41930
|
+
".mov": "video/quicktime",
|
|
41931
|
+
".m4v": "video/x-m4v",
|
|
41932
|
+
".png": "image/png",
|
|
41933
|
+
".jpg": "image/jpeg",
|
|
41934
|
+
".jpeg": "image/jpeg",
|
|
41935
|
+
".webp": "image/webp"
|
|
41936
|
+
};
|
|
41937
|
+
var KB_MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
41938
|
+
server.registerTool(
|
|
41939
|
+
"awesomate_knowledge_upload",
|
|
41940
|
+
{
|
|
41941
|
+
description: "Ingest ONE file from the user's own computer into their Knowledge Base (Pro+). Pass a LOCAL PATH \u2014 this server runs on their machine and streams the file to the hub itself, so the file contents never pass through the conversation. Handles documents (pdf, docx, pptx, xlsx, csv, txt, md, epub), audio and video (transcribed), and images. Max 100 MB per file; bigger media goes through the hub's Knowledge \u2192 Sources page. INGESTING COSTS MONEY and counts against the monthly allowance, so ALWAYS get explicit approval for the specific file(s) first and say what it will consume. For several files, call once per file and report progress \u2014 do not loop silently. Returns a job; poll awesomate_knowledge_sources {action:'jobs'} until it succeeds, then probe the content with awesomate_knowledge_ask before building anything on it. A pack_required response means the allowance is exhausted: nothing was ingested and nothing was purchased.",
|
|
41942
|
+
inputSchema: {
|
|
41943
|
+
path: external_exports.string().min(1).max(4096).describe("Path to the file on the user's machine (~ is expanded)"),
|
|
41944
|
+
title: external_exports.string().max(300).optional().describe("Display title; defaults to the filename")
|
|
41945
|
+
}
|
|
41946
|
+
},
|
|
41947
|
+
async ({ path: rawPath, title }) => {
|
|
41948
|
+
try {
|
|
41949
|
+
const { statSync, existsSync: existsSync3 } = await import("node:fs");
|
|
41950
|
+
const { resolve: resolve2, basename, extname } = await import("node:path");
|
|
41951
|
+
const expanded = rawPath.startsWith("~") ? join3(homedir3(), rawPath.slice(1).replace(/^[/\\]/, "")) : rawPath;
|
|
41952
|
+
const abs = resolve2(expanded);
|
|
41953
|
+
if (!existsSync3(abs)) {
|
|
41954
|
+
return errorResult(
|
|
41955
|
+
new Error(
|
|
41956
|
+
`No file at ${abs}. Check the path with the user \u2014 a path that works in their shell may differ from this server's working directory, so prefer an absolute path.`
|
|
41957
|
+
)
|
|
41958
|
+
);
|
|
41959
|
+
}
|
|
41960
|
+
const st = statSync(abs);
|
|
41961
|
+
if (st.isDirectory()) {
|
|
41962
|
+
return errorResult(
|
|
41963
|
+
new Error(
|
|
41964
|
+
`${abs} is a folder, not a file. This tool takes one file at a time \u2014 list the folder, agree with the user which files to ingest, then call once per file.`
|
|
41965
|
+
)
|
|
41966
|
+
);
|
|
41967
|
+
}
|
|
41968
|
+
if (!st.isFile()) return errorResult(new Error(`${abs} is not a regular file.`));
|
|
41969
|
+
if (st.size === 0) return errorResult(new Error(`${abs} is empty \u2014 nothing to ingest.`));
|
|
41970
|
+
if (st.size > KB_MAX_UPLOAD_BYTES) {
|
|
41971
|
+
return errorResult(
|
|
41972
|
+
new Error(
|
|
41973
|
+
`${basename(abs)} is ${(st.size / 1024 / 1024).toFixed(1)} MB, over the 100 MB limit. Split or compress it, or have the user upload it in the hub at Knowledge \u2192 Sources.`
|
|
41974
|
+
)
|
|
41975
|
+
);
|
|
41976
|
+
}
|
|
41977
|
+
const ext = extname(abs).toLowerCase();
|
|
41978
|
+
const contentType = KB_UPLOAD_TYPES[ext] ?? "application/octet-stream";
|
|
41979
|
+
const filename = title?.trim() ? `${title.trim()}${ext}` : basename(abs);
|
|
41980
|
+
const result = await hubUploadFile(requireConfig(), "/api/knowledge/sources", {
|
|
41981
|
+
localPath: abs,
|
|
41982
|
+
filename,
|
|
41983
|
+
contentType
|
|
41984
|
+
});
|
|
41985
|
+
return knowledgeResult({
|
|
41986
|
+
...result,
|
|
41987
|
+
uploaded: { path: abs, filename, sizeBytes: st.size, contentType },
|
|
41988
|
+
note: `Queued \u2014 NOT yet ingested. Poll awesomate_knowledge_sources {action:"jobs"} and read the job's status AND error: a job that fails during processing creates no source row, so summary/failed_sources still look clean and the failure is invisible there. Big media is transcribed and can take a while. Only after the job reports succeeded, verify with awesomate_knowledge_ask before building on it.`
|
|
41989
|
+
});
|
|
41990
|
+
} catch (err) {
|
|
41991
|
+
return knowledgeError(err);
|
|
41992
|
+
}
|
|
41993
|
+
}
|
|
41994
|
+
);
|
|
41845
41995
|
server.registerPrompt(
|
|
41846
41996
|
"awesomate-status",
|
|
41847
41997
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/hosting-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --banner:js='#!/usr/bin/env node\nimport { createRequire as __awmCreateRequire } from \"node:module\"; const require = __awmCreateRequire(import.meta.url);' --external:node:*",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"test": "npm run build && esbuild src/config.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/config.mjs --external:node:* && esbuild src/knowledge.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/knowledge.mjs --banner:js='import { createRequire as __awmCreateRequire } from \"node:module\"; const require = __awmCreateRequire(import.meta.url);' --external:node:* && esbuild src/skills.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/skills.mjs --external:node:* && node --test test/*.test.mjs",
|
|
22
|
+
"test": "npm run build && esbuild src/config.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/config.mjs --external:node:* && esbuild src/knowledge.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/knowledge.mjs --banner:js='import { createRequire as __awmCreateRequire } from \"node:module\"; const require = __awmCreateRequire(import.meta.url);' --external:node:* && esbuild src/skills.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/skills.mjs --external:node:* && esbuild src/http.ts --bundle --platform=node --target=node18 --format=esm --outfile=test/.build/http.mjs --banner:js='import { createRequire as __awmCreateRequire } from \"node:module\"; const require = __awmCreateRequire(import.meta.url);' --external:node:* && node --test test/*.test.mjs",
|
|
23
23
|
"prepublishOnly": "npm run typecheck && npm run test"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
package/skill/CHANGELOG.json
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.20.0",
|
|
5
|
+
"highlights": [
|
|
6
|
+
"Add your own documents, audio and video to your Knowledge Base straight from your computer - just point Claude at the files",
|
|
7
|
+
"Claude can take you all the way from your files to a tested chat agent live on your website"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
3
10
|
{
|
|
4
11
|
"version": "0.19.2",
|
|
5
12
|
"highlights": [
|
|
@@ -71,10 +71,18 @@ Local FILES (PDFs, videos on disk) cannot travel through these tools — send
|
|
|
71
71
|
the user to the hub's Knowledge → Sources upload page (100 MB per file;
|
|
72
72
|
bigger media by URL). Formats + caps: [ingestion-sources.md](references/ingestion-sources.md).
|
|
73
73
|
|
|
74
|
+
**`awesomate_knowledge_upload {path, title?}`** — ingest ONE file from the
|
|
75
|
+
user's own computer. Pass a local path; the server streams it from disk, so
|
|
76
|
+
file contents never pass through the conversation. 100 MB max, one call per
|
|
77
|
+
file, explicit approval first (it spends allowance). URLs and sitemaps stay on
|
|
78
|
+
`awesomate_knowledge_sources`.
|
|
79
|
+
|
|
74
80
|
## 2. References — read on demand
|
|
75
81
|
|
|
76
82
|
| When | Read |
|
|
77
83
|
|---|---|
|
|
84
|
+
| **"Train this on my files and put a chat on my site"** — the full path | [from-your-files.md](references/from-your-files.md) |
|
|
85
|
+
| Showing a cited image or video (and why a built page breaks) | [showing-media.md](references/showing-media.md) |
|
|
78
86
|
| Choosing/adding sources, estimating ingest cost & time | [ingestion-sources.md](references/ingestion-sources.md) |
|
|
79
87
|
| Explaining citations, refusals, "verified" semantics | [citations-and-grounding.md](references/citations-and-grounding.md) |
|
|
80
88
|
| Wiring the knowledge base into n8n agents | [n8n-connection.md](references/n8n-connection.md) |
|
|
@@ -117,9 +125,10 @@ enough to route through explicit REST calls the user has just approved.
|
|
|
117
125
|
| Action | Endpoint |
|
|
118
126
|
|---|---|
|
|
119
127
|
| Status / provision / sources / jobs / agent / ask | `GET\|POST /api/knowledge/{status,provision,sources,jobs,agent,chat}` (tool equivalents) |
|
|
120
|
-
| Faceted search (what `awesomate_knowledge_search` calls) | `GET /api/knowledge/explore?q=…` + the same facet params |
|
|
128
|
+
| Faceted search (what `awesomate_knowledge_search` calls) | `GET /api/knowledge/explore?q=…` + the same facet params. **Add `&include=media` to get `url`/`poster_url` on image/video hits** — without it you get titles with nothing to display. |
|
|
121
129
|
| Business-data warehouse (what `awesomate_knowledge_data` calls) | `GET /api/knowledge/data/{metrics,datasets,datasets/:id,imports,imports/:id}` · `PATCH /api/knowledge/data/datasets/:id` · `POST /api/knowledge/data/imports/:id/:action` · `POST /api/knowledge/data/query` (read-only SQL) |
|
|
122
130
|
| Agent builder (what `awesomate_knowledge_agents` calls) | `GET\|POST /api/knowledge/agents` · `GET\|PATCH\|DELETE /api/knowledge/agents/:agent_id` · `POST /api/knowledge/agents/:agent_id/{publish,suspend,resume}` |
|
|
131
|
+
| **Test a DRAFT agent** (`action:'test'`) | `POST /api/knowledge/agents/:agent_id/chat` `{message, session_id?}`. There is **no** `/test` endpoint — guessing one returns 404. |
|
|
123
132
|
| Entity layer probe (`available` false = not enabled yet, stop) | `GET /api/knowledge/entities` |
|
|
124
133
|
| People list / detail | `GET /api/knowledge/people?status=named\|unknown\|hidden\|all` · `GET /api/knowledge/people/:id` |
|
|
125
134
|
| Name or hide/unhide a person (after approval) | `PATCH /api/knowledge/people/:id` `{display_name}` or `{status: "hidden"\|"unknown"}` |
|
|
@@ -132,6 +141,20 @@ enough to route through explicit REST calls the user has just approved.
|
|
|
132
141
|
| Delete the WHOLE knowledge base | hub UI only (owner types the account slug) — never via PAT |
|
|
133
142
|
| Fleet/admin views (Awesomate team) | `/api/fleet/knowledge/*` — admin JWT, not a client PAT |
|
|
134
143
|
|
|
144
|
+
|
|
145
|
+
**The two chat endpoints take OPPOSITE field names, and both reject unknown keys.**
|
|
146
|
+
Learning one shape and applying it to the other is a guaranteed `400`:
|
|
147
|
+
|
|
148
|
+
| Endpoint | Body |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `POST /api/knowledge/chat` — ask the knowledge base | `{question, session?, filters?}` |
|
|
151
|
+
| `POST /api/knowledge/agents/:id/chat` — test a draft agent | `{message, session_id?, filters?}` |
|
|
152
|
+
|
|
153
|
+
`question` vs `message`, `session` vs `session_id`. Both are strict objects, so
|
|
154
|
+
sending both spellings to be safe fails too. Measured on a real first-time
|
|
155
|
+
session (2026-09-01): fourteen `400`s and one `404` were spent rediscovering
|
|
156
|
+
exactly this.
|
|
157
|
+
|
|
135
158
|
## 5. Hard rules
|
|
136
159
|
|
|
137
160
|
- **Never ask for, paste, or echo an API key** — not the platform key, not
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# From the user's own files to a live, answering agent
|
|
2
|
+
|
|
3
|
+
The end-to-end path when someone says "train this on my documents and put a
|
|
4
|
+
chat on my website". Every step below has a tool; none of it needs the hub UI
|
|
5
|
+
except buying an allowance pack and reading an agent's API key.
|
|
6
|
+
|
|
7
|
+
Do the steps in order. The one rule that matters: **never build on content you
|
|
8
|
+
have not verified is actually in there.** An agent published over a failed
|
|
9
|
+
ingest answers confidently from nothing.
|
|
10
|
+
|
|
11
|
+
## 1. Agree the file list, and what it will cost
|
|
12
|
+
|
|
13
|
+
Ingest spends real money against a monthly allowance. Before uploading
|
|
14
|
+
anything:
|
|
15
|
+
|
|
16
|
+
- `awesomate_knowledge_status` — plan, consent, and the month-to-date usage
|
|
17
|
+
against the included quota. If `upgrade_required`, relay it and stop.
|
|
18
|
+
- List the candidate files for the user and get an explicit yes on the set.
|
|
19
|
+
Say roughly what it will consume: documents bill as pages (~1 page per
|
|
20
|
+
100 KB), audio and video as media-hours.
|
|
21
|
+
- If the allowance is exhausted, a call returns `pack_required`: **nothing was
|
|
22
|
+
ingested and nothing was bought**. State the price (1 credit = $100) and let
|
|
23
|
+
them buy it in the hub. Never imply you purchased anything.
|
|
24
|
+
|
|
25
|
+
Media is far more expensive than documents. A folder of PDFs is cheap; three
|
|
26
|
+
hours of video is not. Say so before, not after.
|
|
27
|
+
|
|
28
|
+
## 2. Upload, one file at a time
|
|
29
|
+
|
|
30
|
+
`awesomate_knowledge_upload { path, title? }` — a LOCAL path on their machine.
|
|
31
|
+
The MCP server reads the file off disk and streams it to the hub, so the file
|
|
32
|
+
contents never pass through the conversation and a 100 MB PDF costs no context.
|
|
33
|
+
|
|
34
|
+
- One call per file. Report progress as you go; never loop silently through
|
|
35
|
+
twenty files.
|
|
36
|
+
- 100 MB per file. Bigger media goes through the hub at Knowledge → Sources.
|
|
37
|
+
- A folder path is rejected on purpose — list it, agree the files, then upload.
|
|
38
|
+
- Public URLs and whole sitemaps stay on `awesomate_knowledge_sources`
|
|
39
|
+
`{action:'add'}`. Use that for their website; use upload for their disk.
|
|
40
|
+
|
|
41
|
+
## 3. Wait for ingestion, and check it actually succeeded
|
|
42
|
+
|
|
43
|
+
`awesomate_knowledge_sources {action:'jobs'}` until the job reports
|
|
44
|
+
`succeeded`. Transcription of audio/video takes minutes, not seconds.
|
|
45
|
+
|
|
46
|
+
**The JOBS list is the authority on whether an upload worked — not
|
|
47
|
+
`failed_sources`, and not the source list.** A job that fails during document
|
|
48
|
+
processing never creates a source row, so `{action:'summary'}` still reports
|
|
49
|
+
`failed_sources: 0` and the library total is unchanged. Measured live on
|
|
50
|
+
2026-09-01: an upload failed with a platform error while summary read
|
|
51
|
+
`total: 75, failed_sources: 0`. If you check the summary instead of the job,
|
|
52
|
+
a failed ingest looks exactly like one that was never attempted.
|
|
53
|
+
|
|
54
|
+
So: read the job's `status` AND its `error`, and relay the error text to the
|
|
55
|
+
user. Two real ones seen in the wild, both platform-side configuration rather
|
|
56
|
+
than anything wrong with their file:
|
|
57
|
+
|
|
58
|
+
- `new row violates row-level security policy for table "documents"` —
|
|
59
|
+
document/markdown ingestion is not currently permitted for that tenant.
|
|
60
|
+
- `transcription_not_configured: DEEPGRAM_API_KEY is not set` — audio/video
|
|
61
|
+
cannot be transcribed on that deployment yet.
|
|
62
|
+
|
|
63
|
+
Neither is fixable by the user or by retrying. Say plainly that the upload
|
|
64
|
+
reached Awesomate and failed on our side, and raise it with support
|
|
65
|
+
(awesomate-support skill) rather than re-uploading and spending allowance again.
|
|
66
|
+
|
|
67
|
+
Then `{action:'summary'}` for the whole-library counts once the job has
|
|
68
|
+
actually succeeded.
|
|
69
|
+
|
|
70
|
+
## 4. Probe the content before building anything on it
|
|
71
|
+
|
|
72
|
+
`awesomate_knowledge_ask` with five real questions the user cares about, and
|
|
73
|
+
**one question you know the content cannot answer**. A base that answers the
|
|
74
|
+
fifth is not grounded, and that is the single most valuable thing to catch
|
|
75
|
+
before a customer sees it. Show the citations.
|
|
76
|
+
|
|
77
|
+
## 5. Draft the agent
|
|
78
|
+
|
|
79
|
+
`awesomate_knowledge_agents {action:'create', goal}` — describe what the agent
|
|
80
|
+
is for and the platform drafts instructions, scope, tone and test questions
|
|
81
|
+
from the account's own content. It saves as a **private draft**; nothing is
|
|
82
|
+
live and nothing is lost.
|
|
83
|
+
|
|
84
|
+
Then `{action:'test', agentId, message}` — free and unmetered, and the right
|
|
85
|
+
way to check behaviour. Test the awkward cases: something out of scope, a
|
|
86
|
+
pricing question, a complaint, an ambiguous question. Confirm the refusal
|
|
87
|
+
wording is what the business would actually want to say.
|
|
88
|
+
|
|
89
|
+
Editing fields, policies, and API keys is hub UI only (Knowledge → Agents).
|
|
90
|
+
|
|
91
|
+
## 6. Publish only with explicit approval
|
|
92
|
+
|
|
93
|
+
`{action:'publish', agentId}` makes it live **immediately for every key bound
|
|
94
|
+
to the agent**. Ask first, publish, then read the version back and confirm.
|
|
95
|
+
|
|
96
|
+
## 7. Put it where their customers are
|
|
97
|
+
|
|
98
|
+
Two destinations. Ask which they want; do not assume.
|
|
99
|
+
|
|
100
|
+
**A chat on their existing website — via n8n.** This is usually the right
|
|
101
|
+
answer, and the plumbing is already provisioned. See
|
|
102
|
+
`references/n8n-connection.md`: the hub creates the scope credential and the
|
|
103
|
+
`knowledge_answer` tool. Build the workflow with the awesomate-n8n skill
|
|
104
|
+
(Chat Trigger or Webhook -> AI Agent with the `knowledge_answer` tool ->
|
|
105
|
+
Respond), test it, promote it, and give them the webhook URL to embed. Read
|
|
106
|
+
`awesomate-n8n/references/ai-agents.md` before designing the agent node.
|
|
107
|
+
|
|
108
|
+
**A standalone page or app — via the app builder.** When they want a hosted
|
|
109
|
+
"ask our docs" page rather than a widget on an existing site, use the
|
|
110
|
+
awesomate-app-builder skill. A Node app calls the agent server-side so the key
|
|
111
|
+
stays out of the browser: get the agent's API key from the hub (shown once),
|
|
112
|
+
store it with the awesomate-credentials secret-drop flow, and never put it in
|
|
113
|
+
page JavaScript. A static site cannot hold a secret — if the page must call the
|
|
114
|
+
agent directly, route it through their n8n webhook instead.
|
|
115
|
+
|
|
116
|
+
## What to tell the user at the end
|
|
117
|
+
|
|
118
|
+
The library size, what it cost against their allowance, the agent's live
|
|
119
|
+
version, where it is reachable, and how to add more later. If anything failed
|
|
120
|
+
to ingest, say which files and why — a knowledge base with a silent hole is
|
|
121
|
+
worse than a smaller one they trust.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Showing an image or video the knowledge base cited
|
|
2
|
+
|
|
3
|
+
A citation on its own is a title and a locator. To actually DISPLAY the asset
|
|
4
|
+
you need a media URL, and the rules differ depending on whether you are showing
|
|
5
|
+
it in chat or embedding it in something that has to keep working.
|
|
6
|
+
|
|
7
|
+
## Getting the URL
|
|
8
|
+
|
|
9
|
+
`awesomate_knowledge_ask` citations do **not** carry media URLs — they return
|
|
10
|
+
`{ref, title, kind, section_path, locator, url, excerpt}`. When a citation's
|
|
11
|
+
`kind` is `image`, `video` or `audio` and the user wants to see it, follow up
|
|
12
|
+
with a search:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
awesomate_knowledge_search { q: "<the cited title>", kind: "image", include_media: true }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`include_media: true` is what adds `url` and `poster_url` to the hits (over
|
|
19
|
+
REST it is `&include=media` on `/api/knowledge/explore` — easy to miss, and
|
|
20
|
+
without it you get titles with nothing to display). `poster_url` is the still
|
|
21
|
+
frame for a video; `url` is the asset itself.
|
|
22
|
+
|
|
23
|
+
Prefer `doc` filtering when you have the doc id from the citation — it resolves
|
|
24
|
+
the exact asset rather than the best text match for its title.
|
|
25
|
+
|
|
26
|
+
## The constraint that decides your architecture
|
|
27
|
+
|
|
28
|
+
**These URLs are presigned and expire in minutes.** They are for showing
|
|
29
|
+
something to the user right now, in this conversation. They are NOT a link you
|
|
30
|
+
can put in a page, save in a data table, email, or hand to a customer.
|
|
31
|
+
|
|
32
|
+
So:
|
|
33
|
+
|
|
34
|
+
- **Showing it in chat, now** — fetch with `include_media` and display it.
|
|
35
|
+
Fine. Re-fetch if the user comes back later; do not reuse an old URL.
|
|
36
|
+
- **Building a page, app, or an n8n chat that displays assets** — a presigned
|
|
37
|
+
URL will 403 by the time a visitor loads it. Do not embed one. There is
|
|
38
|
+
currently **no stable, routable media URL** for knowledge assets, so the
|
|
39
|
+
page must resolve the asset at request time:
|
|
40
|
+
- the app calls `explore?include=media` server-side (holding the key
|
|
41
|
+
server-side, never in browser JavaScript) and returns a fresh URL per
|
|
42
|
+
request, or
|
|
43
|
+
- it proxies the bytes through the app's own route, or
|
|
44
|
+
- for anything long-lived, upload the asset to somewhere with a durable URL
|
|
45
|
+
(WordPress media via `awesomate_wp_media_import`, or the app's own static
|
|
46
|
+
assets) and reference THAT, using the knowledge base for the text.
|
|
47
|
+
|
|
48
|
+
Say this to the user plainly when they ask for a gallery or an image-rich page:
|
|
49
|
+
the knowledge base is the index, not the CDN. Getting that wrong produces a
|
|
50
|
+
page that looks right when built and is full of broken images an hour later.
|
|
51
|
+
|
|
52
|
+
## Deciding quickly
|
|
53
|
+
|
|
54
|
+
| The user wants | Do |
|
|
55
|
+
|---|---|
|
|
56
|
+
| "show me that photo" | search with `include_media`, display it |
|
|
57
|
+
| "which images mention X" | search `kind:image`, list titles + show a few |
|
|
58
|
+
| a gallery page / a site section | resolve server-side per request, or re-host the assets; never embed a presigned URL |
|
|
59
|
+
| an n8n chat that returns images | have the workflow call the search endpoint at answer time and return fresh URLs in the reply |
|