Run Nest Desk from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page reads the files the way Nest does - every @Module, every constructor injection and how it resolves (with the message Nest prints when it cannot), circular imports, main.ts and its ValidationPipe options, every route with its guards, roles, pipes and body, every DTO with its class-validator decorators, and what each route returns - and hands its findings to the run as facts. The run reviews the feature or rewrites the files; the page then holds the reply to its own reading and re-scans every rewrite (nestreply.js).
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":"nest-desk"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, feature, code, facts) is missing. The page always sends all six of task, feature, code, note, carry, facts; note and carry are plain sentences when there is nothing to say. |
| 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, chosen by task, in the order the work runs - review, then rewrite:
review- a verdict (confirmed or cleared, with a reason) on each of the page's findings, issues the scan could not compute (each with a checklist kind), what the paste cannot show (by kind) and the fixes in the order to make them.rewrite- every file back whole under its path, new files where the fix needs one (a response DTO, a request DTO replacing an interface), a change log tied to the finding ids and the findings kept on purpose.
A missing or unknown task is answered as review. Both lanes send the same six fields. The facts are the page's reading - files, modules, injections and what resolves them, the bootstrap, the routes, the findings with the page's values, the skill's checklist and what the paste cannot show - and a line starting "must:" is a rule the reply has to hold. The page's handoff puts the review, as sentences, in carry for the rewrite lane. A paste longer than 40,000 characters is sent whole file by file in priority order (main.ts, modules, controllers, DTOs, services, entities); the files that do not fit are cut to their decorators, classes and the lines around each finding, every finding is still in the facts, and a rewrite keeps the cut files as pasted.
The input
| Field | Required | What |
|---|---|---|
task | yes | review or rewrite. If it is missing or neither, do review and set "task": "review". |
feature | yes | the feature's name and the size of the paste. |
code | yes | the pasted files, each under a "// file: path" line. A line "// [... lines A-B ... cut by the page ...]" marks code the page read but did not send; every finding in it is in the facts. |
note | send it (a sentence when none) | what the team says about the feature, in their words, or a sentence saying there is no note. |
carry | send it (a sentence when none) | rewrite task: the review of these files, as sentences; otherwise a sentence saying nothing is carried. |
facts | yes | the page's reading: files, modules, injections (class | module | index | token | resolved by), the bootstrap, routes (method | path | handler | guards | roles | pipes | body | validated), the findings (id | rule | severity | file:line | subject | evidence | the page's value), the skill's checklist by kind, and what the paste cannot show (kind | what). A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @affaan-m/nestjs-patterns - the only standard the run applies:
- Keep domain code inside feature modules; put cross-cutting filters, decorators, guards and interceptors in common/; keep DTOs close to the module that owns them.
- Register one global ValidationPipe with whitelist, forbidNonWhitelisted and transform on public APIs, instead of repeating validation config per route.
- Register ClassSerializerInterceptor and one global exception filter at bootstrap.
- Controllers stay thin: parse HTTP input, call a provider, return response DTOs. Business logic lives in injectable services.
- Export only the providers other modules genuinely need. Every injected provider must resolve in its module: provided there, or exported by a module it imports.
- Validate every request DTO with class-validator: a request body is a class, never an interface or any; nested objects need ValidateNested and Type.
- Use dedicated response DTOs or serializers instead of returning ORM entities; never leak password hashes, tokens or audit columns.
- Keep auth strategies and guards module-local unless they are truly shared. Encode coarse access rules in guards (roles are enforced by a guard that reads them), and resource-specific authorization in services. Prefer explicit request types for authenticated requests.
- Keep one consistent error envelope; throw framework exceptions for expected client errors; log and wrap unexpected failures centrally.
- Validate the environment at boot, not at the first request; keep config access behind typed helpers or a config service; never write a secret into the code; split environment concerns in config factories.
- Keep repository and ORM code behind providers that speak domain language; services that own a unit of work hold its transaction; controllers never coordinate multi-step writes.
- Unit test providers with mocked dependencies; add request-level tests for guards, validation pipes and exception filters; reuse the production pipes and filters in tests.
- In production: structured logging and request correlation ids, termination on invalid config, async provider initialization with health checks, background jobs and event consumers in their own modules, and explicit rate limiting, auth and audit logging on public endpoints.
House rules
- Use only the input. Never invent a file, a class, a route, a provider, a database table or a requirement the paste does not show; name things exactly as the code names them.
- The page's findings, ids, routes and injections are facts computed from the code. A finding you clear needs a reason the code itself shows. When a finding carries the page's value, use that value verbatim.
- Write about the code and the people who run it. Never mention the request's plumbing - not its field names, not that something was or was not sent.
- No web links (http or https) in prose.
- Write in English. Keep the JSON keys, the finding ids and the kind ids exactly as given.
- Never use backticks in prose (the files you return are code: template literals belong there). Words and terms of art from the rules above are free to use; what you never do is paste a whole sentence or quoted saying from this prompt, or state a rule with no fact of this paste in it.
- Never write a gendered pronoun for a person - not he, she, his or her; write they, their, the caller, the team or the admin.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
review
Judge the paste against the skill. Give every page finding exactly one verdict; add what the scan could not compute and the code clearly shows; say what the paste cannot show; then order the fixes.
summary- one sentence, at most 45 words: the state of this feature and the most important fix, naming the classes or routes.verdicts- one object per page finding, in id order:id(F1 ...),verdict(confirmed or cleared),why(one sentence from this code - the class, the line, the route).extra- issues the code shows that no finding covers, at most 5:id(X1, X2 ...),kind(one of: structure, di, exports, global-pipe, dto-validation, thin-controllers, response-shape, guards, config, errors, testing, production),file(a pasted path),issue(one sentence),fix(one sentence).[]when there is nothing to add.unseen- one object per gap of the paste, covering every kind the facts list under what the paste cannot show:kind(one of: bootstrap, root-module, unpasted-module, unpasted-class, tests, runtime),what(one sentence: what that missing piece would decide for this feature, naming it).fixes- the changes to make, in the order to make them (what breaks the boot first, then what lets bad input in or secrets out, then the rest):order(1, 2, 3 ...),ids(the finding and extra ids it settles, e.g. "F1, F5"),file(the path to change, or a new path),change(one sentence: the edit, using the page's value where there is one),why(one sentence). Every confirmed error and warning is in some fix.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "review",
"summary": "one sentence",
"verdicts": [ { "id": "F1", "verdict": "confirmed", "why": "one sentence" } ],
"extra": [ { "id": "X1", "kind": "guards", "file": "src/feature/feature.service.ts", "issue": "one sentence", "fix": "one sentence" } ],
"unseen": [ { "kind": "root-module", "what": "one sentence" } ],
"fixes": [ { "order": 1, "ids": "F1", "file": "src/feature/feature.module.ts", "change": "one sentence", "why": "one sentence" } ],
"caveats": [ ]
}
The page holds it to its own reading (check() in nestreply.js; any warning makes the reply flagged): the reply answers the task it was sent; every page finding gets exactly one verdict, confirmed or cleared, with a reason, and no verdict names an id the page did not raise; a finding the page computes from the code itself (an unresolved injection, a circular import, an export nothing provides, a missing or incomplete global pipe, an untyped body, a DTO with no validator, roles no guard reads) is flagged when cleared; every gap of the paste the facts list is covered by its kind; extra issues carry an id X1, X2 ..., a checklist kind and a pasted file; fixes are numbered 1, 2, 3 ... and every confirmed error and warning is in some fix; a summary; no web link, no gendered pronoun and no talk of the request's field names.
rewrite
Return the corrected files. Apply the review when one is carried (keep what it cleared, fix what it confirmed, in its order); otherwise fix every error and warning in the page's findings. Change only what a finding or the review asks for; keep every route (method and path), every class and the code style; keep the team's note.
summary- one sentence, at most 45 words: what the rewrite fixed and what it left, naming files.files- every pasted file, whole, under its own path:path,content(the complete file - imports included, nothing elided). A new file (a response DTO, a request DTO replacing an interface) gets a new path beside the module that owns it. A file you remove on purpose is left out, and a change says so with its path. Never put a "// file:" line inside a content.changes- one object per edit:ids(the finding or review ids it fixes, e.g. "F2, F3"),file(the path),what(one sentence: what changed).kept- one object per finding left as it is:id,why(one sentence).[]when every finding is fixed.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "rewrite",
"summary": "one sentence",
"files": [ { "path": "src/feature/feature.module.ts", "content": "the whole file" } ],
"changes": [ { "ids": "F1", "file": "src/feature/feature.module.ts", "what": "one sentence" } ],
"kept": [ ],
"caveats": [ ]
}
The page puts the returned files back together (a file it cut from the run is kept as pasted) and re-scans them with the same free check: a pasted file, class or route (method and path) missing with no change explaining it is flagged; a change that claims to fix a finding the re-scan still raises is flagged; a finding that still fires and is neither fixed nor kept with a reason is flagged; a finding the rewrite introduces is flagged; a file marker inside a file's content, an empty file, a set of files returned unchanged and an empty change log are flagged. A finding is identified by its rule and subject, never by its number, so renumbering cannot hide one.
Worked examples
Three runs from the page's free samples (invented code): the review of a users feature, its rewrite carrying that review, and the review of an orders module that cannot boot. Each input is exactly what the page sends; each reply is the saved reply the sample replays, generated with every finding id taken from the engine - but the code, the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.
review - A users feature
The page read 7 files and 3 routes and raised 8 findings (F1 input-untyped, F2 entity-leak, F3 entity-leak, F4 entity-leak, F5 pipe-options, F6 dto-prop-undecorated, F7 param-no-pipe, F8 plain-error).
Input (code is 3488 characters; the first 1396 are shown; facts is 5027 characters; the first 1389 are shown):
{
"task": "review",
"feature": "Feature: Users\nPaste: 7 files, 115 lines, 1 module, 3 routes, 8 findings",
"code": "// file: src/main.ts\nimport { ValidationPipe } from '@nestjs/common';\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\n\nasync function bootstrap() {\n const app = await NestFactory.create(AppModule);\n app.setGlobalPrefix('api');\n app.useGlobalPipes(new ValidationPipe({ transform: true }));\n await app.listen(3000);\n}\nbootstrap();\n\n// file: src/users/users.module.ts\nimport { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { User } from './entities/user.entity';\nimport { UsersController } from './users.controller';\nimport { UsersService } from './users.service';\n\n@Module({\n imports: [TypeOrmModule.forFeature([User])],\n controllers: [UsersController],\n providers: [UsersService],\n exports: [UsersService],\n})\nexport class UsersModule {}\n\n// file: src/users/users.controller.ts\nimport { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post } from '@nestjs/common';\nimport { CreateUserInput } from './dto/create-user.input';\nimport { UpdateUserDto } from './dto/update-user.dto';\nimport { User } from './entities/user.entity';\nimport { UsersService } from './users.service';\n\n@Controller('users')\nexport class UsersController {\n constructor(private readonly usersService: UsersService) {}\n\n @Post()\n create(@Body() input: CreateUserInput): Promise<User> {\n return this.usersService.create(input);\n }\n\n[... 2092 more characters cut from this display ...]",
"note": "Users sign up through this create route; ids are uuids. Keep the route paths as they are.",
"carry": "Nothing is carried into a review - it works from the files and the page's findings.",
"facts": "The page's reading: 7 files, 115 lines - 1 module, 1 controller, 1 provider class, 1 DTO, 1 entity, 3 routes.\nFiles (path | lines | classes):\n- src/main.ts | 11 | -\n- src/users/users.module.ts | 13 | UsersModule (module)\n- src/users/users.controller.ts | 25 | UsersController (controller)\n- src/users/users.service.ts | 28 | UsersService (service)\n- src/users/dto/create-user.input.ts | 5 | CreateUserInput (interface)\n- src/users/dto/update-user.dto.ts | 14 | UpdateUserDto (dto)\n- src/users/entities/user.entity.ts | 19 | User (entity)\nModules (module | imports | controllers | providers | exports):\n- UsersModule | TypeOrmModule.forFeature([User]) | UsersController | UsersService | UsersService\nInjections (class | module | index | token | resolved by):\n- UsersController | UsersModule | 0 | UsersService | provided by UsersModule\n- UsersService | UsersModule | 0 | UserRepository | TypeOrmModule.forFeature([User])\nBootstrap: src/main.ts - global prefix 'api'; ValidationPipe new ValidationPipe({ transform: true }) (src/main.ts); interceptors none; filters none; guards none.\nRoutes (method | path | handler | guards | roles | pipes | body | validated):\n- POST | /api/users | UsersController.create | none | - | - | CreateUserInput | no - interface\n- GET | /api/users/:id | UsersController.findOne | none | - | - | - | -\n- PATCH | /api/users/:id | UsersController.update | none | - |\n\n[... 3638 more characters cut from this display ...]"
}
Reply:
{
"task": "review",
"summary": "The create route takes the CreateUserInput interface, so nothing is validated, and all 3 routes return the User entity with its passwordHash; fix the pipe options and the body type first, then add a UserResponseDto.",
"verdicts": [
{
"id": "F1",
"verdict": "confirmed",
"why": "CreateUserInput is an interface, so POST /api/users has no class for the pipe to read and any property reaches users.save()."
},
{
"id": "F2",
"verdict": "confirmed",
"why": "UsersController.create returns Promise<User>, and the saved entity carries passwordHash straight back to the caller."
},
{
"id": "F3",
"verdict": "confirmed",
"why": "UsersController.findOne returns the entity from findOneBy, passwordHash included."
},
{
"id": "F4",
"verdict": "confirmed",
"why": "UsersController.update returns what UsersService.update saves, which is the full User entity."
},
{
"id": "F5",
"verdict": "confirmed",
"why": "main.ts only sets transform: true, so unknown properties are neither stripped nor rejected."
},
{
"id": "F6",
"verdict": "confirmed",
"why": "UpdateUserDto.nickname has no decorator while name and email do, so it slips through unchecked today and would be stripped once whitelist is on."
},
{
"id": "F7",
"verdict": "confirmed",
"why": "The note says ids are uuids, and GET /api/users/:id passes any string to findOneBy while the PATCH route already uses ParseUUIDPipe."
},
{
"id": "F8",
"verdict": "confirmed",
"why": "UsersService.findOne throws a plain Error for a missing user, which the client sees as a 500 instead of a 404."
}
],
"extra": [],
"unseen": [
{
"kind": "root-module",
"what": "AppModule would show whether a global APP_INTERCEPTOR or APP_FILTER already serializes responses and shapes errors for this feature."
},
{
"kind": "tests",
"what": "No spec is pasted, so nothing proves an unknown property on POST /api/users is rejected with a 400 once the pipe is fixed."
},
{
"kind": "runtime",
"what": "Whether the signup route is rate limited and logged with a correlation id cannot be read from these files."
}
],
"fixes": [
{
"order": 1,
"ids": "F5",
"file": "src/main.ts",
"change": "Register new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }) as the global pipe.",
"why": "Unknown properties must be rejected before any DTO change can be trusted."
},
{
"order": 2,
"ids": "F1, F6",
"file": "src/users/dto/create-user.dto.ts",
"change": "Replace the CreateUserInput interface with a CreateUserDto class (email, name, password with class-validator decorators) and give UpdateUserDto.nickname @IsOptional() @IsString().",
"why": "The create body becomes a class the pipe can validate, and no property of the update body is left undecorated."
},
{
"order": 3,
"ids": "F2, F3, F4",
"file": "src/users/dto/user-response.dto.ts",
"change": "Add a UserResponseDto with id, email, name and createdAt, and make every UsersService method return it instead of the User entity.",
"why": "passwordHash must never leave the service."
},
{
"order": 4,
"ids": "F7, F8",
"file": "src/users/users.controller.ts",
"change": "Parse the id of GET /api/users/:id with ParseUUIDPipe and throw NotFoundException from UsersService for a missing user.",
"why": "A malformed id becomes a 400 and a missing user a 404, not a lookup and a 500."
}
],
"caveats": []
}
What step 5 prints for it: F1 confirmed | F2 confirmed | F3 confirmed | F4 confirmed | F5 confirmed | F6 confirmed | F7 confirmed | F8 confirmed
The page's check: kept - nothing to flag
rewrite - A users feature
The input carries the saved review in carry, as the page's handoff does. The page's re-scan of the returned files: 8 findings before, 0 after.
Input (code is 3488 characters; the first 1396 are shown; facts is 5139 characters; the first 1389 are shown):
{
"task": "rewrite",
"feature": "Feature: Users\nPaste: 7 files, 115 lines, 1 module, 3 routes, 8 findings",
"code": "// file: src/main.ts\nimport { ValidationPipe } from '@nestjs/common';\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\n\nasync function bootstrap() {\n const app = await NestFactory.create(AppModule);\n app.setGlobalPrefix('api');\n app.useGlobalPipes(new ValidationPipe({ transform: true }));\n await app.listen(3000);\n}\nbootstrap();\n\n// file: src/users/users.module.ts\nimport { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { User } from './entities/user.entity';\nimport { UsersController } from './users.controller';\nimport { UsersService } from './users.service';\n\n@Module({\n imports: [TypeOrmModule.forFeature([User])],\n controllers: [UsersController],\n providers: [UsersService],\n exports: [UsersService],\n})\nexport class UsersModule {}\n\n// file: src/users/users.controller.ts\nimport { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post } from '@nestjs/common';\nimport { CreateUserInput } from './dto/create-user.input';\nimport { UpdateUserDto } from './dto/update-user.dto';\nimport { User } from './entities/user.entity';\nimport { UsersService } from './users.service';\n\n@Controller('users')\nexport class UsersController {\n constructor(private readonly usersService: UsersService) {}\n\n @Post()\n create(@Body() input: CreateUserInput): Promise<User> {\n return this.usersService.create(input);\n }\n\n[... 2092 more characters cut from this display ...]",
"note": "Users sign up through this create route; ids are uuids. Keep the route paths as they are.",
"carry": "The review confirmed F1, F2, F3, F4, F5, F6, F7 and F8.\nFixes, in order: 1) F5 - src/main.ts: Register new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }) as the global pipe. 2) F1, F6 - src/users/dto/create-user.dto.ts: Replace the CreateUserInput interface with a CreateUserDto class (email, name, password with class-validator decorators) and give UpdateUserDto.nickname @IsOptional() @IsString(). 3) F2, F3, F4 - src/users/dto/user-response.dto.ts: Add a UserResponseDto with id, email, name and createdAt, and make every UsersService method return it instead of the User entity. 4) F7, F8 - src/users/users.controller.ts: Parse the id of GET /api/users/:id with ParseUUIDPipe and throw NotFoundException from UsersService for a missing user.\nKeep every cleared finding as it is and say so; fix the rest.",
"facts": "The page's reading: 7 files, 115 lines - 1 module, 1 controller, 1 provider class, 1 DTO, 1 entity, 3 routes.\nFiles (path | lines | classes):\n- src/main.ts | 11 | -\n- src/users/users.module.ts | 13 | UsersModule (module)\n- src/users/users.controller.ts | 25 | UsersController (controller)\n- src/users/users.service.ts | 28 | UsersService (service)\n- src/users/dto/create-user.input.ts | 5 | CreateUserInput (interface)\n- src/users/dto/update-user.dto.ts | 14 | UpdateUserDto (dto)\n- src/users/entities/user.entity.ts | 19 | User (entity)\nModules (module | imports | controllers | providers | exports):\n- UsersModule | TypeOrmModule.forFeature([User]) | UsersController | UsersService | UsersService\nInjections (class | module | index | token | resolved by):\n- UsersController | UsersModule | 0 | UsersService | provided by UsersModule\n- UsersService | UsersModule | 0 | UserRepository | TypeOrmModule.forFeature([User])\nBootstrap: src/main.ts - global prefix 'api'; ValidationPipe new ValidationPipe({ transform: true }) (src/main.ts); interceptors none; filters none; guards none.\nRoutes (method | path | handler | guards | roles | pipes | body | validated):\n- POST | /api/users | UsersController.create | none | - | - | CreateUserInput | no - interface\n- GET | /api/users/:id | UsersController.findOne | none | - | - | - | -\n- PATCH | /api/users/:id | UsersController.update | none | - |\n\n[... 3750 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "rewrite",
"summary": "The pipe now whitelists and rejects unknown properties, CreateUserDto replaces the interface, every route returns UserResponseDto instead of the entity, and missing or malformed ids give a 404 or a 400.",
"files": [
{
"path": "src/main.ts",
"content": "import { ValidationPipe } from '@nestjs/common';\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\n\nasync function bootstrap() {\n const app = await NestFactory.create(AppModule);\n app.setGlobalPrefix('api');\n app.useGlobalPipes(new ValidationPipe({ whitelist: true, [... 90 more characters cut from this display]"
},
{
"path": "src/users/users.module.ts",
"content": "import { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { User } from './entities/user.entity';\nimport { UsersController } from './users.controller';\nimport { UsersService } from './users.service';\n\n@Module({\n imports: [TypeOrmModule.forFeature([User])],\n controllers: [... 106 more characters cut from this display]"
},
{
"path": "src/users/users.controller.ts",
"content": "import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post } from '@nestjs/common';\nimport { CreateUserDto } from './dto/create-user.dto';\nimport { UpdateUserDto } from './dto/update-user.dto';\nimport { UserResponseDto } from './dto/user-response.dto';\nimport { UsersService } from './users.service'; [... 550 more characters cut from this display]"
},
{
"path": "src/users/users.service.ts",
"content": "import { Injectable, NotFoundException } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { hash } from 'bcrypt';\nimport { Repository } from 'typeorm';\nimport { CreateUserDto } from './dto/create-user.dto';\nimport { UpdateUserDto } from './dto/update-user.dto';\nimport { UserResponseDto [... 993 more characters cut from this display]"
},
{
"path": "src/users/dto/update-user.dto.ts",
"content": "import { IsEmail, IsOptional, IsString, Length } from 'class-validator';\n\nexport class UpdateUserDto {\n @IsOptional()\n @IsString()\n @Length(2, 80)\n name?: string;\n\n @IsOptional()\n @IsEmail()\n email?: string;\n\n @IsOptional()\n @IsString()\n nickname?: string;\n}"
},
{
"path": "src/users/entities/user.entity.ts",
"content": "import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';\n\n@Entity()\nexport class User {\n @PrimaryGeneratedColumn('uuid')\n id: string;\n\n @Column({ unique: true })\n email: string;\n\n @Column()\n name: string;\n\n @Column()\n passwordHash: string;\n\n @CreateDateColumn()\n createdAt: Date;\n}"
},
{
"path": "src/users/dto/create-user.dto.ts",
"content": "import { IsEmail, IsString, Length, MinLength } from 'class-validator';\n\nexport class CreateUserDto {\n @IsEmail()\n email: string;\n\n @IsString()\n @Length(2, 80)\n name: string;\n\n @IsString()\n @MinLength(12)\n password: string;\n}"
},
{
"path": "src/users/dto/user-response.dto.ts",
"content": "import { User } from '../entities/user.entity';\n\nexport class UserResponseDto {\n id: string;\n email: string;\n name: string;\n createdAt: Date;\n\n static from(user: User): UserResponseDto {\n const dto = new UserResponseDto();\n dto.id = user.id;\n dto.email = user.email;\n dto.name = user.name; [... 58 more characters cut from this display]"
}
],
"changes": [
{
"ids": "F5",
"file": "src/main.ts",
"what": "The global pipe is now new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })."
},
{
"ids": "F1",
"file": "src/users/dto/create-user.dto.ts",
"what": "CreateUserDto, a validated class, replaces the CreateUserInput interface, and src/users/dto/create-user.input.ts is removed."
},
{
"ids": "F6",
"file": "src/users/dto/update-user.dto.ts",
"what": "UpdateUserDto.nickname gets @IsOptional() @IsString()."
},
{
"ids": "F2, F3, F4",
"file": "src/users/users.service.ts",
"what": "Every UsersService method returns UserResponseDto.from(user), so passwordHash never leaves the service."
},
{
"ids": "F7",
"file": "src/users/users.controller.ts",
"what": "GET /api/users/:id parses its id with ParseUUIDPipe."
},
{
"ids": "F8",
"file": "src/users/users.service.ts",
"what": "A missing user throws NotFoundException from a private load method."
}
],
"kept": [],
"caveats": [
"UserResponseDto is mapped by hand; switch to ClassSerializerInterceptor with @Exclude() if the team prefers serializers."
]
}
Cut from this display only (the saved reply and the check use the full text): files[0].content (394 characters, 304 shown); files[1].content (417 characters, 311 shown); files[2].content (857 characters, 307 shown); files[3].content (1312 characters, 319 shown); files[7].content (364 characters, 306 shown).
What step 5 prints for it: src/main.ts (11 lines) | src/users/users.module.ts (13 lines) | src/users/users.controller.ts (25 lines) | src/users/users.service.ts (34 lines) | src/users/dto/update-user.dto.ts (16 lines) | src/users/entities/user.entity.ts (19 lines) | src/users/dto/create-user.dto.ts (14 lines) | src/users/dto/user-response.dto.ts (17 lines)
The page's check: kept - The rewrite removes src/users/dto/create-user.input.ts (a change explains it).; The rewrite adds 2 files: src/users/dto/create-user.dto.ts and src/users/dto/user-response.dto.ts.
review - An orders module
The page read 9 files and 3 routes and raised 2 findings (F1 di-unresolved, F2 circular-import).
Input (code is 5638 characters; the first 1394 are shown; facts is 4261 characters; the first 1398 are shown):
{
"task": "review",
"feature": "Feature: Orders and payments\nPaste: 9 files, 161 lines, 3 modules, 3 routes, 2 findings",
"code": "// file: src/main.ts\nimport { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';\nimport { NestFactory, Reflector } from '@nestjs/core';\nimport { AppModule } from './app.module';\nimport { HttpExceptionFilter } from './common/filters/http-exception.filter';\n\nasync function bootstrap() {\n const app = await NestFactory.create(AppModule, { bufferLogs: true });\n app.useGlobalPipes(\n new ValidationPipe({\n whitelist: true,\n forbidNonWhitelisted: true,\n transform: true,\n transformOptions: { enableImplicitConversion: true },\n }),\n );\n app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));\n app.useGlobalFilters(new HttpExceptionFilter());\n await app.listen(3000);\n}\nbootstrap();\n\n// file: src/orders/orders.module.ts\nimport { Module } from '@nestjs/common';\nimport { PaymentsModule } from '../payments/payments.module';\nimport { OrdersController } from './orders.controller';\nimport { OrdersService } from './orders.service';\n\n@Module({\n imports: [PaymentsModule],\n controllers: [OrdersController],\n providers: [OrdersService],\n exports: [OrdersService],\n})\nexport class OrdersModule {}\n\n// file: src/orders/orders.controller.ts\nimport { Body, Controller, Get, Param, ParseUUIDPipe, Post, Req, UseGuards } from '@nestjs/common';\nimport { JwtAuthGuard } from '../auth/jwt-auth.guard';\nimport { AuthenticatedRequest } from\n\n[... 4244 more characters cut from this display ...]",
"note": "An order must check that the buyer exists before payment is authorized.",
"carry": "Nothing is carried into a review - it works from the files and the page's findings.",
"facts": "The page's reading: 9 files, 161 lines - 3 modules, 2 controllers, 2 provider classes, 2 DTOs, 0 entities, 3 routes.\nFiles (path | lines | classes):\n- src/main.ts | 20 | -\n- src/orders/orders.module.ts | 12 | OrdersModule (module)\n- src/orders/orders.controller.ts | 22 | OrdersController (controller)\n- src/orders/orders.service.ts | 30 | OrdersService (service)\n- src/orders/dto/create-order.dto.ts | 24 | OrderItemDto (dto), CreateOrderDto (dto)\n- src/payments/payments.module.ts | 12 | PaymentsModule (module)\n- src/payments/payments.controller.ts | 20 | PaymentsController (controller)\n- src/payments/payments.service.ts | 13 | PaymentsService (service)\n- src/users/users.module.ts | 8 | UsersModule (module)\nModules (module | imports | controllers | providers | exports):\n- OrdersModule | PaymentsModule | OrdersController | OrdersService | OrdersService\n- PaymentsModule | OrdersModule | PaymentsController | PaymentsService | PaymentsService\n- UsersModule | - | - | UsersService | UsersService\nInjections (class | module | index | token | resolved by):\n- OrdersController | OrdersModule | 0 | OrdersService | provided by OrdersModule\n- OrdersService | OrdersModule | 0 | PaymentsService | exported by PaymentsModule\n- OrdersService | OrdersModule | 1 | UsersService | unresolved\n- PaymentsController | PaymentsModule | 0 | PaymentsService | provided by PaymentsModule\n- PaymentsController |\n\n[... 2863 more characters cut from this display ...]"
}
Reply:
{
"task": "review",
"summary": "OrdersModule cannot boot: OrdersService injects UsersService, which OrdersModule never imports, and OrdersModule and PaymentsModule import each other without forwardRef(); import UsersModule and wrap both sides of the cycle.",
"verdicts": [
{
"id": "F1",
"verdict": "confirmed",
"why": "OrdersModule imports only PaymentsModule, and UsersService is provided and exported by UsersModule, which nothing in the orders folder imports."
},
{
"id": "F2",
"verdict": "confirmed",
"why": "orders.module.ts imports PaymentsModule and payments.module.ts imports OrdersModule, both as bare references."
}
],
"extra": [
{
"id": "X1",
"kind": "structure",
"file": "src/orders/orders.service.ts",
"issue": "OrdersService keeps placed orders in an in-memory Map, so they vanish on restart and differ between instances.",
"fix": "Persist orders through a repository provider owned by OrdersModule."
}
],
"unseen": [
{
"kind": "root-module",
"what": "AppModule would show whether OrdersModule, PaymentsModule and UsersModule are all registered and whether any of them is global."
},
{
"kind": "unpasted-class",
"what": "JwtAuthGuard and HttpExceptionFilter are not pasted, so what the guard checks and the error envelope the filter sends cannot be read."
},
{
"kind": "tests",
"what": "No spec is pasted, so nothing shows the module graph compiling in a Test.createTestingModule run."
},
{
"kind": "runtime",
"what": "Whether refunds are rate limited and audit logged is not visible from these files."
}
],
"fixes": [
{
"order": 1,
"ids": "F1",
"file": "src/orders/orders.module.ts",
"change": "Add UsersModule to the imports of OrdersModule (UsersModule exports UsersService).",
"why": "Without it Nest stops at boot with the resolution error the page quotes."
},
{
"order": 2,
"ids": "F2",
"file": "src/payments/payments.module.ts",
"change": "Use forwardRef(() => PaymentsModule) in the imports of OrdersModule and forwardRef(() => OrdersModule) in the imports of PaymentsModule.",
"why": "Each module is otherwise undefined when the other is built."
},
{
"order": 3,
"ids": "X1",
"file": "src/orders/orders.service.ts",
"change": "Move the order store behind a repository provider instead of a Map.",
"why": "Orders must survive a restart and be shared by every instance."
}
],
"caveats": [
"forwardRef() keeps the cycle; moving the refund route into OrdersModule would remove it."
]
}
What step 5 prints for it: F1 confirmed | F2 confirmed
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./nestkit.js"); require("./nestreply.js")), then:
const K = NestKit, R = NestReply;
const f = K.desk({ name: "Orders", note: "",
code: "// file: src/orders/orders.module.ts\n@Module({ controllers: [OrdersController], providers: [OrdersService] })\nexport class OrdersModule {}\n"
+ "// file: src/orders/orders.service.ts\n@Injectable()\nexport class OrdersService {\n constructor(private readonly users: UsersService) {}\n}\n"
+ "// file: src/users/users.module.ts\n@Module({ providers: [UsersService], exports: [UsersService] })\nexport class UsersModule {}\n"
+ "// file: src/users/users.service.ts\n@Injectable()\nexport class UsersService {}\n" });
// f.inv.modules, f.an.injections (cls, module, index, label, via), f.routes (method, path, guards, body, validated),
// f.findings (id, rule, sev, file, line, subject, evidence, fix), f.checks (kind, status), f.an.unseen (kind, what)
// K.findingsCsv(f), K.routesCsv(f), K.e2e(f), K.board(f) - the free exports
const b = K.buildInput(f, "review"); // or "rewrite", with the review as sentences: R.carry(review, f)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "review");
// R.check(reply, b) // {status: kept or flags, notes}
That example's service injects UsersService, but OrdersModule never imports UsersModule, so the check prints what Nest would: Nest can't resolve dependencies of the OrdersService (?). Please make sure that the argument UsersService at index [0] is available in the OrdersModule context. - fix it free before you pay for a run. 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, 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://nest-desk.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://nest-desk.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"]
// nest-desk.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://nest-desk.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://nest-desk.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
}
// NestDesk.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 NestDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://nest-desk.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://nest-desk.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://nest-desk.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://nest-desk.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":"nest-desk"}' | 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": "nest-desk"})["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: "nest-desk" })).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": "nest-desk"}, 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", "nest-desk"), 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: "nest-desk" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "nest-desk"])["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 = "nest-desk" }))!["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.
# input.json: the run input object - {"task": "review", "feature": "Feature: Users
Paste: 7 files, 115 lines, 1 module, 3 routes, 8 findings", "code": "// file: src/main.ts
import { ValidationPipe } from '@nestjs/common';
...", "note": "Users sign up through this create route; ids are uuids. Keep the route paths as they are.", "carry": "Nothing is carried into a review - it works from the files and the page's findings.", "facts": "The page's reading: 7 files, 115 lines - 1 module, 1 controller ...
The page's findings (id | rule | severity | file:line | subject | evidence | the page's value):
- F1 | input-untyped | error | src/users/users.controller.ts:12 | ..."}
# - 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", "feature": "Feature: Users
Paste: 7 files, 115 lines, 1 module, 3 routes, 8 findings", "code": "// file: src/main.ts
import { ValidationPipe } from '@nestjs/common';
...", "note": "Users sign up through this create route; ids are uuids. Keep the route paths as they are.", "carry": "Nothing is carried into a review - it works from the files and the page's findings.", "facts": "The page's reading: 7 files, 115 lines - 1 module, 1 controller ...
The page's findings (id | rule | severity | file:line | subject | evidence | the page's value):
- F1 | input-untyped | error | src/users/users.controller.ts:12 | ..."}
# - 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 "rewrite", feature, code, note, carry, facts} (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 nest-desk:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A review reply carries summary, verdicts, extra, unseen, fixes and caveats; a rewrite reply carries summary, files, changes, kept and caveats. The code below prints the verdicts of a review, or the files of a rewrite.
KEY="nest-desk:$(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 '.task, (.verdicts[]? | "\(.id) \(.verdict): \(.why)"), (.files[]? | "\(.path): \(.content | length) characters")' reply.json
import hashlib, time
key = "nest-desk:" + 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"), reply["task"])
for v in reply.get("verdicts", []):
print(v["id"], v["verdict"], "-", v["why"])
for f in reply.get("files", []):
print(f["path"], len(f["content"]), "characters")
import { createHash } from "node:crypto";
const key = `nest-desk:${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, reply.task);
for (const v of reply.verdicts || []) console.log(v.id, v.verdict, "-", v.why);
for (const f of reply.files || []) console.log(f.path, f.content.length, "characters");
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "nest-desk:" + 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"], reply["task"])
vs, _ := reply["verdicts"].([]any)
for _, v := range vs {
vv := v.(map[string]any)
fmt.Println(vv["id"], vv["verdict"], "-", vv["why"])
}
fs, _ := reply["files"].([]any)
for _, f := range fs {
ff := f.(map[string]any)
fmt.Println(ff["path"], len(ff["content"].(string)), "characters")
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "nest-desk:" + 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") + " " + reply.path("task").asText());
for (JsonNode v : reply.path("verdicts")) System.out.println(v.path("id").asText() + " " + v.path("verdict").asText() + " - " + v.path("why").asText());
for (JsonNode f : reply.path("files")) System.out.println(f.path("path").asText() + " " + f.path("content").asText().length() + " characters");
require "digest"
key = "nest-desk:#{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"], reply["task"]].inspect
(reply["verdicts"] || []).each { |v| puts "#{v["id"]} #{v["verdict"]} - #{v["why"]}" }
(reply["files"] || []).each { |f| puts "#{f["path"]} #{f["content"].length} characters" }
$key = "nest-desk:" . $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"], " ", $reply["task"], "\n";
foreach ($reply["verdicts"] ?? [] as $v) echo $v["id"], " ", $v["verdict"], " - ", $v["why"], "\n";
foreach ($reply["files"] ?? [] as $f) echo $f["path"], " ", strlen($f["content"]), " characters\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"nest-desk:{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"]} {reply["task"]}");
foreach (var v in reply["verdicts"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"{v!["id"]} {v["verdict"]} - {v["why"]}");
foreach (var f in reply["files"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"{f!["path"]} {f["content"]!.GetValue<string>().Length} characters");
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: nest-desk:$(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: {"task": "review", "feature": "Feature: Users
Paste: 7 files, 115 lines, 1 module, 3 routes, 8 findings", "code": "// file: src/main.ts
import { ValidationPipe } from '@nestjs/common';
...", "note": "Users sign up through this create route; ids are uuids. Keep the route paths as they are.", "carry": "Nothing is carried into a review - it works from the files and the page's findings.", "facts": "The page's reading: 7 files, 115 lines - 1 module, 1 controller ...
The page's findings (id | rule | severity | file:line | subject | evidence | the page's value):
- F1 | input-untyped | error | src/users/users.controller.ts:12 | ..."}
# - 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": "nest-desk:" + 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": `nest-desk:${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", "nest-desk:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
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", "nest-desk:" + input.get("task") + ":" + 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" => "nest-desk:#{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");
$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: nest-desk:" . json_decode($rev, true)["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", $"nest-desk:{input["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);