Run Nuxt Clinic from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page scans the .vue file for free - every hydration hazard, data-fetching mistake, plain internal link and eagerly loaded lazy component against the nuxt4-patterns skill, each with its rule id, line and fix, plus the route rule that matches the page, checked against what kind of page it is - in nxscan.js. The run writes the review or fixes the file; the page then holds the reply to its own scan, re-scans any fixed file and checks the route rule the reply proposes (nxreply.js). Not affiliated with or endorsed by the Nuxt team.
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"nuxt-clinic"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a field the run needs (task, code, kind, facts) is missing or not a string. Send every field as a string; route and config may be "". |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes - review (judge the page: a verdict, findings with fixes, a call on every scan finding and the route rule it should have) and fix (return the corrected .vue, behaviour-preserving apart from the fixes, with its route rule). A missing or unknown task is answered as review, with a caveat saying so. Both lanes send the same fields.
The input
Every field is a string.
| Field | Required | What |
|---|---|---|
task | yes | review or fix. The model answers that task and only that task. |
code | yes | the page or component, the whole .vue file. The page clips a long file (over 24,000 characters) by keeping the head and the tail and putting an <!-- ... N lines (a-b) omitted by Nuxt Clinic ... --> comment in the middle; the model keeps that comment where it was and will not guess what it hid. |
route | no (may be "") | the route the page serves, such as /products/[id]; empty for a component. The route rule either lane proposes must match it. |
kind | yes | what kind of page it is: marketing, content, catalog (all three need SEO and server HTML), dashboard, admin (personalised - never served from a shared cache) or other. |
config | no (may be "") | the routeRules part of nuxt.config (the whole defineNuxtConfig call works too). The page clips it past 6,000 characters. |
facts | yes | the page's own scan of the same code and config: the kind, the route, the matching route rule, a prescan verdict, the public API to keep (props, emits, exposed names, definePageMeta), and every finding as rule | severity | file | line | text. See the facts format. |
focus | no | what the developer wants weighed, for example "customers complain the list is sometimes someone else's". May be empty; when given, the summary says how it changed the answer. |
retry_note | no | present only on a reformat retry: the previous reply could not be read, and this says how to answer again. Leave it out otherwise. |
The facts format
facts is the page's scan, written as plain text. An API caller may send their own short summary of the page instead, but the scan's format gets the most out of a run: the review answers every error and warn rule id it finds there (one prescan entry per id), and the fix keeps every prop, emit and exposed name listed under "Public API to keep". The format, line by line:
Page kind: <kind> (<description>)< - needs SEO and server HTML. | - personalised; must never be served from a shared cache. | .>
Route: <route> (matched as <path>). or: Route: (not given).
Route rules: <n> given; the matching rule is '<pattern>' -> <options as JSON>. or: ...; none matches this route. or: none given.
Prescan verdict: <ship|refactor|rework> (<n> errors, <n> warnings, <n> notes).
Public API to keep: props <a, b>; emits <c>; exposed <d><; definePageMeta present>.
Clipped: <only when code or config was clipped>
Findings (rule | severity | file | line | text):
- <rule id> | <error|warn|info> | <vue|nuxt.config> | line <n> | <title> | <the source line>
An empty list is written (none), and a page with no findings gets the single line - none. The prescan verdict is rework at three errors or more, or when errors and warnings land in three of the four areas (hydration, data fetching, links and lazy loading, route rules); refactor on any error or warning; ship otherwise. A real example - the facts the page sends for the sample used in the worked examples below:
Page kind: dashboard (Signed-in user page (account, dashboard)) - personalised; must never be served from a shared cache.
Route: /account/orders (matched as /account/orders).
Route rules: 3 given; the matching rule is '/account/**' -> {"swr":"600"}.
Prescan verdict: refactor (1 error, 3 warnings, 0 notes).
Public API to keep: props pageSize; emits open; exposed (none).
Findings (rule | severity | file | line | text):
- NR-PRIVATECACHE | error | nuxt.config | line 4 | Shared cache on a personalised page | '/account/**' {"swr":"600"}
- NX-NOKEY | warn | vue | line 8 | useAsyncData without a stable key | const { data: orders } = await useAsyncData(async () => {
- NX-SIDEEFFECT | warn | vue | line 8 | Side effect inside a data handler | const { data: orders } = await useAsyncData(async () => {
- NX-LAZYNOPENDING | warn | vue | line 14 | Lazy data with no loading state | const { data: tips } = await useFetch('/api/me/tips', { lazy: true })
Rule ids the scan uses, with their severity (NX- rules read the .vue file, NR- rules read the route rule):
| Rule | Severity | Area | Title |
|---|---|---|---|
NX-NONDET | error | Hydration | Non-deterministic value in the first render |
NX-BROWSERAPI | error | Hydration | Browser-only API during SSR |
NX-VUEROUTER | warn | Hydration | useRoute from vue-router |
NX-FULLPATH | warn | Hydration | route.fullPath / hash drives markup |
NX-TOPFETCH | error | Data fetching | Top-level $fetch for page data |
NX-NOKEY | warn | Data fetching | useAsyncData without a stable key |
NX-SIDEEFFECT | warn | Data fetching | Side effect inside a data handler |
NX-NOAWAIT | warn | Data fetching | useFetch / useAsyncData not awaited |
NX-LAZYNOPENDING | warn | Data fetching | Lazy data with no loading state |
NX-SERVERFALSE | info | Data fetching | server: false on a page that needs SEO |
NX-MOUNTFETCH | info | Data fetching | Data fetched in onMounted |
NX-AXIOS | info | Data fetching | axios / fetch() for app data |
NX-ATAG | warn | Links and lazy loading | Plain <a> for an internal link |
NX-LAZYEAGER | info | Links and lazy loading | Lazy component rendered unconditionally |
NX-LAZYPROPS | info | Links and lazy loading | Lazily hydrated component with reactive props |
NX-CLIENTONLY | info | Hydration | <ClientOnly> content |
NR-NONE | info | Route rules | No route rule matches this page |
NR-SEOCSR | error | Route rules | ssr: false on a page that needs SEO |
NR-PRIVATECACHE | error | Route rules | Shared cache on a personalised page |
NR-GLOBAL | warn | Route rules | One rendering rule for every route |
NR-DYNPRERENDER | info | Route rules | Prerender on a dynamic route |
NR-UNKNOWN | warn | Route rules | Unknown route rule option |
A finding the scan cannot see comes back with the rule NEW.
The standard the model applies
The source skill, @affaan-m/nuxt4-patterns, is the only standard:
- Hydration safety: the first server render and the hydrated client render produce the same markup. No
Date.now(),Math.random(),window,document,localStorageornavigatorin SSR-rendered state - they move behindonMounted(),import.meta.client,<ClientOnly>or a.client.vuecomponent. Nuxt's auto-importeduseRoute(), not the one fromvue-router;route.fullPathand the hash never drive SSR markup. - Data:
await useFetch()for SSR-safe reads, so the server result travels in the payload and nothing is fetched twice.useAsyncData()when the fetcher is not a simple$fetch, always with a stable key that changes with its inputs; handlers stay free of side effects.$fetch()only for user-triggered writes and client-only actions. - Lazy data (
lazy: true,useLazyFetch,useLazyAsyncData) shows a visiblestatus === 'pending'state;server: falseonly for data not needed for SEO or first paint; payloads trimmed withpickor atransform. - Route rules are chosen per route group in
routeRules:prerender,swr,isr,ssr: false,cache,redirect. A page that needs SEO is server-rendered; a personalised page (account, dashboard, admin) is never prerendered or cached withswr,isrorcache, because a shared cache serves one user's data to another. - Lazy loading: the
Lazyprefix for non-critical components, gated withv-if; lazy hydration (hydrate-on-visible,hydrate-on-idle) for below-the-fold interactive UI, with stable props.<NuxtLink>for internal navigation, never a plain<a href="/...">.
House rules
- Every statement is grounded in the pasted code and config; no endpoint, composable, store, component or field is invented. What the answer depends on but could not see (an API's response shape, a store, a child component) goes in
caveats. - Every
errorandwarnfinding in the facts is addressed by its rule id; a finding the model thinks is wrong is disputed with a reason, never silently dropped. - The public API stays exactly as it is: every prop, emit and exposed name in the facts, and
definePageMetawhen present. - The proposed route rule's
patternmatches the page's route, its options are realrouteRulesoptions, it is notssr: falsefor a marketing, content or catalog page, and it never prerenders or caches a dashboard or admin page. - No claim that anything was run, built or tested - "run
nuxi devand compare the server HTML", not "this hydrates cleanly". - Plain text in strings; code goes in the code fields as plain source with
\nnewlines and no Markdown fences. The reply is ONE JSON object and nothing else.
The replies
Both lanes carry a route object - the rule this page should have: pattern (such as /products/**), rule (a JSON object of routeRules options, such as {"swr": 3600}) and why.
review
verdict is ship (renders the same on server and client, fetches SSR-safely, right route rule), refactor (one or two real problems) or rework (mismatches or wrong data fetching throughout, or a route rule that leaks or hides the page). A scan error the model agrees with rules out ship.
summary- two or three sentences: what the page does, the verdict and the first thing to fix.findings- most severe first:rule(the facts' rule id, orNEW),severity(high,mediumorlow),title,where(such asline 9ornuxt.config line 3),why(one or two sentences about this page),fix(the corrected lines).prescan- one entry pererrorandwarnrule id in the facts:rule,call(agreeordisagree),note(why, one sentence).route- the route rule this page should have.caveats- what could not be seen or verified.
{
"task": "review",
"verdict": "refactor",
"summary": "",
"findings": [ { "rule": "NX-TOPFETCH", "severity": "high", "title": "", "where": "line 9", "why": "", "fix": "" } ],
"prescan": [ { "rule": "NX-TOPFETCH", "call": "agree", "note": "" } ],
"route": { "pattern": "/products/**", "rule": { "swr": 3600 }, "why": "" },
"caveats": [ "" ]
}
The page holds it to its own scan: every scan error and warning answered by rule id (in findings or prescan), no rule id the scan did not report other than NEW, no ship over an undisputed error, no rework on a clean scan without a high-severity finding, a route rule that passes the checks above, and no claim that the page was run or checked. Each miss is shown next to the reply as a note.
fix
The corrected file, behaviour-preserving apart from the fixes. Every prop, emit and exposed name listed under "Public API to keep" survives, as does definePageMeta.
summary- two sentences.file-name(such aspages/products/[id].vue) andcode: the complete .vue, including what did not change. A clipped input keeps its omitted-lines comment.route- as in review, the rule this page should have.changes- one per change:rule(the rule id it answers, orNEW),what.kept- anything deliberately left:rule,what,why.caveats- what could not be seen.
{
"task": "fix",
"summary": "",
"file": { "name": "pages/products/[id].vue", "code": "" },
"route": { "pattern": "/products/**", "rule": { "swr": 3600 }, "why": "" },
"changes": [ { "rule": "NX-TOPFETCH", "what": "" } ],
"kept": [ { "rule": "", "what": "", "why": "" } ],
"caveats": [ "" ]
}
The page re-scans the fixed file: a scan error or warning still present and not listed in kept, a new problem introduced, a prop, emit or exposed name that is no longer declared, a dropped definePageMeta, or a missing omitted-lines comment on a clipped input is named. The proposed route rule gets the same checks as in review. It shows the diff and offers a .patch; the nuxt.config change is applied by hand.
Worked examples - both lanes, one page
One signed-in orders page (/account/orders, kind dashboard) whose routeRules cache /account/** with swr: 600 - a scan error and three warnings, and a focus on customers seeing someone else's list - sent once to each lane. Each input is exactly what the page sends; each reply is a real reply, abbreviated for reading: long strings end in ... and the fixed file shows only its first lines. The real replies are complete JSON with every field in full. The page's reply check found nothing to flag in either.
Orders page (dashboard) - review
Input:
{
"task": "review",
"code": "<script setup lang=\"ts\">\nconst props = defineProps<{ pageSize?: number }>()\nconst emit = defineEmits<{ (e: 'open', id: string): void }>()\nconst route = useRoute()\nconst page = computed(() => Number(route.query.page || 1))\nconst lastSeen = useState('lastSeen', () => '')\n\nconst { data: orders } = await useAsyncData(async () => {\n const res = await $fetch('/api/me/orders', { query: { page: page.value, size: props.pageSize ?? 20 } })\n lastSeen.value = res.items[0]?.id ?? ''\n return res\n})\n\nconst { data: tips } = await useFetch('/api/me/tips', { lazy: true })\n</script>\n\n<template>\n <section>\n <h1>Your orders</h1>\n <ul>\n <li v-for=\"o in orders?.items\" :key=\"o.id\" @click=\"emit('open', o.id)\">{{ o.number }} - {{ o.total }}</li>\n </ul>\n <NuxtLink :to=\"{ query: { page: page + 1 } }\">Next page</NuxtLink>\n <aside v-if=\"tips\">{{ tips.text }}</aside>\n </section>\n</template>",
"route": "/account/orders",
"kind": "dashboard",
"config": "export default defineNuxtConfig({\n routeRules: {\n '/': { prerender: true },\n '/account/**': { swr: 600 },\n '/api/**': { cors: true }\n }\n})",
"facts": "Page kind: dashboard (Signed-in user page (account, dashboard)) - personalised; must never be served from a shared cache.\nRoute: /account/orders (matched as /account/orders).\nRoute rules: 3 given; the matching rule is '/account/**' -> {\"swr\":\"600\"}.\nPrescan verdict: refactor (1 error, 3 warnings, 0 notes).\nPublic API to keep: props pageSize; emits open; exposed (none).\nFindings (rule | severity | file | line | text):\n- NR-PRIVATECACHE | error | nuxt.config | line 4 | Shared cache on a personalised page | '/account/**' {\"swr\":\"600\"}\n- NX-NOKEY | warn | vue | line 8 | useAsyncData without a stable key | const { data: orders } = await useAsyncData(async () => {\n- NX-SIDEEFFECT | warn | vue | line 8 | Side effect inside a data handler | const { data: orders } = await useAsyncData(async () => {\n- NX-LAZYNOPENDING | warn | vue | line 14 | Lazy data with no loading state | const { data: tips } = await useFetch('/api/me/tips', { lazy: true })",
"focus": "Orders change often; customers complain the list is sometimes someone else's."
}
Reply (abbreviated). The prescan said refactor; the review agreed with all four scan findings, added three NEW findings (no error UI, a clickable li that a keyboard cannot reach, a Next page link that drops the other query params) and, weighing the focus on someone else's list, moved the verdict to rework and replaced the cache with a private, no-store header:
{
"task": "review",
"verdict": "rework",
"summary": "A signed-in orders list that loads /api/me/orders for the current page and lazily shows tips. Rework: the '/account/**' rule caches it with swr: 600, which is exactly how a customer can be served someone else's order list - that is the first fix, ahead of the unkeyed useAsyncData that never refetches when ?page changes. Your focus on 'someone else's list' made the route rule the lead finding and ruled out any caching option, even a short one, despite the orders changing often.",
"findings": [
{
"rule": "NR-PRIVATECACHE",
"severity": "high",
"title": "swr cache on a per-user page",
"where": "nuxt.config line 4",
"why": "swr: 600 stores the rendered HTML (orders and payload) for /account/orders keyed by URL, not by ...",
"fix": "'/account/**': { headers: { 'cache-control': 'private, no-store' } }"
},
{
"rule": "NX-NOKEY",
"severity": "high",
"title": "No key tied to page and size",
"where": "line 8",
"why": "Without a key Nuxt derives one from file and line, so it never changes with route.query.page or ...",
"fix": "const { data: orders, status, error } = await useAsyncData( ..."
},
{
"rule": "NX-SIDEEFFECT",
"severity": "medium",
"title": "Handler writes useState",
"where": "line 10",
"why": "The handler sets lastSeen while fetching; handlers run on the server and are skipped on hydration ...",
"fix": "watch(orders, (res) => { ..."
},
{
"rule": "NX-LAZYNOPENDING",
"severity": "low",
"title": "Lazy tips with no pending state",
"where": "line 14",
"why": "tips is lazy, so on client navigation it is null until it resolves and the aside just pops in with ...",
"fix": "const { data: tips, status: tipsStatus } = await useFetch('/api/me/tips', { lazy: true }) ..."
},
{
"rule": "NEW",
"severity": "medium",
"title": "No error UI for orders",
"where": "line 8, line 20",
"why": "If /api/me/orders fails (expired session, 500) error is ignored and the user sees an empty list ...",
"fix": "<p v-if=\"error\" role=\"alert\">Your orders could not be loaded. Please try again.</p> ..."
},
{
"rule": "NEW",
"severity": "low",
"title": "Clickable li is not keyboard-accessible",
"where": "line 21",
"why": "A click handler on an li is not focusable or announced as a control, so keyboard and screen-reader ...",
"fix": "<li v-for=\"o in orders?.items\" :key=\"o.id\"> ..."
},
{
"rule": "NEW",
"severity": "low",
"title": "Next page drops other query params",
"where": "line 23",
"why": "{ query: { page: page + 1 } } replaces the whole query, so any other filter in the URL is lost on ...",
"fix": "<NuxtLink :to=\"{ query: { ...route.query, page: page + 1 } }\">Next page</NuxtLink>"
}
],
"prescan": [
{
"rule": "NR-PRIVATECACHE",
"call": "agree",
"note": "swr: 600 on '/account/**' shares one user's rendered orders with others; remove it and send ..."
},
{
"rule": "NX-NOKEY",
"call": "agree",
"note": "The key must include page and pageSize or paging never refetches and pages share a payload entry."
},
{
"rule": "NX-SIDEEFFECT",
"call": "agree",
"note": "lastSeen is written inside the handler; derive it with a watch on orders."
},
{
"rule": "NX-LAZYNOPENDING",
"call": "agree",
"note": "The lazy tips need a visible status === 'pending' state."
}
],
"route": {
"pattern": "/account/**",
"rule": {
"headers": {
"cache-control": "private, no-store"
}
},
"why": "Account pages are per-user: drop swr: 600 and render them on every request, with a private ..."
},
"caveats": [
"The /api/me/orders and /api/me/tips handlers are not shown: I assume they read the session cookie ...",
"Whatever else reads useState('lastSeen') is not shown.",
"The edge or CDN in front of Nitro is not shown; make sure it honours Cache-Control and does not ..."
]
}
Orders page (dashboard) - fix
Input - the same fields, with task set to fix:
{
"task": "fix",
"code": "<script setup lang=\"ts\">\nconst props = defineProps<{ pageSize?: number }>()\nconst emit = defineEmits<{ (e: 'open', id: string): void }>()\nconst route = useRoute()\nconst page = computed(() => Number(route.query.page || 1))\nconst lastSeen = useState('lastSeen', () => '')\n\nconst { data: orders } = await useAsyncData(async () => {\n const res = await $fetch('/api/me/orders', { query: { page: page.value, size: props.pageSize ?? 20 } })\n lastSeen.value = res.items[0]?.id ?? ''\n return res\n})\n\nconst { data: tips } = await useFetch('/api/me/tips', { lazy: true })\n</script>\n\n<template>\n <section>\n <h1>Your orders</h1>\n <ul>\n <li v-for=\"o in orders?.items\" :key=\"o.id\" @click=\"emit('open', o.id)\">{{ o.number }} - {{ o.total }}</li>\n </ul>\n <NuxtLink :to=\"{ query: { page: page + 1 } }\">Next page</NuxtLink>\n <aside v-if=\"tips\">{{ tips.text }}</aside>\n </section>\n</template>",
"route": "/account/orders",
"kind": "dashboard",
"config": "export default defineNuxtConfig({\n routeRules: {\n '/': { prerender: true },\n '/account/**': { swr: 600 },\n '/api/**': { cors: true }\n }\n})",
"facts": "Page kind: dashboard (Signed-in user page (account, dashboard)) - personalised; must never be served from a shared cache.\nRoute: /account/orders (matched as /account/orders).\nRoute rules: 3 given; the matching rule is '/account/**' -> {\"swr\":\"600\"}.\nPrescan verdict: refactor (1 error, 3 warnings, 0 notes).\nPublic API to keep: props pageSize; emits open; exposed (none).\nFindings (rule | severity | file | line | text):\n- NR-PRIVATECACHE | error | nuxt.config | line 4 | Shared cache on a personalised page | '/account/**' {\"swr\":\"600\"}\n- NX-NOKEY | warn | vue | line 8 | useAsyncData without a stable key | const { data: orders } = await useAsyncData(async () => {\n- NX-SIDEEFFECT | warn | vue | line 8 | Side effect inside a data handler | const { data: orders } = await useAsyncData(async () => {\n- NX-LAZYNOPENDING | warn | vue | line 14 | Lazy data with no loading state | const { data: tips } = await useFetch('/api/me/tips', { lazy: true })",
"focus": "Orders change often; customers complain the list is sometimes someone else's."
}
Reply (abbreviated). The data now has a key built from the page and the page size, lastSeen is derived by a watch instead of inside the handler, the lazy tips show a pending state, and pageSize and open survive; the route rule is the same one the review proposed:
{
"task": "fix",
"summary": "The orders list gets a key built from page and pageSize so paging refetches, lastSeen is derived by a watch instead of inside the handler, and the lazy tips show a pending state; error UI and a keyboard-accessible order button are added. The '/account/**' rule drops swr: 600 for a private, no-store header because the focus - customers seeing someone else's list - is that shared cache.",
"file": {
"name": "pages/account/orders.vue",
"code": "<script setup lang=\"ts\">\nconst props = defineProps<{ pageSize?: number }>()\nconst emit = defineEmits<{ (e: 'open', id: string): void }>()\nconst route = useRoute()\nconst page = computed(() => Number(route.query.page || 1))\nconst size = computed(() => props.pageSize ?? 20)\nconst lastSeen = useState('lastSeen', () => '')\n\nconst { data: orders, status, error } = await useAsyncData(\n () => `account-orders:${page.value}:${size.value}`,\n () => $fetch('/api/me/orders', { query: { page: page.value, size: size.value } })\n)\n..."
},
"route": {
"pattern": "/account/**",
"rule": {
"headers": {
"cache-control": "private, no-store"
}
},
"why": "Account pages are per-user: drop swr: 600 and render them on every request, with a private ..."
},
"changes": [
{
"rule": "NR-PRIVATECACHE",
"what": "nuxt.config: replace '/account/**': { swr: 600 } with { headers: { 'cache-control': 'private ..."
},
{
"rule": "NX-NOKEY",
"what": "useAsyncData now takes the key getter () => `account-orders:${page.value}:${size.value}`, so ..."
},
{
"rule": "NX-SIDEEFFECT",
"what": "The handler is now a plain $fetch; lastSeen is set from orders by watch(..., { immediate: true })."
},
{
"rule": "NX-LAZYNOPENDING",
"what": "Tips expose status as tipsStatus and render a 'Loading tips...' aside while pending."
},
{
"rule": "NEW",
"what": "Orders error is read and shown as a role=alert message instead of an empty list; the list carries ..."
},
{
"rule": "NEW",
"what": "Each order is a button inside the li, still emitting open with o.id."
},
{
"rule": "NEW",
"what": "Next page keeps the other query params via { ...route.query, page: page + 1 }."
}
],
"kept": [
{
"rule": "NEW",
"what": "useState('lastSeen') and the props/emits",
"why": "Other code may read lastSeen; pageSize and open are the public API."
},
{
"rule": "NEW",
"what": "lazy tips fetched during SSR",
"why": "Tips are non-critical but cheap; server: false was not needed to fix the finding."
}
],
"caveats": [
"The API handlers and their response shapes ({ items: [{ id, number, total }] }, { text }) are ...",
"The nuxt.config change is outside the .vue: apply it by hand, and check any CDN in front honours ...",
"Run `nuxi dev`, page through ?page=1 and ?page=2, and compare server HTML and response headers for ..."
]
}
Build the input yourself
The scanner and the reply checker are plain scripts that run under node (global.window = global; require("./nxscan.js"); require("./nxreply.js")), then:
const S = NxScan, R = NxReply;
const st = { code: fs.readFileSync("orders.vue", "utf8"), route: "/account/orders", kind: "dashboard",
config: fs.readFileSync("nuxt.config.ts", "utf8"), focus: "Orders change often; ..." };
const scan = S.scan(st); // kind, verdict, counts, api, findings, match (the route rule)
const b = S.buildInput(st, "review"); // or "fix"; clips long files and writes the facts
const wire = S.toWire(b.input); // every field a string - POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "review");
// R.check(reply, b) // {notes, warn}: anything the reply missed or got wrong
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html in place of YOUR_TOKEN, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://nuxt-clinic.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://nuxt-clinic.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// nuxt-clinic.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://nuxt-clinic.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://nuxt-clinic.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// NuxtClinic.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class NuxtClinic {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://nuxt-clinic.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = +"YOUR_TOKEN" # from https://nuxt-clinic.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://nuxt-clinic.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "YOUR_TOKEN"; // from https://nuxt-clinic.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"nuxt-clinic"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "nuxt-clinic"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "nuxt-clinic" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "nuxt-clinic"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "nuxt-clinic"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "nuxt-clinic" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "nuxt-clinic"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "nuxt-clinic" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra. Save one of the worked-example inputs above as input.json to follow along.
# input.json: the run input object - {"task": "review", "code": "<script setup lang=\"ts\"> ...", "route": "/account/orders", "kind": "dashboard", "config": "export default defineNuxtConfig({ ...", "facts": "Page kind: dashboard ...", "focus": "..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "review", "code": "<script setup lang=\"ts\"> ...", "route": "/account/orders", "kind": "dashboard", "config": "export default defineNuxtConfig({ ...", "facts": "Page kind: dashboard ...", "focus": "..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "review" or "fix", code, route, kind, config, facts, focus} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses nuxt-clinic:<task>:<inputHash>:a<attempt> - the lane is part of the key, so a review and a fix of the same page are two runs - and its one automatic reformat retry (sent with a retry_note, which the hash leaves out) is a new attempt number, a2, named in the page as one extra run. Any stable hash of the input works for your own key. A review carries verdict, summary, findings, prescan, route and caveats; a fix carries summary, file, route, changes, kept and caveats. The code below prints the headline parts of either: for a review the verdict, the summary and each finding's severity and title; for a fix the summary and the file name; for both the proposed route pattern.
KEY="nuxt-clinic:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "fix"
then .summary, "file: \(.file.name)"
else "verdict: \(.verdict)", .summary, (.findings[] | "[\(.severity)] \(.title)") end,
"route: \(.route.pattern)"' reply.json
import hashlib, time
key = "nuxt-clinic:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"))
if reply.get("task") == "fix":
print(reply["summary"])
print("file:", reply.get("file", {}).get("name"))
else:
print("verdict:", reply.get("verdict"))
print(reply.get("summary"))
for f in reply.get("findings", []):
print(f"[{f['severity']}] {f['title']}")
print("route:", reply.get("route", {}).get("pattern"))
import { createHash } from "node:crypto";
const key = `nuxt-clinic:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated);
if (reply.task === "fix") {
console.log(reply.summary);
console.log("file:", reply.file?.name);
} else {
console.log("verdict:", reply.verdict);
console.log(reply.summary);
for (const f of reply.findings || []) console.log(`[${f.severity}] ${f.title}`);
}
console.log("route:", reply.route?.pattern);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "nuxt-clinic:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"])
if reply["task"] == "fix" {
fmt.Println(reply["summary"])
file, _ := reply["file"].(map[string]any)
fmt.Println("file:", file["name"])
} else {
fmt.Println("verdict:", reply["verdict"])
fmt.Println(reply["summary"])
findings, _ := reply["findings"].([]any)
for _, x := range findings {
f := x.(map[string]any)
fmt.Printf("[%v] %v\n", f["severity"], f["title"])
}
}
route, _ := reply["route"].(map[string]any)
fmt.Println("route:", route["pattern"])
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "nuxt-clinic:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + j.get("truncated"));
if ("fix".equals(reply.path("task").asText())) {
System.out.println(reply.path("summary").asText());
System.out.println("file: " + reply.path("file").path("name").asText());
} else {
System.out.println("verdict: " + reply.path("verdict").asText());
System.out.println(reply.path("summary").asText());
for (JsonNode f : reply.path("findings")) System.out.println("[" + f.path("severity").asText() + "] " + f.path("title").asText());
}
System.out.println("route: " + reply.path("route").path("pattern").asText());
require "digest"
key = "nuxt-clinic:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"]].inspect
if reply["task"] == "fix"
puts reply["summary"], "file: #{(reply["file"] || {})["name"]}"
else
puts "verdict: #{reply["verdict"]}", reply["summary"]
(reply["findings"] || []).each { |f| puts "[#{f["severity"]}] #{f["title"]}" }
end
puts "route: #{(reply["route"] || {})["pattern"]}"
$key = "nuxt-clinic:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], "\n";
if (($reply["task"] ?? "") === "fix") {
echo $reply["summary"], "\n";
echo "file: ", $reply["file"]["name"] ?? "", "\n";
} else {
echo "verdict: ", $reply["verdict"] ?? "", "\n", $reply["summary"] ?? "", "\n";
foreach ($reply["findings"] ?? [] as $f) echo "[", $f["severity"], "] ", $f["title"], "\n";
}
echo "route: ", $reply["route"]["pattern"] ?? "", "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"nuxt-clinic:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
Console.WriteLine($"{j["charged_credits"]} {j["truncated"]}");
if ((string?)reply["task"] == "fix") {
Console.WriteLine(reply["summary"]);
Console.WriteLine($"file: {reply["file"]?["name"]}");
} else {
Console.WriteLine($"verdict: {reply["verdict"]}");
Console.WriteLine(reply["summary"]);
foreach (var f in reply["findings"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"[{f!["severity"]}] {f["title"]}");
}
Console.WriteLine($"route: {reply["route"]?["pattern"]}");
Step 6 — POST /run-stream
The same run as an SSE stream, with the same input.json and the same kind of key. Browsers receive progress ticks rather than text deltas; the final done message carries the job, whose output.output is the reply.
# SSE: tick/delta messages while the job runs, then a final message with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: nuxt-clinic:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json (see the input table)
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "nuxt-clinic:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `nuxt-clinic:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "nuxt-clinic:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 1<<20), 1<<24) // the final message carries the whole reply
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.header("Idempotency-Key", "nuxt-clinic:" + M.readTree(rev).path("task").asText() + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "nuxt-clinic:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$task = json_decode($rev, true)["task"];
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: nuxt-clinic:$task:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"nuxt-clinic:{JsonNode.Parse(rev)!["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);