Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
196 commits
Select commit Hold shift + click to select a range
ac6d7e8
feat: route LM through configurable api_base with yaml authority
HackedRico Apr 27, 2026
0c17ebc
feat(gui): MCP server checklist + enabled_servers in submit payloads
HackedRico Apr 27, 2026
97b7f18
test: integration tests for RANGE MCP server discovery and merge
HackedRico Apr 27, 2026
4a96349
fix: re-enable factory autolog and stop calling dspy.configure per-run
HackedRico Apr 27, 2026
90eedea
feat(api): /defaults endpoint, history routes, drop dead setup_routes
HackedRico Apr 27, 2026
7aa1bbf
feat(gui): pull Global Model Config defaults from /plugin/mcp/defaults
HackedRico Apr 27, 2026
cf50986
refactor: move MCP plugin code into framework subfolders
HackedRico Apr 27, 2026
cbcefb2
feat: add Workflow and Capability dataclass contracts
HackedRico Apr 27, 2026
75f2339
feat: register Author and Plan and Execute as Workflow instances
HackedRico Apr 27, 2026
5b7364d
feat: register RAG as a Capability instance
HackedRico Apr 27, 2026
b05bb1b
feat: discover workflows and capabilities at boot
HackedRico Apr 27, 2026
5adec3e
feat(svc): switch orchestrator to workflow registry
HackedRico Apr 27, 2026
964f50a
feat(api): expose workflow and capability registries via HTTP
HackedRico Apr 27, 2026
8119dfb
feat(gui): render workflows from discovery and scope per-workflow UI
HackedRico Apr 27, 2026
344ad49
docs: add REFACTOR_PLAN.md describing the workflow and capability red…
HackedRico Apr 27, 2026
dbd5dcb
fix(workflows): demand substantive output instead of methodology recap
HackedRico Apr 27, 2026
db2c379
chore(gitignore): exclude env files and local planning docs
HackedRico Apr 30, 2026
32db1e9
refactor(env): load .env once in the plugin parent process
HackedRico Apr 30, 2026
9c0f8b0
refactor(config): unify resolver and namespace credential env vars
HackedRico Apr 30, 2026
52c1551
refactor(svc): route LM resolution through the unified resolver
HackedRico Apr 30, 2026
fd54209
refactor(workflows): drop duplicate yaml loaders and silent UI overrides
HackedRico Apr 30, 2026
32c29a5
feat(rag): allow embeddings to use a separate provider
HackedRico Apr 30, 2026
5ce24fc
feat(gui): make splash a plugin readiness landing page
HackedRico Apr 30, 2026
48251da
refactor(config): promote config to a package with parent and subproc…
HackedRico Apr 30, 2026
362924b
refactor(svc): drop numbered section banners and trim noise comments
HackedRico Apr 30, 2026
3809aa1
refactor(config): collapse the config package back into a flat module
HackedRico Apr 30, 2026
2fe8a36
refactor(dspy_env): own the LM bootstrap inline; share env-var names …
HackedRico Apr 30, 2026
974214a
fix(gui): render the workflow's process_result as a Result panel
HackedRico Apr 30, 2026
c0599e8
feat(svc): in-memory run-state cache for live polling
HackedRico Apr 30, 2026
26979ac
refactor(api): read live run state from the cache, not MLflow
HackedRico Apr 30, 2026
452a954
feat(gui): render the Result panel as nested lists with bold headings
HackedRico Apr 30, 2026
bc44528
style(gui): match Result panel to the dark theme and fix bold contrast
HackedRico Apr 30, 2026
68a77a4
update gitignore
HackedRico Apr 30, 2026
133c210
chores: no longer used docs
HackedRico Apr 30, 2026
36953c6
docs: support for N plugin mcp servers
HackedRico Apr 30, 2026
154a3b6
feat(gui): chat-style workflow page for the author signature
HackedRico May 6, 2026
b868fd9
fix(core-mcp): harden caldera URL endpoint construction
HackedRico May 6, 2026
97bed15
docs: ui redesign and multi-turn chat plan documents
HackedRico May 6, 2026
ca007b3
feat(workflows): add supports_chat_history opt-in flag
HackedRico May 6, 2026
b64bdee
feat(plan_execute): thread chat_history through signature and run
HackedRico May 6, 2026
40fdf00
feat(mcp_svc): per-session chat history for opt-in workflows
HackedRico May 6, 2026
82aef7e
feat(mcp_api): accept and echo session_id on /execute
HackedRico May 6, 2026
3664d57
feat(gui): track session_id across turns and adapt composer copy
HackedRico May 6, 2026
063027d
feat(chat-history): user-controlled session-wide history toggle
HackedRico May 6, 2026
8481530
style(chat-sidebar): left-align small-cap field labels
HackedRico May 6, 2026
e28489c
ux(chat): move back button from right sidebar to top-left of header
HackedRico May 6, 2026
596febf
style(chat-sidebar): align sidebar header strip with chat header
HackedRico May 7, 2026
8efc4c1
style(chat-header): drop border on back button so it aligns with title
HackedRico May 7, 2026
8b2e1e6
style(chat-header): pin back-button and title to equal height
HackedRico May 7, 2026
b564cc1
style(chat-header): align back button and title by text baseline
HackedRico May 7, 2026
6b69723
style(chat-header): trim header padding and equalize control heights
HackedRico May 7, 2026
d8e1806
style(chat-header): center align row for even padding above and below
HackedRico May 7, 2026
cf676d8
style(chat-header): match font sizes on back button and title
HackedRico May 7, 2026
3699864
fix(chat-header): outrank bulma's h2 margin so author sits beside back
HackedRico May 7, 2026
d8f70d2
feat(gui): route plan_execute through the chat workflow component
HackedRico May 7, 2026
ddbde51
docs: update for plan_execute now using the chat workflow component
HackedRico May 7, 2026
daa4812
fix(chat-header): keep history toggle and new chat side by side
HackedRico May 7, 2026
a8498f6
fix(mcp_svc): strip dspy field markers from result and reasoning
HackedRico May 7, 2026
0be5126
chores: remove unused docs
HackedRico May 7, 2026
4f17aac
ignore graphify context
HackedRico May 7, 2026
2364eb2
chore(gitignore): split graphify scope rules into .graphifyignore
HackedRico May 8, 2026
96134b1
fix(chat-rag): inject $api so the sidebar file picker actually fetches
HackedRico May 8, 2026
1e88086
fix(workflows): translate DSPy parse failures and raise max_tokens de…
HackedRico May 8, 2026
d4906d6
fix(chat): surface the backend run error in the failure panel
HackedRico May 8, 2026
459dbfd
fix(chat): persist transcript and sessionId per workflow across remounts
HackedRico May 8, 2026
596fe2d
fix(mcp_svc): persist chat-history sessions to disk across restarts
HackedRico May 8, 2026
a45583d
fix(mcp_api): drop per-poll status log to DEBUG
HackedRico May 8, 2026
db7a32b
feat: consolidate CTI-backed MCP execution
May 12, 2026
ae5ac6e
test: use placeholder model API key
May 12, 2026
e461880
Merge remote-tracking branch 'caldera-mcp/main' into feat/mcp-cti-pla…
May 12, 2026
e334fe5
docs: add MCP workflow screenshots
May 12, 2026
d18a80c
docs: refresh MCP README
May 12, 2026
dc53143
docs: correct MCP README structure
May 12, 2026
e2152b6
fix(mcp): api_key_env fallback + ae-e2e workflow registration
May 13, 2026
6fca955
ui(mcp): remove AE-E2E card, simplify Plan and Execute, surface dynam…
May 13, 2026
1d17a78
ui(mcp): retire the Author workflow
May 13, 2026
d88cd1c
Revert "ui(mcp): retire the Author workflow"
May 13, 2026
371244b
ui(mcp): drop MCP Servers panel from workspace level 1
May 13, 2026
6c822a0
ui(mcp): keep Plan and Execute description visible everywhere except …
May 13, 2026
7d0fab6
fix(mcp/ae-e2e): pass access dict to create_object_from_schema
May 14, 2026
5c99bc0
SSL-verify + timeout config end-to-end, dynamic MLflow port, .gitigno…
May 18, 2026
55ac701
Merge branch 'feat/ssl-verify-mlflow-dynamic' into 'main'
May 18, 2026
67a4b29
Clean up vendored D3FEND CAD upstream tooling
May 27, 2026
822578e
Merge branch 'chore/cleanup-vendored-d3fend-tooling' into 'main'
May 27, 2026
c2d8b3f
Merge branch 'main' of https://github.com/HackedRico/mcp
HackedRico Aug 28, 2026
ec77f9a
Merge remote-tracking branch 'upstream/main'
HackedRico Aug 28, 2026
23cc50a
Merge branch 'main' of https://github.com/HackedRico/mcp
HackedRico Aug 28, 2026
9b5c338
Merge remote-tracking branch 'upstream/main'
HackedRico Aug 28, 2026
22f6636
Merge remote-tracking branch 'upstream/main'
HackedRico Aug 28, 2026
c2df346
fix(config): make get_config and set_config agree on the envelope
HackedRico Aug 28, 2026
912520c
fix(imports): materialize numpy before spaCy loads
HackedRico Aug 28, 2026
44b2783
test: drop tests for modules that no longer exist
HackedRico Aug 28, 2026
d352984
fix(stage1): report per-file failures honestly
HackedRico Aug 28, 2026
d003c6b
feat(cti): restore offline IR extraction
HackedRico Aug 28, 2026
aec9a8d
refactor(cti)!: drop the CALDERA fact bridge
HackedRico Aug 28, 2026
cd5b034
refactor(cti)!: drop D3FEND enrichment
HackedRico Aug 28, 2026
1497c52
refactor(mcp)!: drop the detection validation tool
HackedRico Aug 28, 2026
8574f6c
refactor(cti)!: drop entity extraction and shrink the STIX builder
HackedRico Aug 28, 2026
10f50f3
refactor(cti): extract the shared text helpers
HackedRico Aug 28, 2026
549e95b
feat(nlp): load the spaCy model once per process
HackedRico Aug 28, 2026
d90104f
feat(mcp): add cti_pipeline_build_adversary
HackedRico Aug 28, 2026
02ee5f4
refactor(workflows)!: delete ae-e2e
HackedRico Aug 28, 2026
c76b529
refactor(cti): drop the nltk dependency
HackedRico Aug 28, 2026
f745905
fix(cti): drop the linguistic source from technique attribution
HackedRico Aug 28, 2026
d5ff2cf
test(cti): does fusing several reports raise recall?
HackedRico Aug 28, 2026
588fb68
build(deps): declare what is imported, drop what is not
HackedRico Aug 28, 2026
b1261f0
docs: describe the real install and first run
HackedRico Aug 28, 2026
38fd96d
fix(mcp): match only parent to child, and cap per technique
HackedRico Aug 28, 2026
734dc89
fix(stage1): restore the taxonomy that grounding and the platform fil…
HackedRico Aug 28, 2026
c3392ca
fix: address the branch review
HackedRico Aug 28, 2026
4bd4f6e
build(deps): declare the test-only requirements
HackedRico Aug 28, 2026
ef7c156
fix(mcp)!: order abilities by ATT&CK kill chain, not CALDERA tactic
HackedRico Aug 28, 2026
8e06e1c
fix(cti): record which extractor produced the IR, and stop discarding…
HackedRico Aug 28, 2026
dd9aac5
refactor(cti): drop plumbing the deletions left behind
HackedRico Aug 28, 2026
25efda8
test: make the regression guards able to fail
HackedRico Aug 28, 2026
dea0681
test: stop asserting the schema fills a key with no producer
HackedRico Aug 28, 2026
83d80ee
fix(gui): stop reporting success for work that did not happen
HackedRico Aug 28, 2026
9eea960
test: stop the integration test mutating the operator's config
HackedRico Aug 28, 2026
5b2384f
fix(mcp): let optional tool args accept the default they declare
HackedRico Aug 28, 2026
78bb9f9
perf(cti): cache the MITRE taxonomy and normalized patterns
HackedRico Aug 30, 2026
094f6cc
refactor(mcp): derive the kill chain order from the bundle
HackedRico Aug 30, 2026
622c465
fix(mcp): send the operation keys core actually reads
HackedRico Aug 30, 2026
cb9e0ed
fix(cti): stop naming the adversary after the payload
HackedRico Aug 30, 2026
04d71e1
fix(api): serve uploaded STIX from the directory the readers use
HackedRico Aug 30, 2026
ac5fe1c
fix(api): reject malformed CTI requests with 400 instead of 500
HackedRico Aug 30, 2026
ddcec79
fix(cti): fall back to offline IR when the endpoint is unreachable
HackedRico Aug 30, 2026
ea73fac
refactor(config): layer the cti profile over llm
HackedRico Aug 30, 2026
31d034d
feat(gui): accept drag and drop for CTI uploads
HackedRico Aug 30, 2026
6a9ed10
style: repair the server description and correct the model size
HackedRico Aug 30, 2026
bc79769
fix(config): declare provider on llm so layered profiles inherit it
HackedRico Aug 30, 2026
7f166a0
style(gui): group the endpoint fields in Global Model Config
HackedRico Aug 30, 2026
a3bfbc0
fix(rag): fall back to the configured model for embeddings
HackedRico Aug 30, 2026
3161a2f
refactor(gui): drop the duplicated endpoint fields from the CTI panel
HackedRico Aug 30, 2026
31d3cc7
refactor!: remove the mock response toggle
HackedRico Aug 30, 2026
b43d55d
style(gui): give the CTI tables the full page width
HackedRico Aug 30, 2026
6311ce7
feat(gui): select CTI rows by clicking anywhere in them
HackedRico Aug 30, 2026
8a6c934
feat(cti): preview an uploaded report in the app
HackedRico Aug 30, 2026
33fe2b2
style(gui): keep the extraction panel open and fill its column
HackedRico Aug 30, 2026
86dbf35
style(gui): align the two CTI tables and add a raw View column
HackedRico Aug 30, 2026
4492719
fix(gui): put the preview close control in the corner
HackedRico Aug 30, 2026
4ca8a83
style(gui): make the extraction panel legible and say what offline does
HackedRico Aug 30, 2026
0d611c4
style(gui): let the dropzone take the ingest box's leftover height
HackedRico Aug 30, 2026
cb536a5
fix(mcp): stop the fallback metadata advertising deleted capabilities
HackedRico Aug 30, 2026
be44fcf
fix(api)!: require authorization on every plugin route
HackedRico Aug 30, 2026
5d1c881
fix(cti): write the repair debug log under data, not the cwd
HackedRico Aug 30, 2026
267d2e7
fix(api)!: contain both delete endpoints to their own directories
HackedRico Aug 30, 2026
0da0be3
docs: fix the clone url and declare poppler
HackedRico Aug 30, 2026
a0a9974
fix(cti): report missing extraction dependencies instead of failing b…
HackedRico Aug 30, 2026
f445174
feat(cti): surface pipeline failures in the browser
HackedRico Aug 30, 2026
ac03c26
fix(cti): stop crediting a model that did not run
HackedRico Aug 30, 2026
96f2c58
fix(config): show which model extraction will actually use
HackedRico Aug 30, 2026
6e3f527
fix(config)!: merge into a config section instead of replacing it
HackedRico Aug 30, 2026
5da2b0a
docs(config): remove a duplicate cti heading from the example
HackedRico Aug 30, 2026
1faa6b7
refactor(config)!: one endpoint, and workloads may only tune generation
HackedRico Aug 30, 2026
ca9a74b
fix(gui): stop the model panel writing local.yml on page load
HackedRico Aug 31, 2026
94a59f6
fix(config): make the writer enforce the rule the reader applies
HackedRico Aug 31, 2026
8bc27f3
fix(cti): scope extraction to the reports the operator selected
HackedRico Aug 31, 2026
681cace
fix(config): honour a pre-allowlist connection instead of retargeting
HackedRico Aug 31, 2026
0d42948
docs: correct the config docs to match the allowlist
HackedRico Aug 31, 2026
e4c79ce
fix(config): one source of truth for the connection
HackedRico Aug 31, 2026
96add3c
refactor(config)!: drop the pre-allowlist compatibility path
HackedRico Aug 31, 2026
ce43dc7
fix(author): pass the predicted field, not the whole Prediction
HackedRico Aug 31, 2026
9e8b41c
fix(cti): a markdown upload was accepted and never parsed
HackedRico Aug 31, 2026
e274198
fix(gui): diff the config sync against the server, not the mount snap…
HackedRico Aug 31, 2026
1098c07
docs(config): tighten the default.yml comments
HackedRico Aug 31, 2026
f82307e
fix(mlflow): record the cause of a failure, not the group wrapping it
HackedRico Aug 31, 2026
ecaf472
test(config): stop the live tests writing the operator's config
HackedRico Aug 31, 2026
71b1efc
test: no test may leave conf/local.yml changed
HackedRico Aug 31, 2026
5fbc99d
fix(config): one temperature and one max_tokens, shown by both panels
HackedRico Aug 31, 2026
4e2582b
fix(cti)!: two reports could clean to one file and one was lost
HackedRico Aug 31, 2026
3cd0f61
fix(cti): a report is processed when it has an IR, not when it moves
HackedRico Aug 31, 2026
71c7c7a
fix(config): close the writer gaps the workload allowlist left open
HackedRico Aug 31, 2026
3c5dcb6
fix(nlp): keep two-word threat actor names, and drop three dead guards
HackedRico Aug 31, 2026
91520a7
chore: replace datetime.utcnow, deprecated since python 3.12
HackedRico Aug 31, 2026
d918d15
refactor(cti)!: stop extracting what the bundle throws away
HackedRico Aug 31, 2026
510e768
fix(cti): a report containing a file hash aborted stage 2
HackedRico Aug 31, 2026
768d14a
fix(config): a configured temperature of 0 was exported as 0.5
HackedRico Aug 31, 2026
d736a17
refactor: delete code nothing reaches
HackedRico Aug 31, 2026
9215984
test: stop the live suites writing a connection to a running server
HackedRico Aug 31, 2026
28b3ead
docs: say what the pipeline extracts and what cti may set
HackedRico Aug 31, 2026
ae5ed70
test: make the STIX compliance check able to fail
HackedRico Aug 31, 2026
d5d614f
fix(hook): stop importing the plugin from spawning a server
HackedRico Aug 31, 2026
4efc8ea
fix(llm): say which model failed and where the setting lives
HackedRico Aug 31, 2026
211d96d
test: stop the live suites touching the operator's data
HackedRico Aug 31, 2026
3290f31
fix(hook): stop MLflow outliving the server that started it
HackedRico Aug 31, 2026
0cb2b25
fix(gui): let the operator dismiss the CTI status banner
HackedRico Aug 31, 2026
311e7dd
fix(gui): stop an expired session rendering as empty CTI tables
HackedRico Aug 31, 2026
38d5631
fix(gui): let the operator dismiss the CTI status banner
HackedRico Aug 31, 2026
8c8a86a
fix(gui): stop an expired session rendering as empty CTI tables
HackedRico Aug 31, 2026
fb98cb7
Merge branch 'fix/cti-ui' of https://github.com/HackedRico/mcp into f…
HackedRico Aug 31, 2026
a2c5f1a
Merge remote-tracking branch 'upstream/main' into fix/cti-ui
HackedRico Aug 31, 2026
d6fd4ec
fix(gui): stop the CTI model panel reporting a save that never happened
HackedRico Aug 31, 2026
d1a6a90
test: sweep the STIX and output dirs too
HackedRico Aug 31, 2026
ca0d003
fix(gui): contain a STIX download that fails mid-transfer
HackedRico Sep 1, 2026
2a7c1f2
fix(gui): stop the CTI page reporting work it did not do
HackedRico Sep 1, 2026
7a18fc3
fix(gui): release the run flag when the poll chain throws
HackedRico Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions gui/components/modelSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@

<div class="field">
<label class="label">Max Tokens</label>
<input class="input" type="number" v-model.number="local.max_tokens" />
<input class="input" type="number" min="1" v-model.number="local.max_tokens" />
<p class="help">Shared with <strong>Global Model Config</strong>.</p>
</div>

<p v-if="validationError" class="help is-danger" role="alert">
{{ validationError }}
</p>

<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="local.offline" /> Offline mode
Expand Down Expand Up @@ -97,6 +101,7 @@
* Imports
* ============================================================ */
import { computed, reactive, ref, watch } from 'vue'
import { request } from '../composables/request.js'

/* ============================================================
* Props / Emits
Expand Down Expand Up @@ -130,11 +135,22 @@ const resolvedModel = computed(
const resolvedApiBase = computed(
() => props.backendConfig?.resolved_api_base || 'not set'
)
const isValid = computed(
() => local.temperature === null || local.temperature === undefined
? true
: local.temperature >= 0 && local.temperature <= 1
)
// An emptied number input yields '', and '' >= 0 && '' <= 1 is true, so a
// cleared box passed validation and saved a blank into the shared llm
// profile, which has no empty-value guard of its own.
const blank = v => v === '' || v === null || v === undefined

const validationError = computed(() => {
const t = local.temperature
const m = local.max_tokens
if (blank(t)) return 'Temperature is required.'
if (!Number.isFinite(t) || t < 0 || t > 1) return 'Temperature must be between 0 and 1.'
if (blank(m)) return 'Max Tokens is required.'
if (!Number.isFinite(m) || m < 1) return 'Max Tokens must be 1 or more.'
return ''
})

const isValid = computed(() => !validationError.value)


/* ============================================================
Expand All @@ -154,6 +170,14 @@ watch(
{ deep: true, immediate: true }
)

// The success message described the values as they were sent, so any edit
// after it retires it rather than letting it vouch for unsaved changes.
watch(
() => ({ ...local }),
() => { if (saveState.value?.tone === 'has-text-success') saveState.value = null },
{ deep: true }
)

/* ============================================================
* Persist
* ============================================================ */
Expand All @@ -166,7 +190,7 @@ async function save() {
try {
// Only the fields this panel edits. conf/local.yml is deep-merged over
// default.yml, so omitted keys keep their shipped defaults.
const res = await fetch('/plugin/mcp/set_config', {
await request('Save failed', '/plugin/mcp/set_config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand All @@ -188,12 +212,12 @@ async function save() {
})
})

if (!res.ok) throw new Error(`HTTP ${res.status}`)

saveState.value = { message: 'Saved', tone: 'has-text-success' }
emit('saved')
} catch (e) {
saveState.value = { message: `Save failed: ${e.message}`, tone: 'has-text-danger' }
// request() already phrases this for the operator, including the expired
// session that used to report a green "Saved" for a save that never ran.
saveState.value = { message: e.message, tone: 'has-text-danger' }
} finally {
saving.value = false
}
Expand Down
58 changes: 58 additions & 0 deletions gui/composables/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* ============================================================
* PLUGIN REQUESTS
*
* Caldera gates plugin routes per handler, and check_permissions answers an
* expired session by raising HTTPFound('/login'). fetch follows that
* redirect, so the reply arrives as a 200 carrying the login page: res.ok is
* true, res.json() throws on the HTML, and the operator was left looking at
* an empty table with nothing to explain it. Every caller goes through these
* so no call site can forget the check.
* ============================================================ */

export const SESSION_EXPIRED =
'Session expired. Sign in to CALDERA again, then reload this page.'

function isLoginPage(res) {
return res.redirected && new URL(res.url).pathname.startsWith('/login')
}

/**
* Call a plugin endpoint and return the response.
*
* Rejects with a message written for the operator, so callers report
* `e.message` rather than assembling their own.
*
* @param {string} what how to name the failure: "Could not load the list"
*/
export async function request(what, url, options) {
let res
try {
res = await fetch(url, options)
} catch (e) {
throw new Error(`Could not reach the server: ${e.message}`)
}

// A non-default login handler answers 401/403 instead of redirecting.
if (isLoginPage(res) || res.status === 401 || res.status === 403) {
throw new Error(SESSION_EXPIRED)
}

if (!res.ok) {
// The API states its own refusals in an "error" key; a 500 has none.
let detail = ''
try { detail = (await res.json()).error || '' } catch { /* non-JSON body */ }
throw new Error(`${what} (${res.status})${detail ? ': ' + detail : ''}.`)
}

return res
}

/** As `request`, decoding the body that every endpoint but download returns. */
export async function requestJson(what, url, options) {
const res = await request(what, url, options)
try {
return await res.json()
} catch {
throw new Error(`${what}: the server sent a reply this page cannot read.`)
}
}
Loading
Loading