@awesomate/hosting-mcp 0.17.0 → 0.19.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/README.md +72 -45
- package/dist/index.js +648 -58
- package/package.json +2 -2
- package/skill/CHANGELOG.json +29 -0
- package/skill/awesomate-app-builder/SKILL.md +6 -3
- package/skill/awesomate-app-builder/references/node-recipes.md +3 -2
- package/skill/awesomate-app-builder/references/stack-decision.md +1 -1
- package/skill/awesomate-hosting/SKILL.md +84 -18
- package/skill/awesomate-hosting/scripts/bootstrap.mjs +11 -4
- package/skill/awesomate-knowledge/SKILL.md +16 -2
- package/skill/awesomate-n8n/SKILL.md +28 -2
- package/skill/awesomate-n8n/references/error-handling.md +91 -0
- package/skill/awesomate-n8n/references/possibilities.md +32 -0
- package/skill/awesomate-n8n/references/recurring-reports.md +82 -0
- package/skill/awesomate-seo/SKILL.md +38 -3
package/dist/index.js
CHANGED
|
@@ -39675,7 +39675,7 @@ var StdioServerTransport = class {
|
|
|
39675
39675
|
};
|
|
39676
39676
|
|
|
39677
39677
|
// src/index.ts
|
|
39678
|
-
import { readFileSync as
|
|
39678
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
39679
39679
|
import { homedir as homedir3 } from "node:os";
|
|
39680
39680
|
import { join as join3, dirname as dirname3 } from "node:path";
|
|
39681
39681
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -39892,14 +39892,28 @@ async function hubRequest(config3, method, path, jsonBody, opts = {}) {
|
|
|
39892
39892
|
}
|
|
39893
39893
|
if (!res.ok) {
|
|
39894
39894
|
const serverMsg = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : `Request failed (${res.status})`;
|
|
39895
|
+
const code = body && typeof body === "object" && "code" in body && typeof body.code === "string" ? body.code : null;
|
|
39895
39896
|
let hint = "";
|
|
39896
39897
|
if (res.status === 401) {
|
|
39897
39898
|
hint = " Your access token is invalid or expired \u2014 ask the user to open the hub Sites page (hub.awesomate.ai/sites), generate a fresh Connect prompt, and re-run the bootstrap.";
|
|
39899
|
+
} else if (res.status === 403 && code === "consent_required") {
|
|
39900
|
+
const settingsUrl = body && typeof body === "object" && "settingsUrl" in body && typeof body.settingsUrl === "string" ? body.settingsUrl : "https://hub.awesomate.ai/settings?tab=privacy";
|
|
39901
|
+
hint = ` This needs a privacy toggle the user must flip themselves \u2014 send them to ${settingsUrl}, wait for them to confirm, then retry. Never suggest a plan upgrade for a consent denial.`;
|
|
39898
39902
|
} else if (res.status === 403) {
|
|
39899
39903
|
const missing = body && typeof body === "object" && "missingScopes" in body ? ` (missing: ${JSON.stringify(body.missingScopes)})` : "";
|
|
39900
|
-
hint = ` This plan/token does not allow that action${missing}.
|
|
39904
|
+
hint = ` This plan/token does not allow that action${missing}. Relay any upgradeUrl/recommendedPlan in the payload honestly and let the user decide \u2014 never retry.`;
|
|
39905
|
+
} else if (res.status === 402) {
|
|
39906
|
+
hint = " Payment/allowance required \u2014 NOTHING was purchased. State the cost plainly and let the user decide in the hub; this tool never spends money.";
|
|
39907
|
+
} else if (res.status === 404) {
|
|
39908
|
+
hint = " Not found \u2014 usually the resource does not exist for THIS account. Re-check the id/domain before retrying; do not invent one.";
|
|
39909
|
+
} else if (res.status === 409) {
|
|
39910
|
+
hint = " Conflict \u2014 the payload carries the specifics (a limit reached, or a prerequisite missing). Read its code field; do not blind-retry.";
|
|
39911
|
+
} else if (res.status === 422) {
|
|
39912
|
+
hint = " The request was understood and refused \u2014 YOUR arguments were wrong or the remote command failed. The payload says why; fix and retry once.";
|
|
39913
|
+
} else if (res.status === 429) {
|
|
39914
|
+
hint = " Rate/quota limit hit. Wait before retrying; if it is a daily plan quota, relay that honestly instead of looping.";
|
|
39901
39915
|
} else if (res.status === 503) {
|
|
39902
|
-
hint = " The hub is temporarily unable to
|
|
39916
|
+
hint = " The hub is temporarily unable to serve this \u2014 retry shortly.";
|
|
39903
39917
|
}
|
|
39904
39918
|
throw new HubApiError(`${serverMsg}.${hint}`, res.status, body);
|
|
39905
39919
|
}
|
|
@@ -39919,28 +39933,70 @@ function hubDelete(config3, path, jsonBody) {
|
|
|
39919
39933
|
}
|
|
39920
39934
|
|
|
39921
39935
|
// src/skills.ts
|
|
39922
|
-
import { cpSync, existsSync as existsSync2, readdirSync, writeFileSync } from "node:fs";
|
|
39936
|
+
import { cpSync, existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
|
|
39923
39937
|
import { homedir as homedir2 } from "node:os";
|
|
39924
39938
|
import { join as join2, dirname as dirname2 } from "node:path";
|
|
39925
39939
|
import { fileURLToPath } from "node:url";
|
|
39926
39940
|
var PACKAGE_ROOT = join2(dirname2(fileURLToPath(import.meta.url)), "..");
|
|
39927
|
-
|
|
39941
|
+
var DEFAULT_SOURCE_ROOT = join2(PACKAGE_ROOT, "skill");
|
|
39942
|
+
function bundledSkillNames(sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
39943
|
+
try {
|
|
39944
|
+
return readdirSync(sourceRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync2(join2(sourceRoot, e.name, "SKILL.md"))).map((e) => e.name);
|
|
39945
|
+
} catch {
|
|
39946
|
+
return [];
|
|
39947
|
+
}
|
|
39948
|
+
}
|
|
39949
|
+
function installedSkillVersion(name) {
|
|
39950
|
+
try {
|
|
39951
|
+
return readFileSync2(join2(homedir2(), ".claude", "skills", name, ".installed-version"), "utf8").trim() || null;
|
|
39952
|
+
} catch {
|
|
39953
|
+
return null;
|
|
39954
|
+
}
|
|
39955
|
+
}
|
|
39956
|
+
function skillVersions(sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
39957
|
+
return bundledSkillNames(sourceRoot).map((name) => ({ name, installed: installedSkillVersion(name) }));
|
|
39958
|
+
}
|
|
39959
|
+
function walkFiles(dir, prefix = "") {
|
|
39960
|
+
const out = [];
|
|
39961
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
39962
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
39963
|
+
if (entry.isDirectory()) out.push(...walkFiles(join2(dir, entry.name), rel));
|
|
39964
|
+
else out.push(rel);
|
|
39965
|
+
}
|
|
39966
|
+
return out;
|
|
39967
|
+
}
|
|
39968
|
+
function installSkills(version2, sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
39928
39969
|
const destRoot = join2(homedir2(), ".claude", "skills");
|
|
39929
39970
|
const installed = [];
|
|
39971
|
+
const updates = [];
|
|
39972
|
+
const removedOrphans = [];
|
|
39930
39973
|
for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
39931
39974
|
if (!entry.isDirectory()) continue;
|
|
39932
39975
|
const src = join2(sourceRoot, entry.name);
|
|
39933
39976
|
if (!existsSync2(join2(src, "SKILL.md"))) continue;
|
|
39934
39977
|
const dest = join2(destRoot, entry.name);
|
|
39978
|
+
const from = installedSkillVersion(entry.name);
|
|
39935
39979
|
cpSync(src, dest, { recursive: true });
|
|
39980
|
+
try {
|
|
39981
|
+
const shipped = new Set(walkFiles(src));
|
|
39982
|
+
for (const rel of walkFiles(dest)) {
|
|
39983
|
+
if (rel === ".installed-version") continue;
|
|
39984
|
+
if (!shipped.has(rel)) {
|
|
39985
|
+
rmSync(join2(dest, rel), { force: true });
|
|
39986
|
+
removedOrphans.push(`${entry.name}/${rel}`);
|
|
39987
|
+
}
|
|
39988
|
+
}
|
|
39989
|
+
} catch {
|
|
39990
|
+
}
|
|
39936
39991
|
try {
|
|
39937
39992
|
writeFileSync(join2(dest, ".installed-version"), `${version2}
|
|
39938
39993
|
`);
|
|
39939
39994
|
} catch {
|
|
39940
39995
|
}
|
|
39941
39996
|
installed.push(entry.name);
|
|
39997
|
+
updates.push({ name: entry.name, from, to: version2 });
|
|
39942
39998
|
}
|
|
39943
|
-
return { installed, version: version2, skillsDir: destRoot };
|
|
39999
|
+
return { installed, updates, removedOrphans, version: version2, skillsDir: destRoot };
|
|
39944
40000
|
}
|
|
39945
40001
|
|
|
39946
40002
|
// src/knowledge.ts
|
|
@@ -39950,11 +40006,11 @@ function scrubKeyMaterial(text) {
|
|
|
39950
40006
|
var KNOWLEDGE_GATE_COPY = {
|
|
39951
40007
|
displayName: "Knowledge Base",
|
|
39952
40008
|
requiredPlan: "Pro",
|
|
39953
|
-
description: "Turn your own content
|
|
40009
|
+
description: "Turn your own content (videos, blog posts, books, socials) into a knowledge base your AI agents answer from, with a citation for every claim. If it isn't in your content, the bot says so instead of making something up.",
|
|
39954
40010
|
bullets: [
|
|
39955
40011
|
"Answers come only from your published content, cited back to the exact source",
|
|
39956
|
-
`No made-up answers in front of a customer
|
|
39957
|
-
"Feed it videos, podcasts, blog posts and books
|
|
40012
|
+
`No made-up answers in front of a customer: no evidence means an honest "I don't know"`,
|
|
40013
|
+
"Feed it videos, podcasts, blog posts and books. We transcribe and index the lot",
|
|
39958
40014
|
"Plugs straight into your n8n chat agents, so the 10pm enquiry gets a real answer",
|
|
39959
40015
|
"Hosted in Sydney, isolated to your account, and gone the moment you delete it"
|
|
39960
40016
|
]
|
|
@@ -39965,7 +40021,7 @@ function upsellPayload(apiBase, plan) {
|
|
|
39965
40021
|
...typeof plan === "string" ? { plan } : {},
|
|
39966
40022
|
feature: KNOWLEDGE_GATE_COPY,
|
|
39967
40023
|
billingUrl: `${apiBase}/billing`,
|
|
39968
|
-
note: "Knowledge Base needs the Pro plan. Do NOT retry
|
|
40024
|
+
note: "Knowledge Base needs the Pro plan. Do NOT retry. Relay this honestly, share the billing link, and let the user decide. Preview + explicit confirmation before any upgrade."
|
|
39969
40025
|
};
|
|
39970
40026
|
}
|
|
39971
40027
|
function consentPayload(message) {
|
|
@@ -39973,14 +40029,14 @@ function consentPayload(message) {
|
|
|
39973
40029
|
consent_required: true,
|
|
39974
40030
|
message,
|
|
39975
40031
|
settingsPath: 'Settings \u2192 Privacy \u2192 "Knowledge Platform"',
|
|
39976
|
-
note: "The user must flip the consent themselves in the hub
|
|
40032
|
+
note: "The user must flip the consent themselves in the hub. Do NOT retry until they confirm it is on."
|
|
39977
40033
|
};
|
|
39978
40034
|
}
|
|
39979
40035
|
function notAvailablePayload(apiBase) {
|
|
39980
40036
|
return {
|
|
39981
40037
|
available: false,
|
|
39982
40038
|
reason: "not_available_on_this_hub",
|
|
39983
|
-
note: "Knowledge Base is not enabled on this hub (feature switch off, or the hub predates it). The connection itself is fine
|
|
40039
|
+
note: "Knowledge Base is not enabled on this hub (feature switch off, or the hub predates it). The connection itself is fine; do not retry. The user can ask Awesomate support when it will be available for their account.",
|
|
39984
40040
|
hubUrl: apiBase
|
|
39985
40041
|
};
|
|
39986
40042
|
}
|
|
@@ -39990,7 +40046,7 @@ function packConfirmationPayload(body) {
|
|
|
39990
40046
|
purchased: false,
|
|
39991
40047
|
dimension: body.dimension ?? null,
|
|
39992
40048
|
pack: body.pack ?? null,
|
|
39993
|
-
note: "This ingest would exceed the included allowance. NOTHING was purchased and the source was NOT added. An Ingestion Pack costs 1 credit ($100)
|
|
40049
|
+
note: "This ingest would exceed the included allowance. NOTHING was purchased and the source was NOT added. An Ingestion Pack costs 1 credit ($100). State that plainly, get an explicit yes, then send the user to the hub Knowledge \u2192 Usage page to buy it (this tool never spends credits)."
|
|
39994
40050
|
};
|
|
39995
40051
|
}
|
|
39996
40052
|
function bodyOf(err) {
|
|
@@ -40018,14 +40074,14 @@ async function knowledgeStatus(config3) {
|
|
|
40018
40074
|
const annotated = {
|
|
40019
40075
|
...status,
|
|
40020
40076
|
usage_glossary: {
|
|
40021
|
-
pages: "month-to-date hub-submitted page quota used (vs usage.included.pages)
|
|
40077
|
+
pages: "month-to-date hub-submitted page quota used (vs usage.included.pages): NOT the size of the library",
|
|
40022
40078
|
sources_total: 'sources currently in the library; kinds in usage.sources_by_kind or awesomate_knowledge_sources {action:"summary"}'
|
|
40023
40079
|
}
|
|
40024
40080
|
};
|
|
40025
40081
|
if (usage.sources_total_source === "hourly_gauge") {
|
|
40026
|
-
annotated.note = "The live source count failed for this call: usage.sources_total / sources_failed are the last hourly gauge
|
|
40082
|
+
annotated.note = "The live source count failed for this call: usage.sources_total / sources_failed are the last hourly gauge: possibly stale, and 0 for a library the gauge could never count. Retry before quoting them; the tenant itself is active.";
|
|
40027
40083
|
} else if (usage.sources_total_truncated === true) {
|
|
40028
|
-
annotated.note = `usage.sources_total is a floor: the live count stopped at ${String(usage.sources_total)} sources (paged-count cap). The library holds at least that many
|
|
40084
|
+
annotated.note = `usage.sources_total is a floor: the live count stopped at ${String(usage.sources_total)} sources (paged-count cap). The library holds at least that many: never quote it as the exact total.`;
|
|
40029
40085
|
}
|
|
40030
40086
|
return annotated;
|
|
40031
40087
|
} catch (err) {
|
|
@@ -40038,7 +40094,7 @@ async function knowledgeProvision(config3) {
|
|
|
40038
40094
|
if (result.pending) {
|
|
40039
40095
|
return {
|
|
40040
40096
|
...result,
|
|
40041
|
-
note: "Provisioning continues in the background
|
|
40097
|
+
note: "Provisioning continues in the background. Poll awesomate_knowledge_status until the tenant is active (a few minutes at most). Re-calling provision is safe (idempotent)."
|
|
40042
40098
|
};
|
|
40043
40099
|
}
|
|
40044
40100
|
return result;
|
|
@@ -40058,7 +40114,7 @@ function describeSourcePage(page) {
|
|
|
40058
40114
|
...page,
|
|
40059
40115
|
page: { returned: page.sources.length, kinds_on_this_page: kinds, has_more: hasMore },
|
|
40060
40116
|
...hasMore ? {
|
|
40061
|
-
note: 'This is ONE page of the most recently ingested sources, not the whole library
|
|
40117
|
+
note: 'This is ONE page of the most recently ingested sources, not the whole library: do not summarise the knowledge base from it. Pass cursor=next_cursor to continue (limit up to 200). For the total, use awesomate_knowledge_status \u2192 usage.sources_total (sources_total_truncated:true means "at least that many").'
|
|
40062
40118
|
} : {}
|
|
40063
40119
|
};
|
|
40064
40120
|
}
|
|
@@ -40104,7 +40160,7 @@ async function knowledgeSources(config3, args) {
|
|
|
40104
40160
|
if (!args.url && !args.sitemap) {
|
|
40105
40161
|
return {
|
|
40106
40162
|
error: "invalid_request",
|
|
40107
|
-
note: "add needs url or sitemap. For a local FILE, send the user to the hub: Knowledge \u2192 Sources \u2192 Upload
|
|
40163
|
+
note: "add needs url or sitemap. For a local FILE, send the user to the hub: Knowledge \u2192 Sources \u2192 Upload. Files stream there; this tool cannot carry file bytes in v1."
|
|
40108
40164
|
};
|
|
40109
40165
|
}
|
|
40110
40166
|
const body = args.url ? { url: args.url } : { sitemap: args.sitemap, ...args.since ? { since: args.since } : {} };
|
|
@@ -40139,7 +40195,7 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
40139
40195
|
detail: streamError ?? (streamedAnswer || null),
|
|
40140
40196
|
configured_fallback: fallback,
|
|
40141
40197
|
...session,
|
|
40142
|
-
note: "The knowledge platform failed to answer
|
|
40198
|
+
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."
|
|
40143
40199
|
};
|
|
40144
40200
|
}
|
|
40145
40201
|
if (status !== "ok") {
|
|
@@ -40149,7 +40205,7 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
40149
40205
|
not_in_verified_content: true,
|
|
40150
40206
|
configured_fallback: fallback,
|
|
40151
40207
|
...session,
|
|
40152
|
-
note: "The knowledge base has no verified answer for this
|
|
40208
|
+
note: "The knowledge base has no verified answer for this. Relay that honestly (use the configured fallback wording if present). Never fill the gap from memory."
|
|
40153
40209
|
};
|
|
40154
40210
|
}
|
|
40155
40211
|
const rawSources = Array.isArray(meta.sources) ? meta.sources : [];
|
|
@@ -40166,7 +40222,7 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
40166
40222
|
sources,
|
|
40167
40223
|
...typeof meta.score === "number" ? { score: meta.score } : {},
|
|
40168
40224
|
...session,
|
|
40169
|
-
note: "Present the answer with its numbered sources
|
|
40225
|
+
note: "Present the answer with its numbered sources; the citations are the product."
|
|
40170
40226
|
};
|
|
40171
40227
|
}
|
|
40172
40228
|
async function knowledgeAsk(config3, args) {
|
|
@@ -40174,7 +40230,7 @@ async function knowledgeAsk(config3, args) {
|
|
|
40174
40230
|
const raw = await hubPost(
|
|
40175
40231
|
config3,
|
|
40176
40232
|
"/api/knowledge/chat",
|
|
40177
|
-
{ question: args.question, ...args.session ? { session: args.session } : {} },
|
|
40233
|
+
{ question: args.question, ...args.session ? { session: args.session } : {}, ...args.filters ? { filters: askFilters(args.filters) } : {} },
|
|
40178
40234
|
{ accept: "application/json" }
|
|
40179
40235
|
);
|
|
40180
40236
|
if (raw && typeof raw === "object") {
|
|
@@ -40210,10 +40266,10 @@ function entityLayerUnavailablePayload() {
|
|
|
40210
40266
|
return {
|
|
40211
40267
|
available: false,
|
|
40212
40268
|
reason: "entity_layer_not_available",
|
|
40213
|
-
note: "People, places and connections are not switched on for this knowledge base yet (the platform has not deployed the entity layer for this tenant). Nothing to fix on the user's side
|
|
40269
|
+
note: "People, places and connections are not switched on for this knowledge base yet (the platform has not deployed the entity layer for this tenant). Nothing to fix on the user's side: do not retry; the counts appear once the first resolution runs."
|
|
40214
40270
|
};
|
|
40215
40271
|
}
|
|
40216
|
-
var PHOTOS_NOTE = "Text only: names, mention counts and the sources behind them. Photos of unnamed people are only viewable on the hub Knowledge \u2192 People page
|
|
40272
|
+
var PHOTOS_NOTE = "Text only: names, mention counts and the sources behind them. Photos of unnamed people are only viewable on the hub Knowledge \u2192 People page: never guess who someone is from a handle.";
|
|
40217
40273
|
function invalid(note) {
|
|
40218
40274
|
return { error: "invalid_request", note };
|
|
40219
40275
|
}
|
|
@@ -40224,7 +40280,7 @@ function mapPeopleError(err, apiBase) {
|
|
|
40224
40280
|
return {
|
|
40225
40281
|
error: "not_found",
|
|
40226
40282
|
message: typeof body.message === "string" ? body.message : "No such person or entity on this knowledge base",
|
|
40227
|
-
note: "Use the exact person_id / entity_id from a fresh list
|
|
40283
|
+
note: "Use the exact person_id / entity_id from a fresh list: ids are opaque and per-tenant."
|
|
40228
40284
|
};
|
|
40229
40285
|
}
|
|
40230
40286
|
}
|
|
@@ -40256,13 +40312,13 @@ async function knowledgePeople(config3, args) {
|
|
|
40256
40312
|
const page = await hubGet(config3, `/api/knowledge/entities/aliases?${q.toString()}`);
|
|
40257
40313
|
return {
|
|
40258
40314
|
...page,
|
|
40259
|
-
note: "Each suggestion is a text name the content uses that probably refers to a known entity. Show the user the alias, the suggested match and the confidence, then use decide with the (kind, alias_norm) pair
|
|
40315
|
+
note: "Each suggestion is a text name the content uses that probably refers to a known entity. Show the user the alias, the suggested match and the confidence, then use decide with the (kind, alias_norm) pair: only after they explicitly say accept, reject, or create."
|
|
40260
40316
|
};
|
|
40261
40317
|
}
|
|
40262
40318
|
const result = await hubPost(config3, "/api/knowledge/entities/resolve", {});
|
|
40263
40319
|
return {
|
|
40264
40320
|
...result,
|
|
40265
|
-
note: result.status === "queued" ? "Resolution queued. It re-scans the content for people/places/topics and counts toward the ingestion allowance; the user's names and decisions are never undone. Check back with list for the refreshed counts." : "A resolution is already queued or running
|
|
40321
|
+
note: result.status === "queued" ? "Resolution queued. It re-scans the content for people/places/topics and counts toward the ingestion allowance; the user's names and decisions are never undone. Check back with list for the refreshed counts." : "A resolution is already queued or running: nothing new was started. Check back with list for the refreshed counts."
|
|
40266
40322
|
};
|
|
40267
40323
|
}
|
|
40268
40324
|
if (args.action === "get") {
|
|
@@ -40278,7 +40334,7 @@ async function knowledgePeople(config3, args) {
|
|
|
40278
40334
|
let body;
|
|
40279
40335
|
if (args.action === "rename") {
|
|
40280
40336
|
const name = args.displayName?.trim();
|
|
40281
|
-
if (!name) return invalid("rename needs displayName (1\u201380 chars)
|
|
40337
|
+
if (!name) return invalid("rename needs displayName (1\u201380 chars): the name the USER gave, never a guess");
|
|
40282
40338
|
body = { display_name: name };
|
|
40283
40339
|
} else {
|
|
40284
40340
|
body = { status: args.action === "hide" ? "hidden" : "unknown" };
|
|
@@ -40292,7 +40348,7 @@ async function knowledgePeople(config3, args) {
|
|
|
40292
40348
|
changed: Object.keys(body),
|
|
40293
40349
|
requested: body,
|
|
40294
40350
|
...result,
|
|
40295
|
-
note: args.action === "rename" ? "The name is attached to every source this person appears in and the agent uses it from the next reindex. Read the new display_name back to the user to confirm." : args.action === "hide" ? "Hidden
|
|
40351
|
+
note: args.action === "rename" ? "The name is attached to every source this person appears in and the agent uses it from the next reindex. Read the new display_name back to the user to confirm." : args.action === "hide" ? "Hidden: this person no longer appears in answers. Reversible with unhide." : "Unhidden: back in answers from the next resolution."
|
|
40296
40352
|
};
|
|
40297
40353
|
}
|
|
40298
40354
|
if (args.action === "merge") {
|
|
@@ -40325,7 +40381,7 @@ async function knowledgePeople(config3, args) {
|
|
|
40325
40381
|
return {
|
|
40326
40382
|
requested: body,
|
|
40327
40383
|
...result,
|
|
40328
|
-
note: args.decision === "reject" ? "Rejected
|
|
40384
|
+
note: args.decision === "reject" ? "Rejected: the alias stays a plain text mention and will not be suggested again." : "Accepted: mentions re-link on the next resolution. Read entity_id back to the user."
|
|
40329
40385
|
};
|
|
40330
40386
|
}
|
|
40331
40387
|
return invalid(`unknown action ${String(args.action)}`);
|
|
@@ -40357,6 +40413,101 @@ async function knowledgeAgent(config3, args) {
|
|
|
40357
40413
|
return mapKnowledgeError(err, config3.apiBase);
|
|
40358
40414
|
}
|
|
40359
40415
|
}
|
|
40416
|
+
function askFilters(f) {
|
|
40417
|
+
const out = {};
|
|
40418
|
+
for (const name of ["kind", "topic", "person", "place", "category", "author"]) {
|
|
40419
|
+
if (f[name] !== void 0) out[name] = f[name];
|
|
40420
|
+
}
|
|
40421
|
+
const docs = f.doc ?? f.doc_ids;
|
|
40422
|
+
if (docs !== void 0) out.doc_ids = docs;
|
|
40423
|
+
if (f.year !== void 0) {
|
|
40424
|
+
const years = Array.isArray(f.year) ? f.year : [f.year];
|
|
40425
|
+
out.year = years.map((y) => Number(y)).filter((y) => Number.isInteger(y));
|
|
40426
|
+
}
|
|
40427
|
+
return out;
|
|
40428
|
+
}
|
|
40429
|
+
var SENTINEL_PRE = "\uE000";
|
|
40430
|
+
var SENTINEL_POST = "\uE001";
|
|
40431
|
+
function plainSnippet(text) {
|
|
40432
|
+
return text.split(SENTINEL_PRE).join("**").split(SENTINEL_POST).join("**");
|
|
40433
|
+
}
|
|
40434
|
+
async function knowledgeSearch(config3, args) {
|
|
40435
|
+
try {
|
|
40436
|
+
const q = new URLSearchParams();
|
|
40437
|
+
if (args.q) q.set("q", args.q);
|
|
40438
|
+
if (args.kind) q.set("kind", args.kind);
|
|
40439
|
+
for (const [name, values] of [
|
|
40440
|
+
["topic", args.topic],
|
|
40441
|
+
["person", args.person],
|
|
40442
|
+
["place", args.place],
|
|
40443
|
+
["category", args.category],
|
|
40444
|
+
["author", args.author],
|
|
40445
|
+
["year", args.year],
|
|
40446
|
+
["doc", args.doc]
|
|
40447
|
+
]) {
|
|
40448
|
+
for (const v of values ?? []) q.append(name, v);
|
|
40449
|
+
}
|
|
40450
|
+
if (args.limit !== void 0) q.set("limit", String(args.limit));
|
|
40451
|
+
if (args.offset !== void 0) q.set("offset", String(args.offset));
|
|
40452
|
+
if (args.include_media) q.set("include", "media");
|
|
40453
|
+
const qs = q.toString();
|
|
40454
|
+
const result = await hubGet(config3, `/api/knowledge/explore${qs ? `?${qs}` : ""}`);
|
|
40455
|
+
const hits = Array.isArray(result.hits) ? result.hits.map((h) => ({
|
|
40456
|
+
...h,
|
|
40457
|
+
snippet: typeof h.snippet === "string" ? plainSnippet(h.snippet) : h.snippet
|
|
40458
|
+
})) : result.hits;
|
|
40459
|
+
const { highlight: _highlight, ...rest } = result;
|
|
40460
|
+
return {
|
|
40461
|
+
...rest,
|
|
40462
|
+
hits,
|
|
40463
|
+
note: "Facet counts describe the CURRENT filters; pass repeated facet values to OR within a facet. media URLs (url/poster_url, present with include_media) are presigned and expire in minutes: use them immediately, never store them."
|
|
40464
|
+
};
|
|
40465
|
+
} catch (err) {
|
|
40466
|
+
return mapKnowledgeError(err, config3.apiBase);
|
|
40467
|
+
}
|
|
40468
|
+
}
|
|
40469
|
+
function mapAgentsError(err, apiBase) {
|
|
40470
|
+
if (err instanceof HubApiError && err.status === 404) {
|
|
40471
|
+
const body = bodyOf(err);
|
|
40472
|
+
if (body && body.error === "not_found") {
|
|
40473
|
+
return { error: "not_found", detail: typeof body.message === "string" ? body.message : "No such agent or resource for this account." };
|
|
40474
|
+
}
|
|
40475
|
+
}
|
|
40476
|
+
return mapKnowledgeError(err, apiBase);
|
|
40477
|
+
}
|
|
40478
|
+
async function knowledgeAgents(config3, args) {
|
|
40479
|
+
try {
|
|
40480
|
+
if (args.action === "list") {
|
|
40481
|
+
return await hubGet(config3, "/api/knowledge/agents");
|
|
40482
|
+
}
|
|
40483
|
+
if (args.action === "get") {
|
|
40484
|
+
if (!args.agentId) return { error: "agentId is required for get" };
|
|
40485
|
+
return await hubGet(config3, `/api/knowledge/agents/${encodeURIComponent(args.agentId)}`);
|
|
40486
|
+
}
|
|
40487
|
+
if (args.action === "create") {
|
|
40488
|
+
if (args.goal) {
|
|
40489
|
+
const draft = await hubPost(config3, "/api/knowledge/agents/draft", { goal: args.goal });
|
|
40490
|
+
if (!draft.proposal) return { error: "draft_failed", detail: draft };
|
|
40491
|
+
const created = await hubPost(config3, "/api/knowledge/agents", draft.proposal);
|
|
40492
|
+
return { ...created, test_questions: draft.test_questions ?? [], status_note: "Created as a private DRAFT: test it, then publish when the user approves." };
|
|
40493
|
+
}
|
|
40494
|
+
if (!args.name) return { error: "name (or goal) is required for create" };
|
|
40495
|
+
return await hubPost(config3, "/api/knowledge/agents", { name: args.name });
|
|
40496
|
+
}
|
|
40497
|
+
if (args.action === "publish") {
|
|
40498
|
+
if (!args.agentId) return { error: "agentId is required for publish" };
|
|
40499
|
+
const out = await hubPost(config3, `/api/knowledge/agents/${encodeURIComponent(args.agentId)}/publish`, {});
|
|
40500
|
+
return { ...out, status_note: "Live immediately for every key bound to this agent." };
|
|
40501
|
+
}
|
|
40502
|
+
if (!args.agentId || !args.message) return { error: "agentId and message are required for test" };
|
|
40503
|
+
return await hubPost(config3, `/api/knowledge/agents/${encodeURIComponent(args.agentId)}/chat`, {
|
|
40504
|
+
message: args.message,
|
|
40505
|
+
...args.sessionId ? { session_id: args.sessionId } : {}
|
|
40506
|
+
});
|
|
40507
|
+
} catch (err) {
|
|
40508
|
+
return mapAgentsError(err, config3.apiBase);
|
|
40509
|
+
}
|
|
40510
|
+
}
|
|
40360
40511
|
|
|
40361
40512
|
// src/index.ts
|
|
40362
40513
|
var config2 = null;
|
|
@@ -40368,51 +40519,108 @@ function requireConfig() {
|
|
|
40368
40519
|
return config2;
|
|
40369
40520
|
}
|
|
40370
40521
|
function accountStamp() {
|
|
40522
|
+
const drift = SKILL_DRIFT ? ` \xB7 skills ${SKILL_DRIFT} \u2014 update ready, run awesomate_skill_update` : "";
|
|
40371
40523
|
if (!config2) return `account: NOT CONNECTED \u2014 ${configError ?? "unknown error"}`;
|
|
40372
|
-
return `account: ${config2.account ?? "unknown (env PAT not matched to a profile)"} (source: ${config2.source})`;
|
|
40524
|
+
return `account: ${config2.account ?? "unknown (env PAT not matched to a profile)"} (source: ${config2.source})${drift}`;
|
|
40373
40525
|
}
|
|
40374
40526
|
var SERVER_VERSION = (() => {
|
|
40375
40527
|
try {
|
|
40376
40528
|
const pkg = JSON.parse(
|
|
40377
|
-
|
|
40529
|
+
readFileSync3(join3(dirname3(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf8")
|
|
40378
40530
|
);
|
|
40379
40531
|
return pkg.version ?? "0.0.0";
|
|
40380
40532
|
} catch {
|
|
40381
40533
|
return "0.0.0";
|
|
40382
40534
|
}
|
|
40383
40535
|
})();
|
|
40384
|
-
|
|
40385
|
-
let installedSkillVersion = null;
|
|
40536
|
+
var CHANGELOG = (() => {
|
|
40386
40537
|
try {
|
|
40387
|
-
|
|
40388
|
-
join3(
|
|
40389
|
-
|
|
40390
|
-
|
|
40538
|
+
const raw = JSON.parse(
|
|
40539
|
+
readFileSync3(join3(dirname3(fileURLToPath2(import.meta.url)), "..", "skill", "CHANGELOG.json"), "utf8")
|
|
40540
|
+
);
|
|
40541
|
+
return Array.isArray(raw.versions) ? raw.versions : [];
|
|
40391
40542
|
} catch {
|
|
40543
|
+
return [];
|
|
40392
40544
|
}
|
|
40545
|
+
})();
|
|
40546
|
+
function changelogSince(installed) {
|
|
40547
|
+
if (!installed) return CHANGELOG.slice(0, 3);
|
|
40548
|
+
const idx = CHANGELOG.findIndex((e) => e.version === installed);
|
|
40549
|
+
return idx === -1 ? CHANGELOG.slice(0, 3) : CHANGELOG.slice(0, idx);
|
|
40550
|
+
}
|
|
40551
|
+
function skillUpdateInfo() {
|
|
40552
|
+
const perSkill = skillVersions();
|
|
40553
|
+
const staleSkills = perSkill.filter((s) => s.installed !== SERVER_VERSION).map((s) => s.name);
|
|
40554
|
+
const oldest = perSkill.reduce(
|
|
40555
|
+
(min, s) => s.installed !== null && (min === null || s.installed < min) ? s.installed : min,
|
|
40556
|
+
null
|
|
40557
|
+
);
|
|
40558
|
+
const updateAvailable = perSkill.length === 0 || staleSkills.length > 0;
|
|
40393
40559
|
return {
|
|
40394
40560
|
serverVersion: SERVER_VERSION,
|
|
40395
|
-
installedSkillVersion,
|
|
40396
|
-
updateAvailable
|
|
40561
|
+
installedSkillVersion: oldest,
|
|
40562
|
+
updateAvailable,
|
|
40563
|
+
staleSkills,
|
|
40564
|
+
whatsNew: updateAvailable ? changelogSince(oldest).flatMap((e) => e.highlights) : []
|
|
40397
40565
|
};
|
|
40398
40566
|
}
|
|
40399
|
-
var
|
|
40400
|
-
|
|
40401
|
-
|
|
40402
|
-
}
|
|
40567
|
+
var SKILL_DRIFT = (() => {
|
|
40568
|
+
const info = skillUpdateInfo();
|
|
40569
|
+
if (!info.updateAvailable) return null;
|
|
40570
|
+
return `${info.installedSkillVersion ?? "unversioned"} \u2192 ${SERVER_VERSION}`;
|
|
40571
|
+
})();
|
|
40572
|
+
var SERVER_INSTRUCTIONS = [
|
|
40573
|
+
"Awesomate connects this account's WordPress hosting, n8n automations, apps and Knowledge Base to Claude.",
|
|
40574
|
+
"Entry point: call awesomate_get_context once per session FIRST \u2014 it returns the account, plan, limits, an `attention` digest (unread notifications, erroring workflows, token expiry) and skill freshness. The domain contexts (awesomate_n8n_context, awesomate_app_context, awesomate_knowledge_status) come after it, only for their domain.",
|
|
40575
|
+
"The awesomate-* skills are installed on this machine \u2014 load the one matching the task (awesomate-hosting, awesomate-n8n, awesomate-app-builder, awesomate-knowledge, awesomate-seo, awesomate-credentials, awesomate-github, awesomate-database, awesomate-support) before working in that area.",
|
|
40576
|
+
"Reads work on every plan; building/writing generally needs Support Plus or above, and the Knowledge Base needs Pro. Never retry a 403: it carries either an upgrade path (relay it honestly, let the user decide) or a privacy toggle only the user can flip (send them the settings link).",
|
|
40577
|
+
"Every response is stamped `account: <slug>` \u2014 if that is not the account the user expects, stop and fix the connection before doing any work.",
|
|
40578
|
+
SKILL_DRIFT ? `Skill files on this machine are ${SKILL_DRIFT}. Mention it ONCE this session (never mid-task): offer awesomate_skill_update, then a Claude Code restart.` : null
|
|
40579
|
+
].filter((line) => line !== null).join("\n");
|
|
40580
|
+
var server = new McpServer(
|
|
40581
|
+
{
|
|
40582
|
+
name: "awesomate-hosting",
|
|
40583
|
+
version: SERVER_VERSION
|
|
40584
|
+
},
|
|
40585
|
+
{ instructions: SERVER_INSTRUCTIONS }
|
|
40586
|
+
);
|
|
40403
40587
|
function textResult(data) {
|
|
40404
40588
|
return {
|
|
40405
40589
|
content: [{ type: "text", text: `${accountStamp()}
|
|
40406
40590
|
${JSON.stringify(data, null, 2)}` }]
|
|
40407
40591
|
};
|
|
40408
40592
|
}
|
|
40593
|
+
var ERROR_BODY_FIELDS = [
|
|
40594
|
+
"code",
|
|
40595
|
+
"recommendedPlan",
|
|
40596
|
+
"deepLink",
|
|
40597
|
+
"upgradeUrl",
|
|
40598
|
+
"settingsUrl",
|
|
40599
|
+
"missingScopes",
|
|
40600
|
+
"plan",
|
|
40601
|
+
"allowed"
|
|
40602
|
+
];
|
|
40409
40603
|
function errorResult(err) {
|
|
40410
40604
|
const message = err instanceof Error ? err.message : String(err);
|
|
40411
|
-
|
|
40412
|
-
${message}
|
|
40605
|
+
let text = `${accountStamp()}
|
|
40606
|
+
${message}`;
|
|
40607
|
+
if (err instanceof HubApiError && err.body && typeof err.body === "object") {
|
|
40608
|
+
const body = err.body;
|
|
40609
|
+
const details = {};
|
|
40610
|
+
for (const field of ERROR_BODY_FIELDS) {
|
|
40611
|
+
if (body[field] !== void 0 && body[field] !== null) details[field] = body[field];
|
|
40612
|
+
}
|
|
40613
|
+
if (Object.keys(details).length > 0) {
|
|
40614
|
+
text += `
|
|
40615
|
+
${JSON.stringify(details, null, 2)}`;
|
|
40616
|
+
}
|
|
40617
|
+
}
|
|
40618
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
40413
40619
|
}
|
|
40620
|
+
var READ_ONLY = { readOnlyHint: true };
|
|
40621
|
+
var DESTRUCTIVE = { destructiveHint: true };
|
|
40414
40622
|
function readTool(name, description, path) {
|
|
40415
|
-
server.registerTool(name, { description, inputSchema: {} }, async () => {
|
|
40623
|
+
server.registerTool(name, { description, inputSchema: {}, annotations: READ_ONLY }, async () => {
|
|
40416
40624
|
try {
|
|
40417
40625
|
return textResult(await hubGet(requireConfig(), path));
|
|
40418
40626
|
} catch (err) {
|
|
@@ -40423,6 +40631,7 @@ function readTool(name, description, path) {
|
|
|
40423
40631
|
server.registerTool(
|
|
40424
40632
|
"awesomate_whoami",
|
|
40425
40633
|
{
|
|
40634
|
+
annotations: READ_ONLY,
|
|
40426
40635
|
description: "Zero-network identity check: which Awesomate account this session is connected to (slug, plan, token expiry) and WHY (env var, project pin file, sole profile, or default). Call it after connecting, after switching folders, and any time the account in play matters \u2014 if the slug is not the account the user expects, STOP and fix the pin/connection before doing any work.",
|
|
40427
40636
|
inputSchema: {}
|
|
40428
40637
|
},
|
|
@@ -40446,7 +40655,8 @@ server.registerTool(
|
|
|
40446
40655
|
server.registerTool(
|
|
40447
40656
|
"awesomate_get_context",
|
|
40448
40657
|
{
|
|
40449
|
-
|
|
40658
|
+
annotations: READ_ONLY,
|
|
40659
|
+
description: "THE session entry point \u2014 call it FIRST, before any domain context. Returns the connected account: plan, capabilities, limits, scopes, token expiry, cPanel routing, PLUS `attention` ({unreadNotifications, erroringWorkflows7d, patExpiresInDays} \u2014 null means unknown, never zero; when something is non-zero, mention it to the user in one line before starting the asked task), `latestMcpVersion` (if serverVersion still lags it after a restart, the npx cache is stale \u2014 remedy: rm -rf ~/.npm/_npx, then restart), and `skill` ({updateAvailable, staleSkills, whatsNew} \u2014 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/sites refreshes skills AND renews the token in one go.",
|
|
40450
40660
|
inputSchema: {}
|
|
40451
40661
|
},
|
|
40452
40662
|
async () => {
|
|
@@ -40466,6 +40676,7 @@ readTool(
|
|
|
40466
40676
|
server.registerTool(
|
|
40467
40677
|
"awesomate_n8n_deploy",
|
|
40468
40678
|
{
|
|
40679
|
+
annotations: DESTRUCTIVE,
|
|
40469
40680
|
description: "Workflow lifecycle writes on the client's n8n, all consent-gated and audited server-side. Actions: 'validate' (structural + n8n-mcp check of workflowJson \u2014 ALWAYS validate before create_draft), 'create_draft' (creates an INACTIVE '[CLI] ' workflow tagged awm:client-cli; returns its webhook URLs), 'activate'/'deactivate' (activation strips the [CLI] prefix; production webhooks respond only while active; agency-managed workflows are refused), 'promote' (swaps a TESTED draft into the live workflow IN PLACE \u2014 live id + webhookIds preserved so external callers keep working; pass workflowId=the LIVE id and draftId=the tested draft; the draft is archived '[promoted <date>]'; response includes operationId for rollback), 'rollback' (restore a promote's pre-swap state \u2014 pass operationId), 'update_draft' (replace an INACTIVE self-built draft's full JSON in place \u2014 pass workflowId + workflowJson; live workflows are refused, use the promote path), 'delete_draft' (inactive self-built drafts only). Get explicit user approval before activate, promote, rollback, and delete_draft. 429 quota_exceeded = daily plan limit; 403 consent_required \u2192 send user to settingsUrl.",
|
|
40470
40681
|
inputSchema: {
|
|
40471
40682
|
action: external_exports.enum(["validate", "create_draft", "update_draft", "activate", "deactivate", "promote", "rollback", "delete_draft"]),
|
|
@@ -40540,6 +40751,7 @@ server.registerTool(
|
|
|
40540
40751
|
server.registerTool(
|
|
40541
40752
|
"awesomate_n8n_workflows",
|
|
40542
40753
|
{
|
|
40754
|
+
annotations: READ_ONLY,
|
|
40543
40755
|
description: "The client's workflows. No id \u2192 EVERY workflow as a node-level summary in one call (nodeCount, nodeTypes, triggers, usesAi, communityNodes, dates; ?active filter + pagination) \u2014 use this for the session's world picture instead of fetching workflows one by one. With id \u2192 that workflow's JSON: detail 'full' (default \u2014 nodes with parameters, connections, settings) or 'structure' (nodes WITHOUT parameters + connections \u2014 cheap shape check for big workflows). 403 consent_required \u2192 send the user to settingsUrl and re-check.",
|
|
40544
40756
|
inputSchema: {
|
|
40545
40757
|
id: external_exports.string().optional().describe("Workflow id for a single-workflow read; omit for the all-workflows summary"),
|
|
@@ -40575,6 +40787,7 @@ server.registerTool(
|
|
|
40575
40787
|
server.registerTool(
|
|
40576
40788
|
"awesomate_n8n_inspect",
|
|
40577
40789
|
{
|
|
40790
|
+
annotations: READ_ONLY,
|
|
40578
40791
|
description: `Instance inventory reads, one tool: 'nodes' (distinct node types in use, counts, versions, community/AI flags), 'datatables' (tables + columns + row counts), 'datatable_rows' (pass datatableId; limit \u2264 100), 'possibilities' (facts for "what could I automate": connected services with live-usage cross-check, unused connections, top nodes, AI tools [null = unknown, not none], community packages, counts \u2014 YOU turn these into suggestions, grounded only in what's actually there), 'credentials' (names/types/inferred service \u2014 never secrets), 'variables' ($vars keys). All consent-gated server-side; 403 consent_required \u2192 settingsUrl.`,
|
|
40579
40792
|
inputSchema: {
|
|
40580
40793
|
what: external_exports.enum(["nodes", "datatables", "datatable_rows", "possibilities", "credentials", "variables"]),
|
|
@@ -40606,7 +40819,8 @@ server.registerTool(
|
|
|
40606
40819
|
server.registerTool(
|
|
40607
40820
|
"awesomate_n8n_executions",
|
|
40608
40821
|
{
|
|
40609
|
-
|
|
40822
|
+
annotations: READ_ONLY,
|
|
40823
|
+
description: "Execution reads. workflowId \u2192 recent executions of that workflow. executionId \u2192 detail with errorSummary \u2014 trust errorSummary.failingNode only when confident:true (structural decode; also carries errorType/code/lastNodeExecuted); when confident:false it came from a heuristic and may name a node that does not exist \u2014 verify against the workflow before editing anything. executionId + debug:true \u2192 node-by-node decode (statuses, timings, errors, 2 example items per node) \u2014 the best failure-diagnosis view; needs the 'error content analysis' privacy toggle (403 consent_required \u2192 settingsUrl), and responses with tooLarge:true mean the payload exceeded 15MB \u2014 fall back to the non-debug detail. For 'what is failing across ALL my workflows', use awesomate_n8n_errors instead.",
|
|
40610
40824
|
inputSchema: {
|
|
40611
40825
|
workflowId: external_exports.string().optional(),
|
|
40612
40826
|
executionId: external_exports.string().optional(),
|
|
@@ -40634,6 +40848,7 @@ server.registerTool(
|
|
|
40634
40848
|
server.registerTool(
|
|
40635
40849
|
"awesomate_n8n_node_docs",
|
|
40636
40850
|
{
|
|
40851
|
+
annotations: READ_ONLY,
|
|
40637
40852
|
description: "Live n8n documentation, 500+ nodes + 2,500+ community templates \u2014 ALWAYS prefer this over memory for node schemas and typeVersions. tools: search_nodes {query}, get_node {nodeType \u2014 full form like 'n8n-nodes-base.gmail' works, add detail:'full' for everything}, search_templates {query} / get_template {templateId} (real importable community workflows \u2014 great starting points), validate_node {nodeType, config}, tools_documentation {}. Works on every plan, no consent needed. 503 node_catalog_unavailable \u2192 use the skill's references/vendor/ files instead; 422 catalog_tool_error \u2192 YOUR args were wrong (message says why), the catalog is fine.",
|
|
40638
40853
|
inputSchema: {
|
|
40639
40854
|
tool: external_exports.enum(["search_nodes", "get_node", "search_templates", "get_template", "validate_node", "tools_documentation"]),
|
|
@@ -40651,6 +40866,7 @@ server.registerTool(
|
|
|
40651
40866
|
server.registerTool(
|
|
40652
40867
|
"awesomate_n8n_datatable_write",
|
|
40653
40868
|
{
|
|
40869
|
+
annotations: DESTRUCTIVE,
|
|
40654
40870
|
description: "Datatable writes (Support Plus+, consent-gated, audited). 'create' {name, columns:[{name,type?}], workflowId?} \u2014 ALWAYS pass workflowId when the table serves a specific workflow (datatables resolve PER PROJECT at runtime; workflowId threads that workflow's project; a projectWarning in the response means pass it). 'add_column' {tableId, name, type?} \u2014 needs the 'direct database writes' privacy toggle; names: letters/digits/underscores only. 'insert' {tableId, rows:[...]} (\u2264100), 'update' {tableId, filter, data}, 'delete_rows' {tableId, filter \u2014 REQUIRED, there is no delete-all}. Writes only work on tables created through Claude Code (403 not_self_created otherwise \u2014 agency tables are off limits). Get explicit user approval before delete_rows.",
|
|
40655
40871
|
inputSchema: {
|
|
40656
40872
|
action: external_exports.enum(["create", "add_column", "insert", "update", "delete_rows"]),
|
|
@@ -40685,13 +40901,24 @@ server.registerTool(
|
|
|
40685
40901
|
server.registerTool(
|
|
40686
40902
|
"awesomate_skill_update",
|
|
40687
40903
|
{
|
|
40688
|
-
description: "
|
|
40904
|
+
description: "The one-step updater for the locally installed Awesomate skills \u2014 run when any response stamp or awesomate_get_context reports an update ready. Refreshes ALL bundled skills in ~/.claude/skills from this (always-latest) server package, removes files newer bundles no longer ship, re-stamps versions, and returns per-skill from\u2192to plus what's-new lines and the exact restart step. New skill content applies from the NEXT Claude Code session; finish the current task first, then hand the user the restart instruction verbatim.",
|
|
40689
40905
|
inputSchema: {}
|
|
40690
40906
|
},
|
|
40691
40907
|
async () => {
|
|
40692
40908
|
try {
|
|
40693
40909
|
const result = installSkills(SERVER_VERSION);
|
|
40694
|
-
|
|
40910
|
+
const changed = result.updates.filter((u) => u.from !== u.to);
|
|
40911
|
+
return textResult({
|
|
40912
|
+
...result,
|
|
40913
|
+
changed: changed.map((u) => `${u.name}: ${u.from ?? "unversioned"} \u2192 ${u.to}`),
|
|
40914
|
+
whatsNew: changelogSince(changed[0]?.from ?? null).flatMap((e) => e.highlights),
|
|
40915
|
+
restart: {
|
|
40916
|
+
note: "Updated skill files load in the next Claude Code session \u2014 one restart lands both the skills and the (self-updating) server.",
|
|
40917
|
+
terminal: "Type exit (or press Ctrl+C twice), then run `claude` again.",
|
|
40918
|
+
vscode: 'Command Palette (Cmd/Ctrl+Shift+P) \u2192 "Developer: Reload Window".',
|
|
40919
|
+
verify: "After restarting, awesomate_get_context should report skill.updateAvailable: false."
|
|
40920
|
+
}
|
|
40921
|
+
});
|
|
40695
40922
|
} catch (err) {
|
|
40696
40923
|
return errorResult(err);
|
|
40697
40924
|
}
|
|
@@ -40731,6 +40958,7 @@ server.registerTool(
|
|
|
40731
40958
|
server.registerTool(
|
|
40732
40959
|
"awesomate_request_build",
|
|
40733
40960
|
{
|
|
40961
|
+
annotations: DESTRUCTIVE,
|
|
40734
40962
|
description: "Submit a DONE-FOR-YOU automation request \u2014 the Awesomate team builds it, for clients who'd rather not build it themselves or whose request is beyond what you can build here. This SPENDS 1 CREDIT ($100). Two steps, always: call WITHOUT confirmCredit first \u2014 it returns the cost and the user's available balance and spends nothing; state both to the user in plain words, get an explicit yes, THEN call again with confirmCredit:true. Needs a wizard-enabled plan (Pro/Embedded) \u2014 a Support Plus user gets upgrade_required, relay it honestly. On success returns a tracking URL (progress shows on My Automations).",
|
|
40735
40963
|
inputSchema: {
|
|
40736
40964
|
title: external_exports.string().describe("Short name for the automation"),
|
|
@@ -40799,6 +41027,7 @@ server.registerTool(
|
|
|
40799
41027
|
server.registerTool(
|
|
40800
41028
|
"awesomate_uninstall_site",
|
|
40801
41029
|
{
|
|
41030
|
+
annotations: DESTRUCTIVE,
|
|
40802
41031
|
description: "Permanently delete a WordPress site (files + database). IRREVERSIBLE \u2014 snapshot first if the user might want it back, and always get explicit confirmation. You MUST pass confirm equal to the exact domain, or the hub refuses. Support Plus+.",
|
|
40803
41032
|
inputSchema: {
|
|
40804
41033
|
domain: external_exports.string().describe("The site domain to delete"),
|
|
@@ -40875,6 +41104,7 @@ var PLAN_LADDER_FALLBACK = {
|
|
|
40875
41104
|
server.registerTool(
|
|
40876
41105
|
"awesomate_get_plan_features",
|
|
40877
41106
|
{
|
|
41107
|
+
annotations: READ_ONLY,
|
|
40878
41108
|
description: "The Awesomate plan ladder: what each plan includes for hosting (WordPress sites, custom domains, hosted apps, cPanel, shell/Claude Code access) and which plans are purchasable. Fetched live from the hub (single source of truth) with a static fallback. Use to explain what an upgrade unlocks; live per-account usage comes from awesomate_get_limits.",
|
|
40879
41109
|
inputSchema: {}
|
|
40880
41110
|
},
|
|
@@ -40915,6 +41145,7 @@ server.registerTool(
|
|
|
40915
41145
|
server.registerTool(
|
|
40916
41146
|
"awesomate_list_snapshots",
|
|
40917
41147
|
{
|
|
41148
|
+
annotations: READ_ONLY,
|
|
40918
41149
|
description: "List a site\u2019s available snapshots (newest first) with their ids, timestamps, and reasons. Requires shell access (Support Plus+).",
|
|
40919
41150
|
inputSchema: { domain: external_exports.string().describe("The site domain") }
|
|
40920
41151
|
},
|
|
@@ -40929,6 +41160,7 @@ server.registerTool(
|
|
|
40929
41160
|
server.registerTool(
|
|
40930
41161
|
"awesomate_rollback_site",
|
|
40931
41162
|
{
|
|
41163
|
+
annotations: DESTRUCTIVE,
|
|
40932
41164
|
description: "Restore a site to a previous snapshot (files + database). The current state is auto-snapshotted first, so a rollback is itself reversible (see preRollbackSnapshotId in the result). Confirm with the user before rolling back \u2014 it overwrites the live site. Requires shell access (Support Plus+).",
|
|
40933
41165
|
inputSchema: {
|
|
40934
41166
|
domain: external_exports.string().describe("The site domain"),
|
|
@@ -40966,6 +41198,7 @@ server.registerTool(
|
|
|
40966
41198
|
server.registerTool(
|
|
40967
41199
|
"awesomate_site_staging_promote",
|
|
40968
41200
|
{
|
|
41201
|
+
annotations: DESTRUCTIVE,
|
|
40969
41202
|
description: "Publish the staging copy to the LIVE site (overwrites live files + database with staging). The live site is auto-snapshotted first \u2014 the result includes preSnapshotId, which awesomate_rollback_site can restore if anything looks wrong. **Confirm with the user before promoting \u2014 it replaces the live site.** Requires shell access (Support Plus+).",
|
|
40970
41203
|
inputSchema: {
|
|
40971
41204
|
domain: external_exports.string().describe("The LIVE site domain whose staging copy should go live")
|
|
@@ -40984,6 +41217,7 @@ server.registerTool(
|
|
|
40984
41217
|
server.registerTool(
|
|
40985
41218
|
"awesomate_site_staging_discard",
|
|
40986
41219
|
{
|
|
41220
|
+
annotations: DESTRUCTIVE,
|
|
40987
41221
|
description: "Delete the staging copy of a site (staging WP install + its awesomate.dev address; the live site is untouched). **Confirm with the user before discarding \u2014 unpromoted staging changes are lost.** Requires shell access (Support Plus+).",
|
|
40988
41222
|
inputSchema: {
|
|
40989
41223
|
domain: external_exports.string().describe("The LIVE site domain whose staging copy should be discarded")
|
|
@@ -41012,6 +41246,7 @@ readTool(
|
|
|
41012
41246
|
server.registerTool(
|
|
41013
41247
|
"awesomate_app_get",
|
|
41014
41248
|
{
|
|
41249
|
+
annotations: READ_ONLY,
|
|
41015
41250
|
description: "Get one app plus its environments (subdomains, ports, db engine/name, deploy + health state). Poll this after awesomate_app_create \u2014 status goes provisioning \u2192 active | failed (read provision_error on failure).",
|
|
41016
41251
|
inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_list / _create") }
|
|
41017
41252
|
},
|
|
@@ -41047,6 +41282,7 @@ server.registerTool(
|
|
|
41047
41282
|
server.registerTool(
|
|
41048
41283
|
"awesomate_app_scaffold",
|
|
41049
41284
|
{
|
|
41285
|
+
annotations: READ_ONLY,
|
|
41050
41286
|
description: "Fetch the app's starter files (its template rendered against the live app metadata \u2014 subdomains, control-plane URL, repo \u2014 with placeholders already substituted). Call after awesomate_app_create reaches status=active: write each returned file into a fresh local project folder at its relative path, then run npm install (Node apps) and follow the bundled CLAUDE.md. This is how the starter code gets onto the user's machine \u2014 don't reconstruct templates by hand.",
|
|
41051
41287
|
inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_create / _list") }
|
|
41052
41288
|
},
|
|
@@ -41059,9 +41295,10 @@ server.registerTool(
|
|
|
41059
41295
|
}
|
|
41060
41296
|
);
|
|
41061
41297
|
server.registerTool(
|
|
41062
|
-
"
|
|
41298
|
+
"awesomate_app_deploy_info",
|
|
41063
41299
|
{
|
|
41064
|
-
|
|
41300
|
+
annotations: READ_ONLY,
|
|
41301
|
+
description: "READ-ONLY deploy briefing \u2014 this never deploys anything (deploys happen via git push). Reports how to deploy an app, its per-env targets, last-deploy/health state, and how to promote (dev\u2192staging\u2192main) or roll back (git revert + push). Node apps deploy via git push (dev/staging/main \u2192 GitHub Actions \u2192 cPanel). Use the awesomate-github skill to wire push-to-deploy the first time.",
|
|
41065
41302
|
inputSchema: { appId: external_exports.number().int().positive().describe("The app id") }
|
|
41066
41303
|
},
|
|
41067
41304
|
async ({ appId }) => {
|
|
@@ -41112,6 +41349,7 @@ server.registerTool(
|
|
|
41112
41349
|
server.registerTool(
|
|
41113
41350
|
"awesomate_app_health",
|
|
41114
41351
|
{
|
|
41352
|
+
annotations: READ_ONLY,
|
|
41115
41353
|
description: "Probe an app's environments live and report health. Node envs hit /api/ready (200 = deployed + DB up + migrations applied); static hits the root. An env that isn't deployed yet reports unreachable \u2014 expected, not a failure. Use after a deploy to confirm it came up, or when the user says something's down.",
|
|
41116
41354
|
inputSchema: { appId: external_exports.number().int().positive().describe("The app id") }
|
|
41117
41355
|
},
|
|
@@ -41159,6 +41397,7 @@ ${scrubKeyMaterial(message)}` }],
|
|
|
41159
41397
|
server.registerTool(
|
|
41160
41398
|
"awesomate_knowledge_status",
|
|
41161
41399
|
{
|
|
41400
|
+
annotations: READ_ONLY,
|
|
41162
41401
|
description: "Call FIRST for any Knowledge Base work. The account's knowledge tenant state (provisioning/active/suspended), plan entitlement, consent flag, month-to-date usage vs included quota, and purchased packs. upgrade_required:true \u2192 relay the included upsell copy + billing link honestly, do NOT retry. available:false \u2192 this hub doesn't serve Knowledge Base yet (kill switch / old hub) \u2014 also not retryable. Reads work on every plan.",
|
|
41163
41402
|
inputSchema: {}
|
|
41164
41403
|
},
|
|
@@ -41209,18 +41448,56 @@ server.registerTool(
|
|
|
41209
41448
|
}
|
|
41210
41449
|
}
|
|
41211
41450
|
);
|
|
41451
|
+
server.registerTool(
|
|
41452
|
+
"awesomate_knowledge_search",
|
|
41453
|
+
{
|
|
41454
|
+
annotations: READ_ONLY,
|
|
41455
|
+
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.",
|
|
41456
|
+
inputSchema: {
|
|
41457
|
+
q: external_exports.string().max(2e3).optional().describe("Search words; empty lists the library filtered by the facets"),
|
|
41458
|
+
kind: external_exports.enum(["book", "document", "web", "image", "video", "audio", "post", "dataset"]).optional(),
|
|
41459
|
+
topic: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41460
|
+
person: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41461
|
+
place: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41462
|
+
category: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41463
|
+
author: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41464
|
+
year: external_exports.array(external_exports.string().regex(/^\d{4}$/)).max(10).optional(),
|
|
41465
|
+
doc: external_exports.array(external_exports.string().max(200)).max(50).optional().describe("Restrict to these doc_ids (from earlier hits)"),
|
|
41466
|
+
limit: external_exports.number().int().min(1).max(50).optional(),
|
|
41467
|
+
offset: external_exports.number().int().min(0).max(2e3).optional(),
|
|
41468
|
+
include_media: external_exports.boolean().optional().describe("Add presigned url/poster_url to hits; they expire in minutes")
|
|
41469
|
+
}
|
|
41470
|
+
},
|
|
41471
|
+
async (args) => {
|
|
41472
|
+
try {
|
|
41473
|
+
return knowledgeResult(await knowledgeSearch(requireConfig(), args));
|
|
41474
|
+
} catch (err) {
|
|
41475
|
+
return knowledgeError(err);
|
|
41476
|
+
}
|
|
41477
|
+
}
|
|
41478
|
+
);
|
|
41212
41479
|
server.registerTool(
|
|
41213
41480
|
"awesomate_knowledge_ask",
|
|
41214
41481
|
{
|
|
41215
41482
|
description: `Ask the account's knowledge base a question and get the VERIFIED answer with numbered sources (title, locator, url) \u2014 the test surface for 'is my content in there and answering well'. Read status: ok \u2192 present answer + sources. no_results / failed_validation (not_in_verified_content:true) \u2192 the verified content has no answer: relay that honestly (use configured_fallback), never fill the gap from memory \u2014 an honest "it doesn't know" is the feature working. error (platform_error:true) \u2192 the platform itself failed (model/API/infra): NOT a content gap \u2014 never tell the user their content lacks the answer; retry once, then awesomate_support. Counts against the monthly answers quota.`,
|
|
41216
41483
|
inputSchema: {
|
|
41217
41484
|
question: external_exports.string().min(1).max(2e3),
|
|
41218
|
-
session: external_exports.string().max(128).optional().describe("Stable id to keep follow-up questions in one conversation thread")
|
|
41485
|
+
session: external_exports.string().max(128).optional().describe("Stable id to keep follow-up questions in one conversation thread"),
|
|
41486
|
+
filters: external_exports.object({
|
|
41487
|
+
kind: external_exports.enum(["book", "document", "web", "image", "video", "audio", "post", "dataset"]).optional(),
|
|
41488
|
+
topic: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41489
|
+
person: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41490
|
+
place: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41491
|
+
category: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41492
|
+
author: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
41493
|
+
year: external_exports.array(external_exports.string().regex(/^\d{4}$/)).max(10).optional(),
|
|
41494
|
+
doc: external_exports.array(external_exports.string().max(200)).max(50).optional().describe("doc_ids from search hits")
|
|
41495
|
+
}).optional().describe("Ask within a slice of the library: EXACTLY the awesomate_knowledge_search input shapes, reusable verbatim; filters only ever narrow")
|
|
41219
41496
|
}
|
|
41220
41497
|
},
|
|
41221
|
-
async ({ question, session }) => {
|
|
41498
|
+
async ({ question, session, filters }) => {
|
|
41222
41499
|
try {
|
|
41223
|
-
return knowledgeResult(await knowledgeAsk(requireConfig(), { question, session }));
|
|
41500
|
+
return knowledgeResult(await knowledgeAsk(requireConfig(), { question, session, filters }));
|
|
41224
41501
|
} catch (err) {
|
|
41225
41502
|
return knowledgeError(err);
|
|
41226
41503
|
}
|
|
@@ -41239,7 +41516,7 @@ server.registerTool(
|
|
|
41239
41516
|
tone: external_exports.string().max(500).optional()
|
|
41240
41517
|
}).optional().describe("set only: persona fields to change"),
|
|
41241
41518
|
no_answer_message: external_exports.string().max(1e3).optional().describe("set only: wording used when the content has no answer"),
|
|
41242
|
-
model_tier: external_exports.enum(["sonnet", "opus"]).optional().describe("set only: opus needs the Embedded plan"),
|
|
41519
|
+
model_tier: external_exports.enum(["flash", "sonnet", "opus"]).optional().describe("set only: flash is the fastest (Gemini Flash, same citation checks), sonnet the thorough default, opus needs the Embedded plan"),
|
|
41243
41520
|
datasets: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("set only: datasets the agent may answer from")
|
|
41244
41521
|
}
|
|
41245
41522
|
},
|
|
@@ -41253,6 +41530,27 @@ server.registerTool(
|
|
|
41253
41530
|
}
|
|
41254
41531
|
}
|
|
41255
41532
|
);
|
|
41533
|
+
server.registerTool(
|
|
41534
|
+
"awesomate_knowledge_agents",
|
|
41535
|
+
{
|
|
41536
|
+
description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list' \u2014 every agent with status (draft/published vN/suspended). 'get' {agentId} \u2014 full config incl. system message and scope. 'create' {goal} \u2014 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. 'test' {agentId, message, sessionId?} \u2014 chat with the DRAFT config: free, unmetered, the right way to check behaviour before going live. 'publish' {agentId} \u2014 makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. Editing fields, policies, API keys and the request log live in the hub UI (Knowledge \u2192 Agents); keys are shown once there and never pass through this tool.",
|
|
41537
|
+
inputSchema: {
|
|
41538
|
+
action: external_exports.enum(["list", "get", "create", "publish", "test"]),
|
|
41539
|
+
agentId: external_exports.string().max(64).optional().describe("get/publish/test: agent_id from list"),
|
|
41540
|
+
name: external_exports.string().max(80).optional().describe("create: blank draft with this name"),
|
|
41541
|
+
goal: external_exports.string().max(2e3).optional().describe("create: describe the agent and AI drafts it from the account content"),
|
|
41542
|
+
message: external_exports.string().max(4e3).optional().describe("test: the question to ask the draft"),
|
|
41543
|
+
sessionId: external_exports.string().max(128).optional().describe("test: keep follow-ups in one thread")
|
|
41544
|
+
}
|
|
41545
|
+
},
|
|
41546
|
+
async (args) => {
|
|
41547
|
+
try {
|
|
41548
|
+
return knowledgeResult(await knowledgeAgents(requireConfig(), args));
|
|
41549
|
+
} catch (err) {
|
|
41550
|
+
return knowledgeError(err);
|
|
41551
|
+
}
|
|
41552
|
+
}
|
|
41553
|
+
);
|
|
41256
41554
|
server.registerTool(
|
|
41257
41555
|
"awesomate_knowledge_people",
|
|
41258
41556
|
{
|
|
@@ -41279,6 +41577,274 @@ server.registerTool(
|
|
|
41279
41577
|
}
|
|
41280
41578
|
}
|
|
41281
41579
|
);
|
|
41580
|
+
server.registerTool(
|
|
41581
|
+
"awesomate_knowledge_data",
|
|
41582
|
+
{
|
|
41583
|
+
description: "The knowledge platform's business-data warehouse (Pro+). action 'metrics' \u2014 headline numbers for the data tab. 'datasets' \u2014 the datasets imported and their columns: read this FIRST, measures/dimensions must name real columns. 'imports' \u2014 import job statuses. 'query' {dataset, measures:[{column, agg}], plus optional dimensions/filters passed through} \u2014 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. New data is imported in the hub UI, not here.",
|
|
41584
|
+
inputSchema: {
|
|
41585
|
+
action: external_exports.enum(["metrics", "datasets", "imports", "query"]),
|
|
41586
|
+
dataset: external_exports.string().max(80).optional().describe("query: dataset name from datasets"),
|
|
41587
|
+
measures: external_exports.array(external_exports.object({ column: external_exports.string().max(80), agg: external_exports.string().max(10) })).max(8).optional().describe("query: e.g. [{column:'amount', agg:'sum'}]"),
|
|
41588
|
+
dimensions: external_exports.array(external_exports.string().max(80)).max(6).optional().describe("query: group-by columns"),
|
|
41589
|
+
filters: external_exports.array(external_exports.record(external_exports.unknown())).max(10).optional().describe("query: filter objects, passed through"),
|
|
41590
|
+
limit: external_exports.number().int().min(1).max(500).optional()
|
|
41591
|
+
}
|
|
41592
|
+
},
|
|
41593
|
+
async ({ action, dataset, measures, dimensions, filters, limit }) => {
|
|
41594
|
+
try {
|
|
41595
|
+
const cfg = requireConfig();
|
|
41596
|
+
if (action === "query") {
|
|
41597
|
+
return knowledgeResult(
|
|
41598
|
+
await hubPost(cfg, "/api/knowledge/data/query", { dataset, measures, dimensions, filters, limit })
|
|
41599
|
+
);
|
|
41600
|
+
}
|
|
41601
|
+
return knowledgeResult(await hubGet(cfg, `/api/knowledge/data/${action}`));
|
|
41602
|
+
} catch (err) {
|
|
41603
|
+
return knowledgeError(err);
|
|
41604
|
+
}
|
|
41605
|
+
}
|
|
41606
|
+
);
|
|
41607
|
+
server.registerTool(
|
|
41608
|
+
"awesomate_n8n_kpis",
|
|
41609
|
+
{
|
|
41610
|
+
annotations: READ_ONLY,
|
|
41611
|
+
description: "Automation KPIs from the hub's monitoring: executions, error rate, avg + p95 duration, time saved, 7-day-vs-prior deltas. No workflowId \u2192 the whole instance (the headline numbers for any report). With workflowId \u2192 that workflow, plus last_error_at and clean_days. Needs the 'aggregate monitoring' privacy toggle (on by default). Pair with awesomate_n8n_errors for what is failing and awesomate_site_uptime for the hosting side.",
|
|
41612
|
+
inputSchema: { workflowId: external_exports.string().max(64).optional() }
|
|
41613
|
+
},
|
|
41614
|
+
async ({ workflowId }) => {
|
|
41615
|
+
try {
|
|
41616
|
+
const cfg = requireConfig();
|
|
41617
|
+
const path = workflowId ? `/api/my-n8n/workflows/${encodeURIComponent(workflowId)}/kpis` : "/api/my-n8n/kpis";
|
|
41618
|
+
return textResult(await hubGet(cfg, path));
|
|
41619
|
+
} catch (err) {
|
|
41620
|
+
return errorResult(err);
|
|
41621
|
+
}
|
|
41622
|
+
}
|
|
41623
|
+
);
|
|
41624
|
+
server.registerTool(
|
|
41625
|
+
"awesomate_n8n_errors",
|
|
41626
|
+
{
|
|
41627
|
+
annotations: READ_ONLY,
|
|
41628
|
+
description: "What is broken across the WHOLE instance \u2014 error events grouped by fingerprint (category, workflow, node, occurrences, first/last seen), newest first. THE first call when the user says 'something is failing' or at the start of an n8n session (a quick 7-day sweep; stay silent when it's clean). Counts and categories only \u2014 drill into a specific failure with awesomate_n8n_executions. days defaults to 30 (max 90).",
|
|
41629
|
+
inputSchema: {
|
|
41630
|
+
days: external_exports.number().int().min(1).max(90).optional(),
|
|
41631
|
+
limit: external_exports.number().int().min(1).max(100).optional()
|
|
41632
|
+
}
|
|
41633
|
+
},
|
|
41634
|
+
async ({ days, limit }) => {
|
|
41635
|
+
try {
|
|
41636
|
+
const qs = new URLSearchParams();
|
|
41637
|
+
if (days) qs.set("days", String(days));
|
|
41638
|
+
if (limit) qs.set("limit", String(limit));
|
|
41639
|
+
return textResult(await hubGet(requireConfig(), `/api/my-n8n/errors${qs.size ? `?${qs}` : ""}`));
|
|
41640
|
+
} catch (err) {
|
|
41641
|
+
return errorResult(err);
|
|
41642
|
+
}
|
|
41643
|
+
}
|
|
41644
|
+
);
|
|
41645
|
+
readTool(
|
|
41646
|
+
"awesomate_n8n_storage",
|
|
41647
|
+
"What the n8n instance's data actually weighs: execution-table bytes, whole-database size, a per-table breakdown, and the last-24h per-workflow write offenders. Use it when executions feel slow, before suggesting cleanup, and as an OPPORTUNITY signal \u2014 heavy binary/media data is a cue to suggest a purpose-built app (awesomate-app-builder) for browsing it. Integers only; content never leaves the instance.",
|
|
41648
|
+
"/api/my-n8n/machine/storage"
|
|
41649
|
+
);
|
|
41650
|
+
server.registerTool(
|
|
41651
|
+
"awesomate_n8n_findings",
|
|
41652
|
+
{
|
|
41653
|
+
annotations: READ_ONLY,
|
|
41654
|
+
description: "Findings from Awesomate's automated error analyzer for THIS account (Pro/Embedded + the 'AI error analysis' privacy toggle): severity, workflow, occurrences, a plain-language clientSummary, needsClientAction (something only the user can fix \u2014 expired logins, third-party quotas), and fixReady (a reviewed fix is prepared \u2014 raise it with awesomate_support to have it applied). Read-only. An empty list on a healthy instance is the good state, not an error.",
|
|
41655
|
+
inputSchema: { limit: external_exports.number().int().min(1).max(100).optional() }
|
|
41656
|
+
},
|
|
41657
|
+
async ({ limit }) => {
|
|
41658
|
+
try {
|
|
41659
|
+
const qs = limit ? `?limit=${limit}` : "";
|
|
41660
|
+
return textResult(await hubGet(requireConfig(), `/api/my-n8n/machine/findings${qs}`));
|
|
41661
|
+
} catch (err) {
|
|
41662
|
+
return errorResult(err);
|
|
41663
|
+
}
|
|
41664
|
+
}
|
|
41665
|
+
);
|
|
41666
|
+
readTool(
|
|
41667
|
+
"awesomate_site_uptime",
|
|
41668
|
+
"Uptime for every monitored hosted site: current up/down state, 30-day availability %, incident count, downtime seconds. Served from the hub's cache \u2014 free and instant, safe to include in any report or health sweep. A site missing from the list simply is not monitored yet, not down.",
|
|
41669
|
+
"/api/client-hosting/uptime"
|
|
41670
|
+
);
|
|
41671
|
+
readTool(
|
|
41672
|
+
"awesomate_dns_check",
|
|
41673
|
+
"Live DNS check for the account's primary domain \u2014 where it ACTUALLY resolves right now versus where Awesomate hosting expects it. The first call when 'my domain isn't working': it separates a DNS problem (user must change records at their registrar) from a hosting problem (ours).",
|
|
41674
|
+
"/api/client-hosting/dns-check"
|
|
41675
|
+
);
|
|
41676
|
+
readTool(
|
|
41677
|
+
"awesomate_dashboard_metrics",
|
|
41678
|
+
"The account-wide dashboard aggregate in one call: executions by status, error rate, time saved, workflows total/active, chat sessions + unique users (30d), active users (7d), a 7-day daily exec/error trend, and the most recent live-workflow failure. The single best data source for a status update or weekly report; combine with awesomate_site_uptime and awesomate_knowledge_status for the full picture.",
|
|
41679
|
+
"/api/client-dashboard/metrics"
|
|
41680
|
+
);
|
|
41681
|
+
readTool(
|
|
41682
|
+
"awesomate_account_report",
|
|
41683
|
+
"A monthly-report-shaped read of the account: what's working (workflows, executions, success rate, chat, hosted sites), what's not (error clusters, open alerts), engagement (logins, credits, webinars) and commercials. Sections are independently fault-tolerant \u2014 null means 'could not be read right now', never zero. Assemble the user-facing story from these sections in THEIR language; don't paste the raw JSON at them.",
|
|
41684
|
+
"/api/client-report"
|
|
41685
|
+
);
|
|
41686
|
+
readTool(
|
|
41687
|
+
"awesomate_privacy_settings",
|
|
41688
|
+
"READ which privacy/consent toggles are on or off for this account \u2014 call it when a tool returns 403 consent_required so you can name the exact toggle instead of guessing. Toggles are changed ONLY by the user in the hub (Settings \u2192 Privacy, hub.awesomate.ai/settings?tab=privacy); there is deliberately no write here.",
|
|
41689
|
+
"/api/client-settings/privacy"
|
|
41690
|
+
);
|
|
41691
|
+
server.registerTool(
|
|
41692
|
+
"awesomate_notifications",
|
|
41693
|
+
{
|
|
41694
|
+
description: "The account's hub notification bell \u2014 the only channel where Awesomate pushes to the client: knowledge quota warnings (80%/100%), 'your quote is ready', support-access events. action 'list' {limit?} \u2192 notifications + unread count (surface unread ones once per session, in one line). 'read' {id} / 'read_all' \u2192 mark seen after you've relayed them. Not for sending anything.",
|
|
41695
|
+
inputSchema: {
|
|
41696
|
+
action: external_exports.enum(["list", "read", "read_all"]),
|
|
41697
|
+
id: external_exports.number().int().positive().optional().describe("read: the notification id"),
|
|
41698
|
+
limit: external_exports.number().int().min(1).max(100).optional().describe("list: default 30")
|
|
41699
|
+
}
|
|
41700
|
+
},
|
|
41701
|
+
async ({ action, id, limit }) => {
|
|
41702
|
+
try {
|
|
41703
|
+
const cfg = requireConfig();
|
|
41704
|
+
if (action === "list") {
|
|
41705
|
+
const qs = limit ? `?limit=${limit}` : "";
|
|
41706
|
+
return textResult(await hubGet(cfg, `/api/notifications${qs}`));
|
|
41707
|
+
}
|
|
41708
|
+
if (action === "read") {
|
|
41709
|
+
if (!id) return errorResult(new Error("action 'read' needs id"));
|
|
41710
|
+
return textResult(await hubPost(cfg, `/api/notifications/${id}/read`));
|
|
41711
|
+
}
|
|
41712
|
+
return textResult(await hubPost(cfg, "/api/notifications/read-all"));
|
|
41713
|
+
} catch (err) {
|
|
41714
|
+
return errorResult(err);
|
|
41715
|
+
}
|
|
41716
|
+
}
|
|
41717
|
+
);
|
|
41718
|
+
server.registerTool(
|
|
41719
|
+
"awesomate_wp_post",
|
|
41720
|
+
{
|
|
41721
|
+
description: "Create, update or read a WordPress post/page on the user's own Awesomate-hosted site \u2014 titles and content with spaces/HTML are fine (unlike awesomate_run_wp_cli). 'create' {domain, title, content?, status?, postType?: post|page, excerpt?, slug?} \u2014 lands as a DRAFT unless status:'publish' is explicit; never publish content the user hasn't seen or approved. 'update' {domain, postId, any of title/content/excerpt/slug/status}. 'get' {domain, postId, includeContent?}. Writes need Support Plus+ and are audited; snapshot the site first (awesomate_snapshot_site) before the session's first content change on a live site. Find post ids via awesomate_run_wp_cli ['post','list'].",
|
|
41722
|
+
inputSchema: {
|
|
41723
|
+
action: external_exports.enum(["create", "update", "get"]),
|
|
41724
|
+
domain: external_exports.string().min(3).max(253).describe("The site domain, e.g. mybusiness.awesomate.site"),
|
|
41725
|
+
postId: external_exports.number().int().positive().optional().describe("update/get"),
|
|
41726
|
+
postType: external_exports.enum(["post", "page"]).optional().describe("create only; default post"),
|
|
41727
|
+
title: external_exports.string().max(300).optional(),
|
|
41728
|
+
content: external_exports.string().max(15e4).optional().describe("HTML or plain text"),
|
|
41729
|
+
excerpt: external_exports.string().max(1e3).optional(),
|
|
41730
|
+
slug: external_exports.string().max(190).optional().describe("URL slug, [a-z0-9-]"),
|
|
41731
|
+
status: external_exports.enum(["draft", "publish", "pending", "private"]).optional(),
|
|
41732
|
+
includeContent: external_exports.boolean().optional().describe("get only")
|
|
41733
|
+
}
|
|
41734
|
+
},
|
|
41735
|
+
async ({ action, domain, postId, postType, title, content, excerpt, slug, status, includeContent }) => {
|
|
41736
|
+
try {
|
|
41737
|
+
const cfg = requireConfig();
|
|
41738
|
+
const dom = encodeURIComponent(domain.toLowerCase());
|
|
41739
|
+
if (action === "get") {
|
|
41740
|
+
if (!postId) return errorResult(new Error("action 'get' needs postId"));
|
|
41741
|
+
const qs = includeContent ? "?includeContent=1" : "";
|
|
41742
|
+
return textResult(await hubGet(cfg, `/api/client-hosting/sites/${dom}/posts/${postId}${qs}`));
|
|
41743
|
+
}
|
|
41744
|
+
return textResult(
|
|
41745
|
+
await hubPost(cfg, `/api/client-hosting/sites/${dom}/posts`, {
|
|
41746
|
+
action,
|
|
41747
|
+
postId,
|
|
41748
|
+
postType,
|
|
41749
|
+
title,
|
|
41750
|
+
content,
|
|
41751
|
+
excerpt,
|
|
41752
|
+
slug,
|
|
41753
|
+
status
|
|
41754
|
+
})
|
|
41755
|
+
);
|
|
41756
|
+
} catch (err) {
|
|
41757
|
+
return errorResult(err);
|
|
41758
|
+
}
|
|
41759
|
+
}
|
|
41760
|
+
);
|
|
41761
|
+
server.registerTool(
|
|
41762
|
+
"awesomate_wp_media_import",
|
|
41763
|
+
{
|
|
41764
|
+
description: "Import ONE media item into the user's WordPress media library by https URL (Support Plus+, audited). Returns the attachmentId to reference from posts. URLs only \u2014 this cannot read local files; for a local file, upload it somewhere reachable first or use wp-admin. Ask before importing anything the user didn't explicitly provide.",
|
|
41765
|
+
inputSchema: {
|
|
41766
|
+
domain: external_exports.string().min(3).max(253),
|
|
41767
|
+
url: external_exports.string().url().max(2048).describe("https URL of the image/file"),
|
|
41768
|
+
title: external_exports.string().max(300).optional()
|
|
41769
|
+
}
|
|
41770
|
+
},
|
|
41771
|
+
async ({ domain, url, title }) => {
|
|
41772
|
+
try {
|
|
41773
|
+
const dom = encodeURIComponent(domain.toLowerCase());
|
|
41774
|
+
return textResult(await hubPost(requireConfig(), `/api/client-hosting/sites/${dom}/media`, { url, title }));
|
|
41775
|
+
} catch (err) {
|
|
41776
|
+
return errorResult(err);
|
|
41777
|
+
}
|
|
41778
|
+
}
|
|
41779
|
+
);
|
|
41780
|
+
server.registerPrompt(
|
|
41781
|
+
"awesomate-status",
|
|
41782
|
+
{
|
|
41783
|
+
description: "One-screen health digest: account, automations, sites, knowledge, anything needing attention."
|
|
41784
|
+
},
|
|
41785
|
+
() => ({
|
|
41786
|
+
messages: [
|
|
41787
|
+
{
|
|
41788
|
+
role: "user",
|
|
41789
|
+
content: {
|
|
41790
|
+
type: "text",
|
|
41791
|
+
text: "Give me a one-screen Awesomate status. Call awesomate_get_context (attention block), awesomate_dashboard_metrics, awesomate_n8n_errors (7 days) and awesomate_site_uptime, then summarize in plain language: what ran, what failed (if anything), site uptime, and anything waiting on me \u2014 unread notifications, expiring token, available update. Lead with whatever needs attention; keep it short if everything is green."
|
|
41792
|
+
}
|
|
41793
|
+
}
|
|
41794
|
+
]
|
|
41795
|
+
})
|
|
41796
|
+
);
|
|
41797
|
+
server.registerPrompt(
|
|
41798
|
+
"awesomate-checkup",
|
|
41799
|
+
{
|
|
41800
|
+
description: "Run the full health sweep: automations, error findings, uptime, DNS, storage, SEO basics."
|
|
41801
|
+
},
|
|
41802
|
+
() => ({
|
|
41803
|
+
messages: [
|
|
41804
|
+
{
|
|
41805
|
+
role: "user",
|
|
41806
|
+
content: {
|
|
41807
|
+
type: "text",
|
|
41808
|
+
text: "Run a full Awesomate checkup and fix what you safely can. Sweep: awesomate_n8n_errors (30 days) + awesomate_n8n_kpis; awesomate_n8n_findings if my plan supports it; awesomate_site_uptime + awesomate_dns_check; awesomate_n8n_storage for anything unusually heavy; and the awesomate-seo skill's upkeep checks on my main site. Report only exceptions in my language, propose fixes before applying anything, and never publish or delete without asking."
|
|
41809
|
+
}
|
|
41810
|
+
}
|
|
41811
|
+
]
|
|
41812
|
+
})
|
|
41813
|
+
);
|
|
41814
|
+
server.registerPrompt(
|
|
41815
|
+
"awesomate-report",
|
|
41816
|
+
{
|
|
41817
|
+
description: "Assemble this account's business report: what worked, what broke, engagement, value delivered."
|
|
41818
|
+
},
|
|
41819
|
+
() => ({
|
|
41820
|
+
messages: [
|
|
41821
|
+
{
|
|
41822
|
+
role: "user",
|
|
41823
|
+
content: {
|
|
41824
|
+
type: "text",
|
|
41825
|
+
text: "Build my Awesomate report. Pull awesomate_account_report, awesomate_dashboard_metrics, awesomate_site_uptime and awesomate_knowledge_status (skip sections that are not available on my plan), then write it for a business owner: what my automations did for me, what needs attention, and one concrete suggestion for next month. Offer to set this up as a recurring weekly email via an n8n workflow (awesomate-n8n skill, recurring-reports recipe)."
|
|
41826
|
+
}
|
|
41827
|
+
}
|
|
41828
|
+
]
|
|
41829
|
+
})
|
|
41830
|
+
);
|
|
41831
|
+
server.registerPrompt(
|
|
41832
|
+
"awesomate-ideas",
|
|
41833
|
+
{
|
|
41834
|
+
description: "Grounded automation ideas from what this account actually has \u2014 with what each would save."
|
|
41835
|
+
},
|
|
41836
|
+
() => ({
|
|
41837
|
+
messages: [
|
|
41838
|
+
{
|
|
41839
|
+
role: "user",
|
|
41840
|
+
content: {
|
|
41841
|
+
type: "text",
|
|
41842
|
+
text: "Suggest what I should automate next \u2014 grounded ONLY in what I actually have. Inventory first: awesomate_n8n_inspect (possibilities + credentials), awesomate_n8n_storage, awesomate_knowledge_status, and my site's content shape (awesomate_run_wp_cli post/media list counts). Then give me at most three ideas, each with: what it does, what it replaces or saves (hours per week or a subscription), and whether I can build it with you now or it's a done-for-you build (1 credit = $100). No generic ideas \u2014 every suggestion must cite something from my inventory."
|
|
41843
|
+
}
|
|
41844
|
+
}
|
|
41845
|
+
]
|
|
41846
|
+
})
|
|
41847
|
+
);
|
|
41282
41848
|
async function main() {
|
|
41283
41849
|
try {
|
|
41284
41850
|
config2 = loadConfig();
|
|
@@ -41286,10 +41852,34 @@ async function main() {
|
|
|
41286
41852
|
configError = err instanceof Error ? err.message : String(err);
|
|
41287
41853
|
console.error(`[awesomate-hosting-mcp] account not resolved: ${configError}`);
|
|
41288
41854
|
}
|
|
41855
|
+
if (SKILL_DRIFT && autoUpdateEnabled()) {
|
|
41856
|
+
try {
|
|
41857
|
+
const result = installSkills(SERVER_VERSION);
|
|
41858
|
+
console.error(
|
|
41859
|
+
`[awesomate-hosting-mcp] skills auto-updated to ${SERVER_VERSION} (${result.installed.length} skills${result.removedOrphans.length ? `, ${result.removedOrphans.length} orphaned files removed` : ""}) \u2014 applies next session`
|
|
41860
|
+
);
|
|
41861
|
+
} catch (err) {
|
|
41862
|
+
console.error(
|
|
41863
|
+
"[awesomate-hosting-mcp] skills auto-update failed (non-fatal):",
|
|
41864
|
+
err instanceof Error ? err.message : err
|
|
41865
|
+
);
|
|
41866
|
+
}
|
|
41867
|
+
}
|
|
41289
41868
|
const transport = new StdioServerTransport();
|
|
41290
41869
|
await server.connect(transport);
|
|
41291
41870
|
console.error(`[awesomate-hosting-mcp] connected (stdio) \u2014 ${accountStamp()}`);
|
|
41292
41871
|
}
|
|
41872
|
+
function autoUpdateEnabled() {
|
|
41873
|
+
const env = process.env.AWESOMATE_SKILLS_AUTOUPDATE;
|
|
41874
|
+
if (env === "1" || env === "true") return true;
|
|
41875
|
+
if (env === "0" || env === "false") return false;
|
|
41876
|
+
try {
|
|
41877
|
+
const raw = JSON.parse(readFileSync3(join3(homedir3(), ".awesomate", "credentials.json"), "utf8"));
|
|
41878
|
+
return raw.autoUpdateSkills === true;
|
|
41879
|
+
} catch {
|
|
41880
|
+
return false;
|
|
41881
|
+
}
|
|
41882
|
+
}
|
|
41293
41883
|
main().catch((err) => {
|
|
41294
41884
|
console.error(
|
|
41295
41885
|
"[awesomate-hosting-mcp] fatal:",
|