← VSIX Ready
API
Tokens

Run VSIX Ready from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page parses both manifests, scans the publish workflow and any plugin.xml as text (nothing is run or installed), runs every contract, architecture, versioning and listing check, diffs the two manifests into fixed change kinds, ranks the job, editor-family and auth ladders, reads the release gates and confirms that no verdict contradicts a failing check, then hands it all to the run as facts. The run writes the go/no-go plan or the release-gate plan; the page then holds it to its own facts (vxreply.js). VSIX Ready is not affiliated with or endorsed by Microsoft, the Eclipse Foundation or JetBrains.

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":"vsix-ready"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, context, manifest, facts) is missing. The page always sends all nine of task, context, manifest, previous, workflow, jetbrains, diff, carry, facts; carry is an empty string for plan, previous and diff are empty strings when no previous package.json was pasted, and workflow and jetbrains are empty when none was pasted.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both lanes are metered and need a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. 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 - the decision, then each release:

A missing or unknown task is answered as plan. Both lanes send the same nine fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the plan's verdicts, auth rung and fixes into carry for the release lane.

The input

FieldRequiredWhat
taskyesplan or release. If it is missing or neither, do plan and set "task": "plan".
contextyesthe interview answers: the jobs and the demand behind each, what exists already, editors by share, remote and browser use, how customers sign in, the owner, restricted fleets, the deadline and the effort ceiling, how the build is marked, the gate answers, the matrix fields and whether the extension is being deprecated.
manifestyesthe package.json as the page read it: the ID, version, engines.vscode, entry points, activation events, commands, settings (a credential setting's default is withheld), contributions, capabilities, extensionKind, dependencies and the listing fields.
previoussend it (empty with no previous package.json; release needs it)the previous version's package.json read the same way, or empty.
workflowsend it (empty with no workflow)the publish workflow's signal lines (line, kind, text, with any written token masked) and whether one packed file goes to both registries, or empty.
jetbrainssend it (empty with no plugin.xml)the plugin.xml as read (id, name, vendor, since-build, until-build) and the previous plugin id, or empty.
diffsend it (empty with no previous package.json)the page's manifest diff, one row per change (kind, subject, detail, action, consequence, the safe alternative), or empty.
carrysend it (empty for plan)release task only: the go/no-go plan's verdicts, auth rung and fixes when the plan ran first; may be empty.
factsyesthe page's checks, one line each with the rule, the id, the subject, the status and the evidence separated by vertical bars; then the job ladder, the editor families, the auth ladder, the gates, the version facts, the compatibility matrix, the skill's workflow numbered 1 to 9, the figure list and the footnote. A line starting "must:" applies to your reply.
retry_notenooptional; if present, your previous reply was unusable - follow it and reply with only the JSON object.

The rules the model applies

From the source skill, @samber/ide-extension-offering - the only standard the run applies:

House rules

The replies

plan

Decide go/no-go per job and plan the first release: the verdict per job, the editor families, the auth rung, one fix per failing check, the first-release sequence and the compatibility matrix.

  1. summary - one sentence, at most 40 words: which jobs the editor does, on which surface, and what stands between the extension and the contract today.
  2. verdicts - one object per job the facts list, in the facts' order: job (exactly the facts' job id: config, resources, api, local, login, findings or mcp), surface (exactly the facts' surface: json_schema, language_server, mcp_server, cli_command, thin_shell, shell_launch or rich_extension), verdict (exactly the facts' verdict: go, go_later or no_go), why (one sentence naming the answer that moved it).
  3. families - the facts' four editor families, in the facts' order: family (vscode, jetbrains, other_editors or visual_studio), rung (exactly the facts' rung: default, promoted, starved, documented, separate or not_needed), why (one sentence).
  4. auth - an object: rung (exactly the facts' rung: authentication_provider, cli_login, secret_store_key or none), why (one sentence naming the answer that moved it).
  5. fixes - one object per check the facts mark fail, and any warn worth fixing: check (the page's check id), subject (the facts' subject for that row, or "" when the row has none), change (one concrete sentence: what to change in the manifest, the workflow or the release process). Every fail appears, by check id and subject.
  6. sequence - the first-release sequence, one object per step, following the skill's workflow: step (one sentence), owner (a role, one of engineering, product, security, release engineering, developer relations, support or legal - never a person), when (the number of the workflow step it belongs to, 1 to 9, never going down from one step to the next).
  7. matrix - the facts' compatibility-matrix row, one object per column in the facts' order: column (extension, min_cli, min_vscode, since_build, api_version or status), value (exactly the facts' value, "unknown" included).
  8. footnote - exactly the footnote.
  9. caveats - at most 4 short strings: what the team must confirm, starting with any unanswered interview question; [] when there is nothing to say.
{
  "task": "plan",
  "summary": "one sentence",
  "verdicts": [ { "job": "resources", "surface": "thin_shell", "verdict": "go", "why": "one sentence" } ],
  "families": [ { "family": "vscode", "rung": "default", "why": "one sentence" } ],
  "auth": { "rung": "cli_login", "why": "one sentence" },
  "fixes": [ { "check": "r3_secret_setting", "subject": "acme.apiKey", "change": "one sentence" } ],
  "sequence": [ { "step": "one sentence", "owner": "engineering", "when": 3 } ],
  "matrix": [ { "column": "extension", "value": "1.4.x" } ],
  "footnote": "† verify against the current registry documentation (VS Code Marketplace, Open VSX, JetBrains Marketplace) before quoting it.",
  "caveats": [ ]
}

The page holds it to its own facts (check() in vxreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no URL with a scheme; no backticks; a summary of at most 40 words; every registry figure with its dagger, and the exact footnote; no implied endorsement by Microsoft, the Eclipse Foundation or JetBrains; no advice to remove or unpublish an extension; no credential put in a setting; one verdict per selected job and no invented job, each with the page's surface and verdict (a moved one is put back); the four editor families in the page's order with the page's rungs (put back); the page's auth rung (put back); a fix for every fail check matched by check id and subject, and only the page's checks; sequence owners that are roles and workflow steps from 1 to 9 that never go down; the page's matrix columns and values (put back).

release

Plan the release of this version: the version facts, what every manifest change does to users and its safe action, every gate with what to do about it, the publish steps for one packed file and the changelog.

  1. summary - one sentence, at most 40 words: whether this version can ship as marked, and what must change first.
  2. version - an object: current (exactly the facts' current version), kind (exactly the facts' kind: release or pre_release), ok (exactly the facts' true or false).
  3. changes - one object per diff row, in the facts' order: kind (exactly the row's kind: id_changed, command_removed, command_renamed, setting_removed, setting_type_changed, setting_default_changed, activation_added_star, engine_floor_raised, dependency_added, trust_widened, telemetry_dep_added or view_removed), subject (exactly the row's subject), consequence (one sentence: what it does to users), action (exactly the row's action: keep, revert, deprecate or narrow). [] when the diff has no row.
  4. gates - one object per gate the facts list, in the facts' order: gate (exactly the facts' gate id: same_vsix, secret_scan, host_tests, plugin_verifier, compat_matrix, telemetry_off or untrusted_test), status (exactly the facts' status: pass, fail or unknown), action (one sentence: what to do before shipping, or what keeps it passing).
  5. publish - the publish steps in order, plain text, each step on its own line. Separate the lines with a newline, written \n inside the JSON string - for example "Package once: vsce package --out acme-cloud-1.4.0.vsix.\nScan the packed file for secrets before it leaves CI.\nPublish that same file: vsce publish --packagePath acme-cloud-1.4.0.vsix --azure-credential, then ovsx publish acme-cloud-1.4.0.vsix --trusted-publishing.\nVerify the same hash on both registries." Package once, scan the packed file, publish that same file to the VS Code Marketplace and Open VSX with federated credentials, verify the same hash - never a personal access token.
  6. changelog - the user-facing changelog lines for this version, plain text, each line on its own line separated by \n, for example "Acme: Open Console has a new command ID; the old one keeps working and is deprecated.\nThe acme.defaultStack setting is deprecated." When the facts raise the engines.vscode floor, one line names the new floor.
  7. footnote - exactly the footnote.
  8. caveats - at most 4 short strings: what to confirm before publishing; [] when there is nothing to say.
{
  "task": "release",
  "summary": "one sentence",
  "version": { "current": "1.4.0", "kind": "release", "ok": false },
  "changes": [ { "kind": "command_renamed", "subject": "acme.openDashboard", "consequence": "one sentence", "action": "deprecate" } ],
  "gates": [ { "gate": "same_vsix", "status": "fail", "action": "one sentence" } ],
  "publish": "first step\nsecond step\nthird step\nfourth step",
  "changelog": "first line\nsecond line",
  "footnote": "† verify against the current registry documentation (VS Code Marketplace, Open VSX, JetBrains Marketplace) before quoting it.",
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; the page's version facts (put back if moved); one change per diff row matched by kind and subject, no invented or doubled change, each with the page's action (put back); one gate per page gate with the page's status (put back); publish steps one per line that package once, scan, publish to the VS Code Marketplace and Open VSX with federated credentials and verify the same hash, with no personal access token; a changelog one line per item that names a raised engines.vscode floor.

Worked examples

Three runs from the page's free samples (invented products, hosts on the reserved .example domain): the go/no-go plan for an official extension that fails a lot, its release-gate plan carrying that plan, and the release-gate plan for a clean thin shell over a language server. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the long fields, strings and lists 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.

plan - A first-party extension that fails a lot

The page read it as: not yet - fails to fix first; acme.acme-cloud 1.4.0 (release, not ok); jobs config (json_schema, go), resources (thin_shell, go), login (thin_shell, go); fail checks: r2_open_vsx, r3_secret_setting (acme.apiKey), r4_logger, r6_vsce_token, a_activation, a_untrusted, v_channel.

Input (context is 1876 characters; the first 1392 are shown; facts is 9368 characters; the first 1399 are shown):

{
 "task": "plan",
 "context": "Extension: Acme Cloud (acme.acme-cloud); build marked: a pre-release; a maintained surface; plan status: confirmed\nJobs (Q1): config (author a config or schema file): real demand (tickets, deals with a job, installs); resources (browse or edit product resources): real demand (tickets, deals with a job, installs); api (call the API from the editor): not asked; local (run a local emulator, tunnel or listener): not asked; login (log in): real demand (tickets, deals with a job, installs); findings (see product findings inline): not asked; mcp (set up the MCP server): not asked\nWhat exists (Q2): a CLI with a --json contract: yes; a CLI command that prints the session or token as JSON: yes; a language server: no; a JSON schema on SchemaStore: no; an MCP server: no; an official extension already: yes; community extension: none known\nEditors by share of paying users (Q3): VS Code: 70%; VS Code forks (Cursor, Windsurf, VSCodium): 20%; JetBrains IDEs: 10%; Visual Studio: not given; Neovim, Zed and other editors: not given; Java, Kotlin or Android-heavy: no; remote work: some; browser (vscode.dev): none\nAuth today (Q4): CLI login: yes; product OAuth for desktop clients: no; API keys: yes\nOwner (Q5): a half-time owner; an owner who knows Kotlin or Java: no; on-call: no; a job needs a view a tree view, quick pick or diagnostic cannot show: no\nRestricted fleets (Q6): allowlists: yes;\n\n[... 484 more characters cut from this display ...]",
 "manifest": "The package.json as read: acme.acme-cloud \"Acme Cloud\" version 1.4.0; engines.vscode ^1.90.0; main ./out/extension.js; browser none\nactivationEvents: *\ncommands: acme.deploy (Acme: Deploy); acme.openConsole (Acme: Open Console); acme.listServices (Acme: List Services)\nsettings: acme.apiKey (string; default withheld; Your Acme API key.); acme.region (string; default \"us-east\"; Region for deployments.); acme.telemetry.enabled (boolean; default true; Send usage data to Acme.)\nviews: acmeServices; walkthroughs: none; authentication: none; jsonValidation: none; languages: none; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces not declared; virtualWorkspaces not declared; extensionKind not set\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, compile; bundler: none\ndependencies: axios, @segment/analytics-node; devDependencies: typescript, @types/vscode, vsce\nlisting: repository yes; bugs no; icon images/icon.png; keywords 0; pricing not set; preview no",
 "previous": "The previous package.json as read: acme.acme-cloud \"Acme Cloud\" version 1.3.2; engines.vscode ^1.90.0; main ./out/extension.js; browser none\nactivationEvents: *\ncommands: acme.deploy (Acme: Deploy); acme.openDashboard (Acme: Open Console); acme.listServices (Acme: List Services)\nsettings: acme.apiKey (string; default withheld; Your Acme API key.); acme.region (string; default \"us-east\"; Region for deployments.); acme.defaultStack (string; default \"\"; Stack the deploy command targets when none is picked.); acme.telemetry.enabled (boolean; default true; Send usage data to Acme.)\nviews: acmeServices; walkthroughs: none; authentication: none; jsonValidation: none; languages: none; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces not declared; virtualWorkspaces not declared; extensionKind not set\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, compile; bundler: none\ndependencies: axios, @segment/analytics-node; devDependencies: typescript, @types/vscode, vsce\nlisting: repository yes; bugs no; icon images/icon.png; keywords 0; pricing not set; preview no",
 "workflow": "The publish workflow (GitHub Actions), signal lines (line | kind | text):\n- 14 | vsce_publish | - run: npx vsce publish -p ${{ secrets.VSCE_PAT }}\nSame packed file to both registries: not published to both",
 "jetbrains": "",
 "diff": "The manifest diff (kind | subject | detail | action | consequence | the safe alternative):\n- command_renamed | acme.openDashboard | acme.openDashboard -> acme.openConsole | deprecate | keybindings, tasks and scripts that call acme.openDashboard stop working; the new command (acme.openConsole) reaches nobody who bound the old one | keep acme.openDashboard registered as an alias of acme.openConsole and deprecate it - never drop the old ID in the same release\n- setting_removed | acme.defaultStack | Stack the deploy command targets when none is picked. | deprecate | users who set acme.defaultStack lose that behaviour silently, and their settings file keeps a dead key | keep acme.defaultStack readable and mark it deprecated, pointing to its replacement",
 "carry": "",
 "facts": "The page's checks (7 fail, 7 warn, 9 pass, 5 info, 10 n/a); verdict: not yet - fails to fix first; rule | id | subject | status | evidence:\n- r1 (rule 1) | r1_direct_api | - | warn | axios in dependencies while a CLI with --json exists (interview) - route through the CLI or language server; two implementations of one behaviour drift\n- r1 (rule 1) | r1_cli_detect | - | info | no CLI path setting - detect the CLI on PATH, offer a configurable path and a pinned install, never curl | sh\n- r2 (rule 2) | r2_open_vsx | - | fail | vsce publish runs, ovsx publish never does - Cursor, VSCodium, Theia and Kiro users install from Open VSX or find a stranger's copy\n- r2 (rule 2) | r2_same_file | - | na | not published to both registries\n- r3 (rule 3) | r3_secret_setting | acme.apiKey | fail | acme.apiKey (string) holds a credential in settings - settings sync, dotfile repos and support screenshots publish it; keep it in SecretStorage or take it from the CLI's own login\n- r4 (rule 4) | r4_own_setting | acme.telemetry.enabled | warn | acme.telemetry.enabled - the host's telemetry level wins; gate on it first, and this setting can only narrow collection further\n- r4 (rule 4) | r4_logger | - | fail | @segment/analytics-node in dependencies with no @vscode/extension-telemetry and no env.createTelemetryLogger (interview) - a hand-rolled gate fires against the user's telemetry level\n- r5 (rule 5)\n\n[... 7969 more characters cut from this display ...]"
}

Reply (shortened for display - see the cuts below):

{
 "task": "plan",
 "summary": "Ship the schema plus a thin shell over the Acme CLI for resources and sign-in; first clear seven fails: Open VSX, the API key setting, Segment telemetry, the PAT, activation on star, workspace trust and the even-minor pre-release.",
 "verdicts": [
  {
   "job": "config",
   "surface": "json_schema",
   "verdict": "go",
   "why": "A schema on SchemaStore validates and completes the config file in VS Code, JetBrains and Neovim alike, so no extension code is needed for this job."
  },
  {
   "job": "resources",
   "surface": "thin_shell",
   "verdict": "go",
   "why": "Real demand for browsing Acme services needs editor UI, and the CLI with a --json contract already owns the calls, so the extension renders tree views over it; the near date keeps the default rung."
  },
  {
   "job": "login",
   "surface": "thin_shell",
   "verdict": "go",
   "why": "Real demand for one sign-in, served by a command over the CLI's token command rather than a second login flow; the near date keeps the default rung."
  }
 ],
 "families": [
  {
   "family": "vscode",
   "rung": "default",
   "why": "One VS Code-API extension reaches VS Code and its forks; with forks at 20% of users, Open VSX is part of the default, not an extra."
  },
  {
   "family": "jetbrains",
   "rung": "starved",
   "why": "JetBrains users are 10% and the second group, but there is no second owner who knows Kotlin or Java, only a half-time owner, and a near date - a second codebase with three platform majors† a year to verify does not fit."
  },
  {
   "family": "other_editors",
   "rung": "not_needed",
   "why": "No Neovim, Zed or other editor users were given; the schema still reaches them through the catalog."
  },
  {
   "family": "visual_studio",
   "rung": "not_needed",
   "why": "No Visual Studio users were given."
  }
 ],
 "auth": {
  "rung": "cli_login",
  "why": "Customers already sign in with the Acme CLI and it has a command that prints the session as JSON, so the extension reuses that login, holds the token in memory only, and no OAuth for desktop clients exists to promote anything higher."
 },
 "fixes": [
  {
   "check": "r2_open_vsx",
   "subject": "",
   "change": "Add an ovsx publish step that ships the very file vsce packed, and claim the acme namespace on Open VSX with an ownership claim before the next release."
  },
  {
   "check": "r3_secret_setting",
   "subject": "acme.apiKey",
   "change": "Drop acme.apiKey from contributes.configuration, read the key from SecretStorage or the CLI's token command, and tell users to delete the old value from their settings and rotate it."
  },
  {
   "check": "r4_logger",
   "subject": "",
   "change": "Send every event through @vscode/extension-telemetry or env.createTelemetryLogger instead of calling Segment directly, so the host's telemetry level gates it first."
  },
  {
   "check": "r6_vsce_token",
   "subject": "",
   "change": "Replace the VSCE_PAT publish with vsce publish --packagePath on the packed file and --azure-credential or --oidc, then revoke the token; global Azure DevOps PATs stop working on 2026-12-01†."
  },
  {
   "check": "a_activation",
   "subject": "",
   "change": "Activate on the deploy and list commands and on the Acme Services view instead of on star, keeping the CLI check lazy."
  }
 ],
 "sequence": [
  {
   "step": "Publish the JSON schema for the config file to SchemaStore.",
   "owner": "engineering",
   "when": 1
  },
  {
   "step": "Claim the acme namespace on Open VSX and file the ownership claim.",
   "owner": "developer relations",
   "when": 2
  },
  {
   "step": "Replace the axios calls with CLI calls, bundle with esbuild, and set extensionKind for remote workspaces.",
   "owner": "engineering",
   "when": 3
  },
  {
   "step": "Move the API key to SecretStorage, reuse the CLI login and declare limited workspace trust.",
   "owner": "security",
   "when": 4
  },
  {
   "step": "Publish the compatibility matrix with CLI 3.2.0 as the minimum and check it at activation.",
   "owner": "engineering",
   "when": 5
  }
 ],
 "matrix": [
  {
   "column": "extension",
   "value": "1.4.x"
  },
  {
   "column": "min_cli",
   "value": "3.2.0"
  },
  {
   "column": "min_vscode",
   "value": "1.90.0"
  },
  {
   "column": "since_build",
   "value": "unknown"
  },
  {
   "column": "api_version",
   "value": "unknown"
  },
  {
   "column": "status",
   "value": "supported"
  }
 ],
 "footnote": "† verify against the current registry documentation (VS Code Marketplace, Open VSX, JetBrains Marketplace) before quoting it.",
 "caveats": [
  "Confirm the oldest fork your users run and compare its Code-OSS base with engines.vscode 1.90.0.",
  "The API version for the matrix is not stated in the input.",
  "Telemetry-off and untrusted-workspace tests are not yet in place or not confirmed."
 ]
}

Cut from this display only (the saved reply and the check use the full text): fixes (9 items, 5 shown); sequence (8 items, 5 shown).

What step 5 prints for it: r2_open_vsx | r3_secret_setting acme.apiKey | r4_logger | r6_vsce_token | a_activation | a_untrusted | v_channel | r4_own_setting acme.telemetry.enabled | r1_direct_api

The page's check: kept - nothing to flag

release - A first-party extension that fails a lot

The input carries the saved plan (R.laneForm fills carry from the plan reply), as the page's handoff does, and the manifest diff: 2 changes; gates: same_vsix fail, secret_scan fail, host_tests fail, compat_matrix unknown, telemetry_off fail, untrusted_test unknown.

Input (context is 1876 characters; the first 1392 are shown; carry is 2153 characters; the first 1394 are shown; facts is 9142 characters; the first 1399 are shown):

{
 "task": "release",
 "context": "Extension: Acme Cloud (acme.acme-cloud); build marked: a pre-release; a maintained surface; plan status: confirmed\nJobs (Q1): config (author a config or schema file): real demand (tickets, deals with a job, installs); resources (browse or edit product resources): real demand (tickets, deals with a job, installs); api (call the API from the editor): not asked; local (run a local emulator, tunnel or listener): not asked; login (log in): real demand (tickets, deals with a job, installs); findings (see product findings inline): not asked; mcp (set up the MCP server): not asked\nWhat exists (Q2): a CLI with a --json contract: yes; a CLI command that prints the session or token as JSON: yes; a language server: no; a JSON schema on SchemaStore: no; an MCP server: no; an official extension already: yes; community extension: none known\nEditors by share of paying users (Q3): VS Code: 70%; VS Code forks (Cursor, Windsurf, VSCodium): 20%; JetBrains IDEs: 10%; Visual Studio: not given; Neovim, Zed and other editors: not given; Java, Kotlin or Android-heavy: no; remote work: some; browser (vscode.dev): none\nAuth today (Q4): CLI login: yes; product OAuth for desktop clients: no; API keys: yes\nOwner (Q5): a half-time owner; an owner who knows Kotlin or Java: no; on-call: no; a job needs a view a tree view, quick pick or diagnostic cannot show: no\nRestricted fleets (Q6): allowlists: yes;\n\n[... 484 more characters cut from this display ...]",
 "manifest": "The package.json as read: acme.acme-cloud \"Acme Cloud\" version 1.4.0; engines.vscode ^1.90.0; main ./out/extension.js; browser none\nactivationEvents: *\ncommands: acme.deploy (Acme: Deploy); acme.openConsole (Acme: Open Console); acme.listServices (Acme: List Services)\nsettings: acme.apiKey (string; default withheld; Your Acme API key.); acme.region (string; default \"us-east\"; Region for deployments.); acme.telemetry.enabled (boolean; default true; Send usage data to Acme.)\nviews: acmeServices; walkthroughs: none; authentication: none; jsonValidation: none; languages: none; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces not declared; virtualWorkspaces not declared; extensionKind not set\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, compile; bundler: none\ndependencies: axios, @segment/analytics-node; devDependencies: typescript, @types/vscode, vsce\nlisting: repository yes; bugs no; icon images/icon.png; keywords 0; pricing not set; preview no",
 "previous": "The previous package.json as read: acme.acme-cloud \"Acme Cloud\" version 1.3.2; engines.vscode ^1.90.0; main ./out/extension.js; browser none\nactivationEvents: *\ncommands: acme.deploy (Acme: Deploy); acme.openDashboard (Acme: Open Console); acme.listServices (Acme: List Services)\nsettings: acme.apiKey (string; default withheld; Your Acme API key.); acme.region (string; default \"us-east\"; Region for deployments.); acme.defaultStack (string; default \"\"; Stack the deploy command targets when none is picked.); acme.telemetry.enabled (boolean; default true; Send usage data to Acme.)\nviews: acmeServices; walkthroughs: none; authentication: none; jsonValidation: none; languages: none; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces not declared; virtualWorkspaces not declared; extensionKind not set\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, compile; bundler: none\ndependencies: axios, @segment/analytics-node; devDependencies: typescript, @types/vscode, vsce\nlisting: repository yes; bugs no; icon images/icon.png; keywords 0; pricing not set; preview no",
 "workflow": "The publish workflow (GitHub Actions), signal lines (line | kind | text):\n- 14 | vsce_publish | - run: npx vsce publish -p ${{ secrets.VSCE_PAT }}\nSame packed file to both registries: not published to both",
 "jetbrains": "",
 "diff": "The manifest diff (kind | subject | detail | action | consequence | the safe alternative):\n- command_renamed | acme.openDashboard | acme.openDashboard -> acme.openConsole | deprecate | keybindings, tasks and scripts that call acme.openDashboard stop working; the new command (acme.openConsole) reaches nobody who bound the old one | keep acme.openDashboard registered as an alias of acme.openConsole and deprecate it - never drop the old ID in the same release\n- setting_removed | acme.defaultStack | Stack the deploy command targets when none is picked. | deprecate | users who set acme.defaultStack lose that behaviour silently, and their settings file keeps a dead key | keep acme.defaultStack readable and mark it deprecated, pointing to its replacement",
 "carry": "The go/no-go plan's verdicts (job: surface, verdict):\n- config: json_schema, go - A schema on SchemaStore validates and completes the config file in VS Code, JetBrains and Neovim alike, so no extension code is needed for this job.\n- resources: thin_shell, go - Real demand for browsing Acme services needs editor UI, and the CLI with a --json contract already owns the calls, so the extension renders tree views over it; the near date keeps the default rung.\n- login: thin_shell, go - Real demand for one sign-in, served by a command over the CLI's token command rather than a second login flow; the near date keeps the default rung.\nThe plan's auth rung: cli_login\nThe plan's fixes:\n- r2_open_vsx: Add an ovsx publish step that ships the very file vsce packed, and claim the acme namespace on Open VSX with an ownership claim before the next release.\n- r3_secret_setting (acme.apiKey): Drop acme.apiKey from contributes.configuration, read the key from SecretStorage or the CLI's token command, and tell users to delete the old value from their settings and rotate it.\n- r4_logger: Send every event through @vscode/extension-telemetry or env.createTelemetryLogger instead of calling Segment directly, so the host's telemetry level gates it first.\n- r6_vsce_token: Replace the VSCE_PAT publish with vsce publish --packagePath on the packed file and --azure-credential or --oidc, then revoke the\n\n[... 759 more characters cut from this display ...]",
 "facts": "The page's checks (7 fail, 7 warn, 9 pass, 5 info, 10 n/a); verdict: not yet - fails to fix first; rule | id | subject | status | evidence:\n- r1 (rule 1) | r1_direct_api | - | warn | axios in dependencies while a CLI with --json exists (interview) - route through the CLI or language server; two implementations of one behaviour drift\n- r1 (rule 1) | r1_cli_detect | - | info | no CLI path setting - detect the CLI on PATH, offer a configurable path and a pinned install, never curl | sh\n- r2 (rule 2) | r2_open_vsx | - | fail | vsce publish runs, ovsx publish never does - Cursor, VSCodium, Theia and Kiro users install from Open VSX or find a stranger's copy\n- r2 (rule 2) | r2_same_file | - | na | not published to both registries\n- r3 (rule 3) | r3_secret_setting | acme.apiKey | fail | acme.apiKey (string) holds a credential in settings - settings sync, dotfile repos and support screenshots publish it; keep it in SecretStorage or take it from the CLI's own login\n- r4 (rule 4) | r4_own_setting | acme.telemetry.enabled | warn | acme.telemetry.enabled - the host's telemetry level wins; gate on it first, and this setting can only narrow collection further\n- r4 (rule 4) | r4_logger | - | fail | @segment/analytics-node in dependencies with no @vscode/extension-telemetry and no env.createTelemetryLogger (interview) - a hand-rolled gate fires against the user's telemetry level\n- r5 (rule 5)\n\n[... 7743 more characters cut from this display ...]"
}

Reply (shortened for display - see the cuts below):

{
 "task": "release",
 "summary": "1.4.0 cannot ship as a pre-release: an even minor reads as a release. Keep the old command and setting deprecated, publish one packed file to both registries with federated credentials, and clear four failing gates first.",
 "version": {
  "current": "1.4.0",
  "kind": "release",
  "ok": false
 },
 "changes": [
  {
   "kind": "command_renamed",
   "subject": "acme.openDashboard",
   "consequence": "Keybindings, tasks and scripts that call acme.openDashboard stop working, and nobody who bound it reaches acme.openConsole.",
   "action": "deprecate"
  },
  {
   "kind": "setting_removed",
   "subject": "acme.defaultStack",
   "consequence": "Users who set acme.defaultStack silently lose the stack their deploys targeted.",
   "action": "deprecate"
  }
 ],
 "gates": [
  {
   "gate": "same_vsix",
   "status": "fail",
   "action": "Package once and publish that one file to both the VS Code Marketplace and Open VSX, then compare the two hashes."
  },
  {
   "gate": "secret_scan",
   "status": "fail",
   "action": "Add a step that unpacks the packed file and scans it for secrets before any publish runs."
  },
  {
   "gate": "host_tests",
   "status": "fail",
   "action": "Add integration tests with the VS Code test CLI on engines 1.90.0, stable and Insiders, plus the main fork and one remote target."
  },
  {
   "gate": "compat_matrix",
   "status": "unknown",
   "action": "Confirm the matrix lists 1.4.x with CLI 3.2.0 and that the minimum CLI check at activation has a test."
  },
  {
   "gate": "telemetry_off",
   "status": "fail",
   "action": "Test that no Segment event fires with the host telemetry level at error or off, once events go through the host logger."
  },
  {
   "gate": "untrusted_test",
   "status": "unknown",
   "action": "Confirm a test opens an untrusted workspace and sees the CLI paths disabled."
  }
 ],
 "publish": "Package once: vsce package --out acme-cloud-1.4.0.vsix, then inspect its file list.\nScan the packed file for secrets before it leaves CI.\nPublish that same file to the VS Code Marketplace with vsce publish --packagePath acme-cloud-1.4.0.vsix [... 160 more characters cut from this display]",
 "changelog": "Acme: Open Console has a new command ID, acme.openConsole; the old acme.openDashboard keeps working and is deprecated.\nThe acme.defaultStack setting is deprecated and still read in this version.\nAcme Cloud now publishes to Open VSX as well as the VS Code [... 13 more characters cut from this display]",
 "footnote": "† verify against the current registry documentation (VS Code Marketplace, Open VSX, JetBrains Marketplace) before quoting it.",
 "caveats": [
  "Decide whether 1.4.0 ships as a release or moves to an odd minor before packaging.",
  "Revoke VSCE_PAT once the federated publish works; global Azure DevOps PATs stop working on 2026-12-01†."
 ]
}

Cut from this display only (the saved reply and the check use the full text): publish (401 characters, 241 shown); changelog (267 characters, 254 shown).

What step 5 prints for it: command_renamed acme.openDashboard -> deprecate | setting_removed acme.defaultStack -> deprecate

The page's check: kept - nothing to flag

release - A clean thin shell over a language server

The input carries the saved plan (R.laneForm fills carry from the plan reply), as the page's handoff does, and the manifest diff: 1 changes; gates: same_vsix pass, secret_scan pass, host_tests pass, compat_matrix pass, telemetry_off pass, untrusted_test unknown.

Input (context is 1821 characters; the first 1396 are shown; facts is 7755 characters; the first 1367 are shown):

{
 "task": "release",
 "context": "Extension: Lumen Lint (quillvane.lumen-lint); build marked: a release; a maintained surface; plan status: confirmed\nJobs (Q1): config (author a config or schema file): real demand (tickets, deals with a job, installs); resources (browse or edit product resources): not asked; api (call the API from the editor): not asked; local (run a local emulator, tunnel or listener): not asked; login (log in): not asked; findings (see product findings inline): real demand (tickets, deals with a job, installs); mcp (set up the MCP server): not asked\nWhat exists (Q2): a CLI with a --json contract: no; a CLI command that prints the session or token as JSON: no; a language server: yes; a JSON schema on SchemaStore: yes; an MCP server: no; an official extension already: yes; community extension: none known\nEditors by share of paying users (Q3): VS Code: 60%; VS Code forks (Cursor, Windsurf, VSCodium): 25%; JetBrains IDEs: 10%; Visual Studio: not given; Neovim, Zed and other editors: 5%; Java, Kotlin or Android-heavy: no; remote work: some; browser (vscode.dev): none\nAuth today (Q4): CLI login: no; product OAuth for desktop clients: no; API keys: no\nOwner (Q5): a staffed team; an owner who knows Kotlin or Java: no; on-call: yes; a job needs a view a tree view, quick pick or diagnostic cannot show: no\nRestricted fleets (Q6): allowlists: no; proxies with custom CAs: no; air-gapped machines: no;\n\n[... 425 more characters cut from this display ...]",
 "manifest": "The package.json as read: quillvane.lumen-lint \"Lumen Lint\" version 2.4.0; engines.vscode ^1.95.0; main ./dist/extension.js; browser none\nactivationEvents: onLanguage:lumen, workspaceContains:**/.lumenrc.json\ncommands: lumen.restartServer (Lumen: Restart Server); lumen.showOutput (Lumen: Show Output); lumen.fixAll (Lumen: Fix All Problems)\nsettings: lumen.serverPath (string; default \"\"; Path to the lumen-ls binary; empty finds it on PATH.); lumen.trace.server (string; default \"off\"; Traces the communication with the language server.)\nviews: none; walkthroughs: lumen.start; authentication: none; jsonValidation: .lumenrc.json (remote schema); languages: lumen; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces \"limited\" (Lumen Lint starts the language server only in trusted workspaces.); restrictedConfigurations lumen.serverPath; virtualWorkspaces false; extensionKind workspace\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, bundle, test; bundler: esbuild\ndependencies: vscode-languageclient, @vscode/extension-telemetry; devDependencies: esbuild, @vscode/vsce, @vscode/test-cli, @vscode/test-electron, ovsx, typescript\nlisting: repository yes; bugs yes; icon media/icon.png; keywords 3; pricing not set; preview no",
 "previous": "The previous package.json as read: quillvane.lumen-lint \"Lumen Lint\" version 2.2.1; engines.vscode ^1.90.0; main ./dist/extension.js; browser none\nactivationEvents: onLanguage:lumen, workspaceContains:**/.lumenrc.json\ncommands: lumen.restartServer (Lumen: Restart Server); lumen.showOutput (Lumen: Show Output)\nsettings: lumen.serverPath (string; default \"\"; Path to the lumen-ls binary; empty finds it on PATH.); lumen.trace.server (string; default \"off\"; Traces the communication with the language server.)\nviews: none; walkthroughs: lumen.start; authentication: none; jsonValidation: .lumenrc.json (remote schema); languages: lumen; debuggers: none; mcpServerDefinitionProviders: none\ncapabilities: untrustedWorkspaces \"limited\" (Lumen Lint starts the language server only in trusted workspaces.); restrictedConfigurations lumen.serverPath; virtualWorkspaces false; extensionKind workspace\nextensionDependencies: none; extensionPack: none\nscripts: vscode:prepublish, bundle, test; bundler: esbuild\ndependencies: vscode-languageclient, @vscode/extension-telemetry; devDependencies: esbuild, @vscode/vsce, @vscode/test-cli, @vscode/test-electron, ovsx, typescript\nlisting: repository yes; bugs yes; icon media/icon.png; keywords 3; pricing not set; preview no",
 "workflow": "The publish workflow (GitHub Actions), signal lines (line | kind | text):\n- 7 | id_token_write | id-token: write\n- 12 | tests_insiders | vscode: [\"1.95.0\", \"stable\", \"insiders\"]\n- 12 | tests_stable | vscode: [\"1.95.0\", \"stable\", \"insiders\"]\n- 20 | tests | - run: xvfb-run -a npx vscode-test\n- 32 | vsce_package | - run: npx vsce package --out lumen-lint.vsix\n- 34 | secret_scan | run: unzip -q lumen-lint.vsix -d packed && npx secretlint \"packed/**/*\"\n- 40 | vsce_publish | - run: npx vsce publish --packagePath lumen-lint.vsix --azure-credential\n- 41 | ovsx_publish | - run: npx ovsx publish lumen-lint.vsix --trusted-publishing\nSame packed file to both registries: yes",
 "jetbrains": "",
 "diff": "The manifest diff (kind | subject | detail | action | consequence | the safe alternative):\n- engine_floor_raised | engines.vscode | 1.90.0 -> 1.95.0 | keep | users on a host or fork older than 1.95.0 stop receiving updates | keep it in this minor, with a changelog line naming the new floor",
 "carry": "The go/no-go plan's verdicts (job: surface, verdict):\n- config: json_schema, go - The schema already on SchemaStore validates .lumenrc.json in every editor that reads the catalog, so no extension code is needed for it.\n- findings: thin_shell, go - Inline findings need diagnostics in the editor, and the language server already owns them, so the extension stays a thin client over it with a staffed team behind it.\nThe plan's auth rung: none",
 "facts": "The page's checks (0 fail, 0 warn, 24 pass, 4 info, 9 n/a); verdict: ready; rule | id | subject | status | evidence:\n- r1 (rule 1) | r1_direct_api | - | pass | no HTTP client or SDK in dependencies\n- r1 (rule 1) | r1_cli_detect | - | na | no CLI with --json (interview)\n- r2 (rule 2) | r2_open_vsx | - | pass | vsce publish and ovsx publish both run\n- r2 (rule 2) | r2_same_file | - | pass | one vsce package, one file published to both (lumen-lint.vsix)\n- r3 (rule 3) | r3_secret_setting | - | pass | 2 settings, none named for a token, key, secret, password or credential\n- r4 (rule 4) | r4_own_setting | - | pass | no telemetry setting of its own\n- r4 (rule 4) | r4_logger | - | pass | @vscode/extension-telemetry honours every telemetry level\n- r5 (rule 5) | r5_ext_id | - | pass | quillvane.lumen-lint in both versions\n- r5 (rule 5) | r5_plugin_id | - | na | no plugin.xml pasted\n- r6 (rule 6) | r6_vsce_token | - | pass | vsce publish uses --azure-credential\n- r6 (rule 6) | r6_ovsx_token | - | pass | ovsx publish --trusted-publishing\n- r6 (rule 6) | r6_id_token | - | pass | permissions grant id-token: write\n- r6 (rule 6) | r6_literal_token | - | pass | no token literal; credentials come from CI secrets or federation\n- r6 (rule 6) | r6_secret_scan | - | pass | a secret scan step runs\n- arch | a_activation | - | pass | activationEvents: onLanguage:lumen,\n\n[... 6388 more characters cut from this display ...]"
}

Reply (shortened for display - see the cuts below):

{
 "task": "release",
 "summary": "2.4.0 ships as a release: an even minor, the engines floor raised in a minor with a changelog line, one packed file to both registries; only the untrusted-workspace test is unconfirmed.",
 "version": {
  "current": "2.4.0",
  "kind": "release",
  "ok": true
 },
 "changes": [
  {
   "kind": "engine_floor_raised",
   "subject": "engines.vscode",
   "consequence": "Users on a host or fork older than 1.95.0 stop receiving updates and stay on 2.2.1.",
   "action": "keep"
  }
 ],
 "gates": [
  {
   "gate": "same_vsix",
   "status": "pass",
   "action": "Keep publishing the one lumen-lint.vsix to both registries and comparing its hash."
  },
  {
   "gate": "secret_scan",
   "status": "pass",
   "action": "Keep scanning the unpacked file before every publish."
  },
  {
   "gate": "host_tests",
   "status": "pass",
   "action": "Keep the 1.95.0, stable and Insiders matrix, plus the main fork and the remote target."
  },
  {
   "gate": "compat_matrix",
   "status": "pass",
   "action": "Add the 2.4.x row with engines 1.95.0 and language server 2.4.0."
  },
  {
   "gate": "telemetry_off",
   "status": "pass",
   "action": "Keep the test that no event fires with telemetry at error or off."
  },
  {
   "gate": "untrusted_test",
   "status": "unknown",
   "action": "Confirm a test opens an untrusted workspace and sees the language server stay off."
  }
 ],
 "publish": "Package once: vsce package --out lumen-lint.vsix.\nScan the packed file for secrets with the secretlint step.\nPublish that same file to the VS Code Marketplace with vsce publish --packagePath lumen-lint.vsix --azure-credential, and to Open VSX with ovsx [... 111 more characters cut from this display]",
 "changelog": "Lumen Lint now needs VS Code 1.95.0 or newer.\nNew command: Lumen: Fix All Problems.",
 "footnote": "† verify against the current registry documentation (VS Code Marketplace, Open VSX, JetBrains Marketplace) before quoting it.",
 "caveats": [
  "Confirm the oldest fork users run is based on Code-OSS 1.95.0 or newer before the floor ships."
 ]
}

Cut from this display only (the saved reply and the check use the full text): publish (363 characters, 252 shown).

What step 5 prints for it: engine_floor_raised engines.vscode -> keep

The page's check: kept - nothing to flag

Build the input yourself

Load the engine in node (global.window = global; require("./vxkit.js"); require("./vxreply.js")), then:

const K = VxKit, R = VxReply;
const prev = { name: "acme-cloud", publisher: "acme", version: "1.3.2", engines: { vscode: "^1.90.0" }, activationEvents: ["onCommand:acme.deploy"],
  contributes: { configuration: { properties: { "acme.region": { type: "string", default: "us-east", description: "Region." } } } } };
const next = JSON.parse(JSON.stringify(prev)); next.version = "1.4.0"; next.contributes.configuration.properties["acme.region"].default = "eu-west";
const f = K.form(Object.assign(K.blank(), { manifest: JSON.stringify(next), previous: JSON.stringify(prev), job_resources: "demand", has_cli_json: "yes", owner: "staffed" }));
// f.parsed and f.prevParsed (an error with its line and column), f.m and f.prevM (the manifests as read), f.wf (the workflow scan),
// f.rows (the checks), f.changes.rows (kind, subject, action), f.jobs, f.families, f.auth, f.gates, f.vf (version facts), f.matrix,
// f.contradictions (verdicts a failing check argues against - always empty for a settled board)
// K.checksCsv(f), K.diffCsv(f), K.matrixMd(f), K.board(f), K.applyPlan(f), K.fixedWorkflow(f), K.deprecationNotice(f) - the exports
const b = K.buildInput(f, "release");                        // or "plan" (R.laneForm(form, "release", planReply) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "release");
// R.check(reply, b)                                         // {status: kept or flagged, notes, fixed}

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://vsix-ready.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"}
}

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":"vsix-ready"}' | jq -r '.data.token')

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}' 

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": "plan", "context": "Extension: Acme Cloud (acme.acme-cloud); build marked: a pre-release ...", "manifest": "The package.json as read: acme.acme-cloud "Acme Cloud" version 1.4.0 ...", "previous": "The previous package.json as read: ...", "workflow": "The publish workflow (GitHub Actions), signal lines ...", "jetbrains": "", "diff": "The manifest diff (kind | subject | ...): ...", "carry": "", "facts": "The page's checks (7 fail, ...); rule | id | subject | status | evidence: ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

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 vsix-ready:<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 plan reply carries summary, verdicts, families, auth, fixes, sequence, matrix, footnote and caveats; a release-lane reply carries summary, version, changes, gates, publish, changelog, footnote and caveats. The code below prints the gist of either: each fix of the plan with its check, or each change with its action.

KEY="vsix-ready:$(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 == "release" then (.changes[] | "\(.kind) \(.subject) -> \(.action)") 
       else (.fixes[] | "\(.check) \(.subject) \(.change)") end' reply.json

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: vsix-ready:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json