@awesomate/hosting-mcp 0.91.1 → 0.93.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 +214 -37
- package/package.json +1 -1
- package/skill/CHANGELOG.json +30 -0
- package/skill/awesomate-hosting/SKILL.md +6 -2
- package/skill/awesomate-hosting/references/connect-troubleshooting.md +8 -0
- package/skill/awesomate-hosting/scripts/bootstrap.mjs +21 -1
- package/skill/awesomate-knowledge/SKILL.md +1 -0
- package/skill/awesomate-onebrain/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -40082,11 +40082,23 @@ ${JSON.stringify(details, null, 2)}`;
|
|
|
40082
40082
|
}
|
|
40083
40083
|
function keyHolder(ctx) {
|
|
40084
40084
|
const c = ctx && typeof ctx === "object" ? ctx : {};
|
|
40085
|
-
const
|
|
40085
|
+
const k = c.key && typeof c.key === "object" ? c.key : {};
|
|
40086
|
+
const email2 = typeof k.email === "string" ? k.email : typeof c.email === "string" ? c.email : null;
|
|
40087
|
+
if (k.holder === "person") {
|
|
40088
|
+
const name = typeof k.name === "string" && k.name ? k.name : email2 ?? "a team member";
|
|
40089
|
+
const level = k.level === "full" ? "full" : "view";
|
|
40090
|
+
return {
|
|
40091
|
+
holder: "person",
|
|
40092
|
+
email: email2,
|
|
40093
|
+
name,
|
|
40094
|
+
level,
|
|
40095
|
+
note: `This key belongs to ${name}${email2 && name !== email2 ? ` (${email2})` : ""}, a team member${level === "view" ? " with view-only access" : ""}: Claude does only what they can do in the hub, never more, and never with a shell on the hosting account. Do not offer anything their access does not include; if they ask, say the account owner can do it.`
|
|
40096
|
+
};
|
|
40097
|
+
}
|
|
40086
40098
|
return {
|
|
40087
40099
|
holder: "account_owner",
|
|
40088
40100
|
email: email2,
|
|
40089
|
-
note: `This key belongs to the account owner${email2 ? ` (${email2})` : ""}: everything Claude does with it is done as the owner. Team members
|
|
40101
|
+
note: `This key belongs to the account owner${email2 ? ` (${email2})` : ""}: everything Claude does with it is done as the owner. Team members connect their own Claude Code, at their own access, once the owner turns it on for them.`
|
|
40090
40102
|
};
|
|
40091
40103
|
}
|
|
40092
40104
|
|
|
@@ -40735,6 +40747,21 @@ function installedSkillVersion(name) {
|
|
|
40735
40747
|
return null;
|
|
40736
40748
|
}
|
|
40737
40749
|
}
|
|
40750
|
+
function compareVersions(a, b) {
|
|
40751
|
+
const pa = a.split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
40752
|
+
const pb = b.split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
40753
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
40754
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
40755
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
40756
|
+
}
|
|
40757
|
+
return 0;
|
|
40758
|
+
}
|
|
40759
|
+
function oldestInstalledVersion(perSkill) {
|
|
40760
|
+
return perSkill.reduce(
|
|
40761
|
+
(min, s) => s.installed !== null && (min === null || compareVersions(s.installed, min) < 0) ? s.installed : min,
|
|
40762
|
+
null
|
|
40763
|
+
);
|
|
40764
|
+
}
|
|
40738
40765
|
function skillVersions(sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
40739
40766
|
return bundledSkillNames(sourceRoot).map((name) => ({ name, installed: installedSkillVersion(name) }));
|
|
40740
40767
|
}
|
|
@@ -40791,8 +40818,9 @@ function installSkills(version2, sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
|
40791
40818
|
}
|
|
40792
40819
|
|
|
40793
40820
|
// src/knowledge.ts
|
|
40794
|
-
|
|
40795
|
-
|
|
40821
|
+
import { createHash } from "node:crypto";
|
|
40822
|
+
function scrubKeyMaterial(text, opts = {}) {
|
|
40823
|
+
return text.replace(/amk_([a-z])_[A-Za-z0-9]{6,}/g, (m, cls) => opts.keepWebsiteKeys && cls === "p" ? m : "[redacted:platform-key]").replace(/amt_[a-z]+_[A-Za-z0-9]{6,}/g, "[redacted:hub-token]");
|
|
40796
40824
|
}
|
|
40797
40825
|
var KNOWLEDGE_GATE_COPY = {
|
|
40798
40826
|
displayName: "Knowledge Base",
|
|
@@ -40878,13 +40906,33 @@ function storageFullPayload(body) {
|
|
|
40878
40906
|
note: "The Knowledge Base storage is full and the source was NOT added. Relay the message exactly as it is; offer no purchase, because none is open to this account or this person right now. Removing sources they no longer need frees space."
|
|
40879
40907
|
};
|
|
40880
40908
|
}
|
|
40909
|
+
async function deleteOrNotFound(config3, path) {
|
|
40910
|
+
try {
|
|
40911
|
+
return await hubDelete(config3, path) ?? { deleted: true };
|
|
40912
|
+
} catch (err) {
|
|
40913
|
+
if (err instanceof HubApiError && err.status === 404 && bodyOf(err)?.error === "not_found") {
|
|
40914
|
+
return {
|
|
40915
|
+
deleted: false,
|
|
40916
|
+
not_found: true,
|
|
40917
|
+
note: "Nothing with that id was there. It may already be deleted (an earlier attempt can go through even when its answer never arrives), or the id is wrong. List again to check before telling the user either way."
|
|
40918
|
+
};
|
|
40919
|
+
}
|
|
40920
|
+
throw err;
|
|
40921
|
+
}
|
|
40922
|
+
}
|
|
40881
40923
|
function bodyOf(err) {
|
|
40882
40924
|
return err.body && typeof err.body === "object" ? err.body : null;
|
|
40883
40925
|
}
|
|
40884
40926
|
function mapKnowledgeError(err, apiBase) {
|
|
40885
40927
|
if (!(err instanceof HubApiError)) throw err;
|
|
40886
40928
|
const body = bodyOf(err);
|
|
40887
|
-
if (err.status === 404)
|
|
40929
|
+
if (err.status === 404) {
|
|
40930
|
+
if (typeof body?.error !== "string" || !/^[a-z0-9_]+$/.test(body.error)) return notAvailablePayload(apiBase);
|
|
40931
|
+
if (body.error === "not_found") {
|
|
40932
|
+
return { error: "not_found", note: typeof body.message === "string" ? body.message : "There is no such item on this account. List again for the current ids." };
|
|
40933
|
+
}
|
|
40934
|
+
throw err;
|
|
40935
|
+
}
|
|
40888
40936
|
if ((err.status === 403 || err.status === 402) && body?.upgrade_required) return upsellPayload(apiBase, body.plan, body);
|
|
40889
40937
|
if (err.status === 403 && body?.consent_required) {
|
|
40890
40938
|
return consentPayload(typeof body.error === "string" ? body.error : 'Knowledge is not using your content: "Use your content for Knowledge" is off', apiBase);
|
|
@@ -41135,10 +41183,7 @@ async function knowledgeSources(config3, args) {
|
|
|
41135
41183
|
}
|
|
41136
41184
|
if (args.action === "remove") {
|
|
41137
41185
|
if (!args.sourceId) throw new Error("sourceId is required for remove");
|
|
41138
|
-
return
|
|
41139
|
-
config3,
|
|
41140
|
-
`/api/knowledge/sources/${encodeURIComponent(args.sourceId)}`
|
|
41141
|
-
);
|
|
41186
|
+
return deleteOrNotFound(config3, `/api/knowledge/sources/${encodeURIComponent(args.sourceId)}`);
|
|
41142
41187
|
}
|
|
41143
41188
|
if (!args.url && !args.sitemap) {
|
|
41144
41189
|
return {
|
|
@@ -41182,15 +41227,18 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
41182
41227
|
const session = typeof meta.session === "string" && meta.session ? { session: meta.session } : {};
|
|
41183
41228
|
const fallback = typeof meta.answer_plain === "string" && meta.answer_plain || typeof meta.no_answer_message === "string" && meta.no_answer_message || null;
|
|
41184
41229
|
if (status === "error") {
|
|
41230
|
+
const errorCode = typeof meta.error_code === "string" ? meta.error_code : null;
|
|
41231
|
+
const ownKey = errorCode === "byok_key_invalid" || errorCode === "byok_no_credit";
|
|
41185
41232
|
return {
|
|
41186
41233
|
status: "error",
|
|
41187
41234
|
answer: null,
|
|
41188
41235
|
platform_error: true,
|
|
41189
41236
|
not_in_verified_content: false,
|
|
41237
|
+
...errorCode ? { error_code: errorCode } : {},
|
|
41190
41238
|
detail: streamError ?? (streamedAnswer || null),
|
|
41191
41239
|
configured_fallback: fallback,
|
|
41192
41240
|
...session,
|
|
41193
|
-
note: "The knowledge platform failed to answer: a service problem, NOT a content gap. Do not tell the user their content lacks this. Say the service hit a problem, retry once, and if it persists raise it via awesomate_support. configured_fallback is only what an end customer would have seen meanwhile."
|
|
41241
|
+
note: ownKey ? `The agent runs on this account's own OpenRouter key, and OpenRouter ${errorCode === "byok_no_credit" ? "says it is out of credit" : "refused it"}. This is NOT a content gap. Tell the user to top up or replace the key at Settings, Integrations (hub.awesomate.ai/settings?tab=integrations), or to clear the agent's model so it runs on Awesomate's.` : "The knowledge platform failed to answer: a service problem, NOT a content gap. Do not tell the user their content lacks this. Say the service hit a problem, retry once, and if it persists raise it via awesomate_support. configured_fallback is only what an end customer would have seen meanwhile."
|
|
41194
41242
|
};
|
|
41195
41243
|
}
|
|
41196
41244
|
if (status !== "ok") {
|
|
@@ -41624,6 +41672,17 @@ function mapAgentsError(err, apiBase) {
|
|
|
41624
41672
|
}
|
|
41625
41673
|
if (err instanceof HubApiError && err.status === 403) {
|
|
41626
41674
|
const body = bodyOf(err);
|
|
41675
|
+
if (body && body.error === "upgrade_required" && body.capability === "public_widget") {
|
|
41676
|
+
const requiredPlan = typeof body.requiredPlan === "string" ? body.requiredPlan : "Support Plus";
|
|
41677
|
+
return {
|
|
41678
|
+
upgrade_required: true,
|
|
41679
|
+
capability: "public_widget",
|
|
41680
|
+
requiredPlan,
|
|
41681
|
+
...typeof body.message === "string" ? { message: body.message } : {},
|
|
41682
|
+
billingUrl: `${apiBase}/billing?upgrade=${encodeURIComponent(requiredPlan)}`,
|
|
41683
|
+
note: "Nothing was made. Relay the message as it is and let the user decide; never retry."
|
|
41684
|
+
};
|
|
41685
|
+
}
|
|
41627
41686
|
if (body && body.error === "upgrade_required" && body.capability === "knowledge_questions") {
|
|
41628
41687
|
const requiredPlan = typeof body.requiredPlan === "string" ? body.requiredPlan : "Support Plus";
|
|
41629
41688
|
return {
|
|
@@ -41638,6 +41697,107 @@ function mapAgentsError(err, apiBase) {
|
|
|
41638
41697
|
}
|
|
41639
41698
|
return mapKnowledgeError(err, apiBase);
|
|
41640
41699
|
}
|
|
41700
|
+
var WEBSITE_KEY_MAX_ORIGINS = 16;
|
|
41701
|
+
function websiteOrigins(input) {
|
|
41702
|
+
const raw = (input ?? []).map((s) => s.trim()).filter(Boolean);
|
|
41703
|
+
if (!raw.length) return { ok: false, error: "website_key needs origins: the website addresses the chat will run on, like https://example.com.au" };
|
|
41704
|
+
const out = [];
|
|
41705
|
+
for (const r of raw) {
|
|
41706
|
+
if (r.includes("*")) return { ok: false, error: `"${r}": a wildcard is not allowed, because the key would then answer on any website.` };
|
|
41707
|
+
let u;
|
|
41708
|
+
try {
|
|
41709
|
+
u = new URL(r.includes("://") ? r : `https://${r}`);
|
|
41710
|
+
} catch {
|
|
41711
|
+
return { ok: false, error: `"${r}" is not a website address.` };
|
|
41712
|
+
}
|
|
41713
|
+
if (u.protocol !== "https:") return { ok: false, error: `"${r}" must be an https:// address.` };
|
|
41714
|
+
if (u.username || u.password) return { ok: false, error: `"${r}" must not include a username or password.` };
|
|
41715
|
+
if (u.pathname !== "/" || u.search || u.hash) return { ok: false, error: `"${r}" should be just the site address, without a path, for example ${u.origin}` };
|
|
41716
|
+
if (!out.includes(u.origin)) out.push(u.origin);
|
|
41717
|
+
}
|
|
41718
|
+
if (out.length > WEBSITE_KEY_MAX_ORIGINS) return { ok: false, error: `At most ${WEBSITE_KEY_MAX_ORIGINS} websites per key.` };
|
|
41719
|
+
return { ok: true, origins: out };
|
|
41720
|
+
}
|
|
41721
|
+
var htmlAttr = (v) => v.replace(/&/g, "&").replace(/"/g, """);
|
|
41722
|
+
function websiteSnippet(widgetUrl, key, agentId, title) {
|
|
41723
|
+
const attrs = [["data-key", key], ["data-agent", agentId]];
|
|
41724
|
+
if (title?.trim()) attrs.push(["data-title", title.trim()]);
|
|
41725
|
+
const lines = attrs.map(([k, v], i) => ` ${k}="${htmlAttr(v)}"${i === attrs.length - 1 ? "></script>" : ""}`);
|
|
41726
|
+
return [`<script src="${htmlAttr(widgetUrl)}"`, ...lines].join("\n");
|
|
41727
|
+
}
|
|
41728
|
+
function websiteKeyRequestId(agentId, origins, label, cap, liveKeyIds) {
|
|
41729
|
+
const h = createHash("sha256").update(`${agentId}|${[...origins].sort().join(",")}|${label}|${cap ?? "default"}|${[...liveKeyIds].sort().join(",")}`).digest("hex");
|
|
41730
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
41731
|
+
}
|
|
41732
|
+
function liveWebsiteKeys(keys, agentId) {
|
|
41733
|
+
const now = Date.now();
|
|
41734
|
+
return keys.filter(
|
|
41735
|
+
(k) => k.class === "p" && (!agentId || k.agent_id === agentId) && !k.revoked_at && !(typeof k.expires_at === "string" && new Date(k.expires_at).getTime() <= now)
|
|
41736
|
+
);
|
|
41737
|
+
}
|
|
41738
|
+
function websiteAgentRefusal(agent) {
|
|
41739
|
+
const name = typeof agent.name === "string" && agent.name ? `"${agent.name}"` : "This agent";
|
|
41740
|
+
if (agent.status !== "published") return `${name} is not published yet, so a website chat would have nothing to answer with. Test it, then publish it (with the user's yes) first.`;
|
|
41741
|
+
if (agent.audience !== "public") return `${name} serves a ${typeof agent.audience === "string" ? agent.audience : "private"} audience, and a website chat answers the public. Change its audience to public and publish again (with the user's yes) first.`;
|
|
41742
|
+
if (agent.suspended_at) return `${name} is paused, so a website chat would not answer. Resume it (with the user's yes) first.`;
|
|
41743
|
+
return null;
|
|
41744
|
+
}
|
|
41745
|
+
var WEBSITE_KEY_NOTE = "A website key is meant to sit in page source: it only answers from the websites listed, only as this agent, and only from sources marked public. It carries its own monthly answer cap (by default half the account's answers), so one busy site cannot use up every answer.";
|
|
41746
|
+
async function websiteKeyAction(config3, args) {
|
|
41747
|
+
const base = "/api/knowledge/agents";
|
|
41748
|
+
if (args.action === "website_keys") {
|
|
41749
|
+
const r = await hubGet(config3, `${base}/keys`);
|
|
41750
|
+
return { keys: liveWebsiteKeys(r.keys ?? [], args.agentId), note: `${WEBSITE_KEY_NOTE} The key itself is shown only once, when it is made; it cannot be read back.` };
|
|
41751
|
+
}
|
|
41752
|
+
if (args.action === "revoke_website_key") {
|
|
41753
|
+
if (!args.keyId) return { error: "invalid_request", note: "revoke_website_key needs keyId (from website_keys)." };
|
|
41754
|
+
const r = await hubGet(config3, `${base}/keys`);
|
|
41755
|
+
if (!liveWebsiteKeys(r.keys ?? []).some((k) => k.key_id === args.keyId)) {
|
|
41756
|
+
return { error: "not_found", note: "There is no working website key with that id. Read website_keys for the ids; other kinds of key are managed in the hub only." };
|
|
41757
|
+
}
|
|
41758
|
+
if (args.confirm !== true) {
|
|
41759
|
+
return { error: "confirmation_required", note: "Revoking this key stops the chat on every website using it at once. Tell the owner, and call again with confirm:true only after they say yes." };
|
|
41760
|
+
}
|
|
41761
|
+
return deleteOrNotFound(config3, `${base}/keys/${encodeURIComponent(args.keyId)}`);
|
|
41762
|
+
}
|
|
41763
|
+
if (!args.agentId) return { error: "invalid_request", note: "website_key needs agentId (from list) and origins." };
|
|
41764
|
+
const origins = websiteOrigins(args.origins);
|
|
41765
|
+
if (!origins.ok) return { error: "invalid_request", note: `${origins.error} Nothing was made.` };
|
|
41766
|
+
const agent = agentOf(await hubGet(config3, `${base}/${encodeURIComponent(args.agentId)}`));
|
|
41767
|
+
const notReady = websiteAgentRefusal(agent);
|
|
41768
|
+
if (notReady) return { error: "agent_not_ready", note: `${notReady} Nothing was made.` };
|
|
41769
|
+
if (args.confirm !== true) {
|
|
41770
|
+
return {
|
|
41771
|
+
error: "confirmation_required",
|
|
41772
|
+
note: `Making a website key puts this agent on ${origins.origins.join(", ")}, where anyone visiting can ask it questions, and those answers count against the account's monthly answers. Tell the owner that, and call again with confirm:true only after they say yes.`
|
|
41773
|
+
};
|
|
41774
|
+
}
|
|
41775
|
+
const status = await hubGet(config3, "/api/knowledge/status");
|
|
41776
|
+
const widgetUrl = typeof status.widget_url === "string" && status.widget_url ? status.widget_url : null;
|
|
41777
|
+
if (!widgetUrl) return { error: "widget_unavailable", note: "This hub has no website chat loader configured, so a key would have nowhere to run. Nothing was made; tell the user to contact Awesomate support." };
|
|
41778
|
+
const listed = await hubGet(config3, `${base}/keys`);
|
|
41779
|
+
const liveIds = liveWebsiteKeys(listed.keys ?? [], args.agentId).map((k) => String(k.key_id));
|
|
41780
|
+
const label = (args.label?.trim() || `website: ${origins.origins.map((o) => o.replace(/^https:\/\//, "")).join(", ")}`).slice(0, 120);
|
|
41781
|
+
const minted = await hubPost(config3, `${base}/keys`, {
|
|
41782
|
+
class: "p",
|
|
41783
|
+
agent_id: args.agentId,
|
|
41784
|
+
allowed_origins: origins.origins,
|
|
41785
|
+
label,
|
|
41786
|
+
request_id: websiteKeyRequestId(args.agentId, origins.origins, label, args.monthlyAnswerCap, liveIds),
|
|
41787
|
+
...args.monthlyAnswerCap !== void 0 ? { monthly_answer_cap: args.monthlyAnswerCap } : {}
|
|
41788
|
+
});
|
|
41789
|
+
const key = typeof minted.key === "string" ? minted.key : "";
|
|
41790
|
+
const title = args.title?.trim() || (typeof agent.name === "string" ? agent.name : "");
|
|
41791
|
+
const cap = typeof minted.monthly_answer_cap === "number" ? minted.monthly_answer_cap : null;
|
|
41792
|
+
return {
|
|
41793
|
+
key_id: minted.key_id ?? null,
|
|
41794
|
+
key,
|
|
41795
|
+
allowed_origins: origins.origins,
|
|
41796
|
+
monthly_answer_cap: cap,
|
|
41797
|
+
snippet: key ? websiteSnippet(widgetUrl, key, args.agentId, title) : null,
|
|
41798
|
+
note: `${WEBSITE_KEY_NOTE} The key is shown ONCE: hand the snippet straight to the user to paste before </body> on those sites (or add it to the site for them), and do not repeat the key anywhere else.` + (cap !== null ? ` Tell the user this key has its own limit of ${cap} answers a month. A key's limit cannot be changed once it is made: to change it, make a new key with monthlyAnswerCap, swap the snippet on the site, then revoke this one.` : "")
|
|
41799
|
+
};
|
|
41800
|
+
}
|
|
41641
41801
|
var VISITOR_TEST_MAX_CHARS = 2e3;
|
|
41642
41802
|
var POLICY_TAGS_MAX = 50;
|
|
41643
41803
|
function policyScope(args) {
|
|
@@ -41656,6 +41816,9 @@ var POLICY_NOTE = "An access policy narrows what a key or a signed-in person may
|
|
|
41656
41816
|
async function knowledgeAgents(config3, args) {
|
|
41657
41817
|
try {
|
|
41658
41818
|
const base = "/api/knowledge/agents";
|
|
41819
|
+
if (args.action === "website_key" || args.action === "website_keys" || args.action === "revoke_website_key") {
|
|
41820
|
+
return await websiteKeyAction(config3, args);
|
|
41821
|
+
}
|
|
41659
41822
|
if (args.action === "logs") {
|
|
41660
41823
|
const q2 = new URLSearchParams();
|
|
41661
41824
|
if (args.agentId) q2.set("agent_id", args.agentId);
|
|
@@ -41733,19 +41896,20 @@ async function knowledgeAgents(config3, args) {
|
|
|
41733
41896
|
const current = scope && typeof scope === "object" && !Array.isArray(scope) ? scope : {};
|
|
41734
41897
|
return touchesScope(scopeArgs) ? { scope: agentPatchBody(scopeArgs, current).scope } : scope !== void 0 ? { scope } : {};
|
|
41735
41898
|
};
|
|
41899
|
+
const callerFields = agentPatchBody({ ...args, collectionIds: void 0, kinds: void 0, sourceIds: void 0 });
|
|
41736
41900
|
let created;
|
|
41737
41901
|
let sentScope;
|
|
41738
41902
|
let extra = {};
|
|
41739
41903
|
if (args.goal) {
|
|
41740
41904
|
const draft = await hubPost(config3, "/api/knowledge/agents/draft", { goal: args.goal });
|
|
41741
41905
|
if (!draft.proposal) return { error: "draft_failed", detail: draft };
|
|
41742
|
-
const body = { ...draft.proposal, ...withScope(draft.proposal.scope)
|
|
41906
|
+
const body = { ...draft.proposal, ...callerFields, ...withScope(draft.proposal.scope) };
|
|
41743
41907
|
sentScope = body.scope;
|
|
41744
41908
|
created = await hubPost(config3, "/api/knowledge/agents", body);
|
|
41745
41909
|
extra = { test_questions: draft.test_questions ?? [], status_note: `Created as a private DRAFT serving audience '${args.audience ?? "private"}': test it, then publish when the user approves.` };
|
|
41746
41910
|
} else {
|
|
41747
41911
|
if (!args.name) return { error: "name (or goal) is required for create" };
|
|
41748
|
-
const body = { name: args.name, ...
|
|
41912
|
+
const body = { ...callerFields, name: args.name, ...withScope(void 0) };
|
|
41749
41913
|
sentScope = body.scope;
|
|
41750
41914
|
created = await hubPost(config3, "/api/knowledge/agents", body);
|
|
41751
41915
|
}
|
|
@@ -41844,7 +42008,7 @@ async function knowledgeCollections(config3, args) {
|
|
|
41844
42008
|
return await hubPatch(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`, body);
|
|
41845
42009
|
}
|
|
41846
42010
|
if (args.action === "delete") {
|
|
41847
|
-
return
|
|
42011
|
+
return deleteOrNotFound(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`);
|
|
41848
42012
|
}
|
|
41849
42013
|
if (!args.sourceIds?.length) return { error: "invalid_request", note: `${args.action} needs sourceIds` };
|
|
41850
42014
|
return await hubPost(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}/sources`, {
|
|
@@ -42123,10 +42287,7 @@ function changelogSince(installed) {
|
|
|
42123
42287
|
function skillUpdateInfo() {
|
|
42124
42288
|
const perSkill = skillVersions();
|
|
42125
42289
|
const staleSkills = perSkill.filter((s) => s.installed !== SERVER_VERSION).map((s) => s.name);
|
|
42126
|
-
const oldest = perSkill
|
|
42127
|
-
(min, s) => s.installed !== null && (min === null || s.installed < min) ? s.installed : min,
|
|
42128
|
-
null
|
|
42129
|
-
);
|
|
42290
|
+
const oldest = oldestInstalledVersion(perSkill);
|
|
42130
42291
|
const updateAvailable = perSkill.length === 0 || staleSkills.length > 0;
|
|
42131
42292
|
return {
|
|
42132
42293
|
serverVersion: SERVER_VERSION,
|
|
@@ -42388,7 +42549,7 @@ server.registerTool(
|
|
|
42388
42549
|
"awesomate_get_context",
|
|
42389
42550
|
{
|
|
42390
42551
|
annotations: annotate("awesomate_get_context", READ_ONLY),
|
|
42391
|
-
description: "THE session entry point: call it FIRST, before any domain context. Returns the connected account: plan, capabilities, limits, scopes, token expiry, cPanel routing, PLUS `key` (whose key this is:
|
|
42552
|
+
description: "THE session entry point: call it FIRST, before any domain context. Returns the connected account: plan, capabilities, limits, scopes, token expiry, cPanel routing, PLUS `key` (whose key this is: the account owner's, so everything is done as the owner, or a team member's own (`holder: 'person'`), which does only what that person can do in the hub and never has a shell: say whose it is, and never offer work outside a person's access; a tool the key's scopes do not cover answers at once with the hub's own refusal), PLUS `attention` ({unreadNotifications, erroringWorkflows7d, patExpiresInDays}: null means unknown, never zero; when something is non-zero, mention it to the user in one line before starting the asked task), `features` ({key: {available, reason, message}}: what this account has; never offer one whose available is false, say its message instead), `latestMcpVersion` (if serverVersion still lags it after a restart, the npx cache is stale; remedy: rm -rf ~/.npm/_npx, then restart), and `skill` ({updateAvailable, staleSkills, whatsNew}: if updateAvailable, mention ONCE with a whatsNew line, offer awesomate_skill_update, never mid-task). If the token is near expiry (patExpiresInDays < 7), re-running Connect Claude Code from hub.awesomate.ai/claude refreshes skills AND renews the token in one go.",
|
|
42392
42553
|
inputSchema: {}
|
|
42393
42554
|
},
|
|
42394
42555
|
async () => {
|
|
@@ -43079,7 +43240,7 @@ server.registerTool(
|
|
|
43079
43240
|
"awesomate_site_staging_promote",
|
|
43080
43241
|
{
|
|
43081
43242
|
annotations: annotate("awesomate_site_staging_promote", DESTRUCTIVE),
|
|
43082
|
-
description: "Publish the staging copy to the LIVE site (overwrites live files + database with staging). The live site is auto-snapshotted first
|
|
43243
|
+
description: "Publish the staging copy to the LIVE site (overwrites live files + database with staging). The live site is auto-snapshotted first: the result includes preSnapshotId, which awesomate_rollback_site can restore if anything looks wrong. The live site keeps its own search engine visibility (staging is always hidden from search engines; that setting is not copied to live); if searchEngineVisibility.warning is present, live may now be hidden from search engines, so relay it to the user. **Confirm with the user before promoting: it replaces the live site.** Requires shell access (Support Plus+).",
|
|
43083
43244
|
inputSchema: {
|
|
43084
43245
|
domain: external_exports.string().describe("The LIVE site domain whose staging copy should go live")
|
|
43085
43246
|
}
|
|
@@ -43414,13 +43575,13 @@ server.registerTool(
|
|
|
43414
43575
|
}
|
|
43415
43576
|
}
|
|
43416
43577
|
);
|
|
43417
|
-
function knowledgeResult(data) {
|
|
43578
|
+
function knowledgeResult(data, opts = {}) {
|
|
43418
43579
|
return {
|
|
43419
43580
|
content: [
|
|
43420
43581
|
{
|
|
43421
43582
|
type: "text",
|
|
43422
43583
|
text: `${accountStamp()}
|
|
43423
|
-
${scrubKeyMaterial(JSON.stringify(data, null, 2))}`
|
|
43584
|
+
${scrubKeyMaterial(JSON.stringify(data, null, 2), opts)}`
|
|
43424
43585
|
}
|
|
43425
43586
|
]
|
|
43426
43587
|
};
|
|
@@ -43452,7 +43613,7 @@ server.registerTool(
|
|
|
43452
43613
|
"awesomate_knowledge_provision",
|
|
43453
43614
|
{
|
|
43454
43615
|
annotations: annotate("awesomate_knowledge_provision", MUTATING),
|
|
43455
|
-
description: `Enable the Knowledge Base for this account (creates their isolated tenant on the Awesomate knowledge platform and wires the n8n credential). Idempotent
|
|
43616
|
+
description: `Enable the Knowledge Base for this account (creates their isolated tenant on the Awesomate knowledge platform and wires the n8n credential). Idempotent: safe to re-call, and the account owner re-calling on an active account puts back a missing "Awesomate Knowledge Base" n8n credential. The response's credential is {status, verified?, reason?}: created / exists are fine; adopted with verified:false means an existing credential was kept and its key may be stale (tell the user to rotate the Knowledge key); skipped or failed carries the reason (for example no n8n API key stored, so the user connects n8n first); a pending:true response means provisioning continues in the background: poll awesomate_knowledge_status. consent_required \u2192 send the user to Settings \u2192 Features (hub.awesomate.ai/settings?tab=features): under the Knowledge row, the second line "Use your content for Knowledge". Switching on Knowledge itself is NOT the consent. They must switch it themselves; re-check, then retry. upgrade_required \u2192 relay the upsell, don't retry. Get the user's explicit go-ahead before enabling.`,
|
|
43456
43617
|
inputSchema: {}
|
|
43457
43618
|
},
|
|
43458
43619
|
async () => {
|
|
@@ -43552,7 +43713,7 @@ server.registerTool(
|
|
|
43552
43713
|
author: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
43553
43714
|
year: external_exports.array(external_exports.string().regex(/^\d{4}$/)).max(10).optional(),
|
|
43554
43715
|
doc: external_exports.array(external_exports.string().max(200)).max(50).optional().describe("doc_ids from search hits")
|
|
43555
|
-
}).optional().describe("Ask within a slice of the library: EXACTLY the awesomate_knowledge_search input shapes, reusable verbatim; filters only ever narrow")
|
|
43716
|
+
}).optional().describe("Ask within a slice of the library: EXACTLY the awesomate_knowledge_search input shapes, reusable verbatim; filters only ever narrow. A filtered question is answered by the workspace's 'knowledge' agent (the default agent takes no filters), so its tone can differ from an unfiltered one")
|
|
43556
43717
|
}
|
|
43557
43718
|
},
|
|
43558
43719
|
async ({ question, session, filters }) => {
|
|
@@ -43595,7 +43756,7 @@ server.registerTool(
|
|
|
43595
43756
|
"awesomate_knowledge_agents",
|
|
43596
43757
|
{
|
|
43597
43758
|
annotations: annotate("awesomate_knowledge_agents", MUTATING),
|
|
43598
|
-
description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list': every agent with status (draft/published vN/suspended). 'get' {agentId}: full config incl. system message and scope. 'create' {goal}: AI drafts the whole setup (instructions, scope, tone, test questions) from the account's own content and saves it as a PRIVATE DRAFT (never live, nothing lost); or {name} for a blank draft. The drafted scope is often too wide: it can come back limited to no collection (so it answers from every business's and every audience's content) or limited to kinds that leave out PDFs (stored as 'book'), FAQs ('faq') or images. Pass collectionIds (and kinds if needed) WITH create to scope it in one step; when the account has collections and the new agent reads none of them, or its kinds skip content the library holds, the result carries a warning listing the collections: fix it with update before testing or publishing. 'update' {agentId, ...fields}: edit the DRAFT config: name, description, systemMessage, noAnswerMessage, grounding, audience, collectionIds (scope to collections from awesomate_knowledge_collections), kinds, sourceIds, maxSources, temperature. Nothing changes for callers until 'publish'. 'test' {agentId, message, sessionId?}: chat with the DRAFT config: free, unmetered, the right way to check wording and scope before going live. WARNING: the draft test ignores the agent's audience, so it is NOT what the public sees for anything touching internal or private sources (seen live: a public-audience draft quoted an internal staff handbook's door code and Wi-Fi password, which the published agent correctly refused a visitor). 'visitor_test' {agentId, message (max 2000 chars), sessionId?}: ask the PUBLISHED agent through the real public visitor path, the only way to check what a website visitor actually gets; run it after publishing whenever the agent is public-facing or its scope includes internal/private sources. It needs the plan the public widget ships with (upgrade_required below it: relay, never retry), and a question it cannot answer lands in the owner's Questions inbox like a real visitor's, so say so before using it. 'publish' {agentId}: makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. 'suspend' / 'resume' {agentId}: stop the agent answering on every key bound to it, or start it again (its last published version); both change what live callers get at once, so get the owner's explicit approval first. 'delete' {agentId, confirm:true}: removes the agent for good and every key bound to it stops answering; tell the owner that and pass confirm:true only after they say yes. 'logs' {agentId?, since?, until?, endpoint?, status?, limit?, cursor?}: the request log (each call to the agents: when, which endpoint, its status), newest first. 'answer_rates' {days? 1-92, default 30}: how each agent is answering, in bands, counts only. 'where_used': each agent with its audience, scope and status, to see which agent a website or automation is using. 'ai_key_status': whether the account has its own AI key saved (names and dates, never a key); saving or removing one is the owner's, in the hub at Settings, Integrations. 'models': the models the account's own AI key can use. Access policies (customer groups): 'policies' lists them; 'policy_get' {policyId}; 'policy_create' {name, description?, collectionIds?|kinds?|sourceIds?|tags?}: a policy named segment:all or segment:<group> is what an agent's customer groups use, so a person in that group answers only from what it allows; 'policy_update' {policyId, name?|description?|scope fields} (a scope REPLACES the stored one); 'policy_delete', 'policy_suspend', 'policy_resume' {policyId}. Every policy change applies to live callers at once: show the owner what changes and get a yes first.
|
|
43759
|
+
description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list': every agent with status (draft/published vN/suspended). 'get' {agentId}: full config incl. system message and scope. 'create' {goal}: AI drafts the whole setup (instructions, scope, tone, test questions) from the account's own content and saves it as a PRIVATE DRAFT (never live, nothing lost); or {name} for a blank draft. The drafted scope is often too wide: it can come back limited to no collection (so it answers from every business's and every audience's content) or limited to kinds that leave out PDFs (stored as 'book'), FAQs ('faq') or images. Pass collectionIds (and kinds if needed) WITH create to scope it in one step; when the account has collections and the new agent reads none of them, or its kinds skip content the library holds, the result carries a warning listing the collections: fix it with update before testing or publishing. 'update' {agentId, ...fields}: edit the DRAFT config: name, description, systemMessage, noAnswerMessage, grounding, audience, collectionIds (scope to collections from awesomate_knowledge_collections), kinds, sourceIds, maxSources, temperature. Nothing changes for callers until 'publish'. 'test' {agentId, message, sessionId?}: chat with the DRAFT config: free, unmetered, the right way to check wording and scope before going live. WARNING: the draft test ignores the agent's audience, so it is NOT what the public sees for anything touching internal or private sources (seen live: a public-audience draft quoted an internal staff handbook's door code and Wi-Fi password, which the published agent correctly refused a visitor). 'visitor_test' {agentId, message (max 2000 chars), sessionId?}: ask the PUBLISHED agent through the real public visitor path, the only way to check what a website visitor actually gets; run it after publishing whenever the agent is public-facing or its scope includes internal/private sources. It needs the plan the public widget ships with (upgrade_required below it: relay, never retry), and a question it cannot answer lands in the owner's Questions inbox like a real visitor's, so say so before using it. 'publish' {agentId}: makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. 'suspend' / 'resume' {agentId}: stop the agent answering on every key bound to it, or start it again (its last published version); both change what live callers get at once, so get the owner's explicit approval first. 'delete' {agentId, confirm:true}: removes the agent for good and every key bound to it stops answering; tell the owner that and pass confirm:true only after they say yes. 'logs' {agentId?, since?, until?, endpoint?, status?, limit?, cursor?}: the request log (each call to the agents: when, which endpoint, its status), newest first. 'answer_rates' {days? 1-92, default 30}: how each agent is answering, in bands, counts only. 'where_used': each agent with its audience, scope and status, to see which agent a website or automation is using. 'ai_key_status': whether the account has its own AI key saved (names and dates, never a key); saving or removing one is the owner's, in the hub at Settings, Integrations. 'models': the models the account's own AI key can use. Access policies (customer groups): 'policies' lists them; 'policy_get' {policyId}; 'policy_create' {name, description?, collectionIds?|kinds?|sourceIds?|tags?}: a policy named segment:all or segment:<group> is what an agent's customer groups use, so a person in that group answers only from what it allows; 'policy_update' {policyId, name?|description?|scope fields} (a scope REPLACES the stored one); 'policy_delete', 'policy_suspend', 'policy_resume' {policyId}. Every policy change applies to live callers at once: show the owner what changes and get a yes first. Website chat (owner only; Support Plus and above, upgrade_required below it: relay it, never retry): 'website_keys' {agentId?}: the working website keys (no key text: a key is shown once, when made). 'website_key' {agentId, origins, title?, label?, monthlyAnswerCap?, confirm:true}: makes a website key for a PUBLISHED, public-audience agent (refused otherwise, with what to do first) and returns it ONCE with the ready <script> snippet to paste before </body>; anyone on those sites can then ask the agent, and answers count against the account's monthly answers. Each key has its own monthly answer limit (by default half the account's at the time it is made), returned as monthly_answer_cap: tell the user the number. A key's limit cannot be changed later; a different limit is a new key, then revoke the old one. Ask the owner which sites, say what it means, and pass confirm:true only after they say yes. 'revoke_website_key' {keyId, confirm:true}: stops the chat on every site using that key at once; ask first. Other keys (for automations and servers) are made in the hub (Knowledge, Agents) and never pass through this tool.",
|
|
43599
43760
|
inputSchema: {
|
|
43600
43761
|
action: external_exports.enum([
|
|
43601
43762
|
"list",
|
|
@@ -43619,9 +43780,17 @@ server.registerTool(
|
|
|
43619
43780
|
"policy_update",
|
|
43620
43781
|
"policy_delete",
|
|
43621
43782
|
"policy_suspend",
|
|
43622
|
-
"policy_resume"
|
|
43783
|
+
"policy_resume",
|
|
43784
|
+
"website_key",
|
|
43785
|
+
"website_keys",
|
|
43786
|
+
"revoke_website_key"
|
|
43623
43787
|
]),
|
|
43624
|
-
confirm: external_exports.boolean().optional().describe("delete: true only after the owner said yes
|
|
43788
|
+
confirm: external_exports.boolean().optional().describe("delete, website_key, revoke_website_key: true only after the owner said yes"),
|
|
43789
|
+
origins: external_exports.array(external_exports.string().max(200)).max(16).optional().describe("website_key: the websites the chat runs on, https://host only (no path, no wildcard), e.g. https://example.com.au and https://www.example.com.au"),
|
|
43790
|
+
title: external_exports.string().max(60).optional().describe("website_key: the chat window's title in the snippet (data-title); defaults to the agent's name"),
|
|
43791
|
+
label: external_exports.string().max(120).optional().describe("website_key: a name for the key in key lists"),
|
|
43792
|
+
monthlyAnswerCap: external_exports.number().int().min(1).optional().describe("website_key: this key's own monthly answer cap; leave it out for half the account's monthly answers"),
|
|
43793
|
+
keyId: external_exports.string().max(128).optional().describe("revoke_website_key: key_id from website_keys"),
|
|
43625
43794
|
since: external_exports.string().max(40).optional().describe("logs: ISO time to read from"),
|
|
43626
43795
|
until: external_exports.string().max(40).optional().describe("logs: ISO time to read to"),
|
|
43627
43796
|
endpoint: external_exports.string().max(200).optional().describe("logs: only calls to this endpoint, e.g. /v1/answer"),
|
|
@@ -43635,22 +43804,22 @@ server.registerTool(
|
|
|
43635
43804
|
name: external_exports.string().max(120).optional().describe("create: blank draft with this name (max 80); update: rename; policy_create/update: the policy name, e.g. segment:members"),
|
|
43636
43805
|
goal: external_exports.string().max(2e3).optional().describe("create: describe the agent and AI drafts it from the account content"),
|
|
43637
43806
|
audience: external_exports.enum(["private", "internal", "public"]).optional().describe("create/update: who the agent serves: 'public' for anything customers or a website will talk to (it then answers from sources marked public ONLY), 'internal' for the team, 'private' (default) for the owner's own tools. Widening later is a publish, so choose now."),
|
|
43638
|
-
description: external_exports.string().max(300).optional().describe("update: one-line description shown in the hub; policy_create/update: what the policy is for"),
|
|
43639
|
-
systemMessage: external_exports.string().max(8e3).optional().describe("update: the agent instructions (system message)"),
|
|
43640
|
-
noAnswerMessage: external_exports.string().max(500).optional().describe("update: what the agent says when the grounding gate declines to answer"),
|
|
43641
|
-
grounding: external_exports.enum(["strict", "grounded_chat"]).optional().describe("update: 'strict' refuses anything not in the retrieved content (use it for anything customer-facing); 'grounded_chat' may add general knowledge around the cited content"),
|
|
43807
|
+
description: external_exports.string().max(300).optional().describe("create/update: one-line description shown in the hub; policy_create/update: what the policy is for"),
|
|
43808
|
+
systemMessage: external_exports.string().max(8e3).optional().describe("create/update: the agent instructions (system message); with goal, it replaces the drafted ones"),
|
|
43809
|
+
noAnswerMessage: external_exports.string().max(500).optional().describe("create/update: what the agent says when the grounding gate declines to answer"),
|
|
43810
|
+
grounding: external_exports.enum(["strict", "grounded_chat"]).optional().describe("create/update: 'strict' refuses anything not in the retrieved content (use it for anything customer-facing); 'grounded_chat' may add general knowledge around the cited content"),
|
|
43642
43811
|
collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("create/update: scope the agent to these collections (collection_id from awesomate_knowledge_collections list); replaces the collection scope only, other scope fields and free-form tags are kept. policy_create/update: the collections the policy allows"),
|
|
43643
43812
|
kinds: external_exports.array(external_exports.string().max(40)).max(16).optional().describe("create/update: restrict retrieval to source kinds (web, document, video, audio, image, book, faq, dataset; PDFs are stored as 'book'); omit for all kinds. policy_create/update: the kinds the policy allows"),
|
|
43644
43813
|
sourceIds: external_exports.array(external_exports.string().max(200)).max(500).optional().describe("create/update, policy_create/update: pin the scope to specific sources"),
|
|
43645
|
-
maxSources: external_exports.number().int().min(1).max(10).optional().describe("update: how many sources one answer may cite (1-10)"),
|
|
43646
|
-
temperature: external_exports.number().min(0).max(1).optional().describe("update: 0 is the most literal"),
|
|
43814
|
+
maxSources: external_exports.number().int().min(1).max(10).optional().describe("create/update: how many sources one answer may cite (1-10)"),
|
|
43815
|
+
temperature: external_exports.number().min(0).max(1).optional().describe("create/update: 0 is the most literal"),
|
|
43647
43816
|
message: external_exports.string().max(4e3).optional().describe("test: the question to ask the draft; visitor_test: the question a visitor asks the published agent (max 2000 characters)"),
|
|
43648
43817
|
sessionId: external_exports.string().max(128).optional().describe("test/visitor_test: keep follow-ups in one thread")
|
|
43649
43818
|
}
|
|
43650
43819
|
},
|
|
43651
43820
|
async (args) => {
|
|
43652
43821
|
try {
|
|
43653
|
-
return knowledgeResult(await knowledgeAgents(requireConfig(), args));
|
|
43822
|
+
return knowledgeResult(await knowledgeAgents(requireConfig(), args), { keepWebsiteKeys: args.action === "website_key" });
|
|
43654
43823
|
} catch (err) {
|
|
43655
43824
|
return knowledgeError(err);
|
|
43656
43825
|
}
|
|
@@ -43793,7 +43962,7 @@ server.registerTool(
|
|
|
43793
43962
|
"awesomate_knowledge_data",
|
|
43794
43963
|
{
|
|
43795
43964
|
annotations: annotate("awesomate_knowledge_data", MUTATING),
|
|
43796
|
-
description: "The knowledge platform's business-data warehouse (every plan, within the plan's row and dataset limits). action 'metrics': headline numbers for the data tab. 'datasets': the datasets imported and
|
|
43965
|
+
description: "The knowledge platform's business-data warehouse (every plan, within the plan's row and dataset limits). action 'metrics': headline numbers for the data tab. 'datasets': the datasets imported (names and ids, no columns): read this FIRST, then 'dataset' {datasetId} for a dataset's columns, because measures/dimensions must name real columns. 'imports': import job statuses. 'query' {dataset, measures:[{column, agg}], plus optional dimensions/filters passed through}: answer QUANTITATIVE questions from the user's own imported business data (revenue by month, top customers); read-only, results come back as rows to present honestly. 'dataset' {datasetId}: one dataset with its columns and how each is read. 'dataset_update' {datasetId, name?, description?, columns?:[{column_id, name?, semantic_type?, role?, unit?, currency?, canonical_metric_key?, pii?}]}: rename it or settle how its columns are read (show the owner the change first). 'import' {importId}: one import, with any questions it is waiting on. 'import_action' {importId, importAction: approve|reject|withdraw|reimport|answer, answers?}: approve lets a waiting import in, reject turns one away, withdraw takes an import that is already in back out again (confirm:true, after the owner says yes), reimport runs it again, answer {answers: {question key: answer}} replies to its questions. Every import_action changes the owner's numbers: say what it does and get a yes first. New data is imported in the hub UI, not here.",
|
|
43797
43966
|
inputSchema: {
|
|
43798
43967
|
action: external_exports.enum(["metrics", "datasets", "imports", "query", "dataset", "dataset_update", "import", "import_action"]),
|
|
43799
43968
|
datasetId: external_exports.string().max(200).optional().describe("dataset, dataset_update: the dataset id from datasets"),
|
|
@@ -45286,19 +45455,25 @@ server.registerTool(
|
|
|
45286
45455
|
"awesomate_knowledge_upload",
|
|
45287
45456
|
{
|
|
45288
45457
|
annotations: annotate("awesomate_knowledge_upload", MUTATING),
|
|
45289
|
-
description: "Ingest ONE file from the user's own computer into their Knowledge Base. Pass a LOCAL PATH
|
|
45458
|
+
description: "Ingest ONE file from the user's own computer into their Knowledge Base. Pass a LOCAL PATH: 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, md, txt), spreadsheets (csv, tsv, xls, xlsx, xlsm, json; these NEED kind, see below), audio and video (transcribed, Pro and above), and images (OCR, Pro and above). Word/PowerPoint/RTF/EPUB files are NOT parseable yet: the tool refuses them with the workaround (export to PDF, or save as .md/.txt). Max 100 MB per file; bigger media goes through the hub's Knowledge \u2192 Sources page. SPREADSHEETS: pass kind, or the tool refuses before uploading. kind 'sheet' makes each row a searchable, citable record: price lists, fee tables, timetables, FAQs, policies, product lists, anything a customer asks about. kind 'data-import' builds a reportable dataset for totals and trends: job or invoice history, sales by month, read with awesomate_knowledge_data. If it is not obvious which the user wants, ask them; never guess. The same goes for a spreadsheet in the File Manager (fromFilesPath): pass kind with it. The source's title comes from the filename, slugified (spaces, punctuation and non-ASCII letters become _), even when title is passed; fix it afterwards with awesomate_knowledge_sources {action:'rename'}. visibility 'public' publishes the file, so it needs confirm: the account slug, passed only after the user agreed in so many words (the same rule as awesomate_knowledge_sources add). 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; do not loop silently. Returns a job with its live status; poll awesomate_knowledge_sources {action:'jobs'} until it succeeds (a duplicate upload replays the earlier job, and a response already reading succeeded needs no polling), 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.",
|
|
45290
45459
|
inputSchema: {
|
|
45291
45460
|
path: external_exports.string().min(1).max(4096).optional().describe("Path to the file on the user's machine (~ is expanded). Omit when using fromFilesPath."),
|
|
45292
45461
|
fromFilesPath: external_exports.string().max(1024).optional().describe("Instead of a local file: a path in the account's File Manager (their n8n file system), relative to files/, e.g. 'public/reports/weekly.md'. The hub streams it from their storage \u2014 nothing leaves this machine."),
|
|
45293
45462
|
title: external_exports.string().max(300).optional().describe("Used as the uploaded file's name (local uploads only). The platform slugifies it (spaces, punctuation and non-ASCII letters become _), so for a readable title rename the source after the job succeeds"),
|
|
45294
45463
|
kind: external_exports.enum(["sheet", "data-import"]).optional().describe("Spreadsheets only (csv, tsv, xls, xlsx, xlsm, json), and required for them: 'sheet' = each row a searchable, citable record (price lists, timetables, fee tables, FAQs); 'data-import' = a reportable dataset for totals and trends (job or invoice history). Ask the user if unclear; never guess"),
|
|
45295
|
-
visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("Who may retrieve this source (default private). public = customers and public chatbots; irreversible once fetched
|
|
45464
|
+
visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("Who may retrieve this source (default private). public = customers and public chatbots; irreversible once fetched, so it also needs confirm."),
|
|
45465
|
+
confirm: external_exports.string().max(64).optional().describe("visibility 'public' only: the account slug, after the user agreed in so many words"),
|
|
45296
45466
|
tags: external_exports.array(external_exports.string().max(64)).max(64).optional().describe("Free-form tags for the new source"),
|
|
45297
45467
|
collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("Put the new source in these collections (awesomate_knowledge_collections list)")
|
|
45298
45468
|
}
|
|
45299
45469
|
},
|
|
45300
|
-
async ({ path: rawPath, fromFilesPath, title, kind, visibility, tags, collectionIds }) => {
|
|
45470
|
+
async ({ path: rawPath, fromFilesPath, title, kind, visibility, confirm, tags, collectionIds }) => {
|
|
45301
45471
|
try {
|
|
45472
|
+
if (visibility === "public" && !confirm) {
|
|
45473
|
+
return errorResult(
|
|
45474
|
+
new Error("Adding a file as public cannot be undone once it has been fetched or crawled. Ask the user in plain words, then call again with confirm: the account slug. Nothing was uploaded.")
|
|
45475
|
+
);
|
|
45476
|
+
}
|
|
45302
45477
|
if (fromFilesPath) {
|
|
45303
45478
|
const dot = fromFilesPath.lastIndexOf(".");
|
|
45304
45479
|
const filesExt = dot > fromFilesPath.lastIndexOf("/") ? fromFilesPath.slice(dot).toLowerCase() : "";
|
|
@@ -45313,6 +45488,7 @@ server.registerTool(
|
|
|
45313
45488
|
path: fromFilesPath,
|
|
45314
45489
|
...kind ? { kind } : {},
|
|
45315
45490
|
...visibility ? { visibility } : {},
|
|
45491
|
+
...visibility === "public" && confirm ? { confirm } : {},
|
|
45316
45492
|
...tags?.length ? { tags } : {},
|
|
45317
45493
|
...collectionIds?.length ? { collection_ids: collectionIds } : {}
|
|
45318
45494
|
});
|
|
@@ -45367,6 +45543,7 @@ server.registerTool(
|
|
|
45367
45543
|
const filename = title?.trim() ? `${title.trim()}${ext}` : basename(abs);
|
|
45368
45544
|
const uploadQuery = new URLSearchParams();
|
|
45369
45545
|
if (visibility) uploadQuery.set("visibility", visibility);
|
|
45546
|
+
if (visibility === "public" && confirm) uploadQuery.set("confirm", confirm);
|
|
45370
45547
|
if (kind) uploadQuery.set("kind", kind);
|
|
45371
45548
|
for (const t of tags ?? []) uploadQuery.append("tags", t);
|
|
45372
45549
|
for (const c of collectionIds ?? []) uploadQuery.append("collection_ids", c);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/hosting-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.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",
|
package/skill/CHANGELOG.json
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.93.0",
|
|
5
|
+
"highlights": [
|
|
6
|
+
"Claude can put a published agent on your website: it makes a website key for the sites you name, after your yes, and hands you the code to paste. Each website key has its own monthly answer limit, half your account's by default, and Claude tells you the number",
|
|
7
|
+
"Uploading a file as public now needs the same typed confirmation as adding a public page",
|
|
8
|
+
"A delete that finds nothing says the item may already be gone or the id may be wrong, and a delete that timed out says it may have gone through instead of saying nothing changed",
|
|
9
|
+
"Asking within filters works with the workspace's default agent, and a new agent drafted from a goal keeps the name you gave it",
|
|
10
|
+
"Skill update checks compare version numbers properly (0.10 is newer than 0.9)",
|
|
11
|
+
"When a Knowledge agent fails because the account's own OpenRouter key is refused or out of credit, Claude says so and where to fix it, instead of a generic service problem"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.92.2",
|
|
16
|
+
"highlights": [
|
|
17
|
+
"Publishing a staging copy keeps the live site's search engine setting, instead of hiding the live site from Google like the staging copy, and says so if it could not"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.92.1",
|
|
22
|
+
"highlights": [
|
|
23
|
+
"Turning on Knowledge again now puts back a missing n8n credential for it, and says why when it cannot (for example, n8n is not connected yet)"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.92.0",
|
|
28
|
+
"highlights": [
|
|
29
|
+
"Claude says whose key it is: the account owner's, or a team member's own, which does only what that person can do",
|
|
30
|
+
"Connecting on a computer already connected to the same account as someone else stops and asks, instead of replacing their key"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
3
33
|
{
|
|
4
34
|
"version": "0.91.1",
|
|
5
35
|
"highlights": [
|
|
@@ -40,6 +40,7 @@ before any work** — every tool response is stamped `account: <slug> (source:
|
|
|
40
40
|
once (plus `awesomate_get_limits` before any create/change) and cache:
|
|
41
41
|
|
|
42
42
|
- **`plan`** and **`capabilities.shell`** — decides which branch below you're on.
|
|
43
|
+
- **`key`**: whose key this is. When `key.holder` is `person`, say so first ("This is Mia's own Claude Code, with view-only access") and never offer work their access does not include; if they ask for it, say the account owner can do it. A person's key never has a shell.
|
|
43
44
|
- **`attention`** — `{unreadNotifications, erroringWorkflows7d, patExpiresInDays}`.
|
|
44
45
|
If it shows erroring workflows or unread notifications, say so in ONE line
|
|
45
46
|
("two of your automations failed this week — want me to look?") before the
|
|
@@ -230,7 +231,7 @@ MCP, so say that rather than stopping.
|
|
|
230
231
|
| Tool | Reach for it when |
|
|
231
232
|
|---|---|
|
|
232
233
|
| `awesomate_whoami` | First call of every session — which account this folder acts on (zero network) |
|
|
233
|
-
| `awesomate_get_context` | Session start: plan, capabilities, PAT expiry, `attention`, skill updates, and `key`: whose key this is (
|
|
234
|
+
| `awesomate_get_context` | Session start: plan, capabilities, PAT expiry, `attention`, skill updates, and `key`: whose key this is (the account owner's, so everything is done as the owner, or a team member's own, `holder: 'person'`, which does only what they can do in the hub). A tool the key's scopes do not cover is refused at once in the hub's words: relay it, never retry |
|
|
234
235
|
| `awesomate_get_limits` | Before any create/change — live plan limits + workflow/AI-editor credit balances |
|
|
235
236
|
| `awesomate_get_plan_features` | The live plan ladder, so upgrade talk is accurate |
|
|
236
237
|
| `awesomate_business` | Before writing anything in the business's name: its own name, services, voice, colours, contact details. `read:'documents'` / `read:'document'` for the brand guide, voice guide and business summary; `read:'suggestions'` for details awaiting the owner's yes |
|
|
@@ -462,7 +463,10 @@ AI crawlers — anyone with the link can view, perfect for client review).
|
|
|
462
463
|
`--domain <staging domain>`), send the user the staging URL to review.
|
|
463
464
|
- `awesomate_site_staging_promote <live domain>` — publishes staging over live.
|
|
464
465
|
Live is auto-snapshotted first; report the returned `preSnapshotId` as the
|
|
465
|
-
undo handle (`awesomate_rollback_site`).
|
|
466
|
+
undo handle (`awesomate_rollback_site`). Live keeps its own search engine
|
|
467
|
+
setting; if the result carries `searchEngineVisibility.warning`, tell the
|
|
468
|
+
user live may now be hidden from search engines and how to check.
|
|
469
|
+
**Confirm before promoting.**
|
|
466
470
|
- `awesomate_site_staging_discard <live domain>` — deletes the staging copy;
|
|
467
471
|
live untouched. **Confirm — unpublished staging changes are lost.**
|
|
468
472
|
|
|
@@ -25,6 +25,14 @@ The bootstrap prints a final `AWESOMATE CONNECT: SUCCESS account=<slug>` (or
|
|
|
25
25
|
silence. `PARTIAL` means the token/pin are stored but a step (listed in
|
|
26
26
|
`issues=`) needs the printed manual fix.
|
|
27
27
|
|
|
28
|
+
`FAILED reason=profile-belongs-to-someone-else` means this computer is already
|
|
29
|
+
connected to the same account as a different person (an owner and a team member
|
|
30
|
+
sharing a computer). Nothing was saved. Ask the person at the computer which key
|
|
31
|
+
should stay; re-run with `--replace` only on their clear yes, never on your own.
|
|
32
|
+
Otherwise they connect from their own user account on the computer. The key made
|
|
33
|
+
by the refused run is unused: they can remove it in the hub at Settings,
|
|
34
|
+
Account, Your Claude Code.
|
|
35
|
+
|
|
28
36
|
## Sandboxed / ephemeral environments (cloud agent containers)
|
|
29
37
|
|
|
30
38
|
- **Egress proxy**: if `HTTPS_PROXY`/`HTTP_PROXY` is set, the environment
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* access token itself is never printed.
|
|
12
12
|
*
|
|
13
13
|
* Usage (Claude runs this — the user doesn't touch a terminal):
|
|
14
|
-
* node bootstrap.mjs --code amt_bs_XXXX [--account <slug>] [--pin-dir <dir>]
|
|
14
|
+
* node bootstrap.mjs --code amt_bs_XXXX [--account <slug>] [--pin-dir <dir>] [--replace]
|
|
15
15
|
* node bootstrap.mjs --pat amt_pat_XXXX [--account <slug>] # resume: skip redeem
|
|
16
16
|
*
|
|
17
17
|
* --account asserts WHICH account this connect is for — mismatch aborts before
|
|
@@ -472,6 +472,7 @@ async function main() {
|
|
|
472
472
|
shellAccess: Boolean(ctx.capabilities?.shell),
|
|
473
473
|
cpanel: ctx.cpanel ?? null,
|
|
474
474
|
expiresAt: ctx.patExpiresAt ?? null,
|
|
475
|
+
...(ctx.key?.holder === 'person' ? { keyKind: 'person_device' } : {}),
|
|
475
476
|
};
|
|
476
477
|
} else {
|
|
477
478
|
console.log(`Redeeming setup code against ${apiBase} …`);
|
|
@@ -514,6 +515,21 @@ async function main() {
|
|
|
514
515
|
redeemed.email = redeemed.email ?? context?.email ?? null;
|
|
515
516
|
}
|
|
516
517
|
|
|
518
|
+
// One computer, two people (per-person keys design §6.1): never silently swap whose key this
|
|
519
|
+
// account uses here. An owner and a team member on a shared computer each keep their own.
|
|
520
|
+
const saved = readJsonFile(credPath) ?? {};
|
|
521
|
+
const before = (saved.profiles ?? {})[slug] ?? (saved.slug === slug ? saved : null);
|
|
522
|
+
const beforeEmail = typeof before?.email === 'string' ? before.email.trim().toLowerCase() : null;
|
|
523
|
+
const newEmail = typeof redeemed.email === 'string' ? redeemed.email.trim().toLowerCase() : null;
|
|
524
|
+
const replacing = Boolean(beforeEmail && newEmail && beforeEmail !== newEmail);
|
|
525
|
+
if (replacing && !process.argv.includes('--replace')) {
|
|
526
|
+
console.error(`This computer is already connected to "${slug}" as ${beforeEmail}. Connecting as ${newEmail} would replace that key here.`);
|
|
527
|
+
console.error('Ask the person at this computer which key should stay. Only on their clear yes, run the same command again with --replace; otherwise connect from your own user account on this computer.');
|
|
528
|
+
console.error(`The new key for ${newEmail} was made but not saved here. If it will not be used, remove it in the hub: Settings, Account, Your Claude Code.`);
|
|
529
|
+
console.error('AWESOMATE CONNECT: FAILED reason=profile-belongs-to-someone-else');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
|
|
517
533
|
const profile = {
|
|
518
534
|
apiBase,
|
|
519
535
|
pat: redeemed.pat,
|
|
@@ -523,6 +539,8 @@ async function main() {
|
|
|
523
539
|
plan: redeemed.plan,
|
|
524
540
|
cpanel: redeemed.cpanel ?? null,
|
|
525
541
|
expiresAt: redeemed.expiresAt ?? null,
|
|
542
|
+
// Someone else's SSH key never carries over to this person's profile.
|
|
543
|
+
...(replacing ? { ssh: undefined } : {}),
|
|
526
544
|
};
|
|
527
545
|
mergeProfile(profile);
|
|
528
546
|
console.log(`✓ Access token stored as profile "${slug}" in ${credPath} (chmod 600). Plan: ${redeemed.plan}.`);
|
|
@@ -571,6 +589,8 @@ async function main() {
|
|
|
571
589
|
console.log(' Everything else is set up. This usually means hosting isn\'t fully provisioned — contact Awesomate support, then ask me to "retry SSH setup".');
|
|
572
590
|
}
|
|
573
591
|
}
|
|
592
|
+
} else if (redeemed.keyKind === 'person_device') {
|
|
593
|
+
console.log('• This is your own key: it does what you can do in the hub. SSH and deploys stay with the account owner.');
|
|
574
594
|
} else {
|
|
575
595
|
console.log('• Plan has no shell access — read/status mode. Upgrade to Support Plus for SSH + deploy.');
|
|
576
596
|
}
|
|
@@ -164,6 +164,7 @@ confirm:true}`, both after the owner's yes.
|
|
|
164
164
|
| 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}` · `GET /api/knowledge/agents/{logs,answer-rates,where-used}` · `GET\|POST /api/knowledge/agents/policies` · `GET\|PATCH\|DELETE /api/knowledge/agents/policies/:policy_id` · `POST /api/knowledge/agents/policies/:policy_id/{suspend,resume}` |
|
|
165
165
|
| **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. It ignores the agent's audience. |
|
|
166
166
|
| **Ask the PUBLISHED agent as a visitor** (`action:'visitor_test'`) | `POST /api/knowledge/agents/:agent_id/visitor-chat` `{message (max 2000), session_id?}`. 403 `upgrade_required` below the public-widget plan; an unanswered question lands in the Questions inbox. |
|
|
167
|
+
| **Website chat key** (`action:'website_key'`, `website_keys`, `revoke_website_key`; owner only, Support Plus and above) | `POST /api/knowledge/agents/keys` `{class:'p', agent_id, allowed_origins (https only, no path or wildcard), label?, request_id?, monthly_answer_cap?}` returns the key ONCE with its `monthly_answer_cap`; with no cap the hub sets half the account's monthly answers at that moment, and a key's cap cannot be changed later (a new limit is a new key). The tool refuses an agent that is not published, not public-audience, or paused. List with `GET /api/knowledge/agents/keys` (class `p`), revoke with `DELETE /api/knowledge/agents/keys/:key_id`. Every one only after the owner's yes. |
|
|
167
168
|
| Entity layer probe (`available` false = not enabled yet, stop) | `GET /api/knowledge/entities` |
|
|
168
169
|
| People list / detail | `GET /api/knowledge/people?status=named\|unknown\|hidden\|all` · `GET /api/knowledge/people/:id` — returns `{people, next_cursor}` and **no `counts` key**. |
|
|
169
170
|
| Entity COUNTS + availability | `GET /api/knowledge/entities` → `{available, counts: {...}}` — counts are nested under `counts`. Reading `people.counts?.x ?? 0` prints a confident row of zeros next to data that plainly exists. |
|
|
@@ -260,8 +260,8 @@ while 1Brain is on.
|
|
|
260
260
|
person's Task goes too), or move one on an older course to the role's
|
|
261
261
|
current course (progress is read again from the new course). Each only on
|
|
262
262
|
the owner's yes.
|
|
263
|
-
- **`my_onboarding`**: the plans of the person this key belongs to
|
|
264
|
-
|
|
263
|
+
- **`my_onboarding`**: the plans of the person this key belongs to: the
|
|
264
|
+
account owner, or a team member on their own key.
|
|
265
265
|
- Writing quizzes and acknowledgements, and sharing a course with a learner,
|
|
266
266
|
are 1Brain's own connector's work (2d), owners and admins only.
|
|
267
267
|
|