{"owner":"Kong","repo":"insomnia","hasSkills":true,"totalSkillsCount":40,"totalTokensCount":55295,"categories":["anthropic-skill","claude-rule","root-instruction","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/component-docs/SKILL.md",".claude/skills/component-docs/template.mdx",".claude/skills/fix-scripting-feature/SKILL.md",".claude/skills/fix-scripting-feature/references/objects/README.md",".claude/skills/fix-scripting-feature/references/objects/async-objects.md",".claude/skills/fix-scripting-feature/references/objects/auth.md",".claude/skills/fix-scripting-feature/references/objects/certificates.md",".claude/skills/fix-scripting-feature/references/objects/collection.md",".claude/skills/fix-scripting-feature/references/objects/console.md",".claude/skills/fix-scripting-feature/references/objects/cookies.md",".claude/skills/fix-scripting-feature/references/objects/environments.md",".claude/skills/fix-scripting-feature/references/objects/execution.md",".claude/skills/fix-scripting-feature/references/objects/folders.md",".claude/skills/fix-scripting-feature/references/objects/headers.md",".claude/skills/fix-scripting-feature/references/objects/insomnia.md",".claude/skills/fix-scripting-feature/references/objects/interfaces.md",".claude/skills/fix-scripting-feature/references/objects/interpolator.md",".claude/skills/fix-scripting-feature/references/objects/properties.md",".claude/skills/fix-scripting-feature/references/objects/proxy-configs.md",".claude/skills/fix-scripting-feature/references/objects/request-info.md",".claude/skills/fix-scripting-feature/references/objects/request.md",".claude/skills/fix-scripting-feature/references/objects/response.md",".claude/skills/fix-scripting-feature/references/objects/send-request.md",".claude/skills/fix-scripting-feature/references/objects/test.md",".claude/skills/fix-scripting-feature/references/objects/urls.md",".claude/skills/fix-scripting-feature/references/objects/utils.md",".claude/skills/fix-scripting-feature/references/objects/variables.md",".claude/skills/fix-test-cli-ci/SKILL.md","AGENTS.md","CLAUDE.md","packages/insomnia-api/README.md","packages/insomnia-component-docs/AGENTS.md","packages/insomnia-data/README.md","packages/insomnia-inso/README.md","packages/insomnia-smoke-test/README.md","packages/insomnia-smoke-test/playwright/pages/README.md","packages/insomnia/README.md","packages/insomnia/src/basic-components/AGENTS.md","packages/insomnia/src/basic-components/README.md","packages/insomnia/src/cpp/README.md"],"skills":{".claude/skills/component-docs/SKILL.md":"---\nname: component-docs\ndescription: \"Author or update a component doc in the Docusaurus site (packages/insomnia-component-docs) for a component in the shared library. Use when adding a new component to src/basic-components, changing a component's props/variants, or when asked to document/update docs for a component.\"\nargument-hint: 'The component name and its source path, e.g. \"Select (packages/insomnia/src/basic-components/select.tsx)\"'\n---\n\n# Component Docs (Docusaurus + MDX)\n\nAuthor/maintain docs for the shared component library on the Docusaurus site at\n`packages/insomnia-component-docs`. Docs are one MDX file per component with live,\neditable examples.\n\n## When to Use\n\n- A new component was added to `packages/insomnia/src/basic-components/` (or migrated in).\n- A component's props/variants/styling changed and its doc needs updating.\n- Asked to \"document\" / \"write docs for\" / \"update docs for\" a component.\n\n## Key Facts (read before editing)\n\n- **One file per component**: `packages/insomnia-component-docs/docs/Components/<name>.mdx` (kebab-case filename).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Just set `sidebar_position` in frontmatter for ordering.\n- **Live examples** use ` ```tsx live ` fenced blocks (via `@docusaurus/theme-live-codeblock`). Component names in those blocks resolve from a shared scope, NOT from imports in the MDX.\n- **⚠️ The easy-to-forget step**: any component used in a `tsx live` block MUST be imported and registered in `packages/insomnia-component-docs/src/theme/ReactLiveScope/index.ts`. If it isn't, the live block fails to compile. Library components import as `insomnia/src/basic-components/<name>`.\n\n## Procedure\n\n1. **Inspect the component source** to get the real API: props, types, defaults, variants (`tv` config), and which theme CSS variables / `theme--*` classes it uses.\n2. **Create/update the MDX** at `docs/Components/<name>.mdx` using `template.mdx` in this skill folder as the starting point. Fill:\n   - Frontmatter: `id`, `sidebar_label`, `sidebar_position` (next free integer, or logical grouping).\n   - Title + one-line description.\n   - `## Props` table (prop / type / default / description). End with a link to the underlying React Aria props page when it extends an RA component.\n   - `## Usage Examples` — at least 2–3 ` ```tsx live ` blocks covering the main variants/states. Keep examples small and self-contained; wrap multiple elements in a `function` component.\n   - `## Styling` → `### CSS Variables` list (only the color vars the component consumes) and, if applicable, a note on the `theme--*` scope class it carries.\n3. **Register in ReactLiveScope** (`src/theme/ReactLiveScope/index.ts`): add the `import` and include the component in the `ReactLiveScope` object. Only needed once per component.\n4. **Verify the site builds** (catches broken live blocks and bad imports):\n   ```bash\n   cd packages/insomnia-component-docs && npm run build\n   ```\n   For interactive preview while writing: `cd packages/insomnia-component-docs && npm run start`.\n\n## Conventions\n\n- Match the existing docs' tone and section order (see `docs/Components/button.mdx` as the reference).\n- Document colors via CSS variables (`--color-*` / `--hl-*`); sizes/spacing are native Tailwind utilities — mirror the component's actual source, not an idealized version. (Library styling conventions live in `packages/insomnia/src/basic-components/AGENTS.md`.)\n- Keep live examples free of business/data dependencies so they render in isolation.\n- If the component is multi-part (e.g. `Modal.Header/Body/Footer`), document the sub-components together in one file.\n",".claude/skills/component-docs/template.mdx":"---\nid: COMPONENT_ID\nsidebar_label: 'ComponentLabel'\nsidebar_position: 99\n---\n\n# ComponentName\n\nOne-line description of the component and what it is built on (e.g. \"built on React Aria's `X` component\").\n\n## Props\n\n| Prop        | Type       | Default | Description        |\n| ----------- | ---------- | ------- | ------------------ |\n| `propName`  | `type`     | `-`     | What it does       |\n| `className` | `string`   | `-`     | Additional classes |\n| `children`  | `ReactNode`| `-`     | Content            |\n\nExtends all [React Aria XProps](https://react-spectrum.adobe.com/react-aria/X.html#props).\n\n## Usage Examples\n\n### Basic\n\n```tsx live\nfunction Example() {\n  return <ComponentName>Hello</ComponentName>;\n}\n```\n\n### Variants\n\n```tsx live\nfunction Example() {\n  return (\n    <div className=\"flex gap-4\">\n      <ComponentName variant=\"a\">A</ComponentName>\n      <ComponentName variant=\"b\">B</ComponentName>\n    </div>\n  );\n}\n```\n\n## Styling\n\n### CSS Variables\n\nThe component uses the following CSS variables for theming:\n\n- `--color-font`: Text color\n- `--color-bg`: Background color\n- `--hl-sm`: Subtle highlight (hover/focus)\n\n<!-- If the component carries a theme scope class (e.g. theme--dialog / theme--tooltip),\n     note it here: user themes can customize this component's colors via that class. -->\n",".claude/skills/fix-scripting-feature/SKILL.md":"---\nname: fix-scripting-feature\ndescription: 'Debug and fix issues in Insomnia''s pre-request/after-response/test scripting feature (the pm/insomnia API surface). Use when scripts throw sandbox violations, timeouts, module import errors, or produce wrong request/response/variable mutations.'\nargument-hint: 'Provide the failing script snippet, the error message, and whether it happened in the app (hidden window) or inso (CLI)'\n---\n\n# Fix Scripting Feature Issues\n\n## When to Use\n\n- A pre-request script, after-response script, or test script (`pm.*` / `insomnia.*` API) fails, throws, or produces incorrect results.\n- Errors like `SECURITY_POLICY_VIOLATION`, `no module is found for \"...\"`, `Timeout: Running script took too long`, or `insomnia object is invalid or script returns earlier than expected.`\n- `insomnia-scripting-environment` SDK objects (`Request`, `Response`, `Environment`, `Variables`, `Collection`, `test()`) behave unexpectedly.\n\n## Architecture (read this first)\n\nPre-request/after-response/test scripts (the Postman-compatible `pm`/`insomnia` API) have **three execution paths** depending on where the script runs:\n\n| Path | Entry point | Sandboxing |\n|---|---|---|\n| Electron app (default) | `packages/insomnia/src/entry.hidden-window.ts` → `packages/insomnia/src/scripting/run-script.ts`, run in a hidden `BrowserWindow` (see `packages/insomnia/src/main/window-utils.ts`) | AST-based via `packages/insomnia/src/scripting/sandbox.ts` + `script-security-rules.ts` + `require-interceptor.ts`, toggled by `context.settings.scriptSandboxEnabled` (default on) |\n| Electron app, opt-in QuickJS sandbox | `packages/insomnia/src/network/concurrency.renderer.ts` → `packages/insomnia/src/scripting/run-script-quickjs.ts`, run in a Web Worker over a WASM QuickJS VM (`quickjs-script-engine.ts`) | Gated by `settings.useQuickJsScriptSandbox` (default off). PoC with a **deliberately minimal, hand-rolled `insomnia`/`$` API** — not the shared SDK below — with no `test()`/`pm.test()`, `collectionVariables`, vault, cookies, client certificates, or request mutation, and a `sendRequest()` limited to plain-text bodies |\n| `inso` CLI and other Node-runtime callers (e.g. `packages/insomnia/src/runtimes/network/network-adapter.node.ts`) | `packages/insomnia/src/script-executor.ts` (plain `AsyncFunction`/`eval`, no Electron) | None — `script-executor.ts` never calls `sandbox.ts`'s `prepareSandbox`, it's a structurally separate implementation. (Don't confuse this with `canSandbox = !!process.type` in `network-adapter.node.ts` — that gates **plugin** request/response hook sandboxing, an unrelated subsystem.) |\n\nThe full SDK object model (`InsomniaObject`, `Request`, `Response`, `Environment`, `Variables`, `Collection`, `Test`, console/send-request shims) lives in `packages/insomnia-scripting-environment/src/objects/` and is shared across the Electron (default) and `inso`/Node paths only — the QuickJS sandbox does not use it.\n\n## `insomnia-scripting-environment` Folder Hierarchy\n\nThe SDK package (`packages/insomnia-scripting-environment/`) is where the `pm`/`insomnia` object model lives — start here for any bug about a specific script API's behavior:\n\n```\ninsomnia-scripting-environment/\n├── src/\n│   ├── objects/                   # the pm/insomnia API surface — one file per SDK concept\n│   │   ├── index.ts               # public re-exports\n│   │   ├── insomnia.ts            # InsomniaObject — the top-level `insomnia`/`pm` instance scripts see\n│   │   ├── interfaces.ts          # RequestContext, IEnvironment — the context shape passed in/out of a script run\n│   │   ├── execution.ts           # Execution — pm.execution (setNextRequest, skipRequest, location)\n│   │   ├── request.ts             # Request, RequestBody — pm.request\n│   │   ├── request-info.ts        # RequestInfo — pm.info (event name, iteration data)\n│   │   ├── response.ts            # Response — pm.response\n│   │   ├── send-request.ts        # pm.sendRequest() implementation\n│   │   ├── environments.ts        # Environment, Variables — pm.environment / pm.globals\n│   │   ├── variables.ts           # Variable, VariableList — underlying key/value model shared by environments/collection\n│   │   ├── collection.ts          # Collection — pm.collectionVariables / folder & collection variable resolution\n│   │   ├── folders.ts             # Folder, ParentFolders — collection folder hierarchy walked for variable resolution\n│   │   ├── properties.ts          # PropertyBase, PropertyList — base classes most SDK objects extend\n│   │   ├── headers.ts             # Header, HeaderList\n│   │   ├── cookies.ts             # Cookie, CookieJar, CookieList — pm.cookies\n│   │   ├── auth.ts                # RequestAuth — pm.request.auth\n│   │   ├── certificates.ts        # Certificate — client cert modeling\n│   │   ├── proxy-configs.ts       # ProxyConfig, ProxyConfigList\n│   │   ├── urls.ts                # Url, QueryParam, UrlMatchPattern — URL parsing/manipulation\n│   │   ├── console.ts             # Console — console.log capture surfaced back to the app\n│   │   ├── test.ts                # pm.test()/test() handler registration + TestHandler, waitForAllTestsDone\n│   │   ├── async-objects.ts       # ProxiedPromise — Promise plumbing so async script code can be awaited by the host\n│   │   ├── interpolator.ts        # template-tag ({{ }}) interpolation used when resolving variable values\n│   │   ├── utils.ts               # misc helpers (e.g. checkIfUrlIncludesTag)\n│   │   └── __tests__/             # tests for selected objects above (request.test.ts, response.test.ts, etc.)\n│   └── autocomplete-snippets.json # generated editor autocomplete data — see scripts/generate-autocomplete.ts\n├── scripts/\n│   └── generate-autocomplete.ts   # regenerates autocomplete-snippets.json from the objects/ source (CI-checked)\n├── docs/                          # TypeDoc-generated API reference (generated from objects/, don't hand-edit)\n├── typedoc.json                   # TypeDoc config for docs/ generation\n└── vitest.config.ts               # test runner config for `npm test -w insomnia-scripting-environment`\n```\n\nIf a bug is about *how* a script executes (sandboxing, timeouts, hidden window vs. `inso`), look in `packages/insomnia/src/scripting/` (see Architecture above). If it's about *what a specific API returns or mutates* (`pm.request.headers`, `pm.environment.get()`, `pm.response.json()`, etc.), it's almost always in this package's `src/objects/`.\n\nFor property/method-level detail on any single object above (real signatures, script-facing surface, gotchas), see `references/objects/<name>.md` in this skill folder — one file per source file in `src/objects/` (e.g. `references/objects/request.md` for `request.ts`, `references/objects/environments.md` for `environments.ts`).\n\n## Procedure\n\n1. **Identify the execution path first** — ask/check whether the failure is in the Electron app or `inso`. Fixes and even error messages differ per path (see table above).\n2. **Reproduce with the narrowest test** before touching app code:\n   - Unit tests for the sandbox/security logic:\n     ```bash\n     npm test -w insomnia -- src/scripting\n     ```\n     (not `npx vitest run src/scripting -w insomnia` — Vitest's own CLI already owns `-w`/`--watch`, so that form drops into watch mode instead of selecting the `insomnia` workspace.)\n     Relevant files: `packages/insomnia/src/scripting/__tests__/sandbox.test.ts`, `__tests__/script-security-policy.test.ts`, `__tests__/require-interceptor.test.ts`.\n   - SDK object-model unit tests:\n     ```bash\n     npm test -w insomnia-scripting-environment\n     ```\n   - Full app unit suite (if narrowing further isn't obvious):\n     ```bash\n     npm test -w insomnia\n     ```\n3. **If the bug only shows up end-to-end** (real app, real request lifecycle), run the Playwright smoke tests:\n   ```bash\n   npm run test:dev -w insomnia-smoke-test -- --grep \"pre-request\"\n   npm run test:dev -w insomnia-smoke-test -- --grep \"after-response\"\n   ```\n   Relevant specs in `packages/insomnia-smoke-test/tests/smoke/`: `pre-request-script-features.test.ts`, `after-response-script-features.test.ts`, `pre-request-script-window.test.ts`.\n4. **Locate the fix by symptom** (see Notes below for the mapping from error message to file).\n5. **If you change the SDK's public API surface** (new/changed methods on `Request`/`Response`/`Environment`/etc. in `insomnia-scripting-environment`), regenerate autocomplete snippets — CI checks this is committed:\n   ```bash\n   npm run generate:autocomplete -w insomnia-scripting-environment\n   ```\n\n## Notes\n\n- Common failure patterns and where to look:\n  - `SECURITY_POLICY_VIOLATION` / script blocked for using `this`, `globalThis`, `__proto__`, `constructor`, or `import` → AST rule in `packages/insomnia/src/scripting/sandbox.ts` (`checkSandboxViolations`) or the rule lists in `script-security-rules.ts`. These are Electron-app-only; `inso` has no AST sandbox at all.\n  - `no module is found for \"...\"` → the module isn't on the allowlist in `packages/insomnia/src/scripting/require-interceptor.ts`. Currently allowed:\n    - Full-access Node builtins: `path`, `assert`, `url`, `punycode`, `querystring`, `string_decoder`, `stream`, `events`\n    - Method-restricted Node builtins (some methods throw): `timers` (`setImmediate` blocked), `buffer` (`allocUnsafe`/`allocUnsafeSlow` blocked), `util` (`inherits`/`debuglog` blocked)\n    - Shims: `atob`, `btoa`\n    - External/npm modules: `ajv`, `chai`, `cheerio`, `crypto-js`, `csv-parse/lib/sync` (note: not bare `csv-parse`), `lodash` (backed by `es-toolkit/compat`), `moment`, `tv4`, `uuid`, `xml2js`\n    - Special-cased: `insomnia-collection` / `postman-collection` (resolves to the SDK's own `Collection` module)\n\n    Adding a module means adding it here, not just installing the npm package.\n  - `Timeout: Running script took too long` (Electron app) → default 5000ms in `entry.hidden-window.ts:39` (`data.context.timeout`).\n  - `insomnia object is invalid or script returns earlier than expected.` → thrown when the wrapped `AsyncFunction` doesn't resolve to an `InsomniaObject` instance; check both `run-script.ts` and `script-executor.ts` since the same invariant is duplicated in each path.\n  - Hidden window \"closed unexpectedly\" / \"froze\" / busy errors → restart/health-check race conditions in `packages/insomnia/src/main/window-utils.ts` (`hiddenWindowIsBusy`, `createHiddenBrowserWindow`).\n  - Works in unit tests but not in CI → confirm `npm run generate:autocomplete -w insomnia-scripting-environment` output matches the committed `autocomplete-snippets.json`; `test.yml` diffs this file and fails the build if it's stale.\n- The script behavior should be as consistent as possible with [the original implementation](https://www.postmanlabs.com/postman-collection/tutorial-concepts.html). The implementation is hosted in this [repo](https://github.com/postmanlabs/postman-collection).\n- CI workflows relevant to this feature: `.github/workflows/test.yml` (unit tests + autocomplete snippet check, runs on PR/push to develop), `.github/workflows/test-e2e.yml` (Playwright smoke tests, same triggers).\n- Success criteria: the targeted vitest/Playwright command passes, and if the SDK surface changed, the autocomplete snippet diff is clean.\n",".claude/skills/fix-scripting-feature/references/objects/README.md":"# `insomnia-scripting-environment/src/objects` Reference\n\nOne doc per source file in `packages/insomnia-scripting-environment/src/objects/`. Each covers: public API (real signatures), how a script actually reaches it (`pm.*`/`insomnia.*`), and gotchas found while reading the code.\n\n## Core / context\n- [insomnia.md](insomnia.md) — `InsomniaObject`, the top-level `insomnia`/`pm` instance\n- [interfaces.md](interfaces.md) — `RequestContext`, `IEnvironment`\n- [execution.md](execution.md) — `Execution` (`pm.execution`)\n- [request-info.md](request-info.md) — `RequestInfo` (`pm.info`)\n- [async-objects.md](async-objects.md) — `ProxiedPromise` and async-task tracking\n- [interpolator.md](interpolator.md) — `{{ }}` template interpolation\n- [utils.md](utils.md) — misc helpers\n\n## Request / response\n- [request.md](request.md) — `Request`, `RequestBody` (`pm.request`)\n- [response.md](response.md) — `Response` (`pm.response`)\n- [headers.md](headers.md) — `Header`, `HeaderList`\n- [urls.md](urls.md) — `Url`, `QueryParam`, `UrlMatchPattern`\n\n## Environment / variables / collection\n- [environments.md](environments.md) — `Environment`, `Variables` (`pm.environment` / `pm.globals`, variable resolution precedence)\n- [variables.md](variables.md) — `Variable`, `VariableList`\n- [collection.md](collection.md) — re-export barrel only; no `Collection` class or logic — see `environments.md`/`insomnia.md` instead\n- [folders.md](folders.md) — `Folder`, `ParentFolders`\n- [properties.md](properties.md) — `PropertyBase`, `PropertyList` base classes\n\n## Auth / cookies / certs / proxy\n- [auth.md](auth.md) — `RequestAuth` (`pm.request.auth`)\n- [cookies.md](cookies.md) — `Cookie`, `CookieJar`, `CookieList` (`pm.cookies`)\n- [certificates.md](certificates.md) — `Certificate` (`pm.request.certificate`)\n- [proxy-configs.md](proxy-configs.md) — `ProxyConfig`, `ProxyConfigList`\n\n## Test / console / send-request\n- [test.md](test.md) — `pm.test()`/`test()` handler registration\n- [console.md](console.md) — `Console` (script `console.log` capture)\n- [send-request.md](send-request.md) — `pm.sendRequest()`\n\n## Flagged during writing (worth a closer look if you hit related symptoms)\n- `insomnia.settings` always returns `undefined` in scripts (getter unconditionally returns nothing) — see `insomnia.md`.\n- `pm.collectionVariables` and `pm.baseEnvironment` are the same object reference, not a copy — see `insomnia.md`/`environments.md`.\n- Writes to `insomnia.info.*` (`RequestInfo`) are never read back by the script-run merge logic — silently discarded — see `request-info.md`.\n- `pm.sendRequest(url, callback)` resolves (does not reject) on network/parse errors when a callback is passed — only the promise-only form rejects — see `send-request.md`.\n- `Response.dataURI()` has a typo bug: emits `baseg4` instead of `base64` — see `response.md`.\n- `Certificate.update()` doesn't update `disabled`, unlike the constructor — see `certificates.md`.\n- `ProxyConfig` has no `key` property despite `_index = 'key'`, breaking base-class `one()`/`indexOf()`/`upsert()` lookups — see `proxy-configs.md`.\n- `Vault` is read-only from scripts even when `enableVaultInScripts` is set — `set`/`unset`/`clear` always throw — see `environments.md`.\n",".claude/skills/fix-scripting-feature/references/objects/async-objects.md":"# Async plumbing (`ProxiedPromise`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/async-objects.ts`\n\n## Purpose\nInternal plumbing (not part of the `pm`/`insomnia` script-facing API by name) that lets the sandbox\ntrack every `Promise` a user script creates, so it can drain/await fire-and-forget promises before\nhanding control back to the host app. This is what prevents an un-awaited `.then()` chain or a\nstray `setTimeout`-driven promise inside a script from silently continuing (or erroring) after the\nscript has already \"finished\".\n\n## Public API\n\n- `export const OriginalPromise = Promise;` — a stashed reference to the real native `Promise`\n  class, captured at module load before anything replaces the global `Promise`.\n- `export class ProxiedPromise<T> extends Promise<T>` — drop-in `Promise` replacement:\n  - `constructor(executor)` — behaves exactly like `Promise`, and additionally pushes `this` into\n    the module-level `scriptPromises` array if `monitoring` is `true`.\n  - `static override all(promises: Promise<any>[])` — delegates to `super.all(promises)`; pushes\n    the resulting promise into `scriptPromises` if `monitoring`.\n  - `static override allSettled(promises: Promise<any>[])` — delegates to `super.allSettled(promises)` only; does **not** push the result into `scriptPromises` (comment: \"promise will be counted in Promise.resolve\").\n  - `static any(_: Promise<any>[])` — **not actually overridden** (no `override` keyword, does not call `super.any`); always returns `super.reject(\"'super.any' not supported\")`. Comment notes `Promise.any` isn't supported for the ES2021 compile target.\n  - `static override race(promises: Promise<any>[])` — delegates to `super.race(promises)`; pushes the result if `monitoring`.\n  - `static override reject(value: any)` — delegates to `super.reject(value)`; pushes the result if `monitoring`.\n  - `static override resolve<T>(value?: T | PromiseLike<T>)` — delegates to `super.resolve(value)`; pushes the result if `monitoring`.\n  - `static withResolvers()` — always returns `super.reject(\"'Promise.withResolvers' not supported\")`; same ES2021 compile-target limitation noted in a comment.\n- `export const asyncTasksAllSettled = async () => { await Promise.allSettled(scriptPromises); scriptPromises = []; }` — awaits every currently-tracked promise, then clears the tracked list.\n- `export const stopMonitorAsyncTasks = () => { monitoring = false; }` — disables tracking of any *new* promises created after this call.\n- `export const resetAsyncTasks = async () => { scriptPromises = []; monitoring = true; }` — clears the tracked list and re-enables monitoring; called at the start of a fresh script run.\n\nModule-level (shared, not per-instance) state: `let monitoring = true;` and\n`let scriptPromises = new Array<Promise<any>>();`. Importing this module also calls\n`resetTestPromises()` (from `./test`) once, as a side effect at load time.\n\n## Script-facing surface\nNone of these names are called directly by a user script. However, the mechanism *is* in the\nscript-facing surface indirectly: `packages/insomnia/src/entry.hidden-window-preload.ts` replaces\nthe sandbox's global `Promise` with `ProxiedPromise`\n(`contextBridge.exposeInMainWorld('Promise', ProxiedPromise); window.Promise = ProxiedPromise;`),\nso `new Promise(...)`, `Promise.resolve(...)`, `Promise.all(...)`, etc. — anything that references\nthe (now-reassigned) global `Promise` binding — is transparently tracked. **`async function` is not\nin that list**: per spec, an async function's returned promise is created against the realm's\nintrinsic `%Promise%` (`NewPromiseCapability(%Promise%)`), not whatever the mutable global `Promise`\nbinding currently points to, so reassigning `window.Promise` does not make it a `ProxiedPromise`.\n`packages/insomnia/src/scripting/run-script.ts` injects\n`resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the executed script function\nas `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__`, calling `__bridgeReset__()` before the\nuser script body runs and `await __bridgeSettle__()` after it, right before returning the mutated\n`insomnia` object. This drains explicitly-tracked promises, but an un-awaited `pm.sendRequest(...)`\ncall or stray `.then()` chain is only guaranteed to resolve before the script's result is used if\nevery promise in its chain was created via a tracked constructor/helper — not guaranteed just\nbecause it originated from an `async function`.\n\n## Gotchas / notable behavior\n- `Promise.any(...)` and `Promise.withResolvers()` are **explicitly unsupported**: calling either\n  from a script returns a promise that rejects with the literal string `\"'super.any' not supported\"`\n  or `\"'Promise.withResolvers' not supported\"` (not a real `Error` object) — worth checking for if a\n  script's failure message looks like that.\n- `allSettled` results are deliberately *not* double-counted into `scriptPromises` — the comment\n  claims this is safe because \"promise will be counted in Promise.resolve\" (i.e. the engine's\n  internal `allSettled` implementation is assumed to route through `Promise.resolve` on the inputs,\n  which are already tracked). This is an implicit coupling to Promise/A+ implementation details.\n- `scriptPromises`/`monitoring` are **module-level singletons**, not scoped per script execution —\n  correctness across sequential script runs depends on `resetAsyncTasks()` (`__bridgeReset__`)\n  being called at the start of every run; if it's ever skipped, promises from a previous run could\n  leak into the next run's settle-and-drain step.\n- Constructing a promise while `monitoring === false` silently skips tracking — no error or warning is raised.\n- Promises returned by an `async function` are **not guaranteed to be added to `scriptPromises`**,\n  even though `window.Promise`/`Promise` was replaced with `ProxiedPromise` — the JS engine builds\n  an async function's result promise from the realm's intrinsic `%Promise%`, bypassing the\n  reassigned global binding entirely. `send-request.ts`'s exported `sendRequest` is itself an\n  `async function` that internally does `return new Promise(async (resolve, reject) => {...})`; the\n  inner explicit `new Promise(...)` is tracked, but the promise actually returned to the caller of\n  `sendRequest(...)` (i.e. what `pm.sendRequest()` hands back to a script when not passed a callback)\n  is not. Symptom: a script that fires `pm.sendRequest(...)` without `await`ing it (or awaiting a\n  `.then()` derived from it) may see the request silently abandoned or racing past\n  `asyncTasksAllSettled()`'s drain, instead of being reliably completed first.\n\n## Related\n- `test.ts` — `resetTestPromises()` is called once at this module's import time; test assertion promises are tracked separately from `scriptPromises`.\n- `packages/insomnia/src/scripting/run-script.ts` — wires `resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the sandboxed script's `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__` calls.\n- `packages/insomnia/src/entry.hidden-window-preload.ts` — replaces the sandbox's global `Promise` with `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` — prepares the broader sandbox execution context that this async tracking runs inside.\n  ",".claude/skills/fix-scripting-feature/references/objects/auth.md":"# RequestAuth\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/auth.ts`\n\n## Purpose\nModels the authentication settings attached to a request (`pm.request.auth`). It stores auth options per-type in a map (so switching `type` doesn't destroy other types' saved options), and provides the two-way transform functions used to convert between Insomnia's native `RequestAuthentication` model and the script-facing `AuthOptions` shape. `RequestAuth` extends `Property` (see `properties.ts`).\n\n## Public API\n\n### Types\n- `type AuthOptionTypes = 'noauth' | 'basic' | 'bearer' | 'jwt' | 'digest' | 'oauth1' | 'oauth2' | 'hawk' | 'awsv4' | 'ntlm' | 'apikey' | 'edgegrid' | 'asap' | 'netrc'` — the full set of auth type strings the SDK models are aware of, but not all are reachable from scripts (see Gotchas).\n- `const AuthTypes: Set<string>` — a `Set` containing the same string values as `AuthOptionTypes`, used by `RequestAuth.isValidType`.\n- `interface AuthOption { key: string; value: string; type?: string }` — a single key/value auth parameter (e.g. `{ key: 'username', value: 'user1' }`).\n- `interface OAuth2AuthOption { key: string; value: string | OAuth2Param[]; type?: string }` — like `AuthOption` but OAuth2 params can themselves be arrays of `OAuth2Param` (used for nested `tokenRequestParams` etc.).\n- `interface OAuth2Param { key: string; value: string; enabled: boolean; send_as: string }`.\n- Per-auth-type option interfaces (shape of the fields you'd set for `.use()`/`.update()` when constructing raw options rather than key/value arrays): `BasicOptions`, `BearerOptions`, `JWTOptions`, `DigestOptions`, `OAuth1Options`, `OAuth2Options`, `HAWKOptions`, `AWSV4Options`, `NTLMOptions`, `APIKeyOptions`, `EdgegridOptions`, `ASAPOptions`. Each mirrors the field names used by the corresponding Insomnia auth UI (e.g. `OAuth1Options` has `consumerKey`, `consumerSecret`, `signatureMethod`, etc.) and all have an optional `id?: string`.\n- `interface AuthOptions { type: AuthOptionTypes; basic?: AuthOption[]; bearer?: AuthOption[]; jwt?: AuthOption[]; digest?: AuthOption[]; oauth1?: AuthOption[]; oauth2?: OAuth2AuthOption[]; hawk?: AuthOption[]; awsv4?: AuthOption[]; ntlm?: AuthOption[]; apikey?: AuthOption[]; edgegrid?: AuthOption[]; asap?: AuthOption[] }` — the JSON shape used to construct a `RequestAuth` and returned by `toJSON()`/`toPreRequestAuth()`. Each type's key holds an array of `AuthOption` (or `OAuth2AuthOption` for oauth2).\n\n### Functions\n- `authOptionsToParams(authMethod: BasicOptions | BearerOptions | ... | ASAPOptions)` — converts a flat options object (e.g. `{ username, password }`) into an array of `{ type: 'any', key, value }` entries. Not used internally by `RequestAuth`; appears to be a helper for callers building `AuthOption[]` from a typed options object.\n- `fromPreRequestAuth(auth: RequestAuth): RequestAuthentication` — converts a script-side `RequestAuth` into Insomnia's native `RequestAuthentication` model (used when a pre-request script finishes and Insomnia needs to write the mutated auth back onto the real request). Calls `auth.toJSON()` then switches on `type`.\n- `toPreRequestAuth(auth: RequestAuthentication | {}): AuthOptions` — the reverse transform: converts Insomnia's native auth model into the `AuthOptions` shape used to construct the script's `RequestAuth` (called when initializing `pm.request.auth` before a script runs).\n\n### `class RequestAuth extends Property`\n- `constructor(options: AuthOptions, parent?: Property)` — throws `Error('invalid auth type ${options.type}')` if `options.type` isn't a recognized type. Populates an internal `Map<string, VariableList<Variable>>` (`authOptions`) with one entry per auth-type key present in `options` (so if you pass an `AuthOptions` with only `type: 'basic'` and `basic: [...]`, only the `'basic'` entry is populated — other types are simply absent from the map, not defaulted).\n- `static isValidType(authType: string): boolean` — checks membership in `AuthTypes`.\n- `clear(type: string): void` — removes the stored options for `type` from the internal map (no-op, silently, if `type` is invalid).\n- `parameters(): VariableList<Variable> | undefined` — returns the `VariableList` of options for the *currently active* `type` (i.e. whatever `this.type` is set to), or `undefined` if nothing is stored for that type.\n- `toJSON(): AuthOptions` — serializes back to the `AuthOptions` shape. For `type === 'noauth'` or `'netrc'` it returns just `{ type }` with no options array. Otherwise returns `{ type, [type]: <array of {key, value} from the VariableList> }`.\n- `update(options: VariableList<Variable> | Variable[] | AuthOptions, type?: AuthOptionTypes): void` — replaces the options for `type` (or the current `this.type` if `type` omitted) and switches `this.type` to it. Throws `Error('no valid RequestAuth options is found')` if no variable list could be derived from `options`.\n- `use(type: AuthOptionTypes, options: VariableList<Variable> | Variable[] | AuthOptions): void` — same as `update` but `type` is required; throws `Error('invalid type (...)')` if `type` isn't in `AuthTypes`, and the same \"no valid options\" error as `update` otherwise. This is the method `Request.authorizeUsing()` calls to switch a request's auth type from a script.\n\n### Internal helper (not exported)\n- `rawOptionsToVariables(options, targetType?)` — normalizes the three accepted input shapes (`VariableList<Variable>`, `Variable[]`, or a full `AuthOptions` object) into `VariableList<Variable>[]`. Throws `Error('options is not valid: it must be VariableList<Variable> | Variable[] | object')` if none of the shapes match.\n\n## Script-facing surface\n- `pm.request.auth` is a `RequestAuth` instance (constructed in `request.ts` from `options.auth || { type: 'noauth' }`).\n- `pm.request.auth.parameters()` — read the currently active auth's key/value options.\n- `pm.request.auth.update(newOptions)` — mutate the current auth type's options in place.\n- `pm.request.authorizeUsing(type, options)` (on `Request`, in `request.ts`) delegates to `this.auth.use(type, options || { type: 'noauth' })` — this is the documented way scripts switch auth type.\n- `pm.request.auth.clear(type)` to drop a stored auth-type's options.\n- When a pre-request script finishes, `fromPreRequestAuth(request.auth)` converts the mutated `RequestAuth` back to Insomnia's native `RequestAuthentication`, which is what actually gets sent on the wire (see `request.ts`, `authentication: fromPreRequestAuth(updatedReq.auth)`).\n\n## Gotchas / notable behavior\n- **`netrc` is a dead end**: `AuthTypes` includes `'netrc'` and `RequestAuth` will happily accept `type: 'netrc'`, but `fromPreRequestAuth` throws `Error('netrc is not supported yet')` when it sees `type === 'netrc'`, and `toPreRequestAuth` throws `Error('netrc auth is not supported in scripting yet')` for the native `'netrc'` type. So a script that sets `type: 'netrc'` will fail only when Insomnia tries to convert it back, not immediately.\n- **`singleToken` is unsupported**: `toPreRequestAuth` throws for native auth type `'singleToken'` — there is no path from this native auth type into the script's `RequestAuth` at all.\n- **`edgegrid` has interfaces but no transform**: `EdgegridOptions` and `'edgegrid'` are defined in `AuthOptionTypes`/`AuthTypes`, but neither `fromPreRequestAuth` nor `toPreRequestAuth` has a `case 'edgegrid'` — passing this type through those functions falls into the `default` branch and throws `Error('unknown auth type: ...')`.\n- **oauth1 `signatureMethod` restrictions**: `fromPreRequestAuth`'s oauth1 branch only supports `HMAC-SHA1`, `HMAC-SHA256`, `RSA-SHA1`, and `PLAINTEXT`. `HMAC-SHA512`, `RSA-SHA256`, and `RSA-SHA512` are recognized strings but explicitly throw `Error('...unsupported signatureMethod type for oauth1: ...')`.\n- **oauth1 `privateKey` is a one-way/unsupported field**: comment in code says \"it is not supported in the script side\" — it's read but not written back into the native auth model in `toPreRequestAuth`.\n- **oauth2 has several fields marked \"not supported yet in the script side\"**: `tokenPrefix`, `responseType`, `origin` are passed through in `fromPreRequestAuth`/`toPreRequestAuth` for round-tripping but are not meaningfully used elsewhere per the inline comments.\n- **hawk loses several fields on the round trip to native Insomnia auth**: `timestamp`, `delegation`, `app`, `nonce`, `user` are commented as \"some keys are lost here\" in `fromPreRequestAuth`'s hawk branch, and `toPreRequestAuth`'s hawk branch hardcodes them back to empty string / `'false'` since \"these fields are not supported in Insomnia side\".\n- **Booleans are stored as strings internally**: all the `AuthOption`/`OAuth2AuthOption` values are `string`, so booleans like `disabled` are represented as `'true'`/`'false'` strings inside the `VariableList`, and compared with `=== 'true'` when converting back.\n- **Constructor does not default missing types**: if you construct `new RequestAuth({ type: 'bearer' })` (no `bearer` key), `parameters()` will return `undefined` immediately — there's no implicit empty array.\n- **`update`/`use` fully replace, not merge**: calling `update()`/`use()` replaces the entire options list for a type; it does not merge in partial fields.\n\n## Related\n- `properties.ts` — `Property` (base class) and `Variable`/`VariableList` container semantics used to store auth options.\n- `variables.ts` — `Variable`, `VariableList` (the underlying storage for each auth type's key/value pairs).\n- `request.ts` — constructs `pm.request.auth` from `AuthOptions`, exposes `authorizeUsing()`, and calls `fromPreRequestAuth` when finalizing the request.\n- `insomnia.ts` — calls `toPreRequestAuth(rawObj.request.authentication)` to seed `pm.request.auth` before a script runs.\n- `insomnia-data` package — source of the native `RequestAuthentication` and `OAuth2ResponseType` types that `fromPreRequestAuth`/`toPreRequestAuth` convert to/from.\n",".claude/skills/fix-scripting-feature/references/objects/certificates.md":"# Certificate\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/certificates.ts`\n\n## Purpose\nModels a single client (mTLS) certificate for the scripting environment — the script-facing representation surfaced as `pm.request.certificate`. It is deliberately simple: unlike Insomnia's native model (which supports a list of client certificates matched by host), the scripting SDK only ever models **one** certificate per request, matched via `UrlMatchPattern`s (from `urls.ts`).\n\n## Public API\n\n### Types\n- `interface SrcRef { src: string }` — a file-path reference; used for `key`, `cert`, and `pfx` since certificate content is loaded from disk, not embedded inline.\n- `interface CertificateOptions { name?: string; matches?: string[]; key?: SrcRef; cert?: SrcRef; passphrase?: string; pfx?: SrcRef; disabled?: boolean }` — constructor/`update()` input. `pfx` is documented as \"PFX or PKCS12 Certificate\".\n\n### `class Certificate extends Property`\n- `override _kind = 'Certificate'`\n- `override name?: string`\n- `matches?: UrlMatchPatternList<UrlMatchPattern>` — constructed internally from the `matches: string[]` option; each string becomes a `UrlMatchPattern`.\n- `key?: SrcRef`\n- `cert?: SrcRef`\n- `passphrase?: string`\n- `pfx?: SrcRef`\n- `constructor(options: CertificateOptions)` — sets all fields directly from `options`, including `this.disabled = options.disabled` (inherited `Property.disabled` field). `matches` defaults to an empty `UrlMatchPatternList` if `options.matches` is not provided.\n- `static isCertificate(obj: object): boolean` — checks `obj._kind === 'Certificate'`.\n- `canApplyTo(url: string): boolean` — returns `this.matches ? this.matches.test(url) : false`, i.e. whether any of the certificate's match patterns matches the given URL. Delegates to `UrlMatchPatternList.test()`.\n- `update(options: CertificateOptions): void` — fully overwrites `name`, `matches` (rebuilt the same way as in the constructor), `key`, `cert`, `passphrase`, `pfx`. Note: **does not** update `disabled` (unlike the constructor, which does set it) — see Gotchas.\n\n## Script-facing surface\n- `pm.request.certificate` is a `Certificate` instance. Per `insomnia.ts`, it's initialized either as an empty placeholder certificate (`name: 'Default Certificate'`, no `key`/`cert`/`pfx`) when the request URL contains unrendered template tags or `filterClientCertificates` returns zero matches, or as `{ name: 'The first matched certificate from Settings', matches: [matchedCertificates[0].host], key: {src: ...}, cert: {src: ...}, passphrase, pfx: {src: ...} }` — built from `matchedCertificates[0]` whenever the match list is **non-empty**, i.e. one or more matches, not only when there's exactly one. If more than one client certificate matches, everything past the first is silently dropped rather than surfaced as the empty-certificate path — a direct consequence of `Certificate` only ever modeling a single certificate (see Gotchas below).\n- Scripts can read `pm.request.certificate.key`, `.cert`, `.pfx`, `.passphrase`, `.name`, call `.canApplyTo(url)`, or call `.update({...})` to replace the certificate used for the request. Per `request.ts`, `Request.certificate` is a `Certificate` built the same way (`options.certificate ? new Certificate(options.certificate) : undefined`), and the certificate is serialized back out (`toJSON()`-style plain object with `name`, `matches` stringified, `key`, `cert`, `passphrase`, `pfx`) when the script finishes so Insomnia can merge it into the request's actual client-certificate list via `mergeClientCertificates` (in `request.ts`).\n\n## Gotchas / notable behavior\n- **Only one certificate is modeled, even though Insomnia supports several.** `request.ts`'s comment: \"Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones.\" When a script sets a certificate, `mergeClientCertificates` prepends it as a new entry ahead of the request's existing native client certificates rather than replacing the whole list.\n- **`update()` does not update `disabled`.** The constructor sets `this.disabled = options.disabled`, but `update()` has no such line — calling `cert.update({ disabled: true, ... })` will not actually change `cert.disabled`. This looks like an inconsistency/bug relative to the constructor's behavior.\n- **`cert+key` and `pfx` are mutually exclusive downstream.** `request.ts`'s `mergeClientCertificates` throws `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if a script sets both a PFX and a cert/key pair on `pm.request.certificate`.\n- **Empty/placeholder certificate is normal, not an error state.** If the request URL contains a template tag (e.g. `{{ baseUrl }}`) or there's no matching client certificate configured in Settings, `pm.request.certificate` is initialized to an empty certificate and `insomnia.ts` logs this via `getExistingConsole().warn(...)` — a warning-level timeline entry, not debug — so it's visible when tracing why the default/empty certificate was selected. Scripts should not assume `key`/`cert`/`pfx` are populated.\n- **`matches` is derived only from certificate `host`, singular** — even though `UrlMatchPatternList` supports multiple patterns, `insomnia.ts` only ever seeds `matches: [matchedCertificates[0].host]` (a single-element array) from the native model.\n\n## Related\n- `properties.ts` — `Property` base class (provides `id`, `name`, `disabled`, `toJSON()`/`toString()` defaults).\n- `urls.ts` — `UrlMatchPattern`, `UrlMatchPatternList` (used for `matches` and `canApplyTo()`).\n- `request.ts` — constructs/serializes `pm.request.certificate`, and contains `mergeClientCertificates()` which reconciles the script's single certificate back into Insomnia's native multi-certificate list.\n- `insomnia.ts` — seeds the initial `pm.request.certificate` value from Insomnia Settings' configured client certificates, using `filterClientCertificates` from `insomnia/src/network/certificate`.\n- `insomnia-data` package — source of the native `ClientCertificate` type.\n",".claude/skills/fix-scripting-feature/references/objects/collection.md":"# collection.ts (re-export barrel — no `Collection` class)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/collection.ts`\n\n## Purpose\n**This file does not define a `Collection` class, and has no logic of its own.** Its entire content is a JSDoc `@module` comment plus eleven `export { ... } from '...'` re-export statements:\n```ts\nexport { RequestAuth } from './auth';\nexport { Certificate } from './certificates';\nexport { Cookie, CookieList } from './cookies';\nexport { Header, HeaderList } from './headers';\nexport { Property, PropertyBase, PropertyList } from './properties';\nexport { ProxyConfig, ProxyConfigList } from './proxy-configs';\nexport { FormParam, Request, RequestBody } from './request';\nexport { Response } from './response';\nexport { QueryParam, Url, UrlMatchPattern, UrlMatchPatternList } from './urls';\nexport { Variable, VariableList } from './variables';\nexport { Folder } from './folders';\n```\nIt exists purely so that `index.ts` can do `export * as Collection from './collection'`, giving SDK/type consumers a `Collection.Property`, `Collection.Variable`, `Collection.Folder`, etc. namespace (mirroring the Postman `postman-collection` package's `Collection.*` type namespace for compatibility/familiarity). There is no runtime behavior, no constructor, and nothing related to collection-scoped *variable storage* in this file.\n\n## Public API\nNo classes/functions of its own. It re-exports (unchanged) the following, whose real definitions and docs live in their own files:\n- `RequestAuth` (`auth.ts`)\n- `Certificate` (`certificates.ts`)\n- `Cookie`, `CookieList` (`cookies.ts`)\n- `Header`, `HeaderList` (`headers.ts`)\n- `Property`, `PropertyBase`, `PropertyList` (`properties.ts` — see `properties.md`)\n- `ProxyConfig`, `ProxyConfigList` (`proxy-configs.ts`)\n- `FormParam`, `Request`, `RequestBody` (`request.ts`)\n- `Response` (`response.ts`)\n- `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` (`urls.ts`)\n- `Variable`, `VariableList` (`variables.ts` — see `variables.md`)\n- `Folder` (`folders.ts` — see `folders.md`)\n\nNote this list is nearly identical to `index.ts`'s own top-level JSDoc module comment (both files carry the same doc block), but `index.ts` itself re-exports far more (all of `environments.ts`, `insomnia.ts`, `test.ts`, `execution.ts`, etc.) in addition to wrapping this file as the `Collection` namespace.\n\n## Script-facing surface\nNo direct script-facing surface of its own. **If you are looking for how `pm.collectionVariables` actually works** (collection-level variable storage and resolution), that logic lives in:\n- `environments.ts` — the `Environment` class backing the actual store.\n- `insomnia.ts` — `InsomniaObject` explicitly maps `this.collectionVariables = this.baseEnvironment` (i.e. `pm.collectionVariables` **is** `pm.baseEnvironment`, the same `Environment` instance, not a separate object) and constructs the `Variables` hierarchy (`collectionVars: baseEnvironment`) used by `pm.variables`.\nSee `environments.md` for the full precedence/resolution behavior.\n\n## Gotchas / notable behavior\n- **Naming trap**: a file named `collection.ts` in a scripting/variable-resolution context strongly suggests a `Collection` class with collection-variable logic — it has none. Anyone debugging \"collection variables\" behavior should look at `environments.ts` (`Environment`) and `insomnia.ts` (`collectionVariables = baseEnvironment` aliasing), not this file.\n- Diffed byte-for-byte against `index.ts`'s doc comment: the JSDoc header text is duplicated between the two files, but the actual export lists differ — `collection.ts` only re-exports a subset (the \"Postman-collection-shaped\" types), while `index.ts` re-exports everything including `insomnia.ts`, `environments.ts`, `console.ts`, `execution.ts`, `test.ts`, `async-objects.ts`, `request-info.ts`.\n- `index.ts` wraps this file's exports under a namespace: `export * as Collection from './collection';` — so external consumers see `Collection.Variable`, `Collection.Folder`, etc., but that `Collection` namespace object is not related to `pm.collectionVariables` at runtime; it's purely a type/value re-export grouping.\n\n## Related\n- `index.ts` — re-exports this file's contents as the `Collection` namespace (`export * as Collection from './collection'`).\n- `environments.ts` — actual implementation backing `pm.collectionVariables`/`pm.baseEnvironment` (see `environments.md`).\n- `insomnia.ts` — wires `collectionVariables` to `baseEnvironment` and builds the `Variables` hierarchy.\n- `properties.ts`, `variables.ts`, `folders.ts`, `urls.ts`, `request.ts`, `response.ts`, `cookies.ts`, `headers.ts`, `auth.ts`, `certificates.ts`, `proxy-configs.ts` — the actual modules whose types this file re-exports.\n",".claude/skills/fix-scripting-feature/references/objects/console.md":"# Console\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/console.ts`\n\n## Purpose\nImplements a sandbox-safe replacement for the global `console` object used inside pre-request/after-response/test scripts. Instead of writing to a real stdout/devtools console, every call is captured into an in-memory row buffer that the execution harness later dumps and surfaces back to the Insomnia app (e.g. the response timeline).\n\n## Public API\n\n### `type LogLevel`\n```ts\ntype LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error';\n```\nNot exported — internal tag for which console method produced a row.\n\n### `interface Row`\n```ts\nexport interface Row {\n  value: string;\n  name: string;\n  timestamp: number;\n}\n```\n- `value` — the rendered `\"<level>: <content>\"` string.\n- `name` — always `'Text'` in current usage.\n- `timestamp` — `Date.now()` at log time.\n\n### `class Console`\nNo explicit constructor (uses field initializers); instantiated with `new Console()`.\n\n- `rows: Row[]` — accumulated log entries, in call order.\n- `printLog = (rows: Row[], level: LogLevel, ...values: any) => void` — core formatter (marked `@ignore`, not really meant to be called directly by users). Maps each value to a string (`typeof value === 'string' ? value : JSON.stringify(value, null, 2)`), joins with `' '`, and pushes `{ value: `${level}: ${content}`, name: 'Text', timestamp: Date.now() }` onto `rows`. If formatting throws, it instead pushes a row whose `value` is `'error: ' + JSON.stringify(e, null, 2)`.\n- `log = (...values: any[]) => void` — calls `printLog(this.rows, 'log', ...values)`.\n- `warn = (...values: any[]) => void` — calls `printLog(this.rows, 'warn', ...values)`.\n- `debug = (...values: any[]) => void` — calls `printLog(this.rows, 'debug', ...values)`.\n- `info = (...values: any[]) => void` — calls `printLog(this.rows, 'info', ...values)`.\n- `error = (...values: any[]) => void` — calls `printLog(this.rows, 'error', ...values)`.\n- `clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => void` — **always throws** `new Error('currently \"clear\" is not supported for the timeline')`. Calling `console.clear()` in a script will throw.\n- `dumpLogs = () => string` — returns all rows serialized as `JSON.stringify(row) + '\\n'`, joined with `'\\n'` (i.e. one JSON blob per row, double-newline separated).\n- `dumpLogsAsArray = () => string[]` — same per-row serialization (`JSON.stringify(row) + '\\n'`) but returned as an array instead of one joined string.\n\n### Module-level singleton helpers\n- `let builtInConsole = new Console();` (module-private).\n- `getExistingConsole(): Console` — returns the current singleton without creating a new one.\n- `getNewConsole(): Console` — replaces the singleton with a fresh `new Console()` and returns it (used to reset log state between script executions).\n\n## Script-facing surface\n- `console.log(...)`, `console.warn(...)`, `console.debug(...)`, `console.info(...)`, `console.error(...)` — available as the script's global `console` inside pre-request/after-response/test scripts (the sandbox injects a `Console` instance as the `console` parameter, not the real JS console).\n- `console.clear()` — present but always throws if called from a script.\n\n## Gotchas / notable behavior\n- **`clear()` is a hard error, not a no-op.** Any script calling `console.clear()` will throw `Error('currently \"clear\" is not supported for the timeline')`, which will surface as a script failure.\n- **Object logging uses `JSON.stringify(value, null, 2)`**, not `util.inspect`-style formatting — circular references or values with `toJSON()` quirks will affect what shows up. If `JSON.stringify` itself throws (e.g. circular reference), the whole `printLog` call falls back to a single `'error: ' + JSON.stringify(e, null, 2)` row rather than losing the log entirely — but the original values are lost in that fallback.\n- **No format-string substitution (`%s`, `%d`, etc.)** — the `// TODO: support replacing substitution` comment confirms this isn't implemented; values are just stringified and space-joined.\n- **State is a module-level singleton**, not per-instance-only: `getExistingConsole()`/`getNewConsole()` share one `builtInConsole` variable across the module. The harness must call `getNewConsole()` before each script run to avoid leaking log rows from a previous execution into the next one's dump.\n- **Two different dump shapes exist** (`dumpLogs()` joined string vs. `dumpLogsAsArray()` array of strings) — different call sites in the harness use one or the other (see Related), so a log line \"disappearing\" could be a symptom of reading from the wrong dump method or a stale singleton reference.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — imports `getExistingConsole` and uses it only for a one-off warning log during `initInsomniaObject` (an empty-certificate warning); it is not the general per-script logging path.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) — calls `getNewConsole()` to obtain a fresh `Console` instance for each script run and passes `scriptConsole.log` into `initInsomniaObject(sandboxContext, scriptConsole.log)`.\n- `packages/insomnia/src/scripting/run-script.ts` — injects the `Console` instance itself as the sandboxed script's `console` global (part of the generated function's parameter list), and at the end calls `scriptConsole.dumpLogsAsArray()`, including the result (`logs`) in the returned `RequestContext`.\n- Legacy path `packages/insomnia/src/script-executor.ts` — calls `scriptConsole.dumpLogs()` (joined-string form) and appends it to a timeline file via `fs.promises.appendFile`, which is how logs end up in Insomnia's response timeline view in that code path.\n",".claude/skills/fix-scripting-feature/references/objects/cookies.md":"# Cookie, CookieJar, CookieList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/cookies.ts`\n\n## Purpose\nModels HTTP cookies for the scripting environment. `Cookie` wraps a `tough-cookie` cookie plus Insomnia-specific extension fields; `CookieList` is a keyed `PropertyList<Cookie>`; `CookieObject` (a `CookieList` subclass) is the concrete class backing `pm.cookies` and pairs the list with a `CookieJar`; `CookieJar` is a custom (non-tough-cookie) jar keyed by domain used for `pm.cookies.jar()`. `mergeCookieJar` reconciles a script-produced jar back into Insomnia's native `CookieJar` model.\n\n## Public API\n\n### Types\n- `interface InsomniaCookieExtensions { creation?: Date; creationIndex?: number; lastAccessed?: Date; pathIsDefault?: boolean }` — Insomnia-only metadata carried alongside the standard cookie fields.\n- `interface CookieOptions extends InsomniaCookieExtensions { id?: string; key: string; value: string; expires?: Date | string | null; maxAge?: number | 'Infinity' | '-Infinity'; domain?: string; path?: string; secure?: boolean; httpOnly?: boolean; hostOnly?: boolean; session?: boolean; extensions?: { key: string; value: string }[] }` — constructor input for `Cookie`.\n\n### `class Cookie extends Property`\n- `constructor(cookieDef: CookieOptions | string)` — if given a string, parses it via `Cookie.parse()` first (throws `Error('failed to parse cookie, the cookie string seems invalid')` if parsing fails or the tough-cookie construction fails). Wraps the result in a `tough-cookie` `Cookie` (`ToughCookie.fromJSON`). `extensions` are stored separately from the tough-cookie object (tough-cookie only handles `string[]` extensions natively).\n- `static override _index = 'key'` — `PropertyList.one()`/`indexOf()` look items up by `key` for cookie lists.\n- `static isCookie(obj: Property): boolean` — checks `obj._kind === 'Cookie'`.\n- `static parse(cookieStr: string): CookieOptions` — parses a `Set-Cookie`-style string (loose mode) via tough-cookie, pulls `HostOnly`/`Session` out of the extensions list into dedicated booleans, and converts remaining string extensions (`key=value` or bare flags, which become `{key, value: 'true'}`) into `{key, value}` objects. Throws if the string can't be parsed by tough-cookie.\n- `static stringify(cookie: Cookie): string` — alias for `cookie.toString()`.\n- `static unparseSingle(cookieOpt: CookieOptions): string` — constructs a `Cookie` from options and returns its string form.\n- `static unparse(cookies: Cookie[]): string` — joins each cookie's `toString()` with `'; '`.\n- `toString(): string` — tough-cookie's own `toString()` plus `; HostOnly`, `; Session`, and `; key=value` segments appended for any extensions/inso flags present.\n- `valueOf(): string` — returns just `cookie.value` (used by `PropertyList.one()` for lookups and by `toObject()`).\n- `get key(): string` — reads through to `this.cookie.toJSON().key`.\n- `toJSON()` — returns the full plain-object shape: `{ id, key, value, expires, maxAge, domain, path, secure, httpOnly, hostOnly, session, extensions, creation, creationIndex, lastAccessed, pathIsDefault }`. Note `expires` is coerced to `undefined` when tough-cookie reports `'Infinity'`.\n\n### `class CookieList extends PropertyList<Cookie>`\n- `constructor(cookies: Cookie[])` — always constructs with `typeClass = Cookie`, no `parent`.\n- `static isCookieList(obj: object): boolean`.\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, string | string[]>` — builds a plain `key -> value` map via `Object.create(null)` (so no prototype — dangerous keys like `__proto__`/`constructor` land as own properties, not pollute the prototype). `excludeDisabled` skips cookies where `cookie.disabled` is true. `multiValue` collects same-key cookies into an array instead of overwriting (last value wins when `multiValue` is falsy).\n\n### `class CookieObject extends CookieList`\nThis is the concrete class instantiated as `pm.cookies`.\n- `constructor(cookieJar: InsomniaCookieJar | null)` — maps each native Insomnia cookie into a script `Cookie` (translating numeric `expires` into a `Date`, and passing through `maxAge: undefined`/`session: undefined`/`extensions: undefined` since those fields aren't tracked by Insomnia's native cookie model). Also builds an internal `CookieJar` (named from `cookieJar.name`, or `''`/empty if `cookieJar` is `null`).\n- `jar(): CookieJar` — returns the internal `CookieJar` instance backing `pm.cookies.jar()`.\n\n### `class CookieJar` (custom, not `tough-cookie`'s jar)\nComment: \"CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain as it reads location from the browser window, it is 'localhost'\". Internally a `Map<domain, Map<cookieKey, Cookie>>`.\n- `constructor(jarName: string, cookies?: Cookie[])` — indexes each cookie by its `domain` (read via `cookie.toJSON().domain`). If a cookie has no `domain`, it's **dropped** with a console warning: `` `domain is not specified for the cookie \"${cookie.key}\" so it is omitted` `` (via `getExistingConsole().warn`).\n- `set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void): void` — `url` is actually used as the domain key (not parsed as a URL). If `value` is a plain string, constructs a minimal `Cookie({ key, value, domain: url })`; otherwise treats `value` as full `CookieOptions` and constructs `new Cookie(value)`. Always calls `cb(undefined, cookie)` — this implementation never produces an error.\n- `get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void): void` — looks up `name` within the `url` domain bucket; calls back with `undefined` cookie if not found (not an error).\n- `getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void): void` — returns all cookies for that domain bucket (empty array if none).\n- `unset(url: string, name: string, cb: (error?: Error | null) => void): void` — deletes `name` from the domain bucket if the bucket exists; always calls back with no error.\n- `clear(url: string, cb: (error?: Error | null) => void): void` — deletes the entire domain bucket.\n- `toInsomniaCookieJar(): { name: string; cookies: Partial<InsomniaCookie>[] }` — flattens the map back into Insomnia's native cookie-jar shape. `expires` is defaulted back to `'Infinity'` if falsy (\"avoid edge cases\").\n\n### Module-level function\n- `mergeCookieJar(originalCookieJar: InsomniaCookieJar, updatedCookieJar: { name: string; cookies: Partial<InsomniaCookie>[] }): InsomniaCookieJar` — assigns a fresh `uuidv4()` id to any cookie missing one (mirroring the id-generation approach in Insomnia's `cookie-list.tsx`), and returns `{ ...originalCookieJar, cookies: cookiesWithId }`. This is the function that reconciles a script's mutated jar back into Insomnia's persisted model after a script runs.\n\n## Script-facing surface\n- `pm.cookies` is a `CookieObject` (constructed in `insomnia.ts` via `new CookieObject(rawObj.cookieJar)`), so scripts get all `CookieList`/`PropertyList` methods directly: `pm.cookies.get(key)` / `pm.cookies.one(key)` (via inherited `PropertyList.get`/`one`, which for `Cookie` returns `cookie.valueOf()` — i.e. just the string value, not the `Cookie` object, because `Cookie.valueOf` is defined), `pm.cookies.toObject()`, `pm.cookies.all()`, `pm.cookies.count()`, `pm.cookies.each()`, `pm.cookies.add()`, etc.\n- `pm.cookies.jar()` returns the `CookieJar`, giving access to `set(url, key, value, cb)`, `get(url, name, cb)`, `getAll(url, cb)`, `unset(url, name, cb)`, `clear(url, cb)` — all Postman-style callback-based cookie-jar operations, scoped by domain (the `url` argument is treated as a plain domain string, not parsed).\n- After a script runs, Insomnia calls `this.cookies.jar().toInsomniaCookieJar()` (see `insomnia.ts`) to get the jar back out, which is then merged via `mergeCookieJar`.\n\n## Gotchas / notable behavior\n- **`PropertyList.one()`/`.get()` return the raw value, not a `Cookie`**: because `Cookie` defines `valueOf()`, and `PropertyList.one()` special-cases items with a `valueOf` method — `pm.cookies.get('foo')` returns the cookie's string value, not the `Cookie` instance. To get the full object, use `pm.cookies.find(...)` or `pm.cookies.all()` instead.\n- **`toObject()` uses `Object.create(null)`** specifically to avoid prototype pollution when cookie keys are things like `__proto__` or `constructor` — confirmed by a dedicated test (`toObject does not pollute the prototype for dangerous cookie keys`).\n- **Cookies without a `domain` are silently dropped** when building a `CookieJar` (constructor only; a warning is logged, not thrown).\n- **`maxAge` and `session` are not persisted from Insomnia's native model**: `CookieObject`'s constructor always sets `maxAge: undefined` and `session: undefined` when converting from `InsomniaCookieJar`, with inline comments \"not supported in Insomnia\".\n- **`extensions` format from Insomnia is unknown** per an inline `TODO` — `CookieObject`'s constructor always passes `extensions: undefined`.\n- **`CookieJar.set()` never reports an error** — the callback signature allows an `Error`, but the implementation has no failure path (a malformed `CookieOptions` would throw inside `new Cookie(...)` before the callback is ever reached, rather than being passed to `cb`).\n- **Duplicate keys in `toObject()` collapse to the last value** unless `multiValue` is passed as `true`, in which case duplicates accumulate into an array.\n- **`Cookie.parse()` of a malformed string like `'=gingerale'`** still succeeds with `key: ''`, `value: 'gingerale'`, `expires: 'Infinity'` (per the test) — it does not throw for merely unusual input, only for input tough-cookie's parser rejects entirely.\n\n## Related\n- `properties.ts` — `Property` (base class for `Cookie`) and `PropertyList` (base class for `CookieList`).\n- `console.ts` — `getExistingConsole()`, used by `CookieJar`'s constructor to warn about domain-less cookies.\n- `insomnia.ts` — constructs `pm.cookies` as `new CookieObject(rawObj.cookieJar)` and reads back `this.cookies.jar().toInsomniaCookieJar()` after script execution.\n- `insomnia-data` package — source of the native `Cookie`/`CookieJar` types this file converts to/from.\n- `tough-cookie` (external) — underlying cookie parse/stringify implementation wrapped by `Cookie`.\n",".claude/skills/fix-scripting-feature/references/objects/environments.md":"# Environment / Variables / Vault\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/environments.ts`\n\n## Purpose\nThis file defines `Environment` (a single flat key-value store), `Variables` (a hierarchical read/write facade over several `Environment` scopes, resolving lookups in precedence order), and `Vault` (a locked-down `Environment` subclass for secrets). `InsomniaObject` (`insomnia.ts`) instantiates these and exposes them as `insomnia.environment`, `insomnia.globals`/`insomnia.baseGlobals` (private), `insomnia.collectionVariables` (aliased to `baseEnvironment`), `insomnia.variables`, and `insomnia.vault` — the core variable model behind pre-request/after-response/test scripts.\n\n## Public API\n\n### `class Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined)\n```\nInitializes `_name` and a `Map<string, boolean|number|string|null|undefined>` from `Object.entries(jsonObject || {})`.\n\n- `get name(): string` — returns `_name` (read-only getter; no setter).\n- `has = (variableName: string) => boolean` — `Map.has`.\n- `get = (variableName: string) => boolean|number|string|null|undefined` — `Map.get`; returns `undefined` if absent.\n- `set = (variableName: string, variableValue: boolean|number|string|undefined|null) => void` — sets the value; if `Number.isNaN(variableValue)` is true, logs a warning via `getExistingConsole().warn` and **does not** set the value.\n- `unset = (variableName: string) => void` — `Map.delete`.\n- `clear = () => void` — `Map.clear`.\n- `replaceIn = async (template: string | object) => Promise<string>` — coerces an object template via `.toString()`, throws `TypeError` if not a string/object, then renders it through `getInterpolator().render(template, this.toObject())`. Supports `{{$randomUUID}}` and `{{varName}}` placeholders.\n- `toObject = () => Record<string, any>` — `Object.fromEntries(this.kvs.entries())`.\n- `toJSON()` — returns `this.toObject()` (drives `JSON.stringify(environment)` serialization).\n\n### `function mergeFolderLevelVars(folderLevelVars: Environment[]): Environment` *(not exported, `@ignore`)*\nReduces an array of folder `Environment`s into one merged `Environment` named `'mergedFolderLevelVars'`, with **later folders in the array winning** on key collisions (`{ ...merged, ...folderLevelEnv.toObject() }`).\n\n### `class Variables`\n```ts\nconstructor(args: {\n  baseGlobalVars: Environment;\n  globalVars: Environment;\n  collectionVars: Environment;\n  environmentVars: Environment;\n  iterationDataVars: Environment;\n  folderLevelVars: Environment[];\n  localVars: Environment;\n})\n```\nStores each scope as a private `Environment` (or `Environment[]` for folder levels).\n\n- `has = (variableName: string) => boolean` — true if any of the 7 scopes has the variable (local, folder-level via `.some`, iterationData, environment, collection, global, baseGlobal — all evaluated eagerly, then OR'd).\n- `get = (variableName: string) => value` — builds an ordered array `[localVars, mergeFolderLevelVars(folderLevelVars), iterationDataVars, environmentVars, collectionVars, globalVars, baseGlobalVars]`, uses `.find(vars => vars.has(variableName))` to pick the **first scope (highest precedence) that has the key**, and returns `scope?.get(variableName)`.\n- `set = (variableName, variableValue) => void` — **always writes only to `localVars`** (same NaN-guard/warning as `Environment.set`). There is no way to write directly to environment/collection/global scope through `Variables.set`.\n- `replaceIn = async (template: string | object) => Promise<string>` — same contract as `Environment.replaceIn`, but the context is `this.toObject()` (the fully merged view across all scopes).\n- `toObject = () => Record<string, any>` — maps `[baseGlobalVars, globalVars, collectionVars, environmentVars, iterationDataVars, mergeFolderLevelVars(folderLevelVars), localVars]` to plain objects and folds them left-to-right with `{...ctx, ...obj}`, so **later entries in that array win** — i.e. `localVars` has final say, matching the precedence order used by `get`.\n- `localVarsToObject = () => Record<string, any>` *(`@ignore`)* — returns just `localVars.toObject()`; used by `InsomniaObject.toObject()` to serialize `insomnia.variables`.\n\n### `class Vault extends Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean)\n```\nCalls `super(name, jsonObject)`, then **returns a `Proxy(this, ...)`** (note: the constructor return value, not `this`, is what callers get). The proxy's `get`/`set` traps throw `new Error('Vault is disabled in script')` for **every property/method access** (including inherited `Environment` methods like `get`/`has`) whenever `enableVaultInScripts` is falsy.\n- `unset = () => { throw new Error('Vault can not be unset in script'); }` — overridden to always throw, regardless of `enableVaultInScripts`.\n- `clear = () => { throw new Error('Vault can not be cleared in script'); }` — always throws.\n- `set = () => { throw new Error('Vault can not be set in script'); }` — always throws (vault is read-only from scripts even when enabled).\n- Inherited `get`, `has`, `toObject`, `toJSON`, `replaceIn`, `name` still work normally when `enableVaultInScripts` is true (subject to the proxy's guard, which only blocks when the flag is false).\n\n## Script-facing surface\n- `insomnia.environment` → an `Environment` instance (the selected sub-environment, or the base environment itself if none is selected — see `initInsomniaObject` in `insomnia.ts`). Scripts call `insomnia.environment.get/set/unset/has/clear/replaceIn/toObject`.\n- `insomnia.baseEnvironment` → the collection's base `Environment`.\n- `insomnia.collectionVariables` → in `insomnia.ts`, this is literally assigned `this.baseEnvironment` (`this.collectionVariables = this.baseEnvironment;`) — it is not a distinct store, it's the *same* `Environment` object as `insomnia.baseEnvironment`.\n- `insomnia.variables` → a `Variables` instance built from all scopes (`insomnia.ts`'s `initInsomniaObject`: `baseGlobalVars: baseGlobals, globalVars: globals, environmentVars: environment, collectionVars: baseEnvironment, iterationDataVars: iterationData, folderLevelVars: parentFolders.getEnvironments(), localVars: localVariables`). Scripts use `insomnia.variables.get/set/has/replaceIn/toObject`. `insomnia.variables.set(...)` only ever affects the transient local scope.\n- `insomnia.vault` → a `Vault` instance; `insomnia.vault.get(<name>)` is the documented usage. Gated by the `enableVaultInScripts` setting.\n- `insomnia.globals`/`insomnia.baseGlobals` are declared `private` on `InsomniaObject`, but that's a TypeScript-only, compile-time restriction — `private` is erased at runtime, and `InsomniaObject`'s constructor returns `new Proxy(this, { get: (target, prop, receiver) => ... Reflect.get(target, prop, receiver) })` (see `insomnia.ts`), which forwards any property name through to the real field. A script can read `insomnia.globals`/`insomnia.baseGlobals` directly at runtime; they're just absent from the TS-declared public API, not actually inaccessible. Treat this as accidental exposure, not a supported surface — don't build fixes or features on scripts reading these, and any change here should aim to actually restrict access (e.g. a proxy guard like `Vault`'s) rather than expose them further.\n- `{{$randomUUID}}`-style and `{{variableName}}` templates in request URLs/bodies/headers are ultimately rendered through `Environment.replaceIn` / `Variables.replaceIn`.\n\n## Gotchas / notable behavior\n- **NaN guard**: both `Environment.set` and `Variables.set` silently refuse to store `NaN` and log a console warning instead of throwing — `null` and `undefined` are accepted just fine.\n- **Precedence order** (highest to lowest) used consistently by `Variables.has`, `.get`, and `.toObject`: local → folder-level (the **last** entry in the `folderLevelVars: Environment[]` array wins on collisions — see the merge-order gotcha below, not necessarily the nearest folder) → iteration data → environment → collection (`baseEnvironment`) → global → base global.\n- **Falsy values are not skipped**: `Variables.get` uses `.has()` to pick the scope, not truthiness, so a local value of `0`, `''`, or `false` is correctly returned instead of falling through to a lower-precedence scope (verified by a parameterized test in `environments.test.ts`).\n- **`collectionVariables` is an alias, not a separate object** — mutating `insomnia.collectionVariables` also mutates `insomnia.baseEnvironment` and vice versa, since they reference the same `Environment` instance.\n- **`Variables.set` cannot target non-local scopes** — there is no API on `Variables` to write into environment/collection/global; scripts must use `insomnia.environment.set(...)`, `insomnia.collectionVariables.set(...)`, etc. directly for those scopes.\n- **Folder-level merge order**: `mergeFolderLevelVars` merges the given `Environment[]` left-to-right with later entries winning; since `ParentFolders.getEnvironments()` returns folders \"from bottom to top\" is *not* guaranteed here — actual nearest-wins behavior depends on the order `folderLevelVars` is constructed in by the caller (see `folders.md`).\n- **`Vault` returns a `Proxy` from its constructor** — `new Vault(...)` does not yield a plain `Vault` instance; every property access goes through the proxy trap, so `enableVaultInScripts` is (re-)checked on *every* access, not just once at construction.\n- **`Vault` is effectively write-protected even when enabled**: `set`, `unset`, and `clear` are all overridden to unconditionally throw, so scripts can only ever read from the vault, never write to it, regardless of the `enableVaultInScripts` flag.\n- Deep/nested object values are not directly supported by `Environment`'s type signature (`boolean | number | string | null | undefined`); nothing in this file coerces or validates that at runtime, so anything else the caller passes is stored as-is.\n\n## Related\n- `properties.ts` — not used directly here, but `Variables`/`Environment` sit at the same conceptual layer that the `Variable`/`VariableList` classes (in `variables.ts`) also use for interpolation (via `getInterpolator()` in `interpolator.ts`).\n- `folders.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies the `folderLevelVars: Environment[]` array consumed by `Variables`.\n- `console.ts` — `getExistingConsole()` is used to emit the NaN warning.\n- `interpolator.ts` — `getInterpolator().render(...)` implements `{{...}}` template substitution for `replaceIn`.\n- `insomnia.ts` — constructs and wires up all the `Environment`/`Variables`/`Vault` instances exposed as `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`, `insomnia.vault`.\n",".claude/skills/fix-scripting-feature/references/objects/execution.md":"# Execution (`insomnia.execution` / `pm.execution`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/execution.ts`\n\n## Purpose\nModels `pm.execution`: where the currently-running request sits in the collection tree, plus two\ncollection-runner controls — skipping the request and rerouting to a different next request. This\nis one of the fields the collection runner reads back after a script finishes.\n\n## Public API\n\n### `interface ExecutionOption`\n```ts\ninterface ExecutionOption {\n  location: string[];\n  skipRequest?: boolean;\n  nextRequestIdOrName?: string;\n}\n```\nPlain-object shape used to construct an `Execution` (and the shape stored on `RequestContext.execution`, see `interfaces.md`).\n\n### `class Execution`\n```ts\nconstructor(options: ExecutionOption)\n```\n- `public location: string[]` — **not a plain array**: the constructor wraps a shallow copy\n  (`[...location]`) in a `Proxy` whose `get` trap adds a `current` accessor returning\n  `target.length > 0 ? target[target.length - 1] : ''` (the last path segment, or `''` if empty),\n  mirroring Postman's `execution.location.current` usage. All other property/index access passes\n  through via `Reflect.get`. Throws `TypeError('Location input must be array of string')` if\n  `options.location` is not an array.\n- `private _skipRequest: boolean` (default `false`), `private _nextRequestIdOrName: string` (default `''`) — no direct getters; only visible via `toObject()`.\n- `skipRequest = () => { this._skipRequest = true; }` — one-way flag setter; there is no method to un-set it.\n- `setNextRequest = (requestIdOrName: string) => { this._nextRequestIdOrName = requestIdOrName; }` — records which request the collection runner should execute next.\n- `toObject = () => ({ location: Array.from(this.location), skipRequest: this._skipRequest, nextRequestIdOrName: this._nextRequestIdOrName })` — serializes state to a plain object; `Array.from` strips the `Proxy`, so the resulting `location` array **loses the `.current` accessor**.\n\n## Script-facing surface\n- `insomnia.execution.location` — array of path segments (e.g. `['project', 'workspace', 'file', 'requestname']`).\n- `insomnia.execution.location.current` — last segment of the location path (Postman-compat).\n- `insomnia.execution.skipRequest()` — marks the current request to be skipped.\n- `insomnia.execution.setNextRequest(requestIdOrName)` — reroutes the collection runner to a specific request by id or name.\n\n## Gotchas / notable behavior\n- `location.current` only works on the live `Proxy`-wrapped array returned from the constructor;\n  once serialized via `toObject()` (which uses `Array.from`), the plain array no longer has\n  `.current`. This only matters for the runtime maintainers reading `toObject()`'s output — script\n  authors reading `insomnia.execution.location.current` directly are unaffected.\n- Passing a non-array `location` (e.g. a string) throws a `TypeError` synchronously from the\n  constructor — this is a `initInsomniaObject`/context-building bug, not something a script can\n  trigger directly, since scripts only read `insomnia.execution`, they don't construct it.\n- There's no public getter for `skipRequest`/`nextRequestIdOrName` state from the script side —\n  scripts can only set them, not read them back; only `toObject()` (used internally when the\n  runtime merges results into the persisted request context) exposes the current values.\n- Comment references Postman's docs on \"using variables in scripts\" for the `location.current`\n  design: https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/#using-variables-in-scripts\n\n## Related\n- `insomnia.ts` — `InsomniaObject.execution: Execution`; `toObject()` calls `execution.toObject()`.\n- `interfaces.ts` — `ExecutionOption` is embedded as `RequestContext.execution`.\n- `packages/insomnia/src/scripting/run-script.ts` — merges `mutatedContextObject.execution` (i.e. `Execution.toObject()`'s output) back into the returned `RequestContext` so the collection runner can act on `skipRequest`/`nextRequestIdOrName`.\n- `__tests__/execution.test.ts` — covers the `location`/`current` proxy behavior, `skipRequest()`/`setNextRequest()`, and the invalid-`location` throw.\n",".claude/skills/fix-scripting-feature/references/objects/folders.md":"# Folder / ParentFolders\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/folders.ts`\n\n## Purpose\nModels the folder hierarchy a request lives in, each folder carrying its own folder-level `Environment` of variables. `ParentFolders` is a container/lookup helper over an ordered array of `Folder`s, and its `getEnvironments()` is what feeds the `folderLevelVars: Environment[]` array consumed by `Variables` (`environments.ts`) — i.e. this is the source of folder-scoped variable resolution for `insomnia.variables`.\n\n## Public API\n\n### `class Folder`\n```ts\nconstructor(id: string, name: string, environmentObject: object | undefined)\n```\n- `id: string`\n- `name: string`\n- `environment: Environment` — constructed as `new Environment(`${id}.environment`, environmentObject)` (see `environments.md`).\n- `toObject = () => { id: string; name: string; environment: Record<string, any> }` — returns `id`, `name`, and `environment.toObject()`.\n\n### `class ParentFolders`\n```ts\nconstructor(private folders: Folder[])\n```\nPer the constructor JSDoc, `folders` is expected \"from bottom to top\" (nearest-ancestor folder first).\n\n- `get = (idOrName: string) => Folder` — finds the first folder whose `name` or `id` matches; **throws** `Error('Folder \"<idOrName>\" not found')` if none match.\n- `getById = (id: string) => Folder` — finds by `id` only; throws the same style of error if not found.\n- `getByName = (folderName: string) => Folder` — finds by `name` only; throws the same style of error if not found.\n- `findValue = (valueKey: string) => value | undefined` — reverses a **copy** of `this.folders` (`[...this.folders].reverse()`) and returns the first folder (after reversal) whose `environment.has(valueKey)`, then returns that folder's `environment.get(valueKey)`; returns `undefined` if no folder has it.\n- `toObject = () => object[]` — maps every folder to `folder.toObject()`, preserving the original (non-reversed) array order.\n- `getEnvironments = () => Environment[]` — maps every folder to `folder.environment`, preserving the original array order. This is the array handed to `Variables({ folderLevelVars: ... })`.\n\n## Script-facing surface\nNot directly exposed as a `pm.*` object itself — `ParentFolders` is internal plumbing constructed in `insomnia.ts`'s `initInsomniaObject`:\n```ts\nconst parentFolders = new ParentFolders(\n  rawObj.parentFolders.map(folderObj => new Folder(folderObj.id, folderObj.name, folderObj.environment)),\n);\n```\nand consumed two ways:\n- `parentFolders.getEnvironments()` → passed as `folderLevelVars` into `insomnia.variables` (`Variables`), so scripts reading `insomnia.variables.get('someFolderVar')` are indirectly reading through `Folder.environment`.\n- `parentFolders.toObject()` → included in `InsomniaObject.toObject()`'s `parentFolders` field (used for serialization/debugging, not typically read directly by user scripts).\nThere is no `insomnia.parentFolders` / `insomnia.folders` public property — `parentFolders` is a private field on `InsomniaObject`.\n\n## Gotchas / notable behavior\n- **The two folder-ordering JSDoc comments in this codebase appear to disagree, and `findValue` is not what scripts actually go through for `insomnia.variables`.** `ParentFolders`'s constructor doc says its `folders` array is ordered \"from bottom to top\" (i.e. index `0` = nearest/bottom, last index = topmost ancestor). `findValue`'s own doc says it searches \"starting from the nearest ancestor folder and moving towards the top ancestor folder.\" But the implementation does `[...this.folders].reverse().find(...)` — reversing a bottom-to-top array produces a top-to-bottom traversal, which is the *opposite* of what `findValue`'s own docstring claims. Treat `findValue`'s precedence as unverified/possibly inverted from its documented intent, and confirm against the actual call order at the construction site (`packages/insomnia/src/network/network.ts`, outside this SDK package) before relying on it to debug a specific precedence question. Note also that `findValue` is a standalone utility on `ParentFolders` — it is **not** what `insomnia.variables.get(...)` uses.\n- **`insomnia.variables.get(...)`'s folder precedence is governed by `mergeFolderLevelVars` in `environments.ts`, not by `ParentFolders.findValue`.** `mergeFolderLevelVars` merges the `folderLevelVars: Environment[]` array left-to-right with **later array entries winning** (`{...merged, ...folderLevelEnv.toObject()}`), and `ParentFolders.getEnvironments()` (used to build that array) preserves the original, non-reversed `this.folders` order. Per `environments.test.ts`'s `'variables operations'` test, `folders.getEnvironments()` is passed directly as `folderLevelVars`, and mutating the folder passed **second** to the `ParentFolders` constructor (`folder2`) is what wins in `variables.get('value')` — i.e. whichever folder appears later in the array given to `new ParentFolders([...])` wins ties in `insomnia.variables`, regardless of what \"nearest\"/\"topmost\" means in that particular caller's ordering convention.\n- `get`, `getById`, and `getByName` all throw plain `Error`s (not a custom error type) with a template string — safe to catch with a generic `try/catch` but there's no error code to switch on.\n- `Folder.environment`'s name is derived (`${id}.environment`), not the folder's own `name` — don't assume `folder.environment.name === folder.name`.\n\n## Related\n- `environments.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies `Variables`'s `folderLevelVars`, and `mergeFolderLevelVars` there is what actually implements the effective folder-variable precedence used by `insomnia.variables`.\n- `insomnia.ts` — constructs `ParentFolders` from `rawObj.parentFolders` and wires `getEnvironments()` into the `Variables` constructor; exposes `parentFolders.toObject()` via `InsomniaObject.toObject()`.\n- `index.ts` — re-exports `Folder` (but not `ParentFolders`) at the top level; `collection.ts` also re-exports `Folder`.\n",".claude/skills/fix-scripting-feature/references/objects/headers.md":"# Header, HeaderList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/headers.ts`\n\n## Purpose\n\nModels a single HTTP header (`Header`) and an ordered collection of headers (`HeaderList`). Used by `Request.headers` and `Response.headers` (`pm.request.headers` / `pm.response.headers`), and internally by `Request`'s header-merge logic. `HeaderList` is a thin specialization of the generic `PropertyList`.\n\n## Public API\n\n### `HeaderDefinition` (interface)\n`{ key: string; value: string; id?: string; name?: string; disabled?: boolean }`\n\n### `Header` (extends `Property`)\n- `override _kind = 'Header'`\n- `key: string`, `value: string`\n- `constructor(opts: HeaderDefinition | string, name?: string)` — if `opts` is a string, it's parsed via `Header.parseSingle` (only `key`/`value` are set — `id`/`name`/`disabled` come from `Property`'s defaults, not from the string). If `opts` is an object, `id`, `key`, `name` (overridden by the `name` param if given), `value`, and `disabled` are all read from it (each falling back to `''`/`false` if absent).\n- `static override _index = 'key'` — tells `PropertyList` to index/dedupe `Header`s by `key` (used by `one`, `indexOf`, `upsert`).\n- `static create(input?: {key, value} | string, name?: string): Header` — defaults `input` to `{key: '', value: ''}`.\n- `static isHeader(obj: object): boolean` — checks `_kind === 'Header'`.\n- `static parse(headerString: string): {key, value}[]` — splits on `\\n`, ignores blank lines, parses each via `parseSingle`.\n- `static parseSingle(headerStr: string): {key, value}` — splits on the **first** colon. **Throws** `Error('Header.parseSingle: the header string seems invalid')` if there's no colon or it's at position 0.\n- `static unparse(headers: {key,value}[] | PropertyList<Header>, separator?: string): string` — maps each header through `unparseSingle` and joins (default separator `'\\n'`).\n- `static unparseSingle(header: {key,value} | Header): string` — `\"key: value\"`.\n- `update(newHeader: {key, value})` — mutates `this.key`/`this.value` in place.\n- `override valueOf()` — returns `this.value`.\n\n### `HeaderList<T extends Header>` (extends `PropertyList<T>`)\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — internally always constructs the underlying `PropertyList` with `Header` as the type class (regardless of `T`).\n- `static isHeaderList(obj: any): boolean` — checks `_kind === 'HeaderList'`.\n- `contentSize(): number` — sum of `header.toString().length` (i.e. `\"key: value\"`.length) across all headers; comment notes special characters aren't handled.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, string | string[]>` — builds a plain key→value(s) map. `excludeDisabled` (default falsy) skips headers with `disabled` truthy. `multiValue` (default falsy) collects same-key headers into an array instead of overwriting with the last value. `_caseSensitive` and `_sanitizeKeys` are accepted but **unused**.\n\n## Script-facing surface\n\n`pm.request.headers` and `pm.response.headers` are `HeaderList<Header>` instances. Beyond the methods above, all generic `PropertyList` methods are available: `add`/`append`, `all()`, `assimilate()`, `clear()`, `count()`, `each()`, `filter()`, `find()`, `get(key)` / `one(key)` (keyed by `key` since `Header._index = 'key'`), `has()`, `idx()`, `indexOf()`, `insert()`/`insertAfter()`, `map()`, `populate()`, `prepend()`, `reduce()`, `remove()`, `repopulate()`, `toString()`, `upsert()`. Typical script usage: `pm.request.headers.add({key, value})`, `pm.request.headers.get('Content-Type')`, `pm.response.headers.has(...)`, `pm.request.headers.upsert(new Header({...}))`.\n\n## Gotchas / notable behavior\n\n- `new Header(stringOpts)` only populates `key`/`value` from the parsed string — `id`, `name`, and `disabled` are left at `Property`'s constructor defaults (`''`, `''`, `false`), never read from the string form.\n- `Header.parseSingle` throws on malformed input (no colon) — a raw header string with a typo can throw instead of silently producing an empty/garbage header.\n- `PropertyList.one(key)` (inherited, used for `HeaderList.get`) iterates **from the end of the list backwards**, so when duplicate keys exist, the **last-added** matching header wins — relevant when a script both sets a default header and later overrides it with the same key.\n- `HeaderList.toObject()`'s `_caseSensitive` and `_sanitizeKeys` parameters exist in the signature (for compatibility with Postman's original API shape) but have **no effect** — only `excludeDisabled` and `multiValue` are actually implemented.\n- `HeaderList`'s constructor ignores its own `T` type parameter internally — it always builds the base `PropertyList` using the concrete `Header` class, not whatever `T` was passed.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property` (base class for `Header`), `PropertyList` (base class for `HeaderList`).\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.headers: HeaderList<Header>`; also reuses `Header`/`HeaderList` in `addHeader`/`removeHeader`/`upsertHeader`/`getHeaders`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response.headers: HeaderList<Header>`; used in `contentInfo()` to find `Content-Type`/`Content-Disposition`.\n",".claude/skills/fix-scripting-feature/references/objects/insomnia.md":"# InsomniaObject (`insomnia` / `pm`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/insomnia.ts`\n\n## Purpose\nThis is the top-level scripting object: the single instance bound into the sandbox as `insomnia`\n(with `$` as a Postman-compat alias — see Script-facing surface). It aggregates every other object\nin this module (environments, variables, request, response, cookies, execution, request info,\nvault, client certificates, test assertions) into one value that a pre-request / after-response /\ntest script reads and mutates, and that the runtime serializes back out via `toObject()` after the\nscript finishes.\n\n## Public API\n\n### `class InsomniaObject`\n\n```ts\nconstructor(rawObj: {\n  globals: Environment;\n  baseGlobals: Environment;\n  iterationData: Environment;\n  environment: Environment;\n  baseEnvironment: Environment;\n  variables: Variables;\n  request: ScriptRequest;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookies: CookieObject;\n  requestInfo: RequestInfo;\n  execution: Execution;\n  response?: ScriptResponse;\n  parentFolders: ParentFolders;\n  vault?: Vault;\n})\n```\n\nPublic/exposed properties:\n- `environment: Environment` — the currently-selected environment.\n- `collectionVariables: Environment` — **same object reference as `baseEnvironment`** (see Gotchas).\n- `baseEnvironment: Environment` — the base (root) environment.\n- `variables: Variables` — layered variable resolver (globals/env/collection/iteration/folder/local).\n- `request: ScriptRequest` — the request being built/sent (`pm.request`).\n- `cookies: CookieObject` — cookie jar wrapper (`pm.cookies`).\n- `info: RequestInfo` — event/iteration metadata (`pm.info`).\n- `response?: ScriptResponse` — present only in after-response/test scripts.\n- `execution: Execution` — location/skip/next-request control (`pm.execution`).\n- `vault?: Vault` — secret vault accessor, gated by `settings.enableVaultInScripts`.\n- `clientCertificates: ClientCertificate[]` — raw client certs available to the request.\n\nPrivate/internal properties (TypeScript-only — see Gotchas for why this doesn't restrict scripts at\nruntime): `_expect` (chai `expect`), `_test`/`_skip` (from `./test`), `iterationData: Environment`,\n`globals: Environment`, `baseGlobals: Environment` (marked\n`// TODO: follows will be enabled after Insomnia supports them`), `_settings: Settings`,\n`requestTestResults: RequestTestResult[]`, `parentFolders: ParentFolders`.\n\nMethods:\n- `sendRequest(request: string | ScriptRequest, cb: (error?: string, response?: ScriptResponse) => void)` — delegates to `sendRequest()` from `./send-request`, passing the internal `_settings`.\n- `test = () => {}` — a no-op placeholder property; the *actual* behavior is installed by the\n  constructor's `Proxy` (see below). Declaring this field exists mainly so `test` shows up as an\n  own property.\n- `expect = (exp: boolean | number | string | object) => this._expect(exp)` — thin pass-through to chai's `expect`.\n- `get settings()` — **always returns `undefined`** (see Gotchas).\n- `toObject = () => {...}` — serializes the whole object graph to a plain object: `globals`,\n  `baseGlobals`, `environment`, `baseEnvironment`, `iterationData` (all via each `Environment`'s\n  `toObject()`), `variables` (via `variables.localVarsToObject()`), `request`, `settings` (calls\n  `this.settings`, i.e. always `undefined`), `clientCertificates`, `cookieJar` (via\n  `cookies.jar().toInsomniaCookieJar()`), `info` (via `info.toObject()`), `response` (via\n  `response.toObject()` or `undefined`), `requestTestResults`, `execution` (via\n  `execution.toObject()`), `parentFolders` (via `parentFolders.toObject()`).\n\nThe constructor returns `new Proxy(this, { get: ... })`: any property access other than `'test'`\npasses straight through via `Reflect.get`. Accessing `.test` instead returns a freshly-built\n`TestHandler` function that calls `this._test(msg, fn, this.pushRequestTestResult)`, with a\n`.skip` method that calls `this._skip(msg, fn, this.pushRequestTestResult)`. `pushRequestTestResult`\nappends each `RequestTestResult` to the private `requestTestResults` array (immutably, via spread).\n\n### `async function initInsomniaObject(rawObj: RequestContext, log: (...args: any[]) => void): Promise<InsomniaObject>`\n\nFactory used by the script runner to build an `InsomniaObject` from a `RequestContext` snapshot\n(see `interfaces.md`). Responsibilities, in order:\n- Maps `globals`/`baseGlobals` — if the same environment id is selected for both, `globals` and\n  `baseGlobals` become the *same* `Environment` instance; otherwise separate instances are created.\n- Maps `environment`/`baseEnvironment` the same way; if no sub-environment is selected (ids equal),\n  logs a warning via `log(...)` that mutations to `insomnia.environment` will apply to the base\n  environment.\n- Builds `iterationData` and `transientVariables`-backed local variables `Environment`s (defaulting\n  to empty named environments when absent).\n- Builds a `Vault` from `rawObj.vault` (or an empty object), gated by\n  `rawObj.settings?.enableVaultInScripts` (defaults `false`).\n- Builds `CookieObject` from `rawObj.cookieJar`.\n- Builds `RequestInfo` from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- Builds `ParentFolders` from `rawObj.parentFolders`, then a `Variables` instance that layers\n  base-global/global/environment/collection/iteration-data/folder-level/local variables.\n- Resolves client certificates: uses `checkIfUrlIncludesTag` (from `./utils`) and\n  `filterClientCertificates` (from `insomnia/src/network/certificate`) against `rawObj.request.url`.\n  If the URL contains template tags or no certificate matches, initializes an **empty** default\n  certificate and logs a warning via `getExistingConsole().warn(...)`; otherwise uses the first\n  matched certificate.\n- Builds the request URL (`toUrlObject`), proxy options (`transformToSdkProxyOptions`,\n  `resolveProtocolForProxy`), adds query params, builds the auth via `toPreRequestAuth`, and\n  constructs a `ScriptRequest`.\n- Constructs an `Execution` from `rawObj.execution`.\n- Reads the response body via `readBodyFromPath` and builds a `ScriptResponse` via\n  `toScriptResponse` if `rawObj.response` is present.\n- Returns `new InsomniaObject({...})` with everything above.\n\n## Script-facing surface\n`InsomniaObject` *is* what a script sees as the global `insomnia` object (and `$`, a Postman-compat\nalias set up as `const $ = insomnia;` by the script wrapper in\n`packages/insomnia/src/scripting/run-script.ts`). Nearly every `pm.*`/`insomnia.*` call in a script\nroutes through this object's properties/methods directly:\n- `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`\n- `insomnia.request`, `insomnia.response`, `insomnia.cookies`, `insomnia.info`, `insomnia.execution`, `insomnia.vault`\n- `insomnia.test(name, fn)` / `insomnia.test.skip(name, fn)`\n- `insomnia.expect(value)`\n- `insomnia.sendRequest(request, callback)`\n\n`run-script.ts` requires the script's `insomnia` result to be `instanceof InsomniaObject` — if the\nuser script returns early or otherwise breaks that invariant, `runScript` throws\n`'insomnia object is invalid or script returns earlier than expected.'`. After the script runs,\n`InsomniaObject.toObject()` is used to merge mutations back into the persisted `RequestContext`.\n\n## Gotchas / notable behavior\n- **`collectionVariables` is not a copy** — `this.collectionVariables = this.baseEnvironment;` in\n  the constructor means `insomnia.collectionVariables` and `insomnia.baseEnvironment` are the exact\n  same `Environment` instance. Mutating one via a script mutates the other.\n- **`insomnia.settings` is always `undefined`** — the `get settings()` accessor unconditionally\n  `return;`s nothing, even though the real `Settings` object is held internally as `_settings` (used\n  only for `sendRequest`'s proxy/certificate resolution). `toObject().settings` is therefore always\n  `undefined` too, regardless of what `Settings` were passed in.\n- **`globals`/`baseGlobals` are `private` in name only** — `private` is TypeScript-only and erased at runtime, and the constructor's `Proxy` forwards them (via `Reflect.get`, no `set` trap) same as any other property, so scripts can read *and* write `insomnia.globals`/`insomnia.baseGlobals` despite them being absent from the TS-declared public surface. Accidental exposure, not a supported feature.\n- **The `test` proxy is easy to miss when reading the class** — the `test = () => {}` field looks\n  like the real implementation, but any actual call to `insomnia.test(...)` is intercepted by the\n  constructor's `Proxy` `get` trap before it ever reaches that field.\n- **Certificate fallback is silent unless you check logs** — if the request URL contains\n  `{{`/`}}`/`{%`/`%}` template tags, or no client certificate matches the host, an *empty* default\n  certificate is substituted and a warning is written via the script console rather than thrown as\n  an error.\n- **No-environment-selected warning** — selecting the base environment (rather than a\n  sub-environment) causes `initInsomniaObject` to log a warning that `insomnia.environment`\n  mutations will land on the base environment, because `environment` and `baseEnvironment` are the\n  same instance in that case.\n\n## Related\n- `interfaces.ts` — defines `RequestContext`, the input shape `initInsomniaObject` consumes.\n- `execution.ts` — `Execution`, held as `insomnia.execution`.\n- `request-info.ts` — `RequestInfo`, held as `insomnia.info`.\n- `environments.ts` — `Environment`, `Variables`, `Vault` classes used throughout.\n- `cookies.ts` — `CookieObject`, held as `insomnia.cookies`.\n- `folders.ts` — `Folder`, `ParentFolders`, used for folder-level variables.\n- `request.ts` — `Request`/`RequestOptions`/`toScriptRequestBody`, builds `insomnia.request`.\n- `response.ts` — `toScriptResponse`, `readBodyFromPath`, builds `insomnia.response`.\n- `send-request.ts` — implements `insomnia.sendRequest(...)`.\n- `test.ts` — `test`/`skip`/`TestHandler`, implements `insomnia.test(...)`.\n- `auth.ts` — `toPreRequestAuth`, used when building the request's auth config.\n- `proxy-configs.ts` — `transformToSdkProxyOptions`, used for request proxy settings.\n- `urls.ts` — `toUrlObject`, `resolveProtocolForProxy`.\n- `utils.ts` — `checkIfUrlIncludesTag`, used for certificate-fallback detection.\n- `console.ts` — `getExistingConsole`, used to log warnings.\n- `packages/insomnia/src/scripting/run-script.ts` — binds this object as the sandbox global `insomnia` and calls `toObject()` after the script runs.\n",".claude/skills/fix-scripting-feature/references/objects/interfaces.md":"# Shared interfaces (`RequestContext`, `IEnvironment`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interfaces.ts`\n\n## Purpose\nPure type-only file (no runtime code) defining the shared shapes used to pass state into and out\nof the scripting environment. `RequestContext` is the full serialized state snapshot that the host\napp (`packages/insomnia`) builds before running a script and that `initInsomniaObject` (in\n`insomnia.ts`) turns into a live `InsomniaObject`.\n\n## Public API\n\n### `interface IEnvironment`\n```ts\ninterface IEnvironment {\n  id: string;\n  name: string;\n  data: object;\n}\n```\nMinimal serialized shape for any environment-like blob (globals, base globals, environment, base\nenvironment, vault). Marked `/** @ignore */` (excluded from generated public docs).\n\n### `interface RequestContext`\n```ts\ninterface RequestContext {\n  request: Request;\n  timelinePath: string;\n  environment: IEnvironment;\n  baseEnvironment: IEnvironment;\n  vault?: IEnvironment;\n  collectionVariables?: object;\n  globals?: IEnvironment;\n  baseGlobals?: IEnvironment;\n  iterationData?: Omit<IEnvironment, 'id'>;\n  timeout: number;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookieJar: CookieJar;\n  response?: any;\n  requestTestResults?: RequestTestResult[];\n  requestInfo: RequestInfoOption;\n  execution: ExecutionOption;\n  logs: string[];\n  transientVariables?: Omit<IEnvironment, 'id'>;\n  parentFolders: { id: string; name: string; environment: Record<string, any> }[];\n}\n```\nFull input/output snapshot for one script execution. Fields of note:\n- `request: Request` — the raw request model (from `insomnia-data`), not the script-facing `ScriptRequest` wrapper in `request.ts`.\n- `globals`/`baseGlobals` — optional; per the inline comment, \"activated only when selected\".\n- `iterationData`/`transientVariables` — typed as `Omit<IEnvironment, 'id'>` (no `id` field needed for these).\n- `response?: any` — deliberately untyped; comment notes \"Callback types defined elsewhere to avoid circular imports\".\n- `requestInfo: RequestInfoOption` and `execution: ExecutionOption` — imported from `./request-info` and `./execution` respectively; these are the plain-object option shapes consumed by the `RequestInfo`/`Execution` constructors (see `request-info.md`/`execution.md`).\n- `parentFolders` — array of plain folder descriptors (`id`, `name`, `environment` data), consumed by `ParentFolders`/`Folder` in `folders.ts`.\n\nBoth interfaces are marked `/** @ignore */`, meaning they're internal/plumbing types not meant to\nappear in the generated public SDK reference.\n\n## Script-facing surface\nNone directly. A user's pre-request/after-response/test script never sees a `RequestContext` or\n`IEnvironment` value — these are internal transport types used by the host application\n(`packages/insomnia/src/scripting/run-script.ts`) to hand state into `initInsomniaObject` and to\nreceive mutated state back out via `InsomniaObject.toObject()`.\n\n## Gotchas / notable behavior\n- `globals`/`baseGlobals`/`vault` are all optional — code reading them elsewhere (e.g.\n  `initInsomniaObject`) must handle `undefined` explicitly (it does, via `?.` and fallbacks).\n- `response` being typed `any` means no compile-time safety on the response shape passed into the\n  scripting environment; the real shape is validated only implicitly by how `toScriptResponse` (in\n  `response.ts`) consumes it.\n- This file has no runtime exports (interfaces only) — importing it has zero side effects.\n\n## Related\n- `insomnia.ts` — `initInsomniaObject(rawObj: RequestContext, log)` is the sole consumer that turns this shape into a live `InsomniaObject`.\n- `execution.ts` — supplies `ExecutionOption`, embedded as `RequestContext.execution`.\n- `request-info.ts` — supplies `RequestInfoOption`, embedded as `RequestContext.requestInfo`.\n- `environments.ts` — `Environment`/`Vault` classes are constructed from `IEnvironment`-shaped data.\n- `folders.ts` — `Folder`/`ParentFolders` are constructed from `RequestContext.parentFolders`.\n- `packages/insomnia/src/scripting/run-script.ts` — builds and consumes `RequestContext` around each script run.\n",".claude/skills/fix-scripting-feature/references/objects/interpolator.md":"# Interpolator (template-tag rendering)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interpolator.ts`\n\n## Purpose\nInternal helper providing `{{variable}}`-style template rendering (plus `{{$fakerFn}}` dynamic\nvalue generation) on top of LiquidJS. It backs every \"replace variables in this string\" operation\nexposed by the environment/property objects (e.g. `insomnia.environment.replaceIn(...)`).\n\n## Public API\n\n### `class Interpolator` (not exported — module-private)\n```ts\nconstructor()\n```\nBuilds a `Liquid` engine configured with:\n- `outputDelimiterLeft: '{{'`, `outputDelimiterRight: '}}'` — variable interpolation delimiters.\n- `tagDelimiterLeft: '{%'`, `tagDelimiterRight: '%}'` — Liquid tag delimiters (e.g. `{% if %}`).\n- `strictVariables: true` — referencing an undefined variable causes a render error rather than silently rendering empty.\n- `jsTruthy: true` — use JS truthiness semantics instead of Liquid's default truthy rules.\n- `ownPropertyOnly: false` — allows resolving inherited/prototype properties on the context object, not just own properties.\n\nMethods:\n- `render = async (template: string, context: object): Promise<string>` — runs `renderWithFaker(template)` first (to substitute any `{{$fakerFn}}` tags with generated values), then parses/renders the result through the Liquid engine against `context`. Comment: `// TODO: support plugins`.\n- `renderWithFaker = (template: string) => string` — a pre-pass over the template string, done via\n  manual string splitting (not Liquid) rather than regex/AST parsing:\n  1. Splits `template` on `'}}'`.\n  2. For each segment, finds the last `'{{'` before the end of the segment.\n  3. If the found tag name starts with `'$'`, strips the `$` prefix, looks it up in\n     `fakerFunctions` (from `insomnia/src/common/templating/faker-functions`), calls it, and\n     splices the generated value in place of the tag.\n  4. If the tag name does not start with `$`, the segment (and its `}}`) is left untouched for the\n     real Liquid engine to interpolate as a normal variable.\n  5. Throws `Error('replaceIn: no faker function is found: ${funcName}')` if the `$`-prefixed name\n     isn't a recognized faker function.\n\n### `function getInterpolator(): Interpolator`\nReturns the single module-level `Interpolator` instance (`const interpolator = new Interpolator();`), constructed once at module load. This is the only exported symbol.\n\n## Script-facing surface\nNo direct surface — scripts never call `getInterpolator()` themselves. It is reached indirectly\nthrough higher-level methods on other objects, all of which delegate to\n`getInterpolator().render(...)`:\n- `insomnia.environment.replaceIn(template)` / same method on `baseEnvironment`, `collectionVariables`, `variables`, etc. (defined in `environments.ts`) — e.g. `insomnia.environment.replaceIn(\"My id is {{$randomUUID}}\")` or `insomnia.environment.replaceIn(\"Visiting URL: {{urlValueFromEnvironment}}\")`.\n- `Property.replaceSubstitutions(content, ...variables)` and `Property.replaceSubstitutionsIn(obj, ...variables)` (static methods in `properties.ts`) — used internally wherever a `Property`-derived object needs variable substitution against multiple variable-scope objects merged together.\n\n## Gotchas / notable behavior\n- `renderWithFaker` is a hand-rolled string scan, not a real parser — it looks for the *last*\n  `'{{'` before each `'}}'` boundary in each split segment. Malformed or nested template syntax\n  could confuse this pass before Liquid ever sees the template.\n- `strictVariables: true` means referencing a variable name that isn't present in `context` will\n  cause the Liquid render to throw/reject, not silently produce an empty string — a common source\n  of \"my `{{someVar}}` template failed\" script errors when the variable isn't actually defined in\n  any active scope.\n- `$`-prefixed faker tags are resolved by this module's own pre-pass, *before* Liquid — an unknown\n  `$xyz` faker function name throws synchronously from `renderWithFaker` with the message\n  `replaceIn: no faker function is found: xyz`.\n- `getInterpolator()` always returns the same singleton `Interpolator` instance across the whole\n  process — there is no way to get a differently-configured interpolator or to reset its Liquid\n  engine mid-run.\n- The `Interpolator` class itself is not exported; only `getInterpolator()` is, so consumers cannot\n  construct their own instance or subclass it.\n\n## Related\n- `environments.ts` — `Environment`/`Variables`/`Vault`'s `replaceIn(...)` methods call `getInterpolator().render(...)`.\n- `properties.ts` — `Property.replaceSubstitutions`/`replaceSubstitutionsIn` call `getInterpolator().render(...)`.\n- `insomnia/src/common/templating/faker-functions` (outside this package) — supplies the `fakerFunctions` map used for `{{$fakerFn}}` tags.\n",".claude/skills/fix-scripting-feature/references/objects/properties.md":"# PropertyBase / Property / PropertyList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/properties.ts`\n\n## Purpose\nBase classes that most other SDK objects extend, mirroring Postman's `postman-collection` `Property`/`PropertyList` model: `PropertyBase` provides parent-chain traversal and JSON serialization, `Property` adds `id`/`name`/`disabled` plus static template-substitution helpers, and `PropertyList<T>` is a generic ordered/keyed collection with Postman-compatible methods (`add`, `each`, `filter`, `find`, `one`, `upsert`, `toObject`, etc.). Concrete subclasses (`Variable`/`VariableList`, `Header`/`HeaderList`, `Cookie`/`CookieList`, `ProxyConfig`/`ProxyConfigList`, etc.) build on top of these.\n\n## Public API\n\n### `unsupportedError(featureName: string, alternative?: string): Error`\nBuilds an `Error` with message `` `${featureName} is not supported yet` `` (optionally appending `, please use ${alternative} instead temporarily.` when `alternative` is given). Used throughout the SDK (e.g. `Variable.types()`, `PropertyList.eachParent`) to mark stubbed-out Postman-compat methods.\n\n### `class PropertyBase`\n```ts\nconstructor(description?: string)\n```\n- `public _kind = 'PropertyBase'` — used as a lightweight runtime type tag (checked via `'_kind' in obj` elsewhere, e.g. `VariableList.isVariableList`).\n- `protected _parent: PropertyBase | undefined` — set only via subclasses/external assignment; no setter method is provided on the base class itself.\n- `protected description?: string`.\n- `static propertyIsMeta(_value: any, key: string): boolean` — `key && key.startsWith('_')` (keys starting with `_` are treated as \"meta\").\n- `static propertyUnprefixMeta(_value: any, key: string): string` — strips a leading `_` from `key` if present.\n- `meta(): {}` — always returns an empty object (not implemented beyond the stub).\n- `parent(): PropertyBase | undefined` — returns `this._parent`.\n- `forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean): PropertyBase[] | undefined` — BFS-style walk up the parent chain (via `parent()`), cloning each ancestor with the `clone` package before passing it to `iterator`; stops as soon as `iterator` returns falsy, and always returns the array of cloned ancestors visited so far (or `undefined` if there is no parent at all). `_options` is accepted but never read.\n- `findInParents(property: string, customizer?: (ancestor: PropertyBase) => boolean): PropertyBase | undefined` — walks the (cloned) parent chain looking for the first ancestor whose `meta()` keys include `property`; if `customizer` is given, keeps walking until `customizer(ancestor)` returns truthy, otherwise returns the first ancestor with that meta key. Returns `undefined` if no parent, or none matches.\n- `toJSON(): Record<string, any>` — returns `Object.entries(this)` filtered to drop function-valued entries, `undefined` values, and the `_kind` key.\n- `toObject(): Record<string, any>` — returns `this.toJSON()`.\n- `toString(): string` — `JSON.stringify(this.toJSON())`.\n\n### `class Property extends PropertyBase`\n```ts\nconstructor(id?: string, name?: string, disabled?: boolean, info?: { id?: string; name?: string })\n```\n`info.id`/`info.name` take priority over the positional `id`/`name` params if both are given; `this._kind` is set to `'Property'`; `disabled` defaults to `false`.\n- `id: string`, `name?: string`, `disabled?: boolean`.\n- `static _index = 'id'` — the field name `PropertyList` uses to key/index items of this type; overridden by subclasses (e.g. `Variable._index = 'key'`).\n- `static async replaceSubstitutions(content: string, ...variables: object[]): Promise<string>` — throws `TypeError` if `variables` isn't an array or `content` isn't a string. Merges `variables` into a single context object: the array is `.reverse()`d first, then folded left-to-right with `{...context, ...variable}`, so the reversed (originally-last) entries are applied first and get overwritten by earlier ones — net effect: **the leftmost/first argument passed to `replaceSubstitutions` wins** on key collisions, matching the inline comment (\"the searching priority of rendering is from left to right\"). Renders `content` against that merged context via `getInterpolator().render(...)`.\n- `static async replaceSubstitutionsIn(obj: object, ...variables: object[]): Promise<object>` — same signature/merge semantics as `replaceSubstitutions`, but stringifies `obj` with `JSON.stringify`, renders it, then `JSON.parse`s the result. Throws `TypeError` for bad args up front; wraps any other error (e.g. a `JSON.parse` failure on non-JSON-safe rendered output) in a new `Error('replaceSubstitutionsIn: ' + e.toString())`.\n- `describe(content: string, typeName: string): void` — sets `this._kind = typeName` and `this.description = content`.\n\n### `class PropertyList<T extends Property>`\n```ts\nconstructor(\n  protected typeClass: { _index?: string },\n  protected parent: Property | PropertyList<any> | undefined,\n  populate: T[],\n)\n```\n- `protected _kind = 'PropertyList'`, `protected list: T[]`.\n- `static isPropertyList(obj: object): boolean` — `'_kind' in obj && obj._kind === 'PropertyList'`.\n- `add(item: T): void` — pushes to the end of `list`.\n- `all(): Record<string, any>[]` — `list.map(pp => pp.toJSON())`.\n- `append(item: T): void` — alias for `add`.\n- `assimilate(source: T[] | PropertyList<T>, prune?: boolean): void` — if `prune`, calls `clear()` first; then pushes all of `source`'s items (`source.list` if it's a `PropertyList`, else the array itself) onto `list`. Per an inline comment, \"it doesn't update values from a source list\" (i.e. this appends, it does not merge/replace by key).\n- `clear(): void` — `list = []`.\n- `count(): number` — `list.length`.\n- `each(iterator: (item: T) => void, context: object): void` — calls `list.forEach(iterator)`; `context` is stashed as `iterator.context` (assigned onto the function) but the iterator itself is not invoked with that context bound — purely a Postman-compat artifact.\n- `eachParent(_iterator, _context?): never` — **always throws** `unsupportedError('eachParent')`; not implemented (\"properties are not organized as hierarchy\" per the TODO comment).\n- `filter(rule: (item: T) => boolean, context: object): T[]` — same `context`-stash pattern as `each`; returns `list.filter(rule)`.\n- `find(rule: (item: T) => boolean, context?: object): T | undefined` — same pattern; `list.find(rule)`.\n- `get(key: string): T | undefined` — alias for `one(key)`.\n- `has(item: T, _value?: any): boolean` — `indexOf(item) >= 0`; `_value` is accepted but unused (\"its usage is unknown\" per comment).\n- `idx(index: number): T | undefined` — returns `list[index]` if `index <= list.length - 1`, else `undefined`.\n- `indexOf(item: string | T): number` — looks up the index field via `typeClass._index || 'id'`; if `item` is a string, matches `record[indexFieldName] === item`; otherwise matches `record[indexFieldName] === (item as Record<string, any>)[indexFieldName]`. Returns `-1` if not found.\n- `insert(item: T, before?: number): void` — splices `item` in before index `before` if valid (`before != null && before >= 0 && before <= list.length - 1`), else falls back to `append(item)`.\n- `insertAfter(item: T, after?: number): void` — splices `item` in right after index `after` under the same bounds check, else falls back to `append(item)`.\n- `map(iterator: (item: T) => any, context: object): any[]` — same `context`-stash pattern; `list.map(iterator)`.\n- `one(id: string): T | undefined` — scans `list` **backwards** (`for (let i = list.length - 1; i >= 0; i--)`) for the first record whose index field equals `id`; if the matched item has a callable `valueOf`, returns `item.valueOf()` instead of the item itself; else returns the item as-is. Returns `undefined` if not found.\n- `populate(items: T[]): void` — `list = [...list, ...items]`.\n- `prepend(item: T): void` — `list = [item, ...list]`.\n- `reduce(iterator: (acc: any, item: T) => any, accumulator: any, context: object): any` — same `context`-stash pattern; `list.reduce(iterator, accumulator)`.\n- `remove(predicate: T | ((item: T) => boolean), context: object): void` — if `predicate` is a function, keeps everything that does *not* match it (via `filter` with the negated predicate); if `predicate` is a value, keeps everything not `deep-equal` to it.\n- `repopulate(items: T[]): void` — `clear()` then `populate(items)`.\n- `toObject(_excludeDisabled?, _caseSensitive?, _multiValue?, _sanitizeKeys?): Record<string, any> | Record<string, any>[]` — base implementation ignores all four arguments and returns `list.map(elem => elem.toJSON())` (an array, not a keyed object) — it's the fallback for lists whose items have no natural key (e.g. `UrlMatchPatternList`, or a raw `PropertyList` not used through a subclass). Subclasses backed by keyed items (`CookieList`, `HeaderList`, `VariableList`, `ProxyConfigList`) override this with a real key→value map that supports `excludeDisabled`/`multiValue`; `caseSensitive`/`sanitizeKeys` remain unsupported even in those overrides.\n- `toString(): string` — `` `[${list.map(item => item.toString()).join('; ')}]` ``.\n- `upsert(item: T): boolean` — returns `false` immediately if `item == null`. If `indexOf(item)` finds an existing entry, **splices it out and reinserts the new item at the same position** (returns `false`, meaning \"updated, not inserted\"); otherwise calls `add(item)` and returns `true` (\"inserted new\"). Note: the \"splice out, then splice again\" implementation calls `this.list.splice(...)` twice on the *same* array reference inside one expression (`[...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]`) — the first `splice` call mutates `this.list` in place before the second one runs against the now-shortened array.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` itself — these are base classes. Scripts interact with them only through concrete subclasses' public APIs, e.g.:\n- `pm.variables`/collections of variables → `VariableList` (extends `PropertyList<Variable>`), `Variable` (extends `Property`) — see `variables.md`.\n- `pm.request.headers`, `pm.response.headers` → `HeaderList`/`Header`.\n- `pm.request.url.query` → `UrlMatchPatternList`/similar list types.\n- Any script calling `.get()`, `.one()`, `.each()`, `.filter()`, `.upsert()`, `.toObject()`, etc. on a collection-like SDK object is going through `PropertyList`'s implementation documented above.\n- `Property.replaceSubstitutions`/`replaceSubstitutionsIn` back the SDK's `{{variable}}` template rendering wherever it's invoked as a static helper (distinct from, but functionally similar to, `Environment.replaceIn`/`Variables.replaceIn` in `environments.ts`).\n\n- **`replaceSubstitutions`/`replaceSubstitutionsIn` mutate their own `variables` rest-array via `.reverse()`** before merging — this is an implementation detail (not observable to callers since `variables` is a fresh rest-parameter array each call), but worth knowing if you're stepping through this code: the reversal is what makes the final left-to-right merge produce \"leftmost argument wins\" precedence.\n- **`eachParent` always throws** `unsupportedError('eachParent')` — calling it from a script will break, regardless of arguments.\n- **`each`/`filter`/`find`/`map`/`reduce`'s `context` parameter does nothing functionally** — it's stashed as a property on the callback function object (`it.context = context`) purely for Postman API-shape compatibility, but the callback is invoked with `Array.prototype`'s normal (unbound) semantics, so relying on `context` for `this`-binding inside the iterator will not work as it might in Postman.\n- **`one(id)` scans backwards and unwraps `valueOf()`** — if two items share the same index-field value, `one()` returns the *last*-added one (matching most recently `add`/`upsert`ed semantics), and if that item defines a custom `valueOf`, the returned object is `item.valueOf()`, not the raw list item.\n- **`upsert(item)` replaces the item at the same index** via `[...this.list.slice(0, itemIdx), item, ...this.list.slice(itemIdx + 1)]`, leaving every other item in place.\n- **`toObject()`'s base implementation returns an array, not an object**, unlike its keyed subclass overrides (`VariableList.toObject()` etc., which return `Record<string, any>`) — code that assumes every `PropertyList.toObject()` yields a key/value map will break for list types that don't override it (e.g. `UrlMatchPatternList`).\n- `PropertyBase.forEachParent`/`findInParents` both `clone()` each ancestor before handing it to the caller/comparing — mutations the iterator/customizer performs on the passed-in ancestor object do not affect the real parent chain.\n- `Property`'s constructor lets `info.id`/`info.name` silently override the positional `id`/`name` arguments — passing both can be surprising if not intentional.\n\n## Related\n- `variables.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>` (see `variables.md`).\n- `environments.ts` — a separate, non-`Property`-based variable model (`Environment`/`Variables`) used for `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables`; don't conflate the two when tracing a variable bug.\n- `interpolator.ts` — `getInterpolator().render(...)` is what `Property.replaceSubstitutions`/`replaceSubstitutionsIn` delegate to for `{{...}}` template rendering.\n- `headers.ts`, `cookies.ts`, `proxy-configs.ts`, `urls.ts` — other concrete `Property`/`PropertyList` subclasses that override `toObject()` with their own keyed semantics.\n- `collection.ts` / `index.ts` — re-export `Property`, `PropertyBase`, `PropertyList` as part of the SDK's public surface.\n",".claude/skills/fix-scripting-feature/references/objects/proxy-configs.md":"# ProxyConfig, ProxyConfigList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts`\n\n## Purpose\nModels a single proxy configuration (`pm.request.proxy`) and a keyed list of them (`ProxyConfigList`, exported for scripts to build their own lists, e.g. via `require('insomnia-collection')`). Also provides `transformToSdkProxyOptions`, the function that converts Insomnia's native proxy settings (`httpProxy`/`httpsProxy`/`proxyEnabled`/`noProxy` strings from Settings) into the SDK's `ProxyConfigOptions` shape used to seed `pm.request.proxy`.\n\n## Public API\n\n### Types\n- `interface ProxyConfigOptions { match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string }` — constructor input for `ProxyConfig`. `match` is a URL match pattern string (see `urls.ts`) initializing a `UrlMatchPattern` internally, e.g. `'http+https://example.com/*'`. `bypass` and `protocol` are called out in comments as \"for compatibility with Insomnia\".\n\n### `class ProxyConfig extends Property`\n- `override _kind = 'ProxyConfig'`\n- `host: string`\n- `match: string` — the raw match-pattern string (not a `UrlMatchPattern` instance on the instance property, though a `UrlMatchPattern` is constructed on the fly inside `getProtocols()`/`test()`).\n- `port?: number`\n- `tunnel: boolean`\n- `authenticate: boolean`\n- `username: string`\n- `password: string`\n- `bypass: string[]` — list of hosts/URLs to bypass the proxy for; comment: \"for compatibility with Insomnia's bypass list\".\n- `protocol: string` — e.g. `\"http\"`/`\"https\"`.\n- `static authenticate = false`, `static bypass: UrlMatchPatternList<UrlMatchPattern> = new UrlMatchPatternList(undefined, [])`, `static host = ''`, `static match = ''`, `static password = ''`, `static port?: number = undefined`, `static tunnel = false` (comment: \"unsupported\"), `static username = ''`, `static protocol = 'https:'` — all marked `@ignore` in JSDoc with the comment \"following properties are hidden as they are not used while must be exposed\". These are class-level defaults, distinct from the instance properties of the same name.\n- `constructor(def: { id?: string; name?: string; match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string })` — sets `id`/`name` from `def` or defaults to `''`, `disabled` defaults to `false`, `bypass` defaults to `[]` if omitted. All other fields copied straight through (no defaulting).\n- `static override _index = 'key'` — Note: `ProxyConfig` has no instance field literally named `key`; `PropertyList.indexOf()`/`.one()` would look for a `key` property on the record, which doesn't exist on `ProxyConfig` — see Gotchas. (`ProxyConfigList.toObject()` keys its map by `match`, not by this `_index`.)\n- `static isProxyConfig(obj: object): boolean` — checks `obj._kind === 'ProxyConfig'`.\n- `getProtocols(): string[]` — builds a `UrlMatchPattern(this.match)` and returns `.getProtocols()` (parses the `'+'`-delimited protocol prefix of the match string, e.g. `'http+https://...'` → `['http', 'https']`).\n- `getProxyUrl(): string` — builds the full proxy URL: `` `${protocol}//${username}:${password}@${host}${port}` `` if `authenticate` is true, otherwise `` `${protocol}//${host}${port}` `` (port segment omitted entirely if `port === undefined`).\n- `test(url?: string): boolean` — returns `false` immediately if `url` is falsy (comment: \"TODO: it is confusing in which case url arg is optional\"). Returns `false` if `url` is literally in `this.bypass` (exact string match, not pattern matching). Otherwise delegates to `new UrlMatchPattern(this.match).test(url)`.\n- `update(options: Omit<ProxyConfigOptions, 'bypass' | 'protocol'>): void` — updates `host`, `match`, `port`, `tunnel`, `authenticate`, `username`, `password`. Does **not** allow updating `bypass` or `protocol` (both omitted from the type, and untouched by the method body).\n- `updateProtocols(_protocols: string[]): never` — always throws `Error('updateProtocols is not supported in Insomnia')`. Comment: \"In Insomnia there is no whitelist while there is a blacklist.\"\n\n### `class ProxyConfigList<T extends ProxyConfig> extends PropertyList<T>`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — constructs the underlying `PropertyList` with `typeClass = ProxyConfig`.\n- `static isProxyConfigList(obj: any): boolean` — checks `obj._kind === 'ProxyConfigList'`.\n- `resolve(url?: Url): object | null` — returns `null` if `url` is falsy. Otherwise stringifies `url`, filters the list to configs whose `.test(urlStr)` is true, maps matches to `.toJSON()`, and returns the **first** match's JSON (or `null` if none match). Comment: \"It only returns the first one if multiple matches are found.\"\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, any>` — builds a plain object keyed by each config's `match` string, using `Object.create(null)` (no prototype). `excludeDisabled` skips `disabled` configs. `multiValue` collects same-`match` configs into an array; otherwise duplicate `match` values collapse to the last one.\n\n### Module-level function\n- `transformToSdkProxyOptions(protocol: string, httpProxy: string, httpsProxy: string, proxyEnabled: boolean, noProxy: string): ProxyConfigOptions` — computes `proxyHost` (the string actually parsed into `host`/`port`/etc. below) as `httpsProxy` or `httpProxy` based on whether `protocol === 'https:'`. But `enabledProxy` is computed separately, from `proxyEnabled && (httpsProxy || httpProxy || '').trim() !== ''` — **not** from `proxyHost` — so it's `true` whenever *either* proxy string is non-empty, regardless of which one the current `protocol` selected (see Gotchas). Splits `noProxy` on commas (trimmed) into the `bypass` list. Always returns `match: '<all_urls>'` (`UrlMatchPattern.MATCH_ALL_URLS`). If the proxy is enabled and a host string is present, parses it with the built-in `URL` class (prefixing `${protocol}//` if the string has no `://`), extracting `port` (only set if non-empty, then `Number.parseInt(..., 10)`), `protocol`, `host` (hostname), `username`, `password`; sets `authenticate = true` if either `username` or `password` came through; logs `` `Using proxy: ${sanitizedProxy}` `` via `getExistingConsole().warn`. Throws `Error('Failed to parse proxy (${protocol}//${proxyHost}): ${e.message}')` if URL parsing fails **and the proxy is enabled** (parsing is skipped entirely, no throw, when the proxy is disabled).\n\n## Script-facing surface\n- `pm.request.proxy` is a single `ProxyConfig` instance (per `request.ts`: `const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined;`), seeded from Insomnia's Settings-level proxy configuration via `transformToSdkProxyOptions` (called in `insomnia.ts`). It is `undefined` if no `proxy` option was supplied when constructing the request.\n- Scripts read/write `pm.request.proxy.host`, `.port`, `.username`, `.password`, `.authenticate`, `.tunnel`, `.bypass`, `.protocol` directly, call `.getProxyUrl()` to see the resolved proxy URL, `.test(url)` to check whether a given URL should go through this proxy, and `.update({...})` to change host/match/port/tunnel/authenticate/username/password.\n- `ProxyConfigList` and `ProxyConfig` are also exported from the `insomnia-collection` module (via `collection.ts`) so scripts can `require('insomnia-collection')` and construct their own lists directly, e.g.: `new ProxyConfigList(undefined, [{match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true}, ...])` (per the inline example comment in the source). There is no evidence in the SDK object graph that `pm.request` itself ever holds a `ProxyConfigList` — only a single `ProxyConfig`.\n\n## Gotchas / notable behavior\n- **`_index = 'key'` but `ProxyConfig` has no `key` property.** Base `PropertyList.one()`/`.indexOf()`/`.upsert()` look up items by `typeClass._index` (here `'key'`), but `ProxyConfig` instances never define a `key` field — so `ProxyConfigList.one('...')`/`.get('...')`/`.indexOf(...)`/`.upsert(...)` would compare against `undefined` for every item and effectively never index correctly by identity (though `.toObject()` and `.resolve()` work fine since they key/filter explicitly by `match` instead of relying on `_index`).\n- **`test(url)` returns `false` silently if `url` is omitted** — the source itself flags this as confusing (\"TODO: it is confusing in which case url arg is optional\").\n- **`bypass` matching is exact string equality**, not pattern-based — a URL must be an exact match to an entry in `bypass` to be excluded; nothing is normalized (no protocol/trailing-slash handling).\n- **`update()` cannot change `bypass` or `protocol`** — these are deliberately excluded from `ProxyConfigOptions` in the `update()` signature's `Omit<...>`.\n- **`updateProtocols()` always throws** — it exists on the class but is not a supported operation in Insomnia.\n- **`getProxyUrl()` omits the port segment entirely when `port === undefined`** (not just falsy — `port: 0` would still render `:0`).\n- **`transformToSdkProxyOptions` swallows parse errors when the proxy is disabled**: a malformed proxy host string does not throw as long as `proxyEnabled` is `false`; it only throws when the proxy would actually be used.\n- **`enabled`/`disabled` can disagree with the protocol-selected proxy string.** `enabledProxy` is derived from `httpsProxy || httpProxy` (https checked first, regardless of `protocol`), while the proxy actually parsed into `host`/`port`/etc. is `proxyHost` (`httpsProxy` only if `protocol === 'https:'`, else `httpProxy`). If `protocol` is `'http:'` with `httpProxy` empty but `httpsProxy` set (or vice versa), `enabledProxy` comes out `true` from the other, unused string, `proxy.disabled` is set to `false`, yet `proxyHost === ''` skips the parsing block entirely — so `pm.request.proxy` ends up `disabled: false` with an empty `host`.\n- **The class-level `static` fields shadow real instance field names** (`static host = ''`, `static match = ''`, etc., separate from `this.host`, `this.match` on instances) — marked `@ignore` and described only as \"hidden as they are not used while must be exposed\"; do not confuse `ProxyConfig.host` (static, always `''`) with `someProxyConfigInstance.host`.\n\n## Related\n- `properties.ts` — `Property` (base class for `ProxyConfig`) and `PropertyList` (base class for `ProxyConfigList`), including the shared `toObject()`/`_index` lookup semantics referenced above.\n- `urls.ts` — `UrlMatchPattern`/`UrlMatchPatternList` (used internally by `getProtocols()`/`test()`) and `Url` (the type accepted by `ProxyConfigList.resolve()`).\n- `console.ts` — `getExistingConsole()`, used by `transformToSdkProxyOptions` to log which proxy is being used.\n- `request.ts` — constructs `pm.request.proxy` from `ProxyConfigOptions` and serializes it back out when building the final request.\n- `insomnia.ts` — calls `transformToSdkProxyOptions` using Insomnia Settings (`proxyEnabled`, http/https proxy strings, `noProxy`) to seed the proxy config before a script runs.\n- `collection.ts` — re-exports `ProxyConfig`/`ProxyConfigList` for direct use via `require('insomnia-collection')`.\n",".claude/skills/fix-scripting-feature/references/objects/request-info.md":"# RequestInfo (`insomnia.info` / `pm.info`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request-info.ts`\n\n## Purpose\nModels `pm.info`: metadata about which script phase is executing and, for the collection runner,\nwhich iteration is in progress. Read-oriented — scripts typically branch on `insomnia.info.eventName`\nor log `insomnia.info.iteration`/`iterationCount`.\n\n## Public API\n\n### `type EventName = 'prerequest' | 'test'`\nThe name of the event that triggered the script: `'prerequest'` (before a request is sent) or\n`'test'` (during the testing/after-response phase).\n\n### `interface RequestInfoOption`\n```ts\ninterface RequestInfoOption {\n  eventName?: EventName;\n  iteration?: number;\n  iterationCount?: number;\n  requestName?: string;\n  requestId?: string;\n}\n```\nPlain-object shape used to construct a `RequestInfo` (and stored on `RequestContext.requestInfo`, see `interfaces.md`).\n\n### `class RequestInfo`\n```ts\nconstructor(options: RequestInfoOption)\n```\nPublic properties (plain, mutable, non-readonly):\n- `eventName: EventName` — defaults to `'prerequest'` if not provided (`options.eventName || 'prerequest'`).\n- `iteration: number` — defaults to `1` (`options.iteration || 1`).\n- `iterationCount: number` — defaults to `1` (`options.iterationCount || 1`).\n- `requestName: string` — defaults to `''`.\n- `requestId: string` — defaults to `''`.\n\nMethod:\n- `toObject = () => ({ eventName, iteration, iterationCount, requestName, requestId })` — plain serialization, same shape as `RequestInfoOption` with all fields always populated.\n\n## Script-facing surface\n- `insomnia.info.eventName` — `'prerequest'` or `'test'`, useful for scripts shared between pre-request and test tabs.\n- `insomnia.info.iteration` — current collection-runner iteration number (1-based).\n- `insomnia.info.iterationCount` — total number of iterations configured for the run.\n- `insomnia.info.requestName` — display name of the request being executed.\n- `insomnia.info.requestId` — the request's unique id.\n\n## Gotchas / notable behavior\n- All five properties are **plain public fields**, not readonly — a script can reassign\n  `insomnia.info.iteration = 99` and nothing in this class prevents it. In practice this has no\n  effect on the actual collection-runner state: `packages/insomnia/src/scripting/run-script.ts`'s\n  merge logic (building the returned `RequestContext`) does not read `requestInfo` back out of the\n  mutated `InsomniaObject` at all, so any script-side mutation of `insomnia.info` is silently\n  discarded after the script finishes.\n- The `||` fallback pattern means an explicit `0` for `iteration`/`iterationCount` is treated the\n  same as \"not provided\" and coerced to `1`. Notably, `initInsomniaObject` (in `insomnia.ts`)\n  builds the options with `iterationCount: rawObj.requestInfo.iterationCount || 0` — but since `0`\n  is falsy, the `RequestInfo` constructor's own `|| 1` fallback still turns that back into `1`, so\n  `iterationCount` effectively can never legitimately be `0` through this path.\n- A `// TODO: update follows when post-request script and iterationData are introduced` comment on\n  the call site in `insomnia.ts` suggests the `eventName`/iteration wiring for after-response\n  scripts may still be incomplete.\n\n## Related\n- `insomnia.ts` — `InsomniaObject.info: RequestInfo`; `initInsomniaObject` builds it from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- `interfaces.ts` — `RequestInfoOption` is embedded as `RequestContext.requestInfo`.\n- `packages/insomnia/src/scripting/run-script.ts` — supplies the initial `RequestInfoOption` per script phase (pre-request vs. test) and — notably — does not persist script-side mutations back into `RequestContext`.\n- `__tests__/request-info.test.ts` — covers default values and `toObject()` shape for both single-request and collection-runner (`iteration`/`iterationCount`) cases.\n",".claude/skills/fix-scripting-feature/references/objects/request.md":"# Request, RequestBody\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request.ts`\n\n## Purpose\n\nModels the outgoing HTTP request as seen/mutated by pre-request and test scripts: `pm.request` / `insomnia.request`. Also holds the merge/serialization logic that converts between this script-facing `Request` shape and Insomnia's own `insomnia-data` request model (`mergeRequests`, `mergeRequestBody`, `mergeSettings`, `mergeClientCertificates`), and payload-size calculation used by `Request.size()` / `Response.size()`.\n\n## Public API\n\n### `FormParam` (extends `Property`)\n- `constructor(options: { key: string; value: string; type?: string; disabled?: boolean })`\n- `key: string`, `value: string`, `type?: string`\n- `static _postman_propertyAllowsMultipleValues()` — **always throws** `Error('unsupported')`.\n- `static _postman_propertyIndexKey()` — **always throws** `Error('unsupported')`.\n- `toJSON()` — `{ key, value, type, disabled }`.\n- `toString()` — `\"key=value\"` with both parts `encodeURIComponent`-escaped.\n- `valueOf()` — returns `value`.\n\n### `RequestBodyOptions` (interface)\n`{ mode: RequestBodyMode; file?: string; formdata?: {key,value,type?,disabled?}[]; graphql?: {query,operationName,variables,disabled?}; raw?: string; urlencoded?: {key,value?,type?,disabled?,multiline?,fileName?}[]; options?: object }`\n`RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'`.\n\n### `RequestBody` (extends `PropertyBase`)\n- `constructor(opts: RequestBodyOptions)`\n- `mode: RequestBodyMode`, `file?: string`, `formdata?: PropertyList<FormParam>`, `graphql?: {query,operationName,variables}`, `raw?: string`, `urlencoded?: PropertyList<QueryParam>`, `options?: object`\n- `isEmpty(): boolean` — switches on `mode` and checks whether the matching field is `null`; **throws** `Error` if `mode` isn't one of the five known modes.\n- `toString()` — renders body as a string per `mode` (`formdata`/`urlencoded` join their `PropertyList` entries with `&`, `graphql` is `JSON.stringify`-ed); wraps any internal error as `Error(\"toString: ...\")`; returns `''` if `mode` is `undefined`.\n- `update(opts: RequestBodyOptions)` — re-derives and replaces all fields (same logic as constructor).\n\n### `RequestOptions` (interface)\n`{ url: string | Url; method?: string; header?: HeaderDefinition[] | object; body?: RequestBodyOptions; auth?: AuthOptions; proxy?: ProxyConfigOptions; certificate?: CertificateOptions; pathParameters?: RequestPathParameter[]; name?: string }`\n\n### `RequestSize` (interface)\n`{ body: number; header: number; total: number; source: string }`\n\n### `Request` (extends `Property`)\n- `constructor(options: RequestOptions)` — sets `_kind = 'Request'`; `method` defaults to `'GET'` if omitted; `header` accepts either an array of `HeaderDefinition` or a plain `{key: value}` object (converted to `Header`s); `auth` defaults to `{ type: 'noauth' }`.\n- Properties: `name: string`, `url: Url`, `method: string`, `headers: HeaderList<Header>`, `body?: RequestBody`, `auth: RequestAuth`, `proxy?: ProxyConfig`, `certificate?: Certificate`, `pathParameters: RequestPathParameter[]`.\n- `static isRequest(obj: object): boolean` — checks `_kind === 'Request'`.\n- `addHeader(header: Header | object)` — accepts a `Header` instance or `{key, value}`; **throws** `Error` otherwise.\n- `addQueryParams(params: QueryParam[] | string)` — delegates to `this.url.addQueryParams`.\n- `authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList<Variable>)` — delegates to `this.auth.use(...)`.\n- `clone(): Request` — `new Request({ ...this.toJSON() })` (JSON round-trip clone, not a deep object clone).\n- `forEachHeader(callback: (header: Header, context?: object) => void)` — delegates to `this.headers.each`.\n- `getHeaders(options?: { ignoreCase, enabled, multiValue, sanitizeKeys }): Record<string, string[] | string>` — merges headers with the same key into an array; `ignoreCase` lowercases keys before merging; `enabled` filters out headers where `disabled` is truthy; `sanitizeKeys` drops headers with a falsy `value`.\n- `removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean })` — rebuilds `this.headers` as a new `HeaderList` excluding matches; **throws** if `toRemove` isn't `string | Header`.\n- `removeQueryParams(params: string | string[] | QueryParam[])` — delegates to `this.url.removeQueryParams`.\n- `size(): RequestSize` — `calculatePayloadSize(this.body?.toString() ?? '', this.headers)`.\n- `toJSON()` — plain object snapshot (`url` as string, `header` array, `body`, `auth`, `proxy`, `certificate`).\n- `update(options: RequestOptions)` — re-derives and replaces every field.\n- `upsertHeader(header: HeaderDefinition)` — removes any existing header with the same `key` (case-sensitive), then appends a new `Header`.\n\n### Module-level functions\n- `mergeSettings(originalSettings: Settings, updatedReq: Request): Settings` — if `updatedReq.proxy` is enabled (not disabled and has a non-empty proxy URL), overrides both `httpProxy` and `httpsProxy` in the returned `Settings` with the same proxy URL; otherwise returns `originalSettings` unchanged.\n- `mergeClientCertificates(originalClientCertificates: ClientCertificate[], updatedReq: Request): ClientCertificate[]` — maps the script's single `updatedReq.certificate` onto Insomnia's certificate list (which supports multiple). Returns originals unchanged if no certificate was set (or it's empty). **Throws** `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if neither a valid `pfx` nor a valid `cert`+`key` pair is present in the certificate.\n- `toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions` — converts Insomnia's native request body (`text` / `fileName` / `params`) into a `RequestBodyOptions` (`raw` / `file` / `urlencoded`); `formdata` and `graphql` modes are not produced here.\n- `mergeRequestBody(updatedReqBody: RequestBody | undefined, originalReqBody: InsomniaRequestBody): InsomniaRequestBody` — infers `mimeType` from `updatedReqBody.mode` (falls back to `originalReqBody.mimeType` if set); **throws** on an unknown `mode`; wraps any other failure as `Error(\"failed to update body: ...\")`.\n- `mergeRequests(originalReq: InsomniaRequest, updatedReq: Request): InsomniaRequest` — builds the outgoing Insomnia request from the script's mutated `Request`: `url` via `toStringWithoutQuery()`, `parameters` from `url.query`, `headers` from `headers`, `authentication` via `fromPreRequestAuth`, `pathParameters` copied as-is, and **hardcodes `preRequestScript: ''`** (i.e., the merged request never carries a pre-request script forward).\n- `calculatePayloadSize(body: string, headers: HeaderList<Header>): RequestSize` — body size via `new Blob([body]).size`; `source` is always `'COMPUTED'`.\n- `calculateHeadersSize(headers: HeaderList<Header>): number` — `Blob` size of all headers joined as `\"key: value\\n\"` lines (via each header's `toString()`, which is `Header`'s inherited/overridden behavior).\n\n## Script-facing surface\n\n`insomnia.request` / `pm.request` is a live `Request` instance (constructed in `insomnia.ts#initInsomniaObject`). Common script usage:\n- `pm.request.url`, `pm.request.method`, `pm.request.headers`, `pm.request.body`\n- `pm.request.headers.add(...)`, `pm.request.addHeader({key, value})`, `pm.request.upsertHeader({key, value})`, `pm.request.removeHeader('X-Foo')`\n- `pm.request.url.addQueryParams(...)` / `pm.request.addQueryParams(...)`, `pm.request.removeQueryParams(...)`\n- `pm.request.auth`, `pm.request.authorizeUsing('basic', ...)`\n- `pm.request.size()` — payload size info\n- Any mutation a pre-request script makes to `pm.request` is read back and merged into the real outgoing request via `mergeRequests` / `mergeRequestBody` / `mergeSettings` / `mergeClientCertificates` after the script finishes.\n\n## Gotchas / notable behavior\n\n- `FormParam._postman_propertyAllowsMultipleValues()` and `_postman_propertyIndexKey()` are stubs that **always throw** — calling them from a script (unlikely, but they exist statically) will always fail. A commented-out `static parse` is also noted as \"not supported yet in existing scripts\".\n- `RequestBody.isEmpty()` / `toString()` both `throw` if `mode` doesn't match one of the five known literals — an unexpected/typo'd `mode` string surfaces as a runtime error, not silently.\n- `Request.getHeaders()`'s `multiValue` option is part of the signature but **is never referenced in the function body** — the implementation always accumulates same-key headers into an array regardless of what `multiValue` is set to.\n- `Request.removeHeader` and `upsertHeader` rebuild `this.headers` as an entirely new `HeaderList` rather than mutating in place.\n- `Request.clone()` is a JSON round-trip (`toJSON()` → `new Request(...)`), not a structural/deep clone — anything not captured by `toJSON()` is lost on clone.\n- `mergeRequests` **always sets `preRequestScript: ''`** on the merged request — a deliberate design choice worth knowing if debugging \"script disappeared\" reports downstream.\n- `mergeClientCertificates` throws if a script sets both `cert`+`key` and `pfx` on `pm.request.certificate` at the same time — Insomnia only supports one or the other.\n- `toScriptRequestBody` only produces `raw` / `file` / `urlencoded` modes; there's no path from Insomnia's native body model to script `formdata`/`graphql` modes in this function.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` used for `Request.headers`.\n- `packages/insomnia-scripting-environment/src/objects/urls.ts` — `Url`, `QueryParam`, `toUrlObject` used for `Request.url` and `RequestBody.urlencoded`.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used for `Request.auth`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate` used for `Request.certificate`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig` used for `Request.proxy`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes.\n- `packages/insomnia-scripting-environment/src/objects/variables.ts` — `Variable`, `VariableList` used by `authorizeUsing`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — imports `calculateHeadersSize`; `Response.originalRequest` is a `Request`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.request` in `initInsomniaObject`.\n",".claude/skills/fix-scripting-feature/references/objects/response.md":"# Response\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/response.ts`\n\n## Purpose\n\nModels the HTTP response as seen by after-response and test scripts: `pm.response` / `insomnia.response`. It wraps the raw body/headers/cookies/timing data returned by the network layer, provides parsing/introspection helpers (`json()`, `contentInfo()`, `size()`), and extends `chai`'s assertion API with Postman-style response assertions (`pm.response.to.have.status(200)`, etc.). `pm.response` is only populated for after-response/test scripts — it's `undefined` during pre-request scripts.\n\n## Public API\n\n### `ResponseOptions` (interface)\n`{ code: number; reason?: string; header?: HeaderDefinition[]; cookie?: CookieOptions[]; body?: string; stream?: Buffer | ArrayBuffer; responseTime: number; originalRequest: Request; bytesRead?: number }`\n\n### `ResponseContentInfo` (interface)\n`{ mimeType: string; mimeFormat: string; charset: string; fileExtension: string; fileName: string; contentType: string }`\n\n### `Response` (extends `Property`)\n- `constructor(options: ResponseOptions)` — sets `_kind = 'Response'`; `body` defaults to `''`; `status` defaults to `options.reason`, falling back to `RESPONSE_CODE_REASONS[options.code]`, falling back to `''`; `bytesRead` (private) defaults to `0`.\n- Properties: `body: string`, `code: number`, `cookies: CookieList`, `headers: HeaderList<Header>`, `originalRequest: Request`, `responseTime: number`, `status: string`, `stream?: Buffer | ArrayBuffer`; `bytesRead` is **private**.\n- `static createFromNode(response: {...}, cookies: CookieOptions[]): Response` — builds a `Response` from a raw Node-style response object (`body`, `headers`, `statusCode`, `statusMessage`, `elapsedTime`, `originalRequest`, `stream`).\n- `static isResponse(obj: object): boolean` — checks `_kind === 'Response'`.\n- `contentInfo(): ResponseContentInfo` — parses the `Content-Type` header for `mimeType`/`charset` (defaults: `application/octet-stream` / `utf8`) and the `Content-Disposition` header for `fileName`/`fileExtension`. **Throws** if a `Content-Type` header exists but its value is blank, or its mime-type segment is empty.\n- `dataURI(): string` — builds a `data:` URI string from `contentInfo().contentType` and `this.stream || this.body`. **Throws** if neither `stream` nor `body` is set.\n- `json(reviver?: (key, value) => any, _strict?: boolean): any` — `JSON.parse(this.body.toString(), reviver)`; wraps parse failures as `Error(\"json: failed to parse: ...\")`. `_strict` is accepted but unused (\"TODO: enable strict after common module is introduced\").\n- `jsonp(_reviver?, _strict?)` — **always throws** `unsupportedError('jsonp()')`.\n- `reason(): string` — returns `this.status`.\n- `size(): { body: number; header: number; total: number; source: 'COMPUTED' }` — `body` is the private `bytesRead` value (not derived from `this.body`'s actual length); `header` via `calculateHeadersSize(this.headers)`.\n- `text(): string` — `this.body.toString()`.\n- `get to()` — returns a `chai.Assertion` wrapping `this`, with Postman-style properties/methods registered onto `chai.Assertion.prototype` on every access: properties `withBody`, `error` (true if `code` is within 400–500 inclusive), `ok` (true if `code === 200`), `json` (true if the body parses to an object); methods `status(val)`, `header(headerName)`, `body(bodyContent)`, `jsonBody(propName)`, `jsonSchema(schema, options?)` (uses `ajv` to validate `this.json()` against `schema`).\n\n### Module-level functions\n- `toScriptResponse(originalRequest: Request, partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, responseBody: string): Response | undefined` — returns `undefined` if `partialInsoResponse` is an error result (network/curl failure); otherwise builds a `Response`, extracting `Set-Cookie` headers into `Cookie.parse(...)` entries for `cookie`.\n- `readBodyFromPath(response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined): Promise<string>` — returns `''` if `response` is missing, an error, or has no `bodyPath`; otherwise reads and decompresses the body from disk via `services.helpers.readCurlResponse`. **Throws** if that read reports an error.\n\n## Script-facing surface\n\n`insomnia.response` / `pm.response` is a `Response` instance, present only in after-response/test scripts (constructed in `insomnia.ts#initInsomniaObject` via `toScriptResponse`; `undefined` if the request errored or during pre-request scripts). Common script usage:\n- `pm.response.code`, `pm.response.status`, `pm.response.headers`, `pm.response.body`\n- `pm.response.json()`, `pm.response.text()`, `pm.response.contentInfo()`\n- `pm.response.to.have.status(200)`, `pm.response.to.have.header('Content-Type')`, `pm.response.to.have.jsonBody('key')`, `pm.response.to.not.have.status(404)`, `pm.response.to.have.jsonSchema({...})`\n\n## Gotchas / notable behavior\n\n- `dataURI()` has a typo in the returned string: it emits `` `data:${contentType};baseg4, ${bodyInBase64}` `` — **`baseg4` instead of `base64`**. Any consumer relying on `dataURI()` producing a spec-compliant data URI will get a malformed one.\n- `jsonp()` is a stub that always throws `unsupportedError('jsonp()')` — it exists on the type but is never functional.\n- `size().body` comes from the **private `bytesRead`** field (set once at construction from `options.bytesRead`, defaulting to `0`), not from measuring `this.body`. If `bytesRead` wasn't passed in, `size()` reports `0` regardless of actual body length. (The test file has a commented-out assertion noting `resp.size()` doesn't fully work yet: `\"this will work after PropertyList.one is improved\"`.)\n- The `error` assertion (`pm.response.to.have.error` / `.to.be.error`) only covers status codes **400–500 inclusive** — not the broader 4xx/5xx range one might expect.\n- **`get to()`'s re-registration is a real correctness risk, not just wasted work.** Every `.to` access calls `chai.use((_chai, utils) => {...})` with a freshly-created arrow function; chai dedupes plugins by callback identity (`used.indexOf(fn)`), so a new closure is never recognized as already-registered and the plugin body reruns every time. Worse, every `withBody`/`error`/`ok`/`json`/`status`/`header`/`body`/`jsonBody`/`jsonSchema` callback is itself an arrow function that reads `utils.flag(respAssertion, 'object')`, where `respAssertion` is closed over from whichever `.to` access most recently ran — not derived from `this`/the assertion instance actually being evaluated (arrow functions ignore the `this` chai binds via `getter.call(this)`/`method.apply(this, ...)`). So if a script holds a `.to`-derived assertion chain and triggers another `.to` access (same or different response) before invoking a method/property on that held chain, the held chain silently asserts against the *other* response instead. Single-statement chains like `pm.response.to.have.status(200)` are unaffected since no `.to` access can intervene mid-expression.\n- `contentInfo()` throws if a `Content-Type` header is present but empty/malformed; scripts that blindly call `pm.response.contentInfo()` on unusual responses can throw.\n- `toScriptResponse` returns `undefined` on network error — scripts must handle `pm.response` potentially being `undefined` (though in practice after-response/test scripts only run when a response exists).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Response.originalRequest: Request`; imports `calculateHeadersSize` from here.\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` for `Response.headers`.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `Cookie`, `CookieList` for `Response.cookies`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `unsupportedError`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.response` in `initInsomniaObject` via `toScriptResponse`/`readBodyFromPath`.\n",".claude/skills/fix-scripting-feature/references/objects/send-request.md":"# sendRequest (pm.sendRequest)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/send-request.ts`\n\n## Purpose\nImplements the standalone `sendRequest()` function that backs `pm.sendRequest()` / `insomnia.sendRequest()` — letting a pre-request/after-response script fire an ad-hoc HTTP request (via libcurl) and get a `Response` back, either through a Node-style callback or a returned promise. It also contains the translation layer from the SDK's `Request`/string/`RequestOptions` shapes into the curl request options consumed by Insomnia's network layer, and from the raw curl output back into a `Response` object.\n\n## Public API\n\n### `sendRequest(request, cb, settings)`\n```ts\nexport async function sendRequest(\n  request: string | Request | RequestOptions,\n  cb: (error?: string, response?: Response) => void,\n  settings: Settings,\n): Promise<Response | void>\n```\n- Builds curl options from `request` via `requestToCurlOptions(request, settings)`.\n- Picks the curl execution path based on environment: in the renderer (`__IS_RENDERER__` true) it uses `window.bridge.curlRequest`; otherwise it dynamically imports `curlRequest` from `insomnia/src/main/network/libcurl-promise`.\n- Awaits the curl call, converts the raw `CurlRequestOutput` to a `Response` via `curlOutputToResponse(output, request)`.\n- **Success:** if `cb` is provided, calls `cb(undefined, transformedOutput)`; either way resolves the returned promise with `transformedOutput`.\n- **Failure:** if `cb` is provided, calls `cb(e)` and resolves the promise with `undefined` (does **not** reject); if no `cb` is provided, rejects the promise with `e`.\n\n### `requestToCurlOptions(req, settings)` (not exported)\n```ts\nfunction requestToCurlOptions(req: string | Request | RequestOptions, settings: Settings)\n```\n- If `req` is a `string`: treats it as a bare URL, builds a minimal GET request object (`no body`, `noauth`, empty headers/cookies, `settingRebuildPath: true`, `settingSendCookies: true`, `settingFollowRedirects` derived from `settings.followRedirects ? 'on' : 'off'`), with `requestId` prefixed `pre-request-script-adhoc-req-simple:<uuid>`.\n- If `req` is a `Request` instance or a plain object (`RequestOptions`): coerces to a `Request` (`new Request(req)` if not already one), derives `mimeType` from `finalReq.body.mode` (`raw`→`text/plain`, `file`→`application/octet-stream`, `formdata`→`multipart/form-data`, `urlencoded`→`application/x-www-form-urlencoded`, `graphql`→`application/json`, otherwise `text/plain`), maps headers/body/certificate/auth into the curl request shape, with `requestId` = `finalReq.id` or a generated `pre-request-script-adhoc-req-custom:<uuid>`.\n- Auth mapping beyond `noauth` is largely commented out / marked `TODO` (see Gotchas).\n- Throws `Error('the request type must be: string | Request | RequestOptions.')` for any other input shape.\n\n### `curlOutputToResponse(result, request)` (not exported)\n```ts\nasync function curlOutputToResponse(\n  result: CurlRequestOutput,\n  request: string | Request | RequestOptions,\n): Promise<Response>\n```\n- Throws if `result.headerResults` is empty, or if `result.patch.error` is set (re-throws that error), or if there's no last redirect entry.\n- Normalizes `request` into a `Request` instance for `originalRequest`.\n- Extracts headers from the last redirect's `headerResults` entry; parses any `Set-Cookie` headers via `tough-cookie`'s `Cookie.parse(..., { loose: true })` into cookie option objects (filtering out unparsable ones).\n- If `result.responseBodyPath` is absent, returns a `Response` with `body: ''`.\n- Otherwise reads the body via `services.helpers.readCurlResponse({ bodyPath, bodyCompression })`; throws if that read reports an error; otherwise returns a fully populated `Response` (`code`, `reason`, `header`, `cookie`, `body`, `responseTime: result.patch.elapsedTime`, `originalRequest`). `stream` is always left `undefined` (\"because it is inaccurate to differentiate if body is binary\").\n\n## Script-facing surface\n- `pm.sendRequest(url, (err, response) => { ... })` — string URL, GET request, callback style.\n- `pm.sendRequest({ url, method, header, body, auth, ... }, (err, response) => { ... })` — full `RequestOptions`-shaped object.\n- `pm.sendRequest(requestInstance, (err, response) => { ... })` — passing an existing `Request` instance (e.g. built via `new Request(...)` or `pm.request`).\n- Can also be used promise-style without a callback: `const response = await pm.sendRequest(url)` (see Gotchas for the resulting error-handling difference).\n- Exposed on the object model as `insomnia.sendRequest(request, cb)`, which is a plain instance method (not proxy-trapped) that forwards to this file's `sendRequest(request, cb, this._settings)`.\n\n## Gotchas / notable behavior\n- **Callback vs. promise error handling differ:** if a `cb` is supplied, network/parsing errors are *swallowed* into the callback (`cb(e)`) and the returned promise still resolves (with `undefined`) — it will not reject. If no `cb` is supplied, the same error instead rejects the returned promise. A script mixing `await pm.sendRequest(url, cb)` with `try/catch` will not catch curl errors, because the promise resolves successfully even on failure when a callback is present.\n- **Environment branching:** `__IS_RENDERER__ ? window.bridge.curlRequest : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest` — the actual network call goes through completely different code paths depending on whether the script executes in the renderer (Electron IPC bridge) or Node/main context. Bugs that only reproduce in one context (e.g. inso CLI vs. the desktop app) may live in this branch.\n- **Auth beyond `noauth` is not wired up** for the custom-`Request` branch: there's a large commented-out block (`// const authHeaders = ...`) showing API-key/bearer header injection was planned but not implemented; `fromPreRequestAuth(finalReq.auth)` is still called and passed through as `authentication`, but the `authHeader` field of the curl options is always `undefined` with a `// TODO: add this for bearer and other auth methods` comment.\n- **Certificates:** only a single client certificate is forwarded (`finalReq.certificate`), built directly into the curl options' `certificates` array; several fields (`disabled`, `isPrivate`, `_id`, `type`, `parentId`, `modified`, `created`, `name`) are hardcoded to empty/`false`/`0` since they're \"unused fields because they are not persisted\".\n- **Cookies are not populated from a jar** — `cookieJar: { cookies: [] }` and `cookies: []` are always empty in the outgoing request; the comment notes \"currently cookies should be handled by user in headers\". Response `Set-Cookie` headers are still parsed back out via `tough-cookie`, though.\n- **`suppressUserAgent` detection** only checks for a *disabled* `User-Agent` header (`h.key.toLowerCase() === 'user-agent' && h.disabled === true`) — an enabled custom `User-Agent` header does not suppress the default one; it presumably just overrides via normal header precedence.\n- **Response body/stream:** `stream` is always `undefined` in the returned `Response`, even though `CurlRequestOutput`/`ResponseOptions` support it, specifically to avoid ambiguity between binary and text bodies — callers should rely on `body` (a string) only.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request`, `RequestOptions`, `RequestBody`/`RequestBodyOptions` types this function consumes as input.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response` class this function constructs as output.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used to translate the SDK's auth model into the curl request's `authentication` field.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `CookieOptions` type used for parsed `Set-Cookie` results.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — exposes this function as `insomnia.sendRequest(request, cb)`, supplying `this._settings` as the `settings` argument.\n- `insomnia/src/main/network/libcurl-promise` (`curlRequest`, `CurlRequestOutput`) — the main/Node-side curl execution path, dynamically imported when not running in the renderer.\n- `window.bridge.curlRequest` — the renderer-side IPC bridge equivalent used when `__IS_RENDERER__` is true.\n- `insomnia-data` (`services.helpers.readCurlResponse`, `Settings` type) — used to read the response body from disk (`responseBodyPath`) and for the `Settings` (e.g. `followRedirects`) passed in.\n",".claude/skills/fix-scripting-feature/references/objects/test.md":"# Test handler (pm.test / test())\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/test.ts`\n\n## Purpose\nImplements the `test()`/`skip()` handler that backs `pm.test(...)` (and `insomnia.test(...)`), plus the bookkeeping used by the script-execution harness to know when all in-flight test callbacks have settled before the script finishes. This is the only file responsible for turning a user's test callback into a `RequestTestResult` record.\n\n## Public API\n\n### `test(msg, fn, log)`\n```ts\nexport async function test(\n  msg: string,\n  fn: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Wraps `fn` in an async closure (`wrapFn`), times its execution with `performance.now()`, awaits it, and calls `log(...)` with a `RequestTestResult`:\n  - On success: `{ testCase: msg, status: 'passed', executionTime, category: 'unknown' }`.\n  - On thrown error `e`: `{ testCase: msg, status: 'failed', executionTime, errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, category: 'unknown' }`.\n- Immediately invokes `wrapFn()`, registers the resulting promise with `startTestObserver`, and returns that same promise (`testPromise`) — i.e. `test()` itself resolves once the wrapped test body (pass or fail) has finished running.\n- `category` is always hardcoded to `'unknown'` here even though `RequestTestResult.category` (in `insomnia-data`) also allows `'pre-request'` and `'after-response'`.\n\n### `skip(msg, _, log)`\n```ts\nexport async function skip(\n  msg: string,\n  _: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Does **not** call `fn` (the second argument is ignored). Immediately calls `log({ testCase: msg, status: 'skipped', executionTime: 0, category: 'unknown' })`.\n\n### `resetTestPromises()`\n```ts\nexport function resetTestPromises(): void\n```\n- Clears the module-level `testPromises` array (`testPromises = []`). Used to reset state between script executions so promises from a previous run aren't awaited again.\n\n### `waitForAllTestsDone()`\n```ts\nexport async function waitForAllTestsDone(): Promise<void>\n```\n- Awaits `Promise.allSettled(testPromises)` (captured as `NativePromise` at module load, so it isn't affected by any sandbox-level `Promise` patching), then resets `testPromises` back to `[]`.\n- This is the drain point the execution harness calls at the end of a script run to make sure every `pm.test(...)` callback (including ones the script itself didn't `await`) has finished before results are collected.\n\n### `startTestObserver(promise)` (not exported)\n```ts\nfunction startTestObserver(promise: Promise<void>): void\n```\n- Pushes `promise` onto the module-level `testPromises` array. Called once per `test()` invocation.\n\n### `TestHandler` interface\n```ts\nexport interface TestHandler {\n  (msg: string, fn: () => Promise<void>): Promise<void>;\n  skip?: (msg: string, fn: () => Promise<void>) => void;\n}\n```\n- Callable interface shape used by the object model to type the value returned for `pm.test` — a function that also carries an optional `.skip` method.\n\n## Script-facing surface\n- `pm.test('name', async () => { ... })` / `insomnia.test('name', fn)` — registers and immediately runs a test, recording a pass/fail result.\n- `pm.test.skip('name', fn)` — records a `skipped` result without running `fn`.\n- There is no bare global `test`/`skip` exposed directly by this file; script-facing exposure happens through the `insomnia`/`pm` object (see Related).\n\n## Gotchas / notable behavior\n- **Fire-and-forget by default:** `test()` starts executing the test body synchronously (well, as soon as the async function runs) and returns a promise, but scripts are not required to `await pm.test(...)`. Because every test promise is also pushed into the shared `testPromises` array via `startTestObserver`, un-awaited tests are still tracked and will be waited on by `waitForAllTestsDone()` before the script finishes — this is what prevents test results from being lost when a script fires multiple `pm.test()` calls without awaiting them.\n- **Module-level mutable state:** `testPromises` is a module-level array, not per-execution-context state. `resetTestPromises()` must be called before a script runs and `waitForAllTestsDone()` after, or results/timing from different executions could leak into each other (relevant if concurrent script executions ever share this module instance).\n- **Error message shape assumes chai-style errors:** the failure branch does `` `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}` ``, which assumes `e` has `.actual`/`.expected` (true for chai `AssertionError`s from `insomnia.expect(...)`). For a plain thrown `Error` or non-chai exception, `ACTUAL`/`EXPECTED` will stringify as `undefined`.\n- **`category` is never anything but `'unknown'`** from this module — any pre-request vs. after-response distinction in `RequestTestResult.category` must be set elsewhere (or is currently unused/always `'unknown'` for script-originated tests).\n- **`NativePromise` capture:** `const NativePromise = Promise;` is captured at module import time specifically so `waitForAllTestsDone`'s `allSettled` call uses the real, un-sandboxed `Promise`, not a possibly-patched one from the script sandbox.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — wires `test`/`skip` into the `InsomniaObject`. The constructor returns a `Proxy` whose `get` trap intercepts `prop === 'test'` and returns a `TestHandler` closure calling `this._test(msg, fn, this.pushRequestTestResult)` (and `.skip` calling `this._skip(...)`); `pushRequestTestResult` appends to `this.requestTestResults: RequestTestResult[]`, later returned from `toObject()`.\n- `packages/insomnia-scripting-environment/src/objects/async-objects.ts` — calls `resetTestPromises()` once at module load alongside its own promise-tracking logic for `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) and `packages/insomnia/src/scripting/run-script.ts` — the harness that injects `__waitForAllTestsDone__` into the generated script function and awaits it (`await __waitForAllTestsDone__();`) before returning the mutated `insomnia` object; `run-script.ts` then reads `mutatedInsomniaObject.toObject().requestTestResults` and includes it in the returned `RequestContext`.\n- `packages/insomnia-data` (`RequestTestResult`, `TestStatus`, `TestCategory` in `src/models/runner-test-result.ts`) — the result shape this file produces.\n- UI consumers of the resulting test results: `packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx` and `request-result-card.tsx`.\n",".claude/skills/fix-scripting-feature/references/objects/urls.md":"# Url, QueryParam, UrlMatchPattern\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/urls.ts`\n\n## Purpose\n\nModels a request URL (`Url`), its individual query-string parameters (`QueryParam`), and Chrome-extension-style match patterns (`UrlMatchPattern` / `UrlMatchPatternList`) used to test whether a URL matches a proxy bypass rule or a client-certificate host rule. `pm.request.url` is a `Url`; `UrlMatchPattern` backs `ProxyConfig.match` and `Certificate.matches`.\n\n## Public API\n\n### `setUrlSearchParams(provider: any)`\nModule-level override hook: replaces the `URLSearchParams` implementation used internally by `QueryParam` (defaults to the global `URLSearchParams`). Test-only utility, not script-facing.\n\n### `QueryParamOptions` (interface)\n`{ key: string; value?: string; type?: string; multiline?: string | boolean; disabled?: boolean; fileName?: string }`\n\n### `QueryParam` (extends `Property`)\n- `override _kind = 'QueryParam'`\n- `key: string`, `value?: string`, `type?: string`, `multiline?: string | boolean`, `fileName?: string` (the latter two are noted in a comment as \"properties from Insomnia... added here to avoid being dropped\").\n- `constructor(options: QueryParamOptions | string)` — if `options` is a string, it's `JSON.parse`d (wraps parse errors as `Error('invalid QueryParam options ...')`); if it's an object with `key`+`value`, fields are copied directly; otherwise **throws** `Error('unknown options for new QueryParam')`.\n- `static override _index = 'key'`\n- `static parse(queryStr: string): {key, value}[]` — parses a query string via `URLSearchParams`.\n- `static parseSingle(paramStr: string, _idx?, _all?): {key, value}` — parses a single `key=value` pair; **throws** if `QueryParam.parse` yields nothing.\n- `static unparse(params: QueryParamOptions[] | Record<string, string>): string` — builds a query string via `URLSearchParams`, URL-encoding as it goes.\n- `static unparseSingle(obj: {key, value}): string | {}` — returns `''`-joined encoded `\"key=value\"` if the input has both `key` and `value`, otherwise returns `{}` (an empty object, not a string — inconsistent return type).\n- `toString(): string` — URL-encodes via `URLSearchParams` (e.g. spaces become `+`).\n- `toRawString(): string` — `\"key=value\"` with **no encoding**.\n- `update(param: string | {key, value, type?})` — parses a string form via `parseSingle` (only sets `key`/`value`, coercing non-string results to `''`) or copies `key`/`value`/`type` from an object; **throws** on any other input shape.\n\n### `UrlOptions` (interface)\n`{ id?: string; auth?: {username, password}; hash?: string; host: string[]; path?: string[]; port?: string; protocol: string; query: {key, value}[]; variables: {key, value}[] }`\n\n### `Url` (extends `PropertyBase`)\n- `override _kind = 'Url'`\n- `id?: string`\n- Getters (all derived from an internal `URL` object, `this.urlObject`, which is `undefined` if the input couldn't be parsed as a URL — e.g. contains unrendered `{{ }}`/`{% %}` template tags): `auth` (`{username, password}` or `undefined`), `hash` (without leading `#`), `host` (hostname split on `.`), `path` (pathname segments, empty ones filtered out), `port`, `protocol`, `query: PropertyList<QueryParam>` (a **new** `PropertyList` wrapping the current query params on every access), `variables: string[]` (**always returns `[]`** — \"TODO: it's usage is unknown\").\n- `constructor(def: UrlOptions | string)`\n- `private initFields(urlOptions)` — parses `def`; if it's a string containing a template tag (`checkIfUrlIncludesTag`), it's kept as an opaque `origin` string and `urlObject` stays `undefined` (to avoid mangling `{% uuid 'v4' %}`-style tags); if it's an object, a URL string is assembled from `protocol`/`auth`/`host`/`port`/`path`/`query`/`hash` (protocol defaults to `'https://'` if blank). Query params are always parsed out into `this.queryParams` (a private array) separately from the `URL` object (whose own `search` is cleared) — \"query params are handled separately as URL object encodes content\".\n- `static _index = 'id'`\n- `static isUrl(obj: object): boolean`\n- `static parse(urlStr: string): UrlOptions | undefined` — returns `undefined` if `URL.canParse` fails; `variables` is always `[]` in the result.\n- `addQueryParams(params: QueryParamOptions[] | string)` — string form splits on `&` then `=` (not URL-decoded); array form copies each entry into a new `QueryParam`. **Throws** `TypeError` on other input.\n- `getHost(): string` — `''` if the URL didn't parse.\n- `getPath(_unresolved?): string` — `_unresolved` param is accepted but unused; `''` if unparsed.\n- `getPathWithQuery(): string` — path + `?` + query string (or just the query string if path is blank).\n- `getQueryString(): string` — joins **enabled** (`!disabled`) query params via `toRawString()` (i.e. **unencoded**, unlike `QueryParam.toString()`).\n- `getRemote(_forcePort?): string` — `_forcePort` is accepted but unused; returns `urlObject.host` (which already includes the port when non-default) or `''`.\n- `removeQueryParams(params: QueryParam[] | string[] | string)` — filters `this.queryParams` by key; **throws** `TypeError` on unrecognized input shape.\n- `override toString(_forceProtocol?): string` — `_forceProtocol` accepted but unused; rebuilds the URL string with the current query string; falls back to the opaque `origin` string if unparsed; special-cases avoiding an added trailing `/` for root-path URLs.\n- `toStringWithoutQuery(_forceProtocol?): string` — same as `toString()` but with `search` cleared.\n- `update(url: UrlOptions | string)` — re-runs `initFields`.\n\n### `UrlMatchPattern` (extends `Property`)\nImplements [Chrome extension match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns) (`scheme://host/path`, wildcards, `<all_urls>`); comment notes it doesn't support top-level-domain wildcards, and that URLs can't start with `-` (unenforced).\n- `override id = ''`\n- `constructor(pattern: string)`\n- `static override _index = 'id'`\n- `static readonly MATCH_ALL_URLS = '<all_urls>'`\n- `static pattern: string | undefined = undefined` — declared but unused (\"TODO: its usage is unknown\"); shadowed by (unrelated to) the instance's private `pattern`.\n- `static readonly PROTOCOL_DELIMITER = '+'` — multiple schemes are written as `http+https+custom://...`.\n- `getProtocols(): string[]` — `['http', 'https', 'file']` for `<all_urls>`; otherwise splits the scheme segment on `+`.\n- `test(urlStr: string): boolean` — true only if protocol, host, path, and port all match.\n- `testHost(hostStr: string): boolean` — segment-by-segment comparison against the pattern's host (split on `.`); a segment of `*` matches anything; segment **counts must match exactly** (no subdomain-wildcard-prefix support beyond single-segment `*`).\n- `testPath(pathStr: string): boolean` — segment-by-segment comparison (split on `/`); segment counts must match exactly; `*` matches any single segment.\n- `testPort(port: string, protocol: string): boolean` — returns `false` immediately if `testProtocol(protocol)` fails; `*` in the pattern matches any port; if no port is specified in either the pattern or input, falls back to protocol-default-port logic for `http`/`https` only.\n- `testProtocol(protocol: string): boolean` — `*` in the pattern matches any protocol.\n- `override toString(): string` — returns the raw pattern string.\n- `update(pattern: string)`\n\n### `UrlMatchPatternList<T extends UrlMatchPattern>` (extends `PropertyList<T>`)\n- `override _kind = 'UrlMatchPatternList'`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])`\n- `static isUrlMatchPatternList(obj: any): boolean`\n- `test(urlStr: string): boolean` — true if **any** pattern in the list matches.\n\n### Module-level functions\n- `toUrlObject(url: string | Url): Url` — **throws** `Error('Request URL is not specified')` if `url` is falsy; passes through an existing `Url` unchanged, otherwise constructs a new one from a string.\n- `resolveProtocolForProxy(rawUrl: string): string` — resolves the protocol to use for proxy selection when the URL may still contain unrendered template tags (pre-request scripts run before rendering); tries `new URL(rawUrl).protocol` first, falling back to `'https:'` on parse failure.\n\n## Script-facing surface\n\n`pm.request.url` is a `Url` instance. Common script usage:\n- `pm.request.url.toString()`, `pm.request.url.getHost()`, `pm.request.url.getPath()`, `pm.request.url.query`\n- `pm.request.url.addQueryParams([{key, value}])` / `pm.request.addQueryParams(...)` (delegates)\n- `pm.request.url.removeQueryParams('key')` / `pm.request.removeQueryParams(...)` (delegates)\n- `pm.request.url.hash`, `.host`, `.port`, `.protocol`, `.auth`\n\n`QueryParam` instances populate `pm.request.url.query` and `RequestBody.urlencoded`.\n\n`UrlMatchPattern`/`UrlMatchPatternList` aren't constructed directly from a typical script, but are reachable via `pm.request.certificate.matches` (see `certificates.ts`), which stores its match rule(s) as a `UrlMatchPatternList` directly. `pm.request.proxy.match` (see `proxy-configs.ts`) is different: it's a raw `string` on the instance, with a `UrlMatchPattern` built on the fly, temporarily, inside `ProxyConfig.test()`/`.getProtocols()` — and `ProxyConfig.bypass` is a plain `string[]` checked by exact string equality, not a `UrlMatchPatternList` at all (that type only appears as an unused `static bypass` field on the class, separate from the instance field of the same name).\n\n## Gotchas / notable behavior\n\n- `Url.variables` **always returns `[]`**, even though `UrlOptions.variables` and `Url.parse()`'s return both have a `variables` field — the field is accepted on input but silently discarded; there is no way to read it back out.\n- `Url`'s internal `urlObject` (a real `URL`) is `undefined` whenever the input string contains a template tag (`{{ }}` / `{% %}`) or otherwise fails `URL.canParse` — in that case nearly every getter/method (`host`, `path`, `port`, `protocol`, `getHost()`, `getRemote()`, etc.) silently returns an empty string/array instead of throwing. Only `toString()`/`toStringWithoutQuery()` fall back to the raw `origin` string.\n- Query-string encoding is inconsistent by design: `QueryParam.toString()` URL-encodes (via `URLSearchParams`), but `QueryParam.toRawString()` and `Url.getQueryString()` (which uses `toRawString()`) do **not** encode. `Url.addQueryParams(stringForm)` also splits on raw `&`/`=` without decoding.\n- `QueryParam.unparseSingle()` returns `{}` (not a string) when the input lacks `key`/`value` — inconsistent return type (`string | {}`) that callers must handle.\n- Several methods accept parameters that are **entirely unused**: `Url.getPath(_unresolved)`, `Url.getRemote(_forcePort)`, `Url.toString(_forceProtocol)`, `Url.toStringWithoutQuery(_forceProtocol)`, `QueryParam.parseSingle(_idx, _all)`. Passing anything for these has no effect.\n- `UrlMatchPattern.static pattern` (class-level) is unused/dead and easy to confuse with the instance's own private `pattern` field.\n- `UrlMatchPattern.testHost`/`testPath` require the **segment counts to match exactly** — a pattern like `*.insomnia.com` (2 segments) will not match `bin.download.insomnia.com` (4 segments), confirmed by the test suite (`urls.test.ts`).\n- `UrlMatchPattern` has no protocol-agnostic short-circuit: calling `testProtocol` on a pattern with no `://` returns `[]` for `getProtocols()`, meaning `testProtocol` always returns `false` for such a malformed pattern (per `urls.test.ts`, \"no protocol\" case).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes; `PropertyList.toObject()`'s fallback comment specifically calls out `UrlMatchPatternList` as a list with no natural key.\n- `packages/insomnia-scripting-environment/src/objects/utils.ts` — `checkIfUrlIncludesTag`, used by `Url.initFields` to avoid mangling template tags.\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.url: Url`; `toUrlObject` used when constructing a `Request`; `RequestBody.urlencoded: PropertyList<QueryParam>`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate.matches: UrlMatchPatternList<UrlMatchPattern>`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig.match: string` is used to construct a `UrlMatchPattern` on demand inside `test()`/`getProtocols()`; the instance's `ProxyConfig.bypass` is a plain `string[]` (exact-match only), not a `UrlMatchPatternList`.\n- `packages/insomnia-scripting-environment/src/objects/collection.ts` — re-exports `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` as part of the public collection API surface.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — uses `resolveProtocolForProxy` and `toUrlObject` in `initInsomniaObject`.\n",".claude/skills/fix-scripting-feature/references/objects/utils.md":"# Utils (misc helpers)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/utils.ts`\n\n## Purpose\nSingle-function utility module. Currently contains only a helper used to detect whether a request\nURL still contains unresolved template-tag syntax, which affects client-certificate matching in\n`insomnia.ts`.\n\n## Public API\n\n### `function checkIfUrlIncludesTag(url: string): boolean`\n```ts\nexport function checkIfUrlIncludesTag(url: string): boolean {\n  return /{%/.test(`${url}`) || /%}/.test(`${url}`) || /{{/.test(`${url}`) || /}}/.test(`${url}`);\n}\n```\nReturns `true` if the given `url` (coerced to a string via template literal) contains any of the\nfour Liquid/interpolation delimiter substrings: `{%`, `%}`, `{{`, `}}`. Used as a proxy for \"this\nURL hasn't been fully rendered yet\" / \"this URL has dynamic template parts that can't be resolved\nagainst a fixed hostname\".\n\n## Script-facing surface\nNone. This is internal plumbing — it is not exposed on `insomnia`/`pm` and scripts cannot call it.\n\n## Gotchas / notable behavior\n- The check is purely textual (four independent regex tests, not a real template parser) — it will\n  return `true` for a URL that merely contains a literal `{{` or `}}` substring even if it isn't\n  actually meant as an interpolation tag.\n- `` `${url}` `` coercion means passing `undefined`/`null`/non-string values won't throw; they'll be\n  stringified first (e.g. `undefined` becomes the string `\"undefined\"`, which does not match any\n  pattern, so the function returns `false`).\n\n## Related\n- `insomnia.ts` — the only current caller. `initInsomniaObject` uses `checkIfUrlIncludesTag(rawObj.request.url)` together with `filterClientCertificates` (from `insomnia/src/network/certificate`) to decide whether to fall back to an empty default client certificate (since a templated URL's real host can't be matched against certificate rules yet).\n",".claude/skills/fix-scripting-feature/references/objects/variables.md":"# Variable / VariableList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/variables.ts`\n\n## Purpose\nThis file defines the low-level key/value model — `Variable` (a single named, typed value) and `VariableList` (an ordered, keyed collection of `Variable`s) — built on top of `Property`/`PropertyList` from `properties.ts`. Unlike `Environment` (a `Map`-backed flat store used for `pm.environment`/`pm.globals`), `Variable`/`VariableList` is the object-per-entry model used elsewhere in the SDK wherever a Postman-compatible \"list of variable-shaped items\" is needed (e.g. request path parameters); it is not the type behind `insomnia.environment` or `insomnia.variables`.\n\n## Public API\n\n### `interface VariableDefinition`\n```ts\ninterface VariableDefinition {\n  id?: string;\n  key: string;\n  name?: string;\n  value: string;\n  type?: string;\n  disabled?: boolean;\n}\n```\nShape used to construct a `Variable`.\n\n### `class Variable extends Property`\n```ts\nconstructor(def?: VariableDefinition)\n```\nCalls `super()` (no args passed to `Property`'s constructor, so `Property`'s own `id`/`name`/`disabled` defaults are set first, then overwritten below). When `def` is provided: `id = def.id || ''`, `key = def.key`, `name = def.name`, `value = def.value`, `type = def.type || 'Variable'`, `disabled = def.disabled`. When `def` is omitted: `id = ''`, `key = ''`, `name = undefined`, `value = ''`, `type = 'Variable'`, `disabled = false`.\n\n- `key: string` — the variable's lookup key.\n- `value: any` — the stored value (any type, despite `VariableDefinition.value` being typed `string`).\n- `type: string` — defaults to `'Variable'`.\n- `override _kind = 'Variable'` *(`@ignore`)*.\n- `static override _index = 'key'` *(`@ignore`)* — tells `PropertyList`/`indexOf`/`one` to index `Variable`s by `key` instead of the default `id`.\n- `static types()` — **throws** `unsupportedError('types')` unconditionally; not implemented.\n- `cast(value: any)` — if `value` has `_kind === 'Variable'`, returns `value.value` (unwraps a `Variable` to its raw value); otherwise returns `undefined`.\n- `get()` — returns `this.value`.\n- `set(value: any)` — sets `this.value = value`.\n\n### `class VariableList<T extends Variable> extends PropertyList<T>`\n```ts\nconstructor(parent: PropertyList<T> | undefined, populate: T[])\n```\nCalls `super(Variable, undefined, populate)` (always uses `Variable` as `typeClass`, ignoring `T`'s actual class for indexing purposes), then sets `this.parent = parent`.\n\n- `override _kind = 'VariableList'` *(`@ignore`)*.\n- `static isVariableList(obj: any): boolean` — `'_kind' in obj && obj._kind === 'VariableList'`.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, any>` — builds a plain object keyed by each `Variable.key`:\n  - skips entries where `excludeDisabled && variable.disabled`.\n  - if `multiValue` is true and the key already exists in the output, coalesces values into an array (`[existing, value]`, growing it on further duplicates).\n  - otherwise, later entries with the same key overwrite earlier ones (last-write-wins) when `multiValue` is falsy.\n  - `_caseSensitive` and `_sanitizeKeys` are accepted for signature compatibility but unused.\n\nAll other list operations (`add`, `all`, `append`, `assimilate`, `clear`, `count`, `each`, `filter`, `find`, `get`, `has`, `idx`, `indexOf`, `insert`, `insertAfter`, `map`, `one`, `populate`, `prepend`, `reduce`, `remove`, `repopulate`, `toString`, `upsert`) are inherited unmodified from `PropertyList` — see `properties.md`. Because `Variable._index = 'key'`, `indexOf`/`one`/`upsert`/`has` on a `VariableList` match by `key`, not `id`.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` on its own — `insomnia.environment` and `insomnia.variables` are backed by `Environment`/`Variables` (see `environments.md`), not by `Variable`/`VariableList`. `Variable`/`VariableList` are the generic building blocks other collection-shaped properties reuse (e.g. anywhere the SDK needs an ordered, keyed set of `{key, value}` items with Postman-style list semantics such as `.upsert()`/`.toObject()`). Exported from the package's public surface via `index.ts` (`export { Variable, VariableList } from './variables'`) and re-exported under the `Collection` namespace via `collection.ts`, so consumers of the SDK's type declarations can reference `Collection.Variable`/`Collection.VariableList`.\n\n## Gotchas / notable behavior\n- `Variable.types()` is a stub that always throws `unsupportedError('types')` — calling it will break a script; it exists only for API-shape completeness.\n- `Variable.cast(value)` assumes `value` is an object with `in` support (`'_kind' in value`) — passing a primitive (e.g. `cast(5)`) will throw a `TypeError` since `in` requires an object operand on the right-hand side. There's no type guard before that check.\n- `VariableList`'s constructor hardcodes `Variable` as the `typeClass` regardless of the generic `T`, so subclasses of `Variable` (if any) still get indexed as plain `Variable` for `_index` purposes.\n- `VariableList.toObject()` **includes disabled variables by default** — callers must explicitly pass `excludeDisabled = true` to filter them out (confirmed by `variables.test.ts`: `toObject()` returns `{h1: 'v1', h2: 'v2'}` even when `h2` is disabled).\n- Duplicate keys: `toObject()` silently drops earlier duplicates unless `multiValue` is passed, in which case duplicates become an array under a single key — this can be surprising if a script assumes object-key uniqueness maps 1:1 to list length.\n\n## Related\n- `properties.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>`; inherits nearly all list/property behavior from there.\n- `environments.ts` — conceptually parallel/alternate model (`Environment`/`Variables`) that is what `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables` actually use; do not confuse the two when debugging variable-resolution issues.\n- `index.ts` / `collection.ts` — re-export `Variable`/`VariableList` as part of the SDK's public/namespaced surface.\n",".claude/skills/fix-test-cli-ci/SKILL.md":"---\nname: fix-test-cli-ci\ndescription: 'Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.'\nargument-hint: 'Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso'\n---\n\n# Fix test-cli.yml CI Failures\n\n## When to Use\n\n- `.github/workflows/test-cli.yml` or the `Test CLI` workflow failed in CI.\n- `inso` bundle tests fail locally or in GitHub Actions.\n- You suspect node module/runtime differences between Node.js and Electron.\n\n## Procedure\n\n1. Start with the failing CI evidence.\n   - Use the provided logs or workflow run link to identify whether the failure happened during the `test:bundle` run, the packaged binary run, or the build/package setup before the tests.\n   - Note the failing test name and the first actionable error message before reproducing locally.\n2. Ensure Node.js native dependencies are installed (not Electron-targeted variants):\n   ```bash\n   npm run install-libcurl-node\n   ```\n3. Build `insomnia-inso` to generate `dist`:\n   ```bash\n   npm run build -w insomnia-inso\n   ```\n4. Start the smoke-test echo server used by the CLI tests:\n   ```bash\n   npm run serve -w insomnia-smoke-test\n   ```\n5. In another terminal, run the bundled CLI test suite:\n   ```bash\n   npm run test:bundle -w insomnia-inso\n   ```\n6. If the CI failure was in the packaged binary path or the bundle suite passes locally, run:\n   ```bash\n   npm run test:binary -w insomnia-inso\n   ```\n\n## Notes\n\n- Keep the smoke-test server running while bundle tests execute.\n- Common failure patterns:\n  - Errors involving `@getinsomnia/node-libcurl`, `NODE_MODULE_VERSION`, `dlopen`, or native module loading usually mean the Node-targeted libcurl binary needs to be reinstalled with `npm run install-libcurl-node`.\n  - Connection failures to `http://localhost:4010` usually mean the smoke-test server is not running.\n  - Missing `dist` output or missing `binaries/inso` usually means `npm run build -w insomnia-inso` or the package step needs to be rerun.\n- Success criteria:\n  - `npm run test:bundle -w insomnia-inso` exits successfully and Vitest reports the `inso dev bundle` tests as passing.\n  - If validating the packaged binary path, `npm run test:binary -w insomnia-inso` also exits successfully.\n\n## Teardown\n\n- Stop the `npm run serve -w insomnia-smoke-test` process once validation is complete.\n- Reinstall the electron-targeted libcurl binary for continued Electron development:\n  ```bash\n  npm run install-libcurl-electron\n  ```\n","AGENTS.md":"# AGENTS.md\n\n## Tech Stack\n\n- **UI:** React with React Router (loaders/actions pattern)\n- **Components:** React Aria Components\n- **Desktop Shell:** Electron (main + renderer processes)\n- **Styling:** TailwindCSS\n- **Language:** TypeScript\n- **Database:** NeDB (`@seald-io/nedb`) — embedded NoSQL\n- **Build/Dev:** Vite, npm workspaces monorepo\n\n_See `package.json` for current versions and `.nvmrc` for the Node version._\n\n## Strict Rules\n\n- **No unsolicited formatting.** Rely on ESLint/Prettier. Do not reformat existing code.\n- **Strict scoping.** Only modify code directly related to the prompt. Do not refactor adjacent code unless asked.\n\n## Command Output\n\nPrefer quiet command variants to minimise output volume:\n\n- `git log --oneline -20` not `git log`\n- `git diff --stat` not `git diff`\n- `npm test --silent` not `npm test`\n- `tsc --noEmit 2>&1 | head -50` for type-check failures\n- Use the `Read` tool with `limit` rather than `cat` on large files\n- Use `Grep` with `head_limit` rather than unrestricted searches\n\n## Validation Commands\n\nRun from repo root before considering work complete:\n\n```bash\nnpm run lint          # ESLint all workspaces\nnpm run type-check    # TypeScript check all workspaces\nnpm test              # Tests all workspaces (or: npm test -w packages/insomnia)\n```\n\n## Worktree Setup\n\n- New git worktrees may not have `node_modules` yet. Before installing or validating, switch to the repo's required runtime from the worktree root:\n\n```bash\nfnm use \"$(cat .nvmrc)\"\nnode -v\nnpm -v\n```\n\n- This repo expects the `.nvmrc` Node version and npm 11+. If `fnm` is unavailable, manually use an equivalent Node/npm version before running any `npm` commands.\n- After switching versions in a fresh worktree, install dependencies from repo root with `npm ci`.\n- Do **not** use `npm ci --ignore-scripts` for normal worktree setup. It leaves Electron partially installed, which later breaks builds, renderer import checks, and other validation commands.\n\n## Repository Structure\n\n`packages/`\n  `insomnia/`                ← Main Electron app\n    `src/`\n      `common/`              ← Shared utils, settings types\n      `routes/`              ← React Router files (clientLoader/clientAction)\n      `ui/`                  ← React components, hooks, `insomnia-fetch.ts`\n      `main/`                ← Electron IPC handlers, `preload.ts`\n      `account/`             ← Auth, session, encryption\n      `sync/`                ← Git/VCS sync\n      `network/`             ← Request execution engine\n      `templating/`          ← Nunjucks rendering (Web Worker)\n  `insomnia-data/`           ← Data models, services, NeDB implementation, shared data utilities\n  `insomnia-api/`            ← Cloud API client\n  `insomnia-inso/`           ← CLI tool\n  `insomnia-testing/`        ← Test framework\n\n## Data Model Hierarchy\n\nOrganization\n  → Project (local | remote/cloud | git-backed)\n    → Workspace (scope: 'collection' | 'design')\n      → Base Environment (auto-created: use `models.environment.getOrCreateForParentId(workspaceId)`)\n        → Sub-Environments\n      → Cookie Jar (auto-created)\n      → Request Group (folders)\n        → Request (HTTP, GraphQL, gRPC, WebSocket, Socket.IO)\n      → Request (can be direct child of workspace)\n**Note:** A Workspace with `scope: 'collection'` IS the collection.\n\n## Key Patterns\n\n- **Route-Based Actions:** Mutations use React Router's `clientAction` (`src/routes/`).\n  - **CRITICAL:** `clientAction` blocks navigation. For long-running UI operations, use plain async functions instead.\n- **Database Buffering:** Always buffer bulk writes (`database.bufferChangesIndefinitely()`, then `flushChanges()`). Unbuffered writes fire UI revalidation per operation, causing severe lag.\n- **State Management:** Use Router loaders/actions and NeDB for persistent state. Use React `useState`/context for ephemeral UI state (No Redux/Zustand).\n- **Electron IPC:** For main↔renderer communication, define handlers in `src/main/ipc/`, expose in `src/main/preload.ts`, and update `window.main` in `src/global.d.ts`.\n- **Templates:** Nunjucks runs in a Web Worker (`src/templating/`). Use `{{ _.variable_name }}`.\n- **Models:** Follow CRUD via `models.<type>` (e.g., `create()`, `update()`).\n- **HTTP Calls:** Use `insomniaFetch()` for Insomnia backend APIs. Use plain `fetch()` for external/third-party APIs.\n- **Styling:** Tailwind utility classes only. Use `clsx`/`tailwind-merge` for conditionals. Use React Aria for interactive HTML elements.\n- **Testing:** Use **Vitest** (unit) and **Playwright** (E2E). Co-locate unit tests as `filename.test.ts`. Use `vi.mock()`. Prefer testing logic via loaders over mounting components.\n- **E2E tests** live in `packages/insomnia-smoke-test/`. Full docs: [`packages/insomnia-smoke-test/README.md`](packages/insomnia-smoke-test/README.md).\n- Run E2E from repo root: `npm run test:smoke:dev` (filter: `npm run test:smoke:dev -- <title-substring>`).\n- New test imports: `import { test } from '../../playwright/test'` and `import { expect } from '@playwright/test'`.\n\n## Sensitive Data\n\n- **Vault system (AES-GCM):** For environment secrets (`EnvironmentKvPairDataType.SECRET`).\n- **Electron safeStorage:** Platform-native encryption (`window.main.secretStorage`).\n","CLAUDE.md":"@AGENTS.md\n","packages/insomnia-api/README.md":"# Insomnia API\n\nThis package contains the API functionality and types for the Insomnia application which could be shared across insomnia and inso-cli.\n\n## Usage\n\n### Install\n\nUses npm workspace, so no need to install.\n\n### Import\n\n```ts\nimport { getUserProfile, getEncryptionKeys, type User, type UserEncryptionKeys } from 'insomnia-api';\n```\n","packages/insomnia-component-docs/AGENTS.md":"# AGENTS.md — Component Docs Site (Docusaurus + MDX)\n\nDocs for Insomnia's shared component library. Prefer the **`component-docs` skill** for authoring —\nit carries the template and full procedure. This file is the quick reference for direct edits.\n\n## Structure\n\n- One MDX file per component: `docs/Components/<name>.mdx` (kebab-case).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Order via\n  `sidebar_position` in frontmatter.\n- Reference doc for format/tone: `docs/Components/button.mdx`.\n\n## Doc format (per file)\n\n1. Frontmatter: `id`, `sidebar_label`, `sidebar_position`.\n2. `# Title` + one-line description.\n3. `## Props` table (prop / type / default / description) + link to the underlying React Aria props page.\n4. `## Usage Examples` — ` ```tsx live ` blocks (live-editable). Wrap multiple elements in a `function`.\n5. `## Styling` → `### CSS Variables` (color vars consumed) + any `theme--*` scope class note.\n\n## ⚠️ Live examples need ReactLiveScope registration\n\n` ```tsx live ` blocks resolve component names from `src/theme/ReactLiveScope/index.ts`, NOT from\nMDX imports. Any component used in a live block must be imported there and added to the\n`ReactLiveScope` object. Library components import from the barrel as `insomnia/src/basic-components`.\n\n## Commands\n\n```bash\ncd packages/insomnia-component-docs\nnpm run start   # local preview while writing\nnpm run build   # verify (catches broken live blocks / bad imports)\n```\n","packages/insomnia-data/README.md":"# insomnia-data\n\nA runtime-agnostic data layer for Insomnia, based on interface + IoC.\n\n## Core idea\n\n- `src/`: runtime-agnostic contracts (`IDatabase`, `Services`, model metadata/types)\n- `node-src/`: Node/main concrete implementations (`createNedbDatabase`, `servicesNodeImpl`)\n- entry points wire once:\n  - `initDatabase(impl)`\n  - `initServices(impl)`\n\nAfter wiring, business code always uses the same APIs: `database`, `services`, `models`.\n\n## Process flows\n\n### Database (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initDatabase] --> R1[clientDatabase implementation]\n        R2[feature code] --> R3[database from insomnia-data]\n        R3 --> R1\n        R1 --> R4[window.database.invoke]\n        R4 --> R5[ipcRenderer.invoke database.invoke]\n    end\n\n    subgraph Main\n        M0[initDatabase] --> M1[mainDatabase implementation]\n        M1 --> M2[createNedbDatabase impl]\n        M2 --> M3[(NeDB)]\n        M4[main feature code] --> M5[database from insomnia-data]\n        M5 --> M1\n        M6[ipcMain.handle database.invoke] --> M1\n        M1 --> M7[webContents.send db.changes]\n    end\n\n    subgraph Inso\n        I0[initDatabase] --> I1[inso database implementation]\n        I1 --> I2[createNedbDatabase impl]\n        I2 --> I3[(NeDB)]\n        I4[inso feature code] --> I5[database from insomnia-data]\n        I5 --> I1\n    end\n\n    R5 --> M6\n    M7 -.notify.-> R2\n```\n\n### Services (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initServices] --> R1[preload servicesProxy implementation]\n        R2[feature code] --> R3[services from insomnia-data]\n        R3 --> R1\n        R1 --> R4[ipcRenderer.invoke services.invoke]\n    end\n\n    subgraph Main\n        M0[initServices] --> M1[servicesNodeImpl]\n        M2[feature code] --> M3[services from insomnia-data]\n        M3 --> M1\n        M1 --> M4[service logic]\n        M4 --> M5[database]\n        M5 --> M6[(NeDB)]\n        M7[ipcMain.handle services.invoke] --> M1\n    end\n\n    subgraph Inso\n        I0[initServices] --> I1[servicesNodeImpl]\n        I2[feature code] --> I3[services from insomnia-data]\n        I3 --> I1\n        I1 --> I6[(NeDB)]\n    end\n\n    R4 --> M7\n```\n\nRenderer services path:\n\n`services.xxx` -> preload proxy -> IPC -> main handler -> `servicesNodeImpl` -> database.\n\n## Why this design\n\n- Same API across runtimes: main, renderer, inso.\n- Feature code is decoupled from Electron/IPC/NeDB details.\n- Renderer has a safer boundary (bridge + IPC, no direct DB internals and node API access).\n- Easy to test or swap implementations by injecting at startup.\n\n## Minimal usage\n\n### Main\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { mainDatabase } from '~/main/database.main';\nimport { servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(mainDatabase);\ninitServices(servicesNodeImpl);\n```\n\n### Renderer\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { clientDatabase } from '~/ui/database.client';\n\nawait initDatabase(clientDatabase);\ninitServices(window._dataServices);\n```\n\n### Inso / Node\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { createNedbDatabase, servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(createNedbDatabase());\ninitServices(servicesNodeImpl);\n```\n\n### Consuming\n\n```ts\nimport { services, models, type Request } from 'insomnia-data';\n\nconst mcpRequest = await services.mcpRequest.create({ url: 'http://localhost:3000' });\nconst all = await services.mcpRequest.all();\n\nconst request: Request = {};\n\nconst requestType = models.request.type;\n```\n","packages/insomnia-inso/README.md":"# Documentation\n\nHow to use [Inso CLI](https://docs.insomnia.rest/inso-cli/introduction).\n\n## Testing\n\n```shell\n# unit tests\nnpm run test:unit\n\n# start smoke test api (required for e2e tests)\nnpm run serve -w insomnia-smoke-test\n\n# e2e tests for dev bundle\nnpm run test:bundle\n\n# e2e tests for binary\nnpm run test:binary\n```\n\n## Development\n\n### Getting started\n\n```shell\nnpm run inso-start\nnpm run test -w insomnia-inso\n# will default to insomnia app database\n$PWD/packages/insomnia-inso/bin/inso run test\n# will use config, useful for testing with fewer args\n$PWD/packages/insomnia-inso/bin/inso -w packages/insomnia-inso/src/db/fixtures/git-repo script runTest\n```\n\n### node-libcurl\n\n`Error: The module '.../insomnia/node_modules/@getinsomnia/node-libcurl/lib/binding/node_libcurl.node' was compiled against a different Node.js version using`\n\nnode-libcurl builds for 3 operating systems and two versions of nodejs. insomnia-inso uses the nodejs build and insomnia app uses the electron build. you can switch between them using the following two commands\n\n```shell\n# install node version\nnpm run install-libcurl-node\n# install electron version\nnpm run install-libcurl-electron\n```\n\n## Run CLI Smoke Tests\n\n```shell\n# Run CLI tests\nnpm run test:bundle -w insomnia-inso\n# Package the Inso CLI binaries\nnpm run inso-package\nnpm run test:binary -w insomnia-inso\n```\n\n## Debugging CLI tests using watcher\n\nThis is helpful for debugging failing api tests\n\nFrom project root, in separate terminals:\n\n```sh\n# start smoke test api\nnpm run serve -w insomnia-smoke-test\n\n# watch inso\nnpm run start -w insomnia-inso\n\n# run api test with dev bundle. To debug run this in a Javascript Debug Terminal in VSCode\n$PWD/packages/insomnia-inso/bin/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n```\n\n## How to debug pkg\n\n```sh\n# run modify package command and then a unit test\nnpm run package -w insomnia-inso && \\\n$PWD/packages/insomnia-inso/binaries/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n\n```\n\n## How to update the `inso-nedb` fixtures\n\nRun Insomnia with `INSOMNIA_DATA_PATH` environment variable set to `fixtures/inso-nedb`, e.g.:\n\n```bash\nINSOMNIA_DATA_PATH=packages/insomnia-smoke-test/fixtures/inso-nedb /Applications/Insomnia.app/Contents/MacOS/Insomnia\n```\n\nRelaunch the app one more time, so that Insomnia compacts the database.\n\nThe `.gitignore` file will explicitly ignore certain database files, to keep the directory size down and avoid prevent sensitive data leaks.\n\n## How to run inso with the `inso-nedb` fixture locally?\n\nSet the `-w` argument pointed to `packages/insomnia-smoke-test/fixtures/inso-nedb`:\n\n```bash\n# if installed globally\ninso -w <INSO_NEDB_PATH>\n\n# using the package bin\n./packages/insomnia-inso/bin/inso -w <INSO_NEDB_PATH>\n\n# using a binary\n./packages/insomnia-inso/binaries/insomnia-inso -w <INSO_NEDB_PATH>\n```\n\n## How to debug the bundled assets\n\n```bash\nDEBUG=1 npm run build\n```\n\nThis will generate an `artifacts` directory containing information about the bundled assets.\nThe meta.json can be uploaded to https://esbuild.github.io/analyze/ to visualize the bundle.\nThe bundle-analysis.log can be used to see the dependency tree of the bundle.\n\n## How to generate documents about inso\n\n1. Ensure your node version matches the project `.nvmrc` perhaps with `fnm use` or another version manager.\n1. Run the script below, which will build inso in dev mode in order to use it to generate docs about itself\n1. The docs appear in your vscode diff, or you can look in `./packages/insomnia-inso/reference/` if the version number looks wrong you might need to check what branch you're on, it should be run from develop ideally as the only changes in the release branch should be hotfixes that dont affect inso docs\n\n```sh\nnpm i && npm run build -w insomnia-inso && $PWD/packages/insomnia-inso/bin/inso generate-docs\n```\n","packages/insomnia-smoke-test/README.md":"# Insomnia Smoke Tests\n\nPlaywright E2E tests for the Insomnia desktop app.\n\n> CLI smoke tests: [CLI.md](CLI.md)\n\n## Test structure\n\n```\ntests/\n  smoke/      # Main suite — runs on every CI push (Ubuntu only)\n  critical/   # Single critical-path test — runs on release\n  migration/  # Data migration tests\n```\n\nAll commands below must be run from the **repo root**.\n\n## Quick-start\n\n```bash\nnpm install\nnpm run test:smoke:dev   # run all Smoke tests (dev mode)\n```\n\nBoth the echo server (port 4010) and the Vite dev server start automatically.\n\n**Filter to one test** — pass a substring of the test title or file name:\n\n```bash\nnpm run test:smoke:dev -- oauth\n```\n\n**Interactive UI:**\n\n```bash\nnpm run test:smoke:dev -- --ui\n```\n\n**Step-through debugger:**\n\n```bash\nPWDEBUG=1 npm run test:smoke:dev\n```\n\n## Additional log levels\n\n```bash\nDEBUG=pw:api npm run test:smoke:dev         # Playwright API logs\nDEBUG=pw:browser npm run test:smoke:dev     # Insomnia console logs\nDEBUG=pw:WebServer npm run test:smoke:dev   # Web server logs\n```\n\n## Traces and error context\n\nOn failure, two artifacts are written under `packages/insomnia-smoke-test/traces/<test-name>/`:\n\n- **`error-context.md`** — error details, ARIA page snapshot at point of failure, and annotated test source. Read this first.\n- **`trace.zip`** — full Playwright trace (network, screenshots, DOM snapshots).\n\nOpen a trace:\n\n```bash\nnpx playwright show-trace packages/insomnia-smoke-test/traces/<test-name>/trace.zip\n```\n\nOr upload to [trace.playwright.dev](https://trace.playwright.dev/).\n\nCI traces are available as artifacts on failed workflow runs.\n\n## Build / package modes\n\nRun against a JS bundle (`build`) or a packaged binary (`package`) instead of the dev watcher:\n\n```bash\n# build mode\nnpm run app-build\nnpm run test:smoke:build\n\n# package mode\nnpm run app-package\nnpm run test:smoke:package\n```\n\n> macOS package mode: set `com.apple.security.cs.disable-library-validation` to `true` in `entitlements.mac.inherit.plist` to allow unsigned local binaries. Do not commit this change.\n\n## Non-CI / pre-release tests\n\n```bash\nnpm run test:dev -w insomnia-smoke-test -- tests/smoke/preferences-interactions.test.ts\n```\n\n## Cert refresh\n\nIf the custom CA cert test fails after 2026:\n\n```bash\nmkcert -install && mkcert localhost && mkcert -CAROOT\n```\n","packages/insomnia-smoke-test/playwright/pages/README.md":"# Playwright POM Conventions\n\nThis directory contains the Insomnia smoke-test Page Object Model (POM).\nThe goal is to keep tests stable, readable, and selector-safe.\n\n## 1. Object Definitions\n\n- `InsomniaApp`: root facade. It wires page objects and shared components.\n- `Page` object: route-level surface (for example, project page).\n- `Component` object: reusable or bounded UI region (for example, statusbar, tabbar, sidebar).\n\n## 2. Page vs Component Split\n\n- Put route-specific workflows in a `Page` object.\n- Put reusable or independently testable UI regions in a `Component`.\n- A `Page` composes its own components.\n- `InsomniaApp` composes top-level pages/components and exposes shortcuts.\n- Tests should use POM APIs first. Raw `page.getBy...` is only acceptable for gaps that are not modeled yet.\n\n## 3. Locator Ownership and Naming\n\n- Every page/component must have its own `root` getter.\n- Every POM must manage all locators used by its own actions.\n- Use `xxxLocator(...)` method naming when the locator needs parameters (for example, list row by name).\n- For a single fixed element (for example, `root`, `plusButton`), expose it as a getter directly.\n- Scope locators from `root` whenever possible to reduce collisions.\n- If a locator belongs to an existing POM, tests should not duplicate that selector.\n\n## 4. API Naming\n\n- Action methods use verb-first names: `closeTab`, `openAddTabMenu`.\n- POM exposes actions and locators; assertions stay in test files.\n- Keep methods focused on one behavior.\n\n## 5. Test Usage Pattern\n\nInstantiate once per test and use composed objects:\n\n```ts\ntest('example test', async ({ insomnia }) => {\n  await insomnia.projectPage.createCollection();\n  await insomnia.tabbar.closeTab('New Request');\n  await expect.soft(insomnia.tabbar.tabLocator('foo')).toHaveAttribute('data-selected', 'true');\n});\n```\n","packages/insomnia/README.md":"# Insomnia\n\nThe main desktop application.\n\n## Data fetching\n\n- To fetch data from our APIs we use the `insomniaFetch` function. This allows us to overcome cross-origin issues and helps us to standardize the way we fetch data and also to handle errors in a centralized way.\n\n- For real-time data fetching we use SSE (Server-Sent Events). This is a standard way to receive data from the server in real-time. The server will send a message to the client when something happens. The client will receive the message and update the data accordingly. To handle SSE we use the `insomnia-event-source://` protocol which is a custom protocol that gets handled by the app in the main process. This allows us to overcome cross-origin issues and also to handle SSE in a centralized way.\n\n- Polling is also used to fetch data from the server. This is used when we need to fetch data periodically as SSE can sometimes fail and data can go out of sync.\n  The main places we use polling are:\n  - Refreshing Insomnia Sync data.\n  - Refreshing Git Sync data.\n  - Refreshing Presence data.\n\n> ⚠️ Using a combination of SSE and polling we can keep the data in sync and up to date but might also lead to some race conditions. For example, if we are polling for data every 5 seconds and the server sends an SSE message at the same time, the data might be out of sync for a few seconds. This is something we need to keep in mind when using SSE and polling.\n","packages/insomnia/src/basic-components/AGENTS.md":"# AGENTS.md — Shared Component Library\n\nThis is Insomnia's shared component library (React Aria + Tailwind). See the design/roadmap\nin https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan\n\n## Conventions\n\n- Build on `react-aria-components` (high-level) first; only drop to `react-aria` hooks when the\n  high-level component can't meet the need (e.g. Tooltip's arbitrary non-focusable trigger).\n- **Colors** use repo CSS variables (`token-(--var)`, e.g. `text-(--color-font)`, `bg-(--hl-sm)`).\n  Everything else (size/spacing/radius/font) uses native Tailwind utilities — do NOT use\n  `--padding-*` / `--radius-*` / `--font-size-*`. Hardcode a color only if no repo variable fits, with a `FIXME`.\n- **Variants** use `tailwind-variants` (`tv`); non-variant components use `cls()` (tailwind-merge).\n- **Preserve `theme--*` scope classes** (`theme--dialog`, `theme--tooltip`, `theme--dropdown__menu`,\n  `theme--link`, …) on the matching component root — they are the per-component theming contract\n  (`src/ui/plugins/misc.ts`). Dropping them silently breaks user theme customization.\n\n## When you add or change a component → update its docs\n\nDocs live in the Docusaurus site at `packages/insomnia-component-docs` (one MDX per component).\nUse the **`component-docs` skill** to create/update the doc. Don't forget to register the component\nin `src/theme/ReactLiveScope/index.ts` or its live examples won't compile.\n\nA component is \"done\" only when its doc + ReactLiveScope entry are in place and the docs site builds\n(`cd packages/insomnia-component-docs && npm run build`).\n","packages/insomnia/src/basic-components/README.md":"# `~/basic-components` — Shared Component Library\n\nInsomnia's shared component library, built on `react-aria-components` + TailwindCSS. Full design/roadmap:\n[Insomnia Shared Component Library Plan](https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan) (Confluence). Conventions: [`AGENTS.md`](./AGENTS.md).\n\nFull per-component docs (props, variants, live examples) live on the Docusaurus site\n`packages/insomnia-component-docs`, not in this file.\n\n## Import\n\n```ts\nimport { Button, Modal, Icon } from '~/basic-components';\n```\n\n**Always import from the barrel (`~/basic-components`).** The barrel is the public contract; the\ncurrent file layout (flat) is an implementation detail. Physical layering into\n`primitives/overlays/collections/layout/typography` is **deferred to M5** (see the plan) — because\nconsumers go through the barrel, that later move only edits the barrel's internal re-exports, never\na business import. New code and the docs' `ReactLiveScope` must use the barrel so those moves stay\nfree. Existing deep imports (`~/basic-components/button`, ~18 files) are left as-is until M5.\n\n## Usage index (current)\n\n| Component       | Barrel export | Location                                                                                |\n| --------------- | ------------- | --------------------------------------------------------------------------------------- |\n| `Button`        | ✅            | `button.tsx`                                                                            |\n| `LearnMoreLink` | ✅            | `link.tsx`                                                                              |\n| `SelectPopover` | ✅            | `select-popover.tsx`                                                                    |\n| `Modal`         | ✅            | `modal.tsx`                                                                             |\n| `Tab` / `Tabs`  | ✅            | `tabs.tsx`                                                                              |\n| `Banner`        | ✅            | `banner.tsx`                                                                            |\n| `Card`          | ✅            | `card.tsx`                                                                              |\n| `Divider`       | ✅            | `divider.tsx`                                                                           |\n| `Progress`      | ✅            | `progress.tsx`                                                                          |\n| `Icon`          | ✅            | `icon.tsx` (re-exports `ui/components/icon`, pending consolidation — see mapping below) |\n\n`utils/` helpers (e.g. `utils/cls.ts`, `utils/variants.ts`, `utils/index.ts`) are library-internal (tailwind-merge / tailwind-variants helpers) and are **not** re-exported from the barrel — see [`AGENTS.md`](./AGENTS.md).\n\n## Migration mapping (old → new)\n\nTracks the consolidation described in the plan (Confluence). Status: 🔜 not started · 🚧 in progress · ✅ done.\n\n| Old                                                                                                                          | New                                                    | Status                                                                    |\n| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |\n| `basic-components/button.tsx` + `ui/components/themed-button/` (9 files) + `async-button.tsx`                                | `Button` (+ loading spinner, `AsyncButton` wrapper)    | 🚧 (canonical file in `button.tsx`; consolidation pending)                |\n| — (new)                                                                                                                      | `IconButton`                                           | 🔜                                                                        |\n| `ui/components/icon.tsx` (38 files) + `basic-components/icon.tsx` (~10) + `ui/components/svg-icon.tsx` (70 glyphs, 13 files) | `Icon`                                                 | 🔜                                                                        |\n| `ui/components/tooltip.tsx` (21) + `ui/components/help-tooltip.tsx` (26)                                                     | `Tooltip` / `HelpTooltip`                              | 🔜                                                                        |\n| `ui/components/base/input.tsx`                                                                                               | `Input` / `TextField`                                  | 🔜                                                                        |\n| `ui/components/base/select.tsx` + `basic-components/select-popover.tsx`                                                      | `Select`                                               | 🚧 (canonical file in `select-popover.tsx`; merge pending)                |\n| `ui/components/base/modal.tsx` + `modal-header/body/footer.tsx`                                                              | `Modal` (+ `size` variant, `Modal.Header/Body/Footer`) | 🚧 (canonical file in `modal.tsx`; feature parity pending, see plan §7.6) |\n| `ui/components/base/dropdown/`                                                                                               | `Menu`                                                 | 🔜                                                                        |\n| — (new)                                                                                                                      | `Popover`                                              | 🔜                                                                        |\n| — (new)                                                                                                                      | `ListBox` / `ListBoxItem`                              | 🔜                                                                        |\n| `basic-components/tabs.tsx`                                                                                                  | `Tabs` (+ controlled/render-prop)                      | 🚧 (canonical file in `tabs.tsx`)                                         |\n| `ui/components/base/checkbox.tsx`                                                                                            | `Checkbox` / `CheckboxGroup`                           | 🔜                                                                        |\n| `ui/components/base/switch.tsx`                                                                                              | `Switch`                                               | 🔜                                                                        |\n| `ui/components/base/input-number.tsx`                                                                                        | `NumberField`                                          | 🔜                                                                        |\n| `ui/components/base/date-picker.tsx`                                                                                         | `DatePicker`                                           | 🔜                                                                        |\n| `ui/components/base/middle-truncate.tsx`                                                                                     | TBD (P2 fill-in)                                       | 🔜                                                                        |\n| `basic-components/link.tsx` + `ui/components/base/link.tsx`                                                                  | `Link`                                                 | 🚧 (canonical file in `link.tsx`; merge pending)                          |\n| `ui/components/unsaved-changes-confirm-dialog.tsx` + guard                                                                   | `ConfirmDialog`                                        | 🔜                                                                        |\n| `ui/components/editable-input.tsx`                                                                                           | `EditableInput`                                        | 🔜                                                                        |\n| `ui/components/time-from-now.tsx`                                                                                            | `TimeFromNow`                                          | 🔜                                                                        |\n| `basic-components/card.tsx` / `divider.tsx` / `banner.tsx` / `progress.tsx`                                                  | `Card` / `Divider` / `Banner` / `Progress`             | 🚧 (`Progress` still has hardcoded colors, see FIXME)                     |\n| `ui/components/base/badge.tsx`                                                                                               | `Badge`                                                | 🔜                                                                        |\n\nNot covered by this library (feature consumers, not generic primitives): `ui/components/tabs/`,\n`ui/components/dropdowns/`.\n","packages/insomnia/src/cpp/README.md":"# Secure Wrapper\n\nThe purpose of this secure wrapper is to mitigate a [local search path vulnerability](https://vuldb.com/?id.295961).\n\nIt utilizes Windows' `PROCESS_MITIGATION_POLICY` to prefer the system paths so that the Electron application does not load hijacked DLLs that are placed in the installation directory (writable by underprivileged users).\n\n## Building Locally\n\nOn Windows, install MSYS2 with the base self-extracting installer: [https://github.com/msys2/msys2-installer/releases](https://github.com/msys2/msys2-installer/releases)\n\nMove that `.sfx.exe` file to wherever you'd like the MSYS2 installation to be, and run it. It will extract the install folder to that location. (i.e. `C:\\msys2-base-x86_64-NNNNNNNN.sfx.exe` to install to `C:\\msys64`).\n\nThen, open the MSYS2 terminal to finish the installation (`C:\\msys64\\msys2.exe`).\n\nClose that terminal, and open the `ucrt64` terminal (`C:\\msys64\\ucrt64.exe`).\nInstall the build system:\n\n```bash\npacman -S mingw-w64-ucrt-x86_64-gcc\n```\n\nIf you don't already have them, install git and node:\n\n```bash\npacman -S git\npacman -S nodejs\n```\n\nOnce that's done, ensure `windres` and `g++` are in your path:\n\n```bash\n$ which windres\n/ucrt64/bin/windres\n\n$ which g++\n/ucrt64/bin/g++\n```\n\nNavigate to the top-level directory of this repository and run:\n\n```bash\nnpm install\n./build-secure-wrapper.sh\n```\n\nOnce built, the Squirrel Installer can be launched from `dist/win-unpacked`.\n"},"files":{".claude/skills/component-docs/SKILL.md":"---\nname: component-docs\ndescription: \"Author or update a component doc in the Docusaurus site (packages/insomnia-component-docs) for a component in the shared library. Use when adding a new component to src/basic-components, changing a component's props/variants, or when asked to document/update docs for a component.\"\nargument-hint: 'The component name and its source path, e.g. \"Select (packages/insomnia/src/basic-components/select.tsx)\"'\n---\n\n# Component Docs (Docusaurus + MDX)\n\nAuthor/maintain docs for the shared component library on the Docusaurus site at\n`packages/insomnia-component-docs`. Docs are one MDX file per component with live,\neditable examples.\n\n## When to Use\n\n- A new component was added to `packages/insomnia/src/basic-components/` (or migrated in).\n- A component's props/variants/styling changed and its doc needs updating.\n- Asked to \"document\" / \"write docs for\" / \"update docs for\" a component.\n\n## Key Facts (read before editing)\n\n- **One file per component**: `packages/insomnia-component-docs/docs/Components/<name>.mdx` (kebab-case filename).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Just set `sidebar_position` in frontmatter for ordering.\n- **Live examples** use ` ```tsx live ` fenced blocks (via `@docusaurus/theme-live-codeblock`). Component names in those blocks resolve from a shared scope, NOT from imports in the MDX.\n- **⚠️ The easy-to-forget step**: any component used in a `tsx live` block MUST be imported and registered in `packages/insomnia-component-docs/src/theme/ReactLiveScope/index.ts`. If it isn't, the live block fails to compile. Library components import as `insomnia/src/basic-components/<name>`.\n\n## Procedure\n\n1. **Inspect the component source** to get the real API: props, types, defaults, variants (`tv` config), and which theme CSS variables / `theme--*` classes it uses.\n2. **Create/update the MDX** at `docs/Components/<name>.mdx` using `template.mdx` in this skill folder as the starting point. Fill:\n   - Frontmatter: `id`, `sidebar_label`, `sidebar_position` (next free integer, or logical grouping).\n   - Title + one-line description.\n   - `## Props` table (prop / type / default / description). End with a link to the underlying React Aria props page when it extends an RA component.\n   - `## Usage Examples` — at least 2–3 ` ```tsx live ` blocks covering the main variants/states. Keep examples small and self-contained; wrap multiple elements in a `function` component.\n   - `## Styling` → `### CSS Variables` list (only the color vars the component consumes) and, if applicable, a note on the `theme--*` scope class it carries.\n3. **Register in ReactLiveScope** (`src/theme/ReactLiveScope/index.ts`): add the `import` and include the component in the `ReactLiveScope` object. Only needed once per component.\n4. **Verify the site builds** (catches broken live blocks and bad imports):\n   ```bash\n   cd packages/insomnia-component-docs && npm run build\n   ```\n   For interactive preview while writing: `cd packages/insomnia-component-docs && npm run start`.\n\n## Conventions\n\n- Match the existing docs' tone and section order (see `docs/Components/button.mdx` as the reference).\n- Document colors via CSS variables (`--color-*` / `--hl-*`); sizes/spacing are native Tailwind utilities — mirror the component's actual source, not an idealized version. (Library styling conventions live in `packages/insomnia/src/basic-components/AGENTS.md`.)\n- Keep live examples free of business/data dependencies so they render in isolation.\n- If the component is multi-part (e.g. `Modal.Header/Body/Footer`), document the sub-components together in one file.\n",".claude/skills/component-docs/template.mdx":"---\nid: COMPONENT_ID\nsidebar_label: 'ComponentLabel'\nsidebar_position: 99\n---\n\n# ComponentName\n\nOne-line description of the component and what it is built on (e.g. \"built on React Aria's `X` component\").\n\n## Props\n\n| Prop        | Type       | Default | Description        |\n| ----------- | ---------- | ------- | ------------------ |\n| `propName`  | `type`     | `-`     | What it does       |\n| `className` | `string`   | `-`     | Additional classes |\n| `children`  | `ReactNode`| `-`     | Content            |\n\nExtends all [React Aria XProps](https://react-spectrum.adobe.com/react-aria/X.html#props).\n\n## Usage Examples\n\n### Basic\n\n```tsx live\nfunction Example() {\n  return <ComponentName>Hello</ComponentName>;\n}\n```\n\n### Variants\n\n```tsx live\nfunction Example() {\n  return (\n    <div className=\"flex gap-4\">\n      <ComponentName variant=\"a\">A</ComponentName>\n      <ComponentName variant=\"b\">B</ComponentName>\n    </div>\n  );\n}\n```\n\n## Styling\n\n### CSS Variables\n\nThe component uses the following CSS variables for theming:\n\n- `--color-font`: Text color\n- `--color-bg`: Background color\n- `--hl-sm`: Subtle highlight (hover/focus)\n\n<!-- If the component carries a theme scope class (e.g. theme--dialog / theme--tooltip),\n     note it here: user themes can customize this component's colors via that class. -->\n",".claude/skills/fix-scripting-feature/SKILL.md":"---\nname: fix-scripting-feature\ndescription: 'Debug and fix issues in Insomnia''s pre-request/after-response/test scripting feature (the pm/insomnia API surface). Use when scripts throw sandbox violations, timeouts, module import errors, or produce wrong request/response/variable mutations.'\nargument-hint: 'Provide the failing script snippet, the error message, and whether it happened in the app (hidden window) or inso (CLI)'\n---\n\n# Fix Scripting Feature Issues\n\n## When to Use\n\n- A pre-request script, after-response script, or test script (`pm.*` / `insomnia.*` API) fails, throws, or produces incorrect results.\n- Errors like `SECURITY_POLICY_VIOLATION`, `no module is found for \"...\"`, `Timeout: Running script took too long`, or `insomnia object is invalid or script returns earlier than expected.`\n- `insomnia-scripting-environment` SDK objects (`Request`, `Response`, `Environment`, `Variables`, `Collection`, `test()`) behave unexpectedly.\n\n## Architecture (read this first)\n\nPre-request/after-response/test scripts (the Postman-compatible `pm`/`insomnia` API) have **three execution paths** depending on where the script runs:\n\n| Path | Entry point | Sandboxing |\n|---|---|---|\n| Electron app (default) | `packages/insomnia/src/entry.hidden-window.ts` → `packages/insomnia/src/scripting/run-script.ts`, run in a hidden `BrowserWindow` (see `packages/insomnia/src/main/window-utils.ts`) | AST-based via `packages/insomnia/src/scripting/sandbox.ts` + `script-security-rules.ts` + `require-interceptor.ts`, toggled by `context.settings.scriptSandboxEnabled` (default on) |\n| Electron app, opt-in QuickJS sandbox | `packages/insomnia/src/network/concurrency.renderer.ts` → `packages/insomnia/src/scripting/run-script-quickjs.ts`, run in a Web Worker over a WASM QuickJS VM (`quickjs-script-engine.ts`) | Gated by `settings.useQuickJsScriptSandbox` (default off). PoC with a **deliberately minimal, hand-rolled `insomnia`/`$` API** — not the shared SDK below — with no `test()`/`pm.test()`, `collectionVariables`, vault, cookies, client certificates, or request mutation, and a `sendRequest()` limited to plain-text bodies |\n| `inso` CLI and other Node-runtime callers (e.g. `packages/insomnia/src/runtimes/network/network-adapter.node.ts`) | `packages/insomnia/src/script-executor.ts` (plain `AsyncFunction`/`eval`, no Electron) | None — `script-executor.ts` never calls `sandbox.ts`'s `prepareSandbox`, it's a structurally separate implementation. (Don't confuse this with `canSandbox = !!process.type` in `network-adapter.node.ts` — that gates **plugin** request/response hook sandboxing, an unrelated subsystem.) |\n\nThe full SDK object model (`InsomniaObject`, `Request`, `Response`, `Environment`, `Variables`, `Collection`, `Test`, console/send-request shims) lives in `packages/insomnia-scripting-environment/src/objects/` and is shared across the Electron (default) and `inso`/Node paths only — the QuickJS sandbox does not use it.\n\n## `insomnia-scripting-environment` Folder Hierarchy\n\nThe SDK package (`packages/insomnia-scripting-environment/`) is where the `pm`/`insomnia` object model lives — start here for any bug about a specific script API's behavior:\n\n```\ninsomnia-scripting-environment/\n├── src/\n│   ├── objects/                   # the pm/insomnia API surface — one file per SDK concept\n│   │   ├── index.ts               # public re-exports\n│   │   ├── insomnia.ts            # InsomniaObject — the top-level `insomnia`/`pm` instance scripts see\n│   │   ├── interfaces.ts          # RequestContext, IEnvironment — the context shape passed in/out of a script run\n│   │   ├── execution.ts           # Execution — pm.execution (setNextRequest, skipRequest, location)\n│   │   ├── request.ts             # Request, RequestBody — pm.request\n│   │   ├── request-info.ts        # RequestInfo — pm.info (event name, iteration data)\n│   │   ├── response.ts            # Response — pm.response\n│   │   ├── send-request.ts        # pm.sendRequest() implementation\n│   │   ├── environments.ts        # Environment, Variables — pm.environment / pm.globals\n│   │   ├── variables.ts           # Variable, VariableList — underlying key/value model shared by environments/collection\n│   │   ├── collection.ts          # Collection — pm.collectionVariables / folder & collection variable resolution\n│   │   ├── folders.ts             # Folder, ParentFolders — collection folder hierarchy walked for variable resolution\n│   │   ├── properties.ts          # PropertyBase, PropertyList — base classes most SDK objects extend\n│   │   ├── headers.ts             # Header, HeaderList\n│   │   ├── cookies.ts             # Cookie, CookieJar, CookieList — pm.cookies\n│   │   ├── auth.ts                # RequestAuth — pm.request.auth\n│   │   ├── certificates.ts        # Certificate — client cert modeling\n│   │   ├── proxy-configs.ts       # ProxyConfig, ProxyConfigList\n│   │   ├── urls.ts                # Url, QueryParam, UrlMatchPattern — URL parsing/manipulation\n│   │   ├── console.ts             # Console — console.log capture surfaced back to the app\n│   │   ├── test.ts                # pm.test()/test() handler registration + TestHandler, waitForAllTestsDone\n│   │   ├── async-objects.ts       # ProxiedPromise — Promise plumbing so async script code can be awaited by the host\n│   │   ├── interpolator.ts        # template-tag ({{ }}) interpolation used when resolving variable values\n│   │   ├── utils.ts               # misc helpers (e.g. checkIfUrlIncludesTag)\n│   │   └── __tests__/             # tests for selected objects above (request.test.ts, response.test.ts, etc.)\n│   └── autocomplete-snippets.json # generated editor autocomplete data — see scripts/generate-autocomplete.ts\n├── scripts/\n│   └── generate-autocomplete.ts   # regenerates autocomplete-snippets.json from the objects/ source (CI-checked)\n├── docs/                          # TypeDoc-generated API reference (generated from objects/, don't hand-edit)\n├── typedoc.json                   # TypeDoc config for docs/ generation\n└── vitest.config.ts               # test runner config for `npm test -w insomnia-scripting-environment`\n```\n\nIf a bug is about *how* a script executes (sandboxing, timeouts, hidden window vs. `inso`), look in `packages/insomnia/src/scripting/` (see Architecture above). If it's about *what a specific API returns or mutates* (`pm.request.headers`, `pm.environment.get()`, `pm.response.json()`, etc.), it's almost always in this package's `src/objects/`.\n\nFor property/method-level detail on any single object above (real signatures, script-facing surface, gotchas), see `references/objects/<name>.md` in this skill folder — one file per source file in `src/objects/` (e.g. `references/objects/request.md` for `request.ts`, `references/objects/environments.md` for `environments.ts`).\n\n## Procedure\n\n1. **Identify the execution path first** — ask/check whether the failure is in the Electron app or `inso`. Fixes and even error messages differ per path (see table above).\n2. **Reproduce with the narrowest test** before touching app code:\n   - Unit tests for the sandbox/security logic:\n     ```bash\n     npm test -w insomnia -- src/scripting\n     ```\n     (not `npx vitest run src/scripting -w insomnia` — Vitest's own CLI already owns `-w`/`--watch`, so that form drops into watch mode instead of selecting the `insomnia` workspace.)\n     Relevant files: `packages/insomnia/src/scripting/__tests__/sandbox.test.ts`, `__tests__/script-security-policy.test.ts`, `__tests__/require-interceptor.test.ts`.\n   - SDK object-model unit tests:\n     ```bash\n     npm test -w insomnia-scripting-environment\n     ```\n   - Full app unit suite (if narrowing further isn't obvious):\n     ```bash\n     npm test -w insomnia\n     ```\n3. **If the bug only shows up end-to-end** (real app, real request lifecycle), run the Playwright smoke tests:\n   ```bash\n   npm run test:dev -w insomnia-smoke-test -- --grep \"pre-request\"\n   npm run test:dev -w insomnia-smoke-test -- --grep \"after-response\"\n   ```\n   Relevant specs in `packages/insomnia-smoke-test/tests/smoke/`: `pre-request-script-features.test.ts`, `after-response-script-features.test.ts`, `pre-request-script-window.test.ts`.\n4. **Locate the fix by symptom** (see Notes below for the mapping from error message to file).\n5. **If you change the SDK's public API surface** (new/changed methods on `Request`/`Response`/`Environment`/etc. in `insomnia-scripting-environment`), regenerate autocomplete snippets — CI checks this is committed:\n   ```bash\n   npm run generate:autocomplete -w insomnia-scripting-environment\n   ```\n\n## Notes\n\n- Common failure patterns and where to look:\n  - `SECURITY_POLICY_VIOLATION` / script blocked for using `this`, `globalThis`, `__proto__`, `constructor`, or `import` → AST rule in `packages/insomnia/src/scripting/sandbox.ts` (`checkSandboxViolations`) or the rule lists in `script-security-rules.ts`. These are Electron-app-only; `inso` has no AST sandbox at all.\n  - `no module is found for \"...\"` → the module isn't on the allowlist in `packages/insomnia/src/scripting/require-interceptor.ts`. Currently allowed:\n    - Full-access Node builtins: `path`, `assert`, `url`, `punycode`, `querystring`, `string_decoder`, `stream`, `events`\n    - Method-restricted Node builtins (some methods throw): `timers` (`setImmediate` blocked), `buffer` (`allocUnsafe`/`allocUnsafeSlow` blocked), `util` (`inherits`/`debuglog` blocked)\n    - Shims: `atob`, `btoa`\n    - External/npm modules: `ajv`, `chai`, `cheerio`, `crypto-js`, `csv-parse/lib/sync` (note: not bare `csv-parse`), `lodash` (backed by `es-toolkit/compat`), `moment`, `tv4`, `uuid`, `xml2js`\n    - Special-cased: `insomnia-collection` / `postman-collection` (resolves to the SDK's own `Collection` module)\n\n    Adding a module means adding it here, not just installing the npm package.\n  - `Timeout: Running script took too long` (Electron app) → default 5000ms in `entry.hidden-window.ts:39` (`data.context.timeout`).\n  - `insomnia object is invalid or script returns earlier than expected.` → thrown when the wrapped `AsyncFunction` doesn't resolve to an `InsomniaObject` instance; check both `run-script.ts` and `script-executor.ts` since the same invariant is duplicated in each path.\n  - Hidden window \"closed unexpectedly\" / \"froze\" / busy errors → restart/health-check race conditions in `packages/insomnia/src/main/window-utils.ts` (`hiddenWindowIsBusy`, `createHiddenBrowserWindow`).\n  - Works in unit tests but not in CI → confirm `npm run generate:autocomplete -w insomnia-scripting-environment` output matches the committed `autocomplete-snippets.json`; `test.yml` diffs this file and fails the build if it's stale.\n- The script behavior should be as consistent as possible with [the original implementation](https://www.postmanlabs.com/postman-collection/tutorial-concepts.html). The implementation is hosted in this [repo](https://github.com/postmanlabs/postman-collection).\n- CI workflows relevant to this feature: `.github/workflows/test.yml` (unit tests + autocomplete snippet check, runs on PR/push to develop), `.github/workflows/test-e2e.yml` (Playwright smoke tests, same triggers).\n- Success criteria: the targeted vitest/Playwright command passes, and if the SDK surface changed, the autocomplete snippet diff is clean.\n",".claude/skills/fix-scripting-feature/references/objects/README.md":"# `insomnia-scripting-environment/src/objects` Reference\n\nOne doc per source file in `packages/insomnia-scripting-environment/src/objects/`. Each covers: public API (real signatures), how a script actually reaches it (`pm.*`/`insomnia.*`), and gotchas found while reading the code.\n\n## Core / context\n- [insomnia.md](insomnia.md) — `InsomniaObject`, the top-level `insomnia`/`pm` instance\n- [interfaces.md](interfaces.md) — `RequestContext`, `IEnvironment`\n- [execution.md](execution.md) — `Execution` (`pm.execution`)\n- [request-info.md](request-info.md) — `RequestInfo` (`pm.info`)\n- [async-objects.md](async-objects.md) — `ProxiedPromise` and async-task tracking\n- [interpolator.md](interpolator.md) — `{{ }}` template interpolation\n- [utils.md](utils.md) — misc helpers\n\n## Request / response\n- [request.md](request.md) — `Request`, `RequestBody` (`pm.request`)\n- [response.md](response.md) — `Response` (`pm.response`)\n- [headers.md](headers.md) — `Header`, `HeaderList`\n- [urls.md](urls.md) — `Url`, `QueryParam`, `UrlMatchPattern`\n\n## Environment / variables / collection\n- [environments.md](environments.md) — `Environment`, `Variables` (`pm.environment` / `pm.globals`, variable resolution precedence)\n- [variables.md](variables.md) — `Variable`, `VariableList`\n- [collection.md](collection.md) — re-export barrel only; no `Collection` class or logic — see `environments.md`/`insomnia.md` instead\n- [folders.md](folders.md) — `Folder`, `ParentFolders`\n- [properties.md](properties.md) — `PropertyBase`, `PropertyList` base classes\n\n## Auth / cookies / certs / proxy\n- [auth.md](auth.md) — `RequestAuth` (`pm.request.auth`)\n- [cookies.md](cookies.md) — `Cookie`, `CookieJar`, `CookieList` (`pm.cookies`)\n- [certificates.md](certificates.md) — `Certificate` (`pm.request.certificate`)\n- [proxy-configs.md](proxy-configs.md) — `ProxyConfig`, `ProxyConfigList`\n\n## Test / console / send-request\n- [test.md](test.md) — `pm.test()`/`test()` handler registration\n- [console.md](console.md) — `Console` (script `console.log` capture)\n- [send-request.md](send-request.md) — `pm.sendRequest()`\n\n## Flagged during writing (worth a closer look if you hit related symptoms)\n- `insomnia.settings` always returns `undefined` in scripts (getter unconditionally returns nothing) — see `insomnia.md`.\n- `pm.collectionVariables` and `pm.baseEnvironment` are the same object reference, not a copy — see `insomnia.md`/`environments.md`.\n- Writes to `insomnia.info.*` (`RequestInfo`) are never read back by the script-run merge logic — silently discarded — see `request-info.md`.\n- `pm.sendRequest(url, callback)` resolves (does not reject) on network/parse errors when a callback is passed — only the promise-only form rejects — see `send-request.md`.\n- `Response.dataURI()` has a typo bug: emits `baseg4` instead of `base64` — see `response.md`.\n- `Certificate.update()` doesn't update `disabled`, unlike the constructor — see `certificates.md`.\n- `ProxyConfig` has no `key` property despite `_index = 'key'`, breaking base-class `one()`/`indexOf()`/`upsert()` lookups — see `proxy-configs.md`.\n- `Vault` is read-only from scripts even when `enableVaultInScripts` is set — `set`/`unset`/`clear` always throw — see `environments.md`.\n",".claude/skills/fix-scripting-feature/references/objects/async-objects.md":"# Async plumbing (`ProxiedPromise`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/async-objects.ts`\n\n## Purpose\nInternal plumbing (not part of the `pm`/`insomnia` script-facing API by name) that lets the sandbox\ntrack every `Promise` a user script creates, so it can drain/await fire-and-forget promises before\nhanding control back to the host app. This is what prevents an un-awaited `.then()` chain or a\nstray `setTimeout`-driven promise inside a script from silently continuing (or erroring) after the\nscript has already \"finished\".\n\n## Public API\n\n- `export const OriginalPromise = Promise;` — a stashed reference to the real native `Promise`\n  class, captured at module load before anything replaces the global `Promise`.\n- `export class ProxiedPromise<T> extends Promise<T>` — drop-in `Promise` replacement:\n  - `constructor(executor)` — behaves exactly like `Promise`, and additionally pushes `this` into\n    the module-level `scriptPromises` array if `monitoring` is `true`.\n  - `static override all(promises: Promise<any>[])` — delegates to `super.all(promises)`; pushes\n    the resulting promise into `scriptPromises` if `monitoring`.\n  - `static override allSettled(promises: Promise<any>[])` — delegates to `super.allSettled(promises)` only; does **not** push the result into `scriptPromises` (comment: \"promise will be counted in Promise.resolve\").\n  - `static any(_: Promise<any>[])` — **not actually overridden** (no `override` keyword, does not call `super.any`); always returns `super.reject(\"'super.any' not supported\")`. Comment notes `Promise.any` isn't supported for the ES2021 compile target.\n  - `static override race(promises: Promise<any>[])` — delegates to `super.race(promises)`; pushes the result if `monitoring`.\n  - `static override reject(value: any)` — delegates to `super.reject(value)`; pushes the result if `monitoring`.\n  - `static override resolve<T>(value?: T | PromiseLike<T>)` — delegates to `super.resolve(value)`; pushes the result if `monitoring`.\n  - `static withResolvers()` — always returns `super.reject(\"'Promise.withResolvers' not supported\")`; same ES2021 compile-target limitation noted in a comment.\n- `export const asyncTasksAllSettled = async () => { await Promise.allSettled(scriptPromises); scriptPromises = []; }` — awaits every currently-tracked promise, then clears the tracked list.\n- `export const stopMonitorAsyncTasks = () => { monitoring = false; }` — disables tracking of any *new* promises created after this call.\n- `export const resetAsyncTasks = async () => { scriptPromises = []; monitoring = true; }` — clears the tracked list and re-enables monitoring; called at the start of a fresh script run.\n\nModule-level (shared, not per-instance) state: `let monitoring = true;` and\n`let scriptPromises = new Array<Promise<any>>();`. Importing this module also calls\n`resetTestPromises()` (from `./test`) once, as a side effect at load time.\n\n## Script-facing surface\nNone of these names are called directly by a user script. However, the mechanism *is* in the\nscript-facing surface indirectly: `packages/insomnia/src/entry.hidden-window-preload.ts` replaces\nthe sandbox's global `Promise` with `ProxiedPromise`\n(`contextBridge.exposeInMainWorld('Promise', ProxiedPromise); window.Promise = ProxiedPromise;`),\nso `new Promise(...)`, `Promise.resolve(...)`, `Promise.all(...)`, etc. — anything that references\nthe (now-reassigned) global `Promise` binding — is transparently tracked. **`async function` is not\nin that list**: per spec, an async function's returned promise is created against the realm's\nintrinsic `%Promise%` (`NewPromiseCapability(%Promise%)`), not whatever the mutable global `Promise`\nbinding currently points to, so reassigning `window.Promise` does not make it a `ProxiedPromise`.\n`packages/insomnia/src/scripting/run-script.ts` injects\n`resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the executed script function\nas `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__`, calling `__bridgeReset__()` before the\nuser script body runs and `await __bridgeSettle__()` after it, right before returning the mutated\n`insomnia` object. This drains explicitly-tracked promises, but an un-awaited `pm.sendRequest(...)`\ncall or stray `.then()` chain is only guaranteed to resolve before the script's result is used if\nevery promise in its chain was created via a tracked constructor/helper — not guaranteed just\nbecause it originated from an `async function`.\n\n## Gotchas / notable behavior\n- `Promise.any(...)` and `Promise.withResolvers()` are **explicitly unsupported**: calling either\n  from a script returns a promise that rejects with the literal string `\"'super.any' not supported\"`\n  or `\"'Promise.withResolvers' not supported\"` (not a real `Error` object) — worth checking for if a\n  script's failure message looks like that.\n- `allSettled` results are deliberately *not* double-counted into `scriptPromises` — the comment\n  claims this is safe because \"promise will be counted in Promise.resolve\" (i.e. the engine's\n  internal `allSettled` implementation is assumed to route through `Promise.resolve` on the inputs,\n  which are already tracked). This is an implicit coupling to Promise/A+ implementation details.\n- `scriptPromises`/`monitoring` are **module-level singletons**, not scoped per script execution —\n  correctness across sequential script runs depends on `resetAsyncTasks()` (`__bridgeReset__`)\n  being called at the start of every run; if it's ever skipped, promises from a previous run could\n  leak into the next run's settle-and-drain step.\n- Constructing a promise while `monitoring === false` silently skips tracking — no error or warning is raised.\n- Promises returned by an `async function` are **not guaranteed to be added to `scriptPromises`**,\n  even though `window.Promise`/`Promise` was replaced with `ProxiedPromise` — the JS engine builds\n  an async function's result promise from the realm's intrinsic `%Promise%`, bypassing the\n  reassigned global binding entirely. `send-request.ts`'s exported `sendRequest` is itself an\n  `async function` that internally does `return new Promise(async (resolve, reject) => {...})`; the\n  inner explicit `new Promise(...)` is tracked, but the promise actually returned to the caller of\n  `sendRequest(...)` (i.e. what `pm.sendRequest()` hands back to a script when not passed a callback)\n  is not. Symptom: a script that fires `pm.sendRequest(...)` without `await`ing it (or awaiting a\n  `.then()` derived from it) may see the request silently abandoned or racing past\n  `asyncTasksAllSettled()`'s drain, instead of being reliably completed first.\n\n## Related\n- `test.ts` — `resetTestPromises()` is called once at this module's import time; test assertion promises are tracked separately from `scriptPromises`.\n- `packages/insomnia/src/scripting/run-script.ts` — wires `resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the sandboxed script's `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__` calls.\n- `packages/insomnia/src/entry.hidden-window-preload.ts` — replaces the sandbox's global `Promise` with `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` — prepares the broader sandbox execution context that this async tracking runs inside.\n  ",".claude/skills/fix-scripting-feature/references/objects/auth.md":"# RequestAuth\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/auth.ts`\n\n## Purpose\nModels the authentication settings attached to a request (`pm.request.auth`). It stores auth options per-type in a map (so switching `type` doesn't destroy other types' saved options), and provides the two-way transform functions used to convert between Insomnia's native `RequestAuthentication` model and the script-facing `AuthOptions` shape. `RequestAuth` extends `Property` (see `properties.ts`).\n\n## Public API\n\n### Types\n- `type AuthOptionTypes = 'noauth' | 'basic' | 'bearer' | 'jwt' | 'digest' | 'oauth1' | 'oauth2' | 'hawk' | 'awsv4' | 'ntlm' | 'apikey' | 'edgegrid' | 'asap' | 'netrc'` — the full set of auth type strings the SDK models are aware of, but not all are reachable from scripts (see Gotchas).\n- `const AuthTypes: Set<string>` — a `Set` containing the same string values as `AuthOptionTypes`, used by `RequestAuth.isValidType`.\n- `interface AuthOption { key: string; value: string; type?: string }` — a single key/value auth parameter (e.g. `{ key: 'username', value: 'user1' }`).\n- `interface OAuth2AuthOption { key: string; value: string | OAuth2Param[]; type?: string }` — like `AuthOption` but OAuth2 params can themselves be arrays of `OAuth2Param` (used for nested `tokenRequestParams` etc.).\n- `interface OAuth2Param { key: string; value: string; enabled: boolean; send_as: string }`.\n- Per-auth-type option interfaces (shape of the fields you'd set for `.use()`/`.update()` when constructing raw options rather than key/value arrays): `BasicOptions`, `BearerOptions`, `JWTOptions`, `DigestOptions`, `OAuth1Options`, `OAuth2Options`, `HAWKOptions`, `AWSV4Options`, `NTLMOptions`, `APIKeyOptions`, `EdgegridOptions`, `ASAPOptions`. Each mirrors the field names used by the corresponding Insomnia auth UI (e.g. `OAuth1Options` has `consumerKey`, `consumerSecret`, `signatureMethod`, etc.) and all have an optional `id?: string`.\n- `interface AuthOptions { type: AuthOptionTypes; basic?: AuthOption[]; bearer?: AuthOption[]; jwt?: AuthOption[]; digest?: AuthOption[]; oauth1?: AuthOption[]; oauth2?: OAuth2AuthOption[]; hawk?: AuthOption[]; awsv4?: AuthOption[]; ntlm?: AuthOption[]; apikey?: AuthOption[]; edgegrid?: AuthOption[]; asap?: AuthOption[] }` — the JSON shape used to construct a `RequestAuth` and returned by `toJSON()`/`toPreRequestAuth()`. Each type's key holds an array of `AuthOption` (or `OAuth2AuthOption` for oauth2).\n\n### Functions\n- `authOptionsToParams(authMethod: BasicOptions | BearerOptions | ... | ASAPOptions)` — converts a flat options object (e.g. `{ username, password }`) into an array of `{ type: 'any', key, value }` entries. Not used internally by `RequestAuth`; appears to be a helper for callers building `AuthOption[]` from a typed options object.\n- `fromPreRequestAuth(auth: RequestAuth): RequestAuthentication` — converts a script-side `RequestAuth` into Insomnia's native `RequestAuthentication` model (used when a pre-request script finishes and Insomnia needs to write the mutated auth back onto the real request). Calls `auth.toJSON()` then switches on `type`.\n- `toPreRequestAuth(auth: RequestAuthentication | {}): AuthOptions` — the reverse transform: converts Insomnia's native auth model into the `AuthOptions` shape used to construct the script's `RequestAuth` (called when initializing `pm.request.auth` before a script runs).\n\n### `class RequestAuth extends Property`\n- `constructor(options: AuthOptions, parent?: Property)` — throws `Error('invalid auth type ${options.type}')` if `options.type` isn't a recognized type. Populates an internal `Map<string, VariableList<Variable>>` (`authOptions`) with one entry per auth-type key present in `options` (so if you pass an `AuthOptions` with only `type: 'basic'` and `basic: [...]`, only the `'basic'` entry is populated — other types are simply absent from the map, not defaulted).\n- `static isValidType(authType: string): boolean` — checks membership in `AuthTypes`.\n- `clear(type: string): void` — removes the stored options for `type` from the internal map (no-op, silently, if `type` is invalid).\n- `parameters(): VariableList<Variable> | undefined` — returns the `VariableList` of options for the *currently active* `type` (i.e. whatever `this.type` is set to), or `undefined` if nothing is stored for that type.\n- `toJSON(): AuthOptions` — serializes back to the `AuthOptions` shape. For `type === 'noauth'` or `'netrc'` it returns just `{ type }` with no options array. Otherwise returns `{ type, [type]: <array of {key, value} from the VariableList> }`.\n- `update(options: VariableList<Variable> | Variable[] | AuthOptions, type?: AuthOptionTypes): void` — replaces the options for `type` (or the current `this.type` if `type` omitted) and switches `this.type` to it. Throws `Error('no valid RequestAuth options is found')` if no variable list could be derived from `options`.\n- `use(type: AuthOptionTypes, options: VariableList<Variable> | Variable[] | AuthOptions): void` — same as `update` but `type` is required; throws `Error('invalid type (...)')` if `type` isn't in `AuthTypes`, and the same \"no valid options\" error as `update` otherwise. This is the method `Request.authorizeUsing()` calls to switch a request's auth type from a script.\n\n### Internal helper (not exported)\n- `rawOptionsToVariables(options, targetType?)` — normalizes the three accepted input shapes (`VariableList<Variable>`, `Variable[]`, or a full `AuthOptions` object) into `VariableList<Variable>[]`. Throws `Error('options is not valid: it must be VariableList<Variable> | Variable[] | object')` if none of the shapes match.\n\n## Script-facing surface\n- `pm.request.auth` is a `RequestAuth` instance (constructed in `request.ts` from `options.auth || { type: 'noauth' }`).\n- `pm.request.auth.parameters()` — read the currently active auth's key/value options.\n- `pm.request.auth.update(newOptions)` — mutate the current auth type's options in place.\n- `pm.request.authorizeUsing(type, options)` (on `Request`, in `request.ts`) delegates to `this.auth.use(type, options || { type: 'noauth' })` — this is the documented way scripts switch auth type.\n- `pm.request.auth.clear(type)` to drop a stored auth-type's options.\n- When a pre-request script finishes, `fromPreRequestAuth(request.auth)` converts the mutated `RequestAuth` back to Insomnia's native `RequestAuthentication`, which is what actually gets sent on the wire (see `request.ts`, `authentication: fromPreRequestAuth(updatedReq.auth)`).\n\n## Gotchas / notable behavior\n- **`netrc` is a dead end**: `AuthTypes` includes `'netrc'` and `RequestAuth` will happily accept `type: 'netrc'`, but `fromPreRequestAuth` throws `Error('netrc is not supported yet')` when it sees `type === 'netrc'`, and `toPreRequestAuth` throws `Error('netrc auth is not supported in scripting yet')` for the native `'netrc'` type. So a script that sets `type: 'netrc'` will fail only when Insomnia tries to convert it back, not immediately.\n- **`singleToken` is unsupported**: `toPreRequestAuth` throws for native auth type `'singleToken'` — there is no path from this native auth type into the script's `RequestAuth` at all.\n- **`edgegrid` has interfaces but no transform**: `EdgegridOptions` and `'edgegrid'` are defined in `AuthOptionTypes`/`AuthTypes`, but neither `fromPreRequestAuth` nor `toPreRequestAuth` has a `case 'edgegrid'` — passing this type through those functions falls into the `default` branch and throws `Error('unknown auth type: ...')`.\n- **oauth1 `signatureMethod` restrictions**: `fromPreRequestAuth`'s oauth1 branch only supports `HMAC-SHA1`, `HMAC-SHA256`, `RSA-SHA1`, and `PLAINTEXT`. `HMAC-SHA512`, `RSA-SHA256`, and `RSA-SHA512` are recognized strings but explicitly throw `Error('...unsupported signatureMethod type for oauth1: ...')`.\n- **oauth1 `privateKey` is a one-way/unsupported field**: comment in code says \"it is not supported in the script side\" — it's read but not written back into the native auth model in `toPreRequestAuth`.\n- **oauth2 has several fields marked \"not supported yet in the script side\"**: `tokenPrefix`, `responseType`, `origin` are passed through in `fromPreRequestAuth`/`toPreRequestAuth` for round-tripping but are not meaningfully used elsewhere per the inline comments.\n- **hawk loses several fields on the round trip to native Insomnia auth**: `timestamp`, `delegation`, `app`, `nonce`, `user` are commented as \"some keys are lost here\" in `fromPreRequestAuth`'s hawk branch, and `toPreRequestAuth`'s hawk branch hardcodes them back to empty string / `'false'` since \"these fields are not supported in Insomnia side\".\n- **Booleans are stored as strings internally**: all the `AuthOption`/`OAuth2AuthOption` values are `string`, so booleans like `disabled` are represented as `'true'`/`'false'` strings inside the `VariableList`, and compared with `=== 'true'` when converting back.\n- **Constructor does not default missing types**: if you construct `new RequestAuth({ type: 'bearer' })` (no `bearer` key), `parameters()` will return `undefined` immediately — there's no implicit empty array.\n- **`update`/`use` fully replace, not merge**: calling `update()`/`use()` replaces the entire options list for a type; it does not merge in partial fields.\n\n## Related\n- `properties.ts` — `Property` (base class) and `Variable`/`VariableList` container semantics used to store auth options.\n- `variables.ts` — `Variable`, `VariableList` (the underlying storage for each auth type's key/value pairs).\n- `request.ts` — constructs `pm.request.auth` from `AuthOptions`, exposes `authorizeUsing()`, and calls `fromPreRequestAuth` when finalizing the request.\n- `insomnia.ts` — calls `toPreRequestAuth(rawObj.request.authentication)` to seed `pm.request.auth` before a script runs.\n- `insomnia-data` package — source of the native `RequestAuthentication` and `OAuth2ResponseType` types that `fromPreRequestAuth`/`toPreRequestAuth` convert to/from.\n",".claude/skills/fix-scripting-feature/references/objects/certificates.md":"# Certificate\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/certificates.ts`\n\n## Purpose\nModels a single client (mTLS) certificate for the scripting environment — the script-facing representation surfaced as `pm.request.certificate`. It is deliberately simple: unlike Insomnia's native model (which supports a list of client certificates matched by host), the scripting SDK only ever models **one** certificate per request, matched via `UrlMatchPattern`s (from `urls.ts`).\n\n## Public API\n\n### Types\n- `interface SrcRef { src: string }` — a file-path reference; used for `key`, `cert`, and `pfx` since certificate content is loaded from disk, not embedded inline.\n- `interface CertificateOptions { name?: string; matches?: string[]; key?: SrcRef; cert?: SrcRef; passphrase?: string; pfx?: SrcRef; disabled?: boolean }` — constructor/`update()` input. `pfx` is documented as \"PFX or PKCS12 Certificate\".\n\n### `class Certificate extends Property`\n- `override _kind = 'Certificate'`\n- `override name?: string`\n- `matches?: UrlMatchPatternList<UrlMatchPattern>` — constructed internally from the `matches: string[]` option; each string becomes a `UrlMatchPattern`.\n- `key?: SrcRef`\n- `cert?: SrcRef`\n- `passphrase?: string`\n- `pfx?: SrcRef`\n- `constructor(options: CertificateOptions)` — sets all fields directly from `options`, including `this.disabled = options.disabled` (inherited `Property.disabled` field). `matches` defaults to an empty `UrlMatchPatternList` if `options.matches` is not provided.\n- `static isCertificate(obj: object): boolean` — checks `obj._kind === 'Certificate'`.\n- `canApplyTo(url: string): boolean` — returns `this.matches ? this.matches.test(url) : false`, i.e. whether any of the certificate's match patterns matches the given URL. Delegates to `UrlMatchPatternList.test()`.\n- `update(options: CertificateOptions): void` — fully overwrites `name`, `matches` (rebuilt the same way as in the constructor), `key`, `cert`, `passphrase`, `pfx`. Note: **does not** update `disabled` (unlike the constructor, which does set it) — see Gotchas.\n\n## Script-facing surface\n- `pm.request.certificate` is a `Certificate` instance. Per `insomnia.ts`, it's initialized either as an empty placeholder certificate (`name: 'Default Certificate'`, no `key`/`cert`/`pfx`) when the request URL contains unrendered template tags or `filterClientCertificates` returns zero matches, or as `{ name: 'The first matched certificate from Settings', matches: [matchedCertificates[0].host], key: {src: ...}, cert: {src: ...}, passphrase, pfx: {src: ...} }` — built from `matchedCertificates[0]` whenever the match list is **non-empty**, i.e. one or more matches, not only when there's exactly one. If more than one client certificate matches, everything past the first is silently dropped rather than surfaced as the empty-certificate path — a direct consequence of `Certificate` only ever modeling a single certificate (see Gotchas below).\n- Scripts can read `pm.request.certificate.key`, `.cert`, `.pfx`, `.passphrase`, `.name`, call `.canApplyTo(url)`, or call `.update({...})` to replace the certificate used for the request. Per `request.ts`, `Request.certificate` is a `Certificate` built the same way (`options.certificate ? new Certificate(options.certificate) : undefined`), and the certificate is serialized back out (`toJSON()`-style plain object with `name`, `matches` stringified, `key`, `cert`, `passphrase`, `pfx`) when the script finishes so Insomnia can merge it into the request's actual client-certificate list via `mergeClientCertificates` (in `request.ts`).\n\n## Gotchas / notable behavior\n- **Only one certificate is modeled, even though Insomnia supports several.** `request.ts`'s comment: \"Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones.\" When a script sets a certificate, `mergeClientCertificates` prepends it as a new entry ahead of the request's existing native client certificates rather than replacing the whole list.\n- **`update()` does not update `disabled`.** The constructor sets `this.disabled = options.disabled`, but `update()` has no such line — calling `cert.update({ disabled: true, ... })` will not actually change `cert.disabled`. This looks like an inconsistency/bug relative to the constructor's behavior.\n- **`cert+key` and `pfx` are mutually exclusive downstream.** `request.ts`'s `mergeClientCertificates` throws `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if a script sets both a PFX and a cert/key pair on `pm.request.certificate`.\n- **Empty/placeholder certificate is normal, not an error state.** If the request URL contains a template tag (e.g. `{{ baseUrl }}`) or there's no matching client certificate configured in Settings, `pm.request.certificate` is initialized to an empty certificate and `insomnia.ts` logs this via `getExistingConsole().warn(...)` — a warning-level timeline entry, not debug — so it's visible when tracing why the default/empty certificate was selected. Scripts should not assume `key`/`cert`/`pfx` are populated.\n- **`matches` is derived only from certificate `host`, singular** — even though `UrlMatchPatternList` supports multiple patterns, `insomnia.ts` only ever seeds `matches: [matchedCertificates[0].host]` (a single-element array) from the native model.\n\n## Related\n- `properties.ts` — `Property` base class (provides `id`, `name`, `disabled`, `toJSON()`/`toString()` defaults).\n- `urls.ts` — `UrlMatchPattern`, `UrlMatchPatternList` (used for `matches` and `canApplyTo()`).\n- `request.ts` — constructs/serializes `pm.request.certificate`, and contains `mergeClientCertificates()` which reconciles the script's single certificate back into Insomnia's native multi-certificate list.\n- `insomnia.ts` — seeds the initial `pm.request.certificate` value from Insomnia Settings' configured client certificates, using `filterClientCertificates` from `insomnia/src/network/certificate`.\n- `insomnia-data` package — source of the native `ClientCertificate` type.\n",".claude/skills/fix-scripting-feature/references/objects/collection.md":"# collection.ts (re-export barrel — no `Collection` class)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/collection.ts`\n\n## Purpose\n**This file does not define a `Collection` class, and has no logic of its own.** Its entire content is a JSDoc `@module` comment plus eleven `export { ... } from '...'` re-export statements:\n```ts\nexport { RequestAuth } from './auth';\nexport { Certificate } from './certificates';\nexport { Cookie, CookieList } from './cookies';\nexport { Header, HeaderList } from './headers';\nexport { Property, PropertyBase, PropertyList } from './properties';\nexport { ProxyConfig, ProxyConfigList } from './proxy-configs';\nexport { FormParam, Request, RequestBody } from './request';\nexport { Response } from './response';\nexport { QueryParam, Url, UrlMatchPattern, UrlMatchPatternList } from './urls';\nexport { Variable, VariableList } from './variables';\nexport { Folder } from './folders';\n```\nIt exists purely so that `index.ts` can do `export * as Collection from './collection'`, giving SDK/type consumers a `Collection.Property`, `Collection.Variable`, `Collection.Folder`, etc. namespace (mirroring the Postman `postman-collection` package's `Collection.*` type namespace for compatibility/familiarity). There is no runtime behavior, no constructor, and nothing related to collection-scoped *variable storage* in this file.\n\n## Public API\nNo classes/functions of its own. It re-exports (unchanged) the following, whose real definitions and docs live in their own files:\n- `RequestAuth` (`auth.ts`)\n- `Certificate` (`certificates.ts`)\n- `Cookie`, `CookieList` (`cookies.ts`)\n- `Header`, `HeaderList` (`headers.ts`)\n- `Property`, `PropertyBase`, `PropertyList` (`properties.ts` — see `properties.md`)\n- `ProxyConfig`, `ProxyConfigList` (`proxy-configs.ts`)\n- `FormParam`, `Request`, `RequestBody` (`request.ts`)\n- `Response` (`response.ts`)\n- `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` (`urls.ts`)\n- `Variable`, `VariableList` (`variables.ts` — see `variables.md`)\n- `Folder` (`folders.ts` — see `folders.md`)\n\nNote this list is nearly identical to `index.ts`'s own top-level JSDoc module comment (both files carry the same doc block), but `index.ts` itself re-exports far more (all of `environments.ts`, `insomnia.ts`, `test.ts`, `execution.ts`, etc.) in addition to wrapping this file as the `Collection` namespace.\n\n## Script-facing surface\nNo direct script-facing surface of its own. **If you are looking for how `pm.collectionVariables` actually works** (collection-level variable storage and resolution), that logic lives in:\n- `environments.ts` — the `Environment` class backing the actual store.\n- `insomnia.ts` — `InsomniaObject` explicitly maps `this.collectionVariables = this.baseEnvironment` (i.e. `pm.collectionVariables` **is** `pm.baseEnvironment`, the same `Environment` instance, not a separate object) and constructs the `Variables` hierarchy (`collectionVars: baseEnvironment`) used by `pm.variables`.\nSee `environments.md` for the full precedence/resolution behavior.\n\n## Gotchas / notable behavior\n- **Naming trap**: a file named `collection.ts` in a scripting/variable-resolution context strongly suggests a `Collection` class with collection-variable logic — it has none. Anyone debugging \"collection variables\" behavior should look at `environments.ts` (`Environment`) and `insomnia.ts` (`collectionVariables = baseEnvironment` aliasing), not this file.\n- Diffed byte-for-byte against `index.ts`'s doc comment: the JSDoc header text is duplicated between the two files, but the actual export lists differ — `collection.ts` only re-exports a subset (the \"Postman-collection-shaped\" types), while `index.ts` re-exports everything including `insomnia.ts`, `environments.ts`, `console.ts`, `execution.ts`, `test.ts`, `async-objects.ts`, `request-info.ts`.\n- `index.ts` wraps this file's exports under a namespace: `export * as Collection from './collection';` — so external consumers see `Collection.Variable`, `Collection.Folder`, etc., but that `Collection` namespace object is not related to `pm.collectionVariables` at runtime; it's purely a type/value re-export grouping.\n\n## Related\n- `index.ts` — re-exports this file's contents as the `Collection` namespace (`export * as Collection from './collection'`).\n- `environments.ts` — actual implementation backing `pm.collectionVariables`/`pm.baseEnvironment` (see `environments.md`).\n- `insomnia.ts` — wires `collectionVariables` to `baseEnvironment` and builds the `Variables` hierarchy.\n- `properties.ts`, `variables.ts`, `folders.ts`, `urls.ts`, `request.ts`, `response.ts`, `cookies.ts`, `headers.ts`, `auth.ts`, `certificates.ts`, `proxy-configs.ts` — the actual modules whose types this file re-exports.\n",".claude/skills/fix-scripting-feature/references/objects/console.md":"# Console\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/console.ts`\n\n## Purpose\nImplements a sandbox-safe replacement for the global `console` object used inside pre-request/after-response/test scripts. Instead of writing to a real stdout/devtools console, every call is captured into an in-memory row buffer that the execution harness later dumps and surfaces back to the Insomnia app (e.g. the response timeline).\n\n## Public API\n\n### `type LogLevel`\n```ts\ntype LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error';\n```\nNot exported — internal tag for which console method produced a row.\n\n### `interface Row`\n```ts\nexport interface Row {\n  value: string;\n  name: string;\n  timestamp: number;\n}\n```\n- `value` — the rendered `\"<level>: <content>\"` string.\n- `name` — always `'Text'` in current usage.\n- `timestamp` — `Date.now()` at log time.\n\n### `class Console`\nNo explicit constructor (uses field initializers); instantiated with `new Console()`.\n\n- `rows: Row[]` — accumulated log entries, in call order.\n- `printLog = (rows: Row[], level: LogLevel, ...values: any) => void` — core formatter (marked `@ignore`, not really meant to be called directly by users). Maps each value to a string (`typeof value === 'string' ? value : JSON.stringify(value, null, 2)`), joins with `' '`, and pushes `{ value: `${level}: ${content}`, name: 'Text', timestamp: Date.now() }` onto `rows`. If formatting throws, it instead pushes a row whose `value` is `'error: ' + JSON.stringify(e, null, 2)`.\n- `log = (...values: any[]) => void` — calls `printLog(this.rows, 'log', ...values)`.\n- `warn = (...values: any[]) => void` — calls `printLog(this.rows, 'warn', ...values)`.\n- `debug = (...values: any[]) => void` — calls `printLog(this.rows, 'debug', ...values)`.\n- `info = (...values: any[]) => void` — calls `printLog(this.rows, 'info', ...values)`.\n- `error = (...values: any[]) => void` — calls `printLog(this.rows, 'error', ...values)`.\n- `clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => void` — **always throws** `new Error('currently \"clear\" is not supported for the timeline')`. Calling `console.clear()` in a script will throw.\n- `dumpLogs = () => string` — returns all rows serialized as `JSON.stringify(row) + '\\n'`, joined with `'\\n'` (i.e. one JSON blob per row, double-newline separated).\n- `dumpLogsAsArray = () => string[]` — same per-row serialization (`JSON.stringify(row) + '\\n'`) but returned as an array instead of one joined string.\n\n### Module-level singleton helpers\n- `let builtInConsole = new Console();` (module-private).\n- `getExistingConsole(): Console` — returns the current singleton without creating a new one.\n- `getNewConsole(): Console` — replaces the singleton with a fresh `new Console()` and returns it (used to reset log state between script executions).\n\n## Script-facing surface\n- `console.log(...)`, `console.warn(...)`, `console.debug(...)`, `console.info(...)`, `console.error(...)` — available as the script's global `console` inside pre-request/after-response/test scripts (the sandbox injects a `Console` instance as the `console` parameter, not the real JS console).\n- `console.clear()` — present but always throws if called from a script.\n\n## Gotchas / notable behavior\n- **`clear()` is a hard error, not a no-op.** Any script calling `console.clear()` will throw `Error('currently \"clear\" is not supported for the timeline')`, which will surface as a script failure.\n- **Object logging uses `JSON.stringify(value, null, 2)`**, not `util.inspect`-style formatting — circular references or values with `toJSON()` quirks will affect what shows up. If `JSON.stringify` itself throws (e.g. circular reference), the whole `printLog` call falls back to a single `'error: ' + JSON.stringify(e, null, 2)` row rather than losing the log entirely — but the original values are lost in that fallback.\n- **No format-string substitution (`%s`, `%d`, etc.)** — the `// TODO: support replacing substitution` comment confirms this isn't implemented; values are just stringified and space-joined.\n- **State is a module-level singleton**, not per-instance-only: `getExistingConsole()`/`getNewConsole()` share one `builtInConsole` variable across the module. The harness must call `getNewConsole()` before each script run to avoid leaking log rows from a previous execution into the next one's dump.\n- **Two different dump shapes exist** (`dumpLogs()` joined string vs. `dumpLogsAsArray()` array of strings) — different call sites in the harness use one or the other (see Related), so a log line \"disappearing\" could be a symptom of reading from the wrong dump method or a stale singleton reference.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — imports `getExistingConsole` and uses it only for a one-off warning log during `initInsomniaObject` (an empty-certificate warning); it is not the general per-script logging path.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) — calls `getNewConsole()` to obtain a fresh `Console` instance for each script run and passes `scriptConsole.log` into `initInsomniaObject(sandboxContext, scriptConsole.log)`.\n- `packages/insomnia/src/scripting/run-script.ts` — injects the `Console` instance itself as the sandboxed script's `console` global (part of the generated function's parameter list), and at the end calls `scriptConsole.dumpLogsAsArray()`, including the result (`logs`) in the returned `RequestContext`.\n- Legacy path `packages/insomnia/src/script-executor.ts` — calls `scriptConsole.dumpLogs()` (joined-string form) and appends it to a timeline file via `fs.promises.appendFile`, which is how logs end up in Insomnia's response timeline view in that code path.\n",".claude/skills/fix-scripting-feature/references/objects/cookies.md":"# Cookie, CookieJar, CookieList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/cookies.ts`\n\n## Purpose\nModels HTTP cookies for the scripting environment. `Cookie` wraps a `tough-cookie` cookie plus Insomnia-specific extension fields; `CookieList` is a keyed `PropertyList<Cookie>`; `CookieObject` (a `CookieList` subclass) is the concrete class backing `pm.cookies` and pairs the list with a `CookieJar`; `CookieJar` is a custom (non-tough-cookie) jar keyed by domain used for `pm.cookies.jar()`. `mergeCookieJar` reconciles a script-produced jar back into Insomnia's native `CookieJar` model.\n\n## Public API\n\n### Types\n- `interface InsomniaCookieExtensions { creation?: Date; creationIndex?: number; lastAccessed?: Date; pathIsDefault?: boolean }` — Insomnia-only metadata carried alongside the standard cookie fields.\n- `interface CookieOptions extends InsomniaCookieExtensions { id?: string; key: string; value: string; expires?: Date | string | null; maxAge?: number | 'Infinity' | '-Infinity'; domain?: string; path?: string; secure?: boolean; httpOnly?: boolean; hostOnly?: boolean; session?: boolean; extensions?: { key: string; value: string }[] }` — constructor input for `Cookie`.\n\n### `class Cookie extends Property`\n- `constructor(cookieDef: CookieOptions | string)` — if given a string, parses it via `Cookie.parse()` first (throws `Error('failed to parse cookie, the cookie string seems invalid')` if parsing fails or the tough-cookie construction fails). Wraps the result in a `tough-cookie` `Cookie` (`ToughCookie.fromJSON`). `extensions` are stored separately from the tough-cookie object (tough-cookie only handles `string[]` extensions natively).\n- `static override _index = 'key'` — `PropertyList.one()`/`indexOf()` look items up by `key` for cookie lists.\n- `static isCookie(obj: Property): boolean` — checks `obj._kind === 'Cookie'`.\n- `static parse(cookieStr: string): CookieOptions` — parses a `Set-Cookie`-style string (loose mode) via tough-cookie, pulls `HostOnly`/`Session` out of the extensions list into dedicated booleans, and converts remaining string extensions (`key=value` or bare flags, which become `{key, value: 'true'}`) into `{key, value}` objects. Throws if the string can't be parsed by tough-cookie.\n- `static stringify(cookie: Cookie): string` — alias for `cookie.toString()`.\n- `static unparseSingle(cookieOpt: CookieOptions): string` — constructs a `Cookie` from options and returns its string form.\n- `static unparse(cookies: Cookie[]): string` — joins each cookie's `toString()` with `'; '`.\n- `toString(): string` — tough-cookie's own `toString()` plus `; HostOnly`, `; Session`, and `; key=value` segments appended for any extensions/inso flags present.\n- `valueOf(): string` — returns just `cookie.value` (used by `PropertyList.one()` for lookups and by `toObject()`).\n- `get key(): string` — reads through to `this.cookie.toJSON().key`.\n- `toJSON()` — returns the full plain-object shape: `{ id, key, value, expires, maxAge, domain, path, secure, httpOnly, hostOnly, session, extensions, creation, creationIndex, lastAccessed, pathIsDefault }`. Note `expires` is coerced to `undefined` when tough-cookie reports `'Infinity'`.\n\n### `class CookieList extends PropertyList<Cookie>`\n- `constructor(cookies: Cookie[])` — always constructs with `typeClass = Cookie`, no `parent`.\n- `static isCookieList(obj: object): boolean`.\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, string | string[]>` — builds a plain `key -> value` map via `Object.create(null)` (so no prototype — dangerous keys like `__proto__`/`constructor` land as own properties, not pollute the prototype). `excludeDisabled` skips cookies where `cookie.disabled` is true. `multiValue` collects same-key cookies into an array instead of overwriting (last value wins when `multiValue` is falsy).\n\n### `class CookieObject extends CookieList`\nThis is the concrete class instantiated as `pm.cookies`.\n- `constructor(cookieJar: InsomniaCookieJar | null)` — maps each native Insomnia cookie into a script `Cookie` (translating numeric `expires` into a `Date`, and passing through `maxAge: undefined`/`session: undefined`/`extensions: undefined` since those fields aren't tracked by Insomnia's native cookie model). Also builds an internal `CookieJar` (named from `cookieJar.name`, or `''`/empty if `cookieJar` is `null`).\n- `jar(): CookieJar` — returns the internal `CookieJar` instance backing `pm.cookies.jar()`.\n\n### `class CookieJar` (custom, not `tough-cookie`'s jar)\nComment: \"CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain as it reads location from the browser window, it is 'localhost'\". Internally a `Map<domain, Map<cookieKey, Cookie>>`.\n- `constructor(jarName: string, cookies?: Cookie[])` — indexes each cookie by its `domain` (read via `cookie.toJSON().domain`). If a cookie has no `domain`, it's **dropped** with a console warning: `` `domain is not specified for the cookie \"${cookie.key}\" so it is omitted` `` (via `getExistingConsole().warn`).\n- `set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void): void` — `url` is actually used as the domain key (not parsed as a URL). If `value` is a plain string, constructs a minimal `Cookie({ key, value, domain: url })`; otherwise treats `value` as full `CookieOptions` and constructs `new Cookie(value)`. Always calls `cb(undefined, cookie)` — this implementation never produces an error.\n- `get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void): void` — looks up `name` within the `url` domain bucket; calls back with `undefined` cookie if not found (not an error).\n- `getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void): void` — returns all cookies for that domain bucket (empty array if none).\n- `unset(url: string, name: string, cb: (error?: Error | null) => void): void` — deletes `name` from the domain bucket if the bucket exists; always calls back with no error.\n- `clear(url: string, cb: (error?: Error | null) => void): void` — deletes the entire domain bucket.\n- `toInsomniaCookieJar(): { name: string; cookies: Partial<InsomniaCookie>[] }` — flattens the map back into Insomnia's native cookie-jar shape. `expires` is defaulted back to `'Infinity'` if falsy (\"avoid edge cases\").\n\n### Module-level function\n- `mergeCookieJar(originalCookieJar: InsomniaCookieJar, updatedCookieJar: { name: string; cookies: Partial<InsomniaCookie>[] }): InsomniaCookieJar` — assigns a fresh `uuidv4()` id to any cookie missing one (mirroring the id-generation approach in Insomnia's `cookie-list.tsx`), and returns `{ ...originalCookieJar, cookies: cookiesWithId }`. This is the function that reconciles a script's mutated jar back into Insomnia's persisted model after a script runs.\n\n## Script-facing surface\n- `pm.cookies` is a `CookieObject` (constructed in `insomnia.ts` via `new CookieObject(rawObj.cookieJar)`), so scripts get all `CookieList`/`PropertyList` methods directly: `pm.cookies.get(key)` / `pm.cookies.one(key)` (via inherited `PropertyList.get`/`one`, which for `Cookie` returns `cookie.valueOf()` — i.e. just the string value, not the `Cookie` object, because `Cookie.valueOf` is defined), `pm.cookies.toObject()`, `pm.cookies.all()`, `pm.cookies.count()`, `pm.cookies.each()`, `pm.cookies.add()`, etc.\n- `pm.cookies.jar()` returns the `CookieJar`, giving access to `set(url, key, value, cb)`, `get(url, name, cb)`, `getAll(url, cb)`, `unset(url, name, cb)`, `clear(url, cb)` — all Postman-style callback-based cookie-jar operations, scoped by domain (the `url` argument is treated as a plain domain string, not parsed).\n- After a script runs, Insomnia calls `this.cookies.jar().toInsomniaCookieJar()` (see `insomnia.ts`) to get the jar back out, which is then merged via `mergeCookieJar`.\n\n## Gotchas / notable behavior\n- **`PropertyList.one()`/`.get()` return the raw value, not a `Cookie`**: because `Cookie` defines `valueOf()`, and `PropertyList.one()` special-cases items with a `valueOf` method — `pm.cookies.get('foo')` returns the cookie's string value, not the `Cookie` instance. To get the full object, use `pm.cookies.find(...)` or `pm.cookies.all()` instead.\n- **`toObject()` uses `Object.create(null)`** specifically to avoid prototype pollution when cookie keys are things like `__proto__` or `constructor` — confirmed by a dedicated test (`toObject does not pollute the prototype for dangerous cookie keys`).\n- **Cookies without a `domain` are silently dropped** when building a `CookieJar` (constructor only; a warning is logged, not thrown).\n- **`maxAge` and `session` are not persisted from Insomnia's native model**: `CookieObject`'s constructor always sets `maxAge: undefined` and `session: undefined` when converting from `InsomniaCookieJar`, with inline comments \"not supported in Insomnia\".\n- **`extensions` format from Insomnia is unknown** per an inline `TODO` — `CookieObject`'s constructor always passes `extensions: undefined`.\n- **`CookieJar.set()` never reports an error** — the callback signature allows an `Error`, but the implementation has no failure path (a malformed `CookieOptions` would throw inside `new Cookie(...)` before the callback is ever reached, rather than being passed to `cb`).\n- **Duplicate keys in `toObject()` collapse to the last value** unless `multiValue` is passed as `true`, in which case duplicates accumulate into an array.\n- **`Cookie.parse()` of a malformed string like `'=gingerale'`** still succeeds with `key: ''`, `value: 'gingerale'`, `expires: 'Infinity'` (per the test) — it does not throw for merely unusual input, only for input tough-cookie's parser rejects entirely.\n\n## Related\n- `properties.ts` — `Property` (base class for `Cookie`) and `PropertyList` (base class for `CookieList`).\n- `console.ts` — `getExistingConsole()`, used by `CookieJar`'s constructor to warn about domain-less cookies.\n- `insomnia.ts` — constructs `pm.cookies` as `new CookieObject(rawObj.cookieJar)` and reads back `this.cookies.jar().toInsomniaCookieJar()` after script execution.\n- `insomnia-data` package — source of the native `Cookie`/`CookieJar` types this file converts to/from.\n- `tough-cookie` (external) — underlying cookie parse/stringify implementation wrapped by `Cookie`.\n",".claude/skills/fix-scripting-feature/references/objects/environments.md":"# Environment / Variables / Vault\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/environments.ts`\n\n## Purpose\nThis file defines `Environment` (a single flat key-value store), `Variables` (a hierarchical read/write facade over several `Environment` scopes, resolving lookups in precedence order), and `Vault` (a locked-down `Environment` subclass for secrets). `InsomniaObject` (`insomnia.ts`) instantiates these and exposes them as `insomnia.environment`, `insomnia.globals`/`insomnia.baseGlobals` (private), `insomnia.collectionVariables` (aliased to `baseEnvironment`), `insomnia.variables`, and `insomnia.vault` — the core variable model behind pre-request/after-response/test scripts.\n\n## Public API\n\n### `class Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined)\n```\nInitializes `_name` and a `Map<string, boolean|number|string|null|undefined>` from `Object.entries(jsonObject || {})`.\n\n- `get name(): string` — returns `_name` (read-only getter; no setter).\n- `has = (variableName: string) => boolean` — `Map.has`.\n- `get = (variableName: string) => boolean|number|string|null|undefined` — `Map.get`; returns `undefined` if absent.\n- `set = (variableName: string, variableValue: boolean|number|string|undefined|null) => void` — sets the value; if `Number.isNaN(variableValue)` is true, logs a warning via `getExistingConsole().warn` and **does not** set the value.\n- `unset = (variableName: string) => void` — `Map.delete`.\n- `clear = () => void` — `Map.clear`.\n- `replaceIn = async (template: string | object) => Promise<string>` — coerces an object template via `.toString()`, throws `TypeError` if not a string/object, then renders it through `getInterpolator().render(template, this.toObject())`. Supports `{{$randomUUID}}` and `{{varName}}` placeholders.\n- `toObject = () => Record<string, any>` — `Object.fromEntries(this.kvs.entries())`.\n- `toJSON()` — returns `this.toObject()` (drives `JSON.stringify(environment)` serialization).\n\n### `function mergeFolderLevelVars(folderLevelVars: Environment[]): Environment` *(not exported, `@ignore`)*\nReduces an array of folder `Environment`s into one merged `Environment` named `'mergedFolderLevelVars'`, with **later folders in the array winning** on key collisions (`{ ...merged, ...folderLevelEnv.toObject() }`).\n\n### `class Variables`\n```ts\nconstructor(args: {\n  baseGlobalVars: Environment;\n  globalVars: Environment;\n  collectionVars: Environment;\n  environmentVars: Environment;\n  iterationDataVars: Environment;\n  folderLevelVars: Environment[];\n  localVars: Environment;\n})\n```\nStores each scope as a private `Environment` (or `Environment[]` for folder levels).\n\n- `has = (variableName: string) => boolean` — true if any of the 7 scopes has the variable (local, folder-level via `.some`, iterationData, environment, collection, global, baseGlobal — all evaluated eagerly, then OR'd).\n- `get = (variableName: string) => value` — builds an ordered array `[localVars, mergeFolderLevelVars(folderLevelVars), iterationDataVars, environmentVars, collectionVars, globalVars, baseGlobalVars]`, uses `.find(vars => vars.has(variableName))` to pick the **first scope (highest precedence) that has the key**, and returns `scope?.get(variableName)`.\n- `set = (variableName, variableValue) => void` — **always writes only to `localVars`** (same NaN-guard/warning as `Environment.set`). There is no way to write directly to environment/collection/global scope through `Variables.set`.\n- `replaceIn = async (template: string | object) => Promise<string>` — same contract as `Environment.replaceIn`, but the context is `this.toObject()` (the fully merged view across all scopes).\n- `toObject = () => Record<string, any>` — maps `[baseGlobalVars, globalVars, collectionVars, environmentVars, iterationDataVars, mergeFolderLevelVars(folderLevelVars), localVars]` to plain objects and folds them left-to-right with `{...ctx, ...obj}`, so **later entries in that array win** — i.e. `localVars` has final say, matching the precedence order used by `get`.\n- `localVarsToObject = () => Record<string, any>` *(`@ignore`)* — returns just `localVars.toObject()`; used by `InsomniaObject.toObject()` to serialize `insomnia.variables`.\n\n### `class Vault extends Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean)\n```\nCalls `super(name, jsonObject)`, then **returns a `Proxy(this, ...)`** (note: the constructor return value, not `this`, is what callers get). The proxy's `get`/`set` traps throw `new Error('Vault is disabled in script')` for **every property/method access** (including inherited `Environment` methods like `get`/`has`) whenever `enableVaultInScripts` is falsy.\n- `unset = () => { throw new Error('Vault can not be unset in script'); }` — overridden to always throw, regardless of `enableVaultInScripts`.\n- `clear = () => { throw new Error('Vault can not be cleared in script'); }` — always throws.\n- `set = () => { throw new Error('Vault can not be set in script'); }` — always throws (vault is read-only from scripts even when enabled).\n- Inherited `get`, `has`, `toObject`, `toJSON`, `replaceIn`, `name` still work normally when `enableVaultInScripts` is true (subject to the proxy's guard, which only blocks when the flag is false).\n\n## Script-facing surface\n- `insomnia.environment` → an `Environment` instance (the selected sub-environment, or the base environment itself if none is selected — see `initInsomniaObject` in `insomnia.ts`). Scripts call `insomnia.environment.get/set/unset/has/clear/replaceIn/toObject`.\n- `insomnia.baseEnvironment` → the collection's base `Environment`.\n- `insomnia.collectionVariables` → in `insomnia.ts`, this is literally assigned `this.baseEnvironment` (`this.collectionVariables = this.baseEnvironment;`) — it is not a distinct store, it's the *same* `Environment` object as `insomnia.baseEnvironment`.\n- `insomnia.variables` → a `Variables` instance built from all scopes (`insomnia.ts`'s `initInsomniaObject`: `baseGlobalVars: baseGlobals, globalVars: globals, environmentVars: environment, collectionVars: baseEnvironment, iterationDataVars: iterationData, folderLevelVars: parentFolders.getEnvironments(), localVars: localVariables`). Scripts use `insomnia.variables.get/set/has/replaceIn/toObject`. `insomnia.variables.set(...)` only ever affects the transient local scope.\n- `insomnia.vault` → a `Vault` instance; `insomnia.vault.get(<name>)` is the documented usage. Gated by the `enableVaultInScripts` setting.\n- `insomnia.globals`/`insomnia.baseGlobals` are declared `private` on `InsomniaObject`, but that's a TypeScript-only, compile-time restriction — `private` is erased at runtime, and `InsomniaObject`'s constructor returns `new Proxy(this, { get: (target, prop, receiver) => ... Reflect.get(target, prop, receiver) })` (see `insomnia.ts`), which forwards any property name through to the real field. A script can read `insomnia.globals`/`insomnia.baseGlobals` directly at runtime; they're just absent from the TS-declared public API, not actually inaccessible. Treat this as accidental exposure, not a supported surface — don't build fixes or features on scripts reading these, and any change here should aim to actually restrict access (e.g. a proxy guard like `Vault`'s) rather than expose them further.\n- `{{$randomUUID}}`-style and `{{variableName}}` templates in request URLs/bodies/headers are ultimately rendered through `Environment.replaceIn` / `Variables.replaceIn`.\n\n## Gotchas / notable behavior\n- **NaN guard**: both `Environment.set` and `Variables.set` silently refuse to store `NaN` and log a console warning instead of throwing — `null` and `undefined` are accepted just fine.\n- **Precedence order** (highest to lowest) used consistently by `Variables.has`, `.get`, and `.toObject`: local → folder-level (the **last** entry in the `folderLevelVars: Environment[]` array wins on collisions — see the merge-order gotcha below, not necessarily the nearest folder) → iteration data → environment → collection (`baseEnvironment`) → global → base global.\n- **Falsy values are not skipped**: `Variables.get` uses `.has()` to pick the scope, not truthiness, so a local value of `0`, `''`, or `false` is correctly returned instead of falling through to a lower-precedence scope (verified by a parameterized test in `environments.test.ts`).\n- **`collectionVariables` is an alias, not a separate object** — mutating `insomnia.collectionVariables` also mutates `insomnia.baseEnvironment` and vice versa, since they reference the same `Environment` instance.\n- **`Variables.set` cannot target non-local scopes** — there is no API on `Variables` to write into environment/collection/global; scripts must use `insomnia.environment.set(...)`, `insomnia.collectionVariables.set(...)`, etc. directly for those scopes.\n- **Folder-level merge order**: `mergeFolderLevelVars` merges the given `Environment[]` left-to-right with later entries winning; since `ParentFolders.getEnvironments()` returns folders \"from bottom to top\" is *not* guaranteed here — actual nearest-wins behavior depends on the order `folderLevelVars` is constructed in by the caller (see `folders.md`).\n- **`Vault` returns a `Proxy` from its constructor** — `new Vault(...)` does not yield a plain `Vault` instance; every property access goes through the proxy trap, so `enableVaultInScripts` is (re-)checked on *every* access, not just once at construction.\n- **`Vault` is effectively write-protected even when enabled**: `set`, `unset`, and `clear` are all overridden to unconditionally throw, so scripts can only ever read from the vault, never write to it, regardless of the `enableVaultInScripts` flag.\n- Deep/nested object values are not directly supported by `Environment`'s type signature (`boolean | number | string | null | undefined`); nothing in this file coerces or validates that at runtime, so anything else the caller passes is stored as-is.\n\n## Related\n- `properties.ts` — not used directly here, but `Variables`/`Environment` sit at the same conceptual layer that the `Variable`/`VariableList` classes (in `variables.ts`) also use for interpolation (via `getInterpolator()` in `interpolator.ts`).\n- `folders.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies the `folderLevelVars: Environment[]` array consumed by `Variables`.\n- `console.ts` — `getExistingConsole()` is used to emit the NaN warning.\n- `interpolator.ts` — `getInterpolator().render(...)` implements `{{...}}` template substitution for `replaceIn`.\n- `insomnia.ts` — constructs and wires up all the `Environment`/`Variables`/`Vault` instances exposed as `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`, `insomnia.vault`.\n",".claude/skills/fix-scripting-feature/references/objects/execution.md":"# Execution (`insomnia.execution` / `pm.execution`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/execution.ts`\n\n## Purpose\nModels `pm.execution`: where the currently-running request sits in the collection tree, plus two\ncollection-runner controls — skipping the request and rerouting to a different next request. This\nis one of the fields the collection runner reads back after a script finishes.\n\n## Public API\n\n### `interface ExecutionOption`\n```ts\ninterface ExecutionOption {\n  location: string[];\n  skipRequest?: boolean;\n  nextRequestIdOrName?: string;\n}\n```\nPlain-object shape used to construct an `Execution` (and the shape stored on `RequestContext.execution`, see `interfaces.md`).\n\n### `class Execution`\n```ts\nconstructor(options: ExecutionOption)\n```\n- `public location: string[]` — **not a plain array**: the constructor wraps a shallow copy\n  (`[...location]`) in a `Proxy` whose `get` trap adds a `current` accessor returning\n  `target.length > 0 ? target[target.length - 1] : ''` (the last path segment, or `''` if empty),\n  mirroring Postman's `execution.location.current` usage. All other property/index access passes\n  through via `Reflect.get`. Throws `TypeError('Location input must be array of string')` if\n  `options.location` is not an array.\n- `private _skipRequest: boolean` (default `false`), `private _nextRequestIdOrName: string` (default `''`) — no direct getters; only visible via `toObject()`.\n- `skipRequest = () => { this._skipRequest = true; }` — one-way flag setter; there is no method to un-set it.\n- `setNextRequest = (requestIdOrName: string) => { this._nextRequestIdOrName = requestIdOrName; }` — records which request the collection runner should execute next.\n- `toObject = () => ({ location: Array.from(this.location), skipRequest: this._skipRequest, nextRequestIdOrName: this._nextRequestIdOrName })` — serializes state to a plain object; `Array.from` strips the `Proxy`, so the resulting `location` array **loses the `.current` accessor**.\n\n## Script-facing surface\n- `insomnia.execution.location` — array of path segments (e.g. `['project', 'workspace', 'file', 'requestname']`).\n- `insomnia.execution.location.current` — last segment of the location path (Postman-compat).\n- `insomnia.execution.skipRequest()` — marks the current request to be skipped.\n- `insomnia.execution.setNextRequest(requestIdOrName)` — reroutes the collection runner to a specific request by id or name.\n\n## Gotchas / notable behavior\n- `location.current` only works on the live `Proxy`-wrapped array returned from the constructor;\n  once serialized via `toObject()` (which uses `Array.from`), the plain array no longer has\n  `.current`. This only matters for the runtime maintainers reading `toObject()`'s output — script\n  authors reading `insomnia.execution.location.current` directly are unaffected.\n- Passing a non-array `location` (e.g. a string) throws a `TypeError` synchronously from the\n  constructor — this is a `initInsomniaObject`/context-building bug, not something a script can\n  trigger directly, since scripts only read `insomnia.execution`, they don't construct it.\n- There's no public getter for `skipRequest`/`nextRequestIdOrName` state from the script side —\n  scripts can only set them, not read them back; only `toObject()` (used internally when the\n  runtime merges results into the persisted request context) exposes the current values.\n- Comment references Postman's docs on \"using variables in scripts\" for the `location.current`\n  design: https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/#using-variables-in-scripts\n\n## Related\n- `insomnia.ts` — `InsomniaObject.execution: Execution`; `toObject()` calls `execution.toObject()`.\n- `interfaces.ts` — `ExecutionOption` is embedded as `RequestContext.execution`.\n- `packages/insomnia/src/scripting/run-script.ts` — merges `mutatedContextObject.execution` (i.e. `Execution.toObject()`'s output) back into the returned `RequestContext` so the collection runner can act on `skipRequest`/`nextRequestIdOrName`.\n- `__tests__/execution.test.ts` — covers the `location`/`current` proxy behavior, `skipRequest()`/`setNextRequest()`, and the invalid-`location` throw.\n",".claude/skills/fix-scripting-feature/references/objects/folders.md":"# Folder / ParentFolders\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/folders.ts`\n\n## Purpose\nModels the folder hierarchy a request lives in, each folder carrying its own folder-level `Environment` of variables. `ParentFolders` is a container/lookup helper over an ordered array of `Folder`s, and its `getEnvironments()` is what feeds the `folderLevelVars: Environment[]` array consumed by `Variables` (`environments.ts`) — i.e. this is the source of folder-scoped variable resolution for `insomnia.variables`.\n\n## Public API\n\n### `class Folder`\n```ts\nconstructor(id: string, name: string, environmentObject: object | undefined)\n```\n- `id: string`\n- `name: string`\n- `environment: Environment` — constructed as `new Environment(`${id}.environment`, environmentObject)` (see `environments.md`).\n- `toObject = () => { id: string; name: string; environment: Record<string, any> }` — returns `id`, `name`, and `environment.toObject()`.\n\n### `class ParentFolders`\n```ts\nconstructor(private folders: Folder[])\n```\nPer the constructor JSDoc, `folders` is expected \"from bottom to top\" (nearest-ancestor folder first).\n\n- `get = (idOrName: string) => Folder` — finds the first folder whose `name` or `id` matches; **throws** `Error('Folder \"<idOrName>\" not found')` if none match.\n- `getById = (id: string) => Folder` — finds by `id` only; throws the same style of error if not found.\n- `getByName = (folderName: string) => Folder` — finds by `name` only; throws the same style of error if not found.\n- `findValue = (valueKey: string) => value | undefined` — reverses a **copy** of `this.folders` (`[...this.folders].reverse()`) and returns the first folder (after reversal) whose `environment.has(valueKey)`, then returns that folder's `environment.get(valueKey)`; returns `undefined` if no folder has it.\n- `toObject = () => object[]` — maps every folder to `folder.toObject()`, preserving the original (non-reversed) array order.\n- `getEnvironments = () => Environment[]` — maps every folder to `folder.environment`, preserving the original array order. This is the array handed to `Variables({ folderLevelVars: ... })`.\n\n## Script-facing surface\nNot directly exposed as a `pm.*` object itself — `ParentFolders` is internal plumbing constructed in `insomnia.ts`'s `initInsomniaObject`:\n```ts\nconst parentFolders = new ParentFolders(\n  rawObj.parentFolders.map(folderObj => new Folder(folderObj.id, folderObj.name, folderObj.environment)),\n);\n```\nand consumed two ways:\n- `parentFolders.getEnvironments()` → passed as `folderLevelVars` into `insomnia.variables` (`Variables`), so scripts reading `insomnia.variables.get('someFolderVar')` are indirectly reading through `Folder.environment`.\n- `parentFolders.toObject()` → included in `InsomniaObject.toObject()`'s `parentFolders` field (used for serialization/debugging, not typically read directly by user scripts).\nThere is no `insomnia.parentFolders` / `insomnia.folders` public property — `parentFolders` is a private field on `InsomniaObject`.\n\n## Gotchas / notable behavior\n- **The two folder-ordering JSDoc comments in this codebase appear to disagree, and `findValue` is not what scripts actually go through for `insomnia.variables`.** `ParentFolders`'s constructor doc says its `folders` array is ordered \"from bottom to top\" (i.e. index `0` = nearest/bottom, last index = topmost ancestor). `findValue`'s own doc says it searches \"starting from the nearest ancestor folder and moving towards the top ancestor folder.\" But the implementation does `[...this.folders].reverse().find(...)` — reversing a bottom-to-top array produces a top-to-bottom traversal, which is the *opposite* of what `findValue`'s own docstring claims. Treat `findValue`'s precedence as unverified/possibly inverted from its documented intent, and confirm against the actual call order at the construction site (`packages/insomnia/src/network/network.ts`, outside this SDK package) before relying on it to debug a specific precedence question. Note also that `findValue` is a standalone utility on `ParentFolders` — it is **not** what `insomnia.variables.get(...)` uses.\n- **`insomnia.variables.get(...)`'s folder precedence is governed by `mergeFolderLevelVars` in `environments.ts`, not by `ParentFolders.findValue`.** `mergeFolderLevelVars` merges the `folderLevelVars: Environment[]` array left-to-right with **later array entries winning** (`{...merged, ...folderLevelEnv.toObject()}`), and `ParentFolders.getEnvironments()` (used to build that array) preserves the original, non-reversed `this.folders` order. Per `environments.test.ts`'s `'variables operations'` test, `folders.getEnvironments()` is passed directly as `folderLevelVars`, and mutating the folder passed **second** to the `ParentFolders` constructor (`folder2`) is what wins in `variables.get('value')` — i.e. whichever folder appears later in the array given to `new ParentFolders([...])` wins ties in `insomnia.variables`, regardless of what \"nearest\"/\"topmost\" means in that particular caller's ordering convention.\n- `get`, `getById`, and `getByName` all throw plain `Error`s (not a custom error type) with a template string — safe to catch with a generic `try/catch` but there's no error code to switch on.\n- `Folder.environment`'s name is derived (`${id}.environment`), not the folder's own `name` — don't assume `folder.environment.name === folder.name`.\n\n## Related\n- `environments.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies `Variables`'s `folderLevelVars`, and `mergeFolderLevelVars` there is what actually implements the effective folder-variable precedence used by `insomnia.variables`.\n- `insomnia.ts` — constructs `ParentFolders` from `rawObj.parentFolders` and wires `getEnvironments()` into the `Variables` constructor; exposes `parentFolders.toObject()` via `InsomniaObject.toObject()`.\n- `index.ts` — re-exports `Folder` (but not `ParentFolders`) at the top level; `collection.ts` also re-exports `Folder`.\n",".claude/skills/fix-scripting-feature/references/objects/headers.md":"# Header, HeaderList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/headers.ts`\n\n## Purpose\n\nModels a single HTTP header (`Header`) and an ordered collection of headers (`HeaderList`). Used by `Request.headers` and `Response.headers` (`pm.request.headers` / `pm.response.headers`), and internally by `Request`'s header-merge logic. `HeaderList` is a thin specialization of the generic `PropertyList`.\n\n## Public API\n\n### `HeaderDefinition` (interface)\n`{ key: string; value: string; id?: string; name?: string; disabled?: boolean }`\n\n### `Header` (extends `Property`)\n- `override _kind = 'Header'`\n- `key: string`, `value: string`\n- `constructor(opts: HeaderDefinition | string, name?: string)` — if `opts` is a string, it's parsed via `Header.parseSingle` (only `key`/`value` are set — `id`/`name`/`disabled` come from `Property`'s defaults, not from the string). If `opts` is an object, `id`, `key`, `name` (overridden by the `name` param if given), `value`, and `disabled` are all read from it (each falling back to `''`/`false` if absent).\n- `static override _index = 'key'` — tells `PropertyList` to index/dedupe `Header`s by `key` (used by `one`, `indexOf`, `upsert`).\n- `static create(input?: {key, value} | string, name?: string): Header` — defaults `input` to `{key: '', value: ''}`.\n- `static isHeader(obj: object): boolean` — checks `_kind === 'Header'`.\n- `static parse(headerString: string): {key, value}[]` — splits on `\\n`, ignores blank lines, parses each via `parseSingle`.\n- `static parseSingle(headerStr: string): {key, value}` — splits on the **first** colon. **Throws** `Error('Header.parseSingle: the header string seems invalid')` if there's no colon or it's at position 0.\n- `static unparse(headers: {key,value}[] | PropertyList<Header>, separator?: string): string` — maps each header through `unparseSingle` and joins (default separator `'\\n'`).\n- `static unparseSingle(header: {key,value} | Header): string` — `\"key: value\"`.\n- `update(newHeader: {key, value})` — mutates `this.key`/`this.value` in place.\n- `override valueOf()` — returns `this.value`.\n\n### `HeaderList<T extends Header>` (extends `PropertyList<T>`)\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — internally always constructs the underlying `PropertyList` with `Header` as the type class (regardless of `T`).\n- `static isHeaderList(obj: any): boolean` — checks `_kind === 'HeaderList'`.\n- `contentSize(): number` — sum of `header.toString().length` (i.e. `\"key: value\"`.length) across all headers; comment notes special characters aren't handled.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, string | string[]>` — builds a plain key→value(s) map. `excludeDisabled` (default falsy) skips headers with `disabled` truthy. `multiValue` (default falsy) collects same-key headers into an array instead of overwriting with the last value. `_caseSensitive` and `_sanitizeKeys` are accepted but **unused**.\n\n## Script-facing surface\n\n`pm.request.headers` and `pm.response.headers` are `HeaderList<Header>` instances. Beyond the methods above, all generic `PropertyList` methods are available: `add`/`append`, `all()`, `assimilate()`, `clear()`, `count()`, `each()`, `filter()`, `find()`, `get(key)` / `one(key)` (keyed by `key` since `Header._index = 'key'`), `has()`, `idx()`, `indexOf()`, `insert()`/`insertAfter()`, `map()`, `populate()`, `prepend()`, `reduce()`, `remove()`, `repopulate()`, `toString()`, `upsert()`. Typical script usage: `pm.request.headers.add({key, value})`, `pm.request.headers.get('Content-Type')`, `pm.response.headers.has(...)`, `pm.request.headers.upsert(new Header({...}))`.\n\n## Gotchas / notable behavior\n\n- `new Header(stringOpts)` only populates `key`/`value` from the parsed string — `id`, `name`, and `disabled` are left at `Property`'s constructor defaults (`''`, `''`, `false`), never read from the string form.\n- `Header.parseSingle` throws on malformed input (no colon) — a raw header string with a typo can throw instead of silently producing an empty/garbage header.\n- `PropertyList.one(key)` (inherited, used for `HeaderList.get`) iterates **from the end of the list backwards**, so when duplicate keys exist, the **last-added** matching header wins — relevant when a script both sets a default header and later overrides it with the same key.\n- `HeaderList.toObject()`'s `_caseSensitive` and `_sanitizeKeys` parameters exist in the signature (for compatibility with Postman's original API shape) but have **no effect** — only `excludeDisabled` and `multiValue` are actually implemented.\n- `HeaderList`'s constructor ignores its own `T` type parameter internally — it always builds the base `PropertyList` using the concrete `Header` class, not whatever `T` was passed.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property` (base class for `Header`), `PropertyList` (base class for `HeaderList`).\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.headers: HeaderList<Header>`; also reuses `Header`/`HeaderList` in `addHeader`/`removeHeader`/`upsertHeader`/`getHeaders`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response.headers: HeaderList<Header>`; used in `contentInfo()` to find `Content-Type`/`Content-Disposition`.\n",".claude/skills/fix-scripting-feature/references/objects/insomnia.md":"# InsomniaObject (`insomnia` / `pm`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/insomnia.ts`\n\n## Purpose\nThis is the top-level scripting object: the single instance bound into the sandbox as `insomnia`\n(with `$` as a Postman-compat alias — see Script-facing surface). It aggregates every other object\nin this module (environments, variables, request, response, cookies, execution, request info,\nvault, client certificates, test assertions) into one value that a pre-request / after-response /\ntest script reads and mutates, and that the runtime serializes back out via `toObject()` after the\nscript finishes.\n\n## Public API\n\n### `class InsomniaObject`\n\n```ts\nconstructor(rawObj: {\n  globals: Environment;\n  baseGlobals: Environment;\n  iterationData: Environment;\n  environment: Environment;\n  baseEnvironment: Environment;\n  variables: Variables;\n  request: ScriptRequest;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookies: CookieObject;\n  requestInfo: RequestInfo;\n  execution: Execution;\n  response?: ScriptResponse;\n  parentFolders: ParentFolders;\n  vault?: Vault;\n})\n```\n\nPublic/exposed properties:\n- `environment: Environment` — the currently-selected environment.\n- `collectionVariables: Environment` — **same object reference as `baseEnvironment`** (see Gotchas).\n- `baseEnvironment: Environment` — the base (root) environment.\n- `variables: Variables` — layered variable resolver (globals/env/collection/iteration/folder/local).\n- `request: ScriptRequest` — the request being built/sent (`pm.request`).\n- `cookies: CookieObject` — cookie jar wrapper (`pm.cookies`).\n- `info: RequestInfo` — event/iteration metadata (`pm.info`).\n- `response?: ScriptResponse` — present only in after-response/test scripts.\n- `execution: Execution` — location/skip/next-request control (`pm.execution`).\n- `vault?: Vault` — secret vault accessor, gated by `settings.enableVaultInScripts`.\n- `clientCertificates: ClientCertificate[]` — raw client certs available to the request.\n\nPrivate/internal properties (TypeScript-only — see Gotchas for why this doesn't restrict scripts at\nruntime): `_expect` (chai `expect`), `_test`/`_skip` (from `./test`), `iterationData: Environment`,\n`globals: Environment`, `baseGlobals: Environment` (marked\n`// TODO: follows will be enabled after Insomnia supports them`), `_settings: Settings`,\n`requestTestResults: RequestTestResult[]`, `parentFolders: ParentFolders`.\n\nMethods:\n- `sendRequest(request: string | ScriptRequest, cb: (error?: string, response?: ScriptResponse) => void)` — delegates to `sendRequest()` from `./send-request`, passing the internal `_settings`.\n- `test = () => {}` — a no-op placeholder property; the *actual* behavior is installed by the\n  constructor's `Proxy` (see below). Declaring this field exists mainly so `test` shows up as an\n  own property.\n- `expect = (exp: boolean | number | string | object) => this._expect(exp)` — thin pass-through to chai's `expect`.\n- `get settings()` — **always returns `undefined`** (see Gotchas).\n- `toObject = () => {...}` — serializes the whole object graph to a plain object: `globals`,\n  `baseGlobals`, `environment`, `baseEnvironment`, `iterationData` (all via each `Environment`'s\n  `toObject()`), `variables` (via `variables.localVarsToObject()`), `request`, `settings` (calls\n  `this.settings`, i.e. always `undefined`), `clientCertificates`, `cookieJar` (via\n  `cookies.jar().toInsomniaCookieJar()`), `info` (via `info.toObject()`), `response` (via\n  `response.toObject()` or `undefined`), `requestTestResults`, `execution` (via\n  `execution.toObject()`), `parentFolders` (via `parentFolders.toObject()`).\n\nThe constructor returns `new Proxy(this, { get: ... })`: any property access other than `'test'`\npasses straight through via `Reflect.get`. Accessing `.test` instead returns a freshly-built\n`TestHandler` function that calls `this._test(msg, fn, this.pushRequestTestResult)`, with a\n`.skip` method that calls `this._skip(msg, fn, this.pushRequestTestResult)`. `pushRequestTestResult`\nappends each `RequestTestResult` to the private `requestTestResults` array (immutably, via spread).\n\n### `async function initInsomniaObject(rawObj: RequestContext, log: (...args: any[]) => void): Promise<InsomniaObject>`\n\nFactory used by the script runner to build an `InsomniaObject` from a `RequestContext` snapshot\n(see `interfaces.md`). Responsibilities, in order:\n- Maps `globals`/`baseGlobals` — if the same environment id is selected for both, `globals` and\n  `baseGlobals` become the *same* `Environment` instance; otherwise separate instances are created.\n- Maps `environment`/`baseEnvironment` the same way; if no sub-environment is selected (ids equal),\n  logs a warning via `log(...)` that mutations to `insomnia.environment` will apply to the base\n  environment.\n- Builds `iterationData` and `transientVariables`-backed local variables `Environment`s (defaulting\n  to empty named environments when absent).\n- Builds a `Vault` from `rawObj.vault` (or an empty object), gated by\n  `rawObj.settings?.enableVaultInScripts` (defaults `false`).\n- Builds `CookieObject` from `rawObj.cookieJar`.\n- Builds `RequestInfo` from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- Builds `ParentFolders` from `rawObj.parentFolders`, then a `Variables` instance that layers\n  base-global/global/environment/collection/iteration-data/folder-level/local variables.\n- Resolves client certificates: uses `checkIfUrlIncludesTag` (from `./utils`) and\n  `filterClientCertificates` (from `insomnia/src/network/certificate`) against `rawObj.request.url`.\n  If the URL contains template tags or no certificate matches, initializes an **empty** default\n  certificate and logs a warning via `getExistingConsole().warn(...)`; otherwise uses the first\n  matched certificate.\n- Builds the request URL (`toUrlObject`), proxy options (`transformToSdkProxyOptions`,\n  `resolveProtocolForProxy`), adds query params, builds the auth via `toPreRequestAuth`, and\n  constructs a `ScriptRequest`.\n- Constructs an `Execution` from `rawObj.execution`.\n- Reads the response body via `readBodyFromPath` and builds a `ScriptResponse` via\n  `toScriptResponse` if `rawObj.response` is present.\n- Returns `new InsomniaObject({...})` with everything above.\n\n## Script-facing surface\n`InsomniaObject` *is* what a script sees as the global `insomnia` object (and `$`, a Postman-compat\nalias set up as `const $ = insomnia;` by the script wrapper in\n`packages/insomnia/src/scripting/run-script.ts`). Nearly every `pm.*`/`insomnia.*` call in a script\nroutes through this object's properties/methods directly:\n- `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`\n- `insomnia.request`, `insomnia.response`, `insomnia.cookies`, `insomnia.info`, `insomnia.execution`, `insomnia.vault`\n- `insomnia.test(name, fn)` / `insomnia.test.skip(name, fn)`\n- `insomnia.expect(value)`\n- `insomnia.sendRequest(request, callback)`\n\n`run-script.ts` requires the script's `insomnia` result to be `instanceof InsomniaObject` — if the\nuser script returns early or otherwise breaks that invariant, `runScript` throws\n`'insomnia object is invalid or script returns earlier than expected.'`. After the script runs,\n`InsomniaObject.toObject()` is used to merge mutations back into the persisted `RequestContext`.\n\n## Gotchas / notable behavior\n- **`collectionVariables` is not a copy** — `this.collectionVariables = this.baseEnvironment;` in\n  the constructor means `insomnia.collectionVariables` and `insomnia.baseEnvironment` are the exact\n  same `Environment` instance. Mutating one via a script mutates the other.\n- **`insomnia.settings` is always `undefined`** — the `get settings()` accessor unconditionally\n  `return;`s nothing, even though the real `Settings` object is held internally as `_settings` (used\n  only for `sendRequest`'s proxy/certificate resolution). `toObject().settings` is therefore always\n  `undefined` too, regardless of what `Settings` were passed in.\n- **`globals`/`baseGlobals` are `private` in name only** — `private` is TypeScript-only and erased at runtime, and the constructor's `Proxy` forwards them (via `Reflect.get`, no `set` trap) same as any other property, so scripts can read *and* write `insomnia.globals`/`insomnia.baseGlobals` despite them being absent from the TS-declared public surface. Accidental exposure, not a supported feature.\n- **The `test` proxy is easy to miss when reading the class** — the `test = () => {}` field looks\n  like the real implementation, but any actual call to `insomnia.test(...)` is intercepted by the\n  constructor's `Proxy` `get` trap before it ever reaches that field.\n- **Certificate fallback is silent unless you check logs** — if the request URL contains\n  `{{`/`}}`/`{%`/`%}` template tags, or no client certificate matches the host, an *empty* default\n  certificate is substituted and a warning is written via the script console rather than thrown as\n  an error.\n- **No-environment-selected warning** — selecting the base environment (rather than a\n  sub-environment) causes `initInsomniaObject` to log a warning that `insomnia.environment`\n  mutations will land on the base environment, because `environment` and `baseEnvironment` are the\n  same instance in that case.\n\n## Related\n- `interfaces.ts` — defines `RequestContext`, the input shape `initInsomniaObject` consumes.\n- `execution.ts` — `Execution`, held as `insomnia.execution`.\n- `request-info.ts` — `RequestInfo`, held as `insomnia.info`.\n- `environments.ts` — `Environment`, `Variables`, `Vault` classes used throughout.\n- `cookies.ts` — `CookieObject`, held as `insomnia.cookies`.\n- `folders.ts` — `Folder`, `ParentFolders`, used for folder-level variables.\n- `request.ts` — `Request`/`RequestOptions`/`toScriptRequestBody`, builds `insomnia.request`.\n- `response.ts` — `toScriptResponse`, `readBodyFromPath`, builds `insomnia.response`.\n- `send-request.ts` — implements `insomnia.sendRequest(...)`.\n- `test.ts` — `test`/`skip`/`TestHandler`, implements `insomnia.test(...)`.\n- `auth.ts` — `toPreRequestAuth`, used when building the request's auth config.\n- `proxy-configs.ts` — `transformToSdkProxyOptions`, used for request proxy settings.\n- `urls.ts` — `toUrlObject`, `resolveProtocolForProxy`.\n- `utils.ts` — `checkIfUrlIncludesTag`, used for certificate-fallback detection.\n- `console.ts` — `getExistingConsole`, used to log warnings.\n- `packages/insomnia/src/scripting/run-script.ts` — binds this object as the sandbox global `insomnia` and calls `toObject()` after the script runs.\n",".claude/skills/fix-scripting-feature/references/objects/interfaces.md":"# Shared interfaces (`RequestContext`, `IEnvironment`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interfaces.ts`\n\n## Purpose\nPure type-only file (no runtime code) defining the shared shapes used to pass state into and out\nof the scripting environment. `RequestContext` is the full serialized state snapshot that the host\napp (`packages/insomnia`) builds before running a script and that `initInsomniaObject` (in\n`insomnia.ts`) turns into a live `InsomniaObject`.\n\n## Public API\n\n### `interface IEnvironment`\n```ts\ninterface IEnvironment {\n  id: string;\n  name: string;\n  data: object;\n}\n```\nMinimal serialized shape for any environment-like blob (globals, base globals, environment, base\nenvironment, vault). Marked `/** @ignore */` (excluded from generated public docs).\n\n### `interface RequestContext`\n```ts\ninterface RequestContext {\n  request: Request;\n  timelinePath: string;\n  environment: IEnvironment;\n  baseEnvironment: IEnvironment;\n  vault?: IEnvironment;\n  collectionVariables?: object;\n  globals?: IEnvironment;\n  baseGlobals?: IEnvironment;\n  iterationData?: Omit<IEnvironment, 'id'>;\n  timeout: number;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookieJar: CookieJar;\n  response?: any;\n  requestTestResults?: RequestTestResult[];\n  requestInfo: RequestInfoOption;\n  execution: ExecutionOption;\n  logs: string[];\n  transientVariables?: Omit<IEnvironment, 'id'>;\n  parentFolders: { id: string; name: string; environment: Record<string, any> }[];\n}\n```\nFull input/output snapshot for one script execution. Fields of note:\n- `request: Request` — the raw request model (from `insomnia-data`), not the script-facing `ScriptRequest` wrapper in `request.ts`.\n- `globals`/`baseGlobals` — optional; per the inline comment, \"activated only when selected\".\n- `iterationData`/`transientVariables` — typed as `Omit<IEnvironment, 'id'>` (no `id` field needed for these).\n- `response?: any` — deliberately untyped; comment notes \"Callback types defined elsewhere to avoid circular imports\".\n- `requestInfo: RequestInfoOption` and `execution: ExecutionOption` — imported from `./request-info` and `./execution` respectively; these are the plain-object option shapes consumed by the `RequestInfo`/`Execution` constructors (see `request-info.md`/`execution.md`).\n- `parentFolders` — array of plain folder descriptors (`id`, `name`, `environment` data), consumed by `ParentFolders`/`Folder` in `folders.ts`.\n\nBoth interfaces are marked `/** @ignore */`, meaning they're internal/plumbing types not meant to\nappear in the generated public SDK reference.\n\n## Script-facing surface\nNone directly. A user's pre-request/after-response/test script never sees a `RequestContext` or\n`IEnvironment` value — these are internal transport types used by the host application\n(`packages/insomnia/src/scripting/run-script.ts`) to hand state into `initInsomniaObject` and to\nreceive mutated state back out via `InsomniaObject.toObject()`.\n\n## Gotchas / notable behavior\n- `globals`/`baseGlobals`/`vault` are all optional — code reading them elsewhere (e.g.\n  `initInsomniaObject`) must handle `undefined` explicitly (it does, via `?.` and fallbacks).\n- `response` being typed `any` means no compile-time safety on the response shape passed into the\n  scripting environment; the real shape is validated only implicitly by how `toScriptResponse` (in\n  `response.ts`) consumes it.\n- This file has no runtime exports (interfaces only) — importing it has zero side effects.\n\n## Related\n- `insomnia.ts` — `initInsomniaObject(rawObj: RequestContext, log)` is the sole consumer that turns this shape into a live `InsomniaObject`.\n- `execution.ts` — supplies `ExecutionOption`, embedded as `RequestContext.execution`.\n- `request-info.ts` — supplies `RequestInfoOption`, embedded as `RequestContext.requestInfo`.\n- `environments.ts` — `Environment`/`Vault` classes are constructed from `IEnvironment`-shaped data.\n- `folders.ts` — `Folder`/`ParentFolders` are constructed from `RequestContext.parentFolders`.\n- `packages/insomnia/src/scripting/run-script.ts` — builds and consumes `RequestContext` around each script run.\n",".claude/skills/fix-scripting-feature/references/objects/interpolator.md":"# Interpolator (template-tag rendering)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interpolator.ts`\n\n## Purpose\nInternal helper providing `{{variable}}`-style template rendering (plus `{{$fakerFn}}` dynamic\nvalue generation) on top of LiquidJS. It backs every \"replace variables in this string\" operation\nexposed by the environment/property objects (e.g. `insomnia.environment.replaceIn(...)`).\n\n## Public API\n\n### `class Interpolator` (not exported — module-private)\n```ts\nconstructor()\n```\nBuilds a `Liquid` engine configured with:\n- `outputDelimiterLeft: '{{'`, `outputDelimiterRight: '}}'` — variable interpolation delimiters.\n- `tagDelimiterLeft: '{%'`, `tagDelimiterRight: '%}'` — Liquid tag delimiters (e.g. `{% if %}`).\n- `strictVariables: true` — referencing an undefined variable causes a render error rather than silently rendering empty.\n- `jsTruthy: true` — use JS truthiness semantics instead of Liquid's default truthy rules.\n- `ownPropertyOnly: false` — allows resolving inherited/prototype properties on the context object, not just own properties.\n\nMethods:\n- `render = async (template: string, context: object): Promise<string>` — runs `renderWithFaker(template)` first (to substitute any `{{$fakerFn}}` tags with generated values), then parses/renders the result through the Liquid engine against `context`. Comment: `// TODO: support plugins`.\n- `renderWithFaker = (template: string) => string` — a pre-pass over the template string, done via\n  manual string splitting (not Liquid) rather than regex/AST parsing:\n  1. Splits `template` on `'}}'`.\n  2. For each segment, finds the last `'{{'` before the end of the segment.\n  3. If the found tag name starts with `'$'`, strips the `$` prefix, looks it up in\n     `fakerFunctions` (from `insomnia/src/common/templating/faker-functions`), calls it, and\n     splices the generated value in place of the tag.\n  4. If the tag name does not start with `$`, the segment (and its `}}`) is left untouched for the\n     real Liquid engine to interpolate as a normal variable.\n  5. Throws `Error('replaceIn: no faker function is found: ${funcName}')` if the `$`-prefixed name\n     isn't a recognized faker function.\n\n### `function getInterpolator(): Interpolator`\nReturns the single module-level `Interpolator` instance (`const interpolator = new Interpolator();`), constructed once at module load. This is the only exported symbol.\n\n## Script-facing surface\nNo direct surface — scripts never call `getInterpolator()` themselves. It is reached indirectly\nthrough higher-level methods on other objects, all of which delegate to\n`getInterpolator().render(...)`:\n- `insomnia.environment.replaceIn(template)` / same method on `baseEnvironment`, `collectionVariables`, `variables`, etc. (defined in `environments.ts`) — e.g. `insomnia.environment.replaceIn(\"My id is {{$randomUUID}}\")` or `insomnia.environment.replaceIn(\"Visiting URL: {{urlValueFromEnvironment}}\")`.\n- `Property.replaceSubstitutions(content, ...variables)` and `Property.replaceSubstitutionsIn(obj, ...variables)` (static methods in `properties.ts`) — used internally wherever a `Property`-derived object needs variable substitution against multiple variable-scope objects merged together.\n\n## Gotchas / notable behavior\n- `renderWithFaker` is a hand-rolled string scan, not a real parser — it looks for the *last*\n  `'{{'` before each `'}}'` boundary in each split segment. Malformed or nested template syntax\n  could confuse this pass before Liquid ever sees the template.\n- `strictVariables: true` means referencing a variable name that isn't present in `context` will\n  cause the Liquid render to throw/reject, not silently produce an empty string — a common source\n  of \"my `{{someVar}}` template failed\" script errors when the variable isn't actually defined in\n  any active scope.\n- `$`-prefixed faker tags are resolved by this module's own pre-pass, *before* Liquid — an unknown\n  `$xyz` faker function name throws synchronously from `renderWithFaker` with the message\n  `replaceIn: no faker function is found: xyz`.\n- `getInterpolator()` always returns the same singleton `Interpolator` instance across the whole\n  process — there is no way to get a differently-configured interpolator or to reset its Liquid\n  engine mid-run.\n- The `Interpolator` class itself is not exported; only `getInterpolator()` is, so consumers cannot\n  construct their own instance or subclass it.\n\n## Related\n- `environments.ts` — `Environment`/`Variables`/`Vault`'s `replaceIn(...)` methods call `getInterpolator().render(...)`.\n- `properties.ts` — `Property.replaceSubstitutions`/`replaceSubstitutionsIn` call `getInterpolator().render(...)`.\n- `insomnia/src/common/templating/faker-functions` (outside this package) — supplies the `fakerFunctions` map used for `{{$fakerFn}}` tags.\n",".claude/skills/fix-scripting-feature/references/objects/properties.md":"# PropertyBase / Property / PropertyList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/properties.ts`\n\n## Purpose\nBase classes that most other SDK objects extend, mirroring Postman's `postman-collection` `Property`/`PropertyList` model: `PropertyBase` provides parent-chain traversal and JSON serialization, `Property` adds `id`/`name`/`disabled` plus static template-substitution helpers, and `PropertyList<T>` is a generic ordered/keyed collection with Postman-compatible methods (`add`, `each`, `filter`, `find`, `one`, `upsert`, `toObject`, etc.). Concrete subclasses (`Variable`/`VariableList`, `Header`/`HeaderList`, `Cookie`/`CookieList`, `ProxyConfig`/`ProxyConfigList`, etc.) build on top of these.\n\n## Public API\n\n### `unsupportedError(featureName: string, alternative?: string): Error`\nBuilds an `Error` with message `` `${featureName} is not supported yet` `` (optionally appending `, please use ${alternative} instead temporarily.` when `alternative` is given). Used throughout the SDK (e.g. `Variable.types()`, `PropertyList.eachParent`) to mark stubbed-out Postman-compat methods.\n\n### `class PropertyBase`\n```ts\nconstructor(description?: string)\n```\n- `public _kind = 'PropertyBase'` — used as a lightweight runtime type tag (checked via `'_kind' in obj` elsewhere, e.g. `VariableList.isVariableList`).\n- `protected _parent: PropertyBase | undefined` — set only via subclasses/external assignment; no setter method is provided on the base class itself.\n- `protected description?: string`.\n- `static propertyIsMeta(_value: any, key: string): boolean` — `key && key.startsWith('_')` (keys starting with `_` are treated as \"meta\").\n- `static propertyUnprefixMeta(_value: any, key: string): string` — strips a leading `_` from `key` if present.\n- `meta(): {}` — always returns an empty object (not implemented beyond the stub).\n- `parent(): PropertyBase | undefined` — returns `this._parent`.\n- `forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean): PropertyBase[] | undefined` — BFS-style walk up the parent chain (via `parent()`), cloning each ancestor with the `clone` package before passing it to `iterator`; stops as soon as `iterator` returns falsy, and always returns the array of cloned ancestors visited so far (or `undefined` if there is no parent at all). `_options` is accepted but never read.\n- `findInParents(property: string, customizer?: (ancestor: PropertyBase) => boolean): PropertyBase | undefined` — walks the (cloned) parent chain looking for the first ancestor whose `meta()` keys include `property`; if `customizer` is given, keeps walking until `customizer(ancestor)` returns truthy, otherwise returns the first ancestor with that meta key. Returns `undefined` if no parent, or none matches.\n- `toJSON(): Record<string, any>` — returns `Object.entries(this)` filtered to drop function-valued entries, `undefined` values, and the `_kind` key.\n- `toObject(): Record<string, any>` — returns `this.toJSON()`.\n- `toString(): string` — `JSON.stringify(this.toJSON())`.\n\n### `class Property extends PropertyBase`\n```ts\nconstructor(id?: string, name?: string, disabled?: boolean, info?: { id?: string; name?: string })\n```\n`info.id`/`info.name` take priority over the positional `id`/`name` params if both are given; `this._kind` is set to `'Property'`; `disabled` defaults to `false`.\n- `id: string`, `name?: string`, `disabled?: boolean`.\n- `static _index = 'id'` — the field name `PropertyList` uses to key/index items of this type; overridden by subclasses (e.g. `Variable._index = 'key'`).\n- `static async replaceSubstitutions(content: string, ...variables: object[]): Promise<string>` — throws `TypeError` if `variables` isn't an array or `content` isn't a string. Merges `variables` into a single context object: the array is `.reverse()`d first, then folded left-to-right with `{...context, ...variable}`, so the reversed (originally-last) entries are applied first and get overwritten by earlier ones — net effect: **the leftmost/first argument passed to `replaceSubstitutions` wins** on key collisions, matching the inline comment (\"the searching priority of rendering is from left to right\"). Renders `content` against that merged context via `getInterpolator().render(...)`.\n- `static async replaceSubstitutionsIn(obj: object, ...variables: object[]): Promise<object>` — same signature/merge semantics as `replaceSubstitutions`, but stringifies `obj` with `JSON.stringify`, renders it, then `JSON.parse`s the result. Throws `TypeError` for bad args up front; wraps any other error (e.g. a `JSON.parse` failure on non-JSON-safe rendered output) in a new `Error('replaceSubstitutionsIn: ' + e.toString())`.\n- `describe(content: string, typeName: string): void` — sets `this._kind = typeName` and `this.description = content`.\n\n### `class PropertyList<T extends Property>`\n```ts\nconstructor(\n  protected typeClass: { _index?: string },\n  protected parent: Property | PropertyList<any> | undefined,\n  populate: T[],\n)\n```\n- `protected _kind = 'PropertyList'`, `protected list: T[]`.\n- `static isPropertyList(obj: object): boolean` — `'_kind' in obj && obj._kind === 'PropertyList'`.\n- `add(item: T): void` — pushes to the end of `list`.\n- `all(): Record<string, any>[]` — `list.map(pp => pp.toJSON())`.\n- `append(item: T): void` — alias for `add`.\n- `assimilate(source: T[] | PropertyList<T>, prune?: boolean): void` — if `prune`, calls `clear()` first; then pushes all of `source`'s items (`source.list` if it's a `PropertyList`, else the array itself) onto `list`. Per an inline comment, \"it doesn't update values from a source list\" (i.e. this appends, it does not merge/replace by key).\n- `clear(): void` — `list = []`.\n- `count(): number` — `list.length`.\n- `each(iterator: (item: T) => void, context: object): void` — calls `list.forEach(iterator)`; `context` is stashed as `iterator.context` (assigned onto the function) but the iterator itself is not invoked with that context bound — purely a Postman-compat artifact.\n- `eachParent(_iterator, _context?): never` — **always throws** `unsupportedError('eachParent')`; not implemented (\"properties are not organized as hierarchy\" per the TODO comment).\n- `filter(rule: (item: T) => boolean, context: object): T[]` — same `context`-stash pattern as `each`; returns `list.filter(rule)`.\n- `find(rule: (item: T) => boolean, context?: object): T | undefined` — same pattern; `list.find(rule)`.\n- `get(key: string): T | undefined` — alias for `one(key)`.\n- `has(item: T, _value?: any): boolean` — `indexOf(item) >= 0`; `_value` is accepted but unused (\"its usage is unknown\" per comment).\n- `idx(index: number): T | undefined` — returns `list[index]` if `index <= list.length - 1`, else `undefined`.\n- `indexOf(item: string | T): number` — looks up the index field via `typeClass._index || 'id'`; if `item` is a string, matches `record[indexFieldName] === item`; otherwise matches `record[indexFieldName] === (item as Record<string, any>)[indexFieldName]`. Returns `-1` if not found.\n- `insert(item: T, before?: number): void` — splices `item` in before index `before` if valid (`before != null && before >= 0 && before <= list.length - 1`), else falls back to `append(item)`.\n- `insertAfter(item: T, after?: number): void` — splices `item` in right after index `after` under the same bounds check, else falls back to `append(item)`.\n- `map(iterator: (item: T) => any, context: object): any[]` — same `context`-stash pattern; `list.map(iterator)`.\n- `one(id: string): T | undefined` — scans `list` **backwards** (`for (let i = list.length - 1; i >= 0; i--)`) for the first record whose index field equals `id`; if the matched item has a callable `valueOf`, returns `item.valueOf()` instead of the item itself; else returns the item as-is. Returns `undefined` if not found.\n- `populate(items: T[]): void` — `list = [...list, ...items]`.\n- `prepend(item: T): void` — `list = [item, ...list]`.\n- `reduce(iterator: (acc: any, item: T) => any, accumulator: any, context: object): any` — same `context`-stash pattern; `list.reduce(iterator, accumulator)`.\n- `remove(predicate: T | ((item: T) => boolean), context: object): void` — if `predicate` is a function, keeps everything that does *not* match it (via `filter` with the negated predicate); if `predicate` is a value, keeps everything not `deep-equal` to it.\n- `repopulate(items: T[]): void` — `clear()` then `populate(items)`.\n- `toObject(_excludeDisabled?, _caseSensitive?, _multiValue?, _sanitizeKeys?): Record<string, any> | Record<string, any>[]` — base implementation ignores all four arguments and returns `list.map(elem => elem.toJSON())` (an array, not a keyed object) — it's the fallback for lists whose items have no natural key (e.g. `UrlMatchPatternList`, or a raw `PropertyList` not used through a subclass). Subclasses backed by keyed items (`CookieList`, `HeaderList`, `VariableList`, `ProxyConfigList`) override this with a real key→value map that supports `excludeDisabled`/`multiValue`; `caseSensitive`/`sanitizeKeys` remain unsupported even in those overrides.\n- `toString(): string` — `` `[${list.map(item => item.toString()).join('; ')}]` ``.\n- `upsert(item: T): boolean` — returns `false` immediately if `item == null`. If `indexOf(item)` finds an existing entry, **splices it out and reinserts the new item at the same position** (returns `false`, meaning \"updated, not inserted\"); otherwise calls `add(item)` and returns `true` (\"inserted new\"). Note: the \"splice out, then splice again\" implementation calls `this.list.splice(...)` twice on the *same* array reference inside one expression (`[...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]`) — the first `splice` call mutates `this.list` in place before the second one runs against the now-shortened array.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` itself — these are base classes. Scripts interact with them only through concrete subclasses' public APIs, e.g.:\n- `pm.variables`/collections of variables → `VariableList` (extends `PropertyList<Variable>`), `Variable` (extends `Property`) — see `variables.md`.\n- `pm.request.headers`, `pm.response.headers` → `HeaderList`/`Header`.\n- `pm.request.url.query` → `UrlMatchPatternList`/similar list types.\n- Any script calling `.get()`, `.one()`, `.each()`, `.filter()`, `.upsert()`, `.toObject()`, etc. on a collection-like SDK object is going through `PropertyList`'s implementation documented above.\n- `Property.replaceSubstitutions`/`replaceSubstitutionsIn` back the SDK's `{{variable}}` template rendering wherever it's invoked as a static helper (distinct from, but functionally similar to, `Environment.replaceIn`/`Variables.replaceIn` in `environments.ts`).\n\n- **`replaceSubstitutions`/`replaceSubstitutionsIn` mutate their own `variables` rest-array via `.reverse()`** before merging — this is an implementation detail (not observable to callers since `variables` is a fresh rest-parameter array each call), but worth knowing if you're stepping through this code: the reversal is what makes the final left-to-right merge produce \"leftmost argument wins\" precedence.\n- **`eachParent` always throws** `unsupportedError('eachParent')` — calling it from a script will break, regardless of arguments.\n- **`each`/`filter`/`find`/`map`/`reduce`'s `context` parameter does nothing functionally** — it's stashed as a property on the callback function object (`it.context = context`) purely for Postman API-shape compatibility, but the callback is invoked with `Array.prototype`'s normal (unbound) semantics, so relying on `context` for `this`-binding inside the iterator will not work as it might in Postman.\n- **`one(id)` scans backwards and unwraps `valueOf()`** — if two items share the same index-field value, `one()` returns the *last*-added one (matching most recently `add`/`upsert`ed semantics), and if that item defines a custom `valueOf`, the returned object is `item.valueOf()`, not the raw list item.\n- **`upsert(item)` replaces the item at the same index** via `[...this.list.slice(0, itemIdx), item, ...this.list.slice(itemIdx + 1)]`, leaving every other item in place.\n- **`toObject()`'s base implementation returns an array, not an object**, unlike its keyed subclass overrides (`VariableList.toObject()` etc., which return `Record<string, any>`) — code that assumes every `PropertyList.toObject()` yields a key/value map will break for list types that don't override it (e.g. `UrlMatchPatternList`).\n- `PropertyBase.forEachParent`/`findInParents` both `clone()` each ancestor before handing it to the caller/comparing — mutations the iterator/customizer performs on the passed-in ancestor object do not affect the real parent chain.\n- `Property`'s constructor lets `info.id`/`info.name` silently override the positional `id`/`name` arguments — passing both can be surprising if not intentional.\n\n## Related\n- `variables.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>` (see `variables.md`).\n- `environments.ts` — a separate, non-`Property`-based variable model (`Environment`/`Variables`) used for `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables`; don't conflate the two when tracing a variable bug.\n- `interpolator.ts` — `getInterpolator().render(...)` is what `Property.replaceSubstitutions`/`replaceSubstitutionsIn` delegate to for `{{...}}` template rendering.\n- `headers.ts`, `cookies.ts`, `proxy-configs.ts`, `urls.ts` — other concrete `Property`/`PropertyList` subclasses that override `toObject()` with their own keyed semantics.\n- `collection.ts` / `index.ts` — re-export `Property`, `PropertyBase`, `PropertyList` as part of the SDK's public surface.\n",".claude/skills/fix-scripting-feature/references/objects/proxy-configs.md":"# ProxyConfig, ProxyConfigList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts`\n\n## Purpose\nModels a single proxy configuration (`pm.request.proxy`) and a keyed list of them (`ProxyConfigList`, exported for scripts to build their own lists, e.g. via `require('insomnia-collection')`). Also provides `transformToSdkProxyOptions`, the function that converts Insomnia's native proxy settings (`httpProxy`/`httpsProxy`/`proxyEnabled`/`noProxy` strings from Settings) into the SDK's `ProxyConfigOptions` shape used to seed `pm.request.proxy`.\n\n## Public API\n\n### Types\n- `interface ProxyConfigOptions { match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string }` — constructor input for `ProxyConfig`. `match` is a URL match pattern string (see `urls.ts`) initializing a `UrlMatchPattern` internally, e.g. `'http+https://example.com/*'`. `bypass` and `protocol` are called out in comments as \"for compatibility with Insomnia\".\n\n### `class ProxyConfig extends Property`\n- `override _kind = 'ProxyConfig'`\n- `host: string`\n- `match: string` — the raw match-pattern string (not a `UrlMatchPattern` instance on the instance property, though a `UrlMatchPattern` is constructed on the fly inside `getProtocols()`/`test()`).\n- `port?: number`\n- `tunnel: boolean`\n- `authenticate: boolean`\n- `username: string`\n- `password: string`\n- `bypass: string[]` — list of hosts/URLs to bypass the proxy for; comment: \"for compatibility with Insomnia's bypass list\".\n- `protocol: string` — e.g. `\"http\"`/`\"https\"`.\n- `static authenticate = false`, `static bypass: UrlMatchPatternList<UrlMatchPattern> = new UrlMatchPatternList(undefined, [])`, `static host = ''`, `static match = ''`, `static password = ''`, `static port?: number = undefined`, `static tunnel = false` (comment: \"unsupported\"), `static username = ''`, `static protocol = 'https:'` — all marked `@ignore` in JSDoc with the comment \"following properties are hidden as they are not used while must be exposed\". These are class-level defaults, distinct from the instance properties of the same name.\n- `constructor(def: { id?: string; name?: string; match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string })` — sets `id`/`name` from `def` or defaults to `''`, `disabled` defaults to `false`, `bypass` defaults to `[]` if omitted. All other fields copied straight through (no defaulting).\n- `static override _index = 'key'` — Note: `ProxyConfig` has no instance field literally named `key`; `PropertyList.indexOf()`/`.one()` would look for a `key` property on the record, which doesn't exist on `ProxyConfig` — see Gotchas. (`ProxyConfigList.toObject()` keys its map by `match`, not by this `_index`.)\n- `static isProxyConfig(obj: object): boolean` — checks `obj._kind === 'ProxyConfig'`.\n- `getProtocols(): string[]` — builds a `UrlMatchPattern(this.match)` and returns `.getProtocols()` (parses the `'+'`-delimited protocol prefix of the match string, e.g. `'http+https://...'` → `['http', 'https']`).\n- `getProxyUrl(): string` — builds the full proxy URL: `` `${protocol}//${username}:${password}@${host}${port}` `` if `authenticate` is true, otherwise `` `${protocol}//${host}${port}` `` (port segment omitted entirely if `port === undefined`).\n- `test(url?: string): boolean` — returns `false` immediately if `url` is falsy (comment: \"TODO: it is confusing in which case url arg is optional\"). Returns `false` if `url` is literally in `this.bypass` (exact string match, not pattern matching). Otherwise delegates to `new UrlMatchPattern(this.match).test(url)`.\n- `update(options: Omit<ProxyConfigOptions, 'bypass' | 'protocol'>): void` — updates `host`, `match`, `port`, `tunnel`, `authenticate`, `username`, `password`. Does **not** allow updating `bypass` or `protocol` (both omitted from the type, and untouched by the method body).\n- `updateProtocols(_protocols: string[]): never` — always throws `Error('updateProtocols is not supported in Insomnia')`. Comment: \"In Insomnia there is no whitelist while there is a blacklist.\"\n\n### `class ProxyConfigList<T extends ProxyConfig> extends PropertyList<T>`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — constructs the underlying `PropertyList` with `typeClass = ProxyConfig`.\n- `static isProxyConfigList(obj: any): boolean` — checks `obj._kind === 'ProxyConfigList'`.\n- `resolve(url?: Url): object | null` — returns `null` if `url` is falsy. Otherwise stringifies `url`, filters the list to configs whose `.test(urlStr)` is true, maps matches to `.toJSON()`, and returns the **first** match's JSON (or `null` if none match). Comment: \"It only returns the first one if multiple matches are found.\"\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, any>` — builds a plain object keyed by each config's `match` string, using `Object.create(null)` (no prototype). `excludeDisabled` skips `disabled` configs. `multiValue` collects same-`match` configs into an array; otherwise duplicate `match` values collapse to the last one.\n\n### Module-level function\n- `transformToSdkProxyOptions(protocol: string, httpProxy: string, httpsProxy: string, proxyEnabled: boolean, noProxy: string): ProxyConfigOptions` — computes `proxyHost` (the string actually parsed into `host`/`port`/etc. below) as `httpsProxy` or `httpProxy` based on whether `protocol === 'https:'`. But `enabledProxy` is computed separately, from `proxyEnabled && (httpsProxy || httpProxy || '').trim() !== ''` — **not** from `proxyHost` — so it's `true` whenever *either* proxy string is non-empty, regardless of which one the current `protocol` selected (see Gotchas). Splits `noProxy` on commas (trimmed) into the `bypass` list. Always returns `match: '<all_urls>'` (`UrlMatchPattern.MATCH_ALL_URLS`). If the proxy is enabled and a host string is present, parses it with the built-in `URL` class (prefixing `${protocol}//` if the string has no `://`), extracting `port` (only set if non-empty, then `Number.parseInt(..., 10)`), `protocol`, `host` (hostname), `username`, `password`; sets `authenticate = true` if either `username` or `password` came through; logs `` `Using proxy: ${sanitizedProxy}` `` via `getExistingConsole().warn`. Throws `Error('Failed to parse proxy (${protocol}//${proxyHost}): ${e.message}')` if URL parsing fails **and the proxy is enabled** (parsing is skipped entirely, no throw, when the proxy is disabled).\n\n## Script-facing surface\n- `pm.request.proxy` is a single `ProxyConfig` instance (per `request.ts`: `const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined;`), seeded from Insomnia's Settings-level proxy configuration via `transformToSdkProxyOptions` (called in `insomnia.ts`). It is `undefined` if no `proxy` option was supplied when constructing the request.\n- Scripts read/write `pm.request.proxy.host`, `.port`, `.username`, `.password`, `.authenticate`, `.tunnel`, `.bypass`, `.protocol` directly, call `.getProxyUrl()` to see the resolved proxy URL, `.test(url)` to check whether a given URL should go through this proxy, and `.update({...})` to change host/match/port/tunnel/authenticate/username/password.\n- `ProxyConfigList` and `ProxyConfig` are also exported from the `insomnia-collection` module (via `collection.ts`) so scripts can `require('insomnia-collection')` and construct their own lists directly, e.g.: `new ProxyConfigList(undefined, [{match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true}, ...])` (per the inline example comment in the source). There is no evidence in the SDK object graph that `pm.request` itself ever holds a `ProxyConfigList` — only a single `ProxyConfig`.\n\n## Gotchas / notable behavior\n- **`_index = 'key'` but `ProxyConfig` has no `key` property.** Base `PropertyList.one()`/`.indexOf()`/`.upsert()` look up items by `typeClass._index` (here `'key'`), but `ProxyConfig` instances never define a `key` field — so `ProxyConfigList.one('...')`/`.get('...')`/`.indexOf(...)`/`.upsert(...)` would compare against `undefined` for every item and effectively never index correctly by identity (though `.toObject()` and `.resolve()` work fine since they key/filter explicitly by `match` instead of relying on `_index`).\n- **`test(url)` returns `false` silently if `url` is omitted** — the source itself flags this as confusing (\"TODO: it is confusing in which case url arg is optional\").\n- **`bypass` matching is exact string equality**, not pattern-based — a URL must be an exact match to an entry in `bypass` to be excluded; nothing is normalized (no protocol/trailing-slash handling).\n- **`update()` cannot change `bypass` or `protocol`** — these are deliberately excluded from `ProxyConfigOptions` in the `update()` signature's `Omit<...>`.\n- **`updateProtocols()` always throws** — it exists on the class but is not a supported operation in Insomnia.\n- **`getProxyUrl()` omits the port segment entirely when `port === undefined`** (not just falsy — `port: 0` would still render `:0`).\n- **`transformToSdkProxyOptions` swallows parse errors when the proxy is disabled**: a malformed proxy host string does not throw as long as `proxyEnabled` is `false`; it only throws when the proxy would actually be used.\n- **`enabled`/`disabled` can disagree with the protocol-selected proxy string.** `enabledProxy` is derived from `httpsProxy || httpProxy` (https checked first, regardless of `protocol`), while the proxy actually parsed into `host`/`port`/etc. is `proxyHost` (`httpsProxy` only if `protocol === 'https:'`, else `httpProxy`). If `protocol` is `'http:'` with `httpProxy` empty but `httpsProxy` set (or vice versa), `enabledProxy` comes out `true` from the other, unused string, `proxy.disabled` is set to `false`, yet `proxyHost === ''` skips the parsing block entirely — so `pm.request.proxy` ends up `disabled: false` with an empty `host`.\n- **The class-level `static` fields shadow real instance field names** (`static host = ''`, `static match = ''`, etc., separate from `this.host`, `this.match` on instances) — marked `@ignore` and described only as \"hidden as they are not used while must be exposed\"; do not confuse `ProxyConfig.host` (static, always `''`) with `someProxyConfigInstance.host`.\n\n## Related\n- `properties.ts` — `Property` (base class for `ProxyConfig`) and `PropertyList` (base class for `ProxyConfigList`), including the shared `toObject()`/`_index` lookup semantics referenced above.\n- `urls.ts` — `UrlMatchPattern`/`UrlMatchPatternList` (used internally by `getProtocols()`/`test()`) and `Url` (the type accepted by `ProxyConfigList.resolve()`).\n- `console.ts` — `getExistingConsole()`, used by `transformToSdkProxyOptions` to log which proxy is being used.\n- `request.ts` — constructs `pm.request.proxy` from `ProxyConfigOptions` and serializes it back out when building the final request.\n- `insomnia.ts` — calls `transformToSdkProxyOptions` using Insomnia Settings (`proxyEnabled`, http/https proxy strings, `noProxy`) to seed the proxy config before a script runs.\n- `collection.ts` — re-exports `ProxyConfig`/`ProxyConfigList` for direct use via `require('insomnia-collection')`.\n",".claude/skills/fix-scripting-feature/references/objects/request-info.md":"# RequestInfo (`insomnia.info` / `pm.info`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request-info.ts`\n\n## Purpose\nModels `pm.info`: metadata about which script phase is executing and, for the collection runner,\nwhich iteration is in progress. Read-oriented — scripts typically branch on `insomnia.info.eventName`\nor log `insomnia.info.iteration`/`iterationCount`.\n\n## Public API\n\n### `type EventName = 'prerequest' | 'test'`\nThe name of the event that triggered the script: `'prerequest'` (before a request is sent) or\n`'test'` (during the testing/after-response phase).\n\n### `interface RequestInfoOption`\n```ts\ninterface RequestInfoOption {\n  eventName?: EventName;\n  iteration?: number;\n  iterationCount?: number;\n  requestName?: string;\n  requestId?: string;\n}\n```\nPlain-object shape used to construct a `RequestInfo` (and stored on `RequestContext.requestInfo`, see `interfaces.md`).\n\n### `class RequestInfo`\n```ts\nconstructor(options: RequestInfoOption)\n```\nPublic properties (plain, mutable, non-readonly):\n- `eventName: EventName` — defaults to `'prerequest'` if not provided (`options.eventName || 'prerequest'`).\n- `iteration: number` — defaults to `1` (`options.iteration || 1`).\n- `iterationCount: number` — defaults to `1` (`options.iterationCount || 1`).\n- `requestName: string` — defaults to `''`.\n- `requestId: string` — defaults to `''`.\n\nMethod:\n- `toObject = () => ({ eventName, iteration, iterationCount, requestName, requestId })` — plain serialization, same shape as `RequestInfoOption` with all fields always populated.\n\n## Script-facing surface\n- `insomnia.info.eventName` — `'prerequest'` or `'test'`, useful for scripts shared between pre-request and test tabs.\n- `insomnia.info.iteration` — current collection-runner iteration number (1-based).\n- `insomnia.info.iterationCount` — total number of iterations configured for the run.\n- `insomnia.info.requestName` — display name of the request being executed.\n- `insomnia.info.requestId` — the request's unique id.\n\n## Gotchas / notable behavior\n- All five properties are **plain public fields**, not readonly — a script can reassign\n  `insomnia.info.iteration = 99` and nothing in this class prevents it. In practice this has no\n  effect on the actual collection-runner state: `packages/insomnia/src/scripting/run-script.ts`'s\n  merge logic (building the returned `RequestContext`) does not read `requestInfo` back out of the\n  mutated `InsomniaObject` at all, so any script-side mutation of `insomnia.info` is silently\n  discarded after the script finishes.\n- The `||` fallback pattern means an explicit `0` for `iteration`/`iterationCount` is treated the\n  same as \"not provided\" and coerced to `1`. Notably, `initInsomniaObject` (in `insomnia.ts`)\n  builds the options with `iterationCount: rawObj.requestInfo.iterationCount || 0` — but since `0`\n  is falsy, the `RequestInfo` constructor's own `|| 1` fallback still turns that back into `1`, so\n  `iterationCount` effectively can never legitimately be `0` through this path.\n- A `// TODO: update follows when post-request script and iterationData are introduced` comment on\n  the call site in `insomnia.ts` suggests the `eventName`/iteration wiring for after-response\n  scripts may still be incomplete.\n\n## Related\n- `insomnia.ts` — `InsomniaObject.info: RequestInfo`; `initInsomniaObject` builds it from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- `interfaces.ts` — `RequestInfoOption` is embedded as `RequestContext.requestInfo`.\n- `packages/insomnia/src/scripting/run-script.ts` — supplies the initial `RequestInfoOption` per script phase (pre-request vs. test) and — notably — does not persist script-side mutations back into `RequestContext`.\n- `__tests__/request-info.test.ts` — covers default values and `toObject()` shape for both single-request and collection-runner (`iteration`/`iterationCount`) cases.\n",".claude/skills/fix-scripting-feature/references/objects/request.md":"# Request, RequestBody\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request.ts`\n\n## Purpose\n\nModels the outgoing HTTP request as seen/mutated by pre-request and test scripts: `pm.request` / `insomnia.request`. Also holds the merge/serialization logic that converts between this script-facing `Request` shape and Insomnia's own `insomnia-data` request model (`mergeRequests`, `mergeRequestBody`, `mergeSettings`, `mergeClientCertificates`), and payload-size calculation used by `Request.size()` / `Response.size()`.\n\n## Public API\n\n### `FormParam` (extends `Property`)\n- `constructor(options: { key: string; value: string; type?: string; disabled?: boolean })`\n- `key: string`, `value: string`, `type?: string`\n- `static _postman_propertyAllowsMultipleValues()` — **always throws** `Error('unsupported')`.\n- `static _postman_propertyIndexKey()` — **always throws** `Error('unsupported')`.\n- `toJSON()` — `{ key, value, type, disabled }`.\n- `toString()` — `\"key=value\"` with both parts `encodeURIComponent`-escaped.\n- `valueOf()` — returns `value`.\n\n### `RequestBodyOptions` (interface)\n`{ mode: RequestBodyMode; file?: string; formdata?: {key,value,type?,disabled?}[]; graphql?: {query,operationName,variables,disabled?}; raw?: string; urlencoded?: {key,value?,type?,disabled?,multiline?,fileName?}[]; options?: object }`\n`RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'`.\n\n### `RequestBody` (extends `PropertyBase`)\n- `constructor(opts: RequestBodyOptions)`\n- `mode: RequestBodyMode`, `file?: string`, `formdata?: PropertyList<FormParam>`, `graphql?: {query,operationName,variables}`, `raw?: string`, `urlencoded?: PropertyList<QueryParam>`, `options?: object`\n- `isEmpty(): boolean` — switches on `mode` and checks whether the matching field is `null`; **throws** `Error` if `mode` isn't one of the five known modes.\n- `toString()` — renders body as a string per `mode` (`formdata`/`urlencoded` join their `PropertyList` entries with `&`, `graphql` is `JSON.stringify`-ed); wraps any internal error as `Error(\"toString: ...\")`; returns `''` if `mode` is `undefined`.\n- `update(opts: RequestBodyOptions)` — re-derives and replaces all fields (same logic as constructor).\n\n### `RequestOptions` (interface)\n`{ url: string | Url; method?: string; header?: HeaderDefinition[] | object; body?: RequestBodyOptions; auth?: AuthOptions; proxy?: ProxyConfigOptions; certificate?: CertificateOptions; pathParameters?: RequestPathParameter[]; name?: string }`\n\n### `RequestSize` (interface)\n`{ body: number; header: number; total: number; source: string }`\n\n### `Request` (extends `Property`)\n- `constructor(options: RequestOptions)` — sets `_kind = 'Request'`; `method` defaults to `'GET'` if omitted; `header` accepts either an array of `HeaderDefinition` or a plain `{key: value}` object (converted to `Header`s); `auth` defaults to `{ type: 'noauth' }`.\n- Properties: `name: string`, `url: Url`, `method: string`, `headers: HeaderList<Header>`, `body?: RequestBody`, `auth: RequestAuth`, `proxy?: ProxyConfig`, `certificate?: Certificate`, `pathParameters: RequestPathParameter[]`.\n- `static isRequest(obj: object): boolean` — checks `_kind === 'Request'`.\n- `addHeader(header: Header | object)` — accepts a `Header` instance or `{key, value}`; **throws** `Error` otherwise.\n- `addQueryParams(params: QueryParam[] | string)` — delegates to `this.url.addQueryParams`.\n- `authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList<Variable>)` — delegates to `this.auth.use(...)`.\n- `clone(): Request` — `new Request({ ...this.toJSON() })` (JSON round-trip clone, not a deep object clone).\n- `forEachHeader(callback: (header: Header, context?: object) => void)` — delegates to `this.headers.each`.\n- `getHeaders(options?: { ignoreCase, enabled, multiValue, sanitizeKeys }): Record<string, string[] | string>` — merges headers with the same key into an array; `ignoreCase` lowercases keys before merging; `enabled` filters out headers where `disabled` is truthy; `sanitizeKeys` drops headers with a falsy `value`.\n- `removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean })` — rebuilds `this.headers` as a new `HeaderList` excluding matches; **throws** if `toRemove` isn't `string | Header`.\n- `removeQueryParams(params: string | string[] | QueryParam[])` — delegates to `this.url.removeQueryParams`.\n- `size(): RequestSize` — `calculatePayloadSize(this.body?.toString() ?? '', this.headers)`.\n- `toJSON()` — plain object snapshot (`url` as string, `header` array, `body`, `auth`, `proxy`, `certificate`).\n- `update(options: RequestOptions)` — re-derives and replaces every field.\n- `upsertHeader(header: HeaderDefinition)` — removes any existing header with the same `key` (case-sensitive), then appends a new `Header`.\n\n### Module-level functions\n- `mergeSettings(originalSettings: Settings, updatedReq: Request): Settings` — if `updatedReq.proxy` is enabled (not disabled and has a non-empty proxy URL), overrides both `httpProxy` and `httpsProxy` in the returned `Settings` with the same proxy URL; otherwise returns `originalSettings` unchanged.\n- `mergeClientCertificates(originalClientCertificates: ClientCertificate[], updatedReq: Request): ClientCertificate[]` — maps the script's single `updatedReq.certificate` onto Insomnia's certificate list (which supports multiple). Returns originals unchanged if no certificate was set (or it's empty). **Throws** `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if neither a valid `pfx` nor a valid `cert`+`key` pair is present in the certificate.\n- `toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions` — converts Insomnia's native request body (`text` / `fileName` / `params`) into a `RequestBodyOptions` (`raw` / `file` / `urlencoded`); `formdata` and `graphql` modes are not produced here.\n- `mergeRequestBody(updatedReqBody: RequestBody | undefined, originalReqBody: InsomniaRequestBody): InsomniaRequestBody` — infers `mimeType` from `updatedReqBody.mode` (falls back to `originalReqBody.mimeType` if set); **throws** on an unknown `mode`; wraps any other failure as `Error(\"failed to update body: ...\")`.\n- `mergeRequests(originalReq: InsomniaRequest, updatedReq: Request): InsomniaRequest` — builds the outgoing Insomnia request from the script's mutated `Request`: `url` via `toStringWithoutQuery()`, `parameters` from `url.query`, `headers` from `headers`, `authentication` via `fromPreRequestAuth`, `pathParameters` copied as-is, and **hardcodes `preRequestScript: ''`** (i.e., the merged request never carries a pre-request script forward).\n- `calculatePayloadSize(body: string, headers: HeaderList<Header>): RequestSize` — body size via `new Blob([body]).size`; `source` is always `'COMPUTED'`.\n- `calculateHeadersSize(headers: HeaderList<Header>): number` — `Blob` size of all headers joined as `\"key: value\\n\"` lines (via each header's `toString()`, which is `Header`'s inherited/overridden behavior).\n\n## Script-facing surface\n\n`insomnia.request` / `pm.request` is a live `Request` instance (constructed in `insomnia.ts#initInsomniaObject`). Common script usage:\n- `pm.request.url`, `pm.request.method`, `pm.request.headers`, `pm.request.body`\n- `pm.request.headers.add(...)`, `pm.request.addHeader({key, value})`, `pm.request.upsertHeader({key, value})`, `pm.request.removeHeader('X-Foo')`\n- `pm.request.url.addQueryParams(...)` / `pm.request.addQueryParams(...)`, `pm.request.removeQueryParams(...)`\n- `pm.request.auth`, `pm.request.authorizeUsing('basic', ...)`\n- `pm.request.size()` — payload size info\n- Any mutation a pre-request script makes to `pm.request` is read back and merged into the real outgoing request via `mergeRequests` / `mergeRequestBody` / `mergeSettings` / `mergeClientCertificates` after the script finishes.\n\n## Gotchas / notable behavior\n\n- `FormParam._postman_propertyAllowsMultipleValues()` and `_postman_propertyIndexKey()` are stubs that **always throw** — calling them from a script (unlikely, but they exist statically) will always fail. A commented-out `static parse` is also noted as \"not supported yet in existing scripts\".\n- `RequestBody.isEmpty()` / `toString()` both `throw` if `mode` doesn't match one of the five known literals — an unexpected/typo'd `mode` string surfaces as a runtime error, not silently.\n- `Request.getHeaders()`'s `multiValue` option is part of the signature but **is never referenced in the function body** — the implementation always accumulates same-key headers into an array regardless of what `multiValue` is set to.\n- `Request.removeHeader` and `upsertHeader` rebuild `this.headers` as an entirely new `HeaderList` rather than mutating in place.\n- `Request.clone()` is a JSON round-trip (`toJSON()` → `new Request(...)`), not a structural/deep clone — anything not captured by `toJSON()` is lost on clone.\n- `mergeRequests` **always sets `preRequestScript: ''`** on the merged request — a deliberate design choice worth knowing if debugging \"script disappeared\" reports downstream.\n- `mergeClientCertificates` throws if a script sets both `cert`+`key` and `pfx` on `pm.request.certificate` at the same time — Insomnia only supports one or the other.\n- `toScriptRequestBody` only produces `raw` / `file` / `urlencoded` modes; there's no path from Insomnia's native body model to script `formdata`/`graphql` modes in this function.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` used for `Request.headers`.\n- `packages/insomnia-scripting-environment/src/objects/urls.ts` — `Url`, `QueryParam`, `toUrlObject` used for `Request.url` and `RequestBody.urlencoded`.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used for `Request.auth`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate` used for `Request.certificate`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig` used for `Request.proxy`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes.\n- `packages/insomnia-scripting-environment/src/objects/variables.ts` — `Variable`, `VariableList` used by `authorizeUsing`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — imports `calculateHeadersSize`; `Response.originalRequest` is a `Request`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.request` in `initInsomniaObject`.\n",".claude/skills/fix-scripting-feature/references/objects/response.md":"# Response\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/response.ts`\n\n## Purpose\n\nModels the HTTP response as seen by after-response and test scripts: `pm.response` / `insomnia.response`. It wraps the raw body/headers/cookies/timing data returned by the network layer, provides parsing/introspection helpers (`json()`, `contentInfo()`, `size()`), and extends `chai`'s assertion API with Postman-style response assertions (`pm.response.to.have.status(200)`, etc.). `pm.response` is only populated for after-response/test scripts — it's `undefined` during pre-request scripts.\n\n## Public API\n\n### `ResponseOptions` (interface)\n`{ code: number; reason?: string; header?: HeaderDefinition[]; cookie?: CookieOptions[]; body?: string; stream?: Buffer | ArrayBuffer; responseTime: number; originalRequest: Request; bytesRead?: number }`\n\n### `ResponseContentInfo` (interface)\n`{ mimeType: string; mimeFormat: string; charset: string; fileExtension: string; fileName: string; contentType: string }`\n\n### `Response` (extends `Property`)\n- `constructor(options: ResponseOptions)` — sets `_kind = 'Response'`; `body` defaults to `''`; `status` defaults to `options.reason`, falling back to `RESPONSE_CODE_REASONS[options.code]`, falling back to `''`; `bytesRead` (private) defaults to `0`.\n- Properties: `body: string`, `code: number`, `cookies: CookieList`, `headers: HeaderList<Header>`, `originalRequest: Request`, `responseTime: number`, `status: string`, `stream?: Buffer | ArrayBuffer`; `bytesRead` is **private**.\n- `static createFromNode(response: {...}, cookies: CookieOptions[]): Response` — builds a `Response` from a raw Node-style response object (`body`, `headers`, `statusCode`, `statusMessage`, `elapsedTime`, `originalRequest`, `stream`).\n- `static isResponse(obj: object): boolean` — checks `_kind === 'Response'`.\n- `contentInfo(): ResponseContentInfo` — parses the `Content-Type` header for `mimeType`/`charset` (defaults: `application/octet-stream` / `utf8`) and the `Content-Disposition` header for `fileName`/`fileExtension`. **Throws** if a `Content-Type` header exists but its value is blank, or its mime-type segment is empty.\n- `dataURI(): string` — builds a `data:` URI string from `contentInfo().contentType` and `this.stream || this.body`. **Throws** if neither `stream` nor `body` is set.\n- `json(reviver?: (key, value) => any, _strict?: boolean): any` — `JSON.parse(this.body.toString(), reviver)`; wraps parse failures as `Error(\"json: failed to parse: ...\")`. `_strict` is accepted but unused (\"TODO: enable strict after common module is introduced\").\n- `jsonp(_reviver?, _strict?)` — **always throws** `unsupportedError('jsonp()')`.\n- `reason(): string` — returns `this.status`.\n- `size(): { body: number; header: number; total: number; source: 'COMPUTED' }` — `body` is the private `bytesRead` value (not derived from `this.body`'s actual length); `header` via `calculateHeadersSize(this.headers)`.\n- `text(): string` — `this.body.toString()`.\n- `get to()` — returns a `chai.Assertion` wrapping `this`, with Postman-style properties/methods registered onto `chai.Assertion.prototype` on every access: properties `withBody`, `error` (true if `code` is within 400–500 inclusive), `ok` (true if `code === 200`), `json` (true if the body parses to an object); methods `status(val)`, `header(headerName)`, `body(bodyContent)`, `jsonBody(propName)`, `jsonSchema(schema, options?)` (uses `ajv` to validate `this.json()` against `schema`).\n\n### Module-level functions\n- `toScriptResponse(originalRequest: Request, partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, responseBody: string): Response | undefined` — returns `undefined` if `partialInsoResponse` is an error result (network/curl failure); otherwise builds a `Response`, extracting `Set-Cookie` headers into `Cookie.parse(...)` entries for `cookie`.\n- `readBodyFromPath(response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined): Promise<string>` — returns `''` if `response` is missing, an error, or has no `bodyPath`; otherwise reads and decompresses the body from disk via `services.helpers.readCurlResponse`. **Throws** if that read reports an error.\n\n## Script-facing surface\n\n`insomnia.response` / `pm.response` is a `Response` instance, present only in after-response/test scripts (constructed in `insomnia.ts#initInsomniaObject` via `toScriptResponse`; `undefined` if the request errored or during pre-request scripts). Common script usage:\n- `pm.response.code`, `pm.response.status`, `pm.response.headers`, `pm.response.body`\n- `pm.response.json()`, `pm.response.text()`, `pm.response.contentInfo()`\n- `pm.response.to.have.status(200)`, `pm.response.to.have.header('Content-Type')`, `pm.response.to.have.jsonBody('key')`, `pm.response.to.not.have.status(404)`, `pm.response.to.have.jsonSchema({...})`\n\n## Gotchas / notable behavior\n\n- `dataURI()` has a typo in the returned string: it emits `` `data:${contentType};baseg4, ${bodyInBase64}` `` — **`baseg4` instead of `base64`**. Any consumer relying on `dataURI()` producing a spec-compliant data URI will get a malformed one.\n- `jsonp()` is a stub that always throws `unsupportedError('jsonp()')` — it exists on the type but is never functional.\n- `size().body` comes from the **private `bytesRead`** field (set once at construction from `options.bytesRead`, defaulting to `0`), not from measuring `this.body`. If `bytesRead` wasn't passed in, `size()` reports `0` regardless of actual body length. (The test file has a commented-out assertion noting `resp.size()` doesn't fully work yet: `\"this will work after PropertyList.one is improved\"`.)\n- The `error` assertion (`pm.response.to.have.error` / `.to.be.error`) only covers status codes **400–500 inclusive** — not the broader 4xx/5xx range one might expect.\n- **`get to()`'s re-registration is a real correctness risk, not just wasted work.** Every `.to` access calls `chai.use((_chai, utils) => {...})` with a freshly-created arrow function; chai dedupes plugins by callback identity (`used.indexOf(fn)`), so a new closure is never recognized as already-registered and the plugin body reruns every time. Worse, every `withBody`/`error`/`ok`/`json`/`status`/`header`/`body`/`jsonBody`/`jsonSchema` callback is itself an arrow function that reads `utils.flag(respAssertion, 'object')`, where `respAssertion` is closed over from whichever `.to` access most recently ran — not derived from `this`/the assertion instance actually being evaluated (arrow functions ignore the `this` chai binds via `getter.call(this)`/`method.apply(this, ...)`). So if a script holds a `.to`-derived assertion chain and triggers another `.to` access (same or different response) before invoking a method/property on that held chain, the held chain silently asserts against the *other* response instead. Single-statement chains like `pm.response.to.have.status(200)` are unaffected since no `.to` access can intervene mid-expression.\n- `contentInfo()` throws if a `Content-Type` header is present but empty/malformed; scripts that blindly call `pm.response.contentInfo()` on unusual responses can throw.\n- `toScriptResponse` returns `undefined` on network error — scripts must handle `pm.response` potentially being `undefined` (though in practice after-response/test scripts only run when a response exists).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Response.originalRequest: Request`; imports `calculateHeadersSize` from here.\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` for `Response.headers`.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `Cookie`, `CookieList` for `Response.cookies`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `unsupportedError`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.response` in `initInsomniaObject` via `toScriptResponse`/`readBodyFromPath`.\n",".claude/skills/fix-scripting-feature/references/objects/send-request.md":"# sendRequest (pm.sendRequest)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/send-request.ts`\n\n## Purpose\nImplements the standalone `sendRequest()` function that backs `pm.sendRequest()` / `insomnia.sendRequest()` — letting a pre-request/after-response script fire an ad-hoc HTTP request (via libcurl) and get a `Response` back, either through a Node-style callback or a returned promise. It also contains the translation layer from the SDK's `Request`/string/`RequestOptions` shapes into the curl request options consumed by Insomnia's network layer, and from the raw curl output back into a `Response` object.\n\n## Public API\n\n### `sendRequest(request, cb, settings)`\n```ts\nexport async function sendRequest(\n  request: string | Request | RequestOptions,\n  cb: (error?: string, response?: Response) => void,\n  settings: Settings,\n): Promise<Response | void>\n```\n- Builds curl options from `request` via `requestToCurlOptions(request, settings)`.\n- Picks the curl execution path based on environment: in the renderer (`__IS_RENDERER__` true) it uses `window.bridge.curlRequest`; otherwise it dynamically imports `curlRequest` from `insomnia/src/main/network/libcurl-promise`.\n- Awaits the curl call, converts the raw `CurlRequestOutput` to a `Response` via `curlOutputToResponse(output, request)`.\n- **Success:** if `cb` is provided, calls `cb(undefined, transformedOutput)`; either way resolves the returned promise with `transformedOutput`.\n- **Failure:** if `cb` is provided, calls `cb(e)` and resolves the promise with `undefined` (does **not** reject); if no `cb` is provided, rejects the promise with `e`.\n\n### `requestToCurlOptions(req, settings)` (not exported)\n```ts\nfunction requestToCurlOptions(req: string | Request | RequestOptions, settings: Settings)\n```\n- If `req` is a `string`: treats it as a bare URL, builds a minimal GET request object (`no body`, `noauth`, empty headers/cookies, `settingRebuildPath: true`, `settingSendCookies: true`, `settingFollowRedirects` derived from `settings.followRedirects ? 'on' : 'off'`), with `requestId` prefixed `pre-request-script-adhoc-req-simple:<uuid>`.\n- If `req` is a `Request` instance or a plain object (`RequestOptions`): coerces to a `Request` (`new Request(req)` if not already one), derives `mimeType` from `finalReq.body.mode` (`raw`→`text/plain`, `file`→`application/octet-stream`, `formdata`→`multipart/form-data`, `urlencoded`→`application/x-www-form-urlencoded`, `graphql`→`application/json`, otherwise `text/plain`), maps headers/body/certificate/auth into the curl request shape, with `requestId` = `finalReq.id` or a generated `pre-request-script-adhoc-req-custom:<uuid>`.\n- Auth mapping beyond `noauth` is largely commented out / marked `TODO` (see Gotchas).\n- Throws `Error('the request type must be: string | Request | RequestOptions.')` for any other input shape.\n\n### `curlOutputToResponse(result, request)` (not exported)\n```ts\nasync function curlOutputToResponse(\n  result: CurlRequestOutput,\n  request: string | Request | RequestOptions,\n): Promise<Response>\n```\n- Throws if `result.headerResults` is empty, or if `result.patch.error` is set (re-throws that error), or if there's no last redirect entry.\n- Normalizes `request` into a `Request` instance for `originalRequest`.\n- Extracts headers from the last redirect's `headerResults` entry; parses any `Set-Cookie` headers via `tough-cookie`'s `Cookie.parse(..., { loose: true })` into cookie option objects (filtering out unparsable ones).\n- If `result.responseBodyPath` is absent, returns a `Response` with `body: ''`.\n- Otherwise reads the body via `services.helpers.readCurlResponse({ bodyPath, bodyCompression })`; throws if that read reports an error; otherwise returns a fully populated `Response` (`code`, `reason`, `header`, `cookie`, `body`, `responseTime: result.patch.elapsedTime`, `originalRequest`). `stream` is always left `undefined` (\"because it is inaccurate to differentiate if body is binary\").\n\n## Script-facing surface\n- `pm.sendRequest(url, (err, response) => { ... })` — string URL, GET request, callback style.\n- `pm.sendRequest({ url, method, header, body, auth, ... }, (err, response) => { ... })` — full `RequestOptions`-shaped object.\n- `pm.sendRequest(requestInstance, (err, response) => { ... })` — passing an existing `Request` instance (e.g. built via `new Request(...)` or `pm.request`).\n- Can also be used promise-style without a callback: `const response = await pm.sendRequest(url)` (see Gotchas for the resulting error-handling difference).\n- Exposed on the object model as `insomnia.sendRequest(request, cb)`, which is a plain instance method (not proxy-trapped) that forwards to this file's `sendRequest(request, cb, this._settings)`.\n\n## Gotchas / notable behavior\n- **Callback vs. promise error handling differ:** if a `cb` is supplied, network/parsing errors are *swallowed* into the callback (`cb(e)`) and the returned promise still resolves (with `undefined`) — it will not reject. If no `cb` is supplied, the same error instead rejects the returned promise. A script mixing `await pm.sendRequest(url, cb)` with `try/catch` will not catch curl errors, because the promise resolves successfully even on failure when a callback is present.\n- **Environment branching:** `__IS_RENDERER__ ? window.bridge.curlRequest : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest` — the actual network call goes through completely different code paths depending on whether the script executes in the renderer (Electron IPC bridge) or Node/main context. Bugs that only reproduce in one context (e.g. inso CLI vs. the desktop app) may live in this branch.\n- **Auth beyond `noauth` is not wired up** for the custom-`Request` branch: there's a large commented-out block (`// const authHeaders = ...`) showing API-key/bearer header injection was planned but not implemented; `fromPreRequestAuth(finalReq.auth)` is still called and passed through as `authentication`, but the `authHeader` field of the curl options is always `undefined` with a `// TODO: add this for bearer and other auth methods` comment.\n- **Certificates:** only a single client certificate is forwarded (`finalReq.certificate`), built directly into the curl options' `certificates` array; several fields (`disabled`, `isPrivate`, `_id`, `type`, `parentId`, `modified`, `created`, `name`) are hardcoded to empty/`false`/`0` since they're \"unused fields because they are not persisted\".\n- **Cookies are not populated from a jar** — `cookieJar: { cookies: [] }` and `cookies: []` are always empty in the outgoing request; the comment notes \"currently cookies should be handled by user in headers\". Response `Set-Cookie` headers are still parsed back out via `tough-cookie`, though.\n- **`suppressUserAgent` detection** only checks for a *disabled* `User-Agent` header (`h.key.toLowerCase() === 'user-agent' && h.disabled === true`) — an enabled custom `User-Agent` header does not suppress the default one; it presumably just overrides via normal header precedence.\n- **Response body/stream:** `stream` is always `undefined` in the returned `Response`, even though `CurlRequestOutput`/`ResponseOptions` support it, specifically to avoid ambiguity between binary and text bodies — callers should rely on `body` (a string) only.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request`, `RequestOptions`, `RequestBody`/`RequestBodyOptions` types this function consumes as input.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response` class this function constructs as output.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used to translate the SDK's auth model into the curl request's `authentication` field.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `CookieOptions` type used for parsed `Set-Cookie` results.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — exposes this function as `insomnia.sendRequest(request, cb)`, supplying `this._settings` as the `settings` argument.\n- `insomnia/src/main/network/libcurl-promise` (`curlRequest`, `CurlRequestOutput`) — the main/Node-side curl execution path, dynamically imported when not running in the renderer.\n- `window.bridge.curlRequest` — the renderer-side IPC bridge equivalent used when `__IS_RENDERER__` is true.\n- `insomnia-data` (`services.helpers.readCurlResponse`, `Settings` type) — used to read the response body from disk (`responseBodyPath`) and for the `Settings` (e.g. `followRedirects`) passed in.\n",".claude/skills/fix-scripting-feature/references/objects/test.md":"# Test handler (pm.test / test())\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/test.ts`\n\n## Purpose\nImplements the `test()`/`skip()` handler that backs `pm.test(...)` (and `insomnia.test(...)`), plus the bookkeeping used by the script-execution harness to know when all in-flight test callbacks have settled before the script finishes. This is the only file responsible for turning a user's test callback into a `RequestTestResult` record.\n\n## Public API\n\n### `test(msg, fn, log)`\n```ts\nexport async function test(\n  msg: string,\n  fn: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Wraps `fn` in an async closure (`wrapFn`), times its execution with `performance.now()`, awaits it, and calls `log(...)` with a `RequestTestResult`:\n  - On success: `{ testCase: msg, status: 'passed', executionTime, category: 'unknown' }`.\n  - On thrown error `e`: `{ testCase: msg, status: 'failed', executionTime, errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, category: 'unknown' }`.\n- Immediately invokes `wrapFn()`, registers the resulting promise with `startTestObserver`, and returns that same promise (`testPromise`) — i.e. `test()` itself resolves once the wrapped test body (pass or fail) has finished running.\n- `category` is always hardcoded to `'unknown'` here even though `RequestTestResult.category` (in `insomnia-data`) also allows `'pre-request'` and `'after-response'`.\n\n### `skip(msg, _, log)`\n```ts\nexport async function skip(\n  msg: string,\n  _: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Does **not** call `fn` (the second argument is ignored). Immediately calls `log({ testCase: msg, status: 'skipped', executionTime: 0, category: 'unknown' })`.\n\n### `resetTestPromises()`\n```ts\nexport function resetTestPromises(): void\n```\n- Clears the module-level `testPromises` array (`testPromises = []`). Used to reset state between script executions so promises from a previous run aren't awaited again.\n\n### `waitForAllTestsDone()`\n```ts\nexport async function waitForAllTestsDone(): Promise<void>\n```\n- Awaits `Promise.allSettled(testPromises)` (captured as `NativePromise` at module load, so it isn't affected by any sandbox-level `Promise` patching), then resets `testPromises` back to `[]`.\n- This is the drain point the execution harness calls at the end of a script run to make sure every `pm.test(...)` callback (including ones the script itself didn't `await`) has finished before results are collected.\n\n### `startTestObserver(promise)` (not exported)\n```ts\nfunction startTestObserver(promise: Promise<void>): void\n```\n- Pushes `promise` onto the module-level `testPromises` array. Called once per `test()` invocation.\n\n### `TestHandler` interface\n```ts\nexport interface TestHandler {\n  (msg: string, fn: () => Promise<void>): Promise<void>;\n  skip?: (msg: string, fn: () => Promise<void>) => void;\n}\n```\n- Callable interface shape used by the object model to type the value returned for `pm.test` — a function that also carries an optional `.skip` method.\n\n## Script-facing surface\n- `pm.test('name', async () => { ... })` / `insomnia.test('name', fn)` — registers and immediately runs a test, recording a pass/fail result.\n- `pm.test.skip('name', fn)` — records a `skipped` result without running `fn`.\n- There is no bare global `test`/`skip` exposed directly by this file; script-facing exposure happens through the `insomnia`/`pm` object (see Related).\n\n## Gotchas / notable behavior\n- **Fire-and-forget by default:** `test()` starts executing the test body synchronously (well, as soon as the async function runs) and returns a promise, but scripts are not required to `await pm.test(...)`. Because every test promise is also pushed into the shared `testPromises` array via `startTestObserver`, un-awaited tests are still tracked and will be waited on by `waitForAllTestsDone()` before the script finishes — this is what prevents test results from being lost when a script fires multiple `pm.test()` calls without awaiting them.\n- **Module-level mutable state:** `testPromises` is a module-level array, not per-execution-context state. `resetTestPromises()` must be called before a script runs and `waitForAllTestsDone()` after, or results/timing from different executions could leak into each other (relevant if concurrent script executions ever share this module instance).\n- **Error message shape assumes chai-style errors:** the failure branch does `` `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}` ``, which assumes `e` has `.actual`/`.expected` (true for chai `AssertionError`s from `insomnia.expect(...)`). For a plain thrown `Error` or non-chai exception, `ACTUAL`/`EXPECTED` will stringify as `undefined`.\n- **`category` is never anything but `'unknown'`** from this module — any pre-request vs. after-response distinction in `RequestTestResult.category` must be set elsewhere (or is currently unused/always `'unknown'` for script-originated tests).\n- **`NativePromise` capture:** `const NativePromise = Promise;` is captured at module import time specifically so `waitForAllTestsDone`'s `allSettled` call uses the real, un-sandboxed `Promise`, not a possibly-patched one from the script sandbox.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — wires `test`/`skip` into the `InsomniaObject`. The constructor returns a `Proxy` whose `get` trap intercepts `prop === 'test'` and returns a `TestHandler` closure calling `this._test(msg, fn, this.pushRequestTestResult)` (and `.skip` calling `this._skip(...)`); `pushRequestTestResult` appends to `this.requestTestResults: RequestTestResult[]`, later returned from `toObject()`.\n- `packages/insomnia-scripting-environment/src/objects/async-objects.ts` — calls `resetTestPromises()` once at module load alongside its own promise-tracking logic for `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) and `packages/insomnia/src/scripting/run-script.ts` — the harness that injects `__waitForAllTestsDone__` into the generated script function and awaits it (`await __waitForAllTestsDone__();`) before returning the mutated `insomnia` object; `run-script.ts` then reads `mutatedInsomniaObject.toObject().requestTestResults` and includes it in the returned `RequestContext`.\n- `packages/insomnia-data` (`RequestTestResult`, `TestStatus`, `TestCategory` in `src/models/runner-test-result.ts`) — the result shape this file produces.\n- UI consumers of the resulting test results: `packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx` and `request-result-card.tsx`.\n",".claude/skills/fix-scripting-feature/references/objects/urls.md":"# Url, QueryParam, UrlMatchPattern\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/urls.ts`\n\n## Purpose\n\nModels a request URL (`Url`), its individual query-string parameters (`QueryParam`), and Chrome-extension-style match patterns (`UrlMatchPattern` / `UrlMatchPatternList`) used to test whether a URL matches a proxy bypass rule or a client-certificate host rule. `pm.request.url` is a `Url`; `UrlMatchPattern` backs `ProxyConfig.match` and `Certificate.matches`.\n\n## Public API\n\n### `setUrlSearchParams(provider: any)`\nModule-level override hook: replaces the `URLSearchParams` implementation used internally by `QueryParam` (defaults to the global `URLSearchParams`). Test-only utility, not script-facing.\n\n### `QueryParamOptions` (interface)\n`{ key: string; value?: string; type?: string; multiline?: string | boolean; disabled?: boolean; fileName?: string }`\n\n### `QueryParam` (extends `Property`)\n- `override _kind = 'QueryParam'`\n- `key: string`, `value?: string`, `type?: string`, `multiline?: string | boolean`, `fileName?: string` (the latter two are noted in a comment as \"properties from Insomnia... added here to avoid being dropped\").\n- `constructor(options: QueryParamOptions | string)` — if `options` is a string, it's `JSON.parse`d (wraps parse errors as `Error('invalid QueryParam options ...')`); if it's an object with `key`+`value`, fields are copied directly; otherwise **throws** `Error('unknown options for new QueryParam')`.\n- `static override _index = 'key'`\n- `static parse(queryStr: string): {key, value}[]` — parses a query string via `URLSearchParams`.\n- `static parseSingle(paramStr: string, _idx?, _all?): {key, value}` — parses a single `key=value` pair; **throws** if `QueryParam.parse` yields nothing.\n- `static unparse(params: QueryParamOptions[] | Record<string, string>): string` — builds a query string via `URLSearchParams`, URL-encoding as it goes.\n- `static unparseSingle(obj: {key, value}): string | {}` — returns `''`-joined encoded `\"key=value\"` if the input has both `key` and `value`, otherwise returns `{}` (an empty object, not a string — inconsistent return type).\n- `toString(): string` — URL-encodes via `URLSearchParams` (e.g. spaces become `+`).\n- `toRawString(): string` — `\"key=value\"` with **no encoding**.\n- `update(param: string | {key, value, type?})` — parses a string form via `parseSingle` (only sets `key`/`value`, coercing non-string results to `''`) or copies `key`/`value`/`type` from an object; **throws** on any other input shape.\n\n### `UrlOptions` (interface)\n`{ id?: string; auth?: {username, password}; hash?: string; host: string[]; path?: string[]; port?: string; protocol: string; query: {key, value}[]; variables: {key, value}[] }`\n\n### `Url` (extends `PropertyBase`)\n- `override _kind = 'Url'`\n- `id?: string`\n- Getters (all derived from an internal `URL` object, `this.urlObject`, which is `undefined` if the input couldn't be parsed as a URL — e.g. contains unrendered `{{ }}`/`{% %}` template tags): `auth` (`{username, password}` or `undefined`), `hash` (without leading `#`), `host` (hostname split on `.`), `path` (pathname segments, empty ones filtered out), `port`, `protocol`, `query: PropertyList<QueryParam>` (a **new** `PropertyList` wrapping the current query params on every access), `variables: string[]` (**always returns `[]`** — \"TODO: it's usage is unknown\").\n- `constructor(def: UrlOptions | string)`\n- `private initFields(urlOptions)` — parses `def`; if it's a string containing a template tag (`checkIfUrlIncludesTag`), it's kept as an opaque `origin` string and `urlObject` stays `undefined` (to avoid mangling `{% uuid 'v4' %}`-style tags); if it's an object, a URL string is assembled from `protocol`/`auth`/`host`/`port`/`path`/`query`/`hash` (protocol defaults to `'https://'` if blank). Query params are always parsed out into `this.queryParams` (a private array) separately from the `URL` object (whose own `search` is cleared) — \"query params are handled separately as URL object encodes content\".\n- `static _index = 'id'`\n- `static isUrl(obj: object): boolean`\n- `static parse(urlStr: string): UrlOptions | undefined` — returns `undefined` if `URL.canParse` fails; `variables` is always `[]` in the result.\n- `addQueryParams(params: QueryParamOptions[] | string)` — string form splits on `&` then `=` (not URL-decoded); array form copies each entry into a new `QueryParam`. **Throws** `TypeError` on other input.\n- `getHost(): string` — `''` if the URL didn't parse.\n- `getPath(_unresolved?): string` — `_unresolved` param is accepted but unused; `''` if unparsed.\n- `getPathWithQuery(): string` — path + `?` + query string (or just the query string if path is blank).\n- `getQueryString(): string` — joins **enabled** (`!disabled`) query params via `toRawString()` (i.e. **unencoded**, unlike `QueryParam.toString()`).\n- `getRemote(_forcePort?): string` — `_forcePort` is accepted but unused; returns `urlObject.host` (which already includes the port when non-default) or `''`.\n- `removeQueryParams(params: QueryParam[] | string[] | string)` — filters `this.queryParams` by key; **throws** `TypeError` on unrecognized input shape.\n- `override toString(_forceProtocol?): string` — `_forceProtocol` accepted but unused; rebuilds the URL string with the current query string; falls back to the opaque `origin` string if unparsed; special-cases avoiding an added trailing `/` for root-path URLs.\n- `toStringWithoutQuery(_forceProtocol?): string` — same as `toString()` but with `search` cleared.\n- `update(url: UrlOptions | string)` — re-runs `initFields`.\n\n### `UrlMatchPattern` (extends `Property`)\nImplements [Chrome extension match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns) (`scheme://host/path`, wildcards, `<all_urls>`); comment notes it doesn't support top-level-domain wildcards, and that URLs can't start with `-` (unenforced).\n- `override id = ''`\n- `constructor(pattern: string)`\n- `static override _index = 'id'`\n- `static readonly MATCH_ALL_URLS = '<all_urls>'`\n- `static pattern: string | undefined = undefined` — declared but unused (\"TODO: its usage is unknown\"); shadowed by (unrelated to) the instance's private `pattern`.\n- `static readonly PROTOCOL_DELIMITER = '+'` — multiple schemes are written as `http+https+custom://...`.\n- `getProtocols(): string[]` — `['http', 'https', 'file']` for `<all_urls>`; otherwise splits the scheme segment on `+`.\n- `test(urlStr: string): boolean` — true only if protocol, host, path, and port all match.\n- `testHost(hostStr: string): boolean` — segment-by-segment comparison against the pattern's host (split on `.`); a segment of `*` matches anything; segment **counts must match exactly** (no subdomain-wildcard-prefix support beyond single-segment `*`).\n- `testPath(pathStr: string): boolean` — segment-by-segment comparison (split on `/`); segment counts must match exactly; `*` matches any single segment.\n- `testPort(port: string, protocol: string): boolean` — returns `false` immediately if `testProtocol(protocol)` fails; `*` in the pattern matches any port; if no port is specified in either the pattern or input, falls back to protocol-default-port logic for `http`/`https` only.\n- `testProtocol(protocol: string): boolean` — `*` in the pattern matches any protocol.\n- `override toString(): string` — returns the raw pattern string.\n- `update(pattern: string)`\n\n### `UrlMatchPatternList<T extends UrlMatchPattern>` (extends `PropertyList<T>`)\n- `override _kind = 'UrlMatchPatternList'`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])`\n- `static isUrlMatchPatternList(obj: any): boolean`\n- `test(urlStr: string): boolean` — true if **any** pattern in the list matches.\n\n### Module-level functions\n- `toUrlObject(url: string | Url): Url` — **throws** `Error('Request URL is not specified')` if `url` is falsy; passes through an existing `Url` unchanged, otherwise constructs a new one from a string.\n- `resolveProtocolForProxy(rawUrl: string): string` — resolves the protocol to use for proxy selection when the URL may still contain unrendered template tags (pre-request scripts run before rendering); tries `new URL(rawUrl).protocol` first, falling back to `'https:'` on parse failure.\n\n## Script-facing surface\n\n`pm.request.url` is a `Url` instance. Common script usage:\n- `pm.request.url.toString()`, `pm.request.url.getHost()`, `pm.request.url.getPath()`, `pm.request.url.query`\n- `pm.request.url.addQueryParams([{key, value}])` / `pm.request.addQueryParams(...)` (delegates)\n- `pm.request.url.removeQueryParams('key')` / `pm.request.removeQueryParams(...)` (delegates)\n- `pm.request.url.hash`, `.host`, `.port`, `.protocol`, `.auth`\n\n`QueryParam` instances populate `pm.request.url.query` and `RequestBody.urlencoded`.\n\n`UrlMatchPattern`/`UrlMatchPatternList` aren't constructed directly from a typical script, but are reachable via `pm.request.certificate.matches` (see `certificates.ts`), which stores its match rule(s) as a `UrlMatchPatternList` directly. `pm.request.proxy.match` (see `proxy-configs.ts`) is different: it's a raw `string` on the instance, with a `UrlMatchPattern` built on the fly, temporarily, inside `ProxyConfig.test()`/`.getProtocols()` — and `ProxyConfig.bypass` is a plain `string[]` checked by exact string equality, not a `UrlMatchPatternList` at all (that type only appears as an unused `static bypass` field on the class, separate from the instance field of the same name).\n\n## Gotchas / notable behavior\n\n- `Url.variables` **always returns `[]`**, even though `UrlOptions.variables` and `Url.parse()`'s return both have a `variables` field — the field is accepted on input but silently discarded; there is no way to read it back out.\n- `Url`'s internal `urlObject` (a real `URL`) is `undefined` whenever the input string contains a template tag (`{{ }}` / `{% %}`) or otherwise fails `URL.canParse` — in that case nearly every getter/method (`host`, `path`, `port`, `protocol`, `getHost()`, `getRemote()`, etc.) silently returns an empty string/array instead of throwing. Only `toString()`/`toStringWithoutQuery()` fall back to the raw `origin` string.\n- Query-string encoding is inconsistent by design: `QueryParam.toString()` URL-encodes (via `URLSearchParams`), but `QueryParam.toRawString()` and `Url.getQueryString()` (which uses `toRawString()`) do **not** encode. `Url.addQueryParams(stringForm)` also splits on raw `&`/`=` without decoding.\n- `QueryParam.unparseSingle()` returns `{}` (not a string) when the input lacks `key`/`value` — inconsistent return type (`string | {}`) that callers must handle.\n- Several methods accept parameters that are **entirely unused**: `Url.getPath(_unresolved)`, `Url.getRemote(_forcePort)`, `Url.toString(_forceProtocol)`, `Url.toStringWithoutQuery(_forceProtocol)`, `QueryParam.parseSingle(_idx, _all)`. Passing anything for these has no effect.\n- `UrlMatchPattern.static pattern` (class-level) is unused/dead and easy to confuse with the instance's own private `pattern` field.\n- `UrlMatchPattern.testHost`/`testPath` require the **segment counts to match exactly** — a pattern like `*.insomnia.com` (2 segments) will not match `bin.download.insomnia.com` (4 segments), confirmed by the test suite (`urls.test.ts`).\n- `UrlMatchPattern` has no protocol-agnostic short-circuit: calling `testProtocol` on a pattern with no `://` returns `[]` for `getProtocols()`, meaning `testProtocol` always returns `false` for such a malformed pattern (per `urls.test.ts`, \"no protocol\" case).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes; `PropertyList.toObject()`'s fallback comment specifically calls out `UrlMatchPatternList` as a list with no natural key.\n- `packages/insomnia-scripting-environment/src/objects/utils.ts` — `checkIfUrlIncludesTag`, used by `Url.initFields` to avoid mangling template tags.\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.url: Url`; `toUrlObject` used when constructing a `Request`; `RequestBody.urlencoded: PropertyList<QueryParam>`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate.matches: UrlMatchPatternList<UrlMatchPattern>`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig.match: string` is used to construct a `UrlMatchPattern` on demand inside `test()`/`getProtocols()`; the instance's `ProxyConfig.bypass` is a plain `string[]` (exact-match only), not a `UrlMatchPatternList`.\n- `packages/insomnia-scripting-environment/src/objects/collection.ts` — re-exports `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` as part of the public collection API surface.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — uses `resolveProtocolForProxy` and `toUrlObject` in `initInsomniaObject`.\n",".claude/skills/fix-scripting-feature/references/objects/utils.md":"# Utils (misc helpers)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/utils.ts`\n\n## Purpose\nSingle-function utility module. Currently contains only a helper used to detect whether a request\nURL still contains unresolved template-tag syntax, which affects client-certificate matching in\n`insomnia.ts`.\n\n## Public API\n\n### `function checkIfUrlIncludesTag(url: string): boolean`\n```ts\nexport function checkIfUrlIncludesTag(url: string): boolean {\n  return /{%/.test(`${url}`) || /%}/.test(`${url}`) || /{{/.test(`${url}`) || /}}/.test(`${url}`);\n}\n```\nReturns `true` if the given `url` (coerced to a string via template literal) contains any of the\nfour Liquid/interpolation delimiter substrings: `{%`, `%}`, `{{`, `}}`. Used as a proxy for \"this\nURL hasn't been fully rendered yet\" / \"this URL has dynamic template parts that can't be resolved\nagainst a fixed hostname\".\n\n## Script-facing surface\nNone. This is internal plumbing — it is not exposed on `insomnia`/`pm` and scripts cannot call it.\n\n## Gotchas / notable behavior\n- The check is purely textual (four independent regex tests, not a real template parser) — it will\n  return `true` for a URL that merely contains a literal `{{` or `}}` substring even if it isn't\n  actually meant as an interpolation tag.\n- `` `${url}` `` coercion means passing `undefined`/`null`/non-string values won't throw; they'll be\n  stringified first (e.g. `undefined` becomes the string `\"undefined\"`, which does not match any\n  pattern, so the function returns `false`).\n\n## Related\n- `insomnia.ts` — the only current caller. `initInsomniaObject` uses `checkIfUrlIncludesTag(rawObj.request.url)` together with `filterClientCertificates` (from `insomnia/src/network/certificate`) to decide whether to fall back to an empty default client certificate (since a templated URL's real host can't be matched against certificate rules yet).\n",".claude/skills/fix-scripting-feature/references/objects/variables.md":"# Variable / VariableList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/variables.ts`\n\n## Purpose\nThis file defines the low-level key/value model — `Variable` (a single named, typed value) and `VariableList` (an ordered, keyed collection of `Variable`s) — built on top of `Property`/`PropertyList` from `properties.ts`. Unlike `Environment` (a `Map`-backed flat store used for `pm.environment`/`pm.globals`), `Variable`/`VariableList` is the object-per-entry model used elsewhere in the SDK wherever a Postman-compatible \"list of variable-shaped items\" is needed (e.g. request path parameters); it is not the type behind `insomnia.environment` or `insomnia.variables`.\n\n## Public API\n\n### `interface VariableDefinition`\n```ts\ninterface VariableDefinition {\n  id?: string;\n  key: string;\n  name?: string;\n  value: string;\n  type?: string;\n  disabled?: boolean;\n}\n```\nShape used to construct a `Variable`.\n\n### `class Variable extends Property`\n```ts\nconstructor(def?: VariableDefinition)\n```\nCalls `super()` (no args passed to `Property`'s constructor, so `Property`'s own `id`/`name`/`disabled` defaults are set first, then overwritten below). When `def` is provided: `id = def.id || ''`, `key = def.key`, `name = def.name`, `value = def.value`, `type = def.type || 'Variable'`, `disabled = def.disabled`. When `def` is omitted: `id = ''`, `key = ''`, `name = undefined`, `value = ''`, `type = 'Variable'`, `disabled = false`.\n\n- `key: string` — the variable's lookup key.\n- `value: any` — the stored value (any type, despite `VariableDefinition.value` being typed `string`).\n- `type: string` — defaults to `'Variable'`.\n- `override _kind = 'Variable'` *(`@ignore`)*.\n- `static override _index = 'key'` *(`@ignore`)* — tells `PropertyList`/`indexOf`/`one` to index `Variable`s by `key` instead of the default `id`.\n- `static types()` — **throws** `unsupportedError('types')` unconditionally; not implemented.\n- `cast(value: any)` — if `value` has `_kind === 'Variable'`, returns `value.value` (unwraps a `Variable` to its raw value); otherwise returns `undefined`.\n- `get()` — returns `this.value`.\n- `set(value: any)` — sets `this.value = value`.\n\n### `class VariableList<T extends Variable> extends PropertyList<T>`\n```ts\nconstructor(parent: PropertyList<T> | undefined, populate: T[])\n```\nCalls `super(Variable, undefined, populate)` (always uses `Variable` as `typeClass`, ignoring `T`'s actual class for indexing purposes), then sets `this.parent = parent`.\n\n- `override _kind = 'VariableList'` *(`@ignore`)*.\n- `static isVariableList(obj: any): boolean` — `'_kind' in obj && obj._kind === 'VariableList'`.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, any>` — builds a plain object keyed by each `Variable.key`:\n  - skips entries where `excludeDisabled && variable.disabled`.\n  - if `multiValue` is true and the key already exists in the output, coalesces values into an array (`[existing, value]`, growing it on further duplicates).\n  - otherwise, later entries with the same key overwrite earlier ones (last-write-wins) when `multiValue` is falsy.\n  - `_caseSensitive` and `_sanitizeKeys` are accepted for signature compatibility but unused.\n\nAll other list operations (`add`, `all`, `append`, `assimilate`, `clear`, `count`, `each`, `filter`, `find`, `get`, `has`, `idx`, `indexOf`, `insert`, `insertAfter`, `map`, `one`, `populate`, `prepend`, `reduce`, `remove`, `repopulate`, `toString`, `upsert`) are inherited unmodified from `PropertyList` — see `properties.md`. Because `Variable._index = 'key'`, `indexOf`/`one`/`upsert`/`has` on a `VariableList` match by `key`, not `id`.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` on its own — `insomnia.environment` and `insomnia.variables` are backed by `Environment`/`Variables` (see `environments.md`), not by `Variable`/`VariableList`. `Variable`/`VariableList` are the generic building blocks other collection-shaped properties reuse (e.g. anywhere the SDK needs an ordered, keyed set of `{key, value}` items with Postman-style list semantics such as `.upsert()`/`.toObject()`). Exported from the package's public surface via `index.ts` (`export { Variable, VariableList } from './variables'`) and re-exported under the `Collection` namespace via `collection.ts`, so consumers of the SDK's type declarations can reference `Collection.Variable`/`Collection.VariableList`.\n\n## Gotchas / notable behavior\n- `Variable.types()` is a stub that always throws `unsupportedError('types')` — calling it will break a script; it exists only for API-shape completeness.\n- `Variable.cast(value)` assumes `value` is an object with `in` support (`'_kind' in value`) — passing a primitive (e.g. `cast(5)`) will throw a `TypeError` since `in` requires an object operand on the right-hand side. There's no type guard before that check.\n- `VariableList`'s constructor hardcodes `Variable` as the `typeClass` regardless of the generic `T`, so subclasses of `Variable` (if any) still get indexed as plain `Variable` for `_index` purposes.\n- `VariableList.toObject()` **includes disabled variables by default** — callers must explicitly pass `excludeDisabled = true` to filter them out (confirmed by `variables.test.ts`: `toObject()` returns `{h1: 'v1', h2: 'v2'}` even when `h2` is disabled).\n- Duplicate keys: `toObject()` silently drops earlier duplicates unless `multiValue` is passed, in which case duplicates become an array under a single key — this can be surprising if a script assumes object-key uniqueness maps 1:1 to list length.\n\n## Related\n- `properties.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>`; inherits nearly all list/property behavior from there.\n- `environments.ts` — conceptually parallel/alternate model (`Environment`/`Variables`) that is what `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables` actually use; do not confuse the two when debugging variable-resolution issues.\n- `index.ts` / `collection.ts` — re-export `Variable`/`VariableList` as part of the SDK's public/namespaced surface.\n",".claude/skills/fix-test-cli-ci/SKILL.md":"---\nname: fix-test-cli-ci\ndescription: 'Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.'\nargument-hint: 'Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso'\n---\n\n# Fix test-cli.yml CI Failures\n\n## When to Use\n\n- `.github/workflows/test-cli.yml` or the `Test CLI` workflow failed in CI.\n- `inso` bundle tests fail locally or in GitHub Actions.\n- You suspect node module/runtime differences between Node.js and Electron.\n\n## Procedure\n\n1. Start with the failing CI evidence.\n   - Use the provided logs or workflow run link to identify whether the failure happened during the `test:bundle` run, the packaged binary run, or the build/package setup before the tests.\n   - Note the failing test name and the first actionable error message before reproducing locally.\n2. Ensure Node.js native dependencies are installed (not Electron-targeted variants):\n   ```bash\n   npm run install-libcurl-node\n   ```\n3. Build `insomnia-inso` to generate `dist`:\n   ```bash\n   npm run build -w insomnia-inso\n   ```\n4. Start the smoke-test echo server used by the CLI tests:\n   ```bash\n   npm run serve -w insomnia-smoke-test\n   ```\n5. In another terminal, run the bundled CLI test suite:\n   ```bash\n   npm run test:bundle -w insomnia-inso\n   ```\n6. If the CI failure was in the packaged binary path or the bundle suite passes locally, run:\n   ```bash\n   npm run test:binary -w insomnia-inso\n   ```\n\n## Notes\n\n- Keep the smoke-test server running while bundle tests execute.\n- Common failure patterns:\n  - Errors involving `@getinsomnia/node-libcurl`, `NODE_MODULE_VERSION`, `dlopen`, or native module loading usually mean the Node-targeted libcurl binary needs to be reinstalled with `npm run install-libcurl-node`.\n  - Connection failures to `http://localhost:4010` usually mean the smoke-test server is not running.\n  - Missing `dist` output or missing `binaries/inso` usually means `npm run build -w insomnia-inso` or the package step needs to be rerun.\n- Success criteria:\n  - `npm run test:bundle -w insomnia-inso` exits successfully and Vitest reports the `inso dev bundle` tests as passing.\n  - If validating the packaged binary path, `npm run test:binary -w insomnia-inso` also exits successfully.\n\n## Teardown\n\n- Stop the `npm run serve -w insomnia-smoke-test` process once validation is complete.\n- Reinstall the electron-targeted libcurl binary for continued Electron development:\n  ```bash\n  npm run install-libcurl-electron\n  ```\n","AGENTS.md":"# AGENTS.md\n\n## Tech Stack\n\n- **UI:** React with React Router (loaders/actions pattern)\n- **Components:** React Aria Components\n- **Desktop Shell:** Electron (main + renderer processes)\n- **Styling:** TailwindCSS\n- **Language:** TypeScript\n- **Database:** NeDB (`@seald-io/nedb`) — embedded NoSQL\n- **Build/Dev:** Vite, npm workspaces monorepo\n\n_See `package.json` for current versions and `.nvmrc` for the Node version._\n\n## Strict Rules\n\n- **No unsolicited formatting.** Rely on ESLint/Prettier. Do not reformat existing code.\n- **Strict scoping.** Only modify code directly related to the prompt. Do not refactor adjacent code unless asked.\n\n## Command Output\n\nPrefer quiet command variants to minimise output volume:\n\n- `git log --oneline -20` not `git log`\n- `git diff --stat` not `git diff`\n- `npm test --silent` not `npm test`\n- `tsc --noEmit 2>&1 | head -50` for type-check failures\n- Use the `Read` tool with `limit` rather than `cat` on large files\n- Use `Grep` with `head_limit` rather than unrestricted searches\n\n## Validation Commands\n\nRun from repo root before considering work complete:\n\n```bash\nnpm run lint          # ESLint all workspaces\nnpm run type-check    # TypeScript check all workspaces\nnpm test              # Tests all workspaces (or: npm test -w packages/insomnia)\n```\n\n## Worktree Setup\n\n- New git worktrees may not have `node_modules` yet. Before installing or validating, switch to the repo's required runtime from the worktree root:\n\n```bash\nfnm use \"$(cat .nvmrc)\"\nnode -v\nnpm -v\n```\n\n- This repo expects the `.nvmrc` Node version and npm 11+. If `fnm` is unavailable, manually use an equivalent Node/npm version before running any `npm` commands.\n- After switching versions in a fresh worktree, install dependencies from repo root with `npm ci`.\n- Do **not** use `npm ci --ignore-scripts` for normal worktree setup. It leaves Electron partially installed, which later breaks builds, renderer import checks, and other validation commands.\n\n## Repository Structure\n\n`packages/`\n  `insomnia/`                ← Main Electron app\n    `src/`\n      `common/`              ← Shared utils, settings types\n      `routes/`              ← React Router files (clientLoader/clientAction)\n      `ui/`                  ← React components, hooks, `insomnia-fetch.ts`\n      `main/`                ← Electron IPC handlers, `preload.ts`\n      `account/`             ← Auth, session, encryption\n      `sync/`                ← Git/VCS sync\n      `network/`             ← Request execution engine\n      `templating/`          ← Nunjucks rendering (Web Worker)\n  `insomnia-data/`           ← Data models, services, NeDB implementation, shared data utilities\n  `insomnia-api/`            ← Cloud API client\n  `insomnia-inso/`           ← CLI tool\n  `insomnia-testing/`        ← Test framework\n\n## Data Model Hierarchy\n\nOrganization\n  → Project (local | remote/cloud | git-backed)\n    → Workspace (scope: 'collection' | 'design')\n      → Base Environment (auto-created: use `models.environment.getOrCreateForParentId(workspaceId)`)\n        → Sub-Environments\n      → Cookie Jar (auto-created)\n      → Request Group (folders)\n        → Request (HTTP, GraphQL, gRPC, WebSocket, Socket.IO)\n      → Request (can be direct child of workspace)\n**Note:** A Workspace with `scope: 'collection'` IS the collection.\n\n## Key Patterns\n\n- **Route-Based Actions:** Mutations use React Router's `clientAction` (`src/routes/`).\n  - **CRITICAL:** `clientAction` blocks navigation. For long-running UI operations, use plain async functions instead.\n- **Database Buffering:** Always buffer bulk writes (`database.bufferChangesIndefinitely()`, then `flushChanges()`). Unbuffered writes fire UI revalidation per operation, causing severe lag.\n- **State Management:** Use Router loaders/actions and NeDB for persistent state. Use React `useState`/context for ephemeral UI state (No Redux/Zustand).\n- **Electron IPC:** For main↔renderer communication, define handlers in `src/main/ipc/`, expose in `src/main/preload.ts`, and update `window.main` in `src/global.d.ts`.\n- **Templates:** Nunjucks runs in a Web Worker (`src/templating/`). Use `{{ _.variable_name }}`.\n- **Models:** Follow CRUD via `models.<type>` (e.g., `create()`, `update()`).\n- **HTTP Calls:** Use `insomniaFetch()` for Insomnia backend APIs. Use plain `fetch()` for external/third-party APIs.\n- **Styling:** Tailwind utility classes only. Use `clsx`/`tailwind-merge` for conditionals. Use React Aria for interactive HTML elements.\n- **Testing:** Use **Vitest** (unit) and **Playwright** (E2E). Co-locate unit tests as `filename.test.ts`. Use `vi.mock()`. Prefer testing logic via loaders over mounting components.\n- **E2E tests** live in `packages/insomnia-smoke-test/`. Full docs: [`packages/insomnia-smoke-test/README.md`](packages/insomnia-smoke-test/README.md).\n- Run E2E from repo root: `npm run test:smoke:dev` (filter: `npm run test:smoke:dev -- <title-substring>`).\n- New test imports: `import { test } from '../../playwright/test'` and `import { expect } from '@playwright/test'`.\n\n## Sensitive Data\n\n- **Vault system (AES-GCM):** For environment secrets (`EnvironmentKvPairDataType.SECRET`).\n- **Electron safeStorage:** Platform-native encryption (`window.main.secretStorage`).\n","CLAUDE.md":"@AGENTS.md\n","packages/insomnia-api/README.md":"# Insomnia API\n\nThis package contains the API functionality and types for the Insomnia application which could be shared across insomnia and inso-cli.\n\n## Usage\n\n### Install\n\nUses npm workspace, so no need to install.\n\n### Import\n\n```ts\nimport { getUserProfile, getEncryptionKeys, type User, type UserEncryptionKeys } from 'insomnia-api';\n```\n","packages/insomnia-component-docs/AGENTS.md":"# AGENTS.md — Component Docs Site (Docusaurus + MDX)\n\nDocs for Insomnia's shared component library. Prefer the **`component-docs` skill** for authoring —\nit carries the template and full procedure. This file is the quick reference for direct edits.\n\n## Structure\n\n- One MDX file per component: `docs/Components/<name>.mdx` (kebab-case).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Order via\n  `sidebar_position` in frontmatter.\n- Reference doc for format/tone: `docs/Components/button.mdx`.\n\n## Doc format (per file)\n\n1. Frontmatter: `id`, `sidebar_label`, `sidebar_position`.\n2. `# Title` + one-line description.\n3. `## Props` table (prop / type / default / description) + link to the underlying React Aria props page.\n4. `## Usage Examples` — ` ```tsx live ` blocks (live-editable). Wrap multiple elements in a `function`.\n5. `## Styling` → `### CSS Variables` (color vars consumed) + any `theme--*` scope class note.\n\n## ⚠️ Live examples need ReactLiveScope registration\n\n` ```tsx live ` blocks resolve component names from `src/theme/ReactLiveScope/index.ts`, NOT from\nMDX imports. Any component used in a live block must be imported there and added to the\n`ReactLiveScope` object. Library components import from the barrel as `insomnia/src/basic-components`.\n\n## Commands\n\n```bash\ncd packages/insomnia-component-docs\nnpm run start   # local preview while writing\nnpm run build   # verify (catches broken live blocks / bad imports)\n```\n","packages/insomnia-data/README.md":"# insomnia-data\n\nA runtime-agnostic data layer for Insomnia, based on interface + IoC.\n\n## Core idea\n\n- `src/`: runtime-agnostic contracts (`IDatabase`, `Services`, model metadata/types)\n- `node-src/`: Node/main concrete implementations (`createNedbDatabase`, `servicesNodeImpl`)\n- entry points wire once:\n  - `initDatabase(impl)`\n  - `initServices(impl)`\n\nAfter wiring, business code always uses the same APIs: `database`, `services`, `models`.\n\n## Process flows\n\n### Database (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initDatabase] --> R1[clientDatabase implementation]\n        R2[feature code] --> R3[database from insomnia-data]\n        R3 --> R1\n        R1 --> R4[window.database.invoke]\n        R4 --> R5[ipcRenderer.invoke database.invoke]\n    end\n\n    subgraph Main\n        M0[initDatabase] --> M1[mainDatabase implementation]\n        M1 --> M2[createNedbDatabase impl]\n        M2 --> M3[(NeDB)]\n        M4[main feature code] --> M5[database from insomnia-data]\n        M5 --> M1\n        M6[ipcMain.handle database.invoke] --> M1\n        M1 --> M7[webContents.send db.changes]\n    end\n\n    subgraph Inso\n        I0[initDatabase] --> I1[inso database implementation]\n        I1 --> I2[createNedbDatabase impl]\n        I2 --> I3[(NeDB)]\n        I4[inso feature code] --> I5[database from insomnia-data]\n        I5 --> I1\n    end\n\n    R5 --> M6\n    M7 -.notify.-> R2\n```\n\n### Services (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initServices] --> R1[preload servicesProxy implementation]\n        R2[feature code] --> R3[services from insomnia-data]\n        R3 --> R1\n        R1 --> R4[ipcRenderer.invoke services.invoke]\n    end\n\n    subgraph Main\n        M0[initServices] --> M1[servicesNodeImpl]\n        M2[feature code] --> M3[services from insomnia-data]\n        M3 --> M1\n        M1 --> M4[service logic]\n        M4 --> M5[database]\n        M5 --> M6[(NeDB)]\n        M7[ipcMain.handle services.invoke] --> M1\n    end\n\n    subgraph Inso\n        I0[initServices] --> I1[servicesNodeImpl]\n        I2[feature code] --> I3[services from insomnia-data]\n        I3 --> I1\n        I1 --> I6[(NeDB)]\n    end\n\n    R4 --> M7\n```\n\nRenderer services path:\n\n`services.xxx` -> preload proxy -> IPC -> main handler -> `servicesNodeImpl` -> database.\n\n## Why this design\n\n- Same API across runtimes: main, renderer, inso.\n- Feature code is decoupled from Electron/IPC/NeDB details.\n- Renderer has a safer boundary (bridge + IPC, no direct DB internals and node API access).\n- Easy to test or swap implementations by injecting at startup.\n\n## Minimal usage\n\n### Main\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { mainDatabase } from '~/main/database.main';\nimport { servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(mainDatabase);\ninitServices(servicesNodeImpl);\n```\n\n### Renderer\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { clientDatabase } from '~/ui/database.client';\n\nawait initDatabase(clientDatabase);\ninitServices(window._dataServices);\n```\n\n### Inso / Node\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { createNedbDatabase, servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(createNedbDatabase());\ninitServices(servicesNodeImpl);\n```\n\n### Consuming\n\n```ts\nimport { services, models, type Request } from 'insomnia-data';\n\nconst mcpRequest = await services.mcpRequest.create({ url: 'http://localhost:3000' });\nconst all = await services.mcpRequest.all();\n\nconst request: Request = {};\n\nconst requestType = models.request.type;\n```\n","packages/insomnia-inso/README.md":"# Documentation\n\nHow to use [Inso CLI](https://docs.insomnia.rest/inso-cli/introduction).\n\n## Testing\n\n```shell\n# unit tests\nnpm run test:unit\n\n# start smoke test api (required for e2e tests)\nnpm run serve -w insomnia-smoke-test\n\n# e2e tests for dev bundle\nnpm run test:bundle\n\n# e2e tests for binary\nnpm run test:binary\n```\n\n## Development\n\n### Getting started\n\n```shell\nnpm run inso-start\nnpm run test -w insomnia-inso\n# will default to insomnia app database\n$PWD/packages/insomnia-inso/bin/inso run test\n# will use config, useful for testing with fewer args\n$PWD/packages/insomnia-inso/bin/inso -w packages/insomnia-inso/src/db/fixtures/git-repo script runTest\n```\n\n### node-libcurl\n\n`Error: The module '.../insomnia/node_modules/@getinsomnia/node-libcurl/lib/binding/node_libcurl.node' was compiled against a different Node.js version using`\n\nnode-libcurl builds for 3 operating systems and two versions of nodejs. insomnia-inso uses the nodejs build and insomnia app uses the electron build. you can switch between them using the following two commands\n\n```shell\n# install node version\nnpm run install-libcurl-node\n# install electron version\nnpm run install-libcurl-electron\n```\n\n## Run CLI Smoke Tests\n\n```shell\n# Run CLI tests\nnpm run test:bundle -w insomnia-inso\n# Package the Inso CLI binaries\nnpm run inso-package\nnpm run test:binary -w insomnia-inso\n```\n\n## Debugging CLI tests using watcher\n\nThis is helpful for debugging failing api tests\n\nFrom project root, in separate terminals:\n\n```sh\n# start smoke test api\nnpm run serve -w insomnia-smoke-test\n\n# watch inso\nnpm run start -w insomnia-inso\n\n# run api test with dev bundle. To debug run this in a Javascript Debug Terminal in VSCode\n$PWD/packages/insomnia-inso/bin/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n```\n\n## How to debug pkg\n\n```sh\n# run modify package command and then a unit test\nnpm run package -w insomnia-inso && \\\n$PWD/packages/insomnia-inso/binaries/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n\n```\n\n## How to update the `inso-nedb` fixtures\n\nRun Insomnia with `INSOMNIA_DATA_PATH` environment variable set to `fixtures/inso-nedb`, e.g.:\n\n```bash\nINSOMNIA_DATA_PATH=packages/insomnia-smoke-test/fixtures/inso-nedb /Applications/Insomnia.app/Contents/MacOS/Insomnia\n```\n\nRelaunch the app one more time, so that Insomnia compacts the database.\n\nThe `.gitignore` file will explicitly ignore certain database files, to keep the directory size down and avoid prevent sensitive data leaks.\n\n## How to run inso with the `inso-nedb` fixture locally?\n\nSet the `-w` argument pointed to `packages/insomnia-smoke-test/fixtures/inso-nedb`:\n\n```bash\n# if installed globally\ninso -w <INSO_NEDB_PATH>\n\n# using the package bin\n./packages/insomnia-inso/bin/inso -w <INSO_NEDB_PATH>\n\n# using a binary\n./packages/insomnia-inso/binaries/insomnia-inso -w <INSO_NEDB_PATH>\n```\n\n## How to debug the bundled assets\n\n```bash\nDEBUG=1 npm run build\n```\n\nThis will generate an `artifacts` directory containing information about the bundled assets.\nThe meta.json can be uploaded to https://esbuild.github.io/analyze/ to visualize the bundle.\nThe bundle-analysis.log can be used to see the dependency tree of the bundle.\n\n## How to generate documents about inso\n\n1. Ensure your node version matches the project `.nvmrc` perhaps with `fnm use` or another version manager.\n1. Run the script below, which will build inso in dev mode in order to use it to generate docs about itself\n1. The docs appear in your vscode diff, or you can look in `./packages/insomnia-inso/reference/` if the version number looks wrong you might need to check what branch you're on, it should be run from develop ideally as the only changes in the release branch should be hotfixes that dont affect inso docs\n\n```sh\nnpm i && npm run build -w insomnia-inso && $PWD/packages/insomnia-inso/bin/inso generate-docs\n```\n","packages/insomnia-smoke-test/README.md":"# Insomnia Smoke Tests\n\nPlaywright E2E tests for the Insomnia desktop app.\n\n> CLI smoke tests: [CLI.md](CLI.md)\n\n## Test structure\n\n```\ntests/\n  smoke/      # Main suite — runs on every CI push (Ubuntu only)\n  critical/   # Single critical-path test — runs on release\n  migration/  # Data migration tests\n```\n\nAll commands below must be run from the **repo root**.\n\n## Quick-start\n\n```bash\nnpm install\nnpm run test:smoke:dev   # run all Smoke tests (dev mode)\n```\n\nBoth the echo server (port 4010) and the Vite dev server start automatically.\n\n**Filter to one test** — pass a substring of the test title or file name:\n\n```bash\nnpm run test:smoke:dev -- oauth\n```\n\n**Interactive UI:**\n\n```bash\nnpm run test:smoke:dev -- --ui\n```\n\n**Step-through debugger:**\n\n```bash\nPWDEBUG=1 npm run test:smoke:dev\n```\n\n## Additional log levels\n\n```bash\nDEBUG=pw:api npm run test:smoke:dev         # Playwright API logs\nDEBUG=pw:browser npm run test:smoke:dev     # Insomnia console logs\nDEBUG=pw:WebServer npm run test:smoke:dev   # Web server logs\n```\n\n## Traces and error context\n\nOn failure, two artifacts are written under `packages/insomnia-smoke-test/traces/<test-name>/`:\n\n- **`error-context.md`** — error details, ARIA page snapshot at point of failure, and annotated test source. Read this first.\n- **`trace.zip`** — full Playwright trace (network, screenshots, DOM snapshots).\n\nOpen a trace:\n\n```bash\nnpx playwright show-trace packages/insomnia-smoke-test/traces/<test-name>/trace.zip\n```\n\nOr upload to [trace.playwright.dev](https://trace.playwright.dev/).\n\nCI traces are available as artifacts on failed workflow runs.\n\n## Build / package modes\n\nRun against a JS bundle (`build`) or a packaged binary (`package`) instead of the dev watcher:\n\n```bash\n# build mode\nnpm run app-build\nnpm run test:smoke:build\n\n# package mode\nnpm run app-package\nnpm run test:smoke:package\n```\n\n> macOS package mode: set `com.apple.security.cs.disable-library-validation` to `true` in `entitlements.mac.inherit.plist` to allow unsigned local binaries. Do not commit this change.\n\n## Non-CI / pre-release tests\n\n```bash\nnpm run test:dev -w insomnia-smoke-test -- tests/smoke/preferences-interactions.test.ts\n```\n\n## Cert refresh\n\nIf the custom CA cert test fails after 2026:\n\n```bash\nmkcert -install && mkcert localhost && mkcert -CAROOT\n```\n","packages/insomnia-smoke-test/playwright/pages/README.md":"# Playwright POM Conventions\n\nThis directory contains the Insomnia smoke-test Page Object Model (POM).\nThe goal is to keep tests stable, readable, and selector-safe.\n\n## 1. Object Definitions\n\n- `InsomniaApp`: root facade. It wires page objects and shared components.\n- `Page` object: route-level surface (for example, project page).\n- `Component` object: reusable or bounded UI region (for example, statusbar, tabbar, sidebar).\n\n## 2. Page vs Component Split\n\n- Put route-specific workflows in a `Page` object.\n- Put reusable or independently testable UI regions in a `Component`.\n- A `Page` composes its own components.\n- `InsomniaApp` composes top-level pages/components and exposes shortcuts.\n- Tests should use POM APIs first. Raw `page.getBy...` is only acceptable for gaps that are not modeled yet.\n\n## 3. Locator Ownership and Naming\n\n- Every page/component must have its own `root` getter.\n- Every POM must manage all locators used by its own actions.\n- Use `xxxLocator(...)` method naming when the locator needs parameters (for example, list row by name).\n- For a single fixed element (for example, `root`, `plusButton`), expose it as a getter directly.\n- Scope locators from `root` whenever possible to reduce collisions.\n- If a locator belongs to an existing POM, tests should not duplicate that selector.\n\n## 4. API Naming\n\n- Action methods use verb-first names: `closeTab`, `openAddTabMenu`.\n- POM exposes actions and locators; assertions stay in test files.\n- Keep methods focused on one behavior.\n\n## 5. Test Usage Pattern\n\nInstantiate once per test and use composed objects:\n\n```ts\ntest('example test', async ({ insomnia }) => {\n  await insomnia.projectPage.createCollection();\n  await insomnia.tabbar.closeTab('New Request');\n  await expect.soft(insomnia.tabbar.tabLocator('foo')).toHaveAttribute('data-selected', 'true');\n});\n```\n","packages/insomnia/README.md":"# Insomnia\n\nThe main desktop application.\n\n## Data fetching\n\n- To fetch data from our APIs we use the `insomniaFetch` function. This allows us to overcome cross-origin issues and helps us to standardize the way we fetch data and also to handle errors in a centralized way.\n\n- For real-time data fetching we use SSE (Server-Sent Events). This is a standard way to receive data from the server in real-time. The server will send a message to the client when something happens. The client will receive the message and update the data accordingly. To handle SSE we use the `insomnia-event-source://` protocol which is a custom protocol that gets handled by the app in the main process. This allows us to overcome cross-origin issues and also to handle SSE in a centralized way.\n\n- Polling is also used to fetch data from the server. This is used when we need to fetch data periodically as SSE can sometimes fail and data can go out of sync.\n  The main places we use polling are:\n  - Refreshing Insomnia Sync data.\n  - Refreshing Git Sync data.\n  - Refreshing Presence data.\n\n> ⚠️ Using a combination of SSE and polling we can keep the data in sync and up to date but might also lead to some race conditions. For example, if we are polling for data every 5 seconds and the server sends an SSE message at the same time, the data might be out of sync for a few seconds. This is something we need to keep in mind when using SSE and polling.\n","packages/insomnia/src/basic-components/AGENTS.md":"# AGENTS.md — Shared Component Library\n\nThis is Insomnia's shared component library (React Aria + Tailwind). See the design/roadmap\nin https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan\n\n## Conventions\n\n- Build on `react-aria-components` (high-level) first; only drop to `react-aria` hooks when the\n  high-level component can't meet the need (e.g. Tooltip's arbitrary non-focusable trigger).\n- **Colors** use repo CSS variables (`token-(--var)`, e.g. `text-(--color-font)`, `bg-(--hl-sm)`).\n  Everything else (size/spacing/radius/font) uses native Tailwind utilities — do NOT use\n  `--padding-*` / `--radius-*` / `--font-size-*`. Hardcode a color only if no repo variable fits, with a `FIXME`.\n- **Variants** use `tailwind-variants` (`tv`); non-variant components use `cls()` (tailwind-merge).\n- **Preserve `theme--*` scope classes** (`theme--dialog`, `theme--tooltip`, `theme--dropdown__menu`,\n  `theme--link`, …) on the matching component root — they are the per-component theming contract\n  (`src/ui/plugins/misc.ts`). Dropping them silently breaks user theme customization.\n\n## When you add or change a component → update its docs\n\nDocs live in the Docusaurus site at `packages/insomnia-component-docs` (one MDX per component).\nUse the **`component-docs` skill** to create/update the doc. Don't forget to register the component\nin `src/theme/ReactLiveScope/index.ts` or its live examples won't compile.\n\nA component is \"done\" only when its doc + ReactLiveScope entry are in place and the docs site builds\n(`cd packages/insomnia-component-docs && npm run build`).\n","packages/insomnia/src/basic-components/README.md":"# `~/basic-components` — Shared Component Library\n\nInsomnia's shared component library, built on `react-aria-components` + TailwindCSS. Full design/roadmap:\n[Insomnia Shared Component Library Plan](https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan) (Confluence). Conventions: [`AGENTS.md`](./AGENTS.md).\n\nFull per-component docs (props, variants, live examples) live on the Docusaurus site\n`packages/insomnia-component-docs`, not in this file.\n\n## Import\n\n```ts\nimport { Button, Modal, Icon } from '~/basic-components';\n```\n\n**Always import from the barrel (`~/basic-components`).** The barrel is the public contract; the\ncurrent file layout (flat) is an implementation detail. Physical layering into\n`primitives/overlays/collections/layout/typography` is **deferred to M5** (see the plan) — because\nconsumers go through the barrel, that later move only edits the barrel's internal re-exports, never\na business import. New code and the docs' `ReactLiveScope` must use the barrel so those moves stay\nfree. Existing deep imports (`~/basic-components/button`, ~18 files) are left as-is until M5.\n\n## Usage index (current)\n\n| Component       | Barrel export | Location                                                                                |\n| --------------- | ------------- | --------------------------------------------------------------------------------------- |\n| `Button`        | ✅            | `button.tsx`                                                                            |\n| `LearnMoreLink` | ✅            | `link.tsx`                                                                              |\n| `SelectPopover` | ✅            | `select-popover.tsx`                                                                    |\n| `Modal`         | ✅            | `modal.tsx`                                                                             |\n| `Tab` / `Tabs`  | ✅            | `tabs.tsx`                                                                              |\n| `Banner`        | ✅            | `banner.tsx`                                                                            |\n| `Card`          | ✅            | `card.tsx`                                                                              |\n| `Divider`       | ✅            | `divider.tsx`                                                                           |\n| `Progress`      | ✅            | `progress.tsx`                                                                          |\n| `Icon`          | ✅            | `icon.tsx` (re-exports `ui/components/icon`, pending consolidation — see mapping below) |\n\n`utils/` helpers (e.g. `utils/cls.ts`, `utils/variants.ts`, `utils/index.ts`) are library-internal (tailwind-merge / tailwind-variants helpers) and are **not** re-exported from the barrel — see [`AGENTS.md`](./AGENTS.md).\n\n## Migration mapping (old → new)\n\nTracks the consolidation described in the plan (Confluence). Status: 🔜 not started · 🚧 in progress · ✅ done.\n\n| Old                                                                                                                          | New                                                    | Status                                                                    |\n| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |\n| `basic-components/button.tsx` + `ui/components/themed-button/` (9 files) + `async-button.tsx`                                | `Button` (+ loading spinner, `AsyncButton` wrapper)    | 🚧 (canonical file in `button.tsx`; consolidation pending)                |\n| — (new)                                                                                                                      | `IconButton`                                           | 🔜                                                                        |\n| `ui/components/icon.tsx` (38 files) + `basic-components/icon.tsx` (~10) + `ui/components/svg-icon.tsx` (70 glyphs, 13 files) | `Icon`                                                 | 🔜                                                                        |\n| `ui/components/tooltip.tsx` (21) + `ui/components/help-tooltip.tsx` (26)                                                     | `Tooltip` / `HelpTooltip`                              | 🔜                                                                        |\n| `ui/components/base/input.tsx`                                                                                               | `Input` / `TextField`                                  | 🔜                                                                        |\n| `ui/components/base/select.tsx` + `basic-components/select-popover.tsx`                                                      | `Select`                                               | 🚧 (canonical file in `select-popover.tsx`; merge pending)                |\n| `ui/components/base/modal.tsx` + `modal-header/body/footer.tsx`                                                              | `Modal` (+ `size` variant, `Modal.Header/Body/Footer`) | 🚧 (canonical file in `modal.tsx`; feature parity pending, see plan §7.6) |\n| `ui/components/base/dropdown/`                                                                                               | `Menu`                                                 | 🔜                                                                        |\n| — (new)                                                                                                                      | `Popover`                                              | 🔜                                                                        |\n| — (new)                                                                                                                      | `ListBox` / `ListBoxItem`                              | 🔜                                                                        |\n| `basic-components/tabs.tsx`                                                                                                  | `Tabs` (+ controlled/render-prop)                      | 🚧 (canonical file in `tabs.tsx`)                                         |\n| `ui/components/base/checkbox.tsx`                                                                                            | `Checkbox` / `CheckboxGroup`                           | 🔜                                                                        |\n| `ui/components/base/switch.tsx`                                                                                              | `Switch`                                               | 🔜                                                                        |\n| `ui/components/base/input-number.tsx`                                                                                        | `NumberField`                                          | 🔜                                                                        |\n| `ui/components/base/date-picker.tsx`                                                                                         | `DatePicker`                                           | 🔜                                                                        |\n| `ui/components/base/middle-truncate.tsx`                                                                                     | TBD (P2 fill-in)                                       | 🔜                                                                        |\n| `basic-components/link.tsx` + `ui/components/base/link.tsx`                                                                  | `Link`                                                 | 🚧 (canonical file in `link.tsx`; merge pending)                          |\n| `ui/components/unsaved-changes-confirm-dialog.tsx` + guard                                                                   | `ConfirmDialog`                                        | 🔜                                                                        |\n| `ui/components/editable-input.tsx`                                                                                           | `EditableInput`                                        | 🔜                                                                        |\n| `ui/components/time-from-now.tsx`                                                                                            | `TimeFromNow`                                          | 🔜                                                                        |\n| `basic-components/card.tsx` / `divider.tsx` / `banner.tsx` / `progress.tsx`                                                  | `Card` / `Divider` / `Banner` / `Progress`             | 🚧 (`Progress` still has hardcoded colors, see FIXME)                     |\n| `ui/components/base/badge.tsx`                                                                                               | `Badge`                                                | 🔜                                                                        |\n\nNot covered by this library (feature consumers, not generic primitives): `ui/components/tabs/`,\n`ui/components/dropdowns/`.\n","packages/insomnia/src/cpp/README.md":"# Secure Wrapper\n\nThe purpose of this secure wrapper is to mitigate a [local search path vulnerability](https://vuldb.com/?id.295961).\n\nIt utilizes Windows' `PROCESS_MITIGATION_POLICY` to prefer the system paths so that the Electron application does not load hijacked DLLs that are placed in the installation directory (writable by underprivileged users).\n\n## Building Locally\n\nOn Windows, install MSYS2 with the base self-extracting installer: [https://github.com/msys2/msys2-installer/releases](https://github.com/msys2/msys2-installer/releases)\n\nMove that `.sfx.exe` file to wherever you'd like the MSYS2 installation to be, and run it. It will extract the install folder to that location. (i.e. `C:\\msys2-base-x86_64-NNNNNNNN.sfx.exe` to install to `C:\\msys64`).\n\nThen, open the MSYS2 terminal to finish the installation (`C:\\msys64\\msys2.exe`).\n\nClose that terminal, and open the `ucrt64` terminal (`C:\\msys64\\ucrt64.exe`).\nInstall the build system:\n\n```bash\npacman -S mingw-w64-ucrt-x86_64-gcc\n```\n\nIf you don't already have them, install git and node:\n\n```bash\npacman -S git\npacman -S nodejs\n```\n\nOnce that's done, ensure `windres` and `g++` are in your path:\n\n```bash\n$ which windres\n/ucrt64/bin/windres\n\n$ which g++\n/ucrt64/bin/g++\n```\n\nNavigate to the top-level directory of this repository and run:\n\n```bash\nnpm install\n./build-secure-wrapper.sh\n```\n\nOnce built, the Squirrel Installer can be launched from `dist/win-unpacked`.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\n## Tech Stack\n\n- **UI:** React with React Router (loaders/actions pattern)\n- **Components:** React Aria Components\n- **Desktop Shell:** Electron (main + renderer processes)\n- **Styling:** TailwindCSS\n- **Language:** TypeScript\n- **Database:** NeDB (`@seald-io/nedb`) — embedded NoSQL\n- **Build/Dev:** Vite, npm workspaces monorepo\n\n_See `package.json` for current versions and `.nvmrc` for the Node version._\n\n## Strict Rules\n\n- **No unsolicited formatting.** Rely on ESLint/Prettier. Do not reformat existing code.\n- **Strict scoping.** Only modify code directly related to the prompt. Do not refactor adjacent code unless asked.\n\n## Command Output\n\nPrefer quiet command variants to minimise output volume:\n\n- `git log --oneline -20` not `git log`\n- `git diff --stat` not `git diff`\n- `npm test --silent` not `npm test`\n- `tsc --noEmit 2>&1 | head -50` for type-check failures\n- Use the `Read` tool with `limit` rather than `cat` on large files\n- Use `Grep` with `head_limit` rather than unrestricted searches\n\n## Validation Commands\n\nRun from repo root before considering work complete:\n\n```bash\nnpm run lint          # ESLint all workspaces\nnpm run type-check    # TypeScript check all workspaces\nnpm test              # Tests all workspaces (or: npm test -w packages/insomnia)\n```\n\n## Worktree Setup\n\n- New git worktrees may not have `node_modules` yet. Before installing or validating, switch to the repo's required runtime from the worktree root:\n\n```bash\nfnm use \"$(cat .nvmrc)\"\nnode -v\nnpm -v\n```\n\n- This repo expects the `.nvmrc` Node version and npm 11+. If `fnm` is unavailable, manually use an equivalent Node/npm version before running any `npm` commands.\n- After switching versions in a fresh worktree, install dependencies from repo root with `npm ci`.\n- Do **not** use `npm ci --ignore-scripts` for normal worktree setup. It leaves Electron partially installed, which later breaks builds, renderer import checks, and other validation commands.\n\n## Repository Structure\n\n`packages/`\n  `insomnia/`                ← Main Electron app\n    `src/`\n      `common/`              ← Shared utils, settings types\n      `routes/`              ← React Router files (clientLoader/clientAction)\n      `ui/`                  ← React components, hooks, `insomnia-fetch.ts`\n      `main/`                ← Electron IPC handlers, `preload.ts`\n      `account/`             ← Auth, session, encryption\n      `sync/`                ← Git/VCS sync\n      `network/`             ← Request execution engine\n      `templating/`          ← Nunjucks rendering (Web Worker)\n  `insomnia-data/`           ← Data models, services, NeDB implementation, shared data utilities\n  `insomnia-api/`            ← Cloud API client\n  `insomnia-inso/`           ← CLI tool\n  `insomnia-testing/`        ← Test framework\n\n## Data Model Hierarchy\n\nOrganization\n  → Project (local | remote/cloud | git-backed)\n    → Workspace (scope: 'collection' | 'design')\n      → Base Environment (auto-created: use `models.environment.getOrCreateForParentId(workspaceId)`)\n        → Sub-Environments\n      → Cookie Jar (auto-created)\n      → Request Group (folders)\n        → Request (HTTP, GraphQL, gRPC, WebSocket, Socket.IO)\n      → Request (can be direct child of workspace)\n**Note:** A Workspace with `scope: 'collection'` IS the collection.\n\n## Key Patterns\n\n- **Route-Based Actions:** Mutations use React Router's `clientAction` (`src/routes/`).\n  - **CRITICAL:** `clientAction` blocks navigation. For long-running UI operations, use plain async functions instead.\n- **Database Buffering:** Always buffer bulk writes (`database.bufferChangesIndefinitely()`, then `flushChanges()`). Unbuffered writes fire UI revalidation per operation, causing severe lag.\n- **State Management:** Use Router loaders/actions and NeDB for persistent state. Use React `useState`/context for ephemeral UI state (No Redux/Zustand).\n- **Electron IPC:** For main↔renderer communication, define handlers in `src/main/ipc/`, expose in `src/main/preload.ts`, and update `window.main` in `src/global.d.ts`.\n- **Templates:** Nunjucks runs in a Web Worker (`src/templating/`). Use `{{ _.variable_name }}`.\n- **Models:** Follow CRUD via `models.<type>` (e.g., `create()`, `update()`).\n- **HTTP Calls:** Use `insomniaFetch()` for Insomnia backend APIs. Use plain `fetch()` for external/third-party APIs.\n- **Styling:** Tailwind utility classes only. Use `clsx`/`tailwind-merge` for conditionals. Use React Aria for interactive HTML elements.\n- **Testing:** Use **Vitest** (unit) and **Playwright** (E2E). Co-locate unit tests as `filename.test.ts`. Use `vi.mock()`. Prefer testing logic via loaders over mounting components.\n- **E2E tests** live in `packages/insomnia-smoke-test/`. Full docs: [`packages/insomnia-smoke-test/README.md`](packages/insomnia-smoke-test/README.md).\n- Run E2E from repo root: `npm run test:smoke:dev` (filter: `npm run test:smoke:dev -- <title-substring>`).\n- New test imports: `import { test } from '../../playwright/test'` and `import { expect } from '@playwright/test'`.\n\n## Sensitive Data\n\n- **Vault system (AES-GCM):** For environment secrets (`EnvironmentKvPairDataType.SECRET`).\n- **Electron safeStorage:** Platform-native encryption (`window.main.secretStorage`).\n","isInternal":false,"tokens":1310,"sizeBytes":5288},{"name":"template.mdx","path":".claude/skills/component-docs/template.mdx","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/component-docs/template.mdx","title":"Claude Directive: template.mdx","category":"claude-rule","format":"markdown","content":"---\nid: COMPONENT_ID\nsidebar_label: 'ComponentLabel'\nsidebar_position: 99\n---\n\n# ComponentName\n\nOne-line description of the component and what it is built on (e.g. \"built on React Aria's `X` component\").\n\n## Props\n\n| Prop        | Type       | Default | Description        |\n| ----------- | ---------- | ------- | ------------------ |\n| `propName`  | `type`     | `-`     | What it does       |\n| `className` | `string`   | `-`     | Additional classes |\n| `children`  | `ReactNode`| `-`     | Content            |\n\nExtends all [React Aria XProps](https://react-spectrum.adobe.com/react-aria/X.html#props).\n\n## Usage Examples\n\n### Basic\n\n```tsx live\nfunction Example() {\n  return <ComponentName>Hello</ComponentName>;\n}\n```\n\n### Variants\n\n```tsx live\nfunction Example() {\n  return (\n    <div className=\"flex gap-4\">\n      <ComponentName variant=\"a\">A</ComponentName>\n      <ComponentName variant=\"b\">B</ComponentName>\n    </div>\n  );\n}\n```\n\n## Styling\n\n### CSS Variables\n\nThe component uses the following CSS variables for theming:\n\n- `--color-font`: Text color\n- `--color-bg`: Background color\n- `--hl-sm`: Subtle highlight (hover/focus)\n\n<!-- If the component carries a theme scope class (e.g. theme--dialog / theme--tooltip),\n     note it here: user themes can customize this component's colors via that class. -->\n","frontmatter":{"id":"COMPONENT_ID","sidebar_label":"ComponentLabel","sidebar_position":99},"isInternal":false,"tokens":337,"sizeBytes":1318},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"SKILL.md","path":".claude/skills/component-docs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/component-docs/SKILL.md","title":"component-docs","category":"anthropic-skill","format":"markdown","content":"---\nname: component-docs\ndescription: \"Author or update a component doc in the Docusaurus site (packages/insomnia-component-docs) for a component in the shared library. Use when adding a new component to src/basic-components, changing a component's props/variants, or when asked to document/update docs for a component.\"\nargument-hint: 'The component name and its source path, e.g. \"Select (packages/insomnia/src/basic-components/select.tsx)\"'\n---\n\n# Component Docs (Docusaurus + MDX)\n\nAuthor/maintain docs for the shared component library on the Docusaurus site at\n`packages/insomnia-component-docs`. Docs are one MDX file per component with live,\neditable examples.\n\n## When to Use\n\n- A new component was added to `packages/insomnia/src/basic-components/` (or migrated in).\n- A component's props/variants/styling changed and its doc needs updating.\n- Asked to \"document\" / \"write docs for\" / \"update docs for\" a component.\n\n## Key Facts (read before editing)\n\n- **One file per component**: `packages/insomnia-component-docs/docs/Components/<name>.mdx` (kebab-case filename).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Just set `sidebar_position` in frontmatter for ordering.\n- **Live examples** use ` ```tsx live ` fenced blocks (via `@docusaurus/theme-live-codeblock`). Component names in those blocks resolve from a shared scope, NOT from imports in the MDX.\n- **⚠️ The easy-to-forget step**: any component used in a `tsx live` block MUST be imported and registered in `packages/insomnia-component-docs/src/theme/ReactLiveScope/index.ts`. If it isn't, the live block fails to compile. Library components import as `insomnia/src/basic-components/<name>`.\n\n## Procedure\n\n1. **Inspect the component source** to get the real API: props, types, defaults, variants (`tv` config), and which theme CSS variables / `theme--*` classes it uses.\n2. **Create/update the MDX** at `docs/Components/<name>.mdx` using `template.mdx` in this skill folder as the starting point. Fill:\n   - Frontmatter: `id`, `sidebar_label`, `sidebar_position` (next free integer, or logical grouping).\n   - Title + one-line description.\n   - `## Props` table (prop / type / default / description). End with a link to the underlying React Aria props page when it extends an RA component.\n   - `## Usage Examples` — at least 2–3 ` ```tsx live ` blocks covering the main variants/states. Keep examples small and self-contained; wrap multiple elements in a `function` component.\n   - `## Styling` → `### CSS Variables` list (only the color vars the component consumes) and, if applicable, a note on the `theme--*` scope class it carries.\n3. **Register in ReactLiveScope** (`src/theme/ReactLiveScope/index.ts`): add the `import` and include the component in the `ReactLiveScope` object. Only needed once per component.\n4. **Verify the site builds** (catches broken live blocks and bad imports):\n   ```bash\n   cd packages/insomnia-component-docs && npm run build\n   ```\n   For interactive preview while writing: `cd packages/insomnia-component-docs && npm run start`.\n\n## Conventions\n\n- Match the existing docs' tone and section order (see `docs/Components/button.mdx` as the reference).\n- Document colors via CSS variables (`--color-*` / `--hl-*`); sizes/spacing are native Tailwind utilities — mirror the component's actual source, not an idealized version. (Library styling conventions live in `packages/insomnia/src/basic-components/AGENTS.md`.)\n- Keep live examples free of business/data dependencies so they render in isolation.\n- If the component is multi-part (e.g. `Modal.Header/Body/Footer`), document the sub-components together in one file.\n","frontmatter":{"name":"component-docs","description":"Author or update a component doc in the Docusaurus site (packages/insomnia-component-docs) for a component in the shared library. Use when adding a new component to src/basic-components, changing a component's props/variants, or when asked to document/update docs for a component.","argument-hint":"The component name and its source path, e.g. \"Select (packages/insomnia/src/basic-components/select.tsx)\""},"isInternal":false,"tokens":870,"sizeBytes":3664},{"name":"async-objects.md","path":".claude/skills/fix-scripting-feature/references/objects/async-objects.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/async-objects.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Async plumbing (`ProxiedPromise`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/async-objects.ts`\n\n## Purpose\nInternal plumbing (not part of the `pm`/`insomnia` script-facing API by name) that lets the sandbox\ntrack every `Promise` a user script creates, so it can drain/await fire-and-forget promises before\nhanding control back to the host app. This is what prevents an un-awaited `.then()` chain or a\nstray `setTimeout`-driven promise inside a script from silently continuing (or erroring) after the\nscript has already \"finished\".\n\n## Public API\n\n- `export const OriginalPromise = Promise;` — a stashed reference to the real native `Promise`\n  class, captured at module load before anything replaces the global `Promise`.\n- `export class ProxiedPromise<T> extends Promise<T>` — drop-in `Promise` replacement:\n  - `constructor(executor)` — behaves exactly like `Promise`, and additionally pushes `this` into\n    the module-level `scriptPromises` array if `monitoring` is `true`.\n  - `static override all(promises: Promise<any>[])` — delegates to `super.all(promises)`; pushes\n    the resulting promise into `scriptPromises` if `monitoring`.\n  - `static override allSettled(promises: Promise<any>[])` — delegates to `super.allSettled(promises)` only; does **not** push the result into `scriptPromises` (comment: \"promise will be counted in Promise.resolve\").\n  - `static any(_: Promise<any>[])` — **not actually overridden** (no `override` keyword, does not call `super.any`); always returns `super.reject(\"'super.any' not supported\")`. Comment notes `Promise.any` isn't supported for the ES2021 compile target.\n  - `static override race(promises: Promise<any>[])` — delegates to `super.race(promises)`; pushes the result if `monitoring`.\n  - `static override reject(value: any)` — delegates to `super.reject(value)`; pushes the result if `monitoring`.\n  - `static override resolve<T>(value?: T | PromiseLike<T>)` — delegates to `super.resolve(value)`; pushes the result if `monitoring`.\n  - `static withResolvers()` — always returns `super.reject(\"'Promise.withResolvers' not supported\")`; same ES2021 compile-target limitation noted in a comment.\n- `export const asyncTasksAllSettled = async () => { await Promise.allSettled(scriptPromises); scriptPromises = []; }` — awaits every currently-tracked promise, then clears the tracked list.\n- `export const stopMonitorAsyncTasks = () => { monitoring = false; }` — disables tracking of any *new* promises created after this call.\n- `export const resetAsyncTasks = async () => { scriptPromises = []; monitoring = true; }` — clears the tracked list and re-enables monitoring; called at the start of a fresh script run.\n\nModule-level (shared, not per-instance) state: `let monitoring = true;` and\n`let scriptPromises = new Array<Promise<any>>();`. Importing this module also calls\n`resetTestPromises()` (from `./test`) once, as a side effect at load time.\n\n## Script-facing surface\nNone of these names are called directly by a user script. However, the mechanism *is* in the\nscript-facing surface indirectly: `packages/insomnia/src/entry.hidden-window-preload.ts` replaces\nthe sandbox's global `Promise` with `ProxiedPromise`\n(`contextBridge.exposeInMainWorld('Promise', ProxiedPromise); window.Promise = ProxiedPromise;`),\nso `new Promise(...)`, `Promise.resolve(...)`, `Promise.all(...)`, etc. — anything that references\nthe (now-reassigned) global `Promise` binding — is transparently tracked. **`async function` is not\nin that list**: per spec, an async function's returned promise is created against the realm's\nintrinsic `%Promise%` (`NewPromiseCapability(%Promise%)`), not whatever the mutable global `Promise`\nbinding currently points to, so reassigning `window.Promise` does not make it a `ProxiedPromise`.\n`packages/insomnia/src/scripting/run-script.ts` injects\n`resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the executed script function\nas `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__`, calling `__bridgeReset__()` before the\nuser script body runs and `await __bridgeSettle__()` after it, right before returning the mutated\n`insomnia` object. This drains explicitly-tracked promises, but an un-awaited `pm.sendRequest(...)`\ncall or stray `.then()` chain is only guaranteed to resolve before the script's result is used if\nevery promise in its chain was created via a tracked constructor/helper — not guaranteed just\nbecause it originated from an `async function`.\n\n## Gotchas / notable behavior\n- `Promise.any(...)` and `Promise.withResolvers()` are **explicitly unsupported**: calling either\n  from a script returns a promise that rejects with the literal string `\"'super.any' not supported\"`\n  or `\"'Promise.withResolvers' not supported\"` (not a real `Error` object) — worth checking for if a\n  script's failure message looks like that.\n- `allSettled` results are deliberately *not* double-counted into `scriptPromises` — the comment\n  claims this is safe because \"promise will be counted in Promise.resolve\" (i.e. the engine's\n  internal `allSettled` implementation is assumed to route through `Promise.resolve` on the inputs,\n  which are already tracked). This is an implicit coupling to Promise/A+ implementation details.\n- `scriptPromises`/`monitoring` are **module-level singletons**, not scoped per script execution —\n  correctness across sequential script runs depends on `resetAsyncTasks()` (`__bridgeReset__`)\n  being called at the start of every run; if it's ever skipped, promises from a previous run could\n  leak into the next run's settle-and-drain step.\n- Constructing a promise while `monitoring === false` silently skips tracking — no error or warning is raised.\n- Promises returned by an `async function` are **not guaranteed to be added to `scriptPromises`**,\n  even though `window.Promise`/`Promise` was replaced with `ProxiedPromise` — the JS engine builds\n  an async function's result promise from the realm's intrinsic `%Promise%`, bypassing the\n  reassigned global binding entirely. `send-request.ts`'s exported `sendRequest` is itself an\n  `async function` that internally does `return new Promise(async (resolve, reject) => {...})`; the\n  inner explicit `new Promise(...)` is tracked, but the promise actually returned to the caller of\n  `sendRequest(...)` (i.e. what `pm.sendRequest()` hands back to a script when not passed a callback)\n  is not. Symptom: a script that fires `pm.sendRequest(...)` without `await`ing it (or awaiting a\n  `.then()` derived from it) may see the request silently abandoned or racing past\n  `asyncTasksAllSettled()`'s drain, instead of being reliably completed first.\n\n## Related\n- `test.ts` — `resetTestPromises()` is called once at this module's import time; test assertion promises are tracked separately from `scriptPromises`.\n- `packages/insomnia/src/scripting/run-script.ts` — wires `resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the sandboxed script's `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__` calls.\n- `packages/insomnia/src/entry.hidden-window-preload.ts` — replaces the sandbox's global `Promise` with `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` — prepares the broader sandbox execution context that this async tracking runs inside.\n  ","isInternal":false,"tokens":1753,"sizeBytes":7318},{"name":"auth.md","path":".claude/skills/fix-scripting-feature/references/objects/auth.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/auth.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# RequestAuth\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/auth.ts`\n\n## Purpose\nModels the authentication settings attached to a request (`pm.request.auth`). It stores auth options per-type in a map (so switching `type` doesn't destroy other types' saved options), and provides the two-way transform functions used to convert between Insomnia's native `RequestAuthentication` model and the script-facing `AuthOptions` shape. `RequestAuth` extends `Property` (see `properties.ts`).\n\n## Public API\n\n### Types\n- `type AuthOptionTypes = 'noauth' | 'basic' | 'bearer' | 'jwt' | 'digest' | 'oauth1' | 'oauth2' | 'hawk' | 'awsv4' | 'ntlm' | 'apikey' | 'edgegrid' | 'asap' | 'netrc'` — the full set of auth type strings the SDK models are aware of, but not all are reachable from scripts (see Gotchas).\n- `const AuthTypes: Set<string>` — a `Set` containing the same string values as `AuthOptionTypes`, used by `RequestAuth.isValidType`.\n- `interface AuthOption { key: string; value: string; type?: string }` — a single key/value auth parameter (e.g. `{ key: 'username', value: 'user1' }`).\n- `interface OAuth2AuthOption { key: string; value: string | OAuth2Param[]; type?: string }` — like `AuthOption` but OAuth2 params can themselves be arrays of `OAuth2Param` (used for nested `tokenRequestParams` etc.).\n- `interface OAuth2Param { key: string; value: string; enabled: boolean; send_as: string }`.\n- Per-auth-type option interfaces (shape of the fields you'd set for `.use()`/`.update()` when constructing raw options rather than key/value arrays): `BasicOptions`, `BearerOptions`, `JWTOptions`, `DigestOptions`, `OAuth1Options`, `OAuth2Options`, `HAWKOptions`, `AWSV4Options`, `NTLMOptions`, `APIKeyOptions`, `EdgegridOptions`, `ASAPOptions`. Each mirrors the field names used by the corresponding Insomnia auth UI (e.g. `OAuth1Options` has `consumerKey`, `consumerSecret`, `signatureMethod`, etc.) and all have an optional `id?: string`.\n- `interface AuthOptions { type: AuthOptionTypes; basic?: AuthOption[]; bearer?: AuthOption[]; jwt?: AuthOption[]; digest?: AuthOption[]; oauth1?: AuthOption[]; oauth2?: OAuth2AuthOption[]; hawk?: AuthOption[]; awsv4?: AuthOption[]; ntlm?: AuthOption[]; apikey?: AuthOption[]; edgegrid?: AuthOption[]; asap?: AuthOption[] }` — the JSON shape used to construct a `RequestAuth` and returned by `toJSON()`/`toPreRequestAuth()`. Each type's key holds an array of `AuthOption` (or `OAuth2AuthOption` for oauth2).\n\n### Functions\n- `authOptionsToParams(authMethod: BasicOptions | BearerOptions | ... | ASAPOptions)` — converts a flat options object (e.g. `{ username, password }`) into an array of `{ type: 'any', key, value }` entries. Not used internally by `RequestAuth`; appears to be a helper for callers building `AuthOption[]` from a typed options object.\n- `fromPreRequestAuth(auth: RequestAuth): RequestAuthentication` — converts a script-side `RequestAuth` into Insomnia's native `RequestAuthentication` model (used when a pre-request script finishes and Insomnia needs to write the mutated auth back onto the real request). Calls `auth.toJSON()` then switches on `type`.\n- `toPreRequestAuth(auth: RequestAuthentication | {}): AuthOptions` — the reverse transform: converts Insomnia's native auth model into the `AuthOptions` shape used to construct the script's `RequestAuth` (called when initializing `pm.request.auth` before a script runs).\n\n### `class RequestAuth extends Property`\n- `constructor(options: AuthOptions, parent?: Property)` — throws `Error('invalid auth type ${options.type}')` if `options.type` isn't a recognized type. Populates an internal `Map<string, VariableList<Variable>>` (`authOptions`) with one entry per auth-type key present in `options` (so if you pass an `AuthOptions` with only `type: 'basic'` and `basic: [...]`, only the `'basic'` entry is populated — other types are simply absent from the map, not defaulted).\n- `static isValidType(authType: string): boolean` — checks membership in `AuthTypes`.\n- `clear(type: string): void` — removes the stored options for `type` from the internal map (no-op, silently, if `type` is invalid).\n- `parameters(): VariableList<Variable> | undefined` — returns the `VariableList` of options for the *currently active* `type` (i.e. whatever `this.type` is set to), or `undefined` if nothing is stored for that type.\n- `toJSON(): AuthOptions` — serializes back to the `AuthOptions` shape. For `type === 'noauth'` or `'netrc'` it returns just `{ type }` with no options array. Otherwise returns `{ type, [type]: <array of {key, value} from the VariableList> }`.\n- `update(options: VariableList<Variable> | Variable[] | AuthOptions, type?: AuthOptionTypes): void` — replaces the options for `type` (or the current `this.type` if `type` omitted) and switches `this.type` to it. Throws `Error('no valid RequestAuth options is found')` if no variable list could be derived from `options`.\n- `use(type: AuthOptionTypes, options: VariableList<Variable> | Variable[] | AuthOptions): void` — same as `update` but `type` is required; throws `Error('invalid type (...)')` if `type` isn't in `AuthTypes`, and the same \"no valid options\" error as `update` otherwise. This is the method `Request.authorizeUsing()` calls to switch a request's auth type from a script.\n\n### Internal helper (not exported)\n- `rawOptionsToVariables(options, targetType?)` — normalizes the three accepted input shapes (`VariableList<Variable>`, `Variable[]`, or a full `AuthOptions` object) into `VariableList<Variable>[]`. Throws `Error('options is not valid: it must be VariableList<Variable> | Variable[] | object')` if none of the shapes match.\n\n## Script-facing surface\n- `pm.request.auth` is a `RequestAuth` instance (constructed in `request.ts` from `options.auth || { type: 'noauth' }`).\n- `pm.request.auth.parameters()` — read the currently active auth's key/value options.\n- `pm.request.auth.update(newOptions)` — mutate the current auth type's options in place.\n- `pm.request.authorizeUsing(type, options)` (on `Request`, in `request.ts`) delegates to `this.auth.use(type, options || { type: 'noauth' })` — this is the documented way scripts switch auth type.\n- `pm.request.auth.clear(type)` to drop a stored auth-type's options.\n- When a pre-request script finishes, `fromPreRequestAuth(request.auth)` converts the mutated `RequestAuth` back to Insomnia's native `RequestAuthentication`, which is what actually gets sent on the wire (see `request.ts`, `authentication: fromPreRequestAuth(updatedReq.auth)`).\n\n## Gotchas / notable behavior\n- **`netrc` is a dead end**: `AuthTypes` includes `'netrc'` and `RequestAuth` will happily accept `type: 'netrc'`, but `fromPreRequestAuth` throws `Error('netrc is not supported yet')` when it sees `type === 'netrc'`, and `toPreRequestAuth` throws `Error('netrc auth is not supported in scripting yet')` for the native `'netrc'` type. So a script that sets `type: 'netrc'` will fail only when Insomnia tries to convert it back, not immediately.\n- **`singleToken` is unsupported**: `toPreRequestAuth` throws for native auth type `'singleToken'` — there is no path from this native auth type into the script's `RequestAuth` at all.\n- **`edgegrid` has interfaces but no transform**: `EdgegridOptions` and `'edgegrid'` are defined in `AuthOptionTypes`/`AuthTypes`, but neither `fromPreRequestAuth` nor `toPreRequestAuth` has a `case 'edgegrid'` — passing this type through those functions falls into the `default` branch and throws `Error('unknown auth type: ...')`.\n- **oauth1 `signatureMethod` restrictions**: `fromPreRequestAuth`'s oauth1 branch only supports `HMAC-SHA1`, `HMAC-SHA256`, `RSA-SHA1`, and `PLAINTEXT`. `HMAC-SHA512`, `RSA-SHA256`, and `RSA-SHA512` are recognized strings but explicitly throw `Error('...unsupported signatureMethod type for oauth1: ...')`.\n- **oauth1 `privateKey` is a one-way/unsupported field**: comment in code says \"it is not supported in the script side\" — it's read but not written back into the native auth model in `toPreRequestAuth`.\n- **oauth2 has several fields marked \"not supported yet in the script side\"**: `tokenPrefix`, `responseType`, `origin` are passed through in `fromPreRequestAuth`/`toPreRequestAuth` for round-tripping but are not meaningfully used elsewhere per the inline comments.\n- **hawk loses several fields on the round trip to native Insomnia auth**: `timestamp`, `delegation`, `app`, `nonce`, `user` are commented as \"some keys are lost here\" in `fromPreRequestAuth`'s hawk branch, and `toPreRequestAuth`'s hawk branch hardcodes them back to empty string / `'false'` since \"these fields are not supported in Insomnia side\".\n- **Booleans are stored as strings internally**: all the `AuthOption`/`OAuth2AuthOption` values are `string`, so booleans like `disabled` are represented as `'true'`/`'false'` strings inside the `VariableList`, and compared with `=== 'true'` when converting back.\n- **Constructor does not default missing types**: if you construct `new RequestAuth({ type: 'bearer' })` (no `bearer` key), `parameters()` will return `undefined` immediately — there's no implicit empty array.\n- **`update`/`use` fully replace, not merge**: calling `update()`/`use()` replaces the entire options list for a type; it does not merge in partial fields.\n\n## Related\n- `properties.ts` — `Property` (base class) and `Variable`/`VariableList` container semantics used to store auth options.\n- `variables.ts` — `Variable`, `VariableList` (the underlying storage for each auth type's key/value pairs).\n- `request.ts` — constructs `pm.request.auth` from `AuthOptions`, exposes `authorizeUsing()`, and calls `fromPreRequestAuth` when finalizing the request.\n- `insomnia.ts` — calls `toPreRequestAuth(rawObj.request.authentication)` to seed `pm.request.auth` before a script runs.\n- `insomnia-data` package — source of the native `RequestAuthentication` and `OAuth2ResponseType` types that `fromPreRequestAuth`/`toPreRequestAuth` convert to/from.\n","isInternal":false,"tokens":2515,"sizeBytes":10002},{"name":"certificates.md","path":".claude/skills/fix-scripting-feature/references/objects/certificates.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/certificates.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Certificate\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/certificates.ts`\n\n## Purpose\nModels a single client (mTLS) certificate for the scripting environment — the script-facing representation surfaced as `pm.request.certificate`. It is deliberately simple: unlike Insomnia's native model (which supports a list of client certificates matched by host), the scripting SDK only ever models **one** certificate per request, matched via `UrlMatchPattern`s (from `urls.ts`).\n\n## Public API\n\n### Types\n- `interface SrcRef { src: string }` — a file-path reference; used for `key`, `cert`, and `pfx` since certificate content is loaded from disk, not embedded inline.\n- `interface CertificateOptions { name?: string; matches?: string[]; key?: SrcRef; cert?: SrcRef; passphrase?: string; pfx?: SrcRef; disabled?: boolean }` — constructor/`update()` input. `pfx` is documented as \"PFX or PKCS12 Certificate\".\n\n### `class Certificate extends Property`\n- `override _kind = 'Certificate'`\n- `override name?: string`\n- `matches?: UrlMatchPatternList<UrlMatchPattern>` — constructed internally from the `matches: string[]` option; each string becomes a `UrlMatchPattern`.\n- `key?: SrcRef`\n- `cert?: SrcRef`\n- `passphrase?: string`\n- `pfx?: SrcRef`\n- `constructor(options: CertificateOptions)` — sets all fields directly from `options`, including `this.disabled = options.disabled` (inherited `Property.disabled` field). `matches` defaults to an empty `UrlMatchPatternList` if `options.matches` is not provided.\n- `static isCertificate(obj: object): boolean` — checks `obj._kind === 'Certificate'`.\n- `canApplyTo(url: string): boolean` — returns `this.matches ? this.matches.test(url) : false`, i.e. whether any of the certificate's match patterns matches the given URL. Delegates to `UrlMatchPatternList.test()`.\n- `update(options: CertificateOptions): void` — fully overwrites `name`, `matches` (rebuilt the same way as in the constructor), `key`, `cert`, `passphrase`, `pfx`. Note: **does not** update `disabled` (unlike the constructor, which does set it) — see Gotchas.\n\n## Script-facing surface\n- `pm.request.certificate` is a `Certificate` instance. Per `insomnia.ts`, it's initialized either as an empty placeholder certificate (`name: 'Default Certificate'`, no `key`/`cert`/`pfx`) when the request URL contains unrendered template tags or `filterClientCertificates` returns zero matches, or as `{ name: 'The first matched certificate from Settings', matches: [matchedCertificates[0].host], key: {src: ...}, cert: {src: ...}, passphrase, pfx: {src: ...} }` — built from `matchedCertificates[0]` whenever the match list is **non-empty**, i.e. one or more matches, not only when there's exactly one. If more than one client certificate matches, everything past the first is silently dropped rather than surfaced as the empty-certificate path — a direct consequence of `Certificate` only ever modeling a single certificate (see Gotchas below).\n- Scripts can read `pm.request.certificate.key`, `.cert`, `.pfx`, `.passphrase`, `.name`, call `.canApplyTo(url)`, or call `.update({...})` to replace the certificate used for the request. Per `request.ts`, `Request.certificate` is a `Certificate` built the same way (`options.certificate ? new Certificate(options.certificate) : undefined`), and the certificate is serialized back out (`toJSON()`-style plain object with `name`, `matches` stringified, `key`, `cert`, `passphrase`, `pfx`) when the script finishes so Insomnia can merge it into the request's actual client-certificate list via `mergeClientCertificates` (in `request.ts`).\n\n## Gotchas / notable behavior\n- **Only one certificate is modeled, even though Insomnia supports several.** `request.ts`'s comment: \"Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones.\" When a script sets a certificate, `mergeClientCertificates` prepends it as a new entry ahead of the request's existing native client certificates rather than replacing the whole list.\n- **`update()` does not update `disabled`.** The constructor sets `this.disabled = options.disabled`, but `update()` has no such line — calling `cert.update({ disabled: true, ... })` will not actually change `cert.disabled`. This looks like an inconsistency/bug relative to the constructor's behavior.\n- **`cert+key` and `pfx` are mutually exclusive downstream.** `request.ts`'s `mergeClientCertificates` throws `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if a script sets both a PFX and a cert/key pair on `pm.request.certificate`.\n- **Empty/placeholder certificate is normal, not an error state.** If the request URL contains a template tag (e.g. `{{ baseUrl }}`) or there's no matching client certificate configured in Settings, `pm.request.certificate` is initialized to an empty certificate and `insomnia.ts` logs this via `getExistingConsole().warn(...)` — a warning-level timeline entry, not debug — so it's visible when tracing why the default/empty certificate was selected. Scripts should not assume `key`/`cert`/`pfx` are populated.\n- **`matches` is derived only from certificate `host`, singular** — even though `UrlMatchPatternList` supports multiple patterns, `insomnia.ts` only ever seeds `matches: [matchedCertificates[0].host]` (a single-element array) from the native model.\n\n## Related\n- `properties.ts` — `Property` base class (provides `id`, `name`, `disabled`, `toJSON()`/`toString()` defaults).\n- `urls.ts` — `UrlMatchPattern`, `UrlMatchPatternList` (used for `matches` and `canApplyTo()`).\n- `request.ts` — constructs/serializes `pm.request.certificate`, and contains `mergeClientCertificates()` which reconciles the script's single certificate back into Insomnia's native multi-certificate list.\n- `insomnia.ts` — seeds the initial `pm.request.certificate` value from Insomnia Settings' configured client certificates, using `filterClientCertificates` from `insomnia/src/network/certificate`.\n- `insomnia-data` package — source of the native `ClientCertificate` type.\n","isInternal":false,"tokens":1428,"sizeBytes":6110},{"name":"collection.md","path":".claude/skills/fix-scripting-feature/references/objects/collection.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/collection.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# collection.ts (re-export barrel — no `Collection` class)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/collection.ts`\n\n## Purpose\n**This file does not define a `Collection` class, and has no logic of its own.** Its entire content is a JSDoc `@module` comment plus eleven `export { ... } from '...'` re-export statements:\n```ts\nexport { RequestAuth } from './auth';\nexport { Certificate } from './certificates';\nexport { Cookie, CookieList } from './cookies';\nexport { Header, HeaderList } from './headers';\nexport { Property, PropertyBase, PropertyList } from './properties';\nexport { ProxyConfig, ProxyConfigList } from './proxy-configs';\nexport { FormParam, Request, RequestBody } from './request';\nexport { Response } from './response';\nexport { QueryParam, Url, UrlMatchPattern, UrlMatchPatternList } from './urls';\nexport { Variable, VariableList } from './variables';\nexport { Folder } from './folders';\n```\nIt exists purely so that `index.ts` can do `export * as Collection from './collection'`, giving SDK/type consumers a `Collection.Property`, `Collection.Variable`, `Collection.Folder`, etc. namespace (mirroring the Postman `postman-collection` package's `Collection.*` type namespace for compatibility/familiarity). There is no runtime behavior, no constructor, and nothing related to collection-scoped *variable storage* in this file.\n\n## Public API\nNo classes/functions of its own. It re-exports (unchanged) the following, whose real definitions and docs live in their own files:\n- `RequestAuth` (`auth.ts`)\n- `Certificate` (`certificates.ts`)\n- `Cookie`, `CookieList` (`cookies.ts`)\n- `Header`, `HeaderList` (`headers.ts`)\n- `Property`, `PropertyBase`, `PropertyList` (`properties.ts` — see `properties.md`)\n- `ProxyConfig`, `ProxyConfigList` (`proxy-configs.ts`)\n- `FormParam`, `Request`, `RequestBody` (`request.ts`)\n- `Response` (`response.ts`)\n- `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` (`urls.ts`)\n- `Variable`, `VariableList` (`variables.ts` — see `variables.md`)\n- `Folder` (`folders.ts` — see `folders.md`)\n\nNote this list is nearly identical to `index.ts`'s own top-level JSDoc module comment (both files carry the same doc block), but `index.ts` itself re-exports far more (all of `environments.ts`, `insomnia.ts`, `test.ts`, `execution.ts`, etc.) in addition to wrapping this file as the `Collection` namespace.\n\n## Script-facing surface\nNo direct script-facing surface of its own. **If you are looking for how `pm.collectionVariables` actually works** (collection-level variable storage and resolution), that logic lives in:\n- `environments.ts` — the `Environment` class backing the actual store.\n- `insomnia.ts` — `InsomniaObject` explicitly maps `this.collectionVariables = this.baseEnvironment` (i.e. `pm.collectionVariables` **is** `pm.baseEnvironment`, the same `Environment` instance, not a separate object) and constructs the `Variables` hierarchy (`collectionVars: baseEnvironment`) used by `pm.variables`.\nSee `environments.md` for the full precedence/resolution behavior.\n\n## Gotchas / notable behavior\n- **Naming trap**: a file named `collection.ts` in a scripting/variable-resolution context strongly suggests a `Collection` class with collection-variable logic — it has none. Anyone debugging \"collection variables\" behavior should look at `environments.ts` (`Environment`) and `insomnia.ts` (`collectionVariables = baseEnvironment` aliasing), not this file.\n- Diffed byte-for-byte against `index.ts`'s doc comment: the JSDoc header text is duplicated between the two files, but the actual export lists differ — `collection.ts` only re-exports a subset (the \"Postman-collection-shaped\" types), while `index.ts` re-exports everything including `insomnia.ts`, `environments.ts`, `console.ts`, `execution.ts`, `test.ts`, `async-objects.ts`, `request-info.ts`.\n- `index.ts` wraps this file's exports under a namespace: `export * as Collection from './collection';` — so external consumers see `Collection.Variable`, `Collection.Folder`, etc., but that `Collection` namespace object is not related to `pm.collectionVariables` at runtime; it's purely a type/value re-export grouping.\n\n## Related\n- `index.ts` — re-exports this file's contents as the `Collection` namespace (`export * as Collection from './collection'`).\n- `environments.ts` — actual implementation backing `pm.collectionVariables`/`pm.baseEnvironment` (see `environments.md`).\n- `insomnia.ts` — wires `collectionVariables` to `baseEnvironment` and builds the `Variables` hierarchy.\n- `properties.ts`, `variables.ts`, `folders.ts`, `urls.ts`, `request.ts`, `response.ts`, `cookies.ts`, `headers.ts`, `auth.ts`, `certificates.ts`, `proxy-configs.ts` — the actual modules whose types this file re-exports.\n","isInternal":false,"tokens":1112,"sizeBytes":4774},{"name":"console.md","path":".claude/skills/fix-scripting-feature/references/objects/console.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/console.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Console\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/console.ts`\n\n## Purpose\nImplements a sandbox-safe replacement for the global `console` object used inside pre-request/after-response/test scripts. Instead of writing to a real stdout/devtools console, every call is captured into an in-memory row buffer that the execution harness later dumps and surfaces back to the Insomnia app (e.g. the response timeline).\n\n## Public API\n\n### `type LogLevel`\n```ts\ntype LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error';\n```\nNot exported — internal tag for which console method produced a row.\n\n### `interface Row`\n```ts\nexport interface Row {\n  value: string;\n  name: string;\n  timestamp: number;\n}\n```\n- `value` — the rendered `\"<level>: <content>\"` string.\n- `name` — always `'Text'` in current usage.\n- `timestamp` — `Date.now()` at log time.\n\n### `class Console`\nNo explicit constructor (uses field initializers); instantiated with `new Console()`.\n\n- `rows: Row[]` — accumulated log entries, in call order.\n- `printLog = (rows: Row[], level: LogLevel, ...values: any) => void` — core formatter (marked `@ignore`, not really meant to be called directly by users). Maps each value to a string (`typeof value === 'string' ? value : JSON.stringify(value, null, 2)`), joins with `' '`, and pushes `{ value: `${level}: ${content}`, name: 'Text', timestamp: Date.now() }` onto `rows`. If formatting throws, it instead pushes a row whose `value` is `'error: ' + JSON.stringify(e, null, 2)`.\n- `log = (...values: any[]) => void` — calls `printLog(this.rows, 'log', ...values)`.\n- `warn = (...values: any[]) => void` — calls `printLog(this.rows, 'warn', ...values)`.\n- `debug = (...values: any[]) => void` — calls `printLog(this.rows, 'debug', ...values)`.\n- `info = (...values: any[]) => void` — calls `printLog(this.rows, 'info', ...values)`.\n- `error = (...values: any[]) => void` — calls `printLog(this.rows, 'error', ...values)`.\n- `clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => void` — **always throws** `new Error('currently \"clear\" is not supported for the timeline')`. Calling `console.clear()` in a script will throw.\n- `dumpLogs = () => string` — returns all rows serialized as `JSON.stringify(row) + '\\n'`, joined with `'\\n'` (i.e. one JSON blob per row, double-newline separated).\n- `dumpLogsAsArray = () => string[]` — same per-row serialization (`JSON.stringify(row) + '\\n'`) but returned as an array instead of one joined string.\n\n### Module-level singleton helpers\n- `let builtInConsole = new Console();` (module-private).\n- `getExistingConsole(): Console` — returns the current singleton without creating a new one.\n- `getNewConsole(): Console` — replaces the singleton with a fresh `new Console()` and returns it (used to reset log state between script executions).\n\n## Script-facing surface\n- `console.log(...)`, `console.warn(...)`, `console.debug(...)`, `console.info(...)`, `console.error(...)` — available as the script's global `console` inside pre-request/after-response/test scripts (the sandbox injects a `Console` instance as the `console` parameter, not the real JS console).\n- `console.clear()` — present but always throws if called from a script.\n\n## Gotchas / notable behavior\n- **`clear()` is a hard error, not a no-op.** Any script calling `console.clear()` will throw `Error('currently \"clear\" is not supported for the timeline')`, which will surface as a script failure.\n- **Object logging uses `JSON.stringify(value, null, 2)`**, not `util.inspect`-style formatting — circular references or values with `toJSON()` quirks will affect what shows up. If `JSON.stringify` itself throws (e.g. circular reference), the whole `printLog` call falls back to a single `'error: ' + JSON.stringify(e, null, 2)` row rather than losing the log entirely — but the original values are lost in that fallback.\n- **No format-string substitution (`%s`, `%d`, etc.)** — the `// TODO: support replacing substitution` comment confirms this isn't implemented; values are just stringified and space-joined.\n- **State is a module-level singleton**, not per-instance-only: `getExistingConsole()`/`getNewConsole()` share one `builtInConsole` variable across the module. The harness must call `getNewConsole()` before each script run to avoid leaking log rows from a previous execution into the next one's dump.\n- **Two different dump shapes exist** (`dumpLogs()` joined string vs. `dumpLogsAsArray()` array of strings) — different call sites in the harness use one or the other (see Related), so a log line \"disappearing\" could be a symptom of reading from the wrong dump method or a stale singleton reference.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — imports `getExistingConsole` and uses it only for a one-off warning log during `initInsomniaObject` (an empty-certificate warning); it is not the general per-script logging path.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) — calls `getNewConsole()` to obtain a fresh `Console` instance for each script run and passes `scriptConsole.log` into `initInsomniaObject(sandboxContext, scriptConsole.log)`.\n- `packages/insomnia/src/scripting/run-script.ts` — injects the `Console` instance itself as the sandboxed script's `console` global (part of the generated function's parameter list), and at the end calls `scriptConsole.dumpLogsAsArray()`, including the result (`logs`) in the returned `RequestContext`.\n- Legacy path `packages/insomnia/src/script-executor.ts` — calls `scriptConsole.dumpLogs()` (joined-string form) and appends it to a timeline file via `fs.promises.appendFile`, which is how logs end up in Insomnia's response timeline view in that code path.\n","isInternal":false,"tokens":1397,"sizeBytes":5779},{"name":"cookies.md","path":".claude/skills/fix-scripting-feature/references/objects/cookies.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/cookies.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Cookie, CookieJar, CookieList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/cookies.ts`\n\n## Purpose\nModels HTTP cookies for the scripting environment. `Cookie` wraps a `tough-cookie` cookie plus Insomnia-specific extension fields; `CookieList` is a keyed `PropertyList<Cookie>`; `CookieObject` (a `CookieList` subclass) is the concrete class backing `pm.cookies` and pairs the list with a `CookieJar`; `CookieJar` is a custom (non-tough-cookie) jar keyed by domain used for `pm.cookies.jar()`. `mergeCookieJar` reconciles a script-produced jar back into Insomnia's native `CookieJar` model.\n\n## Public API\n\n### Types\n- `interface InsomniaCookieExtensions { creation?: Date; creationIndex?: number; lastAccessed?: Date; pathIsDefault?: boolean }` — Insomnia-only metadata carried alongside the standard cookie fields.\n- `interface CookieOptions extends InsomniaCookieExtensions { id?: string; key: string; value: string; expires?: Date | string | null; maxAge?: number | 'Infinity' | '-Infinity'; domain?: string; path?: string; secure?: boolean; httpOnly?: boolean; hostOnly?: boolean; session?: boolean; extensions?: { key: string; value: string }[] }` — constructor input for `Cookie`.\n\n### `class Cookie extends Property`\n- `constructor(cookieDef: CookieOptions | string)` — if given a string, parses it via `Cookie.parse()` first (throws `Error('failed to parse cookie, the cookie string seems invalid')` if parsing fails or the tough-cookie construction fails). Wraps the result in a `tough-cookie` `Cookie` (`ToughCookie.fromJSON`). `extensions` are stored separately from the tough-cookie object (tough-cookie only handles `string[]` extensions natively).\n- `static override _index = 'key'` — `PropertyList.one()`/`indexOf()` look items up by `key` for cookie lists.\n- `static isCookie(obj: Property): boolean` — checks `obj._kind === 'Cookie'`.\n- `static parse(cookieStr: string): CookieOptions` — parses a `Set-Cookie`-style string (loose mode) via tough-cookie, pulls `HostOnly`/`Session` out of the extensions list into dedicated booleans, and converts remaining string extensions (`key=value` or bare flags, which become `{key, value: 'true'}`) into `{key, value}` objects. Throws if the string can't be parsed by tough-cookie.\n- `static stringify(cookie: Cookie): string` — alias for `cookie.toString()`.\n- `static unparseSingle(cookieOpt: CookieOptions): string` — constructs a `Cookie` from options and returns its string form.\n- `static unparse(cookies: Cookie[]): string` — joins each cookie's `toString()` with `'; '`.\n- `toString(): string` — tough-cookie's own `toString()` plus `; HostOnly`, `; Session`, and `; key=value` segments appended for any extensions/inso flags present.\n- `valueOf(): string` — returns just `cookie.value` (used by `PropertyList.one()` for lookups and by `toObject()`).\n- `get key(): string` — reads through to `this.cookie.toJSON().key`.\n- `toJSON()` — returns the full plain-object shape: `{ id, key, value, expires, maxAge, domain, path, secure, httpOnly, hostOnly, session, extensions, creation, creationIndex, lastAccessed, pathIsDefault }`. Note `expires` is coerced to `undefined` when tough-cookie reports `'Infinity'`.\n\n### `class CookieList extends PropertyList<Cookie>`\n- `constructor(cookies: Cookie[])` — always constructs with `typeClass = Cookie`, no `parent`.\n- `static isCookieList(obj: object): boolean`.\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, string | string[]>` — builds a plain `key -> value` map via `Object.create(null)` (so no prototype — dangerous keys like `__proto__`/`constructor` land as own properties, not pollute the prototype). `excludeDisabled` skips cookies where `cookie.disabled` is true. `multiValue` collects same-key cookies into an array instead of overwriting (last value wins when `multiValue` is falsy).\n\n### `class CookieObject extends CookieList`\nThis is the concrete class instantiated as `pm.cookies`.\n- `constructor(cookieJar: InsomniaCookieJar | null)` — maps each native Insomnia cookie into a script `Cookie` (translating numeric `expires` into a `Date`, and passing through `maxAge: undefined`/`session: undefined`/`extensions: undefined` since those fields aren't tracked by Insomnia's native cookie model). Also builds an internal `CookieJar` (named from `cookieJar.name`, or `''`/empty if `cookieJar` is `null`).\n- `jar(): CookieJar` — returns the internal `CookieJar` instance backing `pm.cookies.jar()`.\n\n### `class CookieJar` (custom, not `tough-cookie`'s jar)\nComment: \"CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain as it reads location from the browser window, it is 'localhost'\". Internally a `Map<domain, Map<cookieKey, Cookie>>`.\n- `constructor(jarName: string, cookies?: Cookie[])` — indexes each cookie by its `domain` (read via `cookie.toJSON().domain`). If a cookie has no `domain`, it's **dropped** with a console warning: `` `domain is not specified for the cookie \"${cookie.key}\" so it is omitted` `` (via `getExistingConsole().warn`).\n- `set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void): void` — `url` is actually used as the domain key (not parsed as a URL). If `value` is a plain string, constructs a minimal `Cookie({ key, value, domain: url })`; otherwise treats `value` as full `CookieOptions` and constructs `new Cookie(value)`. Always calls `cb(undefined, cookie)` — this implementation never produces an error.\n- `get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void): void` — looks up `name` within the `url` domain bucket; calls back with `undefined` cookie if not found (not an error).\n- `getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void): void` — returns all cookies for that domain bucket (empty array if none).\n- `unset(url: string, name: string, cb: (error?: Error | null) => void): void` — deletes `name` from the domain bucket if the bucket exists; always calls back with no error.\n- `clear(url: string, cb: (error?: Error | null) => void): void` — deletes the entire domain bucket.\n- `toInsomniaCookieJar(): { name: string; cookies: Partial<InsomniaCookie>[] }` — flattens the map back into Insomnia's native cookie-jar shape. `expires` is defaulted back to `'Infinity'` if falsy (\"avoid edge cases\").\n\n### Module-level function\n- `mergeCookieJar(originalCookieJar: InsomniaCookieJar, updatedCookieJar: { name: string; cookies: Partial<InsomniaCookie>[] }): InsomniaCookieJar` — assigns a fresh `uuidv4()` id to any cookie missing one (mirroring the id-generation approach in Insomnia's `cookie-list.tsx`), and returns `{ ...originalCookieJar, cookies: cookiesWithId }`. This is the function that reconciles a script's mutated jar back into Insomnia's persisted model after a script runs.\n\n## Script-facing surface\n- `pm.cookies` is a `CookieObject` (constructed in `insomnia.ts` via `new CookieObject(rawObj.cookieJar)`), so scripts get all `CookieList`/`PropertyList` methods directly: `pm.cookies.get(key)` / `pm.cookies.one(key)` (via inherited `PropertyList.get`/`one`, which for `Cookie` returns `cookie.valueOf()` — i.e. just the string value, not the `Cookie` object, because `Cookie.valueOf` is defined), `pm.cookies.toObject()`, `pm.cookies.all()`, `pm.cookies.count()`, `pm.cookies.each()`, `pm.cookies.add()`, etc.\n- `pm.cookies.jar()` returns the `CookieJar`, giving access to `set(url, key, value, cb)`, `get(url, name, cb)`, `getAll(url, cb)`, `unset(url, name, cb)`, `clear(url, cb)` — all Postman-style callback-based cookie-jar operations, scoped by domain (the `url` argument is treated as a plain domain string, not parsed).\n- After a script runs, Insomnia calls `this.cookies.jar().toInsomniaCookieJar()` (see `insomnia.ts`) to get the jar back out, which is then merged via `mergeCookieJar`.\n\n## Gotchas / notable behavior\n- **`PropertyList.one()`/`.get()` return the raw value, not a `Cookie`**: because `Cookie` defines `valueOf()`, and `PropertyList.one()` special-cases items with a `valueOf` method — `pm.cookies.get('foo')` returns the cookie's string value, not the `Cookie` instance. To get the full object, use `pm.cookies.find(...)` or `pm.cookies.all()` instead.\n- **`toObject()` uses `Object.create(null)`** specifically to avoid prototype pollution when cookie keys are things like `__proto__` or `constructor` — confirmed by a dedicated test (`toObject does not pollute the prototype for dangerous cookie keys`).\n- **Cookies without a `domain` are silently dropped** when building a `CookieJar` (constructor only; a warning is logged, not thrown).\n- **`maxAge` and `session` are not persisted from Insomnia's native model**: `CookieObject`'s constructor always sets `maxAge: undefined` and `session: undefined` when converting from `InsomniaCookieJar`, with inline comments \"not supported in Insomnia\".\n- **`extensions` format from Insomnia is unknown** per an inline `TODO` — `CookieObject`'s constructor always passes `extensions: undefined`.\n- **`CookieJar.set()` never reports an error** — the callback signature allows an `Error`, but the implementation has no failure path (a malformed `CookieOptions` would throw inside `new Cookie(...)` before the callback is ever reached, rather than being passed to `cb`).\n- **Duplicate keys in `toObject()` collapse to the last value** unless `multiValue` is passed as `true`, in which case duplicates accumulate into an array.\n- **`Cookie.parse()` of a malformed string like `'=gingerale'`** still succeeds with `key: ''`, `value: 'gingerale'`, `expires: 'Infinity'` (per the test) — it does not throw for merely unusual input, only for input tough-cookie's parser rejects entirely.\n\n## Related\n- `properties.ts` — `Property` (base class for `Cookie`) and `PropertyList` (base class for `CookieList`).\n- `console.ts` — `getExistingConsole()`, used by `CookieJar`'s constructor to warn about domain-less cookies.\n- `insomnia.ts` — constructs `pm.cookies` as `new CookieObject(rawObj.cookieJar)` and reads back `this.cookies.jar().toInsomniaCookieJar()` after script execution.\n- `insomnia-data` package — source of the native `Cookie`/`CookieJar` types this file converts to/from.\n- `tough-cookie` (external) — underlying cookie parse/stringify implementation wrapped by `Cookie`.\n","isInternal":false,"tokens":2554,"sizeBytes":10426},{"name":"environments.md","path":".claude/skills/fix-scripting-feature/references/objects/environments.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/environments.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Environment / Variables / Vault\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/environments.ts`\n\n## Purpose\nThis file defines `Environment` (a single flat key-value store), `Variables` (a hierarchical read/write facade over several `Environment` scopes, resolving lookups in precedence order), and `Vault` (a locked-down `Environment` subclass for secrets). `InsomniaObject` (`insomnia.ts`) instantiates these and exposes them as `insomnia.environment`, `insomnia.globals`/`insomnia.baseGlobals` (private), `insomnia.collectionVariables` (aliased to `baseEnvironment`), `insomnia.variables`, and `insomnia.vault` — the core variable model behind pre-request/after-response/test scripts.\n\n## Public API\n\n### `class Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined)\n```\nInitializes `_name` and a `Map<string, boolean|number|string|null|undefined>` from `Object.entries(jsonObject || {})`.\n\n- `get name(): string` — returns `_name` (read-only getter; no setter).\n- `has = (variableName: string) => boolean` — `Map.has`.\n- `get = (variableName: string) => boolean|number|string|null|undefined` — `Map.get`; returns `undefined` if absent.\n- `set = (variableName: string, variableValue: boolean|number|string|undefined|null) => void` — sets the value; if `Number.isNaN(variableValue)` is true, logs a warning via `getExistingConsole().warn` and **does not** set the value.\n- `unset = (variableName: string) => void` — `Map.delete`.\n- `clear = () => void` — `Map.clear`.\n- `replaceIn = async (template: string | object) => Promise<string>` — coerces an object template via `.toString()`, throws `TypeError` if not a string/object, then renders it through `getInterpolator().render(template, this.toObject())`. Supports `{{$randomUUID}}` and `{{varName}}` placeholders.\n- `toObject = () => Record<string, any>` — `Object.fromEntries(this.kvs.entries())`.\n- `toJSON()` — returns `this.toObject()` (drives `JSON.stringify(environment)` serialization).\n\n### `function mergeFolderLevelVars(folderLevelVars: Environment[]): Environment` *(not exported, `@ignore`)*\nReduces an array of folder `Environment`s into one merged `Environment` named `'mergedFolderLevelVars'`, with **later folders in the array winning** on key collisions (`{ ...merged, ...folderLevelEnv.toObject() }`).\n\n### `class Variables`\n```ts\nconstructor(args: {\n  baseGlobalVars: Environment;\n  globalVars: Environment;\n  collectionVars: Environment;\n  environmentVars: Environment;\n  iterationDataVars: Environment;\n  folderLevelVars: Environment[];\n  localVars: Environment;\n})\n```\nStores each scope as a private `Environment` (or `Environment[]` for folder levels).\n\n- `has = (variableName: string) => boolean` — true if any of the 7 scopes has the variable (local, folder-level via `.some`, iterationData, environment, collection, global, baseGlobal — all evaluated eagerly, then OR'd).\n- `get = (variableName: string) => value` — builds an ordered array `[localVars, mergeFolderLevelVars(folderLevelVars), iterationDataVars, environmentVars, collectionVars, globalVars, baseGlobalVars]`, uses `.find(vars => vars.has(variableName))` to pick the **first scope (highest precedence) that has the key**, and returns `scope?.get(variableName)`.\n- `set = (variableName, variableValue) => void` — **always writes only to `localVars`** (same NaN-guard/warning as `Environment.set`). There is no way to write directly to environment/collection/global scope through `Variables.set`.\n- `replaceIn = async (template: string | object) => Promise<string>` — same contract as `Environment.replaceIn`, but the context is `this.toObject()` (the fully merged view across all scopes).\n- `toObject = () => Record<string, any>` — maps `[baseGlobalVars, globalVars, collectionVars, environmentVars, iterationDataVars, mergeFolderLevelVars(folderLevelVars), localVars]` to plain objects and folds them left-to-right with `{...ctx, ...obj}`, so **later entries in that array win** — i.e. `localVars` has final say, matching the precedence order used by `get`.\n- `localVarsToObject = () => Record<string, any>` *(`@ignore`)* — returns just `localVars.toObject()`; used by `InsomniaObject.toObject()` to serialize `insomnia.variables`.\n\n### `class Vault extends Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean)\n```\nCalls `super(name, jsonObject)`, then **returns a `Proxy(this, ...)`** (note: the constructor return value, not `this`, is what callers get). The proxy's `get`/`set` traps throw `new Error('Vault is disabled in script')` for **every property/method access** (including inherited `Environment` methods like `get`/`has`) whenever `enableVaultInScripts` is falsy.\n- `unset = () => { throw new Error('Vault can not be unset in script'); }` — overridden to always throw, regardless of `enableVaultInScripts`.\n- `clear = () => { throw new Error('Vault can not be cleared in script'); }` — always throws.\n- `set = () => { throw new Error('Vault can not be set in script'); }` — always throws (vault is read-only from scripts even when enabled).\n- Inherited `get`, `has`, `toObject`, `toJSON`, `replaceIn`, `name` still work normally when `enableVaultInScripts` is true (subject to the proxy's guard, which only blocks when the flag is false).\n\n## Script-facing surface\n- `insomnia.environment` → an `Environment` instance (the selected sub-environment, or the base environment itself if none is selected — see `initInsomniaObject` in `insomnia.ts`). Scripts call `insomnia.environment.get/set/unset/has/clear/replaceIn/toObject`.\n- `insomnia.baseEnvironment` → the collection's base `Environment`.\n- `insomnia.collectionVariables` → in `insomnia.ts`, this is literally assigned `this.baseEnvironment` (`this.collectionVariables = this.baseEnvironment;`) — it is not a distinct store, it's the *same* `Environment` object as `insomnia.baseEnvironment`.\n- `insomnia.variables` → a `Variables` instance built from all scopes (`insomnia.ts`'s `initInsomniaObject`: `baseGlobalVars: baseGlobals, globalVars: globals, environmentVars: environment, collectionVars: baseEnvironment, iterationDataVars: iterationData, folderLevelVars: parentFolders.getEnvironments(), localVars: localVariables`). Scripts use `insomnia.variables.get/set/has/replaceIn/toObject`. `insomnia.variables.set(...)` only ever affects the transient local scope.\n- `insomnia.vault` → a `Vault` instance; `insomnia.vault.get(<name>)` is the documented usage. Gated by the `enableVaultInScripts` setting.\n- `insomnia.globals`/`insomnia.baseGlobals` are declared `private` on `InsomniaObject`, but that's a TypeScript-only, compile-time restriction — `private` is erased at runtime, and `InsomniaObject`'s constructor returns `new Proxy(this, { get: (target, prop, receiver) => ... Reflect.get(target, prop, receiver) })` (see `insomnia.ts`), which forwards any property name through to the real field. A script can read `insomnia.globals`/`insomnia.baseGlobals` directly at runtime; they're just absent from the TS-declared public API, not actually inaccessible. Treat this as accidental exposure, not a supported surface — don't build fixes or features on scripts reading these, and any change here should aim to actually restrict access (e.g. a proxy guard like `Vault`'s) rather than expose them further.\n- `{{$randomUUID}}`-style and `{{variableName}}` templates in request URLs/bodies/headers are ultimately rendered through `Environment.replaceIn` / `Variables.replaceIn`.\n\n## Gotchas / notable behavior\n- **NaN guard**: both `Environment.set` and `Variables.set` silently refuse to store `NaN` and log a console warning instead of throwing — `null` and `undefined` are accepted just fine.\n- **Precedence order** (highest to lowest) used consistently by `Variables.has`, `.get`, and `.toObject`: local → folder-level (the **last** entry in the `folderLevelVars: Environment[]` array wins on collisions — see the merge-order gotcha below, not necessarily the nearest folder) → iteration data → environment → collection (`baseEnvironment`) → global → base global.\n- **Falsy values are not skipped**: `Variables.get` uses `.has()` to pick the scope, not truthiness, so a local value of `0`, `''`, or `false` is correctly returned instead of falling through to a lower-precedence scope (verified by a parameterized test in `environments.test.ts`).\n- **`collectionVariables` is an alias, not a separate object** — mutating `insomnia.collectionVariables` also mutates `insomnia.baseEnvironment` and vice versa, since they reference the same `Environment` instance.\n- **`Variables.set` cannot target non-local scopes** — there is no API on `Variables` to write into environment/collection/global; scripts must use `insomnia.environment.set(...)`, `insomnia.collectionVariables.set(...)`, etc. directly for those scopes.\n- **Folder-level merge order**: `mergeFolderLevelVars` merges the given `Environment[]` left-to-right with later entries winning; since `ParentFolders.getEnvironments()` returns folders \"from bottom to top\" is *not* guaranteed here — actual nearest-wins behavior depends on the order `folderLevelVars` is constructed in by the caller (see `folders.md`).\n- **`Vault` returns a `Proxy` from its constructor** — `new Vault(...)` does not yield a plain `Vault` instance; every property access goes through the proxy trap, so `enableVaultInScripts` is (re-)checked on *every* access, not just once at construction.\n- **`Vault` is effectively write-protected even when enabled**: `set`, `unset`, and `clear` are all overridden to unconditionally throw, so scripts can only ever read from the vault, never write to it, regardless of the `enableVaultInScripts` flag.\n- Deep/nested object values are not directly supported by `Environment`'s type signature (`boolean | number | string | null | undefined`); nothing in this file coerces or validates that at runtime, so anything else the caller passes is stored as-is.\n\n## Related\n- `properties.ts` — not used directly here, but `Variables`/`Environment` sit at the same conceptual layer that the `Variable`/`VariableList` classes (in `variables.ts`) also use for interpolation (via `getInterpolator()` in `interpolator.ts`).\n- `folders.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies the `folderLevelVars: Environment[]` array consumed by `Variables`.\n- `console.ts` — `getExistingConsole()` is used to emit the NaN warning.\n- `interpolator.ts` — `getInterpolator().render(...)` implements `{{...}}` template substitution for `replaceIn`.\n- `insomnia.ts` — constructs and wires up all the `Environment`/`Variables`/`Vault` instances exposed as `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`, `insomnia.vault`.\n","isInternal":false,"tokens":2549,"sizeBytes":10886},{"name":"execution.md","path":".claude/skills/fix-scripting-feature/references/objects/execution.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/execution.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Execution (`insomnia.execution` / `pm.execution`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/execution.ts`\n\n## Purpose\nModels `pm.execution`: where the currently-running request sits in the collection tree, plus two\ncollection-runner controls — skipping the request and rerouting to a different next request. This\nis one of the fields the collection runner reads back after a script finishes.\n\n## Public API\n\n### `interface ExecutionOption`\n```ts\ninterface ExecutionOption {\n  location: string[];\n  skipRequest?: boolean;\n  nextRequestIdOrName?: string;\n}\n```\nPlain-object shape used to construct an `Execution` (and the shape stored on `RequestContext.execution`, see `interfaces.md`).\n\n### `class Execution`\n```ts\nconstructor(options: ExecutionOption)\n```\n- `public location: string[]` — **not a plain array**: the constructor wraps a shallow copy\n  (`[...location]`) in a `Proxy` whose `get` trap adds a `current` accessor returning\n  `target.length > 0 ? target[target.length - 1] : ''` (the last path segment, or `''` if empty),\n  mirroring Postman's `execution.location.current` usage. All other property/index access passes\n  through via `Reflect.get`. Throws `TypeError('Location input must be array of string')` if\n  `options.location` is not an array.\n- `private _skipRequest: boolean` (default `false`), `private _nextRequestIdOrName: string` (default `''`) — no direct getters; only visible via `toObject()`.\n- `skipRequest = () => { this._skipRequest = true; }` — one-way flag setter; there is no method to un-set it.\n- `setNextRequest = (requestIdOrName: string) => { this._nextRequestIdOrName = requestIdOrName; }` — records which request the collection runner should execute next.\n- `toObject = () => ({ location: Array.from(this.location), skipRequest: this._skipRequest, nextRequestIdOrName: this._nextRequestIdOrName })` — serializes state to a plain object; `Array.from` strips the `Proxy`, so the resulting `location` array **loses the `.current` accessor**.\n\n## Script-facing surface\n- `insomnia.execution.location` — array of path segments (e.g. `['project', 'workspace', 'file', 'requestname']`).\n- `insomnia.execution.location.current` — last segment of the location path (Postman-compat).\n- `insomnia.execution.skipRequest()` — marks the current request to be skipped.\n- `insomnia.execution.setNextRequest(requestIdOrName)` — reroutes the collection runner to a specific request by id or name.\n\n## Gotchas / notable behavior\n- `location.current` only works on the live `Proxy`-wrapped array returned from the constructor;\n  once serialized via `toObject()` (which uses `Array.from`), the plain array no longer has\n  `.current`. This only matters for the runtime maintainers reading `toObject()`'s output — script\n  authors reading `insomnia.execution.location.current` directly are unaffected.\n- Passing a non-array `location` (e.g. a string) throws a `TypeError` synchronously from the\n  constructor — this is a `initInsomniaObject`/context-building bug, not something a script can\n  trigger directly, since scripts only read `insomnia.execution`, they don't construct it.\n- There's no public getter for `skipRequest`/`nextRequestIdOrName` state from the script side —\n  scripts can only set them, not read them back; only `toObject()` (used internally when the\n  runtime merges results into the persisted request context) exposes the current values.\n- Comment references Postman's docs on \"using variables in scripts\" for the `location.current`\n  design: https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/#using-variables-in-scripts\n\n## Related\n- `insomnia.ts` — `InsomniaObject.execution: Execution`; `toObject()` calls `execution.toObject()`.\n- `interfaces.ts` — `ExecutionOption` is embedded as `RequestContext.execution`.\n- `packages/insomnia/src/scripting/run-script.ts` — merges `mutatedContextObject.execution` (i.e. `Execution.toObject()`'s output) back into the returned `RequestContext` so the collection runner can act on `skipRequest`/`nextRequestIdOrName`.\n- `__tests__/execution.test.ts` — covers the `location`/`current` proxy behavior, `skipRequest()`/`setNextRequest()`, and the invalid-`location` throw.\n","isInternal":false,"tokens":1001,"sizeBytes":4255},{"name":"folders.md","path":".claude/skills/fix-scripting-feature/references/objects/folders.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/folders.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Folder / ParentFolders\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/folders.ts`\n\n## Purpose\nModels the folder hierarchy a request lives in, each folder carrying its own folder-level `Environment` of variables. `ParentFolders` is a container/lookup helper over an ordered array of `Folder`s, and its `getEnvironments()` is what feeds the `folderLevelVars: Environment[]` array consumed by `Variables` (`environments.ts`) — i.e. this is the source of folder-scoped variable resolution for `insomnia.variables`.\n\n## Public API\n\n### `class Folder`\n```ts\nconstructor(id: string, name: string, environmentObject: object | undefined)\n```\n- `id: string`\n- `name: string`\n- `environment: Environment` — constructed as `new Environment(`${id}.environment`, environmentObject)` (see `environments.md`).\n- `toObject = () => { id: string; name: string; environment: Record<string, any> }` — returns `id`, `name`, and `environment.toObject()`.\n\n### `class ParentFolders`\n```ts\nconstructor(private folders: Folder[])\n```\nPer the constructor JSDoc, `folders` is expected \"from bottom to top\" (nearest-ancestor folder first).\n\n- `get = (idOrName: string) => Folder` — finds the first folder whose `name` or `id` matches; **throws** `Error('Folder \"<idOrName>\" not found')` if none match.\n- `getById = (id: string) => Folder` — finds by `id` only; throws the same style of error if not found.\n- `getByName = (folderName: string) => Folder` — finds by `name` only; throws the same style of error if not found.\n- `findValue = (valueKey: string) => value | undefined` — reverses a **copy** of `this.folders` (`[...this.folders].reverse()`) and returns the first folder (after reversal) whose `environment.has(valueKey)`, then returns that folder's `environment.get(valueKey)`; returns `undefined` if no folder has it.\n- `toObject = () => object[]` — maps every folder to `folder.toObject()`, preserving the original (non-reversed) array order.\n- `getEnvironments = () => Environment[]` — maps every folder to `folder.environment`, preserving the original array order. This is the array handed to `Variables({ folderLevelVars: ... })`.\n\n## Script-facing surface\nNot directly exposed as a `pm.*` object itself — `ParentFolders` is internal plumbing constructed in `insomnia.ts`'s `initInsomniaObject`:\n```ts\nconst parentFolders = new ParentFolders(\n  rawObj.parentFolders.map(folderObj => new Folder(folderObj.id, folderObj.name, folderObj.environment)),\n);\n```\nand consumed two ways:\n- `parentFolders.getEnvironments()` → passed as `folderLevelVars` into `insomnia.variables` (`Variables`), so scripts reading `insomnia.variables.get('someFolderVar')` are indirectly reading through `Folder.environment`.\n- `parentFolders.toObject()` → included in `InsomniaObject.toObject()`'s `parentFolders` field (used for serialization/debugging, not typically read directly by user scripts).\nThere is no `insomnia.parentFolders` / `insomnia.folders` public property — `parentFolders` is a private field on `InsomniaObject`.\n\n## Gotchas / notable behavior\n- **The two folder-ordering JSDoc comments in this codebase appear to disagree, and `findValue` is not what scripts actually go through for `insomnia.variables`.** `ParentFolders`'s constructor doc says its `folders` array is ordered \"from bottom to top\" (i.e. index `0` = nearest/bottom, last index = topmost ancestor). `findValue`'s own doc says it searches \"starting from the nearest ancestor folder and moving towards the top ancestor folder.\" But the implementation does `[...this.folders].reverse().find(...)` — reversing a bottom-to-top array produces a top-to-bottom traversal, which is the *opposite* of what `findValue`'s own docstring claims. Treat `findValue`'s precedence as unverified/possibly inverted from its documented intent, and confirm against the actual call order at the construction site (`packages/insomnia/src/network/network.ts`, outside this SDK package) before relying on it to debug a specific precedence question. Note also that `findValue` is a standalone utility on `ParentFolders` — it is **not** what `insomnia.variables.get(...)` uses.\n- **`insomnia.variables.get(...)`'s folder precedence is governed by `mergeFolderLevelVars` in `environments.ts`, not by `ParentFolders.findValue`.** `mergeFolderLevelVars` merges the `folderLevelVars: Environment[]` array left-to-right with **later array entries winning** (`{...merged, ...folderLevelEnv.toObject()}`), and `ParentFolders.getEnvironments()` (used to build that array) preserves the original, non-reversed `this.folders` order. Per `environments.test.ts`'s `'variables operations'` test, `folders.getEnvironments()` is passed directly as `folderLevelVars`, and mutating the folder passed **second** to the `ParentFolders` constructor (`folder2`) is what wins in `variables.get('value')` — i.e. whichever folder appears later in the array given to `new ParentFolders([...])` wins ties in `insomnia.variables`, regardless of what \"nearest\"/\"topmost\" means in that particular caller's ordering convention.\n- `get`, `getById`, and `getByName` all throw plain `Error`s (not a custom error type) with a template string — safe to catch with a generic `try/catch` but there's no error code to switch on.\n- `Folder.environment`'s name is derived (`${id}.environment`), not the folder's own `name` — don't assume `folder.environment.name === folder.name`.\n\n## Related\n- `environments.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies `Variables`'s `folderLevelVars`, and `mergeFolderLevelVars` there is what actually implements the effective folder-variable precedence used by `insomnia.variables`.\n- `insomnia.ts` — constructs `ParentFolders` from `rawObj.parentFolders` and wires `getEnvironments()` into the `Variables` constructor; exposes `parentFolders.toObject()` via `InsomniaObject.toObject()`.\n- `index.ts` — re-exports `Folder` (but not `ParentFolders`) at the top level; `collection.ts` also re-exports `Folder`.\n","isInternal":false,"tokens":1426,"sizeBytes":6041},{"name":"headers.md","path":".claude/skills/fix-scripting-feature/references/objects/headers.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/headers.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Header, HeaderList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/headers.ts`\n\n## Purpose\n\nModels a single HTTP header (`Header`) and an ordered collection of headers (`HeaderList`). Used by `Request.headers` and `Response.headers` (`pm.request.headers` / `pm.response.headers`), and internally by `Request`'s header-merge logic. `HeaderList` is a thin specialization of the generic `PropertyList`.\n\n## Public API\n\n### `HeaderDefinition` (interface)\n`{ key: string; value: string; id?: string; name?: string; disabled?: boolean }`\n\n### `Header` (extends `Property`)\n- `override _kind = 'Header'`\n- `key: string`, `value: string`\n- `constructor(opts: HeaderDefinition | string, name?: string)` — if `opts` is a string, it's parsed via `Header.parseSingle` (only `key`/`value` are set — `id`/`name`/`disabled` come from `Property`'s defaults, not from the string). If `opts` is an object, `id`, `key`, `name` (overridden by the `name` param if given), `value`, and `disabled` are all read from it (each falling back to `''`/`false` if absent).\n- `static override _index = 'key'` — tells `PropertyList` to index/dedupe `Header`s by `key` (used by `one`, `indexOf`, `upsert`).\n- `static create(input?: {key, value} | string, name?: string): Header` — defaults `input` to `{key: '', value: ''}`.\n- `static isHeader(obj: object): boolean` — checks `_kind === 'Header'`.\n- `static parse(headerString: string): {key, value}[]` — splits on `\\n`, ignores blank lines, parses each via `parseSingle`.\n- `static parseSingle(headerStr: string): {key, value}` — splits on the **first** colon. **Throws** `Error('Header.parseSingle: the header string seems invalid')` if there's no colon or it's at position 0.\n- `static unparse(headers: {key,value}[] | PropertyList<Header>, separator?: string): string` — maps each header through `unparseSingle` and joins (default separator `'\\n'`).\n- `static unparseSingle(header: {key,value} | Header): string` — `\"key: value\"`.\n- `update(newHeader: {key, value})` — mutates `this.key`/`this.value` in place.\n- `override valueOf()` — returns `this.value`.\n\n### `HeaderList<T extends Header>` (extends `PropertyList<T>`)\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — internally always constructs the underlying `PropertyList` with `Header` as the type class (regardless of `T`).\n- `static isHeaderList(obj: any): boolean` — checks `_kind === 'HeaderList'`.\n- `contentSize(): number` — sum of `header.toString().length` (i.e. `\"key: value\"`.length) across all headers; comment notes special characters aren't handled.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, string | string[]>` — builds a plain key→value(s) map. `excludeDisabled` (default falsy) skips headers with `disabled` truthy. `multiValue` (default falsy) collects same-key headers into an array instead of overwriting with the last value. `_caseSensitive` and `_sanitizeKeys` are accepted but **unused**.\n\n## Script-facing surface\n\n`pm.request.headers` and `pm.response.headers` are `HeaderList<Header>` instances. Beyond the methods above, all generic `PropertyList` methods are available: `add`/`append`, `all()`, `assimilate()`, `clear()`, `count()`, `each()`, `filter()`, `find()`, `get(key)` / `one(key)` (keyed by `key` since `Header._index = 'key'`), `has()`, `idx()`, `indexOf()`, `insert()`/`insertAfter()`, `map()`, `populate()`, `prepend()`, `reduce()`, `remove()`, `repopulate()`, `toString()`, `upsert()`. Typical script usage: `pm.request.headers.add({key, value})`, `pm.request.headers.get('Content-Type')`, `pm.response.headers.has(...)`, `pm.request.headers.upsert(new Header({...}))`.\n\n## Gotchas / notable behavior\n\n- `new Header(stringOpts)` only populates `key`/`value` from the parsed string — `id`, `name`, and `disabled` are left at `Property`'s constructor defaults (`''`, `''`, `false`), never read from the string form.\n- `Header.parseSingle` throws on malformed input (no colon) — a raw header string with a typo can throw instead of silently producing an empty/garbage header.\n- `PropertyList.one(key)` (inherited, used for `HeaderList.get`) iterates **from the end of the list backwards**, so when duplicate keys exist, the **last-added** matching header wins — relevant when a script both sets a default header and later overrides it with the same key.\n- `HeaderList.toObject()`'s `_caseSensitive` and `_sanitizeKeys` parameters exist in the signature (for compatibility with Postman's original API shape) but have **no effect** — only `excludeDisabled` and `multiValue` are actually implemented.\n- `HeaderList`'s constructor ignores its own `T` type parameter internally — it always builds the base `PropertyList` using the concrete `Header` class, not whatever `T` was passed.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property` (base class for `Header`), `PropertyList` (base class for `HeaderList`).\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.headers: HeaderList<Header>`; also reuses `Header`/`HeaderList` in `addHeader`/`removeHeader`/`upsertHeader`/`getHeaders`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response.headers: HeaderList<Header>`; used in `contentInfo()` to find `Content-Type`/`Content-Disposition`.\n","isInternal":false,"tokens":1417,"sizeBytes":5429},{"name":"insomnia.md","path":".claude/skills/fix-scripting-feature/references/objects/insomnia.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/insomnia.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# InsomniaObject (`insomnia` / `pm`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/insomnia.ts`\n\n## Purpose\nThis is the top-level scripting object: the single instance bound into the sandbox as `insomnia`\n(with `$` as a Postman-compat alias — see Script-facing surface). It aggregates every other object\nin this module (environments, variables, request, response, cookies, execution, request info,\nvault, client certificates, test assertions) into one value that a pre-request / after-response /\ntest script reads and mutates, and that the runtime serializes back out via `toObject()` after the\nscript finishes.\n\n## Public API\n\n### `class InsomniaObject`\n\n```ts\nconstructor(rawObj: {\n  globals: Environment;\n  baseGlobals: Environment;\n  iterationData: Environment;\n  environment: Environment;\n  baseEnvironment: Environment;\n  variables: Variables;\n  request: ScriptRequest;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookies: CookieObject;\n  requestInfo: RequestInfo;\n  execution: Execution;\n  response?: ScriptResponse;\n  parentFolders: ParentFolders;\n  vault?: Vault;\n})\n```\n\nPublic/exposed properties:\n- `environment: Environment` — the currently-selected environment.\n- `collectionVariables: Environment` — **same object reference as `baseEnvironment`** (see Gotchas).\n- `baseEnvironment: Environment` — the base (root) environment.\n- `variables: Variables` — layered variable resolver (globals/env/collection/iteration/folder/local).\n- `request: ScriptRequest` — the request being built/sent (`pm.request`).\n- `cookies: CookieObject` — cookie jar wrapper (`pm.cookies`).\n- `info: RequestInfo` — event/iteration metadata (`pm.info`).\n- `response?: ScriptResponse` — present only in after-response/test scripts.\n- `execution: Execution` — location/skip/next-request control (`pm.execution`).\n- `vault?: Vault` — secret vault accessor, gated by `settings.enableVaultInScripts`.\n- `clientCertificates: ClientCertificate[]` — raw client certs available to the request.\n\nPrivate/internal properties (TypeScript-only — see Gotchas for why this doesn't restrict scripts at\nruntime): `_expect` (chai `expect`), `_test`/`_skip` (from `./test`), `iterationData: Environment`,\n`globals: Environment`, `baseGlobals: Environment` (marked\n`// TODO: follows will be enabled after Insomnia supports them`), `_settings: Settings`,\n`requestTestResults: RequestTestResult[]`, `parentFolders: ParentFolders`.\n\nMethods:\n- `sendRequest(request: string | ScriptRequest, cb: (error?: string, response?: ScriptResponse) => void)` — delegates to `sendRequest()` from `./send-request`, passing the internal `_settings`.\n- `test = () => {}` — a no-op placeholder property; the *actual* behavior is installed by the\n  constructor's `Proxy` (see below). Declaring this field exists mainly so `test` shows up as an\n  own property.\n- `expect = (exp: boolean | number | string | object) => this._expect(exp)` — thin pass-through to chai's `expect`.\n- `get settings()` — **always returns `undefined`** (see Gotchas).\n- `toObject = () => {...}` — serializes the whole object graph to a plain object: `globals`,\n  `baseGlobals`, `environment`, `baseEnvironment`, `iterationData` (all via each `Environment`'s\n  `toObject()`), `variables` (via `variables.localVarsToObject()`), `request`, `settings` (calls\n  `this.settings`, i.e. always `undefined`), `clientCertificates`, `cookieJar` (via\n  `cookies.jar().toInsomniaCookieJar()`), `info` (via `info.toObject()`), `response` (via\n  `response.toObject()` or `undefined`), `requestTestResults`, `execution` (via\n  `execution.toObject()`), `parentFolders` (via `parentFolders.toObject()`).\n\nThe constructor returns `new Proxy(this, { get: ... })`: any property access other than `'test'`\npasses straight through via `Reflect.get`. Accessing `.test` instead returns a freshly-built\n`TestHandler` function that calls `this._test(msg, fn, this.pushRequestTestResult)`, with a\n`.skip` method that calls `this._skip(msg, fn, this.pushRequestTestResult)`. `pushRequestTestResult`\nappends each `RequestTestResult` to the private `requestTestResults` array (immutably, via spread).\n\n### `async function initInsomniaObject(rawObj: RequestContext, log: (...args: any[]) => void): Promise<InsomniaObject>`\n\nFactory used by the script runner to build an `InsomniaObject` from a `RequestContext` snapshot\n(see `interfaces.md`). Responsibilities, in order:\n- Maps `globals`/`baseGlobals` — if the same environment id is selected for both, `globals` and\n  `baseGlobals` become the *same* `Environment` instance; otherwise separate instances are created.\n- Maps `environment`/`baseEnvironment` the same way; if no sub-environment is selected (ids equal),\n  logs a warning via `log(...)` that mutations to `insomnia.environment` will apply to the base\n  environment.\n- Builds `iterationData` and `transientVariables`-backed local variables `Environment`s (defaulting\n  to empty named environments when absent).\n- Builds a `Vault` from `rawObj.vault` (or an empty object), gated by\n  `rawObj.settings?.enableVaultInScripts` (defaults `false`).\n- Builds `CookieObject` from `rawObj.cookieJar`.\n- Builds `RequestInfo` from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- Builds `ParentFolders` from `rawObj.parentFolders`, then a `Variables` instance that layers\n  base-global/global/environment/collection/iteration-data/folder-level/local variables.\n- Resolves client certificates: uses `checkIfUrlIncludesTag` (from `./utils`) and\n  `filterClientCertificates` (from `insomnia/src/network/certificate`) against `rawObj.request.url`.\n  If the URL contains template tags or no certificate matches, initializes an **empty** default\n  certificate and logs a warning via `getExistingConsole().warn(...)`; otherwise uses the first\n  matched certificate.\n- Builds the request URL (`toUrlObject`), proxy options (`transformToSdkProxyOptions`,\n  `resolveProtocolForProxy`), adds query params, builds the auth via `toPreRequestAuth`, and\n  constructs a `ScriptRequest`.\n- Constructs an `Execution` from `rawObj.execution`.\n- Reads the response body via `readBodyFromPath` and builds a `ScriptResponse` via\n  `toScriptResponse` if `rawObj.response` is present.\n- Returns `new InsomniaObject({...})` with everything above.\n\n## Script-facing surface\n`InsomniaObject` *is* what a script sees as the global `insomnia` object (and `$`, a Postman-compat\nalias set up as `const $ = insomnia;` by the script wrapper in\n`packages/insomnia/src/scripting/run-script.ts`). Nearly every `pm.*`/`insomnia.*` call in a script\nroutes through this object's properties/methods directly:\n- `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`\n- `insomnia.request`, `insomnia.response`, `insomnia.cookies`, `insomnia.info`, `insomnia.execution`, `insomnia.vault`\n- `insomnia.test(name, fn)` / `insomnia.test.skip(name, fn)`\n- `insomnia.expect(value)`\n- `insomnia.sendRequest(request, callback)`\n\n`run-script.ts` requires the script's `insomnia` result to be `instanceof InsomniaObject` — if the\nuser script returns early or otherwise breaks that invariant, `runScript` throws\n`'insomnia object is invalid or script returns earlier than expected.'`. After the script runs,\n`InsomniaObject.toObject()` is used to merge mutations back into the persisted `RequestContext`.\n\n## Gotchas / notable behavior\n- **`collectionVariables` is not a copy** — `this.collectionVariables = this.baseEnvironment;` in\n  the constructor means `insomnia.collectionVariables` and `insomnia.baseEnvironment` are the exact\n  same `Environment` instance. Mutating one via a script mutates the other.\n- **`insomnia.settings` is always `undefined`** — the `get settings()` accessor unconditionally\n  `return;`s nothing, even though the real `Settings` object is held internally as `_settings` (used\n  only for `sendRequest`'s proxy/certificate resolution). `toObject().settings` is therefore always\n  `undefined` too, regardless of what `Settings` were passed in.\n- **`globals`/`baseGlobals` are `private` in name only** — `private` is TypeScript-only and erased at runtime, and the constructor's `Proxy` forwards them (via `Reflect.get`, no `set` trap) same as any other property, so scripts can read *and* write `insomnia.globals`/`insomnia.baseGlobals` despite them being absent from the TS-declared public surface. Accidental exposure, not a supported feature.\n- **The `test` proxy is easy to miss when reading the class** — the `test = () => {}` field looks\n  like the real implementation, but any actual call to `insomnia.test(...)` is intercepted by the\n  constructor's `Proxy` `get` trap before it ever reaches that field.\n- **Certificate fallback is silent unless you check logs** — if the request URL contains\n  `{{`/`}}`/`{%`/`%}` template tags, or no client certificate matches the host, an *empty* default\n  certificate is substituted and a warning is written via the script console rather than thrown as\n  an error.\n- **No-environment-selected warning** — selecting the base environment (rather than a\n  sub-environment) causes `initInsomniaObject` to log a warning that `insomnia.environment`\n  mutations will land on the base environment, because `environment` and `baseEnvironment` are the\n  same instance in that case.\n\n## Related\n- `interfaces.ts` — defines `RequestContext`, the input shape `initInsomniaObject` consumes.\n- `execution.ts` — `Execution`, held as `insomnia.execution`.\n- `request-info.ts` — `RequestInfo`, held as `insomnia.info`.\n- `environments.ts` — `Environment`, `Variables`, `Vault` classes used throughout.\n- `cookies.ts` — `CookieObject`, held as `insomnia.cookies`.\n- `folders.ts` — `Folder`, `ParentFolders`, used for folder-level variables.\n- `request.ts` — `Request`/`RequestOptions`/`toScriptRequestBody`, builds `insomnia.request`.\n- `response.ts` — `toScriptResponse`, `readBodyFromPath`, builds `insomnia.response`.\n- `send-request.ts` — implements `insomnia.sendRequest(...)`.\n- `test.ts` — `test`/`skip`/`TestHandler`, implements `insomnia.test(...)`.\n- `auth.ts` — `toPreRequestAuth`, used when building the request's auth config.\n- `proxy-configs.ts` — `transformToSdkProxyOptions`, used for request proxy settings.\n- `urls.ts` — `toUrlObject`, `resolveProtocolForProxy`.\n- `utils.ts` — `checkIfUrlIncludesTag`, used for certificate-fallback detection.\n- `console.ts` — `getExistingConsole`, used to log warnings.\n- `packages/insomnia/src/scripting/run-script.ts` — binds this object as the sandbox global `insomnia` and calls `toObject()` after the script runs.\n","isInternal":false,"tokens":2567,"sizeBytes":10647},{"name":"interfaces.md","path":".claude/skills/fix-scripting-feature/references/objects/interfaces.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/interfaces.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Shared interfaces (`RequestContext`, `IEnvironment`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interfaces.ts`\n\n## Purpose\nPure type-only file (no runtime code) defining the shared shapes used to pass state into and out\nof the scripting environment. `RequestContext` is the full serialized state snapshot that the host\napp (`packages/insomnia`) builds before running a script and that `initInsomniaObject` (in\n`insomnia.ts`) turns into a live `InsomniaObject`.\n\n## Public API\n\n### `interface IEnvironment`\n```ts\ninterface IEnvironment {\n  id: string;\n  name: string;\n  data: object;\n}\n```\nMinimal serialized shape for any environment-like blob (globals, base globals, environment, base\nenvironment, vault). Marked `/** @ignore */` (excluded from generated public docs).\n\n### `interface RequestContext`\n```ts\ninterface RequestContext {\n  request: Request;\n  timelinePath: string;\n  environment: IEnvironment;\n  baseEnvironment: IEnvironment;\n  vault?: IEnvironment;\n  collectionVariables?: object;\n  globals?: IEnvironment;\n  baseGlobals?: IEnvironment;\n  iterationData?: Omit<IEnvironment, 'id'>;\n  timeout: number;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookieJar: CookieJar;\n  response?: any;\n  requestTestResults?: RequestTestResult[];\n  requestInfo: RequestInfoOption;\n  execution: ExecutionOption;\n  logs: string[];\n  transientVariables?: Omit<IEnvironment, 'id'>;\n  parentFolders: { id: string; name: string; environment: Record<string, any> }[];\n}\n```\nFull input/output snapshot for one script execution. Fields of note:\n- `request: Request` — the raw request model (from `insomnia-data`), not the script-facing `ScriptRequest` wrapper in `request.ts`.\n- `globals`/`baseGlobals` — optional; per the inline comment, \"activated only when selected\".\n- `iterationData`/`transientVariables` — typed as `Omit<IEnvironment, 'id'>` (no `id` field needed for these).\n- `response?: any` — deliberately untyped; comment notes \"Callback types defined elsewhere to avoid circular imports\".\n- `requestInfo: RequestInfoOption` and `execution: ExecutionOption` — imported from `./request-info` and `./execution` respectively; these are the plain-object option shapes consumed by the `RequestInfo`/`Execution` constructors (see `request-info.md`/`execution.md`).\n- `parentFolders` — array of plain folder descriptors (`id`, `name`, `environment` data), consumed by `ParentFolders`/`Folder` in `folders.ts`.\n\nBoth interfaces are marked `/** @ignore */`, meaning they're internal/plumbing types not meant to\nappear in the generated public SDK reference.\n\n## Script-facing surface\nNone directly. A user's pre-request/after-response/test script never sees a `RequestContext` or\n`IEnvironment` value — these are internal transport types used by the host application\n(`packages/insomnia/src/scripting/run-script.ts`) to hand state into `initInsomniaObject` and to\nreceive mutated state back out via `InsomniaObject.toObject()`.\n\n## Gotchas / notable behavior\n- `globals`/`baseGlobals`/`vault` are all optional — code reading them elsewhere (e.g.\n  `initInsomniaObject`) must handle `undefined` explicitly (it does, via `?.` and fallbacks).\n- `response` being typed `any` means no compile-time safety on the response shape passed into the\n  scripting environment; the real shape is validated only implicitly by how `toScriptResponse` (in\n  `response.ts`) consumes it.\n- This file has no runtime exports (interfaces only) — importing it has zero side effects.\n\n## Related\n- `insomnia.ts` — `initInsomniaObject(rawObj: RequestContext, log)` is the sole consumer that turns this shape into a live `InsomniaObject`.\n- `execution.ts` — supplies `ExecutionOption`, embedded as `RequestContext.execution`.\n- `request-info.ts` — supplies `RequestInfoOption`, embedded as `RequestContext.requestInfo`.\n- `environments.ts` — `Environment`/`Vault` classes are constructed from `IEnvironment`-shaped data.\n- `folders.ts` — `Folder`/`ParentFolders` are constructed from `RequestContext.parentFolders`.\n- `packages/insomnia/src/scripting/run-script.ts` — builds and consumes `RequestContext` around each script run.\n","isInternal":false,"tokens":967,"sizeBytes":4168},{"name":"interpolator.md","path":".claude/skills/fix-scripting-feature/references/objects/interpolator.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/interpolator.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Interpolator (template-tag rendering)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interpolator.ts`\n\n## Purpose\nInternal helper providing `{{variable}}`-style template rendering (plus `{{$fakerFn}}` dynamic\nvalue generation) on top of LiquidJS. It backs every \"replace variables in this string\" operation\nexposed by the environment/property objects (e.g. `insomnia.environment.replaceIn(...)`).\n\n## Public API\n\n### `class Interpolator` (not exported — module-private)\n```ts\nconstructor()\n```\nBuilds a `Liquid` engine configured with:\n- `outputDelimiterLeft: '{{'`, `outputDelimiterRight: '}}'` — variable interpolation delimiters.\n- `tagDelimiterLeft: '{%'`, `tagDelimiterRight: '%}'` — Liquid tag delimiters (e.g. `{% if %}`).\n- `strictVariables: true` — referencing an undefined variable causes a render error rather than silently rendering empty.\n- `jsTruthy: true` — use JS truthiness semantics instead of Liquid's default truthy rules.\n- `ownPropertyOnly: false` — allows resolving inherited/prototype properties on the context object, not just own properties.\n\nMethods:\n- `render = async (template: string, context: object): Promise<string>` — runs `renderWithFaker(template)` first (to substitute any `{{$fakerFn}}` tags with generated values), then parses/renders the result through the Liquid engine against `context`. Comment: `// TODO: support plugins`.\n- `renderWithFaker = (template: string) => string` — a pre-pass over the template string, done via\n  manual string splitting (not Liquid) rather than regex/AST parsing:\n  1. Splits `template` on `'}}'`.\n  2. For each segment, finds the last `'{{'` before the end of the segment.\n  3. If the found tag name starts with `'$'`, strips the `$` prefix, looks it up in\n     `fakerFunctions` (from `insomnia/src/common/templating/faker-functions`), calls it, and\n     splices the generated value in place of the tag.\n  4. If the tag name does not start with `$`, the segment (and its `}}`) is left untouched for the\n     real Liquid engine to interpolate as a normal variable.\n  5. Throws `Error('replaceIn: no faker function is found: ${funcName}')` if the `$`-prefixed name\n     isn't a recognized faker function.\n\n### `function getInterpolator(): Interpolator`\nReturns the single module-level `Interpolator` instance (`const interpolator = new Interpolator();`), constructed once at module load. This is the only exported symbol.\n\n## Script-facing surface\nNo direct surface — scripts never call `getInterpolator()` themselves. It is reached indirectly\nthrough higher-level methods on other objects, all of which delegate to\n`getInterpolator().render(...)`:\n- `insomnia.environment.replaceIn(template)` / same method on `baseEnvironment`, `collectionVariables`, `variables`, etc. (defined in `environments.ts`) — e.g. `insomnia.environment.replaceIn(\"My id is {{$randomUUID}}\")` or `insomnia.environment.replaceIn(\"Visiting URL: {{urlValueFromEnvironment}}\")`.\n- `Property.replaceSubstitutions(content, ...variables)` and `Property.replaceSubstitutionsIn(obj, ...variables)` (static methods in `properties.ts`) — used internally wherever a `Property`-derived object needs variable substitution against multiple variable-scope objects merged together.\n\n## Gotchas / notable behavior\n- `renderWithFaker` is a hand-rolled string scan, not a real parser — it looks for the *last*\n  `'{{'` before each `'}}'` boundary in each split segment. Malformed or nested template syntax\n  could confuse this pass before Liquid ever sees the template.\n- `strictVariables: true` means referencing a variable name that isn't present in `context` will\n  cause the Liquid render to throw/reject, not silently produce an empty string — a common source\n  of \"my `{{someVar}}` template failed\" script errors when the variable isn't actually defined in\n  any active scope.\n- `$`-prefixed faker tags are resolved by this module's own pre-pass, *before* Liquid — an unknown\n  `$xyz` faker function name throws synchronously from `renderWithFaker` with the message\n  `replaceIn: no faker function is found: xyz`.\n- `getInterpolator()` always returns the same singleton `Interpolator` instance across the whole\n  process — there is no way to get a differently-configured interpolator or to reset its Liquid\n  engine mid-run.\n- The `Interpolator` class itself is not exported; only `getInterpolator()` is, so consumers cannot\n  construct their own instance or subclass it.\n\n## Related\n- `environments.ts` — `Environment`/`Variables`/`Vault`'s `replaceIn(...)` methods call `getInterpolator().render(...)`.\n- `properties.ts` — `Property.replaceSubstitutions`/`replaceSubstitutionsIn` call `getInterpolator().render(...)`.\n- `insomnia/src/common/templating/faker-functions` (outside this package) — supplies the `fakerFunctions` map used for `{{$fakerFn}}` tags.\n","isInternal":false,"tokens":1130,"sizeBytes":4851},{"name":"properties.md","path":".claude/skills/fix-scripting-feature/references/objects/properties.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/properties.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# PropertyBase / Property / PropertyList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/properties.ts`\n\n## Purpose\nBase classes that most other SDK objects extend, mirroring Postman's `postman-collection` `Property`/`PropertyList` model: `PropertyBase` provides parent-chain traversal and JSON serialization, `Property` adds `id`/`name`/`disabled` plus static template-substitution helpers, and `PropertyList<T>` is a generic ordered/keyed collection with Postman-compatible methods (`add`, `each`, `filter`, `find`, `one`, `upsert`, `toObject`, etc.). Concrete subclasses (`Variable`/`VariableList`, `Header`/`HeaderList`, `Cookie`/`CookieList`, `ProxyConfig`/`ProxyConfigList`, etc.) build on top of these.\n\n## Public API\n\n### `unsupportedError(featureName: string, alternative?: string): Error`\nBuilds an `Error` with message `` `${featureName} is not supported yet` `` (optionally appending `, please use ${alternative} instead temporarily.` when `alternative` is given). Used throughout the SDK (e.g. `Variable.types()`, `PropertyList.eachParent`) to mark stubbed-out Postman-compat methods.\n\n### `class PropertyBase`\n```ts\nconstructor(description?: string)\n```\n- `public _kind = 'PropertyBase'` — used as a lightweight runtime type tag (checked via `'_kind' in obj` elsewhere, e.g. `VariableList.isVariableList`).\n- `protected _parent: PropertyBase | undefined` — set only via subclasses/external assignment; no setter method is provided on the base class itself.\n- `protected description?: string`.\n- `static propertyIsMeta(_value: any, key: string): boolean` — `key && key.startsWith('_')` (keys starting with `_` are treated as \"meta\").\n- `static propertyUnprefixMeta(_value: any, key: string): string` — strips a leading `_` from `key` if present.\n- `meta(): {}` — always returns an empty object (not implemented beyond the stub).\n- `parent(): PropertyBase | undefined` — returns `this._parent`.\n- `forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean): PropertyBase[] | undefined` — BFS-style walk up the parent chain (via `parent()`), cloning each ancestor with the `clone` package before passing it to `iterator`; stops as soon as `iterator` returns falsy, and always returns the array of cloned ancestors visited so far (or `undefined` if there is no parent at all). `_options` is accepted but never read.\n- `findInParents(property: string, customizer?: (ancestor: PropertyBase) => boolean): PropertyBase | undefined` — walks the (cloned) parent chain looking for the first ancestor whose `meta()` keys include `property`; if `customizer` is given, keeps walking until `customizer(ancestor)` returns truthy, otherwise returns the first ancestor with that meta key. Returns `undefined` if no parent, or none matches.\n- `toJSON(): Record<string, any>` — returns `Object.entries(this)` filtered to drop function-valued entries, `undefined` values, and the `_kind` key.\n- `toObject(): Record<string, any>` — returns `this.toJSON()`.\n- `toString(): string` — `JSON.stringify(this.toJSON())`.\n\n### `class Property extends PropertyBase`\n```ts\nconstructor(id?: string, name?: string, disabled?: boolean, info?: { id?: string; name?: string })\n```\n`info.id`/`info.name` take priority over the positional `id`/`name` params if both are given; `this._kind` is set to `'Property'`; `disabled` defaults to `false`.\n- `id: string`, `name?: string`, `disabled?: boolean`.\n- `static _index = 'id'` — the field name `PropertyList` uses to key/index items of this type; overridden by subclasses (e.g. `Variable._index = 'key'`).\n- `static async replaceSubstitutions(content: string, ...variables: object[]): Promise<string>` — throws `TypeError` if `variables` isn't an array or `content` isn't a string. Merges `variables` into a single context object: the array is `.reverse()`d first, then folded left-to-right with `{...context, ...variable}`, so the reversed (originally-last) entries are applied first and get overwritten by earlier ones — net effect: **the leftmost/first argument passed to `replaceSubstitutions` wins** on key collisions, matching the inline comment (\"the searching priority of rendering is from left to right\"). Renders `content` against that merged context via `getInterpolator().render(...)`.\n- `static async replaceSubstitutionsIn(obj: object, ...variables: object[]): Promise<object>` — same signature/merge semantics as `replaceSubstitutions`, but stringifies `obj` with `JSON.stringify`, renders it, then `JSON.parse`s the result. Throws `TypeError` for bad args up front; wraps any other error (e.g. a `JSON.parse` failure on non-JSON-safe rendered output) in a new `Error('replaceSubstitutionsIn: ' + e.toString())`.\n- `describe(content: string, typeName: string): void` — sets `this._kind = typeName` and `this.description = content`.\n\n### `class PropertyList<T extends Property>`\n```ts\nconstructor(\n  protected typeClass: { _index?: string },\n  protected parent: Property | PropertyList<any> | undefined,\n  populate: T[],\n)\n```\n- `protected _kind = 'PropertyList'`, `protected list: T[]`.\n- `static isPropertyList(obj: object): boolean` — `'_kind' in obj && obj._kind === 'PropertyList'`.\n- `add(item: T): void` — pushes to the end of `list`.\n- `all(): Record<string, any>[]` — `list.map(pp => pp.toJSON())`.\n- `append(item: T): void` — alias for `add`.\n- `assimilate(source: T[] | PropertyList<T>, prune?: boolean): void` — if `prune`, calls `clear()` first; then pushes all of `source`'s items (`source.list` if it's a `PropertyList`, else the array itself) onto `list`. Per an inline comment, \"it doesn't update values from a source list\" (i.e. this appends, it does not merge/replace by key).\n- `clear(): void` — `list = []`.\n- `count(): number` — `list.length`.\n- `each(iterator: (item: T) => void, context: object): void` — calls `list.forEach(iterator)`; `context` is stashed as `iterator.context` (assigned onto the function) but the iterator itself is not invoked with that context bound — purely a Postman-compat artifact.\n- `eachParent(_iterator, _context?): never` — **always throws** `unsupportedError('eachParent')`; not implemented (\"properties are not organized as hierarchy\" per the TODO comment).\n- `filter(rule: (item: T) => boolean, context: object): T[]` — same `context`-stash pattern as `each`; returns `list.filter(rule)`.\n- `find(rule: (item: T) => boolean, context?: object): T | undefined` — same pattern; `list.find(rule)`.\n- `get(key: string): T | undefined` — alias for `one(key)`.\n- `has(item: T, _value?: any): boolean` — `indexOf(item) >= 0`; `_value` is accepted but unused (\"its usage is unknown\" per comment).\n- `idx(index: number): T | undefined` — returns `list[index]` if `index <= list.length - 1`, else `undefined`.\n- `indexOf(item: string | T): number` — looks up the index field via `typeClass._index || 'id'`; if `item` is a string, matches `record[indexFieldName] === item`; otherwise matches `record[indexFieldName] === (item as Record<string, any>)[indexFieldName]`. Returns `-1` if not found.\n- `insert(item: T, before?: number): void` — splices `item` in before index `before` if valid (`before != null && before >= 0 && before <= list.length - 1`), else falls back to `append(item)`.\n- `insertAfter(item: T, after?: number): void` — splices `item` in right after index `after` under the same bounds check, else falls back to `append(item)`.\n- `map(iterator: (item: T) => any, context: object): any[]` — same `context`-stash pattern; `list.map(iterator)`.\n- `one(id: string): T | undefined` — scans `list` **backwards** (`for (let i = list.length - 1; i >= 0; i--)`) for the first record whose index field equals `id`; if the matched item has a callable `valueOf`, returns `item.valueOf()` instead of the item itself; else returns the item as-is. Returns `undefined` if not found.\n- `populate(items: T[]): void` — `list = [...list, ...items]`.\n- `prepend(item: T): void` — `list = [item, ...list]`.\n- `reduce(iterator: (acc: any, item: T) => any, accumulator: any, context: object): any` — same `context`-stash pattern; `list.reduce(iterator, accumulator)`.\n- `remove(predicate: T | ((item: T) => boolean), context: object): void` — if `predicate` is a function, keeps everything that does *not* match it (via `filter` with the negated predicate); if `predicate` is a value, keeps everything not `deep-equal` to it.\n- `repopulate(items: T[]): void` — `clear()` then `populate(items)`.\n- `toObject(_excludeDisabled?, _caseSensitive?, _multiValue?, _sanitizeKeys?): Record<string, any> | Record<string, any>[]` — base implementation ignores all four arguments and returns `list.map(elem => elem.toJSON())` (an array, not a keyed object) — it's the fallback for lists whose items have no natural key (e.g. `UrlMatchPatternList`, or a raw `PropertyList` not used through a subclass). Subclasses backed by keyed items (`CookieList`, `HeaderList`, `VariableList`, `ProxyConfigList`) override this with a real key→value map that supports `excludeDisabled`/`multiValue`; `caseSensitive`/`sanitizeKeys` remain unsupported even in those overrides.\n- `toString(): string` — `` `[${list.map(item => item.toString()).join('; ')}]` ``.\n- `upsert(item: T): boolean` — returns `false` immediately if `item == null`. If `indexOf(item)` finds an existing entry, **splices it out and reinserts the new item at the same position** (returns `false`, meaning \"updated, not inserted\"); otherwise calls `add(item)` and returns `true` (\"inserted new\"). Note: the \"splice out, then splice again\" implementation calls `this.list.splice(...)` twice on the *same* array reference inside one expression (`[...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]`) — the first `splice` call mutates `this.list` in place before the second one runs against the now-shortened array.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` itself — these are base classes. Scripts interact with them only through concrete subclasses' public APIs, e.g.:\n- `pm.variables`/collections of variables → `VariableList` (extends `PropertyList<Variable>`), `Variable` (extends `Property`) — see `variables.md`.\n- `pm.request.headers`, `pm.response.headers` → `HeaderList`/`Header`.\n- `pm.request.url.query` → `UrlMatchPatternList`/similar list types.\n- Any script calling `.get()`, `.one()`, `.each()`, `.filter()`, `.upsert()`, `.toObject()`, etc. on a collection-like SDK object is going through `PropertyList`'s implementation documented above.\n- `Property.replaceSubstitutions`/`replaceSubstitutionsIn` back the SDK's `{{variable}}` template rendering wherever it's invoked as a static helper (distinct from, but functionally similar to, `Environment.replaceIn`/`Variables.replaceIn` in `environments.ts`).\n\n- **`replaceSubstitutions`/`replaceSubstitutionsIn` mutate their own `variables` rest-array via `.reverse()`** before merging — this is an implementation detail (not observable to callers since `variables` is a fresh rest-parameter array each call), but worth knowing if you're stepping through this code: the reversal is what makes the final left-to-right merge produce \"leftmost argument wins\" precedence.\n- **`eachParent` always throws** `unsupportedError('eachParent')` — calling it from a script will break, regardless of arguments.\n- **`each`/`filter`/`find`/`map`/`reduce`'s `context` parameter does nothing functionally** — it's stashed as a property on the callback function object (`it.context = context`) purely for Postman API-shape compatibility, but the callback is invoked with `Array.prototype`'s normal (unbound) semantics, so relying on `context` for `this`-binding inside the iterator will not work as it might in Postman.\n- **`one(id)` scans backwards and unwraps `valueOf()`** — if two items share the same index-field value, `one()` returns the *last*-added one (matching most recently `add`/`upsert`ed semantics), and if that item defines a custom `valueOf`, the returned object is `item.valueOf()`, not the raw list item.\n- **`upsert(item)` replaces the item at the same index** via `[...this.list.slice(0, itemIdx), item, ...this.list.slice(itemIdx + 1)]`, leaving every other item in place.\n- **`toObject()`'s base implementation returns an array, not an object**, unlike its keyed subclass overrides (`VariableList.toObject()` etc., which return `Record<string, any>`) — code that assumes every `PropertyList.toObject()` yields a key/value map will break for list types that don't override it (e.g. `UrlMatchPatternList`).\n- `PropertyBase.forEachParent`/`findInParents` both `clone()` each ancestor before handing it to the caller/comparing — mutations the iterator/customizer performs on the passed-in ancestor object do not affect the real parent chain.\n- `Property`'s constructor lets `info.id`/`info.name` silently override the positional `id`/`name` arguments — passing both can be surprising if not intentional.\n\n## Related\n- `variables.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>` (see `variables.md`).\n- `environments.ts` — a separate, non-`Property`-based variable model (`Environment`/`Variables`) used for `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables`; don't conflate the two when tracing a variable bug.\n- `interpolator.ts` — `getInterpolator().render(...)` is what `Property.replaceSubstitutions`/`replaceSubstitutionsIn` delegate to for `{{...}}` template rendering.\n- `headers.ts`, `cookies.ts`, `proxy-configs.ts`, `urls.ts` — other concrete `Property`/`PropertyList` subclasses that override `toObject()` with their own keyed semantics.\n- `collection.ts` / `index.ts` — re-export `Property`, `PropertyBase`, `PropertyList` as part of the SDK's public surface.\n","isInternal":false,"tokens":3426,"sizeBytes":13825},{"name":"proxy-configs.md","path":".claude/skills/fix-scripting-feature/references/objects/proxy-configs.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/proxy-configs.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# ProxyConfig, ProxyConfigList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts`\n\n## Purpose\nModels a single proxy configuration (`pm.request.proxy`) and a keyed list of them (`ProxyConfigList`, exported for scripts to build their own lists, e.g. via `require('insomnia-collection')`). Also provides `transformToSdkProxyOptions`, the function that converts Insomnia's native proxy settings (`httpProxy`/`httpsProxy`/`proxyEnabled`/`noProxy` strings from Settings) into the SDK's `ProxyConfigOptions` shape used to seed `pm.request.proxy`.\n\n## Public API\n\n### Types\n- `interface ProxyConfigOptions { match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string }` — constructor input for `ProxyConfig`. `match` is a URL match pattern string (see `urls.ts`) initializing a `UrlMatchPattern` internally, e.g. `'http+https://example.com/*'`. `bypass` and `protocol` are called out in comments as \"for compatibility with Insomnia\".\n\n### `class ProxyConfig extends Property`\n- `override _kind = 'ProxyConfig'`\n- `host: string`\n- `match: string` — the raw match-pattern string (not a `UrlMatchPattern` instance on the instance property, though a `UrlMatchPattern` is constructed on the fly inside `getProtocols()`/`test()`).\n- `port?: number`\n- `tunnel: boolean`\n- `authenticate: boolean`\n- `username: string`\n- `password: string`\n- `bypass: string[]` — list of hosts/URLs to bypass the proxy for; comment: \"for compatibility with Insomnia's bypass list\".\n- `protocol: string` — e.g. `\"http\"`/`\"https\"`.\n- `static authenticate = false`, `static bypass: UrlMatchPatternList<UrlMatchPattern> = new UrlMatchPatternList(undefined, [])`, `static host = ''`, `static match = ''`, `static password = ''`, `static port?: number = undefined`, `static tunnel = false` (comment: \"unsupported\"), `static username = ''`, `static protocol = 'https:'` — all marked `@ignore` in JSDoc with the comment \"following properties are hidden as they are not used while must be exposed\". These are class-level defaults, distinct from the instance properties of the same name.\n- `constructor(def: { id?: string; name?: string; match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string })` — sets `id`/`name` from `def` or defaults to `''`, `disabled` defaults to `false`, `bypass` defaults to `[]` if omitted. All other fields copied straight through (no defaulting).\n- `static override _index = 'key'` — Note: `ProxyConfig` has no instance field literally named `key`; `PropertyList.indexOf()`/`.one()` would look for a `key` property on the record, which doesn't exist on `ProxyConfig` — see Gotchas. (`ProxyConfigList.toObject()` keys its map by `match`, not by this `_index`.)\n- `static isProxyConfig(obj: object): boolean` — checks `obj._kind === 'ProxyConfig'`.\n- `getProtocols(): string[]` — builds a `UrlMatchPattern(this.match)` and returns `.getProtocols()` (parses the `'+'`-delimited protocol prefix of the match string, e.g. `'http+https://...'` → `['http', 'https']`).\n- `getProxyUrl(): string` — builds the full proxy URL: `` `${protocol}//${username}:${password}@${host}${port}` `` if `authenticate` is true, otherwise `` `${protocol}//${host}${port}` `` (port segment omitted entirely if `port === undefined`).\n- `test(url?: string): boolean` — returns `false` immediately if `url` is falsy (comment: \"TODO: it is confusing in which case url arg is optional\"). Returns `false` if `url` is literally in `this.bypass` (exact string match, not pattern matching). Otherwise delegates to `new UrlMatchPattern(this.match).test(url)`.\n- `update(options: Omit<ProxyConfigOptions, 'bypass' | 'protocol'>): void` — updates `host`, `match`, `port`, `tunnel`, `authenticate`, `username`, `password`. Does **not** allow updating `bypass` or `protocol` (both omitted from the type, and untouched by the method body).\n- `updateProtocols(_protocols: string[]): never` — always throws `Error('updateProtocols is not supported in Insomnia')`. Comment: \"In Insomnia there is no whitelist while there is a blacklist.\"\n\n### `class ProxyConfigList<T extends ProxyConfig> extends PropertyList<T>`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — constructs the underlying `PropertyList` with `typeClass = ProxyConfig`.\n- `static isProxyConfigList(obj: any): boolean` — checks `obj._kind === 'ProxyConfigList'`.\n- `resolve(url?: Url): object | null` — returns `null` if `url` is falsy. Otherwise stringifies `url`, filters the list to configs whose `.test(urlStr)` is true, maps matches to `.toJSON()`, and returns the **first** match's JSON (or `null` if none match). Comment: \"It only returns the first one if multiple matches are found.\"\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, any>` — builds a plain object keyed by each config's `match` string, using `Object.create(null)` (no prototype). `excludeDisabled` skips `disabled` configs. `multiValue` collects same-`match` configs into an array; otherwise duplicate `match` values collapse to the last one.\n\n### Module-level function\n- `transformToSdkProxyOptions(protocol: string, httpProxy: string, httpsProxy: string, proxyEnabled: boolean, noProxy: string): ProxyConfigOptions` — computes `proxyHost` (the string actually parsed into `host`/`port`/etc. below) as `httpsProxy` or `httpProxy` based on whether `protocol === 'https:'`. But `enabledProxy` is computed separately, from `proxyEnabled && (httpsProxy || httpProxy || '').trim() !== ''` — **not** from `proxyHost` — so it's `true` whenever *either* proxy string is non-empty, regardless of which one the current `protocol` selected (see Gotchas). Splits `noProxy` on commas (trimmed) into the `bypass` list. Always returns `match: '<all_urls>'` (`UrlMatchPattern.MATCH_ALL_URLS`). If the proxy is enabled and a host string is present, parses it with the built-in `URL` class (prefixing `${protocol}//` if the string has no `://`), extracting `port` (only set if non-empty, then `Number.parseInt(..., 10)`), `protocol`, `host` (hostname), `username`, `password`; sets `authenticate = true` if either `username` or `password` came through; logs `` `Using proxy: ${sanitizedProxy}` `` via `getExistingConsole().warn`. Throws `Error('Failed to parse proxy (${protocol}//${proxyHost}): ${e.message}')` if URL parsing fails **and the proxy is enabled** (parsing is skipped entirely, no throw, when the proxy is disabled).\n\n## Script-facing surface\n- `pm.request.proxy` is a single `ProxyConfig` instance (per `request.ts`: `const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined;`), seeded from Insomnia's Settings-level proxy configuration via `transformToSdkProxyOptions` (called in `insomnia.ts`). It is `undefined` if no `proxy` option was supplied when constructing the request.\n- Scripts read/write `pm.request.proxy.host`, `.port`, `.username`, `.password`, `.authenticate`, `.tunnel`, `.bypass`, `.protocol` directly, call `.getProxyUrl()` to see the resolved proxy URL, `.test(url)` to check whether a given URL should go through this proxy, and `.update({...})` to change host/match/port/tunnel/authenticate/username/password.\n- `ProxyConfigList` and `ProxyConfig` are also exported from the `insomnia-collection` module (via `collection.ts`) so scripts can `require('insomnia-collection')` and construct their own lists directly, e.g.: `new ProxyConfigList(undefined, [{match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true}, ...])` (per the inline example comment in the source). There is no evidence in the SDK object graph that `pm.request` itself ever holds a `ProxyConfigList` — only a single `ProxyConfig`.\n\n## Gotchas / notable behavior\n- **`_index = 'key'` but `ProxyConfig` has no `key` property.** Base `PropertyList.one()`/`.indexOf()`/`.upsert()` look up items by `typeClass._index` (here `'key'`), but `ProxyConfig` instances never define a `key` field — so `ProxyConfigList.one('...')`/`.get('...')`/`.indexOf(...)`/`.upsert(...)` would compare against `undefined` for every item and effectively never index correctly by identity (though `.toObject()` and `.resolve()` work fine since they key/filter explicitly by `match` instead of relying on `_index`).\n- **`test(url)` returns `false` silently if `url` is omitted** — the source itself flags this as confusing (\"TODO: it is confusing in which case url arg is optional\").\n- **`bypass` matching is exact string equality**, not pattern-based — a URL must be an exact match to an entry in `bypass` to be excluded; nothing is normalized (no protocol/trailing-slash handling).\n- **`update()` cannot change `bypass` or `protocol`** — these are deliberately excluded from `ProxyConfigOptions` in the `update()` signature's `Omit<...>`.\n- **`updateProtocols()` always throws** — it exists on the class but is not a supported operation in Insomnia.\n- **`getProxyUrl()` omits the port segment entirely when `port === undefined`** (not just falsy — `port: 0` would still render `:0`).\n- **`transformToSdkProxyOptions` swallows parse errors when the proxy is disabled**: a malformed proxy host string does not throw as long as `proxyEnabled` is `false`; it only throws when the proxy would actually be used.\n- **`enabled`/`disabled` can disagree with the protocol-selected proxy string.** `enabledProxy` is derived from `httpsProxy || httpProxy` (https checked first, regardless of `protocol`), while the proxy actually parsed into `host`/`port`/etc. is `proxyHost` (`httpsProxy` only if `protocol === 'https:'`, else `httpProxy`). If `protocol` is `'http:'` with `httpProxy` empty but `httpsProxy` set (or vice versa), `enabledProxy` comes out `true` from the other, unused string, `proxy.disabled` is set to `false`, yet `proxyHost === ''` skips the parsing block entirely — so `pm.request.proxy` ends up `disabled: false` with an empty `host`.\n- **The class-level `static` fields shadow real instance field names** (`static host = ''`, `static match = ''`, etc., separate from `this.host`, `this.match` on instances) — marked `@ignore` and described only as \"hidden as they are not used while must be exposed\"; do not confuse `ProxyConfig.host` (static, always `''`) with `someProxyConfigInstance.host`.\n\n## Related\n- `properties.ts` — `Property` (base class for `ProxyConfig`) and `PropertyList` (base class for `ProxyConfigList`), including the shared `toObject()`/`_index` lookup semantics referenced above.\n- `urls.ts` — `UrlMatchPattern`/`UrlMatchPatternList` (used internally by `getProtocols()`/`test()`) and `Url` (the type accepted by `ProxyConfigList.resolve()`).\n- `console.ts` — `getExistingConsole()`, used by `transformToSdkProxyOptions` to log which proxy is being used.\n- `request.ts` — constructs `pm.request.proxy` from `ProxyConfigOptions` and serializes it back out when building the final request.\n- `insomnia.ts` — calls `transformToSdkProxyOptions` using Insomnia Settings (`proxyEnabled`, http/https proxy strings, `noProxy`) to seed the proxy config before a script runs.\n- `collection.ts` — re-exports `ProxyConfig`/`ProxyConfigList` for direct use via `require('insomnia-collection')`.\n","isInternal":false,"tokens":2845,"sizeBytes":11411},{"name":"README.md","path":".claude/skills/fix-scripting-feature/references/objects/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/README.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# `insomnia-scripting-environment/src/objects` Reference\n\nOne doc per source file in `packages/insomnia-scripting-environment/src/objects/`. Each covers: public API (real signatures), how a script actually reaches it (`pm.*`/`insomnia.*`), and gotchas found while reading the code.\n\n## Core / context\n- [insomnia.md](insomnia.md) — `InsomniaObject`, the top-level `insomnia`/`pm` instance\n- [interfaces.md](interfaces.md) — `RequestContext`, `IEnvironment`\n- [execution.md](execution.md) — `Execution` (`pm.execution`)\n- [request-info.md](request-info.md) — `RequestInfo` (`pm.info`)\n- [async-objects.md](async-objects.md) — `ProxiedPromise` and async-task tracking\n- [interpolator.md](interpolator.md) — `{{ }}` template interpolation\n- [utils.md](utils.md) — misc helpers\n\n## Request / response\n- [request.md](request.md) — `Request`, `RequestBody` (`pm.request`)\n- [response.md](response.md) — `Response` (`pm.response`)\n- [headers.md](headers.md) — `Header`, `HeaderList`\n- [urls.md](urls.md) — `Url`, `QueryParam`, `UrlMatchPattern`\n\n## Environment / variables / collection\n- [environments.md](environments.md) — `Environment`, `Variables` (`pm.environment` / `pm.globals`, variable resolution precedence)\n- [variables.md](variables.md) — `Variable`, `VariableList`\n- [collection.md](collection.md) — re-export barrel only; no `Collection` class or logic — see `environments.md`/`insomnia.md` instead\n- [folders.md](folders.md) — `Folder`, `ParentFolders`\n- [properties.md](properties.md) — `PropertyBase`, `PropertyList` base classes\n\n## Auth / cookies / certs / proxy\n- [auth.md](auth.md) — `RequestAuth` (`pm.request.auth`)\n- [cookies.md](cookies.md) — `Cookie`, `CookieJar`, `CookieList` (`pm.cookies`)\n- [certificates.md](certificates.md) — `Certificate` (`pm.request.certificate`)\n- [proxy-configs.md](proxy-configs.md) — `ProxyConfig`, `ProxyConfigList`\n\n## Test / console / send-request\n- [test.md](test.md) — `pm.test()`/`test()` handler registration\n- [console.md](console.md) — `Console` (script `console.log` capture)\n- [send-request.md](send-request.md) — `pm.sendRequest()`\n\n## Flagged during writing (worth a closer look if you hit related symptoms)\n- `insomnia.settings` always returns `undefined` in scripts (getter unconditionally returns nothing) — see `insomnia.md`.\n- `pm.collectionVariables` and `pm.baseEnvironment` are the same object reference, not a copy — see `insomnia.md`/`environments.md`.\n- Writes to `insomnia.info.*` (`RequestInfo`) are never read back by the script-run merge logic — silently discarded — see `request-info.md`.\n- `pm.sendRequest(url, callback)` resolves (does not reject) on network/parse errors when a callback is passed — only the promise-only form rejects — see `send-request.md`.\n- `Response.dataURI()` has a typo bug: emits `baseg4` instead of `base64` — see `response.md`.\n- `Certificate.update()` doesn't update `disabled`, unlike the constructor — see `certificates.md`.\n- `ProxyConfig` has no `key` property despite `_index = 'key'`, breaking base-class `one()`/`indexOf()`/`upsert()` lookups — see `proxy-configs.md`.\n- `Vault` is read-only from scripts even when `enableVaultInScripts` is set — `set`/`unset`/`clear` always throw — see `environments.md`.\n","isInternal":false,"tokens":847,"sizeBytes":3292},{"name":"request-info.md","path":".claude/skills/fix-scripting-feature/references/objects/request-info.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/request-info.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# RequestInfo (`insomnia.info` / `pm.info`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request-info.ts`\n\n## Purpose\nModels `pm.info`: metadata about which script phase is executing and, for the collection runner,\nwhich iteration is in progress. Read-oriented — scripts typically branch on `insomnia.info.eventName`\nor log `insomnia.info.iteration`/`iterationCount`.\n\n## Public API\n\n### `type EventName = 'prerequest' | 'test'`\nThe name of the event that triggered the script: `'prerequest'` (before a request is sent) or\n`'test'` (during the testing/after-response phase).\n\n### `interface RequestInfoOption`\n```ts\ninterface RequestInfoOption {\n  eventName?: EventName;\n  iteration?: number;\n  iterationCount?: number;\n  requestName?: string;\n  requestId?: string;\n}\n```\nPlain-object shape used to construct a `RequestInfo` (and stored on `RequestContext.requestInfo`, see `interfaces.md`).\n\n### `class RequestInfo`\n```ts\nconstructor(options: RequestInfoOption)\n```\nPublic properties (plain, mutable, non-readonly):\n- `eventName: EventName` — defaults to `'prerequest'` if not provided (`options.eventName || 'prerequest'`).\n- `iteration: number` — defaults to `1` (`options.iteration || 1`).\n- `iterationCount: number` — defaults to `1` (`options.iterationCount || 1`).\n- `requestName: string` — defaults to `''`.\n- `requestId: string` — defaults to `''`.\n\nMethod:\n- `toObject = () => ({ eventName, iteration, iterationCount, requestName, requestId })` — plain serialization, same shape as `RequestInfoOption` with all fields always populated.\n\n## Script-facing surface\n- `insomnia.info.eventName` — `'prerequest'` or `'test'`, useful for scripts shared between pre-request and test tabs.\n- `insomnia.info.iteration` — current collection-runner iteration number (1-based).\n- `insomnia.info.iterationCount` — total number of iterations configured for the run.\n- `insomnia.info.requestName` — display name of the request being executed.\n- `insomnia.info.requestId` — the request's unique id.\n\n## Gotchas / notable behavior\n- All five properties are **plain public fields**, not readonly — a script can reassign\n  `insomnia.info.iteration = 99` and nothing in this class prevents it. In practice this has no\n  effect on the actual collection-runner state: `packages/insomnia/src/scripting/run-script.ts`'s\n  merge logic (building the returned `RequestContext`) does not read `requestInfo` back out of the\n  mutated `InsomniaObject` at all, so any script-side mutation of `insomnia.info` is silently\n  discarded after the script finishes.\n- The `||` fallback pattern means an explicit `0` for `iteration`/`iterationCount` is treated the\n  same as \"not provided\" and coerced to `1`. Notably, `initInsomniaObject` (in `insomnia.ts`)\n  builds the options with `iterationCount: rawObj.requestInfo.iterationCount || 0` — but since `0`\n  is falsy, the `RequestInfo` constructor's own `|| 1` fallback still turns that back into `1`, so\n  `iterationCount` effectively can never legitimately be `0` through this path.\n- A `// TODO: update follows when post-request script and iterationData are introduced` comment on\n  the call site in `insomnia.ts` suggests the `eventName`/iteration wiring for after-response\n  scripts may still be incomplete.\n\n## Related\n- `insomnia.ts` — `InsomniaObject.info: RequestInfo`; `initInsomniaObject` builds it from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- `interfaces.ts` — `RequestInfoOption` is embedded as `RequestContext.requestInfo`.\n- `packages/insomnia/src/scripting/run-script.ts` — supplies the initial `RequestInfoOption` per script phase (pre-request vs. test) and — notably — does not persist script-side mutations back into `RequestContext`.\n- `__tests__/request-info.test.ts` — covers default values and `toObject()` shape for both single-request and collection-runner (`iteration`/`iterationCount`) cases.\n","isInternal":false,"tokens":955,"sizeBytes":3920},{"name":"request.md","path":".claude/skills/fix-scripting-feature/references/objects/request.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/request.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Request, RequestBody\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request.ts`\n\n## Purpose\n\nModels the outgoing HTTP request as seen/mutated by pre-request and test scripts: `pm.request` / `insomnia.request`. Also holds the merge/serialization logic that converts between this script-facing `Request` shape and Insomnia's own `insomnia-data` request model (`mergeRequests`, `mergeRequestBody`, `mergeSettings`, `mergeClientCertificates`), and payload-size calculation used by `Request.size()` / `Response.size()`.\n\n## Public API\n\n### `FormParam` (extends `Property`)\n- `constructor(options: { key: string; value: string; type?: string; disabled?: boolean })`\n- `key: string`, `value: string`, `type?: string`\n- `static _postman_propertyAllowsMultipleValues()` — **always throws** `Error('unsupported')`.\n- `static _postman_propertyIndexKey()` — **always throws** `Error('unsupported')`.\n- `toJSON()` — `{ key, value, type, disabled }`.\n- `toString()` — `\"key=value\"` with both parts `encodeURIComponent`-escaped.\n- `valueOf()` — returns `value`.\n\n### `RequestBodyOptions` (interface)\n`{ mode: RequestBodyMode; file?: string; formdata?: {key,value,type?,disabled?}[]; graphql?: {query,operationName,variables,disabled?}; raw?: string; urlencoded?: {key,value?,type?,disabled?,multiline?,fileName?}[]; options?: object }`\n`RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'`.\n\n### `RequestBody` (extends `PropertyBase`)\n- `constructor(opts: RequestBodyOptions)`\n- `mode: RequestBodyMode`, `file?: string`, `formdata?: PropertyList<FormParam>`, `graphql?: {query,operationName,variables}`, `raw?: string`, `urlencoded?: PropertyList<QueryParam>`, `options?: object`\n- `isEmpty(): boolean` — switches on `mode` and checks whether the matching field is `null`; **throws** `Error` if `mode` isn't one of the five known modes.\n- `toString()` — renders body as a string per `mode` (`formdata`/`urlencoded` join their `PropertyList` entries with `&`, `graphql` is `JSON.stringify`-ed); wraps any internal error as `Error(\"toString: ...\")`; returns `''` if `mode` is `undefined`.\n- `update(opts: RequestBodyOptions)` — re-derives and replaces all fields (same logic as constructor).\n\n### `RequestOptions` (interface)\n`{ url: string | Url; method?: string; header?: HeaderDefinition[] | object; body?: RequestBodyOptions; auth?: AuthOptions; proxy?: ProxyConfigOptions; certificate?: CertificateOptions; pathParameters?: RequestPathParameter[]; name?: string }`\n\n### `RequestSize` (interface)\n`{ body: number; header: number; total: number; source: string }`\n\n### `Request` (extends `Property`)\n- `constructor(options: RequestOptions)` — sets `_kind = 'Request'`; `method` defaults to `'GET'` if omitted; `header` accepts either an array of `HeaderDefinition` or a plain `{key: value}` object (converted to `Header`s); `auth` defaults to `{ type: 'noauth' }`.\n- Properties: `name: string`, `url: Url`, `method: string`, `headers: HeaderList<Header>`, `body?: RequestBody`, `auth: RequestAuth`, `proxy?: ProxyConfig`, `certificate?: Certificate`, `pathParameters: RequestPathParameter[]`.\n- `static isRequest(obj: object): boolean` — checks `_kind === 'Request'`.\n- `addHeader(header: Header | object)` — accepts a `Header` instance or `{key, value}`; **throws** `Error` otherwise.\n- `addQueryParams(params: QueryParam[] | string)` — delegates to `this.url.addQueryParams`.\n- `authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList<Variable>)` — delegates to `this.auth.use(...)`.\n- `clone(): Request` — `new Request({ ...this.toJSON() })` (JSON round-trip clone, not a deep object clone).\n- `forEachHeader(callback: (header: Header, context?: object) => void)` — delegates to `this.headers.each`.\n- `getHeaders(options?: { ignoreCase, enabled, multiValue, sanitizeKeys }): Record<string, string[] | string>` — merges headers with the same key into an array; `ignoreCase` lowercases keys before merging; `enabled` filters out headers where `disabled` is truthy; `sanitizeKeys` drops headers with a falsy `value`.\n- `removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean })` — rebuilds `this.headers` as a new `HeaderList` excluding matches; **throws** if `toRemove` isn't `string | Header`.\n- `removeQueryParams(params: string | string[] | QueryParam[])` — delegates to `this.url.removeQueryParams`.\n- `size(): RequestSize` — `calculatePayloadSize(this.body?.toString() ?? '', this.headers)`.\n- `toJSON()` — plain object snapshot (`url` as string, `header` array, `body`, `auth`, `proxy`, `certificate`).\n- `update(options: RequestOptions)` — re-derives and replaces every field.\n- `upsertHeader(header: HeaderDefinition)` — removes any existing header with the same `key` (case-sensitive), then appends a new `Header`.\n\n### Module-level functions\n- `mergeSettings(originalSettings: Settings, updatedReq: Request): Settings` — if `updatedReq.proxy` is enabled (not disabled and has a non-empty proxy URL), overrides both `httpProxy` and `httpsProxy` in the returned `Settings` with the same proxy URL; otherwise returns `originalSettings` unchanged.\n- `mergeClientCertificates(originalClientCertificates: ClientCertificate[], updatedReq: Request): ClientCertificate[]` — maps the script's single `updatedReq.certificate` onto Insomnia's certificate list (which supports multiple). Returns originals unchanged if no certificate was set (or it's empty). **Throws** `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if neither a valid `pfx` nor a valid `cert`+`key` pair is present in the certificate.\n- `toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions` — converts Insomnia's native request body (`text` / `fileName` / `params`) into a `RequestBodyOptions` (`raw` / `file` / `urlencoded`); `formdata` and `graphql` modes are not produced here.\n- `mergeRequestBody(updatedReqBody: RequestBody | undefined, originalReqBody: InsomniaRequestBody): InsomniaRequestBody` — infers `mimeType` from `updatedReqBody.mode` (falls back to `originalReqBody.mimeType` if set); **throws** on an unknown `mode`; wraps any other failure as `Error(\"failed to update body: ...\")`.\n- `mergeRequests(originalReq: InsomniaRequest, updatedReq: Request): InsomniaRequest` — builds the outgoing Insomnia request from the script's mutated `Request`: `url` via `toStringWithoutQuery()`, `parameters` from `url.query`, `headers` from `headers`, `authentication` via `fromPreRequestAuth`, `pathParameters` copied as-is, and **hardcodes `preRequestScript: ''`** (i.e., the merged request never carries a pre-request script forward).\n- `calculatePayloadSize(body: string, headers: HeaderList<Header>): RequestSize` — body size via `new Blob([body]).size`; `source` is always `'COMPUTED'`.\n- `calculateHeadersSize(headers: HeaderList<Header>): number` — `Blob` size of all headers joined as `\"key: value\\n\"` lines (via each header's `toString()`, which is `Header`'s inherited/overridden behavior).\n\n## Script-facing surface\n\n`insomnia.request` / `pm.request` is a live `Request` instance (constructed in `insomnia.ts#initInsomniaObject`). Common script usage:\n- `pm.request.url`, `pm.request.method`, `pm.request.headers`, `pm.request.body`\n- `pm.request.headers.add(...)`, `pm.request.addHeader({key, value})`, `pm.request.upsertHeader({key, value})`, `pm.request.removeHeader('X-Foo')`\n- `pm.request.url.addQueryParams(...)` / `pm.request.addQueryParams(...)`, `pm.request.removeQueryParams(...)`\n- `pm.request.auth`, `pm.request.authorizeUsing('basic', ...)`\n- `pm.request.size()` — payload size info\n- Any mutation a pre-request script makes to `pm.request` is read back and merged into the real outgoing request via `mergeRequests` / `mergeRequestBody` / `mergeSettings` / `mergeClientCertificates` after the script finishes.\n\n## Gotchas / notable behavior\n\n- `FormParam._postman_propertyAllowsMultipleValues()` and `_postman_propertyIndexKey()` are stubs that **always throw** — calling them from a script (unlikely, but they exist statically) will always fail. A commented-out `static parse` is also noted as \"not supported yet in existing scripts\".\n- `RequestBody.isEmpty()` / `toString()` both `throw` if `mode` doesn't match one of the five known literals — an unexpected/typo'd `mode` string surfaces as a runtime error, not silently.\n- `Request.getHeaders()`'s `multiValue` option is part of the signature but **is never referenced in the function body** — the implementation always accumulates same-key headers into an array regardless of what `multiValue` is set to.\n- `Request.removeHeader` and `upsertHeader` rebuild `this.headers` as an entirely new `HeaderList` rather than mutating in place.\n- `Request.clone()` is a JSON round-trip (`toJSON()` → `new Request(...)`), not a structural/deep clone — anything not captured by `toJSON()` is lost on clone.\n- `mergeRequests` **always sets `preRequestScript: ''`** on the merged request — a deliberate design choice worth knowing if debugging \"script disappeared\" reports downstream.\n- `mergeClientCertificates` throws if a script sets both `cert`+`key` and `pfx` on `pm.request.certificate` at the same time — Insomnia only supports one or the other.\n- `toScriptRequestBody` only produces `raw` / `file` / `urlencoded` modes; there's no path from Insomnia's native body model to script `formdata`/`graphql` modes in this function.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` used for `Request.headers`.\n- `packages/insomnia-scripting-environment/src/objects/urls.ts` — `Url`, `QueryParam`, `toUrlObject` used for `Request.url` and `RequestBody.urlencoded`.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used for `Request.auth`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate` used for `Request.certificate`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig` used for `Request.proxy`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes.\n- `packages/insomnia-scripting-environment/src/objects/variables.ts` — `Variable`, `VariableList` used by `authorizeUsing`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — imports `calculateHeadersSize`; `Response.originalRequest` is a `Request`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.request` in `initInsomniaObject`.\n","isInternal":false,"tokens":2616,"sizeBytes":10684},{"name":"response.md","path":".claude/skills/fix-scripting-feature/references/objects/response.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/response.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Response\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/response.ts`\n\n## Purpose\n\nModels the HTTP response as seen by after-response and test scripts: `pm.response` / `insomnia.response`. It wraps the raw body/headers/cookies/timing data returned by the network layer, provides parsing/introspection helpers (`json()`, `contentInfo()`, `size()`), and extends `chai`'s assertion API with Postman-style response assertions (`pm.response.to.have.status(200)`, etc.). `pm.response` is only populated for after-response/test scripts — it's `undefined` during pre-request scripts.\n\n## Public API\n\n### `ResponseOptions` (interface)\n`{ code: number; reason?: string; header?: HeaderDefinition[]; cookie?: CookieOptions[]; body?: string; stream?: Buffer | ArrayBuffer; responseTime: number; originalRequest: Request; bytesRead?: number }`\n\n### `ResponseContentInfo` (interface)\n`{ mimeType: string; mimeFormat: string; charset: string; fileExtension: string; fileName: string; contentType: string }`\n\n### `Response` (extends `Property`)\n- `constructor(options: ResponseOptions)` — sets `_kind = 'Response'`; `body` defaults to `''`; `status` defaults to `options.reason`, falling back to `RESPONSE_CODE_REASONS[options.code]`, falling back to `''`; `bytesRead` (private) defaults to `0`.\n- Properties: `body: string`, `code: number`, `cookies: CookieList`, `headers: HeaderList<Header>`, `originalRequest: Request`, `responseTime: number`, `status: string`, `stream?: Buffer | ArrayBuffer`; `bytesRead` is **private**.\n- `static createFromNode(response: {...}, cookies: CookieOptions[]): Response` — builds a `Response` from a raw Node-style response object (`body`, `headers`, `statusCode`, `statusMessage`, `elapsedTime`, `originalRequest`, `stream`).\n- `static isResponse(obj: object): boolean` — checks `_kind === 'Response'`.\n- `contentInfo(): ResponseContentInfo` — parses the `Content-Type` header for `mimeType`/`charset` (defaults: `application/octet-stream` / `utf8`) and the `Content-Disposition` header for `fileName`/`fileExtension`. **Throws** if a `Content-Type` header exists but its value is blank, or its mime-type segment is empty.\n- `dataURI(): string` — builds a `data:` URI string from `contentInfo().contentType` and `this.stream || this.body`. **Throws** if neither `stream` nor `body` is set.\n- `json(reviver?: (key, value) => any, _strict?: boolean): any` — `JSON.parse(this.body.toString(), reviver)`; wraps parse failures as `Error(\"json: failed to parse: ...\")`. `_strict` is accepted but unused (\"TODO: enable strict after common module is introduced\").\n- `jsonp(_reviver?, _strict?)` — **always throws** `unsupportedError('jsonp()')`.\n- `reason(): string` — returns `this.status`.\n- `size(): { body: number; header: number; total: number; source: 'COMPUTED' }` — `body` is the private `bytesRead` value (not derived from `this.body`'s actual length); `header` via `calculateHeadersSize(this.headers)`.\n- `text(): string` — `this.body.toString()`.\n- `get to()` — returns a `chai.Assertion` wrapping `this`, with Postman-style properties/methods registered onto `chai.Assertion.prototype` on every access: properties `withBody`, `error` (true if `code` is within 400–500 inclusive), `ok` (true if `code === 200`), `json` (true if the body parses to an object); methods `status(val)`, `header(headerName)`, `body(bodyContent)`, `jsonBody(propName)`, `jsonSchema(schema, options?)` (uses `ajv` to validate `this.json()` against `schema`).\n\n### Module-level functions\n- `toScriptResponse(originalRequest: Request, partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, responseBody: string): Response | undefined` — returns `undefined` if `partialInsoResponse` is an error result (network/curl failure); otherwise builds a `Response`, extracting `Set-Cookie` headers into `Cookie.parse(...)` entries for `cookie`.\n- `readBodyFromPath(response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined): Promise<string>` — returns `''` if `response` is missing, an error, or has no `bodyPath`; otherwise reads and decompresses the body from disk via `services.helpers.readCurlResponse`. **Throws** if that read reports an error.\n\n## Script-facing surface\n\n`insomnia.response` / `pm.response` is a `Response` instance, present only in after-response/test scripts (constructed in `insomnia.ts#initInsomniaObject` via `toScriptResponse`; `undefined` if the request errored or during pre-request scripts). Common script usage:\n- `pm.response.code`, `pm.response.status`, `pm.response.headers`, `pm.response.body`\n- `pm.response.json()`, `pm.response.text()`, `pm.response.contentInfo()`\n- `pm.response.to.have.status(200)`, `pm.response.to.have.header('Content-Type')`, `pm.response.to.have.jsonBody('key')`, `pm.response.to.not.have.status(404)`, `pm.response.to.have.jsonSchema({...})`\n\n## Gotchas / notable behavior\n\n- `dataURI()` has a typo in the returned string: it emits `` `data:${contentType};baseg4, ${bodyInBase64}` `` — **`baseg4` instead of `base64`**. Any consumer relying on `dataURI()` producing a spec-compliant data URI will get a malformed one.\n- `jsonp()` is a stub that always throws `unsupportedError('jsonp()')` — it exists on the type but is never functional.\n- `size().body` comes from the **private `bytesRead`** field (set once at construction from `options.bytesRead`, defaulting to `0`), not from measuring `this.body`. If `bytesRead` wasn't passed in, `size()` reports `0` regardless of actual body length. (The test file has a commented-out assertion noting `resp.size()` doesn't fully work yet: `\"this will work after PropertyList.one is improved\"`.)\n- The `error` assertion (`pm.response.to.have.error` / `.to.be.error`) only covers status codes **400–500 inclusive** — not the broader 4xx/5xx range one might expect.\n- **`get to()`'s re-registration is a real correctness risk, not just wasted work.** Every `.to` access calls `chai.use((_chai, utils) => {...})` with a freshly-created arrow function; chai dedupes plugins by callback identity (`used.indexOf(fn)`), so a new closure is never recognized as already-registered and the plugin body reruns every time. Worse, every `withBody`/`error`/`ok`/`json`/`status`/`header`/`body`/`jsonBody`/`jsonSchema` callback is itself an arrow function that reads `utils.flag(respAssertion, 'object')`, where `respAssertion` is closed over from whichever `.to` access most recently ran — not derived from `this`/the assertion instance actually being evaluated (arrow functions ignore the `this` chai binds via `getter.call(this)`/`method.apply(this, ...)`). So if a script holds a `.to`-derived assertion chain and triggers another `.to` access (same or different response) before invoking a method/property on that held chain, the held chain silently asserts against the *other* response instead. Single-statement chains like `pm.response.to.have.status(200)` are unaffected since no `.to` access can intervene mid-expression.\n- `contentInfo()` throws if a `Content-Type` header is present but empty/malformed; scripts that blindly call `pm.response.contentInfo()` on unusual responses can throw.\n- `toScriptResponse` returns `undefined` on network error — scripts must handle `pm.response` potentially being `undefined` (though in practice after-response/test scripts only run when a response exists).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Response.originalRequest: Request`; imports `calculateHeadersSize` from here.\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` for `Response.headers`.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `Cookie`, `CookieList` for `Response.cookies`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `unsupportedError`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.response` in `initInsomniaObject` via `toScriptResponse`/`readBodyFromPath`.\n","isInternal":false,"tokens":1974,"sizeBytes":8075},{"name":"send-request.md","path":".claude/skills/fix-scripting-feature/references/objects/send-request.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/send-request.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# sendRequest (pm.sendRequest)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/send-request.ts`\n\n## Purpose\nImplements the standalone `sendRequest()` function that backs `pm.sendRequest()` / `insomnia.sendRequest()` — letting a pre-request/after-response script fire an ad-hoc HTTP request (via libcurl) and get a `Response` back, either through a Node-style callback or a returned promise. It also contains the translation layer from the SDK's `Request`/string/`RequestOptions` shapes into the curl request options consumed by Insomnia's network layer, and from the raw curl output back into a `Response` object.\n\n## Public API\n\n### `sendRequest(request, cb, settings)`\n```ts\nexport async function sendRequest(\n  request: string | Request | RequestOptions,\n  cb: (error?: string, response?: Response) => void,\n  settings: Settings,\n): Promise<Response | void>\n```\n- Builds curl options from `request` via `requestToCurlOptions(request, settings)`.\n- Picks the curl execution path based on environment: in the renderer (`__IS_RENDERER__` true) it uses `window.bridge.curlRequest`; otherwise it dynamically imports `curlRequest` from `insomnia/src/main/network/libcurl-promise`.\n- Awaits the curl call, converts the raw `CurlRequestOutput` to a `Response` via `curlOutputToResponse(output, request)`.\n- **Success:** if `cb` is provided, calls `cb(undefined, transformedOutput)`; either way resolves the returned promise with `transformedOutput`.\n- **Failure:** if `cb` is provided, calls `cb(e)` and resolves the promise with `undefined` (does **not** reject); if no `cb` is provided, rejects the promise with `e`.\n\n### `requestToCurlOptions(req, settings)` (not exported)\n```ts\nfunction requestToCurlOptions(req: string | Request | RequestOptions, settings: Settings)\n```\n- If `req` is a `string`: treats it as a bare URL, builds a minimal GET request object (`no body`, `noauth`, empty headers/cookies, `settingRebuildPath: true`, `settingSendCookies: true`, `settingFollowRedirects` derived from `settings.followRedirects ? 'on' : 'off'`), with `requestId` prefixed `pre-request-script-adhoc-req-simple:<uuid>`.\n- If `req` is a `Request` instance or a plain object (`RequestOptions`): coerces to a `Request` (`new Request(req)` if not already one), derives `mimeType` from `finalReq.body.mode` (`raw`→`text/plain`, `file`→`application/octet-stream`, `formdata`→`multipart/form-data`, `urlencoded`→`application/x-www-form-urlencoded`, `graphql`→`application/json`, otherwise `text/plain`), maps headers/body/certificate/auth into the curl request shape, with `requestId` = `finalReq.id` or a generated `pre-request-script-adhoc-req-custom:<uuid>`.\n- Auth mapping beyond `noauth` is largely commented out / marked `TODO` (see Gotchas).\n- Throws `Error('the request type must be: string | Request | RequestOptions.')` for any other input shape.\n\n### `curlOutputToResponse(result, request)` (not exported)\n```ts\nasync function curlOutputToResponse(\n  result: CurlRequestOutput,\n  request: string | Request | RequestOptions,\n): Promise<Response>\n```\n- Throws if `result.headerResults` is empty, or if `result.patch.error` is set (re-throws that error), or if there's no last redirect entry.\n- Normalizes `request` into a `Request` instance for `originalRequest`.\n- Extracts headers from the last redirect's `headerResults` entry; parses any `Set-Cookie` headers via `tough-cookie`'s `Cookie.parse(..., { loose: true })` into cookie option objects (filtering out unparsable ones).\n- If `result.responseBodyPath` is absent, returns a `Response` with `body: ''`.\n- Otherwise reads the body via `services.helpers.readCurlResponse({ bodyPath, bodyCompression })`; throws if that read reports an error; otherwise returns a fully populated `Response` (`code`, `reason`, `header`, `cookie`, `body`, `responseTime: result.patch.elapsedTime`, `originalRequest`). `stream` is always left `undefined` (\"because it is inaccurate to differentiate if body is binary\").\n\n## Script-facing surface\n- `pm.sendRequest(url, (err, response) => { ... })` — string URL, GET request, callback style.\n- `pm.sendRequest({ url, method, header, body, auth, ... }, (err, response) => { ... })` — full `RequestOptions`-shaped object.\n- `pm.sendRequest(requestInstance, (err, response) => { ... })` — passing an existing `Request` instance (e.g. built via `new Request(...)` or `pm.request`).\n- Can also be used promise-style without a callback: `const response = await pm.sendRequest(url)` (see Gotchas for the resulting error-handling difference).\n- Exposed on the object model as `insomnia.sendRequest(request, cb)`, which is a plain instance method (not proxy-trapped) that forwards to this file's `sendRequest(request, cb, this._settings)`.\n\n## Gotchas / notable behavior\n- **Callback vs. promise error handling differ:** if a `cb` is supplied, network/parsing errors are *swallowed* into the callback (`cb(e)`) and the returned promise still resolves (with `undefined`) — it will not reject. If no `cb` is supplied, the same error instead rejects the returned promise. A script mixing `await pm.sendRequest(url, cb)` with `try/catch` will not catch curl errors, because the promise resolves successfully even on failure when a callback is present.\n- **Environment branching:** `__IS_RENDERER__ ? window.bridge.curlRequest : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest` — the actual network call goes through completely different code paths depending on whether the script executes in the renderer (Electron IPC bridge) or Node/main context. Bugs that only reproduce in one context (e.g. inso CLI vs. the desktop app) may live in this branch.\n- **Auth beyond `noauth` is not wired up** for the custom-`Request` branch: there's a large commented-out block (`// const authHeaders = ...`) showing API-key/bearer header injection was planned but not implemented; `fromPreRequestAuth(finalReq.auth)` is still called and passed through as `authentication`, but the `authHeader` field of the curl options is always `undefined` with a `// TODO: add this for bearer and other auth methods` comment.\n- **Certificates:** only a single client certificate is forwarded (`finalReq.certificate`), built directly into the curl options' `certificates` array; several fields (`disabled`, `isPrivate`, `_id`, `type`, `parentId`, `modified`, `created`, `name`) are hardcoded to empty/`false`/`0` since they're \"unused fields because they are not persisted\".\n- **Cookies are not populated from a jar** — `cookieJar: { cookies: [] }` and `cookies: []` are always empty in the outgoing request; the comment notes \"currently cookies should be handled by user in headers\". Response `Set-Cookie` headers are still parsed back out via `tough-cookie`, though.\n- **`suppressUserAgent` detection** only checks for a *disabled* `User-Agent` header (`h.key.toLowerCase() === 'user-agent' && h.disabled === true`) — an enabled custom `User-Agent` header does not suppress the default one; it presumably just overrides via normal header precedence.\n- **Response body/stream:** `stream` is always `undefined` in the returned `Response`, even though `CurlRequestOutput`/`ResponseOptions` support it, specifically to avoid ambiguity between binary and text bodies — callers should rely on `body` (a string) only.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request`, `RequestOptions`, `RequestBody`/`RequestBodyOptions` types this function consumes as input.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response` class this function constructs as output.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used to translate the SDK's auth model into the curl request's `authentication` field.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `CookieOptions` type used for parsed `Set-Cookie` results.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — exposes this function as `insomnia.sendRequest(request, cb)`, supplying `this._settings` as the `settings` argument.\n- `insomnia/src/main/network/libcurl-promise` (`curlRequest`, `CurlRequestOutput`) — the main/Node-side curl execution path, dynamically imported when not running in the renderer.\n- `window.bridge.curlRequest` — the renderer-side IPC bridge equivalent used when `__IS_RENDERER__` is true.\n- `insomnia-data` (`services.helpers.readCurlResponse`, `Settings` type) — used to read the response body from disk (`responseBodyPath`) and for the `Settings` (e.g. `followRedirects`) passed in.\n","isInternal":false,"tokens":2047,"sizeBytes":8632},{"name":"test.md","path":".claude/skills/fix-scripting-feature/references/objects/test.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/test.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Test handler (pm.test / test())\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/test.ts`\n\n## Purpose\nImplements the `test()`/`skip()` handler that backs `pm.test(...)` (and `insomnia.test(...)`), plus the bookkeeping used by the script-execution harness to know when all in-flight test callbacks have settled before the script finishes. This is the only file responsible for turning a user's test callback into a `RequestTestResult` record.\n\n## Public API\n\n### `test(msg, fn, log)`\n```ts\nexport async function test(\n  msg: string,\n  fn: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Wraps `fn` in an async closure (`wrapFn`), times its execution with `performance.now()`, awaits it, and calls `log(...)` with a `RequestTestResult`:\n  - On success: `{ testCase: msg, status: 'passed', executionTime, category: 'unknown' }`.\n  - On thrown error `e`: `{ testCase: msg, status: 'failed', executionTime, errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, category: 'unknown' }`.\n- Immediately invokes `wrapFn()`, registers the resulting promise with `startTestObserver`, and returns that same promise (`testPromise`) — i.e. `test()` itself resolves once the wrapped test body (pass or fail) has finished running.\n- `category` is always hardcoded to `'unknown'` here even though `RequestTestResult.category` (in `insomnia-data`) also allows `'pre-request'` and `'after-response'`.\n\n### `skip(msg, _, log)`\n```ts\nexport async function skip(\n  msg: string,\n  _: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Does **not** call `fn` (the second argument is ignored). Immediately calls `log({ testCase: msg, status: 'skipped', executionTime: 0, category: 'unknown' })`.\n\n### `resetTestPromises()`\n```ts\nexport function resetTestPromises(): void\n```\n- Clears the module-level `testPromises` array (`testPromises = []`). Used to reset state between script executions so promises from a previous run aren't awaited again.\n\n### `waitForAllTestsDone()`\n```ts\nexport async function waitForAllTestsDone(): Promise<void>\n```\n- Awaits `Promise.allSettled(testPromises)` (captured as `NativePromise` at module load, so it isn't affected by any sandbox-level `Promise` patching), then resets `testPromises` back to `[]`.\n- This is the drain point the execution harness calls at the end of a script run to make sure every `pm.test(...)` callback (including ones the script itself didn't `await`) has finished before results are collected.\n\n### `startTestObserver(promise)` (not exported)\n```ts\nfunction startTestObserver(promise: Promise<void>): void\n```\n- Pushes `promise` onto the module-level `testPromises` array. Called once per `test()` invocation.\n\n### `TestHandler` interface\n```ts\nexport interface TestHandler {\n  (msg: string, fn: () => Promise<void>): Promise<void>;\n  skip?: (msg: string, fn: () => Promise<void>) => void;\n}\n```\n- Callable interface shape used by the object model to type the value returned for `pm.test` — a function that also carries an optional `.skip` method.\n\n## Script-facing surface\n- `pm.test('name', async () => { ... })` / `insomnia.test('name', fn)` — registers and immediately runs a test, recording a pass/fail result.\n- `pm.test.skip('name', fn)` — records a `skipped` result without running `fn`.\n- There is no bare global `test`/`skip` exposed directly by this file; script-facing exposure happens through the `insomnia`/`pm` object (see Related).\n\n## Gotchas / notable behavior\n- **Fire-and-forget by default:** `test()` starts executing the test body synchronously (well, as soon as the async function runs) and returns a promise, but scripts are not required to `await pm.test(...)`. Because every test promise is also pushed into the shared `testPromises` array via `startTestObserver`, un-awaited tests are still tracked and will be waited on by `waitForAllTestsDone()` before the script finishes — this is what prevents test results from being lost when a script fires multiple `pm.test()` calls without awaiting them.\n- **Module-level mutable state:** `testPromises` is a module-level array, not per-execution-context state. `resetTestPromises()` must be called before a script runs and `waitForAllTestsDone()` after, or results/timing from different executions could leak into each other (relevant if concurrent script executions ever share this module instance).\n- **Error message shape assumes chai-style errors:** the failure branch does `` `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}` ``, which assumes `e` has `.actual`/`.expected` (true for chai `AssertionError`s from `insomnia.expect(...)`). For a plain thrown `Error` or non-chai exception, `ACTUAL`/`EXPECTED` will stringify as `undefined`.\n- **`category` is never anything but `'unknown'`** from this module — any pre-request vs. after-response distinction in `RequestTestResult.category` must be set elsewhere (or is currently unused/always `'unknown'` for script-originated tests).\n- **`NativePromise` capture:** `const NativePromise = Promise;` is captured at module import time specifically so `waitForAllTestsDone`'s `allSettled` call uses the real, un-sandboxed `Promise`, not a possibly-patched one from the script sandbox.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — wires `test`/`skip` into the `InsomniaObject`. The constructor returns a `Proxy` whose `get` trap intercepts `prop === 'test'` and returns a `TestHandler` closure calling `this._test(msg, fn, this.pushRequestTestResult)` (and `.skip` calling `this._skip(...)`); `pushRequestTestResult` appends to `this.requestTestResults: RequestTestResult[]`, later returned from `toObject()`.\n- `packages/insomnia-scripting-environment/src/objects/async-objects.ts` — calls `resetTestPromises()` once at module load alongside its own promise-tracking logic for `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) and `packages/insomnia/src/scripting/run-script.ts` — the harness that injects `__waitForAllTestsDone__` into the generated script function and awaits it (`await __waitForAllTestsDone__();`) before returning the mutated `insomnia` object; `run-script.ts` then reads `mutatedInsomniaObject.toObject().requestTestResults` and includes it in the returned `RequestContext`.\n- `packages/insomnia-data` (`RequestTestResult`, `TestStatus`, `TestCategory` in `src/models/runner-test-result.ts`) — the result shape this file produces.\n- UI consumers of the resulting test results: `packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx` and `request-result-card.tsx`.\n","isInternal":false,"tokens":1627,"sizeBytes":6684},{"name":"urls.md","path":".claude/skills/fix-scripting-feature/references/objects/urls.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/urls.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Url, QueryParam, UrlMatchPattern\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/urls.ts`\n\n## Purpose\n\nModels a request URL (`Url`), its individual query-string parameters (`QueryParam`), and Chrome-extension-style match patterns (`UrlMatchPattern` / `UrlMatchPatternList`) used to test whether a URL matches a proxy bypass rule or a client-certificate host rule. `pm.request.url` is a `Url`; `UrlMatchPattern` backs `ProxyConfig.match` and `Certificate.matches`.\n\n## Public API\n\n### `setUrlSearchParams(provider: any)`\nModule-level override hook: replaces the `URLSearchParams` implementation used internally by `QueryParam` (defaults to the global `URLSearchParams`). Test-only utility, not script-facing.\n\n### `QueryParamOptions` (interface)\n`{ key: string; value?: string; type?: string; multiline?: string | boolean; disabled?: boolean; fileName?: string }`\n\n### `QueryParam` (extends `Property`)\n- `override _kind = 'QueryParam'`\n- `key: string`, `value?: string`, `type?: string`, `multiline?: string | boolean`, `fileName?: string` (the latter two are noted in a comment as \"properties from Insomnia... added here to avoid being dropped\").\n- `constructor(options: QueryParamOptions | string)` — if `options` is a string, it's `JSON.parse`d (wraps parse errors as `Error('invalid QueryParam options ...')`); if it's an object with `key`+`value`, fields are copied directly; otherwise **throws** `Error('unknown options for new QueryParam')`.\n- `static override _index = 'key'`\n- `static parse(queryStr: string): {key, value}[]` — parses a query string via `URLSearchParams`.\n- `static parseSingle(paramStr: string, _idx?, _all?): {key, value}` — parses a single `key=value` pair; **throws** if `QueryParam.parse` yields nothing.\n- `static unparse(params: QueryParamOptions[] | Record<string, string>): string` — builds a query string via `URLSearchParams`, URL-encoding as it goes.\n- `static unparseSingle(obj: {key, value}): string | {}` — returns `''`-joined encoded `\"key=value\"` if the input has both `key` and `value`, otherwise returns `{}` (an empty object, not a string — inconsistent return type).\n- `toString(): string` — URL-encodes via `URLSearchParams` (e.g. spaces become `+`).\n- `toRawString(): string` — `\"key=value\"` with **no encoding**.\n- `update(param: string | {key, value, type?})` — parses a string form via `parseSingle` (only sets `key`/`value`, coercing non-string results to `''`) or copies `key`/`value`/`type` from an object; **throws** on any other input shape.\n\n### `UrlOptions` (interface)\n`{ id?: string; auth?: {username, password}; hash?: string; host: string[]; path?: string[]; port?: string; protocol: string; query: {key, value}[]; variables: {key, value}[] }`\n\n### `Url` (extends `PropertyBase`)\n- `override _kind = 'Url'`\n- `id?: string`\n- Getters (all derived from an internal `URL` object, `this.urlObject`, which is `undefined` if the input couldn't be parsed as a URL — e.g. contains unrendered `{{ }}`/`{% %}` template tags): `auth` (`{username, password}` or `undefined`), `hash` (without leading `#`), `host` (hostname split on `.`), `path` (pathname segments, empty ones filtered out), `port`, `protocol`, `query: PropertyList<QueryParam>` (a **new** `PropertyList` wrapping the current query params on every access), `variables: string[]` (**always returns `[]`** — \"TODO: it's usage is unknown\").\n- `constructor(def: UrlOptions | string)`\n- `private initFields(urlOptions)` — parses `def`; if it's a string containing a template tag (`checkIfUrlIncludesTag`), it's kept as an opaque `origin` string and `urlObject` stays `undefined` (to avoid mangling `{% uuid 'v4' %}`-style tags); if it's an object, a URL string is assembled from `protocol`/`auth`/`host`/`port`/`path`/`query`/`hash` (protocol defaults to `'https://'` if blank). Query params are always parsed out into `this.queryParams` (a private array) separately from the `URL` object (whose own `search` is cleared) — \"query params are handled separately as URL object encodes content\".\n- `static _index = 'id'`\n- `static isUrl(obj: object): boolean`\n- `static parse(urlStr: string): UrlOptions | undefined` — returns `undefined` if `URL.canParse` fails; `variables` is always `[]` in the result.\n- `addQueryParams(params: QueryParamOptions[] | string)` — string form splits on `&` then `=` (not URL-decoded); array form copies each entry into a new `QueryParam`. **Throws** `TypeError` on other input.\n- `getHost(): string` — `''` if the URL didn't parse.\n- `getPath(_unresolved?): string` — `_unresolved` param is accepted but unused; `''` if unparsed.\n- `getPathWithQuery(): string` — path + `?` + query string (or just the query string if path is blank).\n- `getQueryString(): string` — joins **enabled** (`!disabled`) query params via `toRawString()` (i.e. **unencoded**, unlike `QueryParam.toString()`).\n- `getRemote(_forcePort?): string` — `_forcePort` is accepted but unused; returns `urlObject.host` (which already includes the port when non-default) or `''`.\n- `removeQueryParams(params: QueryParam[] | string[] | string)` — filters `this.queryParams` by key; **throws** `TypeError` on unrecognized input shape.\n- `override toString(_forceProtocol?): string` — `_forceProtocol` accepted but unused; rebuilds the URL string with the current query string; falls back to the opaque `origin` string if unparsed; special-cases avoiding an added trailing `/` for root-path URLs.\n- `toStringWithoutQuery(_forceProtocol?): string` — same as `toString()` but with `search` cleared.\n- `update(url: UrlOptions | string)` — re-runs `initFields`.\n\n### `UrlMatchPattern` (extends `Property`)\nImplements [Chrome extension match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns) (`scheme://host/path`, wildcards, `<all_urls>`); comment notes it doesn't support top-level-domain wildcards, and that URLs can't start with `-` (unenforced).\n- `override id = ''`\n- `constructor(pattern: string)`\n- `static override _index = 'id'`\n- `static readonly MATCH_ALL_URLS = '<all_urls>'`\n- `static pattern: string | undefined = undefined` — declared but unused (\"TODO: its usage is unknown\"); shadowed by (unrelated to) the instance's private `pattern`.\n- `static readonly PROTOCOL_DELIMITER = '+'` — multiple schemes are written as `http+https+custom://...`.\n- `getProtocols(): string[]` — `['http', 'https', 'file']` for `<all_urls>`; otherwise splits the scheme segment on `+`.\n- `test(urlStr: string): boolean` — true only if protocol, host, path, and port all match.\n- `testHost(hostStr: string): boolean` — segment-by-segment comparison against the pattern's host (split on `.`); a segment of `*` matches anything; segment **counts must match exactly** (no subdomain-wildcard-prefix support beyond single-segment `*`).\n- `testPath(pathStr: string): boolean` — segment-by-segment comparison (split on `/`); segment counts must match exactly; `*` matches any single segment.\n- `testPort(port: string, protocol: string): boolean` — returns `false` immediately if `testProtocol(protocol)` fails; `*` in the pattern matches any port; if no port is specified in either the pattern or input, falls back to protocol-default-port logic for `http`/`https` only.\n- `testProtocol(protocol: string): boolean` — `*` in the pattern matches any protocol.\n- `override toString(): string` — returns the raw pattern string.\n- `update(pattern: string)`\n\n### `UrlMatchPatternList<T extends UrlMatchPattern>` (extends `PropertyList<T>`)\n- `override _kind = 'UrlMatchPatternList'`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])`\n- `static isUrlMatchPatternList(obj: any): boolean`\n- `test(urlStr: string): boolean` — true if **any** pattern in the list matches.\n\n### Module-level functions\n- `toUrlObject(url: string | Url): Url` — **throws** `Error('Request URL is not specified')` if `url` is falsy; passes through an existing `Url` unchanged, otherwise constructs a new one from a string.\n- `resolveProtocolForProxy(rawUrl: string): string` — resolves the protocol to use for proxy selection when the URL may still contain unrendered template tags (pre-request scripts run before rendering); tries `new URL(rawUrl).protocol` first, falling back to `'https:'` on parse failure.\n\n## Script-facing surface\n\n`pm.request.url` is a `Url` instance. Common script usage:\n- `pm.request.url.toString()`, `pm.request.url.getHost()`, `pm.request.url.getPath()`, `pm.request.url.query`\n- `pm.request.url.addQueryParams([{key, value}])` / `pm.request.addQueryParams(...)` (delegates)\n- `pm.request.url.removeQueryParams('key')` / `pm.request.removeQueryParams(...)` (delegates)\n- `pm.request.url.hash`, `.host`, `.port`, `.protocol`, `.auth`\n\n`QueryParam` instances populate `pm.request.url.query` and `RequestBody.urlencoded`.\n\n`UrlMatchPattern`/`UrlMatchPatternList` aren't constructed directly from a typical script, but are reachable via `pm.request.certificate.matches` (see `certificates.ts`), which stores its match rule(s) as a `UrlMatchPatternList` directly. `pm.request.proxy.match` (see `proxy-configs.ts`) is different: it's a raw `string` on the instance, with a `UrlMatchPattern` built on the fly, temporarily, inside `ProxyConfig.test()`/`.getProtocols()` — and `ProxyConfig.bypass` is a plain `string[]` checked by exact string equality, not a `UrlMatchPatternList` at all (that type only appears as an unused `static bypass` field on the class, separate from the instance field of the same name).\n\n## Gotchas / notable behavior\n\n- `Url.variables` **always returns `[]`**, even though `UrlOptions.variables` and `Url.parse()`'s return both have a `variables` field — the field is accepted on input but silently discarded; there is no way to read it back out.\n- `Url`'s internal `urlObject` (a real `URL`) is `undefined` whenever the input string contains a template tag (`{{ }}` / `{% %}`) or otherwise fails `URL.canParse` — in that case nearly every getter/method (`host`, `path`, `port`, `protocol`, `getHost()`, `getRemote()`, etc.) silently returns an empty string/array instead of throwing. Only `toString()`/`toStringWithoutQuery()` fall back to the raw `origin` string.\n- Query-string encoding is inconsistent by design: `QueryParam.toString()` URL-encodes (via `URLSearchParams`), but `QueryParam.toRawString()` and `Url.getQueryString()` (which uses `toRawString()`) do **not** encode. `Url.addQueryParams(stringForm)` also splits on raw `&`/`=` without decoding.\n- `QueryParam.unparseSingle()` returns `{}` (not a string) when the input lacks `key`/`value` — inconsistent return type (`string | {}`) that callers must handle.\n- Several methods accept parameters that are **entirely unused**: `Url.getPath(_unresolved)`, `Url.getRemote(_forcePort)`, `Url.toString(_forceProtocol)`, `Url.toStringWithoutQuery(_forceProtocol)`, `QueryParam.parseSingle(_idx, _all)`. Passing anything for these has no effect.\n- `UrlMatchPattern.static pattern` (class-level) is unused/dead and easy to confuse with the instance's own private `pattern` field.\n- `UrlMatchPattern.testHost`/`testPath` require the **segment counts to match exactly** — a pattern like `*.insomnia.com` (2 segments) will not match `bin.download.insomnia.com` (4 segments), confirmed by the test suite (`urls.test.ts`).\n- `UrlMatchPattern` has no protocol-agnostic short-circuit: calling `testProtocol` on a pattern with no `://` returns `[]` for `getProtocols()`, meaning `testProtocol` always returns `false` for such a malformed pattern (per `urls.test.ts`, \"no protocol\" case).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes; `PropertyList.toObject()`'s fallback comment specifically calls out `UrlMatchPatternList` as a list with no natural key.\n- `packages/insomnia-scripting-environment/src/objects/utils.ts` — `checkIfUrlIncludesTag`, used by `Url.initFields` to avoid mangling template tags.\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.url: Url`; `toUrlObject` used when constructing a `Request`; `RequestBody.urlencoded: PropertyList<QueryParam>`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate.matches: UrlMatchPatternList<UrlMatchPattern>`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig.match: string` is used to construct a `UrlMatchPattern` on demand inside `test()`/`getProtocols()`; the instance's `ProxyConfig.bypass` is a plain `string[]` (exact-match only), not a `UrlMatchPatternList`.\n- `packages/insomnia-scripting-environment/src/objects/collection.ts` — re-exports `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` as part of the public collection API surface.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — uses `resolveProtocolForProxy` and `toUrlObject` in `initInsomniaObject`.\n","isInternal":false,"tokens":3261,"sizeBytes":12978},{"name":"utils.md","path":".claude/skills/fix-scripting-feature/references/objects/utils.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/utils.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Utils (misc helpers)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/utils.ts`\n\n## Purpose\nSingle-function utility module. Currently contains only a helper used to detect whether a request\nURL still contains unresolved template-tag syntax, which affects client-certificate matching in\n`insomnia.ts`.\n\n## Public API\n\n### `function checkIfUrlIncludesTag(url: string): boolean`\n```ts\nexport function checkIfUrlIncludesTag(url: string): boolean {\n  return /{%/.test(`${url}`) || /%}/.test(`${url}`) || /{{/.test(`${url}`) || /}}/.test(`${url}`);\n}\n```\nReturns `true` if the given `url` (coerced to a string via template literal) contains any of the\nfour Liquid/interpolation delimiter substrings: `{%`, `%}`, `{{`, `}}`. Used as a proxy for \"this\nURL hasn't been fully rendered yet\" / \"this URL has dynamic template parts that can't be resolved\nagainst a fixed hostname\".\n\n## Script-facing surface\nNone. This is internal plumbing — it is not exposed on `insomnia`/`pm` and scripts cannot call it.\n\n## Gotchas / notable behavior\n- The check is purely textual (four independent regex tests, not a real template parser) — it will\n  return `true` for a URL that merely contains a literal `{{` or `}}` substring even if it isn't\n  actually meant as an interpolation tag.\n- `` `${url}` `` coercion means passing `undefined`/`null`/non-string values won't throw; they'll be\n  stringified first (e.g. `undefined` becomes the string `\"undefined\"`, which does not match any\n  pattern, so the function returns `false`).\n\n## Related\n- `insomnia.ts` — the only current caller. `initInsomniaObject` uses `checkIfUrlIncludesTag(rawObj.request.url)` together with `filterClientCertificates` (from `insomnia/src/network/certificate`) to decide whether to fall back to an empty default client certificate (since a templated URL's real host can't be matched against certificate rules yet).\n","isInternal":false,"tokens":458,"sizeBytes":1893},{"name":"variables.md","path":".claude/skills/fix-scripting-feature/references/objects/variables.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/references/objects/variables.md","title":"Objects Skill","category":"anthropic-skill","format":"markdown","content":"# Variable / VariableList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/variables.ts`\n\n## Purpose\nThis file defines the low-level key/value model — `Variable` (a single named, typed value) and `VariableList` (an ordered, keyed collection of `Variable`s) — built on top of `Property`/`PropertyList` from `properties.ts`. Unlike `Environment` (a `Map`-backed flat store used for `pm.environment`/`pm.globals`), `Variable`/`VariableList` is the object-per-entry model used elsewhere in the SDK wherever a Postman-compatible \"list of variable-shaped items\" is needed (e.g. request path parameters); it is not the type behind `insomnia.environment` or `insomnia.variables`.\n\n## Public API\n\n### `interface VariableDefinition`\n```ts\ninterface VariableDefinition {\n  id?: string;\n  key: string;\n  name?: string;\n  value: string;\n  type?: string;\n  disabled?: boolean;\n}\n```\nShape used to construct a `Variable`.\n\n### `class Variable extends Property`\n```ts\nconstructor(def?: VariableDefinition)\n```\nCalls `super()` (no args passed to `Property`'s constructor, so `Property`'s own `id`/`name`/`disabled` defaults are set first, then overwritten below). When `def` is provided: `id = def.id || ''`, `key = def.key`, `name = def.name`, `value = def.value`, `type = def.type || 'Variable'`, `disabled = def.disabled`. When `def` is omitted: `id = ''`, `key = ''`, `name = undefined`, `value = ''`, `type = 'Variable'`, `disabled = false`.\n\n- `key: string` — the variable's lookup key.\n- `value: any` — the stored value (any type, despite `VariableDefinition.value` being typed `string`).\n- `type: string` — defaults to `'Variable'`.\n- `override _kind = 'Variable'` *(`@ignore`)*.\n- `static override _index = 'key'` *(`@ignore`)* — tells `PropertyList`/`indexOf`/`one` to index `Variable`s by `key` instead of the default `id`.\n- `static types()` — **throws** `unsupportedError('types')` unconditionally; not implemented.\n- `cast(value: any)` — if `value` has `_kind === 'Variable'`, returns `value.value` (unwraps a `Variable` to its raw value); otherwise returns `undefined`.\n- `get()` — returns `this.value`.\n- `set(value: any)` — sets `this.value = value`.\n\n### `class VariableList<T extends Variable> extends PropertyList<T>`\n```ts\nconstructor(parent: PropertyList<T> | undefined, populate: T[])\n```\nCalls `super(Variable, undefined, populate)` (always uses `Variable` as `typeClass`, ignoring `T`'s actual class for indexing purposes), then sets `this.parent = parent`.\n\n- `override _kind = 'VariableList'` *(`@ignore`)*.\n- `static isVariableList(obj: any): boolean` — `'_kind' in obj && obj._kind === 'VariableList'`.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, any>` — builds a plain object keyed by each `Variable.key`:\n  - skips entries where `excludeDisabled && variable.disabled`.\n  - if `multiValue` is true and the key already exists in the output, coalesces values into an array (`[existing, value]`, growing it on further duplicates).\n  - otherwise, later entries with the same key overwrite earlier ones (last-write-wins) when `multiValue` is falsy.\n  - `_caseSensitive` and `_sanitizeKeys` are accepted for signature compatibility but unused.\n\nAll other list operations (`add`, `all`, `append`, `assimilate`, `clear`, `count`, `each`, `filter`, `find`, `get`, `has`, `idx`, `indexOf`, `insert`, `insertAfter`, `map`, `one`, `populate`, `prepend`, `reduce`, `remove`, `repopulate`, `toString`, `upsert`) are inherited unmodified from `PropertyList` — see `properties.md`. Because `Variable._index = 'key'`, `indexOf`/`one`/`upsert`/`has` on a `VariableList` match by `key`, not `id`.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` on its own — `insomnia.environment` and `insomnia.variables` are backed by `Environment`/`Variables` (see `environments.md`), not by `Variable`/`VariableList`. `Variable`/`VariableList` are the generic building blocks other collection-shaped properties reuse (e.g. anywhere the SDK needs an ordered, keyed set of `{key, value}` items with Postman-style list semantics such as `.upsert()`/`.toObject()`). Exported from the package's public surface via `index.ts` (`export { Variable, VariableList } from './variables'`) and re-exported under the `Collection` namespace via `collection.ts`, so consumers of the SDK's type declarations can reference `Collection.Variable`/`Collection.VariableList`.\n\n## Gotchas / notable behavior\n- `Variable.types()` is a stub that always throws `unsupportedError('types')` — calling it will break a script; it exists only for API-shape completeness.\n- `Variable.cast(value)` assumes `value` is an object with `in` support (`'_kind' in value`) — passing a primitive (e.g. `cast(5)`) will throw a `TypeError` since `in` requires an object operand on the right-hand side. There's no type guard before that check.\n- `VariableList`'s constructor hardcodes `Variable` as the `typeClass` regardless of the generic `T`, so subclasses of `Variable` (if any) still get indexed as plain `Variable` for `_index` purposes.\n- `VariableList.toObject()` **includes disabled variables by default** — callers must explicitly pass `excludeDisabled = true` to filter them out (confirmed by `variables.test.ts`: `toObject()` returns `{h1: 'v1', h2: 'v2'}` even when `h2` is disabled).\n- Duplicate keys: `toObject()` silently drops earlier duplicates unless `multiValue` is passed, in which case duplicates become an array under a single key — this can be surprising if a script assumes object-key uniqueness maps 1:1 to list length.\n\n## Related\n- `properties.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>`; inherits nearly all list/property behavior from there.\n- `environments.ts` — conceptually parallel/alternate model (`Environment`/`Variables`) that is what `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables` actually use; do not confuse the two when debugging variable-resolution issues.\n- `index.ts` / `collection.ts` — re-export `Variable`/`VariableList` as part of the SDK's public/namespaced surface.\n","isInternal":false,"tokens":1558,"sizeBytes":6186},{"name":"SKILL.md","path":".claude/skills/fix-scripting-feature/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-scripting-feature/SKILL.md","title":"fix-scripting-feature","category":"anthropic-skill","format":"markdown","content":"---\nname: fix-scripting-feature\ndescription: 'Debug and fix issues in Insomnia''s pre-request/after-response/test scripting feature (the pm/insomnia API surface). Use when scripts throw sandbox violations, timeouts, module import errors, or produce wrong request/response/variable mutations.'\nargument-hint: 'Provide the failing script snippet, the error message, and whether it happened in the app (hidden window) or inso (CLI)'\n---\n\n# Fix Scripting Feature Issues\n\n## When to Use\n\n- A pre-request script, after-response script, or test script (`pm.*` / `insomnia.*` API) fails, throws, or produces incorrect results.\n- Errors like `SECURITY_POLICY_VIOLATION`, `no module is found for \"...\"`, `Timeout: Running script took too long`, or `insomnia object is invalid or script returns earlier than expected.`\n- `insomnia-scripting-environment` SDK objects (`Request`, `Response`, `Environment`, `Variables`, `Collection`, `test()`) behave unexpectedly.\n\n## Architecture (read this first)\n\nPre-request/after-response/test scripts (the Postman-compatible `pm`/`insomnia` API) have **three execution paths** depending on where the script runs:\n\n| Path | Entry point | Sandboxing |\n|---|---|---|\n| Electron app (default) | `packages/insomnia/src/entry.hidden-window.ts` → `packages/insomnia/src/scripting/run-script.ts`, run in a hidden `BrowserWindow` (see `packages/insomnia/src/main/window-utils.ts`) | AST-based via `packages/insomnia/src/scripting/sandbox.ts` + `script-security-rules.ts` + `require-interceptor.ts`, toggled by `context.settings.scriptSandboxEnabled` (default on) |\n| Electron app, opt-in QuickJS sandbox | `packages/insomnia/src/network/concurrency.renderer.ts` → `packages/insomnia/src/scripting/run-script-quickjs.ts`, run in a Web Worker over a WASM QuickJS VM (`quickjs-script-engine.ts`) | Gated by `settings.useQuickJsScriptSandbox` (default off). PoC with a **deliberately minimal, hand-rolled `insomnia`/`$` API** — not the shared SDK below — with no `test()`/`pm.test()`, `collectionVariables`, vault, cookies, client certificates, or request mutation, and a `sendRequest()` limited to plain-text bodies |\n| `inso` CLI and other Node-runtime callers (e.g. `packages/insomnia/src/runtimes/network/network-adapter.node.ts`) | `packages/insomnia/src/script-executor.ts` (plain `AsyncFunction`/`eval`, no Electron) | None — `script-executor.ts` never calls `sandbox.ts`'s `prepareSandbox`, it's a structurally separate implementation. (Don't confuse this with `canSandbox = !!process.type` in `network-adapter.node.ts` — that gates **plugin** request/response hook sandboxing, an unrelated subsystem.) |\n\nThe full SDK object model (`InsomniaObject`, `Request`, `Response`, `Environment`, `Variables`, `Collection`, `Test`, console/send-request shims) lives in `packages/insomnia-scripting-environment/src/objects/` and is shared across the Electron (default) and `inso`/Node paths only — the QuickJS sandbox does not use it.\n\n## `insomnia-scripting-environment` Folder Hierarchy\n\nThe SDK package (`packages/insomnia-scripting-environment/`) is where the `pm`/`insomnia` object model lives — start here for any bug about a specific script API's behavior:\n\n```\ninsomnia-scripting-environment/\n├── src/\n│   ├── objects/                   # the pm/insomnia API surface — one file per SDK concept\n│   │   ├── index.ts               # public re-exports\n│   │   ├── insomnia.ts            # InsomniaObject — the top-level `insomnia`/`pm` instance scripts see\n│   │   ├── interfaces.ts          # RequestContext, IEnvironment — the context shape passed in/out of a script run\n│   │   ├── execution.ts           # Execution — pm.execution (setNextRequest, skipRequest, location)\n│   │   ├── request.ts             # Request, RequestBody — pm.request\n│   │   ├── request-info.ts        # RequestInfo — pm.info (event name, iteration data)\n│   │   ├── response.ts            # Response — pm.response\n│   │   ├── send-request.ts        # pm.sendRequest() implementation\n│   │   ├── environments.ts        # Environment, Variables — pm.environment / pm.globals\n│   │   ├── variables.ts           # Variable, VariableList — underlying key/value model shared by environments/collection\n│   │   ├── collection.ts          # Collection — pm.collectionVariables / folder & collection variable resolution\n│   │   ├── folders.ts             # Folder, ParentFolders — collection folder hierarchy walked for variable resolution\n│   │   ├── properties.ts          # PropertyBase, PropertyList — base classes most SDK objects extend\n│   │   ├── headers.ts             # Header, HeaderList\n│   │   ├── cookies.ts             # Cookie, CookieJar, CookieList — pm.cookies\n│   │   ├── auth.ts                # RequestAuth — pm.request.auth\n│   │   ├── certificates.ts        # Certificate — client cert modeling\n│   │   ├── proxy-configs.ts       # ProxyConfig, ProxyConfigList\n│   │   ├── urls.ts                # Url, QueryParam, UrlMatchPattern — URL parsing/manipulation\n│   │   ├── console.ts             # Console — console.log capture surfaced back to the app\n│   │   ├── test.ts                # pm.test()/test() handler registration + TestHandler, waitForAllTestsDone\n│   │   ├── async-objects.ts       # ProxiedPromise — Promise plumbing so async script code can be awaited by the host\n│   │   ├── interpolator.ts        # template-tag ({{ }}) interpolation used when resolving variable values\n│   │   ├── utils.ts               # misc helpers (e.g. checkIfUrlIncludesTag)\n│   │   └── __tests__/             # tests for selected objects above (request.test.ts, response.test.ts, etc.)\n│   └── autocomplete-snippets.json # generated editor autocomplete data — see scripts/generate-autocomplete.ts\n├── scripts/\n│   └── generate-autocomplete.ts   # regenerates autocomplete-snippets.json from the objects/ source (CI-checked)\n├── docs/                          # TypeDoc-generated API reference (generated from objects/, don't hand-edit)\n├── typedoc.json                   # TypeDoc config for docs/ generation\n└── vitest.config.ts               # test runner config for `npm test -w insomnia-scripting-environment`\n```\n\nIf a bug is about *how* a script executes (sandboxing, timeouts, hidden window vs. `inso`), look in `packages/insomnia/src/scripting/` (see Architecture above). If it's about *what a specific API returns or mutates* (`pm.request.headers`, `pm.environment.get()`, `pm.response.json()`, etc.), it's almost always in this package's `src/objects/`.\n\nFor property/method-level detail on any single object above (real signatures, script-facing surface, gotchas), see `references/objects/<name>.md` in this skill folder — one file per source file in `src/objects/` (e.g. `references/objects/request.md` for `request.ts`, `references/objects/environments.md` for `environments.ts`).\n\n## Procedure\n\n1. **Identify the execution path first** — ask/check whether the failure is in the Electron app or `inso`. Fixes and even error messages differ per path (see table above).\n2. **Reproduce with the narrowest test** before touching app code:\n   - Unit tests for the sandbox/security logic:\n     ```bash\n     npm test -w insomnia -- src/scripting\n     ```\n     (not `npx vitest run src/scripting -w insomnia` — Vitest's own CLI already owns `-w`/`--watch`, so that form drops into watch mode instead of selecting the `insomnia` workspace.)\n     Relevant files: `packages/insomnia/src/scripting/__tests__/sandbox.test.ts`, `__tests__/script-security-policy.test.ts`, `__tests__/require-interceptor.test.ts`.\n   - SDK object-model unit tests:\n     ```bash\n     npm test -w insomnia-scripting-environment\n     ```\n   - Full app unit suite (if narrowing further isn't obvious):\n     ```bash\n     npm test -w insomnia\n     ```\n3. **If the bug only shows up end-to-end** (real app, real request lifecycle), run the Playwright smoke tests:\n   ```bash\n   npm run test:dev -w insomnia-smoke-test -- --grep \"pre-request\"\n   npm run test:dev -w insomnia-smoke-test -- --grep \"after-response\"\n   ```\n   Relevant specs in `packages/insomnia-smoke-test/tests/smoke/`: `pre-request-script-features.test.ts`, `after-response-script-features.test.ts`, `pre-request-script-window.test.ts`.\n4. **Locate the fix by symptom** (see Notes below for the mapping from error message to file).\n5. **If you change the SDK's public API surface** (new/changed methods on `Request`/`Response`/`Environment`/etc. in `insomnia-scripting-environment`), regenerate autocomplete snippets — CI checks this is committed:\n   ```bash\n   npm run generate:autocomplete -w insomnia-scripting-environment\n   ```\n\n## Notes\n\n- Common failure patterns and where to look:\n  - `SECURITY_POLICY_VIOLATION` / script blocked for using `this`, `globalThis`, `__proto__`, `constructor`, or `import` → AST rule in `packages/insomnia/src/scripting/sandbox.ts` (`checkSandboxViolations`) or the rule lists in `script-security-rules.ts`. These are Electron-app-only; `inso` has no AST sandbox at all.\n  - `no module is found for \"...\"` → the module isn't on the allowlist in `packages/insomnia/src/scripting/require-interceptor.ts`. Currently allowed:\n    - Full-access Node builtins: `path`, `assert`, `url`, `punycode`, `querystring`, `string_decoder`, `stream`, `events`\n    - Method-restricted Node builtins (some methods throw): `timers` (`setImmediate` blocked), `buffer` (`allocUnsafe`/`allocUnsafeSlow` blocked), `util` (`inherits`/`debuglog` blocked)\n    - Shims: `atob`, `btoa`\n    - External/npm modules: `ajv`, `chai`, `cheerio`, `crypto-js`, `csv-parse/lib/sync` (note: not bare `csv-parse`), `lodash` (backed by `es-toolkit/compat`), `moment`, `tv4`, `uuid`, `xml2js`\n    - Special-cased: `insomnia-collection` / `postman-collection` (resolves to the SDK's own `Collection` module)\n\n    Adding a module means adding it here, not just installing the npm package.\n  - `Timeout: Running script took too long` (Electron app) → default 5000ms in `entry.hidden-window.ts:39` (`data.context.timeout`).\n  - `insomnia object is invalid or script returns earlier than expected.` → thrown when the wrapped `AsyncFunction` doesn't resolve to an `InsomniaObject` instance; check both `run-script.ts` and `script-executor.ts` since the same invariant is duplicated in each path.\n  - Hidden window \"closed unexpectedly\" / \"froze\" / busy errors → restart/health-check race conditions in `packages/insomnia/src/main/window-utils.ts` (`hiddenWindowIsBusy`, `createHiddenBrowserWindow`).\n  - Works in unit tests but not in CI → confirm `npm run generate:autocomplete -w insomnia-scripting-environment` output matches the committed `autocomplete-snippets.json`; `test.yml` diffs this file and fails the build if it's stale.\n- The script behavior should be as consistent as possible with [the original implementation](https://www.postmanlabs.com/postman-collection/tutorial-concepts.html). The implementation is hosted in this [repo](https://github.com/postmanlabs/postman-collection).\n- CI workflows relevant to this feature: `.github/workflows/test.yml` (unit tests + autocomplete snippet check, runs on PR/push to develop), `.github/workflows/test-e2e.yml` (Playwright smoke tests, same triggers).\n- Success criteria: the targeted vitest/Playwright command passes, and if the SDK surface changed, the autocomplete snippet diff is clean.\n","frontmatter":{"name":"fix-scripting-feature","description":"Debug and fix issues in Insomnia's pre-request/after-response/test scripting feature (the pm/insomnia API surface). Use when scripts throw sandbox violations, timeouts, module import errors, or produce wrong request/response/variable mutations.","argument-hint":"Provide the failing script snippet, the error message, and whether it happened in the app (hidden window) or inso (CLI)"},"isInternal":false,"tokens":2734,"sizeBytes":11662},{"name":"SKILL.md","path":".claude/skills/fix-test-cli-ci/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/.claude/skills/fix-test-cli-ci/SKILL.md","title":"fix-test-cli-ci","category":"anthropic-skill","format":"markdown","content":"---\nname: fix-test-cli-ci\ndescription: 'Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.'\nargument-hint: 'Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso'\n---\n\n# Fix test-cli.yml CI Failures\n\n## When to Use\n\n- `.github/workflows/test-cli.yml` or the `Test CLI` workflow failed in CI.\n- `inso` bundle tests fail locally or in GitHub Actions.\n- You suspect node module/runtime differences between Node.js and Electron.\n\n## Procedure\n\n1. Start with the failing CI evidence.\n   - Use the provided logs or workflow run link to identify whether the failure happened during the `test:bundle` run, the packaged binary run, or the build/package setup before the tests.\n   - Note the failing test name and the first actionable error message before reproducing locally.\n2. Ensure Node.js native dependencies are installed (not Electron-targeted variants):\n   ```bash\n   npm run install-libcurl-node\n   ```\n3. Build `insomnia-inso` to generate `dist`:\n   ```bash\n   npm run build -w insomnia-inso\n   ```\n4. Start the smoke-test echo server used by the CLI tests:\n   ```bash\n   npm run serve -w insomnia-smoke-test\n   ```\n5. In another terminal, run the bundled CLI test suite:\n   ```bash\n   npm run test:bundle -w insomnia-inso\n   ```\n6. If the CI failure was in the packaged binary path or the bundle suite passes locally, run:\n   ```bash\n   npm run test:binary -w insomnia-inso\n   ```\n\n## Notes\n\n- Keep the smoke-test server running while bundle tests execute.\n- Common failure patterns:\n  - Errors involving `@getinsomnia/node-libcurl`, `NODE_MODULE_VERSION`, `dlopen`, or native module loading usually mean the Node-targeted libcurl binary needs to be reinstalled with `npm run install-libcurl-node`.\n  - Connection failures to `http://localhost:4010` usually mean the smoke-test server is not running.\n  - Missing `dist` output or missing `binaries/inso` usually means `npm run build -w insomnia-inso` or the package step needs to be rerun.\n- Success criteria:\n  - `npm run test:bundle -w insomnia-inso` exits successfully and Vitest reports the `inso dev bundle` tests as passing.\n  - If validating the packaged binary path, `npm run test:binary -w insomnia-inso` also exits successfully.\n\n## Teardown\n\n- Stop the `npm run serve -w insomnia-smoke-test` process once validation is complete.\n- Reinstall the electron-targeted libcurl binary for continued Electron development:\n  ```bash\n  npm run install-libcurl-electron\n  ```\n","frontmatter":{"name":"fix-test-cli-ci","description":"Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.","argument-hint":"Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso"},"isInternal":false,"tokens":617,"sizeBytes":2627},{"name":"AGENTS.md","path":"packages/insomnia-component-docs/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-component-docs/AGENTS.md","title":"Insomnia-component-docs Skill","category":"anthropic-skill","format":"markdown","content":"# AGENTS.md — Component Docs Site (Docusaurus + MDX)\n\nDocs for Insomnia's shared component library. Prefer the **`component-docs` skill** for authoring —\nit carries the template and full procedure. This file is the quick reference for direct edits.\n\n## Structure\n\n- One MDX file per component: `docs/Components/<name>.mdx` (kebab-case).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Order via\n  `sidebar_position` in frontmatter.\n- Reference doc for format/tone: `docs/Components/button.mdx`.\n\n## Doc format (per file)\n\n1. Frontmatter: `id`, `sidebar_label`, `sidebar_position`.\n2. `# Title` + one-line description.\n3. `## Props` table (prop / type / default / description) + link to the underlying React Aria props page.\n4. `## Usage Examples` — ` ```tsx live ` blocks (live-editable). Wrap multiple elements in a `function`.\n5. `## Styling` → `### CSS Variables` (color vars consumed) + any `theme--*` scope class note.\n\n## ⚠️ Live examples need ReactLiveScope registration\n\n` ```tsx live ` blocks resolve component names from `src/theme/ReactLiveScope/index.ts`, NOT from\nMDX imports. Any component used in a live block must be imported there and added to the\n`ReactLiveScope` object. Library components import from the barrel as `insomnia/src/basic-components`.\n\n## Commands\n\n```bash\ncd packages/insomnia-component-docs\nnpm run start   # local preview while writing\nnpm run build   # verify (catches broken live blocks / bad imports)\n```\n","isInternal":false,"tokens":367,"sizeBytes":1486},{"name":"AGENTS.md","path":"packages/insomnia/src/basic-components/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia/src/basic-components/AGENTS.md","title":"Basic-components Skill","category":"anthropic-skill","format":"markdown","content":"# AGENTS.md — Shared Component Library\n\nThis is Insomnia's shared component library (React Aria + Tailwind). See the design/roadmap\nin https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan\n\n## Conventions\n\n- Build on `react-aria-components` (high-level) first; only drop to `react-aria` hooks when the\n  high-level component can't meet the need (e.g. Tooltip's arbitrary non-focusable trigger).\n- **Colors** use repo CSS variables (`token-(--var)`, e.g. `text-(--color-font)`, `bg-(--hl-sm)`).\n  Everything else (size/spacing/radius/font) uses native Tailwind utilities — do NOT use\n  `--padding-*` / `--radius-*` / `--font-size-*`. Hardcode a color only if no repo variable fits, with a `FIXME`.\n- **Variants** use `tailwind-variants` (`tv`); non-variant components use `cls()` (tailwind-merge).\n- **Preserve `theme--*` scope classes** (`theme--dialog`, `theme--tooltip`, `theme--dropdown__menu`,\n  `theme--link`, …) on the matching component root — they are the per-component theming contract\n  (`src/ui/plugins/misc.ts`). Dropping them silently breaks user theme customization.\n\n## When you add or change a component → update its docs\n\nDocs live in the Docusaurus site at `packages/insomnia-component-docs` (one MDX per component).\nUse the **`component-docs` skill** to create/update the doc. Don't forget to register the component\nin `src/theme/ReactLiveScope/index.ts` or its live examples won't compile.\n\nA component is \"done\" only when its doc + ReactLiveScope entry are in place and the docs site builds\n(`cd packages/insomnia-component-docs && npm run build`).\n","isInternal":false,"tokens":443,"sizeBytes":1663},{"name":"README.md","path":"packages/insomnia-api/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-api/README.md","title":"insomnia-api Documentation","category":"plugin-manifest","format":"markdown","content":"# Insomnia API\n\nThis package contains the API functionality and types for the Insomnia application which could be shared across insomnia and inso-cli.\n\n## Usage\n\n### Install\n\nUses npm workspace, so no need to install.\n\n### Import\n\n```ts\nimport { getUserProfile, getEncryptionKeys, type User, type UserEncryptionKeys } from 'insomnia-api';\n```\n","isInternal":false,"tokens":76,"sizeBytes":343},{"name":"README.md","path":"packages/insomnia-data/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-data/README.md","title":"insomnia-data Documentation","category":"plugin-manifest","format":"markdown","content":"# insomnia-data\n\nA runtime-agnostic data layer for Insomnia, based on interface + IoC.\n\n## Core idea\n\n- `src/`: runtime-agnostic contracts (`IDatabase`, `Services`, model metadata/types)\n- `node-src/`: Node/main concrete implementations (`createNedbDatabase`, `servicesNodeImpl`)\n- entry points wire once:\n  - `initDatabase(impl)`\n  - `initServices(impl)`\n\nAfter wiring, business code always uses the same APIs: `database`, `services`, `models`.\n\n## Process flows\n\n### Database (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initDatabase] --> R1[clientDatabase implementation]\n        R2[feature code] --> R3[database from insomnia-data]\n        R3 --> R1\n        R1 --> R4[window.database.invoke]\n        R4 --> R5[ipcRenderer.invoke database.invoke]\n    end\n\n    subgraph Main\n        M0[initDatabase] --> M1[mainDatabase implementation]\n        M1 --> M2[createNedbDatabase impl]\n        M2 --> M3[(NeDB)]\n        M4[main feature code] --> M5[database from insomnia-data]\n        M5 --> M1\n        M6[ipcMain.handle database.invoke] --> M1\n        M1 --> M7[webContents.send db.changes]\n    end\n\n    subgraph Inso\n        I0[initDatabase] --> I1[inso database implementation]\n        I1 --> I2[createNedbDatabase impl]\n        I2 --> I3[(NeDB)]\n        I4[inso feature code] --> I5[database from insomnia-data]\n        I5 --> I1\n    end\n\n    R5 --> M6\n    M7 -.notify.-> R2\n```\n\n### Services (main / renderer / inso)\n\n```mermaid\nflowchart LR\n    subgraph Renderer\n        R0[initServices] --> R1[preload servicesProxy implementation]\n        R2[feature code] --> R3[services from insomnia-data]\n        R3 --> R1\n        R1 --> R4[ipcRenderer.invoke services.invoke]\n    end\n\n    subgraph Main\n        M0[initServices] --> M1[servicesNodeImpl]\n        M2[feature code] --> M3[services from insomnia-data]\n        M3 --> M1\n        M1 --> M4[service logic]\n        M4 --> M5[database]\n        M5 --> M6[(NeDB)]\n        M7[ipcMain.handle services.invoke] --> M1\n    end\n\n    subgraph Inso\n        I0[initServices] --> I1[servicesNodeImpl]\n        I2[feature code] --> I3[services from insomnia-data]\n        I3 --> I1\n        I1 --> I6[(NeDB)]\n    end\n\n    R4 --> M7\n```\n\nRenderer services path:\n\n`services.xxx` -> preload proxy -> IPC -> main handler -> `servicesNodeImpl` -> database.\n\n## Why this design\n\n- Same API across runtimes: main, renderer, inso.\n- Feature code is decoupled from Electron/IPC/NeDB details.\n- Renderer has a safer boundary (bridge + IPC, no direct DB internals and node API access).\n- Easy to test or swap implementations by injecting at startup.\n\n## Minimal usage\n\n### Main\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { mainDatabase } from '~/main/database.main';\nimport { servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(mainDatabase);\ninitServices(servicesNodeImpl);\n```\n\n### Renderer\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { clientDatabase } from '~/ui/database.client';\n\nawait initDatabase(clientDatabase);\ninitServices(window._dataServices);\n```\n\n### Inso / Node\n\n```ts\nimport { initDatabase, initServices } from 'insomnia-data';\nimport { createNedbDatabase, servicesNodeImpl } from 'insomnia-data/node';\n\nawait initDatabase(createNedbDatabase());\ninitServices(servicesNodeImpl);\n```\n\n### Consuming\n\n```ts\nimport { services, models, type Request } from 'insomnia-data';\n\nconst mcpRequest = await services.mcpRequest.create({ url: 'http://localhost:3000' });\nconst all = await services.mcpRequest.all();\n\nconst request: Request = {};\n\nconst requestType = models.request.type;\n```\n","isInternal":false,"tokens":952,"sizeBytes":3631},{"name":"README.md","path":"packages/insomnia-inso/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-inso/README.md","title":"insomnia-inso Documentation","category":"plugin-manifest","format":"markdown","content":"# Documentation\n\nHow to use [Inso CLI](https://docs.insomnia.rest/inso-cli/introduction).\n\n## Testing\n\n```shell\n# unit tests\nnpm run test:unit\n\n# start smoke test api (required for e2e tests)\nnpm run serve -w insomnia-smoke-test\n\n# e2e tests for dev bundle\nnpm run test:bundle\n\n# e2e tests for binary\nnpm run test:binary\n```\n\n## Development\n\n### Getting started\n\n```shell\nnpm run inso-start\nnpm run test -w insomnia-inso\n# will default to insomnia app database\n$PWD/packages/insomnia-inso/bin/inso run test\n# will use config, useful for testing with fewer args\n$PWD/packages/insomnia-inso/bin/inso -w packages/insomnia-inso/src/db/fixtures/git-repo script runTest\n```\n\n### node-libcurl\n\n`Error: The module '.../insomnia/node_modules/@getinsomnia/node-libcurl/lib/binding/node_libcurl.node' was compiled against a different Node.js version using`\n\nnode-libcurl builds for 3 operating systems and two versions of nodejs. insomnia-inso uses the nodejs build and insomnia app uses the electron build. you can switch between them using the following two commands\n\n```shell\n# install node version\nnpm run install-libcurl-node\n# install electron version\nnpm run install-libcurl-electron\n```\n\n## Run CLI Smoke Tests\n\n```shell\n# Run CLI tests\nnpm run test:bundle -w insomnia-inso\n# Package the Inso CLI binaries\nnpm run inso-package\nnpm run test:binary -w insomnia-inso\n```\n\n## Debugging CLI tests using watcher\n\nThis is helpful for debugging failing api tests\n\nFrom project root, in separate terminals:\n\n```sh\n# start smoke test api\nnpm run serve -w insomnia-smoke-test\n\n# watch inso\nnpm run start -w insomnia-inso\n\n# run api test with dev bundle. To debug run this in a Javascript Debug Terminal in VSCode\n$PWD/packages/insomnia-inso/bin/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n```\n\n## How to debug pkg\n\n```sh\n# run modify package command and then a unit test\nnpm run package -w insomnia-inso && \\\n$PWD/packages/insomnia-inso/binaries/inso run test \"Echo Test Suite\" -w $PWD/packages/insomnia-smoke-test/fixtures/inso-nedb --env Dev --verbose\n\n```\n\n## How to update the `inso-nedb` fixtures\n\nRun Insomnia with `INSOMNIA_DATA_PATH` environment variable set to `fixtures/inso-nedb`, e.g.:\n\n```bash\nINSOMNIA_DATA_PATH=packages/insomnia-smoke-test/fixtures/inso-nedb /Applications/Insomnia.app/Contents/MacOS/Insomnia\n```\n\nRelaunch the app one more time, so that Insomnia compacts the database.\n\nThe `.gitignore` file will explicitly ignore certain database files, to keep the directory size down and avoid prevent sensitive data leaks.\n\n## How to run inso with the `inso-nedb` fixture locally?\n\nSet the `-w` argument pointed to `packages/insomnia-smoke-test/fixtures/inso-nedb`:\n\n```bash\n# if installed globally\ninso -w <INSO_NEDB_PATH>\n\n# using the package bin\n./packages/insomnia-inso/bin/inso -w <INSO_NEDB_PATH>\n\n# using a binary\n./packages/insomnia-inso/binaries/insomnia-inso -w <INSO_NEDB_PATH>\n```\n\n## How to debug the bundled assets\n\n```bash\nDEBUG=1 npm run build\n```\n\nThis will generate an `artifacts` directory containing information about the bundled assets.\nThe meta.json can be uploaded to https://esbuild.github.io/analyze/ to visualize the bundle.\nThe bundle-analysis.log can be used to see the dependency tree of the bundle.\n\n## How to generate documents about inso\n\n1. Ensure your node version matches the project `.nvmrc` perhaps with `fnm use` or another version manager.\n1. Run the script below, which will build inso in dev mode in order to use it to generate docs about itself\n1. The docs appear in your vscode diff, or you can look in `./packages/insomnia-inso/reference/` if the version number looks wrong you might need to check what branch you're on, it should be run from develop ideally as the only changes in the release branch should be hotfixes that dont affect inso docs\n\n```sh\nnpm i && npm run build -w insomnia-inso && $PWD/packages/insomnia-inso/bin/inso generate-docs\n```\n","isInternal":false,"tokens":1019,"sizeBytes":3967},{"name":"README.md","path":"packages/insomnia-smoke-test/playwright/pages/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-smoke-test/playwright/pages/README.md","title":"pages Documentation","category":"plugin-manifest","format":"markdown","content":"# Playwright POM Conventions\n\nThis directory contains the Insomnia smoke-test Page Object Model (POM).\nThe goal is to keep tests stable, readable, and selector-safe.\n\n## 1. Object Definitions\n\n- `InsomniaApp`: root facade. It wires page objects and shared components.\n- `Page` object: route-level surface (for example, project page).\n- `Component` object: reusable or bounded UI region (for example, statusbar, tabbar, sidebar).\n\n## 2. Page vs Component Split\n\n- Put route-specific workflows in a `Page` object.\n- Put reusable or independently testable UI regions in a `Component`.\n- A `Page` composes its own components.\n- `InsomniaApp` composes top-level pages/components and exposes shortcuts.\n- Tests should use POM APIs first. Raw `page.getBy...` is only acceptable for gaps that are not modeled yet.\n\n## 3. Locator Ownership and Naming\n\n- Every page/component must have its own `root` getter.\n- Every POM must manage all locators used by its own actions.\n- Use `xxxLocator(...)` method naming when the locator needs parameters (for example, list row by name).\n- For a single fixed element (for example, `root`, `plusButton`), expose it as a getter directly.\n- Scope locators from `root` whenever possible to reduce collisions.\n- If a locator belongs to an existing POM, tests should not duplicate that selector.\n\n## 4. API Naming\n\n- Action methods use verb-first names: `closeTab`, `openAddTabMenu`.\n- POM exposes actions and locators; assertions stay in test files.\n- Keep methods focused on one behavior.\n\n## 5. Test Usage Pattern\n\nInstantiate once per test and use composed objects:\n\n```ts\ntest('example test', async ({ insomnia }) => {\n  await insomnia.projectPage.createCollection();\n  await insomnia.tabbar.closeTab('New Request');\n  await expect.soft(insomnia.tabbar.tabLocator('foo')).toHaveAttribute('data-selected', 'true');\n});\n```\n","isInternal":false,"tokens":428,"sizeBytes":1849},{"name":"README.md","path":"packages/insomnia-smoke-test/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia-smoke-test/README.md","title":"insomnia-smoke-test Documentation","category":"plugin-manifest","format":"markdown","content":"# Insomnia Smoke Tests\n\nPlaywright E2E tests for the Insomnia desktop app.\n\n> CLI smoke tests: [CLI.md](CLI.md)\n\n## Test structure\n\n```\ntests/\n  smoke/      # Main suite — runs on every CI push (Ubuntu only)\n  critical/   # Single critical-path test — runs on release\n  migration/  # Data migration tests\n```\n\nAll commands below must be run from the **repo root**.\n\n## Quick-start\n\n```bash\nnpm install\nnpm run test:smoke:dev   # run all Smoke tests (dev mode)\n```\n\nBoth the echo server (port 4010) and the Vite dev server start automatically.\n\n**Filter to one test** — pass a substring of the test title or file name:\n\n```bash\nnpm run test:smoke:dev -- oauth\n```\n\n**Interactive UI:**\n\n```bash\nnpm run test:smoke:dev -- --ui\n```\n\n**Step-through debugger:**\n\n```bash\nPWDEBUG=1 npm run test:smoke:dev\n```\n\n## Additional log levels\n\n```bash\nDEBUG=pw:api npm run test:smoke:dev         # Playwright API logs\nDEBUG=pw:browser npm run test:smoke:dev     # Insomnia console logs\nDEBUG=pw:WebServer npm run test:smoke:dev   # Web server logs\n```\n\n## Traces and error context\n\nOn failure, two artifacts are written under `packages/insomnia-smoke-test/traces/<test-name>/`:\n\n- **`error-context.md`** — error details, ARIA page snapshot at point of failure, and annotated test source. Read this first.\n- **`trace.zip`** — full Playwright trace (network, screenshots, DOM snapshots).\n\nOpen a trace:\n\n```bash\nnpx playwright show-trace packages/insomnia-smoke-test/traces/<test-name>/trace.zip\n```\n\nOr upload to [trace.playwright.dev](https://trace.playwright.dev/).\n\nCI traces are available as artifacts on failed workflow runs.\n\n## Build / package modes\n\nRun against a JS bundle (`build`) or a packaged binary (`package`) instead of the dev watcher:\n\n```bash\n# build mode\nnpm run app-build\nnpm run test:smoke:build\n\n# package mode\nnpm run app-package\nnpm run test:smoke:package\n```\n\n> macOS package mode: set `com.apple.security.cs.disable-library-validation` to `true` in `entitlements.mac.inherit.plist` to allow unsigned local binaries. Do not commit this change.\n\n## Non-CI / pre-release tests\n\n```bash\nnpm run test:dev -w insomnia-smoke-test -- tests/smoke/preferences-interactions.test.ts\n```\n\n## Cert refresh\n\nIf the custom CA cert test fails after 2026:\n\n```bash\nmkcert -install && mkcert localhost && mkcert -CAROOT\n```\n","isInternal":false,"tokens":613,"sizeBytes":2326},{"name":"README.md","path":"packages/insomnia/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia/README.md","title":"insomnia Documentation","category":"plugin-manifest","format":"markdown","content":"# Insomnia\n\nThe main desktop application.\n\n## Data fetching\n\n- To fetch data from our APIs we use the `insomniaFetch` function. This allows us to overcome cross-origin issues and helps us to standardize the way we fetch data and also to handle errors in a centralized way.\n\n- For real-time data fetching we use SSE (Server-Sent Events). This is a standard way to receive data from the server in real-time. The server will send a message to the client when something happens. The client will receive the message and update the data accordingly. To handle SSE we use the `insomnia-event-source://` protocol which is a custom protocol that gets handled by the app in the main process. This allows us to overcome cross-origin issues and also to handle SSE in a centralized way.\n\n- Polling is also used to fetch data from the server. This is used when we need to fetch data periodically as SSE can sometimes fail and data can go out of sync.\n  The main places we use polling are:\n  - Refreshing Insomnia Sync data.\n  - Refreshing Git Sync data.\n  - Refreshing Presence data.\n\n> ⚠️ Using a combination of SSE and polling we can keep the data in sync and up to date but might also lead to some race conditions. For example, if we are polling for data every 5 seconds and the server sends an SSE message at the same time, the data might be out of sync for a few seconds. This is something we need to keep in mind when using SSE and polling.\n","isInternal":false,"tokens":315,"sizeBytes":1437},{"name":"README.md","path":"packages/insomnia/src/basic-components/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia/src/basic-components/README.md","title":"basic-components Documentation","category":"plugin-manifest","format":"markdown","content":"# `~/basic-components` — Shared Component Library\n\nInsomnia's shared component library, built on `react-aria-components` + TailwindCSS. Full design/roadmap:\n[Insomnia Shared Component Library Plan](https://konghq.atlassian.net/wiki/spaces/~712020f987dca73e964e60aafab91ddc862706/pages/5729714608/Insomnia+Shared+Component+Library+Plan) (Confluence). Conventions: [`AGENTS.md`](./AGENTS.md).\n\nFull per-component docs (props, variants, live examples) live on the Docusaurus site\n`packages/insomnia-component-docs`, not in this file.\n\n## Import\n\n```ts\nimport { Button, Modal, Icon } from '~/basic-components';\n```\n\n**Always import from the barrel (`~/basic-components`).** The barrel is the public contract; the\ncurrent file layout (flat) is an implementation detail. Physical layering into\n`primitives/overlays/collections/layout/typography` is **deferred to M5** (see the plan) — because\nconsumers go through the barrel, that later move only edits the barrel's internal re-exports, never\na business import. New code and the docs' `ReactLiveScope` must use the barrel so those moves stay\nfree. Existing deep imports (`~/basic-components/button`, ~18 files) are left as-is until M5.\n\n## Usage index (current)\n\n| Component       | Barrel export | Location                                                                                |\n| --------------- | ------------- | --------------------------------------------------------------------------------------- |\n| `Button`        | ✅            | `button.tsx`                                                                            |\n| `LearnMoreLink` | ✅            | `link.tsx`                                                                              |\n| `SelectPopover` | ✅            | `select-popover.tsx`                                                                    |\n| `Modal`         | ✅            | `modal.tsx`                                                                             |\n| `Tab` / `Tabs`  | ✅            | `tabs.tsx`                                                                              |\n| `Banner`        | ✅            | `banner.tsx`                                                                            |\n| `Card`          | ✅            | `card.tsx`                                                                              |\n| `Divider`       | ✅            | `divider.tsx`                                                                           |\n| `Progress`      | ✅            | `progress.tsx`                                                                          |\n| `Icon`          | ✅            | `icon.tsx` (re-exports `ui/components/icon`, pending consolidation — see mapping below) |\n\n`utils/` helpers (e.g. `utils/cls.ts`, `utils/variants.ts`, `utils/index.ts`) are library-internal (tailwind-merge / tailwind-variants helpers) and are **not** re-exported from the barrel — see [`AGENTS.md`](./AGENTS.md).\n\n## Migration mapping (old → new)\n\nTracks the consolidation described in the plan (Confluence). Status: 🔜 not started · 🚧 in progress · ✅ done.\n\n| Old                                                                                                                          | New                                                    | Status                                                                    |\n| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |\n| `basic-components/button.tsx` + `ui/components/themed-button/` (9 files) + `async-button.tsx`                                | `Button` (+ loading spinner, `AsyncButton` wrapper)    | 🚧 (canonical file in `button.tsx`; consolidation pending)                |\n| — (new)                                                                                                                      | `IconButton`                                           | 🔜                                                                        |\n| `ui/components/icon.tsx` (38 files) + `basic-components/icon.tsx` (~10) + `ui/components/svg-icon.tsx` (70 glyphs, 13 files) | `Icon`                                                 | 🔜                                                                        |\n| `ui/components/tooltip.tsx` (21) + `ui/components/help-tooltip.tsx` (26)                                                     | `Tooltip` / `HelpTooltip`                              | 🔜                                                                        |\n| `ui/components/base/input.tsx`                                                                                               | `Input` / `TextField`                                  | 🔜                                                                        |\n| `ui/components/base/select.tsx` + `basic-components/select-popover.tsx`                                                      | `Select`                                               | 🚧 (canonical file in `select-popover.tsx`; merge pending)                |\n| `ui/components/base/modal.tsx` + `modal-header/body/footer.tsx`                                                              | `Modal` (+ `size` variant, `Modal.Header/Body/Footer`) | 🚧 (canonical file in `modal.tsx`; feature parity pending, see plan §7.6) |\n| `ui/components/base/dropdown/`                                                                                               | `Menu`                                                 | 🔜                                                                        |\n| — (new)                                                                                                                      | `Popover`                                              | 🔜                                                                        |\n| — (new)                                                                                                                      | `ListBox` / `ListBoxItem`                              | 🔜                                                                        |\n| `basic-components/tabs.tsx`                                                                                                  | `Tabs` (+ controlled/render-prop)                      | 🚧 (canonical file in `tabs.tsx`)                                         |\n| `ui/components/base/checkbox.tsx`                                                                                            | `Checkbox` / `CheckboxGroup`                           | 🔜                                                                        |\n| `ui/components/base/switch.tsx`                                                                                              | `Switch`                                               | 🔜                                                                        |\n| `ui/components/base/input-number.tsx`                                                                                        | `NumberField`                                          | 🔜                                                                        |\n| `ui/components/base/date-picker.tsx`                                                                                         | `DatePicker`                                           | 🔜                                                                        |\n| `ui/components/base/middle-truncate.tsx`                                                                                     | TBD (P2 fill-in)                                       | 🔜                                                                        |\n| `basic-components/link.tsx` + `ui/components/base/link.tsx`                                                                  | `Link`                                                 | 🚧 (canonical file in `link.tsx`; merge pending)                          |\n| `ui/components/unsaved-changes-confirm-dialog.tsx` + guard                                                                   | `ConfirmDialog`                                        | 🔜                                                                        |\n| `ui/components/editable-input.tsx`                                                                                           | `EditableInput`                                        | 🔜                                                                        |\n| `ui/components/time-from-now.tsx`                                                                                            | `TimeFromNow`                                          | 🔜                                                                        |\n| `basic-components/card.tsx` / `divider.tsx` / `banner.tsx` / `progress.tsx`                                                  | `Card` / `Divider` / `Banner` / `Progress`             | 🚧 (`Progress` still has hardcoded colors, see FIXME)                     |\n| `ui/components/base/badge.tsx`                                                                                               | `Badge`                                                | 🔜                                                                        |\n\nNot covered by this library (feature consumers, not generic primitives): `ui/components/tabs/`,\n`ui/components/dropdowns/`.\n","isInternal":false,"tokens":1381,"sizeBytes":9581},{"name":"README.md","path":"packages/insomnia/src/cpp/README.md","rawUrl":"https://raw.githubusercontent.com/Kong/insomnia/HEAD/packages/insomnia/src/cpp/README.md","title":"cpp Documentation","category":"plugin-manifest","format":"markdown","content":"# Secure Wrapper\n\nThe purpose of this secure wrapper is to mitigate a [local search path vulnerability](https://vuldb.com/?id.295961).\n\nIt utilizes Windows' `PROCESS_MITIGATION_POLICY` to prefer the system paths so that the Electron application does not load hijacked DLLs that are placed in the installation directory (writable by underprivileged users).\n\n## Building Locally\n\nOn Windows, install MSYS2 with the base self-extracting installer: [https://github.com/msys2/msys2-installer/releases](https://github.com/msys2/msys2-installer/releases)\n\nMove that `.sfx.exe` file to wherever you'd like the MSYS2 installation to be, and run it. It will extract the install folder to that location. (i.e. `C:\\msys2-base-x86_64-NNNNNNNN.sfx.exe` to install to `C:\\msys64`).\n\nThen, open the MSYS2 terminal to finish the installation (`C:\\msys64\\msys2.exe`).\n\nClose that terminal, and open the `ucrt64` terminal (`C:\\msys64\\ucrt64.exe`).\nInstall the build system:\n\n```bash\npacman -S mingw-w64-ucrt-x86_64-gcc\n```\n\nIf you don't already have them, install git and node:\n\n```bash\npacman -S git\npacman -S nodejs\n```\n\nOnce that's done, ensure `windres` and `g++` are in your path:\n\n```bash\n$ which windres\n/ucrt64/bin/windres\n\n$ which g++\n/ucrt64/bin/g++\n```\n\nNavigate to the top-level directory of this repository and run:\n\n```bash\nnpm install\n./build-secure-wrapper.sh\n```\n\nOnce built, the Squirrel Installer can be launched from `dist/win-unpacked`.\n","isInternal":false,"tokens":398,"sizeBytes":1439}],"systemPromptSnippet":"<yakaai_skills repo=\"Kong/insomnia\">\n<!-- File: AGENTS.md (Tokens: ~1310 | Category: root-instruction) -->\n# AGENTS.md\n\n## Tech Stack\n\n- **UI:** React with React Router (loaders/actions pattern)\n- **Components:** React Aria Components\n- **Desktop Shell:** Electron (main + renderer processes)\n- **Styling:** TailwindCSS\n- **Language:** TypeScript\n- **Database:** NeDB (`@seald-io/nedb`) — embedded NoSQL\n- **Build/Dev:** Vite, npm workspaces monorepo\n\n_See `package.json` for current versions and `.nvmrc` for the Node version._\n\n## Strict Rules\n\n- **No unsolicited formatting.** Rely on ESLint/Prettier. Do not reformat existing code.\n- **Strict scoping.** Only modify code directly related to the prompt. Do not refactor adjacent code unless asked.\n\n## Command Output\n\nPrefer quiet command variants to minimise output volume:\n\n- `git log --oneline -20` not `git log`\n- `git diff --stat` not `git diff`\n- `npm test --silent` not `npm test`\n- `tsc --noEmit 2>&1 | head -50` for type-check failures\n- Use the `Read` tool with `limit` rather than `cat` on large files\n- Use `Grep` with `head_limit` rather than unrestricted searches\n\n## Validation Commands\n\nRun from repo root before considering work complete:\n\n```bash\nnpm run lint          # ESLint all workspaces\nnpm run type-check    # TypeScript check all workspaces\nnpm test              # Tests all workspaces (or: npm test -w packages/insomnia)\n```\n\n## Worktree Setup\n\n- New git worktrees may not have `node_modules` yet. Before installing or validating, switch to the repo's required runtime from the worktree root:\n\n```bash\nfnm use \"$(cat .nvmrc)\"\nnode -v\nnpm -v\n```\n\n- This repo expects the `.nvmrc` Node version and npm 11+. If `fnm` is unavailable, manually use an equivalent Node/npm version before running any `npm` commands.\n- After switching versions in a fresh worktree, install dependencies from repo root with `npm ci`.\n- Do **not** use `npm ci --ignore-scripts` for normal worktree setup. It leaves Electron partially installed, which later breaks builds, renderer import checks, and other validation commands.\n\n## Repository Structure\n\n`packages/`\n  `insomnia/`                ← Main Electron app\n    `src/`\n      `common/`              ← Shared utils, settings types\n      `routes/`              ← React Router files (clientLoader/clientAction)\n      `ui/`                  ← React components, hooks, `insomnia-fetch.ts`\n      `main/`                ← Electron IPC handlers, `preload.ts`\n      `account/`             ← Auth, session, encryption\n      `sync/`                ← Git/VCS sync\n      `network/`             ← Request execution engine\n      `templating/`          ← Nunjucks rendering (Web Worker)\n  `insomnia-data/`           ← Data models, services, NeDB implementation, shared data utilities\n  `insomnia-api/`            ← Cloud API client\n  `insomnia-inso/`           ← CLI tool\n  `insomnia-testing/`        ← Test framework\n\n## Data Model Hierarchy\n\nOrganization\n  → Project (local | remote/cloud | git-backed)\n    → Workspace (scope: 'collection' | 'design')\n      → Base Environment (auto-created: use `models.environment.getOrCreateForParentId(workspaceId)`)\n        → Sub-Environments\n      → Cookie Jar (auto-created)\n      → Request Group (folders)\n        → Request (HTTP, GraphQL, gRPC, WebSocket, Socket.IO)\n      → Request (can be direct child of workspace)\n**Note:** A Workspace with `scope: 'collection'` IS the collection.\n\n## Key Patterns\n\n- **Route-Based Actions:** Mutations use React Router's `clientAction` (`src/routes/`).\n  - **CRITICAL:** `clientAction` blocks navigation. For long-running UI operations, use plain async functions instead.\n- **Database Buffering:** Always buffer bulk writes (`database.bufferChangesIndefinitely()`, then `flushChanges()`). Unbuffered writes fire UI revalidation per operation, causing severe lag.\n- **State Management:** Use Router loaders/actions and NeDB for persistent state. Use React `useState`/context for ephemeral UI state (No Redux/Zustand).\n- **Electron IPC:** For main↔renderer communication, define handlers in `src/main/ipc/`, expose in `src/main/preload.ts`, and update `window.main` in `src/global.d.ts`.\n- **Templates:** Nunjucks runs in a Web Worker (`src/templating/`). Use `{{ _.variable_name }}`.\n- **Models:** Follow CRUD via `models.<type>` (e.g., `create()`, `update()`).\n- **HTTP Calls:** Use `insomniaFetch()` for Insomnia backend APIs. Use plain `fetch()` for external/third-party APIs.\n- **Styling:** Tailwind utility classes only. Use `clsx`/`tailwind-merge` for conditionals. Use React Aria for interactive HTML elements.\n- **Testing:** Use **Vitest** (unit) and **Playwright** (E2E). Co-locate unit tests as `filename.test.ts`. Use `vi.mock()`. Prefer testing logic via loaders over mounting components.\n- **E2E tests** live in `packages/insomnia-smoke-test/`. Full docs: [`packages/insomnia-smoke-test/README.md`](packages/insomnia-smoke-test/README.md).\n- Run E2E from repo root: `npm run test:smoke:dev` (filter: `npm run test:smoke:dev -- <title-substring>`).\n- New test imports: `import { test } from '../../playwright/test'` and `import { expect } from '@playwright/test'`.\n\n## Sensitive Data\n\n- **Vault system (AES-GCM):** For environment secrets (`EnvironmentKvPairDataType.SECRET`).\n- **Electron safeStorage:** Platform-native encryption (`window.main.secretStorage`).\n\n<!-- File: .claude/skills/component-docs/template.mdx (Tokens: ~337 | Category: claude-rule) -->\n---\nid: COMPONENT_ID\nsidebar_label: 'ComponentLabel'\nsidebar_position: 99\n---\n\n# ComponentName\n\nOne-line description of the component and what it is built on (e.g. \"built on React Aria's `X` component\").\n\n## Props\n\n| Prop        | Type       | Default | Description        |\n| ----------- | ---------- | ------- | ------------------ |\n| `propName`  | `type`     | `-`     | What it does       |\n| `className` | `string`   | `-`     | Additional classes |\n| `children`  | `ReactNode`| `-`     | Content            |\n\nExtends all [React Aria XProps](https://react-spectrum.adobe.com/react-aria/X.html#props).\n\n## Usage Examples\n\n### Basic\n\n```tsx live\nfunction Example() {\n  return <ComponentName>Hello</ComponentName>;\n}\n```\n\n### Variants\n\n```tsx live\nfunction Example() {\n  return (\n    <div className=\"flex gap-4\">\n      <ComponentName variant=\"a\">A</ComponentName>\n      <ComponentName variant=\"b\">B</ComponentName>\n    </div>\n  );\n}\n```\n\n## Styling\n\n### CSS Variables\n\nThe component uses the following CSS variables for theming:\n\n- `--color-font`: Text color\n- `--color-bg`: Background color\n- `--hl-sm`: Subtle highlight (hover/focus)\n\n<!-- If the component carries a theme scope class (e.g. theme--dialog / theme--tooltip),\n     note it here: user themes can customize this component's colors via that class. -->\n\n<!-- File: CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\n@AGENTS.md\n\n<!-- File: .claude/skills/component-docs/SKILL.md (Tokens: ~870 | Category: anthropic-skill) -->\n---\nname: component-docs\ndescription: \"Author or update a component doc in the Docusaurus site (packages/insomnia-component-docs) for a component in the shared library. Use when adding a new component to src/basic-components, changing a component's props/variants, or when asked to document/update docs for a component.\"\nargument-hint: 'The component name and its source path, e.g. \"Select (packages/insomnia/src/basic-components/select.tsx)\"'\n---\n\n# Component Docs (Docusaurus + MDX)\n\nAuthor/maintain docs for the shared component library on the Docusaurus site at\n`packages/insomnia-component-docs`. Docs are one MDX file per component with live,\neditable examples.\n\n## When to Use\n\n- A new component was added to `packages/insomnia/src/basic-components/` (or migrated in).\n- A component's props/variants/styling changed and its doc needs updating.\n- Asked to \"document\" / \"write docs for\" / \"update docs for\" a component.\n\n## Key Facts (read before editing)\n\n- **One file per component**: `packages/insomnia-component-docs/docs/Components/<name>.mdx` (kebab-case filename).\n- **Sidebar is autogenerated** from the folder — do NOT edit `sidebars.ts`. Just set `sidebar_position` in frontmatter for ordering.\n- **Live examples** use ` ```tsx live ` fenced blocks (via `@docusaurus/theme-live-codeblock`). Component names in those blocks resolve from a shared scope, NOT from imports in the MDX.\n- **⚠️ The easy-to-forget step**: any component used in a `tsx live` block MUST be imported and registered in `packages/insomnia-component-docs/src/theme/ReactLiveScope/index.ts`. If it isn't, the live block fails to compile. Library components import as `insomnia/src/basic-components/<name>`.\n\n## Procedure\n\n1. **Inspect the component source** to get the real API: props, types, defaults, variants (`tv` config), and which theme CSS variables / `theme--*` classes it uses.\n2. **Create/update the MDX** at `docs/Components/<name>.mdx` using `template.mdx` in this skill folder as the starting point. Fill:\n   - Frontmatter: `id`, `sidebar_label`, `sidebar_position` (next free integer, or logical grouping).\n   - Title + one-line description.\n   - `## Props` table (prop / type / default / description). End with a link to the underlying React Aria props page when it extends an RA component.\n   - `## Usage Examples` — at least 2–3 ` ```tsx live ` blocks covering the main variants/states. Keep examples small and self-contained; wrap multiple elements in a `function` component.\n   - `## Styling` → `### CSS Variables` list (only the color vars the component consumes) and, if applicable, a note on the `theme--*` scope class it carries.\n3. **Register in ReactLiveScope** (`src/theme/ReactLiveScope/index.ts`): add the `import` and include the component in the `ReactLiveScope` object. Only needed once per component.\n4. **Verify the site builds** (catches broken live blocks and bad imports):\n   ```bash\n   cd packages/insomnia-component-docs && npm run build\n   ```\n   For interactive preview while writing: `cd packages/insomnia-component-docs && npm run start`.\n\n## Conventions\n\n- Match the existing docs' tone and section order (see `docs/Components/button.mdx` as the reference).\n- Document colors via CSS variables (`--color-*` / `--hl-*`); sizes/spacing are native Tailwind utilities — mirror the component's actual source, not an idealized version. (Library styling conventions live in `packages/insomnia/src/basic-components/AGENTS.md`.)\n- Keep live examples free of business/data dependencies so they render in isolation.\n- If the component is multi-part (e.g. `Modal.Header/Body/Footer`), document the sub-components together in one file.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/async-objects.md (Tokens: ~1753 | Category: anthropic-skill) -->\n# Async plumbing (`ProxiedPromise`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/async-objects.ts`\n\n## Purpose\nInternal plumbing (not part of the `pm`/`insomnia` script-facing API by name) that lets the sandbox\ntrack every `Promise` a user script creates, so it can drain/await fire-and-forget promises before\nhanding control back to the host app. This is what prevents an un-awaited `.then()` chain or a\nstray `setTimeout`-driven promise inside a script from silently continuing (or erroring) after the\nscript has already \"finished\".\n\n## Public API\n\n- `export const OriginalPromise = Promise;` — a stashed reference to the real native `Promise`\n  class, captured at module load before anything replaces the global `Promise`.\n- `export class ProxiedPromise<T> extends Promise<T>` — drop-in `Promise` replacement:\n  - `constructor(executor)` — behaves exactly like `Promise`, and additionally pushes `this` into\n    the module-level `scriptPromises` array if `monitoring` is `true`.\n  - `static override all(promises: Promise<any>[])` — delegates to `super.all(promises)`; pushes\n    the resulting promise into `scriptPromises` if `monitoring`.\n  - `static override allSettled(promises: Promise<any>[])` — delegates to `super.allSettled(promises)` only; does **not** push the result into `scriptPromises` (comment: \"promise will be counted in Promise.resolve\").\n  - `static any(_: Promise<any>[])` — **not actually overridden** (no `override` keyword, does not call `super.any`); always returns `super.reject(\"'super.any' not supported\")`. Comment notes `Promise.any` isn't supported for the ES2021 compile target.\n  - `static override race(promises: Promise<any>[])` — delegates to `super.race(promises)`; pushes the result if `monitoring`.\n  - `static override reject(value: any)` — delegates to `super.reject(value)`; pushes the result if `monitoring`.\n  - `static override resolve<T>(value?: T | PromiseLike<T>)` — delegates to `super.resolve(value)`; pushes the result if `monitoring`.\n  - `static withResolvers()` — always returns `super.reject(\"'Promise.withResolvers' not supported\")`; same ES2021 compile-target limitation noted in a comment.\n- `export const asyncTasksAllSettled = async () => { await Promise.allSettled(scriptPromises); scriptPromises = []; }` — awaits every currently-tracked promise, then clears the tracked list.\n- `export const stopMonitorAsyncTasks = () => { monitoring = false; }` — disables tracking of any *new* promises created after this call.\n- `export const resetAsyncTasks = async () => { scriptPromises = []; monitoring = true; }` — clears the tracked list and re-enables monitoring; called at the start of a fresh script run.\n\nModule-level (shared, not per-instance) state: `let monitoring = true;` and\n`let scriptPromises = new Array<Promise<any>>();`. Importing this module also calls\n`resetTestPromises()` (from `./test`) once, as a side effect at load time.\n\n## Script-facing surface\nNone of these names are called directly by a user script. However, the mechanism *is* in the\nscript-facing surface indirectly: `packages/insomnia/src/entry.hidden-window-preload.ts` replaces\nthe sandbox's global `Promise` with `ProxiedPromise`\n(`contextBridge.exposeInMainWorld('Promise', ProxiedPromise); window.Promise = ProxiedPromise;`),\nso `new Promise(...)`, `Promise.resolve(...)`, `Promise.all(...)`, etc. — anything that references\nthe (now-reassigned) global `Promise` binding — is transparently tracked. **`async function` is not\nin that list**: per spec, an async function's returned promise is created against the realm's\nintrinsic `%Promise%` (`NewPromiseCapability(%Promise%)`), not whatever the mutable global `Promise`\nbinding currently points to, so reassigning `window.Promise` does not make it a `ProxiedPromise`.\n`packages/insomnia/src/scripting/run-script.ts` injects\n`resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the executed script function\nas `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__`, calling `__bridgeReset__()` before the\nuser script body runs and `await __bridgeSettle__()` after it, right before returning the mutated\n`insomnia` object. This drains explicitly-tracked promises, but an un-awaited `pm.sendRequest(...)`\ncall or stray `.then()` chain is only guaranteed to resolve before the script's result is used if\nevery promise in its chain was created via a tracked constructor/helper — not guaranteed just\nbecause it originated from an `async function`.\n\n## Gotchas / notable behavior\n- `Promise.any(...)` and `Promise.withResolvers()` are **explicitly unsupported**: calling either\n  from a script returns a promise that rejects with the literal string `\"'super.any' not supported\"`\n  or `\"'Promise.withResolvers' not supported\"` (not a real `Error` object) — worth checking for if a\n  script's failure message looks like that.\n- `allSettled` results are deliberately *not* double-counted into `scriptPromises` — the comment\n  claims this is safe because \"promise will be counted in Promise.resolve\" (i.e. the engine's\n  internal `allSettled` implementation is assumed to route through `Promise.resolve` on the inputs,\n  which are already tracked). This is an implicit coupling to Promise/A+ implementation details.\n- `scriptPromises`/`monitoring` are **module-level singletons**, not scoped per script execution —\n  correctness across sequential script runs depends on `resetAsyncTasks()` (`__bridgeReset__`)\n  being called at the start of every run; if it's ever skipped, promises from a previous run could\n  leak into the next run's settle-and-drain step.\n- Constructing a promise while `monitoring === false` silently skips tracking — no error or warning is raised.\n- Promises returned by an `async function` are **not guaranteed to be added to `scriptPromises`**,\n  even though `window.Promise`/`Promise` was replaced with `ProxiedPromise` — the JS engine builds\n  an async function's result promise from the realm's intrinsic `%Promise%`, bypassing the\n  reassigned global binding entirely. `send-request.ts`'s exported `sendRequest` is itself an\n  `async function` that internally does `return new Promise(async (resolve, reject) => {...})`; the\n  inner explicit `new Promise(...)` is tracked, but the promise actually returned to the caller of\n  `sendRequest(...)` (i.e. what `pm.sendRequest()` hands back to a script when not passed a callback)\n  is not. Symptom: a script that fires `pm.sendRequest(...)` without `await`ing it (or awaiting a\n  `.then()` derived from it) may see the request silently abandoned or racing past\n  `asyncTasksAllSettled()`'s drain, instead of being reliably completed first.\n\n## Related\n- `test.ts` — `resetTestPromises()` is called once at this module's import time; test assertion promises are tracked separately from `scriptPromises`.\n- `packages/insomnia/src/scripting/run-script.ts` — wires `resetAsyncTasks`/`stopMonitorAsyncTasks`/`asyncTasksAllSettled` into the sandboxed script's `__bridgeReset__`/`__bridgeStop__`/`__bridgeSettle__` calls.\n- `packages/insomnia/src/entry.hidden-window-preload.ts` — replaces the sandbox's global `Promise` with `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` — prepares the broader sandbox execution context that this async tracking runs inside.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/auth.md (Tokens: ~2515 | Category: anthropic-skill) -->\n# RequestAuth\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/auth.ts`\n\n## Purpose\nModels the authentication settings attached to a request (`pm.request.auth`). It stores auth options per-type in a map (so switching `type` doesn't destroy other types' saved options), and provides the two-way transform functions used to convert between Insomnia's native `RequestAuthentication` model and the script-facing `AuthOptions` shape. `RequestAuth` extends `Property` (see `properties.ts`).\n\n## Public API\n\n### Types\n- `type AuthOptionTypes = 'noauth' | 'basic' | 'bearer' | 'jwt' | 'digest' | 'oauth1' | 'oauth2' | 'hawk' | 'awsv4' | 'ntlm' | 'apikey' | 'edgegrid' | 'asap' | 'netrc'` — the full set of auth type strings the SDK models are aware of, but not all are reachable from scripts (see Gotchas).\n- `const AuthTypes: Set<string>` — a `Set` containing the same string values as `AuthOptionTypes`, used by `RequestAuth.isValidType`.\n- `interface AuthOption { key: string; value: string; type?: string }` — a single key/value auth parameter (e.g. `{ key: 'username', value: 'user1' }`).\n- `interface OAuth2AuthOption { key: string; value: string | OAuth2Param[]; type?: string }` — like `AuthOption` but OAuth2 params can themselves be arrays of `OAuth2Param` (used for nested `tokenRequestParams` etc.).\n- `interface OAuth2Param { key: string; value: string; enabled: boolean; send_as: string }`.\n- Per-auth-type option interfaces (shape of the fields you'd set for `.use()`/`.update()` when constructing raw options rather than key/value arrays): `BasicOptions`, `BearerOptions`, `JWTOptions`, `DigestOptions`, `OAuth1Options`, `OAuth2Options`, `HAWKOptions`, `AWSV4Options`, `NTLMOptions`, `APIKeyOptions`, `EdgegridOptions`, `ASAPOptions`. Each mirrors the field names used by the corresponding Insomnia auth UI (e.g. `OAuth1Options` has `consumerKey`, `consumerSecret`, `signatureMethod`, etc.) and all have an optional `id?: string`.\n- `interface AuthOptions { type: AuthOptionTypes; basic?: AuthOption[]; bearer?: AuthOption[]; jwt?: AuthOption[]; digest?: AuthOption[]; oauth1?: AuthOption[]; oauth2?: OAuth2AuthOption[]; hawk?: AuthOption[]; awsv4?: AuthOption[]; ntlm?: AuthOption[]; apikey?: AuthOption[]; edgegrid?: AuthOption[]; asap?: AuthOption[] }` — the JSON shape used to construct a `RequestAuth` and returned by `toJSON()`/`toPreRequestAuth()`. Each type's key holds an array of `AuthOption` (or `OAuth2AuthOption` for oauth2).\n\n### Functions\n- `authOptionsToParams(authMethod: BasicOptions | BearerOptions | ... | ASAPOptions)` — converts a flat options object (e.g. `{ username, password }`) into an array of `{ type: 'any', key, value }` entries. Not used internally by `RequestAuth`; appears to be a helper for callers building `AuthOption[]` from a typed options object.\n- `fromPreRequestAuth(auth: RequestAuth): RequestAuthentication` — converts a script-side `RequestAuth` into Insomnia's native `RequestAuthentication` model (used when a pre-request script finishes and Insomnia needs to write the mutated auth back onto the real request). Calls `auth.toJSON()` then switches on `type`.\n- `toPreRequestAuth(auth: RequestAuthentication | {}): AuthOptions` — the reverse transform: converts Insomnia's native auth model into the `AuthOptions` shape used to construct the script's `RequestAuth` (called when initializing `pm.request.auth` before a script runs).\n\n### `class RequestAuth extends Property`\n- `constructor(options: AuthOptions, parent?: Property)` — throws `Error('invalid auth type ${options.type}')` if `options.type` isn't a recognized type. Populates an internal `Map<string, VariableList<Variable>>` (`authOptions`) with one entry per auth-type key present in `options` (so if you pass an `AuthOptions` with only `type: 'basic'` and `basic: [...]`, only the `'basic'` entry is populated — other types are simply absent from the map, not defaulted).\n- `static isValidType(authType: string): boolean` — checks membership in `AuthTypes`.\n- `clear(type: string): void` — removes the stored options for `type` from the internal map (no-op, silently, if `type` is invalid).\n- `parameters(): VariableList<Variable> | undefined` — returns the `VariableList` of options for the *currently active* `type` (i.e. whatever `this.type` is set to), or `undefined` if nothing is stored for that type.\n- `toJSON(): AuthOptions` — serializes back to the `AuthOptions` shape. For `type === 'noauth'` or `'netrc'` it returns just `{ type }` with no options array. Otherwise returns `{ type, [type]: <array of {key, value} from the VariableList> }`.\n- `update(options: VariableList<Variable> | Variable[] | AuthOptions, type?: AuthOptionTypes): void` — replaces the options for `type` (or the current `this.type` if `type` omitted) and switches `this.type` to it. Throws `Error('no valid RequestAuth options is found')` if no variable list could be derived from `options`.\n- `use(type: AuthOptionTypes, options: VariableList<Variable> | Variable[] | AuthOptions): void` — same as `update` but `type` is required; throws `Error('invalid type (...)')` if `type` isn't in `AuthTypes`, and the same \"no valid options\" error as `update` otherwise. This is the method `Request.authorizeUsing()` calls to switch a request's auth type from a script.\n\n### Internal helper (not exported)\n- `rawOptionsToVariables(options, targetType?)` — normalizes the three accepted input shapes (`VariableList<Variable>`, `Variable[]`, or a full `AuthOptions` object) into `VariableList<Variable>[]`. Throws `Error('options is not valid: it must be VariableList<Variable> | Variable[] | object')` if none of the shapes match.\n\n## Script-facing surface\n- `pm.request.auth` is a `RequestAuth` instance (constructed in `request.ts` from `options.auth || { type: 'noauth' }`).\n- `pm.request.auth.parameters()` — read the currently active auth's key/value options.\n- `pm.request.auth.update(newOptions)` — mutate the current auth type's options in place.\n- `pm.request.authorizeUsing(type, options)` (on `Request`, in `request.ts`) delegates to `this.auth.use(type, options || { type: 'noauth' })` — this is the documented way scripts switch auth type.\n- `pm.request.auth.clear(type)` to drop a stored auth-type's options.\n- When a pre-request script finishes, `fromPreRequestAuth(request.auth)` converts the mutated `RequestAuth` back to Insomnia's native `RequestAuthentication`, which is what actually gets sent on the wire (see `request.ts`, `authentication: fromPreRequestAuth(updatedReq.auth)`).\n\n## Gotchas / notable behavior\n- **`netrc` is a dead end**: `AuthTypes` includes `'netrc'` and `RequestAuth` will happily accept `type: 'netrc'`, but `fromPreRequestAuth` throws `Error('netrc is not supported yet')` when it sees `type === 'netrc'`, and `toPreRequestAuth` throws `Error('netrc auth is not supported in scripting yet')` for the native `'netrc'` type. So a script that sets `type: 'netrc'` will fail only when Insomnia tries to convert it back, not immediately.\n- **`singleToken` is unsupported**: `toPreRequestAuth` throws for native auth type `'singleToken'` — there is no path from this native auth type into the script's `RequestAuth` at all.\n- **`edgegrid` has interfaces but no transform**: `EdgegridOptions` and `'edgegrid'` are defined in `AuthOptionTypes`/`AuthTypes`, but neither `fromPreRequestAuth` nor `toPreRequestAuth` has a `case 'edgegrid'` — passing this type through those functions falls into the `default` branch and throws `Error('unknown auth type: ...')`.\n- **oauth1 `signatureMethod` restrictions**: `fromPreRequestAuth`'s oauth1 branch only supports `HMAC-SHA1`, `HMAC-SHA256`, `RSA-SHA1`, and `PLAINTEXT`. `HMAC-SHA512`, `RSA-SHA256`, and `RSA-SHA512` are recognized strings but explicitly throw `Error('...unsupported signatureMethod type for oauth1: ...')`.\n- **oauth1 `privateKey` is a one-way/unsupported field**: comment in code says \"it is not supported in the script side\" — it's read but not written back into the native auth model in `toPreRequestAuth`.\n- **oauth2 has several fields marked \"not supported yet in the script side\"**: `tokenPrefix`, `responseType`, `origin` are passed through in `fromPreRequestAuth`/`toPreRequestAuth` for round-tripping but are not meaningfully used elsewhere per the inline comments.\n- **hawk loses several fields on the round trip to native Insomnia auth**: `timestamp`, `delegation`, `app`, `nonce`, `user` are commented as \"some keys are lost here\" in `fromPreRequestAuth`'s hawk branch, and `toPreRequestAuth`'s hawk branch hardcodes them back to empty string / `'false'` since \"these fields are not supported in Insomnia side\".\n- **Booleans are stored as strings internally**: all the `AuthOption`/`OAuth2AuthOption` values are `string`, so booleans like `disabled` are represented as `'true'`/`'false'` strings inside the `VariableList`, and compared with `=== 'true'` when converting back.\n- **Constructor does not default missing types**: if you construct `new RequestAuth({ type: 'bearer' })` (no `bearer` key), `parameters()` will return `undefined` immediately — there's no implicit empty array.\n- **`update`/`use` fully replace, not merge**: calling `update()`/`use()` replaces the entire options list for a type; it does not merge in partial fields.\n\n## Related\n- `properties.ts` — `Property` (base class) and `Variable`/`VariableList` container semantics used to store auth options.\n- `variables.ts` — `Variable`, `VariableList` (the underlying storage for each auth type's key/value pairs).\n- `request.ts` — constructs `pm.request.auth` from `AuthOptions`, exposes `authorizeUsing()`, and calls `fromPreRequestAuth` when finalizing the request.\n- `insomnia.ts` — calls `toPreRequestAuth(rawObj.request.authentication)` to seed `pm.request.auth` before a script runs.\n- `insomnia-data` package — source of the native `RequestAuthentication` and `OAuth2ResponseType` types that `fromPreRequestAuth`/`toPreRequestAuth` convert to/from.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/certificates.md (Tokens: ~1428 | Category: anthropic-skill) -->\n# Certificate\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/certificates.ts`\n\n## Purpose\nModels a single client (mTLS) certificate for the scripting environment — the script-facing representation surfaced as `pm.request.certificate`. It is deliberately simple: unlike Insomnia's native model (which supports a list of client certificates matched by host), the scripting SDK only ever models **one** certificate per request, matched via `UrlMatchPattern`s (from `urls.ts`).\n\n## Public API\n\n### Types\n- `interface SrcRef { src: string }` — a file-path reference; used for `key`, `cert`, and `pfx` since certificate content is loaded from disk, not embedded inline.\n- `interface CertificateOptions { name?: string; matches?: string[]; key?: SrcRef; cert?: SrcRef; passphrase?: string; pfx?: SrcRef; disabled?: boolean }` — constructor/`update()` input. `pfx` is documented as \"PFX or PKCS12 Certificate\".\n\n### `class Certificate extends Property`\n- `override _kind = 'Certificate'`\n- `override name?: string`\n- `matches?: UrlMatchPatternList<UrlMatchPattern>` — constructed internally from the `matches: string[]` option; each string becomes a `UrlMatchPattern`.\n- `key?: SrcRef`\n- `cert?: SrcRef`\n- `passphrase?: string`\n- `pfx?: SrcRef`\n- `constructor(options: CertificateOptions)` — sets all fields directly from `options`, including `this.disabled = options.disabled` (inherited `Property.disabled` field). `matches` defaults to an empty `UrlMatchPatternList` if `options.matches` is not provided.\n- `static isCertificate(obj: object): boolean` — checks `obj._kind === 'Certificate'`.\n- `canApplyTo(url: string): boolean` — returns `this.matches ? this.matches.test(url) : false`, i.e. whether any of the certificate's match patterns matches the given URL. Delegates to `UrlMatchPatternList.test()`.\n- `update(options: CertificateOptions): void` — fully overwrites `name`, `matches` (rebuilt the same way as in the constructor), `key`, `cert`, `passphrase`, `pfx`. Note: **does not** update `disabled` (unlike the constructor, which does set it) — see Gotchas.\n\n## Script-facing surface\n- `pm.request.certificate` is a `Certificate` instance. Per `insomnia.ts`, it's initialized either as an empty placeholder certificate (`name: 'Default Certificate'`, no `key`/`cert`/`pfx`) when the request URL contains unrendered template tags or `filterClientCertificates` returns zero matches, or as `{ name: 'The first matched certificate from Settings', matches: [matchedCertificates[0].host], key: {src: ...}, cert: {src: ...}, passphrase, pfx: {src: ...} }` — built from `matchedCertificates[0]` whenever the match list is **non-empty**, i.e. one or more matches, not only when there's exactly one. If more than one client certificate matches, everything past the first is silently dropped rather than surfaced as the empty-certificate path — a direct consequence of `Certificate` only ever modeling a single certificate (see Gotchas below).\n- Scripts can read `pm.request.certificate.key`, `.cert`, `.pfx`, `.passphrase`, `.name`, call `.canApplyTo(url)`, or call `.update({...})` to replace the certificate used for the request. Per `request.ts`, `Request.certificate` is a `Certificate` built the same way (`options.certificate ? new Certificate(options.certificate) : undefined`), and the certificate is serialized back out (`toJSON()`-style plain object with `name`, `matches` stringified, `key`, `cert`, `passphrase`, `pfx`) when the script finishes so Insomnia can merge it into the request's actual client-certificate list via `mergeClientCertificates` (in `request.ts`).\n\n## Gotchas / notable behavior\n- **Only one certificate is modeled, even though Insomnia supports several.** `request.ts`'s comment: \"Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones.\" When a script sets a certificate, `mergeClientCertificates` prepends it as a new entry ahead of the request's existing native client certificates rather than replacing the whole list.\n- **`update()` does not update `disabled`.** The constructor sets `this.disabled = options.disabled`, but `update()` has no such line — calling `cert.update({ disabled: true, ... })` will not actually change `cert.disabled`. This looks like an inconsistency/bug relative to the constructor's behavior.\n- **`cert+key` and `pfx` are mutually exclusive downstream.** `request.ts`'s `mergeClientCertificates` throws `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if a script sets both a PFX and a cert/key pair on `pm.request.certificate`.\n- **Empty/placeholder certificate is normal, not an error state.** If the request URL contains a template tag (e.g. `{{ baseUrl }}`) or there's no matching client certificate configured in Settings, `pm.request.certificate` is initialized to an empty certificate and `insomnia.ts` logs this via `getExistingConsole().warn(...)` — a warning-level timeline entry, not debug — so it's visible when tracing why the default/empty certificate was selected. Scripts should not assume `key`/`cert`/`pfx` are populated.\n- **`matches` is derived only from certificate `host`, singular** — even though `UrlMatchPatternList` supports multiple patterns, `insomnia.ts` only ever seeds `matches: [matchedCertificates[0].host]` (a single-element array) from the native model.\n\n## Related\n- `properties.ts` — `Property` base class (provides `id`, `name`, `disabled`, `toJSON()`/`toString()` defaults).\n- `urls.ts` — `UrlMatchPattern`, `UrlMatchPatternList` (used for `matches` and `canApplyTo()`).\n- `request.ts` — constructs/serializes `pm.request.certificate`, and contains `mergeClientCertificates()` which reconciles the script's single certificate back into Insomnia's native multi-certificate list.\n- `insomnia.ts` — seeds the initial `pm.request.certificate` value from Insomnia Settings' configured client certificates, using `filterClientCertificates` from `insomnia/src/network/certificate`.\n- `insomnia-data` package — source of the native `ClientCertificate` type.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/collection.md (Tokens: ~1112 | Category: anthropic-skill) -->\n# collection.ts (re-export barrel — no `Collection` class)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/collection.ts`\n\n## Purpose\n**This file does not define a `Collection` class, and has no logic of its own.** Its entire content is a JSDoc `@module` comment plus eleven `export { ... } from '...'` re-export statements:\n```ts\nexport { RequestAuth } from './auth';\nexport { Certificate } from './certificates';\nexport { Cookie, CookieList } from './cookies';\nexport { Header, HeaderList } from './headers';\nexport { Property, PropertyBase, PropertyList } from './properties';\nexport { ProxyConfig, ProxyConfigList } from './proxy-configs';\nexport { FormParam, Request, RequestBody } from './request';\nexport { Response } from './response';\nexport { QueryParam, Url, UrlMatchPattern, UrlMatchPatternList } from './urls';\nexport { Variable, VariableList } from './variables';\nexport { Folder } from './folders';\n```\nIt exists purely so that `index.ts` can do `export * as Collection from './collection'`, giving SDK/type consumers a `Collection.Property`, `Collection.Variable`, `Collection.Folder`, etc. namespace (mirroring the Postman `postman-collection` package's `Collection.*` type namespace for compatibility/familiarity). There is no runtime behavior, no constructor, and nothing related to collection-scoped *variable storage* in this file.\n\n## Public API\nNo classes/functions of its own. It re-exports (unchanged) the following, whose real definitions and docs live in their own files:\n- `RequestAuth` (`auth.ts`)\n- `Certificate` (`certificates.ts`)\n- `Cookie`, `CookieList` (`cookies.ts`)\n- `Header`, `HeaderList` (`headers.ts`)\n- `Property`, `PropertyBase`, `PropertyList` (`properties.ts` — see `properties.md`)\n- `ProxyConfig`, `ProxyConfigList` (`proxy-configs.ts`)\n- `FormParam`, `Request`, `RequestBody` (`request.ts`)\n- `Response` (`response.ts`)\n- `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` (`urls.ts`)\n- `Variable`, `VariableList` (`variables.ts` — see `variables.md`)\n- `Folder` (`folders.ts` — see `folders.md`)\n\nNote this list is nearly identical to `index.ts`'s own top-level JSDoc module comment (both files carry the same doc block), but `index.ts` itself re-exports far more (all of `environments.ts`, `insomnia.ts`, `test.ts`, `execution.ts`, etc.) in addition to wrapping this file as the `Collection` namespace.\n\n## Script-facing surface\nNo direct script-facing surface of its own. **If you are looking for how `pm.collectionVariables` actually works** (collection-level variable storage and resolution), that logic lives in:\n- `environments.ts` — the `Environment` class backing the actual store.\n- `insomnia.ts` — `InsomniaObject` explicitly maps `this.collectionVariables = this.baseEnvironment` (i.e. `pm.collectionVariables` **is** `pm.baseEnvironment`, the same `Environment` instance, not a separate object) and constructs the `Variables` hierarchy (`collectionVars: baseEnvironment`) used by `pm.variables`.\nSee `environments.md` for the full precedence/resolution behavior.\n\n## Gotchas / notable behavior\n- **Naming trap**: a file named `collection.ts` in a scripting/variable-resolution context strongly suggests a `Collection` class with collection-variable logic — it has none. Anyone debugging \"collection variables\" behavior should look at `environments.ts` (`Environment`) and `insomnia.ts` (`collectionVariables = baseEnvironment` aliasing), not this file.\n- Diffed byte-for-byte against `index.ts`'s doc comment: the JSDoc header text is duplicated between the two files, but the actual export lists differ — `collection.ts` only re-exports a subset (the \"Postman-collection-shaped\" types), while `index.ts` re-exports everything including `insomnia.ts`, `environments.ts`, `console.ts`, `execution.ts`, `test.ts`, `async-objects.ts`, `request-info.ts`.\n- `index.ts` wraps this file's exports under a namespace: `export * as Collection from './collection';` — so external consumers see `Collection.Variable`, `Collection.Folder`, etc., but that `Collection` namespace object is not related to `pm.collectionVariables` at runtime; it's purely a type/value re-export grouping.\n\n## Related\n- `index.ts` — re-exports this file's contents as the `Collection` namespace (`export * as Collection from './collection'`).\n- `environments.ts` — actual implementation backing `pm.collectionVariables`/`pm.baseEnvironment` (see `environments.md`).\n- `insomnia.ts` — wires `collectionVariables` to `baseEnvironment` and builds the `Variables` hierarchy.\n- `properties.ts`, `variables.ts`, `folders.ts`, `urls.ts`, `request.ts`, `response.ts`, `cookies.ts`, `headers.ts`, `auth.ts`, `certificates.ts`, `proxy-configs.ts` — the actual modules whose types this file re-exports.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/console.md (Tokens: ~1397 | Category: anthropic-skill) -->\n# Console\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/console.ts`\n\n## Purpose\nImplements a sandbox-safe replacement for the global `console` object used inside pre-request/after-response/test scripts. Instead of writing to a real stdout/devtools console, every call is captured into an in-memory row buffer that the execution harness later dumps and surfaces back to the Insomnia app (e.g. the response timeline).\n\n## Public API\n\n### `type LogLevel`\n```ts\ntype LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error';\n```\nNot exported — internal tag for which console method produced a row.\n\n### `interface Row`\n```ts\nexport interface Row {\n  value: string;\n  name: string;\n  timestamp: number;\n}\n```\n- `value` — the rendered `\"<level>: <content>\"` string.\n- `name` — always `'Text'` in current usage.\n- `timestamp` — `Date.now()` at log time.\n\n### `class Console`\nNo explicit constructor (uses field initializers); instantiated with `new Console()`.\n\n- `rows: Row[]` — accumulated log entries, in call order.\n- `printLog = (rows: Row[], level: LogLevel, ...values: any) => void` — core formatter (marked `@ignore`, not really meant to be called directly by users). Maps each value to a string (`typeof value === 'string' ? value : JSON.stringify(value, null, 2)`), joins with `' '`, and pushes `{ value: `${level}: ${content}`, name: 'Text', timestamp: Date.now() }` onto `rows`. If formatting throws, it instead pushes a row whose `value` is `'error: ' + JSON.stringify(e, null, 2)`.\n- `log = (...values: any[]) => void` — calls `printLog(this.rows, 'log', ...values)`.\n- `warn = (...values: any[]) => void` — calls `printLog(this.rows, 'warn', ...values)`.\n- `debug = (...values: any[]) => void` — calls `printLog(this.rows, 'debug', ...values)`.\n- `info = (...values: any[]) => void` — calls `printLog(this.rows, 'info', ...values)`.\n- `error = (...values: any[]) => void` — calls `printLog(this.rows, 'error', ...values)`.\n- `clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => void` — **always throws** `new Error('currently \"clear\" is not supported for the timeline')`. Calling `console.clear()` in a script will throw.\n- `dumpLogs = () => string` — returns all rows serialized as `JSON.stringify(row) + '\\n'`, joined with `'\\n'` (i.e. one JSON blob per row, double-newline separated).\n- `dumpLogsAsArray = () => string[]` — same per-row serialization (`JSON.stringify(row) + '\\n'`) but returned as an array instead of one joined string.\n\n### Module-level singleton helpers\n- `let builtInConsole = new Console();` (module-private).\n- `getExistingConsole(): Console` — returns the current singleton without creating a new one.\n- `getNewConsole(): Console` — replaces the singleton with a fresh `new Console()` and returns it (used to reset log state between script executions).\n\n## Script-facing surface\n- `console.log(...)`, `console.warn(...)`, `console.debug(...)`, `console.info(...)`, `console.error(...)` — available as the script's global `console` inside pre-request/after-response/test scripts (the sandbox injects a `Console` instance as the `console` parameter, not the real JS console).\n- `console.clear()` — present but always throws if called from a script.\n\n## Gotchas / notable behavior\n- **`clear()` is a hard error, not a no-op.** Any script calling `console.clear()` will throw `Error('currently \"clear\" is not supported for the timeline')`, which will surface as a script failure.\n- **Object logging uses `JSON.stringify(value, null, 2)`**, not `util.inspect`-style formatting — circular references or values with `toJSON()` quirks will affect what shows up. If `JSON.stringify` itself throws (e.g. circular reference), the whole `printLog` call falls back to a single `'error: ' + JSON.stringify(e, null, 2)` row rather than losing the log entirely — but the original values are lost in that fallback.\n- **No format-string substitution (`%s`, `%d`, etc.)** — the `// TODO: support replacing substitution` comment confirms this isn't implemented; values are just stringified and space-joined.\n- **State is a module-level singleton**, not per-instance-only: `getExistingConsole()`/`getNewConsole()` share one `builtInConsole` variable across the module. The harness must call `getNewConsole()` before each script run to avoid leaking log rows from a previous execution into the next one's dump.\n- **Two different dump shapes exist** (`dumpLogs()` joined string vs. `dumpLogsAsArray()` array of strings) — different call sites in the harness use one or the other (see Related), so a log line \"disappearing\" could be a symptom of reading from the wrong dump method or a stale singleton reference.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — imports `getExistingConsole` and uses it only for a one-off warning log during `initInsomniaObject` (an empty-certificate warning); it is not the general per-script logging path.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) — calls `getNewConsole()` to obtain a fresh `Console` instance for each script run and passes `scriptConsole.log` into `initInsomniaObject(sandboxContext, scriptConsole.log)`.\n- `packages/insomnia/src/scripting/run-script.ts` — injects the `Console` instance itself as the sandboxed script's `console` global (part of the generated function's parameter list), and at the end calls `scriptConsole.dumpLogsAsArray()`, including the result (`logs`) in the returned `RequestContext`.\n- Legacy path `packages/insomnia/src/script-executor.ts` — calls `scriptConsole.dumpLogs()` (joined-string form) and appends it to a timeline file via `fs.promises.appendFile`, which is how logs end up in Insomnia's response timeline view in that code path.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/cookies.md (Tokens: ~2554 | Category: anthropic-skill) -->\n# Cookie, CookieJar, CookieList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/cookies.ts`\n\n## Purpose\nModels HTTP cookies for the scripting environment. `Cookie` wraps a `tough-cookie` cookie plus Insomnia-specific extension fields; `CookieList` is a keyed `PropertyList<Cookie>`; `CookieObject` (a `CookieList` subclass) is the concrete class backing `pm.cookies` and pairs the list with a `CookieJar`; `CookieJar` is a custom (non-tough-cookie) jar keyed by domain used for `pm.cookies.jar()`. `mergeCookieJar` reconciles a script-produced jar back into Insomnia's native `CookieJar` model.\n\n## Public API\n\n### Types\n- `interface InsomniaCookieExtensions { creation?: Date; creationIndex?: number; lastAccessed?: Date; pathIsDefault?: boolean }` — Insomnia-only metadata carried alongside the standard cookie fields.\n- `interface CookieOptions extends InsomniaCookieExtensions { id?: string; key: string; value: string; expires?: Date | string | null; maxAge?: number | 'Infinity' | '-Infinity'; domain?: string; path?: string; secure?: boolean; httpOnly?: boolean; hostOnly?: boolean; session?: boolean; extensions?: { key: string; value: string }[] }` — constructor input for `Cookie`.\n\n### `class Cookie extends Property`\n- `constructor(cookieDef: CookieOptions | string)` — if given a string, parses it via `Cookie.parse()` first (throws `Error('failed to parse cookie, the cookie string seems invalid')` if parsing fails or the tough-cookie construction fails). Wraps the result in a `tough-cookie` `Cookie` (`ToughCookie.fromJSON`). `extensions` are stored separately from the tough-cookie object (tough-cookie only handles `string[]` extensions natively).\n- `static override _index = 'key'` — `PropertyList.one()`/`indexOf()` look items up by `key` for cookie lists.\n- `static isCookie(obj: Property): boolean` — checks `obj._kind === 'Cookie'`.\n- `static parse(cookieStr: string): CookieOptions` — parses a `Set-Cookie`-style string (loose mode) via tough-cookie, pulls `HostOnly`/`Session` out of the extensions list into dedicated booleans, and converts remaining string extensions (`key=value` or bare flags, which become `{key, value: 'true'}`) into `{key, value}` objects. Throws if the string can't be parsed by tough-cookie.\n- `static stringify(cookie: Cookie): string` — alias for `cookie.toString()`.\n- `static unparseSingle(cookieOpt: CookieOptions): string` — constructs a `Cookie` from options and returns its string form.\n- `static unparse(cookies: Cookie[]): string` — joins each cookie's `toString()` with `'; '`.\n- `toString(): string` — tough-cookie's own `toString()` plus `; HostOnly`, `; Session`, and `; key=value` segments appended for any extensions/inso flags present.\n- `valueOf(): string` — returns just `cookie.value` (used by `PropertyList.one()` for lookups and by `toObject()`).\n- `get key(): string` — reads through to `this.cookie.toJSON().key`.\n- `toJSON()` — returns the full plain-object shape: `{ id, key, value, expires, maxAge, domain, path, secure, httpOnly, hostOnly, session, extensions, creation, creationIndex, lastAccessed, pathIsDefault }`. Note `expires` is coerced to `undefined` when tough-cookie reports `'Infinity'`.\n\n### `class CookieList extends PropertyList<Cookie>`\n- `constructor(cookies: Cookie[])` — always constructs with `typeClass = Cookie`, no `parent`.\n- `static isCookieList(obj: object): boolean`.\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, string | string[]>` — builds a plain `key -> value` map via `Object.create(null)` (so no prototype — dangerous keys like `__proto__`/`constructor` land as own properties, not pollute the prototype). `excludeDisabled` skips cookies where `cookie.disabled` is true. `multiValue` collects same-key cookies into an array instead of overwriting (last value wins when `multiValue` is falsy).\n\n### `class CookieObject extends CookieList`\nThis is the concrete class instantiated as `pm.cookies`.\n- `constructor(cookieJar: InsomniaCookieJar | null)` — maps each native Insomnia cookie into a script `Cookie` (translating numeric `expires` into a `Date`, and passing through `maxAge: undefined`/`session: undefined`/`extensions: undefined` since those fields aren't tracked by Insomnia's native cookie model). Also builds an internal `CookieJar` (named from `cookieJar.name`, or `''`/empty if `cookieJar` is `null`).\n- `jar(): CookieJar` — returns the internal `CookieJar` instance backing `pm.cookies.jar()`.\n\n### `class CookieJar` (custom, not `tough-cookie`'s jar)\nComment: \"CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain as it reads location from the browser window, it is 'localhost'\". Internally a `Map<domain, Map<cookieKey, Cookie>>`.\n- `constructor(jarName: string, cookies?: Cookie[])` — indexes each cookie by its `domain` (read via `cookie.toJSON().domain`). If a cookie has no `domain`, it's **dropped** with a console warning: `` `domain is not specified for the cookie \"${cookie.key}\" so it is omitted` `` (via `getExistingConsole().warn`).\n- `set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void): void` — `url` is actually used as the domain key (not parsed as a URL). If `value` is a plain string, constructs a minimal `Cookie({ key, value, domain: url })`; otherwise treats `value` as full `CookieOptions` and constructs `new Cookie(value)`. Always calls `cb(undefined, cookie)` — this implementation never produces an error.\n- `get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void): void` — looks up `name` within the `url` domain bucket; calls back with `undefined` cookie if not found (not an error).\n- `getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void): void` — returns all cookies for that domain bucket (empty array if none).\n- `unset(url: string, name: string, cb: (error?: Error | null) => void): void` — deletes `name` from the domain bucket if the bucket exists; always calls back with no error.\n- `clear(url: string, cb: (error?: Error | null) => void): void` — deletes the entire domain bucket.\n- `toInsomniaCookieJar(): { name: string; cookies: Partial<InsomniaCookie>[] }` — flattens the map back into Insomnia's native cookie-jar shape. `expires` is defaulted back to `'Infinity'` if falsy (\"avoid edge cases\").\n\n### Module-level function\n- `mergeCookieJar(originalCookieJar: InsomniaCookieJar, updatedCookieJar: { name: string; cookies: Partial<InsomniaCookie>[] }): InsomniaCookieJar` — assigns a fresh `uuidv4()` id to any cookie missing one (mirroring the id-generation approach in Insomnia's `cookie-list.tsx`), and returns `{ ...originalCookieJar, cookies: cookiesWithId }`. This is the function that reconciles a script's mutated jar back into Insomnia's persisted model after a script runs.\n\n## Script-facing surface\n- `pm.cookies` is a `CookieObject` (constructed in `insomnia.ts` via `new CookieObject(rawObj.cookieJar)`), so scripts get all `CookieList`/`PropertyList` methods directly: `pm.cookies.get(key)` / `pm.cookies.one(key)` (via inherited `PropertyList.get`/`one`, which for `Cookie` returns `cookie.valueOf()` — i.e. just the string value, not the `Cookie` object, because `Cookie.valueOf` is defined), `pm.cookies.toObject()`, `pm.cookies.all()`, `pm.cookies.count()`, `pm.cookies.each()`, `pm.cookies.add()`, etc.\n- `pm.cookies.jar()` returns the `CookieJar`, giving access to `set(url, key, value, cb)`, `get(url, name, cb)`, `getAll(url, cb)`, `unset(url, name, cb)`, `clear(url, cb)` — all Postman-style callback-based cookie-jar operations, scoped by domain (the `url` argument is treated as a plain domain string, not parsed).\n- After a script runs, Insomnia calls `this.cookies.jar().toInsomniaCookieJar()` (see `insomnia.ts`) to get the jar back out, which is then merged via `mergeCookieJar`.\n\n## Gotchas / notable behavior\n- **`PropertyList.one()`/`.get()` return the raw value, not a `Cookie`**: because `Cookie` defines `valueOf()`, and `PropertyList.one()` special-cases items with a `valueOf` method — `pm.cookies.get('foo')` returns the cookie's string value, not the `Cookie` instance. To get the full object, use `pm.cookies.find(...)` or `pm.cookies.all()` instead.\n- **`toObject()` uses `Object.create(null)`** specifically to avoid prototype pollution when cookie keys are things like `__proto__` or `constructor` — confirmed by a dedicated test (`toObject does not pollute the prototype for dangerous cookie keys`).\n- **Cookies without a `domain` are silently dropped** when building a `CookieJar` (constructor only; a warning is logged, not thrown).\n- **`maxAge` and `session` are not persisted from Insomnia's native model**: `CookieObject`'s constructor always sets `maxAge: undefined` and `session: undefined` when converting from `InsomniaCookieJar`, with inline comments \"not supported in Insomnia\".\n- **`extensions` format from Insomnia is unknown** per an inline `TODO` — `CookieObject`'s constructor always passes `extensions: undefined`.\n- **`CookieJar.set()` never reports an error** — the callback signature allows an `Error`, but the implementation has no failure path (a malformed `CookieOptions` would throw inside `new Cookie(...)` before the callback is ever reached, rather than being passed to `cb`).\n- **Duplicate keys in `toObject()` collapse to the last value** unless `multiValue` is passed as `true`, in which case duplicates accumulate into an array.\n- **`Cookie.parse()` of a malformed string like `'=gingerale'`** still succeeds with `key: ''`, `value: 'gingerale'`, `expires: 'Infinity'` (per the test) — it does not throw for merely unusual input, only for input tough-cookie's parser rejects entirely.\n\n## Related\n- `properties.ts` — `Property` (base class for `Cookie`) and `PropertyList` (base class for `CookieList`).\n- `console.ts` — `getExistingConsole()`, used by `CookieJar`'s constructor to warn about domain-less cookies.\n- `insomnia.ts` — constructs `pm.cookies` as `new CookieObject(rawObj.cookieJar)` and reads back `this.cookies.jar().toInsomniaCookieJar()` after script execution.\n- `insomnia-data` package — source of the native `Cookie`/`CookieJar` types this file converts to/from.\n- `tough-cookie` (external) — underlying cookie parse/stringify implementation wrapped by `Cookie`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/environments.md (Tokens: ~2549 | Category: anthropic-skill) -->\n# Environment / Variables / Vault\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/environments.ts`\n\n## Purpose\nThis file defines `Environment` (a single flat key-value store), `Variables` (a hierarchical read/write facade over several `Environment` scopes, resolving lookups in precedence order), and `Vault` (a locked-down `Environment` subclass for secrets). `InsomniaObject` (`insomnia.ts`) instantiates these and exposes them as `insomnia.environment`, `insomnia.globals`/`insomnia.baseGlobals` (private), `insomnia.collectionVariables` (aliased to `baseEnvironment`), `insomnia.variables`, and `insomnia.vault` — the core variable model behind pre-request/after-response/test scripts.\n\n## Public API\n\n### `class Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined)\n```\nInitializes `_name` and a `Map<string, boolean|number|string|null|undefined>` from `Object.entries(jsonObject || {})`.\n\n- `get name(): string` — returns `_name` (read-only getter; no setter).\n- `has = (variableName: string) => boolean` — `Map.has`.\n- `get = (variableName: string) => boolean|number|string|null|undefined` — `Map.get`; returns `undefined` if absent.\n- `set = (variableName: string, variableValue: boolean|number|string|undefined|null) => void` — sets the value; if `Number.isNaN(variableValue)` is true, logs a warning via `getExistingConsole().warn` and **does not** set the value.\n- `unset = (variableName: string) => void` — `Map.delete`.\n- `clear = () => void` — `Map.clear`.\n- `replaceIn = async (template: string | object) => Promise<string>` — coerces an object template via `.toString()`, throws `TypeError` if not a string/object, then renders it through `getInterpolator().render(template, this.toObject())`. Supports `{{$randomUUID}}` and `{{varName}}` placeholders.\n- `toObject = () => Record<string, any>` — `Object.fromEntries(this.kvs.entries())`.\n- `toJSON()` — returns `this.toObject()` (drives `JSON.stringify(environment)` serialization).\n\n### `function mergeFolderLevelVars(folderLevelVars: Environment[]): Environment` *(not exported, `@ignore`)*\nReduces an array of folder `Environment`s into one merged `Environment` named `'mergedFolderLevelVars'`, with **later folders in the array winning** on key collisions (`{ ...merged, ...folderLevelEnv.toObject() }`).\n\n### `class Variables`\n```ts\nconstructor(args: {\n  baseGlobalVars: Environment;\n  globalVars: Environment;\n  collectionVars: Environment;\n  environmentVars: Environment;\n  iterationDataVars: Environment;\n  folderLevelVars: Environment[];\n  localVars: Environment;\n})\n```\nStores each scope as a private `Environment` (or `Environment[]` for folder levels).\n\n- `has = (variableName: string) => boolean` — true if any of the 7 scopes has the variable (local, folder-level via `.some`, iterationData, environment, collection, global, baseGlobal — all evaluated eagerly, then OR'd).\n- `get = (variableName: string) => value` — builds an ordered array `[localVars, mergeFolderLevelVars(folderLevelVars), iterationDataVars, environmentVars, collectionVars, globalVars, baseGlobalVars]`, uses `.find(vars => vars.has(variableName))` to pick the **first scope (highest precedence) that has the key**, and returns `scope?.get(variableName)`.\n- `set = (variableName, variableValue) => void` — **always writes only to `localVars`** (same NaN-guard/warning as `Environment.set`). There is no way to write directly to environment/collection/global scope through `Variables.set`.\n- `replaceIn = async (template: string | object) => Promise<string>` — same contract as `Environment.replaceIn`, but the context is `this.toObject()` (the fully merged view across all scopes).\n- `toObject = () => Record<string, any>` — maps `[baseGlobalVars, globalVars, collectionVars, environmentVars, iterationDataVars, mergeFolderLevelVars(folderLevelVars), localVars]` to plain objects and folds them left-to-right with `{...ctx, ...obj}`, so **later entries in that array win** — i.e. `localVars` has final say, matching the precedence order used by `get`.\n- `localVarsToObject = () => Record<string, any>` *(`@ignore`)* — returns just `localVars.toObject()`; used by `InsomniaObject.toObject()` to serialize `insomnia.variables`.\n\n### `class Vault extends Environment`\n```ts\nconstructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean)\n```\nCalls `super(name, jsonObject)`, then **returns a `Proxy(this, ...)`** (note: the constructor return value, not `this`, is what callers get). The proxy's `get`/`set` traps throw `new Error('Vault is disabled in script')` for **every property/method access** (including inherited `Environment` methods like `get`/`has`) whenever `enableVaultInScripts` is falsy.\n- `unset = () => { throw new Error('Vault can not be unset in script'); }` — overridden to always throw, regardless of `enableVaultInScripts`.\n- `clear = () => { throw new Error('Vault can not be cleared in script'); }` — always throws.\n- `set = () => { throw new Error('Vault can not be set in script'); }` — always throws (vault is read-only from scripts even when enabled).\n- Inherited `get`, `has`, `toObject`, `toJSON`, `replaceIn`, `name` still work normally when `enableVaultInScripts` is true (subject to the proxy's guard, which only blocks when the flag is false).\n\n## Script-facing surface\n- `insomnia.environment` → an `Environment` instance (the selected sub-environment, or the base environment itself if none is selected — see `initInsomniaObject` in `insomnia.ts`). Scripts call `insomnia.environment.get/set/unset/has/clear/replaceIn/toObject`.\n- `insomnia.baseEnvironment` → the collection's base `Environment`.\n- `insomnia.collectionVariables` → in `insomnia.ts`, this is literally assigned `this.baseEnvironment` (`this.collectionVariables = this.baseEnvironment;`) — it is not a distinct store, it's the *same* `Environment` object as `insomnia.baseEnvironment`.\n- `insomnia.variables` → a `Variables` instance built from all scopes (`insomnia.ts`'s `initInsomniaObject`: `baseGlobalVars: baseGlobals, globalVars: globals, environmentVars: environment, collectionVars: baseEnvironment, iterationDataVars: iterationData, folderLevelVars: parentFolders.getEnvironments(), localVars: localVariables`). Scripts use `insomnia.variables.get/set/has/replaceIn/toObject`. `insomnia.variables.set(...)` only ever affects the transient local scope.\n- `insomnia.vault` → a `Vault` instance; `insomnia.vault.get(<name>)` is the documented usage. Gated by the `enableVaultInScripts` setting.\n- `insomnia.globals`/`insomnia.baseGlobals` are declared `private` on `InsomniaObject`, but that's a TypeScript-only, compile-time restriction — `private` is erased at runtime, and `InsomniaObject`'s constructor returns `new Proxy(this, { get: (target, prop, receiver) => ... Reflect.get(target, prop, receiver) })` (see `insomnia.ts`), which forwards any property name through to the real field. A script can read `insomnia.globals`/`insomnia.baseGlobals` directly at runtime; they're just absent from the TS-declared public API, not actually inaccessible. Treat this as accidental exposure, not a supported surface — don't build fixes or features on scripts reading these, and any change here should aim to actually restrict access (e.g. a proxy guard like `Vault`'s) rather than expose them further.\n- `{{$randomUUID}}`-style and `{{variableName}}` templates in request URLs/bodies/headers are ultimately rendered through `Environment.replaceIn` / `Variables.replaceIn`.\n\n## Gotchas / notable behavior\n- **NaN guard**: both `Environment.set` and `Variables.set` silently refuse to store `NaN` and log a console warning instead of throwing — `null` and `undefined` are accepted just fine.\n- **Precedence order** (highest to lowest) used consistently by `Variables.has`, `.get`, and `.toObject`: local → folder-level (the **last** entry in the `folderLevelVars: Environment[]` array wins on collisions — see the merge-order gotcha below, not necessarily the nearest folder) → iteration data → environment → collection (`baseEnvironment`) → global → base global.\n- **Falsy values are not skipped**: `Variables.get` uses `.has()` to pick the scope, not truthiness, so a local value of `0`, `''`, or `false` is correctly returned instead of falling through to a lower-precedence scope (verified by a parameterized test in `environments.test.ts`).\n- **`collectionVariables` is an alias, not a separate object** — mutating `insomnia.collectionVariables` also mutates `insomnia.baseEnvironment` and vice versa, since they reference the same `Environment` instance.\n- **`Variables.set` cannot target non-local scopes** — there is no API on `Variables` to write into environment/collection/global; scripts must use `insomnia.environment.set(...)`, `insomnia.collectionVariables.set(...)`, etc. directly for those scopes.\n- **Folder-level merge order**: `mergeFolderLevelVars` merges the given `Environment[]` left-to-right with later entries winning; since `ParentFolders.getEnvironments()` returns folders \"from bottom to top\" is *not* guaranteed here — actual nearest-wins behavior depends on the order `folderLevelVars` is constructed in by the caller (see `folders.md`).\n- **`Vault` returns a `Proxy` from its constructor** — `new Vault(...)` does not yield a plain `Vault` instance; every property access goes through the proxy trap, so `enableVaultInScripts` is (re-)checked on *every* access, not just once at construction.\n- **`Vault` is effectively write-protected even when enabled**: `set`, `unset`, and `clear` are all overridden to unconditionally throw, so scripts can only ever read from the vault, never write to it, regardless of the `enableVaultInScripts` flag.\n- Deep/nested object values are not directly supported by `Environment`'s type signature (`boolean | number | string | null | undefined`); nothing in this file coerces or validates that at runtime, so anything else the caller passes is stored as-is.\n\n## Related\n- `properties.ts` — not used directly here, but `Variables`/`Environment` sit at the same conceptual layer that the `Variable`/`VariableList` classes (in `variables.ts`) also use for interpolation (via `getInterpolator()` in `interpolator.ts`).\n- `folders.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies the `folderLevelVars: Environment[]` array consumed by `Variables`.\n- `console.ts` — `getExistingConsole()` is used to emit the NaN warning.\n- `interpolator.ts` — `getInterpolator().render(...)` implements `{{...}}` template substitution for `replaceIn`.\n- `insomnia.ts` — constructs and wires up all the `Environment`/`Variables`/`Vault` instances exposed as `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`, `insomnia.vault`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/execution.md (Tokens: ~1001 | Category: anthropic-skill) -->\n# Execution (`insomnia.execution` / `pm.execution`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/execution.ts`\n\n## Purpose\nModels `pm.execution`: where the currently-running request sits in the collection tree, plus two\ncollection-runner controls — skipping the request and rerouting to a different next request. This\nis one of the fields the collection runner reads back after a script finishes.\n\n## Public API\n\n### `interface ExecutionOption`\n```ts\ninterface ExecutionOption {\n  location: string[];\n  skipRequest?: boolean;\n  nextRequestIdOrName?: string;\n}\n```\nPlain-object shape used to construct an `Execution` (and the shape stored on `RequestContext.execution`, see `interfaces.md`).\n\n### `class Execution`\n```ts\nconstructor(options: ExecutionOption)\n```\n- `public location: string[]` — **not a plain array**: the constructor wraps a shallow copy\n  (`[...location]`) in a `Proxy` whose `get` trap adds a `current` accessor returning\n  `target.length > 0 ? target[target.length - 1] : ''` (the last path segment, or `''` if empty),\n  mirroring Postman's `execution.location.current` usage. All other property/index access passes\n  through via `Reflect.get`. Throws `TypeError('Location input must be array of string')` if\n  `options.location` is not an array.\n- `private _skipRequest: boolean` (default `false`), `private _nextRequestIdOrName: string` (default `''`) — no direct getters; only visible via `toObject()`.\n- `skipRequest = () => { this._skipRequest = true; }` — one-way flag setter; there is no method to un-set it.\n- `setNextRequest = (requestIdOrName: string) => { this._nextRequestIdOrName = requestIdOrName; }` — records which request the collection runner should execute next.\n- `toObject = () => ({ location: Array.from(this.location), skipRequest: this._skipRequest, nextRequestIdOrName: this._nextRequestIdOrName })` — serializes state to a plain object; `Array.from` strips the `Proxy`, so the resulting `location` array **loses the `.current` accessor**.\n\n## Script-facing surface\n- `insomnia.execution.location` — array of path segments (e.g. `['project', 'workspace', 'file', 'requestname']`).\n- `insomnia.execution.location.current` — last segment of the location path (Postman-compat).\n- `insomnia.execution.skipRequest()` — marks the current request to be skipped.\n- `insomnia.execution.setNextRequest(requestIdOrName)` — reroutes the collection runner to a specific request by id or name.\n\n## Gotchas / notable behavior\n- `location.current` only works on the live `Proxy`-wrapped array returned from the constructor;\n  once serialized via `toObject()` (which uses `Array.from`), the plain array no longer has\n  `.current`. This only matters for the runtime maintainers reading `toObject()`'s output — script\n  authors reading `insomnia.execution.location.current` directly are unaffected.\n- Passing a non-array `location` (e.g. a string) throws a `TypeError` synchronously from the\n  constructor — this is a `initInsomniaObject`/context-building bug, not something a script can\n  trigger directly, since scripts only read `insomnia.execution`, they don't construct it.\n- There's no public getter for `skipRequest`/`nextRequestIdOrName` state from the script side —\n  scripts can only set them, not read them back; only `toObject()` (used internally when the\n  runtime merges results into the persisted request context) exposes the current values.\n- Comment references Postman's docs on \"using variables in scripts\" for the `location.current`\n  design: https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/#using-variables-in-scripts\n\n## Related\n- `insomnia.ts` — `InsomniaObject.execution: Execution`; `toObject()` calls `execution.toObject()`.\n- `interfaces.ts` — `ExecutionOption` is embedded as `RequestContext.execution`.\n- `packages/insomnia/src/scripting/run-script.ts` — merges `mutatedContextObject.execution` (i.e. `Execution.toObject()`'s output) back into the returned `RequestContext` so the collection runner can act on `skipRequest`/`nextRequestIdOrName`.\n- `__tests__/execution.test.ts` — covers the `location`/`current` proxy behavior, `skipRequest()`/`setNextRequest()`, and the invalid-`location` throw.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/folders.md (Tokens: ~1426 | Category: anthropic-skill) -->\n# Folder / ParentFolders\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/folders.ts`\n\n## Purpose\nModels the folder hierarchy a request lives in, each folder carrying its own folder-level `Environment` of variables. `ParentFolders` is a container/lookup helper over an ordered array of `Folder`s, and its `getEnvironments()` is what feeds the `folderLevelVars: Environment[]` array consumed by `Variables` (`environments.ts`) — i.e. this is the source of folder-scoped variable resolution for `insomnia.variables`.\n\n## Public API\n\n### `class Folder`\n```ts\nconstructor(id: string, name: string, environmentObject: object | undefined)\n```\n- `id: string`\n- `name: string`\n- `environment: Environment` — constructed as `new Environment(`${id}.environment`, environmentObject)` (see `environments.md`).\n- `toObject = () => { id: string; name: string; environment: Record<string, any> }` — returns `id`, `name`, and `environment.toObject()`.\n\n### `class ParentFolders`\n```ts\nconstructor(private folders: Folder[])\n```\nPer the constructor JSDoc, `folders` is expected \"from bottom to top\" (nearest-ancestor folder first).\n\n- `get = (idOrName: string) => Folder` — finds the first folder whose `name` or `id` matches; **throws** `Error('Folder \"<idOrName>\" not found')` if none match.\n- `getById = (id: string) => Folder` — finds by `id` only; throws the same style of error if not found.\n- `getByName = (folderName: string) => Folder` — finds by `name` only; throws the same style of error if not found.\n- `findValue = (valueKey: string) => value | undefined` — reverses a **copy** of `this.folders` (`[...this.folders].reverse()`) and returns the first folder (after reversal) whose `environment.has(valueKey)`, then returns that folder's `environment.get(valueKey)`; returns `undefined` if no folder has it.\n- `toObject = () => object[]` — maps every folder to `folder.toObject()`, preserving the original (non-reversed) array order.\n- `getEnvironments = () => Environment[]` — maps every folder to `folder.environment`, preserving the original array order. This is the array handed to `Variables({ folderLevelVars: ... })`.\n\n## Script-facing surface\nNot directly exposed as a `pm.*` object itself — `ParentFolders` is internal plumbing constructed in `insomnia.ts`'s `initInsomniaObject`:\n```ts\nconst parentFolders = new ParentFolders(\n  rawObj.parentFolders.map(folderObj => new Folder(folderObj.id, folderObj.name, folderObj.environment)),\n);\n```\nand consumed two ways:\n- `parentFolders.getEnvironments()` → passed as `folderLevelVars` into `insomnia.variables` (`Variables`), so scripts reading `insomnia.variables.get('someFolderVar')` are indirectly reading through `Folder.environment`.\n- `parentFolders.toObject()` → included in `InsomniaObject.toObject()`'s `parentFolders` field (used for serialization/debugging, not typically read directly by user scripts).\nThere is no `insomnia.parentFolders` / `insomnia.folders` public property — `parentFolders` is a private field on `InsomniaObject`.\n\n## Gotchas / notable behavior\n- **The two folder-ordering JSDoc comments in this codebase appear to disagree, and `findValue` is not what scripts actually go through for `insomnia.variables`.** `ParentFolders`'s constructor doc says its `folders` array is ordered \"from bottom to top\" (i.e. index `0` = nearest/bottom, last index = topmost ancestor). `findValue`'s own doc says it searches \"starting from the nearest ancestor folder and moving towards the top ancestor folder.\" But the implementation does `[...this.folders].reverse().find(...)` — reversing a bottom-to-top array produces a top-to-bottom traversal, which is the *opposite* of what `findValue`'s own docstring claims. Treat `findValue`'s precedence as unverified/possibly inverted from its documented intent, and confirm against the actual call order at the construction site (`packages/insomnia/src/network/network.ts`, outside this SDK package) before relying on it to debug a specific precedence question. Note also that `findValue` is a standalone utility on `ParentFolders` — it is **not** what `insomnia.variables.get(...)` uses.\n- **`insomnia.variables.get(...)`'s folder precedence is governed by `mergeFolderLevelVars` in `environments.ts`, not by `ParentFolders.findValue`.** `mergeFolderLevelVars` merges the `folderLevelVars: Environment[]` array left-to-right with **later array entries winning** (`{...merged, ...folderLevelEnv.toObject()}`), and `ParentFolders.getEnvironments()` (used to build that array) preserves the original, non-reversed `this.folders` order. Per `environments.test.ts`'s `'variables operations'` test, `folders.getEnvironments()` is passed directly as `folderLevelVars`, and mutating the folder passed **second** to the `ParentFolders` constructor (`folder2`) is what wins in `variables.get('value')` — i.e. whichever folder appears later in the array given to `new ParentFolders([...])` wins ties in `insomnia.variables`, regardless of what \"nearest\"/\"topmost\" means in that particular caller's ordering convention.\n- `get`, `getById`, and `getByName` all throw plain `Error`s (not a custom error type) with a template string — safe to catch with a generic `try/catch` but there's no error code to switch on.\n- `Folder.environment`'s name is derived (`${id}.environment`), not the folder's own `name` — don't assume `folder.environment.name === folder.name`.\n\n## Related\n- `environments.ts` — `Folder.environment` is an `Environment` instance; `ParentFolders.getEnvironments()` supplies `Variables`'s `folderLevelVars`, and `mergeFolderLevelVars` there is what actually implements the effective folder-variable precedence used by `insomnia.variables`.\n- `insomnia.ts` — constructs `ParentFolders` from `rawObj.parentFolders` and wires `getEnvironments()` into the `Variables` constructor; exposes `parentFolders.toObject()` via `InsomniaObject.toObject()`.\n- `index.ts` — re-exports `Folder` (but not `ParentFolders`) at the top level; `collection.ts` also re-exports `Folder`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/headers.md (Tokens: ~1417 | Category: anthropic-skill) -->\n# Header, HeaderList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/headers.ts`\n\n## Purpose\n\nModels a single HTTP header (`Header`) and an ordered collection of headers (`HeaderList`). Used by `Request.headers` and `Response.headers` (`pm.request.headers` / `pm.response.headers`), and internally by `Request`'s header-merge logic. `HeaderList` is a thin specialization of the generic `PropertyList`.\n\n## Public API\n\n### `HeaderDefinition` (interface)\n`{ key: string; value: string; id?: string; name?: string; disabled?: boolean }`\n\n### `Header` (extends `Property`)\n- `override _kind = 'Header'`\n- `key: string`, `value: string`\n- `constructor(opts: HeaderDefinition | string, name?: string)` — if `opts` is a string, it's parsed via `Header.parseSingle` (only `key`/`value` are set — `id`/`name`/`disabled` come from `Property`'s defaults, not from the string). If `opts` is an object, `id`, `key`, `name` (overridden by the `name` param if given), `value`, and `disabled` are all read from it (each falling back to `''`/`false` if absent).\n- `static override _index = 'key'` — tells `PropertyList` to index/dedupe `Header`s by `key` (used by `one`, `indexOf`, `upsert`).\n- `static create(input?: {key, value} | string, name?: string): Header` — defaults `input` to `{key: '', value: ''}`.\n- `static isHeader(obj: object): boolean` — checks `_kind === 'Header'`.\n- `static parse(headerString: string): {key, value}[]` — splits on `\\n`, ignores blank lines, parses each via `parseSingle`.\n- `static parseSingle(headerStr: string): {key, value}` — splits on the **first** colon. **Throws** `Error('Header.parseSingle: the header string seems invalid')` if there's no colon or it's at position 0.\n- `static unparse(headers: {key,value}[] | PropertyList<Header>, separator?: string): string` — maps each header through `unparseSingle` and joins (default separator `'\\n'`).\n- `static unparseSingle(header: {key,value} | Header): string` — `\"key: value\"`.\n- `update(newHeader: {key, value})` — mutates `this.key`/`this.value` in place.\n- `override valueOf()` — returns `this.value`.\n\n### `HeaderList<T extends Header>` (extends `PropertyList<T>`)\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — internally always constructs the underlying `PropertyList` with `Header` as the type class (regardless of `T`).\n- `static isHeaderList(obj: any): boolean` — checks `_kind === 'HeaderList'`.\n- `contentSize(): number` — sum of `header.toString().length` (i.e. `\"key: value\"`.length) across all headers; comment notes special characters aren't handled.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, string | string[]>` — builds a plain key→value(s) map. `excludeDisabled` (default falsy) skips headers with `disabled` truthy. `multiValue` (default falsy) collects same-key headers into an array instead of overwriting with the last value. `_caseSensitive` and `_sanitizeKeys` are accepted but **unused**.\n\n## Script-facing surface\n\n`pm.request.headers` and `pm.response.headers` are `HeaderList<Header>` instances. Beyond the methods above, all generic `PropertyList` methods are available: `add`/`append`, `all()`, `assimilate()`, `clear()`, `count()`, `each()`, `filter()`, `find()`, `get(key)` / `one(key)` (keyed by `key` since `Header._index = 'key'`), `has()`, `idx()`, `indexOf()`, `insert()`/`insertAfter()`, `map()`, `populate()`, `prepend()`, `reduce()`, `remove()`, `repopulate()`, `toString()`, `upsert()`. Typical script usage: `pm.request.headers.add({key, value})`, `pm.request.headers.get('Content-Type')`, `pm.response.headers.has(...)`, `pm.request.headers.upsert(new Header({...}))`.\n\n## Gotchas / notable behavior\n\n- `new Header(stringOpts)` only populates `key`/`value` from the parsed string — `id`, `name`, and `disabled` are left at `Property`'s constructor defaults (`''`, `''`, `false`), never read from the string form.\n- `Header.parseSingle` throws on malformed input (no colon) — a raw header string with a typo can throw instead of silently producing an empty/garbage header.\n- `PropertyList.one(key)` (inherited, used for `HeaderList.get`) iterates **from the end of the list backwards**, so when duplicate keys exist, the **last-added** matching header wins — relevant when a script both sets a default header and later overrides it with the same key.\n- `HeaderList.toObject()`'s `_caseSensitive` and `_sanitizeKeys` parameters exist in the signature (for compatibility with Postman's original API shape) but have **no effect** — only `excludeDisabled` and `multiValue` are actually implemented.\n- `HeaderList`'s constructor ignores its own `T` type parameter internally — it always builds the base `PropertyList` using the concrete `Header` class, not whatever `T` was passed.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property` (base class for `Header`), `PropertyList` (base class for `HeaderList`).\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.headers: HeaderList<Header>`; also reuses `Header`/`HeaderList` in `addHeader`/`removeHeader`/`upsertHeader`/`getHeaders`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response.headers: HeaderList<Header>`; used in `contentInfo()` to find `Content-Type`/`Content-Disposition`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/insomnia.md (Tokens: ~2567 | Category: anthropic-skill) -->\n# InsomniaObject (`insomnia` / `pm`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/insomnia.ts`\n\n## Purpose\nThis is the top-level scripting object: the single instance bound into the sandbox as `insomnia`\n(with `$` as a Postman-compat alias — see Script-facing surface). It aggregates every other object\nin this module (environments, variables, request, response, cookies, execution, request info,\nvault, client certificates, test assertions) into one value that a pre-request / after-response /\ntest script reads and mutates, and that the runtime serializes back out via `toObject()` after the\nscript finishes.\n\n## Public API\n\n### `class InsomniaObject`\n\n```ts\nconstructor(rawObj: {\n  globals: Environment;\n  baseGlobals: Environment;\n  iterationData: Environment;\n  environment: Environment;\n  baseEnvironment: Environment;\n  variables: Variables;\n  request: ScriptRequest;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookies: CookieObject;\n  requestInfo: RequestInfo;\n  execution: Execution;\n  response?: ScriptResponse;\n  parentFolders: ParentFolders;\n  vault?: Vault;\n})\n```\n\nPublic/exposed properties:\n- `environment: Environment` — the currently-selected environment.\n- `collectionVariables: Environment` — **same object reference as `baseEnvironment`** (see Gotchas).\n- `baseEnvironment: Environment` — the base (root) environment.\n- `variables: Variables` — layered variable resolver (globals/env/collection/iteration/folder/local).\n- `request: ScriptRequest` — the request being built/sent (`pm.request`).\n- `cookies: CookieObject` — cookie jar wrapper (`pm.cookies`).\n- `info: RequestInfo` — event/iteration metadata (`pm.info`).\n- `response?: ScriptResponse` — present only in after-response/test scripts.\n- `execution: Execution` — location/skip/next-request control (`pm.execution`).\n- `vault?: Vault` — secret vault accessor, gated by `settings.enableVaultInScripts`.\n- `clientCertificates: ClientCertificate[]` — raw client certs available to the request.\n\nPrivate/internal properties (TypeScript-only — see Gotchas for why this doesn't restrict scripts at\nruntime): `_expect` (chai `expect`), `_test`/`_skip` (from `./test`), `iterationData: Environment`,\n`globals: Environment`, `baseGlobals: Environment` (marked\n`// TODO: follows will be enabled after Insomnia supports them`), `_settings: Settings`,\n`requestTestResults: RequestTestResult[]`, `parentFolders: ParentFolders`.\n\nMethods:\n- `sendRequest(request: string | ScriptRequest, cb: (error?: string, response?: ScriptResponse) => void)` — delegates to `sendRequest()` from `./send-request`, passing the internal `_settings`.\n- `test = () => {}` — a no-op placeholder property; the *actual* behavior is installed by the\n  constructor's `Proxy` (see below). Declaring this field exists mainly so `test` shows up as an\n  own property.\n- `expect = (exp: boolean | number | string | object) => this._expect(exp)` — thin pass-through to chai's `expect`.\n- `get settings()` — **always returns `undefined`** (see Gotchas).\n- `toObject = () => {...}` — serializes the whole object graph to a plain object: `globals`,\n  `baseGlobals`, `environment`, `baseEnvironment`, `iterationData` (all via each `Environment`'s\n  `toObject()`), `variables` (via `variables.localVarsToObject()`), `request`, `settings` (calls\n  `this.settings`, i.e. always `undefined`), `clientCertificates`, `cookieJar` (via\n  `cookies.jar().toInsomniaCookieJar()`), `info` (via `info.toObject()`), `response` (via\n  `response.toObject()` or `undefined`), `requestTestResults`, `execution` (via\n  `execution.toObject()`), `parentFolders` (via `parentFolders.toObject()`).\n\nThe constructor returns `new Proxy(this, { get: ... })`: any property access other than `'test'`\npasses straight through via `Reflect.get`. Accessing `.test` instead returns a freshly-built\n`TestHandler` function that calls `this._test(msg, fn, this.pushRequestTestResult)`, with a\n`.skip` method that calls `this._skip(msg, fn, this.pushRequestTestResult)`. `pushRequestTestResult`\nappends each `RequestTestResult` to the private `requestTestResults` array (immutably, via spread).\n\n### `async function initInsomniaObject(rawObj: RequestContext, log: (...args: any[]) => void): Promise<InsomniaObject>`\n\nFactory used by the script runner to build an `InsomniaObject` from a `RequestContext` snapshot\n(see `interfaces.md`). Responsibilities, in order:\n- Maps `globals`/`baseGlobals` — if the same environment id is selected for both, `globals` and\n  `baseGlobals` become the *same* `Environment` instance; otherwise separate instances are created.\n- Maps `environment`/`baseEnvironment` the same way; if no sub-environment is selected (ids equal),\n  logs a warning via `log(...)` that mutations to `insomnia.environment` will apply to the base\n  environment.\n- Builds `iterationData` and `transientVariables`-backed local variables `Environment`s (defaulting\n  to empty named environments when absent).\n- Builds a `Vault` from `rawObj.vault` (or an empty object), gated by\n  `rawObj.settings?.enableVaultInScripts` (defaults `false`).\n- Builds `CookieObject` from `rawObj.cookieJar`.\n- Builds `RequestInfo` from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- Builds `ParentFolders` from `rawObj.parentFolders`, then a `Variables` instance that layers\n  base-global/global/environment/collection/iteration-data/folder-level/local variables.\n- Resolves client certificates: uses `checkIfUrlIncludesTag` (from `./utils`) and\n  `filterClientCertificates` (from `insomnia/src/network/certificate`) against `rawObj.request.url`.\n  If the URL contains template tags or no certificate matches, initializes an **empty** default\n  certificate and logs a warning via `getExistingConsole().warn(...)`; otherwise uses the first\n  matched certificate.\n- Builds the request URL (`toUrlObject`), proxy options (`transformToSdkProxyOptions`,\n  `resolveProtocolForProxy`), adds query params, builds the auth via `toPreRequestAuth`, and\n  constructs a `ScriptRequest`.\n- Constructs an `Execution` from `rawObj.execution`.\n- Reads the response body via `readBodyFromPath` and builds a `ScriptResponse` via\n  `toScriptResponse` if `rawObj.response` is present.\n- Returns `new InsomniaObject({...})` with everything above.\n\n## Script-facing surface\n`InsomniaObject` *is* what a script sees as the global `insomnia` object (and `$`, a Postman-compat\nalias set up as `const $ = insomnia;` by the script wrapper in\n`packages/insomnia/src/scripting/run-script.ts`). Nearly every `pm.*`/`insomnia.*` call in a script\nroutes through this object's properties/methods directly:\n- `insomnia.environment`, `insomnia.collectionVariables`, `insomnia.baseEnvironment`, `insomnia.variables`\n- `insomnia.request`, `insomnia.response`, `insomnia.cookies`, `insomnia.info`, `insomnia.execution`, `insomnia.vault`\n- `insomnia.test(name, fn)` / `insomnia.test.skip(name, fn)`\n- `insomnia.expect(value)`\n- `insomnia.sendRequest(request, callback)`\n\n`run-script.ts` requires the script's `insomnia` result to be `instanceof InsomniaObject` — if the\nuser script returns early or otherwise breaks that invariant, `runScript` throws\n`'insomnia object is invalid or script returns earlier than expected.'`. After the script runs,\n`InsomniaObject.toObject()` is used to merge mutations back into the persisted `RequestContext`.\n\n## Gotchas / notable behavior\n- **`collectionVariables` is not a copy** — `this.collectionVariables = this.baseEnvironment;` in\n  the constructor means `insomnia.collectionVariables` and `insomnia.baseEnvironment` are the exact\n  same `Environment` instance. Mutating one via a script mutates the other.\n- **`insomnia.settings` is always `undefined`** — the `get settings()` accessor unconditionally\n  `return;`s nothing, even though the real `Settings` object is held internally as `_settings` (used\n  only for `sendRequest`'s proxy/certificate resolution). `toObject().settings` is therefore always\n  `undefined` too, regardless of what `Settings` were passed in.\n- **`globals`/`baseGlobals` are `private` in name only** — `private` is TypeScript-only and erased at runtime, and the constructor's `Proxy` forwards them (via `Reflect.get`, no `set` trap) same as any other property, so scripts can read *and* write `insomnia.globals`/`insomnia.baseGlobals` despite them being absent from the TS-declared public surface. Accidental exposure, not a supported feature.\n- **The `test` proxy is easy to miss when reading the class** — the `test = () => {}` field looks\n  like the real implementation, but any actual call to `insomnia.test(...)` is intercepted by the\n  constructor's `Proxy` `get` trap before it ever reaches that field.\n- **Certificate fallback is silent unless you check logs** — if the request URL contains\n  `{{`/`}}`/`{%`/`%}` template tags, or no client certificate matches the host, an *empty* default\n  certificate is substituted and a warning is written via the script console rather than thrown as\n  an error.\n- **No-environment-selected warning** — selecting the base environment (rather than a\n  sub-environment) causes `initInsomniaObject` to log a warning that `insomnia.environment`\n  mutations will land on the base environment, because `environment` and `baseEnvironment` are the\n  same instance in that case.\n\n## Related\n- `interfaces.ts` — defines `RequestContext`, the input shape `initInsomniaObject` consumes.\n- `execution.ts` — `Execution`, held as `insomnia.execution`.\n- `request-info.ts` — `RequestInfo`, held as `insomnia.info`.\n- `environments.ts` — `Environment`, `Variables`, `Vault` classes used throughout.\n- `cookies.ts` — `CookieObject`, held as `insomnia.cookies`.\n- `folders.ts` — `Folder`, `ParentFolders`, used for folder-level variables.\n- `request.ts` — `Request`/`RequestOptions`/`toScriptRequestBody`, builds `insomnia.request`.\n- `response.ts` — `toScriptResponse`, `readBodyFromPath`, builds `insomnia.response`.\n- `send-request.ts` — implements `insomnia.sendRequest(...)`.\n- `test.ts` — `test`/`skip`/`TestHandler`, implements `insomnia.test(...)`.\n- `auth.ts` — `toPreRequestAuth`, used when building the request's auth config.\n- `proxy-configs.ts` — `transformToSdkProxyOptions`, used for request proxy settings.\n- `urls.ts` — `toUrlObject`, `resolveProtocolForProxy`.\n- `utils.ts` — `checkIfUrlIncludesTag`, used for certificate-fallback detection.\n- `console.ts` — `getExistingConsole`, used to log warnings.\n- `packages/insomnia/src/scripting/run-script.ts` — binds this object as the sandbox global `insomnia` and calls `toObject()` after the script runs.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/interfaces.md (Tokens: ~967 | Category: anthropic-skill) -->\n# Shared interfaces (`RequestContext`, `IEnvironment`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interfaces.ts`\n\n## Purpose\nPure type-only file (no runtime code) defining the shared shapes used to pass state into and out\nof the scripting environment. `RequestContext` is the full serialized state snapshot that the host\napp (`packages/insomnia`) builds before running a script and that `initInsomniaObject` (in\n`insomnia.ts`) turns into a live `InsomniaObject`.\n\n## Public API\n\n### `interface IEnvironment`\n```ts\ninterface IEnvironment {\n  id: string;\n  name: string;\n  data: object;\n}\n```\nMinimal serialized shape for any environment-like blob (globals, base globals, environment, base\nenvironment, vault). Marked `/** @ignore */` (excluded from generated public docs).\n\n### `interface RequestContext`\n```ts\ninterface RequestContext {\n  request: Request;\n  timelinePath: string;\n  environment: IEnvironment;\n  baseEnvironment: IEnvironment;\n  vault?: IEnvironment;\n  collectionVariables?: object;\n  globals?: IEnvironment;\n  baseGlobals?: IEnvironment;\n  iterationData?: Omit<IEnvironment, 'id'>;\n  timeout: number;\n  settings: Settings;\n  clientCertificates: ClientCertificate[];\n  cookieJar: CookieJar;\n  response?: any;\n  requestTestResults?: RequestTestResult[];\n  requestInfo: RequestInfoOption;\n  execution: ExecutionOption;\n  logs: string[];\n  transientVariables?: Omit<IEnvironment, 'id'>;\n  parentFolders: { id: string; name: string; environment: Record<string, any> }[];\n}\n```\nFull input/output snapshot for one script execution. Fields of note:\n- `request: Request` — the raw request model (from `insomnia-data`), not the script-facing `ScriptRequest` wrapper in `request.ts`.\n- `globals`/`baseGlobals` — optional; per the inline comment, \"activated only when selected\".\n- `iterationData`/`transientVariables` — typed as `Omit<IEnvironment, 'id'>` (no `id` field needed for these).\n- `response?: any` — deliberately untyped; comment notes \"Callback types defined elsewhere to avoid circular imports\".\n- `requestInfo: RequestInfoOption` and `execution: ExecutionOption` — imported from `./request-info` and `./execution` respectively; these are the plain-object option shapes consumed by the `RequestInfo`/`Execution` constructors (see `request-info.md`/`execution.md`).\n- `parentFolders` — array of plain folder descriptors (`id`, `name`, `environment` data), consumed by `ParentFolders`/`Folder` in `folders.ts`.\n\nBoth interfaces are marked `/** @ignore */`, meaning they're internal/plumbing types not meant to\nappear in the generated public SDK reference.\n\n## Script-facing surface\nNone directly. A user's pre-request/after-response/test script never sees a `RequestContext` or\n`IEnvironment` value — these are internal transport types used by the host application\n(`packages/insomnia/src/scripting/run-script.ts`) to hand state into `initInsomniaObject` and to\nreceive mutated state back out via `InsomniaObject.toObject()`.\n\n## Gotchas / notable behavior\n- `globals`/`baseGlobals`/`vault` are all optional — code reading them elsewhere (e.g.\n  `initInsomniaObject`) must handle `undefined` explicitly (it does, via `?.` and fallbacks).\n- `response` being typed `any` means no compile-time safety on the response shape passed into the\n  scripting environment; the real shape is validated only implicitly by how `toScriptResponse` (in\n  `response.ts`) consumes it.\n- This file has no runtime exports (interfaces only) — importing it has zero side effects.\n\n## Related\n- `insomnia.ts` — `initInsomniaObject(rawObj: RequestContext, log)` is the sole consumer that turns this shape into a live `InsomniaObject`.\n- `execution.ts` — supplies `ExecutionOption`, embedded as `RequestContext.execution`.\n- `request-info.ts` — supplies `RequestInfoOption`, embedded as `RequestContext.requestInfo`.\n- `environments.ts` — `Environment`/`Vault` classes are constructed from `IEnvironment`-shaped data.\n- `folders.ts` — `Folder`/`ParentFolders` are constructed from `RequestContext.parentFolders`.\n- `packages/insomnia/src/scripting/run-script.ts` — builds and consumes `RequestContext` around each script run.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/interpolator.md (Tokens: ~1130 | Category: anthropic-skill) -->\n# Interpolator (template-tag rendering)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/interpolator.ts`\n\n## Purpose\nInternal helper providing `{{variable}}`-style template rendering (plus `{{$fakerFn}}` dynamic\nvalue generation) on top of LiquidJS. It backs every \"replace variables in this string\" operation\nexposed by the environment/property objects (e.g. `insomnia.environment.replaceIn(...)`).\n\n## Public API\n\n### `class Interpolator` (not exported — module-private)\n```ts\nconstructor()\n```\nBuilds a `Liquid` engine configured with:\n- `outputDelimiterLeft: '{{'`, `outputDelimiterRight: '}}'` — variable interpolation delimiters.\n- `tagDelimiterLeft: '{%'`, `tagDelimiterRight: '%}'` — Liquid tag delimiters (e.g. `{% if %}`).\n- `strictVariables: true` — referencing an undefined variable causes a render error rather than silently rendering empty.\n- `jsTruthy: true` — use JS truthiness semantics instead of Liquid's default truthy rules.\n- `ownPropertyOnly: false` — allows resolving inherited/prototype properties on the context object, not just own properties.\n\nMethods:\n- `render = async (template: string, context: object): Promise<string>` — runs `renderWithFaker(template)` first (to substitute any `{{$fakerFn}}` tags with generated values), then parses/renders the result through the Liquid engine against `context`. Comment: `// TODO: support plugins`.\n- `renderWithFaker = (template: string) => string` — a pre-pass over the template string, done via\n  manual string splitting (not Liquid) rather than regex/AST parsing:\n  1. Splits `template` on `'}}'`.\n  2. For each segment, finds the last `'{{'` before the end of the segment.\n  3. If the found tag name starts with `'$'`, strips the `$` prefix, looks it up in\n     `fakerFunctions` (from `insomnia/src/common/templating/faker-functions`), calls it, and\n     splices the generated value in place of the tag.\n  4. If the tag name does not start with `$`, the segment (and its `}}`) is left untouched for the\n     real Liquid engine to interpolate as a normal variable.\n  5. Throws `Error('replaceIn: no faker function is found: ${funcName}')` if the `$`-prefixed name\n     isn't a recognized faker function.\n\n### `function getInterpolator(): Interpolator`\nReturns the single module-level `Interpolator` instance (`const interpolator = new Interpolator();`), constructed once at module load. This is the only exported symbol.\n\n## Script-facing surface\nNo direct surface — scripts never call `getInterpolator()` themselves. It is reached indirectly\nthrough higher-level methods on other objects, all of which delegate to\n`getInterpolator().render(...)`:\n- `insomnia.environment.replaceIn(template)` / same method on `baseEnvironment`, `collectionVariables`, `variables`, etc. (defined in `environments.ts`) — e.g. `insomnia.environment.replaceIn(\"My id is {{$randomUUID}}\")` or `insomnia.environment.replaceIn(\"Visiting URL: {{urlValueFromEnvironment}}\")`.\n- `Property.replaceSubstitutions(content, ...variables)` and `Property.replaceSubstitutionsIn(obj, ...variables)` (static methods in `properties.ts`) — used internally wherever a `Property`-derived object needs variable substitution against multiple variable-scope objects merged together.\n\n## Gotchas / notable behavior\n- `renderWithFaker` is a hand-rolled string scan, not a real parser — it looks for the *last*\n  `'{{'` before each `'}}'` boundary in each split segment. Malformed or nested template syntax\n  could confuse this pass before Liquid ever sees the template.\n- `strictVariables: true` means referencing a variable name that isn't present in `context` will\n  cause the Liquid render to throw/reject, not silently produce an empty string — a common source\n  of \"my `{{someVar}}` template failed\" script errors when the variable isn't actually defined in\n  any active scope.\n- `$`-prefixed faker tags are resolved by this module's own pre-pass, *before* Liquid — an unknown\n  `$xyz` faker function name throws synchronously from `renderWithFaker` with the message\n  `replaceIn: no faker function is found: xyz`.\n- `getInterpolator()` always returns the same singleton `Interpolator` instance across the whole\n  process — there is no way to get a differently-configured interpolator or to reset its Liquid\n  engine mid-run.\n- The `Interpolator` class itself is not exported; only `getInterpolator()` is, so consumers cannot\n  construct their own instance or subclass it.\n\n## Related\n- `environments.ts` — `Environment`/`Variables`/`Vault`'s `replaceIn(...)` methods call `getInterpolator().render(...)`.\n- `properties.ts` — `Property.replaceSubstitutions`/`replaceSubstitutionsIn` call `getInterpolator().render(...)`.\n- `insomnia/src/common/templating/faker-functions` (outside this package) — supplies the `fakerFunctions` map used for `{{$fakerFn}}` tags.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/properties.md (Tokens: ~3426 | Category: anthropic-skill) -->\n# PropertyBase / Property / PropertyList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/properties.ts`\n\n## Purpose\nBase classes that most other SDK objects extend, mirroring Postman's `postman-collection` `Property`/`PropertyList` model: `PropertyBase` provides parent-chain traversal and JSON serialization, `Property` adds `id`/`name`/`disabled` plus static template-substitution helpers, and `PropertyList<T>` is a generic ordered/keyed collection with Postman-compatible methods (`add`, `each`, `filter`, `find`, `one`, `upsert`, `toObject`, etc.). Concrete subclasses (`Variable`/`VariableList`, `Header`/`HeaderList`, `Cookie`/`CookieList`, `ProxyConfig`/`ProxyConfigList`, etc.) build on top of these.\n\n## Public API\n\n### `unsupportedError(featureName: string, alternative?: string): Error`\nBuilds an `Error` with message `` `${featureName} is not supported yet` `` (optionally appending `, please use ${alternative} instead temporarily.` when `alternative` is given). Used throughout the SDK (e.g. `Variable.types()`, `PropertyList.eachParent`) to mark stubbed-out Postman-compat methods.\n\n### `class PropertyBase`\n```ts\nconstructor(description?: string)\n```\n- `public _kind = 'PropertyBase'` — used as a lightweight runtime type tag (checked via `'_kind' in obj` elsewhere, e.g. `VariableList.isVariableList`).\n- `protected _parent: PropertyBase | undefined` — set only via subclasses/external assignment; no setter method is provided on the base class itself.\n- `protected description?: string`.\n- `static propertyIsMeta(_value: any, key: string): boolean` — `key && key.startsWith('_')` (keys starting with `_` are treated as \"meta\").\n- `static propertyUnprefixMeta(_value: any, key: string): string` — strips a leading `_` from `key` if present.\n- `meta(): {}` — always returns an empty object (not implemented beyond the stub).\n- `parent(): PropertyBase | undefined` — returns `this._parent`.\n- `forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean): PropertyBase[] | undefined` — BFS-style walk up the parent chain (via `parent()`), cloning each ancestor with the `clone` package before passing it to `iterator`; stops as soon as `iterator` returns falsy, and always returns the array of cloned ancestors visited so far (or `undefined` if there is no parent at all). `_options` is accepted but never read.\n- `findInParents(property: string, customizer?: (ancestor: PropertyBase) => boolean): PropertyBase | undefined` — walks the (cloned) parent chain looking for the first ancestor whose `meta()` keys include `property`; if `customizer` is given, keeps walking until `customizer(ancestor)` returns truthy, otherwise returns the first ancestor with that meta key. Returns `undefined` if no parent, or none matches.\n- `toJSON(): Record<string, any>` — returns `Object.entries(this)` filtered to drop function-valued entries, `undefined` values, and the `_kind` key.\n- `toObject(): Record<string, any>` — returns `this.toJSON()`.\n- `toString(): string` — `JSON.stringify(this.toJSON())`.\n\n### `class Property extends PropertyBase`\n```ts\nconstructor(id?: string, name?: string, disabled?: boolean, info?: { id?: string; name?: string })\n```\n`info.id`/`info.name` take priority over the positional `id`/`name` params if both are given; `this._kind` is set to `'Property'`; `disabled` defaults to `false`.\n- `id: string`, `name?: string`, `disabled?: boolean`.\n- `static _index = 'id'` — the field name `PropertyList` uses to key/index items of this type; overridden by subclasses (e.g. `Variable._index = 'key'`).\n- `static async replaceSubstitutions(content: string, ...variables: object[]): Promise<string>` — throws `TypeError` if `variables` isn't an array or `content` isn't a string. Merges `variables` into a single context object: the array is `.reverse()`d first, then folded left-to-right with `{...context, ...variable}`, so the reversed (originally-last) entries are applied first and get overwritten by earlier ones — net effect: **the leftmost/first argument passed to `replaceSubstitutions` wins** on key collisions, matching the inline comment (\"the searching priority of rendering is from left to right\"). Renders `content` against that merged context via `getInterpolator().render(...)`.\n- `static async replaceSubstitutionsIn(obj: object, ...variables: object[]): Promise<object>` — same signature/merge semantics as `replaceSubstitutions`, but stringifies `obj` with `JSON.stringify`, renders it, then `JSON.parse`s the result. Throws `TypeError` for bad args up front; wraps any other error (e.g. a `JSON.parse` failure on non-JSON-safe rendered output) in a new `Error('replaceSubstitutionsIn: ' + e.toString())`.\n- `describe(content: string, typeName: string): void` — sets `this._kind = typeName` and `this.description = content`.\n\n### `class PropertyList<T extends Property>`\n```ts\nconstructor(\n  protected typeClass: { _index?: string },\n  protected parent: Property | PropertyList<any> | undefined,\n  populate: T[],\n)\n```\n- `protected _kind = 'PropertyList'`, `protected list: T[]`.\n- `static isPropertyList(obj: object): boolean` — `'_kind' in obj && obj._kind === 'PropertyList'`.\n- `add(item: T): void` — pushes to the end of `list`.\n- `all(): Record<string, any>[]` — `list.map(pp => pp.toJSON())`.\n- `append(item: T): void` — alias for `add`.\n- `assimilate(source: T[] | PropertyList<T>, prune?: boolean): void` — if `prune`, calls `clear()` first; then pushes all of `source`'s items (`source.list` if it's a `PropertyList`, else the array itself) onto `list`. Per an inline comment, \"it doesn't update values from a source list\" (i.e. this appends, it does not merge/replace by key).\n- `clear(): void` — `list = []`.\n- `count(): number` — `list.length`.\n- `each(iterator: (item: T) => void, context: object): void` — calls `list.forEach(iterator)`; `context` is stashed as `iterator.context` (assigned onto the function) but the iterator itself is not invoked with that context bound — purely a Postman-compat artifact.\n- `eachParent(_iterator, _context?): never` — **always throws** `unsupportedError('eachParent')`; not implemented (\"properties are not organized as hierarchy\" per the TODO comment).\n- `filter(rule: (item: T) => boolean, context: object): T[]` — same `context`-stash pattern as `each`; returns `list.filter(rule)`.\n- `find(rule: (item: T) => boolean, context?: object): T | undefined` — same pattern; `list.find(rule)`.\n- `get(key: string): T | undefined` — alias for `one(key)`.\n- `has(item: T, _value?: any): boolean` — `indexOf(item) >= 0`; `_value` is accepted but unused (\"its usage is unknown\" per comment).\n- `idx(index: number): T | undefined` — returns `list[index]` if `index <= list.length - 1`, else `undefined`.\n- `indexOf(item: string | T): number` — looks up the index field via `typeClass._index || 'id'`; if `item` is a string, matches `record[indexFieldName] === item`; otherwise matches `record[indexFieldName] === (item as Record<string, any>)[indexFieldName]`. Returns `-1` if not found.\n- `insert(item: T, before?: number): void` — splices `item` in before index `before` if valid (`before != null && before >= 0 && before <= list.length - 1`), else falls back to `append(item)`.\n- `insertAfter(item: T, after?: number): void` — splices `item` in right after index `after` under the same bounds check, else falls back to `append(item)`.\n- `map(iterator: (item: T) => any, context: object): any[]` — same `context`-stash pattern; `list.map(iterator)`.\n- `one(id: string): T | undefined` — scans `list` **backwards** (`for (let i = list.length - 1; i >= 0; i--)`) for the first record whose index field equals `id`; if the matched item has a callable `valueOf`, returns `item.valueOf()` instead of the item itself; else returns the item as-is. Returns `undefined` if not found.\n- `populate(items: T[]): void` — `list = [...list, ...items]`.\n- `prepend(item: T): void` — `list = [item, ...list]`.\n- `reduce(iterator: (acc: any, item: T) => any, accumulator: any, context: object): any` — same `context`-stash pattern; `list.reduce(iterator, accumulator)`.\n- `remove(predicate: T | ((item: T) => boolean), context: object): void` — if `predicate` is a function, keeps everything that does *not* match it (via `filter` with the negated predicate); if `predicate` is a value, keeps everything not `deep-equal` to it.\n- `repopulate(items: T[]): void` — `clear()` then `populate(items)`.\n- `toObject(_excludeDisabled?, _caseSensitive?, _multiValue?, _sanitizeKeys?): Record<string, any> | Record<string, any>[]` — base implementation ignores all four arguments and returns `list.map(elem => elem.toJSON())` (an array, not a keyed object) — it's the fallback for lists whose items have no natural key (e.g. `UrlMatchPatternList`, or a raw `PropertyList` not used through a subclass). Subclasses backed by keyed items (`CookieList`, `HeaderList`, `VariableList`, `ProxyConfigList`) override this with a real key→value map that supports `excludeDisabled`/`multiValue`; `caseSensitive`/`sanitizeKeys` remain unsupported even in those overrides.\n- `toString(): string` — `` `[${list.map(item => item.toString()).join('; ')}]` ``.\n- `upsert(item: T): boolean` — returns `false` immediately if `item == null`. If `indexOf(item)` finds an existing entry, **splices it out and reinserts the new item at the same position** (returns `false`, meaning \"updated, not inserted\"); otherwise calls `add(item)` and returns `true` (\"inserted new\"). Note: the \"splice out, then splice again\" implementation calls `this.list.splice(...)` twice on the *same* array reference inside one expression (`[...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]`) — the first `splice` call mutates `this.list` in place before the second one runs against the now-shortened array.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` itself — these are base classes. Scripts interact with them only through concrete subclasses' public APIs, e.g.:\n- `pm.variables`/collections of variables → `VariableList` (extends `PropertyList<Variable>`), `Variable` (extends `Property`) — see `variables.md`.\n- `pm.request.headers`, `pm.response.headers` → `HeaderList`/`Header`.\n- `pm.request.url.query` → `UrlMatchPatternList`/similar list types.\n- Any script calling `.get()`, `.one()`, `.each()`, `.filter()`, `.upsert()`, `.toObject()`, etc. on a collection-like SDK object is going through `PropertyList`'s implementation documented above.\n- `Property.replaceSubstitutions`/`replaceSubstitutionsIn` back the SDK's `{{variable}}` template rendering wherever it's invoked as a static helper (distinct from, but functionally similar to, `Environment.replaceIn`/`Variables.replaceIn` in `environments.ts`).\n\n- **`replaceSubstitutions`/`replaceSubstitutionsIn` mutate their own `variables` rest-array via `.reverse()`** before merging — this is an implementation detail (not observable to callers since `variables` is a fresh rest-parameter array each call), but worth knowing if you're stepping through this code: the reversal is what makes the final left-to-right merge produce \"leftmost argument wins\" precedence.\n- **`eachParent` always throws** `unsupportedError('eachParent')` — calling it from a script will break, regardless of arguments.\n- **`each`/`filter`/`find`/`map`/`reduce`'s `context` parameter does nothing functionally** — it's stashed as a property on the callback function object (`it.context = context`) purely for Postman API-shape compatibility, but the callback is invoked with `Array.prototype`'s normal (unbound) semantics, so relying on `context` for `this`-binding inside the iterator will not work as it might in Postman.\n- **`one(id)` scans backwards and unwraps `valueOf()`** — if two items share the same index-field value, `one()` returns the *last*-added one (matching most recently `add`/`upsert`ed semantics), and if that item defines a custom `valueOf`, the returned object is `item.valueOf()`, not the raw list item.\n- **`upsert(item)` replaces the item at the same index** via `[...this.list.slice(0, itemIdx), item, ...this.list.slice(itemIdx + 1)]`, leaving every other item in place.\n- **`toObject()`'s base implementation returns an array, not an object**, unlike its keyed subclass overrides (`VariableList.toObject()` etc., which return `Record<string, any>`) — code that assumes every `PropertyList.toObject()` yields a key/value map will break for list types that don't override it (e.g. `UrlMatchPatternList`).\n- `PropertyBase.forEachParent`/`findInParents` both `clone()` each ancestor before handing it to the caller/comparing — mutations the iterator/customizer performs on the passed-in ancestor object do not affect the real parent chain.\n- `Property`'s constructor lets `info.id`/`info.name` silently override the positional `id`/`name` arguments — passing both can be surprising if not intentional.\n\n## Related\n- `variables.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>` (see `variables.md`).\n- `environments.ts` — a separate, non-`Property`-based variable model (`Environment`/`Variables`) used for `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables`; don't conflate the two when tracing a variable bug.\n- `interpolator.ts` — `getInterpolator().render(...)` is what `Property.replaceSubstitutions`/`replaceSubstitutionsIn` delegate to for `{{...}}` template rendering.\n- `headers.ts`, `cookies.ts`, `proxy-configs.ts`, `urls.ts` — other concrete `Property`/`PropertyList` subclasses that override `toObject()` with their own keyed semantics.\n- `collection.ts` / `index.ts` — re-export `Property`, `PropertyBase`, `PropertyList` as part of the SDK's public surface.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/proxy-configs.md (Tokens: ~2845 | Category: anthropic-skill) -->\n# ProxyConfig, ProxyConfigList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts`\n\n## Purpose\nModels a single proxy configuration (`pm.request.proxy`) and a keyed list of them (`ProxyConfigList`, exported for scripts to build their own lists, e.g. via `require('insomnia-collection')`). Also provides `transformToSdkProxyOptions`, the function that converts Insomnia's native proxy settings (`httpProxy`/`httpsProxy`/`proxyEnabled`/`noProxy` strings from Settings) into the SDK's `ProxyConfigOptions` shape used to seed `pm.request.proxy`.\n\n## Public API\n\n### Types\n- `interface ProxyConfigOptions { match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string }` — constructor input for `ProxyConfig`. `match` is a URL match pattern string (see `urls.ts`) initializing a `UrlMatchPattern` internally, e.g. `'http+https://example.com/*'`. `bypass` and `protocol` are called out in comments as \"for compatibility with Insomnia\".\n\n### `class ProxyConfig extends Property`\n- `override _kind = 'ProxyConfig'`\n- `host: string`\n- `match: string` — the raw match-pattern string (not a `UrlMatchPattern` instance on the instance property, though a `UrlMatchPattern` is constructed on the fly inside `getProtocols()`/`test()`).\n- `port?: number`\n- `tunnel: boolean`\n- `authenticate: boolean`\n- `username: string`\n- `password: string`\n- `bypass: string[]` — list of hosts/URLs to bypass the proxy for; comment: \"for compatibility with Insomnia's bypass list\".\n- `protocol: string` — e.g. `\"http\"`/`\"https\"`.\n- `static authenticate = false`, `static bypass: UrlMatchPatternList<UrlMatchPattern> = new UrlMatchPatternList(undefined, [])`, `static host = ''`, `static match = ''`, `static password = ''`, `static port?: number = undefined`, `static tunnel = false` (comment: \"unsupported\"), `static username = ''`, `static protocol = 'https:'` — all marked `@ignore` in JSDoc with the comment \"following properties are hidden as they are not used while must be exposed\". These are class-level defaults, distinct from the instance properties of the same name.\n- `constructor(def: { id?: string; name?: string; match: string; host: string; port?: number; tunnel: boolean; disabled?: boolean; authenticate: boolean; username: string; password: string; bypass?: string[]; protocol: string })` — sets `id`/`name` from `def` or defaults to `''`, `disabled` defaults to `false`, `bypass` defaults to `[]` if omitted. All other fields copied straight through (no defaulting).\n- `static override _index = 'key'` — Note: `ProxyConfig` has no instance field literally named `key`; `PropertyList.indexOf()`/`.one()` would look for a `key` property on the record, which doesn't exist on `ProxyConfig` — see Gotchas. (`ProxyConfigList.toObject()` keys its map by `match`, not by this `_index`.)\n- `static isProxyConfig(obj: object): boolean` — checks `obj._kind === 'ProxyConfig'`.\n- `getProtocols(): string[]` — builds a `UrlMatchPattern(this.match)` and returns `.getProtocols()` (parses the `'+'`-delimited protocol prefix of the match string, e.g. `'http+https://...'` → `['http', 'https']`).\n- `getProxyUrl(): string` — builds the full proxy URL: `` `${protocol}//${username}:${password}@${host}${port}` `` if `authenticate` is true, otherwise `` `${protocol}//${host}${port}` `` (port segment omitted entirely if `port === undefined`).\n- `test(url?: string): boolean` — returns `false` immediately if `url` is falsy (comment: \"TODO: it is confusing in which case url arg is optional\"). Returns `false` if `url` is literally in `this.bypass` (exact string match, not pattern matching). Otherwise delegates to `new UrlMatchPattern(this.match).test(url)`.\n- `update(options: Omit<ProxyConfigOptions, 'bypass' | 'protocol'>): void` — updates `host`, `match`, `port`, `tunnel`, `authenticate`, `username`, `password`. Does **not** allow updating `bypass` or `protocol` (both omitted from the type, and untouched by the method body).\n- `updateProtocols(_protocols: string[]): never` — always throws `Error('updateProtocols is not supported in Insomnia')`. Comment: \"In Insomnia there is no whitelist while there is a blacklist.\"\n\n### `class ProxyConfigList<T extends ProxyConfig> extends PropertyList<T>`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])` — constructs the underlying `PropertyList` with `typeClass = ProxyConfig`.\n- `static isProxyConfigList(obj: any): boolean` — checks `obj._kind === 'ProxyConfigList'`.\n- `resolve(url?: Url): object | null` — returns `null` if `url` is falsy. Otherwise stringifies `url`, filters the list to configs whose `.test(urlStr)` is true, maps matches to `.toJSON()`, and returns the **first** match's JSON (or `null` if none match). Comment: \"It only returns the first one if multiple matches are found.\"\n- `override toObject(excludeDisabled?, _caseSensitive?, multiValue?, _sanitizeKeys?): Record<string, any>` — builds a plain object keyed by each config's `match` string, using `Object.create(null)` (no prototype). `excludeDisabled` skips `disabled` configs. `multiValue` collects same-`match` configs into an array; otherwise duplicate `match` values collapse to the last one.\n\n### Module-level function\n- `transformToSdkProxyOptions(protocol: string, httpProxy: string, httpsProxy: string, proxyEnabled: boolean, noProxy: string): ProxyConfigOptions` — computes `proxyHost` (the string actually parsed into `host`/`port`/etc. below) as `httpsProxy` or `httpProxy` based on whether `protocol === 'https:'`. But `enabledProxy` is computed separately, from `proxyEnabled && (httpsProxy || httpProxy || '').trim() !== ''` — **not** from `proxyHost` — so it's `true` whenever *either* proxy string is non-empty, regardless of which one the current `protocol` selected (see Gotchas). Splits `noProxy` on commas (trimmed) into the `bypass` list. Always returns `match: '<all_urls>'` (`UrlMatchPattern.MATCH_ALL_URLS`). If the proxy is enabled and a host string is present, parses it with the built-in `URL` class (prefixing `${protocol}//` if the string has no `://`), extracting `port` (only set if non-empty, then `Number.parseInt(..., 10)`), `protocol`, `host` (hostname), `username`, `password`; sets `authenticate = true` if either `username` or `password` came through; logs `` `Using proxy: ${sanitizedProxy}` `` via `getExistingConsole().warn`. Throws `Error('Failed to parse proxy (${protocol}//${proxyHost}): ${e.message}')` if URL parsing fails **and the proxy is enabled** (parsing is skipped entirely, no throw, when the proxy is disabled).\n\n## Script-facing surface\n- `pm.request.proxy` is a single `ProxyConfig` instance (per `request.ts`: `const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined;`), seeded from Insomnia's Settings-level proxy configuration via `transformToSdkProxyOptions` (called in `insomnia.ts`). It is `undefined` if no `proxy` option was supplied when constructing the request.\n- Scripts read/write `pm.request.proxy.host`, `.port`, `.username`, `.password`, `.authenticate`, `.tunnel`, `.bypass`, `.protocol` directly, call `.getProxyUrl()` to see the resolved proxy URL, `.test(url)` to check whether a given URL should go through this proxy, and `.update({...})` to change host/match/port/tunnel/authenticate/username/password.\n- `ProxyConfigList` and `ProxyConfig` are also exported from the `insomnia-collection` module (via `collection.ts`) so scripts can `require('insomnia-collection')` and construct their own lists directly, e.g.: `new ProxyConfigList(undefined, [{match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true}, ...])` (per the inline example comment in the source). There is no evidence in the SDK object graph that `pm.request` itself ever holds a `ProxyConfigList` — only a single `ProxyConfig`.\n\n## Gotchas / notable behavior\n- **`_index = 'key'` but `ProxyConfig` has no `key` property.** Base `PropertyList.one()`/`.indexOf()`/`.upsert()` look up items by `typeClass._index` (here `'key'`), but `ProxyConfig` instances never define a `key` field — so `ProxyConfigList.one('...')`/`.get('...')`/`.indexOf(...)`/`.upsert(...)` would compare against `undefined` for every item and effectively never index correctly by identity (though `.toObject()` and `.resolve()` work fine since they key/filter explicitly by `match` instead of relying on `_index`).\n- **`test(url)` returns `false` silently if `url` is omitted** — the source itself flags this as confusing (\"TODO: it is confusing in which case url arg is optional\").\n- **`bypass` matching is exact string equality**, not pattern-based — a URL must be an exact match to an entry in `bypass` to be excluded; nothing is normalized (no protocol/trailing-slash handling).\n- **`update()` cannot change `bypass` or `protocol`** — these are deliberately excluded from `ProxyConfigOptions` in the `update()` signature's `Omit<...>`.\n- **`updateProtocols()` always throws** — it exists on the class but is not a supported operation in Insomnia.\n- **`getProxyUrl()` omits the port segment entirely when `port === undefined`** (not just falsy — `port: 0` would still render `:0`).\n- **`transformToSdkProxyOptions` swallows parse errors when the proxy is disabled**: a malformed proxy host string does not throw as long as `proxyEnabled` is `false`; it only throws when the proxy would actually be used.\n- **`enabled`/`disabled` can disagree with the protocol-selected proxy string.** `enabledProxy` is derived from `httpsProxy || httpProxy` (https checked first, regardless of `protocol`), while the proxy actually parsed into `host`/`port`/etc. is `proxyHost` (`httpsProxy` only if `protocol === 'https:'`, else `httpProxy`). If `protocol` is `'http:'` with `httpProxy` empty but `httpsProxy` set (or vice versa), `enabledProxy` comes out `true` from the other, unused string, `proxy.disabled` is set to `false`, yet `proxyHost === ''` skips the parsing block entirely — so `pm.request.proxy` ends up `disabled: false` with an empty `host`.\n- **The class-level `static` fields shadow real instance field names** (`static host = ''`, `static match = ''`, etc., separate from `this.host`, `this.match` on instances) — marked `@ignore` and described only as \"hidden as they are not used while must be exposed\"; do not confuse `ProxyConfig.host` (static, always `''`) with `someProxyConfigInstance.host`.\n\n## Related\n- `properties.ts` — `Property` (base class for `ProxyConfig`) and `PropertyList` (base class for `ProxyConfigList`), including the shared `toObject()`/`_index` lookup semantics referenced above.\n- `urls.ts` — `UrlMatchPattern`/`UrlMatchPatternList` (used internally by `getProtocols()`/`test()`) and `Url` (the type accepted by `ProxyConfigList.resolve()`).\n- `console.ts` — `getExistingConsole()`, used by `transformToSdkProxyOptions` to log which proxy is being used.\n- `request.ts` — constructs `pm.request.proxy` from `ProxyConfigOptions` and serializes it back out when building the final request.\n- `insomnia.ts` — calls `transformToSdkProxyOptions` using Insomnia Settings (`proxyEnabled`, http/https proxy strings, `noProxy`) to seed the proxy config before a script runs.\n- `collection.ts` — re-exports `ProxyConfig`/`ProxyConfigList` for direct use via `require('insomnia-collection')`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/README.md (Tokens: ~847 | Category: anthropic-skill) -->\n# `insomnia-scripting-environment/src/objects` Reference\n\nOne doc per source file in `packages/insomnia-scripting-environment/src/objects/`. Each covers: public API (real signatures), how a script actually reaches it (`pm.*`/`insomnia.*`), and gotchas found while reading the code.\n\n## Core / context\n- [insomnia.md](insomnia.md) — `InsomniaObject`, the top-level `insomnia`/`pm` instance\n- [interfaces.md](interfaces.md) — `RequestContext`, `IEnvironment`\n- [execution.md](execution.md) — `Execution` (`pm.execution`)\n- [request-info.md](request-info.md) — `RequestInfo` (`pm.info`)\n- [async-objects.md](async-objects.md) — `ProxiedPromise` and async-task tracking\n- [interpolator.md](interpolator.md) — `{{ }}` template interpolation\n- [utils.md](utils.md) — misc helpers\n\n## Request / response\n- [request.md](request.md) — `Request`, `RequestBody` (`pm.request`)\n- [response.md](response.md) — `Response` (`pm.response`)\n- [headers.md](headers.md) — `Header`, `HeaderList`\n- [urls.md](urls.md) — `Url`, `QueryParam`, `UrlMatchPattern`\n\n## Environment / variables / collection\n- [environments.md](environments.md) — `Environment`, `Variables` (`pm.environment` / `pm.globals`, variable resolution precedence)\n- [variables.md](variables.md) — `Variable`, `VariableList`\n- [collection.md](collection.md) — re-export barrel only; no `Collection` class or logic — see `environments.md`/`insomnia.md` instead\n- [folders.md](folders.md) — `Folder`, `ParentFolders`\n- [properties.md](properties.md) — `PropertyBase`, `PropertyList` base classes\n\n## Auth / cookies / certs / proxy\n- [auth.md](auth.md) — `RequestAuth` (`pm.request.auth`)\n- [cookies.md](cookies.md) — `Cookie`, `CookieJar`, `CookieList` (`pm.cookies`)\n- [certificates.md](certificates.md) — `Certificate` (`pm.request.certificate`)\n- [proxy-configs.md](proxy-configs.md) — `ProxyConfig`, `ProxyConfigList`\n\n## Test / console / send-request\n- [test.md](test.md) — `pm.test()`/`test()` handler registration\n- [console.md](console.md) — `Console` (script `console.log` capture)\n- [send-request.md](send-request.md) — `pm.sendRequest()`\n\n## Flagged during writing (worth a closer look if you hit related symptoms)\n- `insomnia.settings` always returns `undefined` in scripts (getter unconditionally returns nothing) — see `insomnia.md`.\n- `pm.collectionVariables` and `pm.baseEnvironment` are the same object reference, not a copy — see `insomnia.md`/`environments.md`.\n- Writes to `insomnia.info.*` (`RequestInfo`) are never read back by the script-run merge logic — silently discarded — see `request-info.md`.\n- `pm.sendRequest(url, callback)` resolves (does not reject) on network/parse errors when a callback is passed — only the promise-only form rejects — see `send-request.md`.\n- `Response.dataURI()` has a typo bug: emits `baseg4` instead of `base64` — see `response.md`.\n- `Certificate.update()` doesn't update `disabled`, unlike the constructor — see `certificates.md`.\n- `ProxyConfig` has no `key` property despite `_index = 'key'`, breaking base-class `one()`/`indexOf()`/`upsert()` lookups — see `proxy-configs.md`.\n- `Vault` is read-only from scripts even when `enableVaultInScripts` is set — `set`/`unset`/`clear` always throw — see `environments.md`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/request-info.md (Tokens: ~955 | Category: anthropic-skill) -->\n# RequestInfo (`insomnia.info` / `pm.info`)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request-info.ts`\n\n## Purpose\nModels `pm.info`: metadata about which script phase is executing and, for the collection runner,\nwhich iteration is in progress. Read-oriented — scripts typically branch on `insomnia.info.eventName`\nor log `insomnia.info.iteration`/`iterationCount`.\n\n## Public API\n\n### `type EventName = 'prerequest' | 'test'`\nThe name of the event that triggered the script: `'prerequest'` (before a request is sent) or\n`'test'` (during the testing/after-response phase).\n\n### `interface RequestInfoOption`\n```ts\ninterface RequestInfoOption {\n  eventName?: EventName;\n  iteration?: number;\n  iterationCount?: number;\n  requestName?: string;\n  requestId?: string;\n}\n```\nPlain-object shape used to construct a `RequestInfo` (and stored on `RequestContext.requestInfo`, see `interfaces.md`).\n\n### `class RequestInfo`\n```ts\nconstructor(options: RequestInfoOption)\n```\nPublic properties (plain, mutable, non-readonly):\n- `eventName: EventName` — defaults to `'prerequest'` if not provided (`options.eventName || 'prerequest'`).\n- `iteration: number` — defaults to `1` (`options.iteration || 1`).\n- `iterationCount: number` — defaults to `1` (`options.iterationCount || 1`).\n- `requestName: string` — defaults to `''`.\n- `requestId: string` — defaults to `''`.\n\nMethod:\n- `toObject = () => ({ eventName, iteration, iterationCount, requestName, requestId })` — plain serialization, same shape as `RequestInfoOption` with all fields always populated.\n\n## Script-facing surface\n- `insomnia.info.eventName` — `'prerequest'` or `'test'`, useful for scripts shared between pre-request and test tabs.\n- `insomnia.info.iteration` — current collection-runner iteration number (1-based).\n- `insomnia.info.iterationCount` — total number of iterations configured for the run.\n- `insomnia.info.requestName` — display name of the request being executed.\n- `insomnia.info.requestId` — the request's unique id.\n\n## Gotchas / notable behavior\n- All five properties are **plain public fields**, not readonly — a script can reassign\n  `insomnia.info.iteration = 99` and nothing in this class prevents it. In practice this has no\n  effect on the actual collection-runner state: `packages/insomnia/src/scripting/run-script.ts`'s\n  merge logic (building the returned `RequestContext`) does not read `requestInfo` back out of the\n  mutated `InsomniaObject` at all, so any script-side mutation of `insomnia.info` is silently\n  discarded after the script finishes.\n- The `||` fallback pattern means an explicit `0` for `iteration`/`iterationCount` is treated the\n  same as \"not provided\" and coerced to `1`. Notably, `initInsomniaObject` (in `insomnia.ts`)\n  builds the options with `iterationCount: rawObj.requestInfo.iterationCount || 0` — but since `0`\n  is falsy, the `RequestInfo` constructor's own `|| 1` fallback still turns that back into `1`, so\n  `iterationCount` effectively can never legitimately be `0` through this path.\n- A `// TODO: update follows when post-request script and iterationData are introduced` comment on\n  the call site in `insomnia.ts` suggests the `eventName`/iteration wiring for after-response\n  scripts may still be incomplete.\n\n## Related\n- `insomnia.ts` — `InsomniaObject.info: RequestInfo`; `initInsomniaObject` builds it from `rawObj.requestInfo` plus `rawObj.request.name`/`_id`.\n- `interfaces.ts` — `RequestInfoOption` is embedded as `RequestContext.requestInfo`.\n- `packages/insomnia/src/scripting/run-script.ts` — supplies the initial `RequestInfoOption` per script phase (pre-request vs. test) and — notably — does not persist script-side mutations back into `RequestContext`.\n- `__tests__/request-info.test.ts` — covers default values and `toObject()` shape for both single-request and collection-runner (`iteration`/`iterationCount`) cases.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/request.md (Tokens: ~2616 | Category: anthropic-skill) -->\n# Request, RequestBody\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/request.ts`\n\n## Purpose\n\nModels the outgoing HTTP request as seen/mutated by pre-request and test scripts: `pm.request` / `insomnia.request`. Also holds the merge/serialization logic that converts between this script-facing `Request` shape and Insomnia's own `insomnia-data` request model (`mergeRequests`, `mergeRequestBody`, `mergeSettings`, `mergeClientCertificates`), and payload-size calculation used by `Request.size()` / `Response.size()`.\n\n## Public API\n\n### `FormParam` (extends `Property`)\n- `constructor(options: { key: string; value: string; type?: string; disabled?: boolean })`\n- `key: string`, `value: string`, `type?: string`\n- `static _postman_propertyAllowsMultipleValues()` — **always throws** `Error('unsupported')`.\n- `static _postman_propertyIndexKey()` — **always throws** `Error('unsupported')`.\n- `toJSON()` — `{ key, value, type, disabled }`.\n- `toString()` — `\"key=value\"` with both parts `encodeURIComponent`-escaped.\n- `valueOf()` — returns `value`.\n\n### `RequestBodyOptions` (interface)\n`{ mode: RequestBodyMode; file?: string; formdata?: {key,value,type?,disabled?}[]; graphql?: {query,operationName,variables,disabled?}; raw?: string; urlencoded?: {key,value?,type?,disabled?,multiline?,fileName?}[]; options?: object }`\n`RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'`.\n\n### `RequestBody` (extends `PropertyBase`)\n- `constructor(opts: RequestBodyOptions)`\n- `mode: RequestBodyMode`, `file?: string`, `formdata?: PropertyList<FormParam>`, `graphql?: {query,operationName,variables}`, `raw?: string`, `urlencoded?: PropertyList<QueryParam>`, `options?: object`\n- `isEmpty(): boolean` — switches on `mode` and checks whether the matching field is `null`; **throws** `Error` if `mode` isn't one of the five known modes.\n- `toString()` — renders body as a string per `mode` (`formdata`/`urlencoded` join their `PropertyList` entries with `&`, `graphql` is `JSON.stringify`-ed); wraps any internal error as `Error(\"toString: ...\")`; returns `''` if `mode` is `undefined`.\n- `update(opts: RequestBodyOptions)` — re-derives and replaces all fields (same logic as constructor).\n\n### `RequestOptions` (interface)\n`{ url: string | Url; method?: string; header?: HeaderDefinition[] | object; body?: RequestBodyOptions; auth?: AuthOptions; proxy?: ProxyConfigOptions; certificate?: CertificateOptions; pathParameters?: RequestPathParameter[]; name?: string }`\n\n### `RequestSize` (interface)\n`{ body: number; header: number; total: number; source: string }`\n\n### `Request` (extends `Property`)\n- `constructor(options: RequestOptions)` — sets `_kind = 'Request'`; `method` defaults to `'GET'` if omitted; `header` accepts either an array of `HeaderDefinition` or a plain `{key: value}` object (converted to `Header`s); `auth` defaults to `{ type: 'noauth' }`.\n- Properties: `name: string`, `url: Url`, `method: string`, `headers: HeaderList<Header>`, `body?: RequestBody`, `auth: RequestAuth`, `proxy?: ProxyConfig`, `certificate?: Certificate`, `pathParameters: RequestPathParameter[]`.\n- `static isRequest(obj: object): boolean` — checks `_kind === 'Request'`.\n- `addHeader(header: Header | object)` — accepts a `Header` instance or `{key, value}`; **throws** `Error` otherwise.\n- `addQueryParams(params: QueryParam[] | string)` — delegates to `this.url.addQueryParams`.\n- `authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList<Variable>)` — delegates to `this.auth.use(...)`.\n- `clone(): Request` — `new Request({ ...this.toJSON() })` (JSON round-trip clone, not a deep object clone).\n- `forEachHeader(callback: (header: Header, context?: object) => void)` — delegates to `this.headers.each`.\n- `getHeaders(options?: { ignoreCase, enabled, multiValue, sanitizeKeys }): Record<string, string[] | string>` — merges headers with the same key into an array; `ignoreCase` lowercases keys before merging; `enabled` filters out headers where `disabled` is truthy; `sanitizeKeys` drops headers with a falsy `value`.\n- `removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean })` — rebuilds `this.headers` as a new `HeaderList` excluding matches; **throws** if `toRemove` isn't `string | Header`.\n- `removeQueryParams(params: string | string[] | QueryParam[])` — delegates to `this.url.removeQueryParams`.\n- `size(): RequestSize` — `calculatePayloadSize(this.body?.toString() ?? '', this.headers)`.\n- `toJSON()` — plain object snapshot (`url` as string, `header` array, `body`, `auth`, `proxy`, `certificate`).\n- `update(options: RequestOptions)` — re-derives and replaces every field.\n- `upsertHeader(header: HeaderDefinition)` — removes any existing header with the same `key` (case-sensitive), then appends a new `Header`.\n\n### Module-level functions\n- `mergeSettings(originalSettings: Settings, updatedReq: Request): Settings` — if `updatedReq.proxy` is enabled (not disabled and has a non-empty proxy URL), overrides both `httpProxy` and `httpsProxy` in the returned `Settings` with the same proxy URL; otherwise returns `originalSettings` unchanged.\n- `mergeClientCertificates(originalClientCertificates: ClientCertificate[], updatedReq: Request): ClientCertificate[]` — maps the script's single `updatedReq.certificate` onto Insomnia's certificate list (which supports multiple). Returns originals unchanged if no certificate was set (or it's empty). **Throws** `Error('Invalid certificate configuration: \"cert+key\" and \"pfx\" can not be set at the same time')` if neither a valid `pfx` nor a valid `cert`+`key` pair is present in the certificate.\n- `toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions` — converts Insomnia's native request body (`text` / `fileName` / `params`) into a `RequestBodyOptions` (`raw` / `file` / `urlencoded`); `formdata` and `graphql` modes are not produced here.\n- `mergeRequestBody(updatedReqBody: RequestBody | undefined, originalReqBody: InsomniaRequestBody): InsomniaRequestBody` — infers `mimeType` from `updatedReqBody.mode` (falls back to `originalReqBody.mimeType` if set); **throws** on an unknown `mode`; wraps any other failure as `Error(\"failed to update body: ...\")`.\n- `mergeRequests(originalReq: InsomniaRequest, updatedReq: Request): InsomniaRequest` — builds the outgoing Insomnia request from the script's mutated `Request`: `url` via `toStringWithoutQuery()`, `parameters` from `url.query`, `headers` from `headers`, `authentication` via `fromPreRequestAuth`, `pathParameters` copied as-is, and **hardcodes `preRequestScript: ''`** (i.e., the merged request never carries a pre-request script forward).\n- `calculatePayloadSize(body: string, headers: HeaderList<Header>): RequestSize` — body size via `new Blob([body]).size`; `source` is always `'COMPUTED'`.\n- `calculateHeadersSize(headers: HeaderList<Header>): number` — `Blob` size of all headers joined as `\"key: value\\n\"` lines (via each header's `toString()`, which is `Header`'s inherited/overridden behavior).\n\n## Script-facing surface\n\n`insomnia.request` / `pm.request` is a live `Request` instance (constructed in `insomnia.ts#initInsomniaObject`). Common script usage:\n- `pm.request.url`, `pm.request.method`, `pm.request.headers`, `pm.request.body`\n- `pm.request.headers.add(...)`, `pm.request.addHeader({key, value})`, `pm.request.upsertHeader({key, value})`, `pm.request.removeHeader('X-Foo')`\n- `pm.request.url.addQueryParams(...)` / `pm.request.addQueryParams(...)`, `pm.request.removeQueryParams(...)`\n- `pm.request.auth`, `pm.request.authorizeUsing('basic', ...)`\n- `pm.request.size()` — payload size info\n- Any mutation a pre-request script makes to `pm.request` is read back and merged into the real outgoing request via `mergeRequests` / `mergeRequestBody` / `mergeSettings` / `mergeClientCertificates` after the script finishes.\n\n## Gotchas / notable behavior\n\n- `FormParam._postman_propertyAllowsMultipleValues()` and `_postman_propertyIndexKey()` are stubs that **always throw** — calling them from a script (unlikely, but they exist statically) will always fail. A commented-out `static parse` is also noted as \"not supported yet in existing scripts\".\n- `RequestBody.isEmpty()` / `toString()` both `throw` if `mode` doesn't match one of the five known literals — an unexpected/typo'd `mode` string surfaces as a runtime error, not silently.\n- `Request.getHeaders()`'s `multiValue` option is part of the signature but **is never referenced in the function body** — the implementation always accumulates same-key headers into an array regardless of what `multiValue` is set to.\n- `Request.removeHeader` and `upsertHeader` rebuild `this.headers` as an entirely new `HeaderList` rather than mutating in place.\n- `Request.clone()` is a JSON round-trip (`toJSON()` → `new Request(...)`), not a structural/deep clone — anything not captured by `toJSON()` is lost on clone.\n- `mergeRequests` **always sets `preRequestScript: ''`** on the merged request — a deliberate design choice worth knowing if debugging \"script disappeared\" reports downstream.\n- `mergeClientCertificates` throws if a script sets both `cert`+`key` and `pfx` on `pm.request.certificate` at the same time — Insomnia only supports one or the other.\n- `toScriptRequestBody` only produces `raw` / `file` / `urlencoded` modes; there's no path from Insomnia's native body model to script `formdata`/`graphql` modes in this function.\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` used for `Request.headers`.\n- `packages/insomnia-scripting-environment/src/objects/urls.ts` — `Url`, `QueryParam`, `toUrlObject` used for `Request.url` and `RequestBody.urlencoded`.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used for `Request.auth`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate` used for `Request.certificate`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig` used for `Request.proxy`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes.\n- `packages/insomnia-scripting-environment/src/objects/variables.ts` — `Variable`, `VariableList` used by `authorizeUsing`.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — imports `calculateHeadersSize`; `Response.originalRequest` is a `Request`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.request` in `initInsomniaObject`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/response.md (Tokens: ~1974 | Category: anthropic-skill) -->\n# Response\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/response.ts`\n\n## Purpose\n\nModels the HTTP response as seen by after-response and test scripts: `pm.response` / `insomnia.response`. It wraps the raw body/headers/cookies/timing data returned by the network layer, provides parsing/introspection helpers (`json()`, `contentInfo()`, `size()`), and extends `chai`'s assertion API with Postman-style response assertions (`pm.response.to.have.status(200)`, etc.). `pm.response` is only populated for after-response/test scripts — it's `undefined` during pre-request scripts.\n\n## Public API\n\n### `ResponseOptions` (interface)\n`{ code: number; reason?: string; header?: HeaderDefinition[]; cookie?: CookieOptions[]; body?: string; stream?: Buffer | ArrayBuffer; responseTime: number; originalRequest: Request; bytesRead?: number }`\n\n### `ResponseContentInfo` (interface)\n`{ mimeType: string; mimeFormat: string; charset: string; fileExtension: string; fileName: string; contentType: string }`\n\n### `Response` (extends `Property`)\n- `constructor(options: ResponseOptions)` — sets `_kind = 'Response'`; `body` defaults to `''`; `status` defaults to `options.reason`, falling back to `RESPONSE_CODE_REASONS[options.code]`, falling back to `''`; `bytesRead` (private) defaults to `0`.\n- Properties: `body: string`, `code: number`, `cookies: CookieList`, `headers: HeaderList<Header>`, `originalRequest: Request`, `responseTime: number`, `status: string`, `stream?: Buffer | ArrayBuffer`; `bytesRead` is **private**.\n- `static createFromNode(response: {...}, cookies: CookieOptions[]): Response` — builds a `Response` from a raw Node-style response object (`body`, `headers`, `statusCode`, `statusMessage`, `elapsedTime`, `originalRequest`, `stream`).\n- `static isResponse(obj: object): boolean` — checks `_kind === 'Response'`.\n- `contentInfo(): ResponseContentInfo` — parses the `Content-Type` header for `mimeType`/`charset` (defaults: `application/octet-stream` / `utf8`) and the `Content-Disposition` header for `fileName`/`fileExtension`. **Throws** if a `Content-Type` header exists but its value is blank, or its mime-type segment is empty.\n- `dataURI(): string` — builds a `data:` URI string from `contentInfo().contentType` and `this.stream || this.body`. **Throws** if neither `stream` nor `body` is set.\n- `json(reviver?: (key, value) => any, _strict?: boolean): any` — `JSON.parse(this.body.toString(), reviver)`; wraps parse failures as `Error(\"json: failed to parse: ...\")`. `_strict` is accepted but unused (\"TODO: enable strict after common module is introduced\").\n- `jsonp(_reviver?, _strict?)` — **always throws** `unsupportedError('jsonp()')`.\n- `reason(): string` — returns `this.status`.\n- `size(): { body: number; header: number; total: number; source: 'COMPUTED' }` — `body` is the private `bytesRead` value (not derived from `this.body`'s actual length); `header` via `calculateHeadersSize(this.headers)`.\n- `text(): string` — `this.body.toString()`.\n- `get to()` — returns a `chai.Assertion` wrapping `this`, with Postman-style properties/methods registered onto `chai.Assertion.prototype` on every access: properties `withBody`, `error` (true if `code` is within 400–500 inclusive), `ok` (true if `code === 200`), `json` (true if the body parses to an object); methods `status(val)`, `header(headerName)`, `body(bodyContent)`, `jsonBody(propName)`, `jsonSchema(schema, options?)` (uses `ajv` to validate `this.json()` against `schema`).\n\n### Module-level functions\n- `toScriptResponse(originalRequest: Request, partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, responseBody: string): Response | undefined` — returns `undefined` if `partialInsoResponse` is an error result (network/curl failure); otherwise builds a `Response`, extracting `Set-Cookie` headers into `Cookie.parse(...)` entries for `cookie`.\n- `readBodyFromPath(response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined): Promise<string>` — returns `''` if `response` is missing, an error, or has no `bodyPath`; otherwise reads and decompresses the body from disk via `services.helpers.readCurlResponse`. **Throws** if that read reports an error.\n\n## Script-facing surface\n\n`insomnia.response` / `pm.response` is a `Response` instance, present only in after-response/test scripts (constructed in `insomnia.ts#initInsomniaObject` via `toScriptResponse`; `undefined` if the request errored or during pre-request scripts). Common script usage:\n- `pm.response.code`, `pm.response.status`, `pm.response.headers`, `pm.response.body`\n- `pm.response.json()`, `pm.response.text()`, `pm.response.contentInfo()`\n- `pm.response.to.have.status(200)`, `pm.response.to.have.header('Content-Type')`, `pm.response.to.have.jsonBody('key')`, `pm.response.to.not.have.status(404)`, `pm.response.to.have.jsonSchema({...})`\n\n## Gotchas / notable behavior\n\n- `dataURI()` has a typo in the returned string: it emits `` `data:${contentType};baseg4, ${bodyInBase64}` `` — **`baseg4` instead of `base64`**. Any consumer relying on `dataURI()` producing a spec-compliant data URI will get a malformed one.\n- `jsonp()` is a stub that always throws `unsupportedError('jsonp()')` — it exists on the type but is never functional.\n- `size().body` comes from the **private `bytesRead`** field (set once at construction from `options.bytesRead`, defaulting to `0`), not from measuring `this.body`. If `bytesRead` wasn't passed in, `size()` reports `0` regardless of actual body length. (The test file has a commented-out assertion noting `resp.size()` doesn't fully work yet: `\"this will work after PropertyList.one is improved\"`.)\n- The `error` assertion (`pm.response.to.have.error` / `.to.be.error`) only covers status codes **400–500 inclusive** — not the broader 4xx/5xx range one might expect.\n- **`get to()`'s re-registration is a real correctness risk, not just wasted work.** Every `.to` access calls `chai.use((_chai, utils) => {...})` with a freshly-created arrow function; chai dedupes plugins by callback identity (`used.indexOf(fn)`), so a new closure is never recognized as already-registered and the plugin body reruns every time. Worse, every `withBody`/`error`/`ok`/`json`/`status`/`header`/`body`/`jsonBody`/`jsonSchema` callback is itself an arrow function that reads `utils.flag(respAssertion, 'object')`, where `respAssertion` is closed over from whichever `.to` access most recently ran — not derived from `this`/the assertion instance actually being evaluated (arrow functions ignore the `this` chai binds via `getter.call(this)`/`method.apply(this, ...)`). So if a script holds a `.to`-derived assertion chain and triggers another `.to` access (same or different response) before invoking a method/property on that held chain, the held chain silently asserts against the *other* response instead. Single-statement chains like `pm.response.to.have.status(200)` are unaffected since no `.to` access can intervene mid-expression.\n- `contentInfo()` throws if a `Content-Type` header is present but empty/malformed; scripts that blindly call `pm.response.contentInfo()` on unusual responses can throw.\n- `toScriptResponse` returns `undefined` on network error — scripts must handle `pm.response` potentially being `undefined` (though in practice after-response/test scripts only run when a response exists).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Response.originalRequest: Request`; imports `calculateHeadersSize` from here.\n- `packages/insomnia-scripting-environment/src/objects/headers.ts` — `Header`, `HeaderList` for `Response.headers`.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `Cookie`, `CookieList` for `Response.cookies`.\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `unsupportedError`.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — constructs `pm.response` in `initInsomniaObject` via `toScriptResponse`/`readBodyFromPath`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/send-request.md (Tokens: ~2047 | Category: anthropic-skill) -->\n# sendRequest (pm.sendRequest)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/send-request.ts`\n\n## Purpose\nImplements the standalone `sendRequest()` function that backs `pm.sendRequest()` / `insomnia.sendRequest()` — letting a pre-request/after-response script fire an ad-hoc HTTP request (via libcurl) and get a `Response` back, either through a Node-style callback or a returned promise. It also contains the translation layer from the SDK's `Request`/string/`RequestOptions` shapes into the curl request options consumed by Insomnia's network layer, and from the raw curl output back into a `Response` object.\n\n## Public API\n\n### `sendRequest(request, cb, settings)`\n```ts\nexport async function sendRequest(\n  request: string | Request | RequestOptions,\n  cb: (error?: string, response?: Response) => void,\n  settings: Settings,\n): Promise<Response | void>\n```\n- Builds curl options from `request` via `requestToCurlOptions(request, settings)`.\n- Picks the curl execution path based on environment: in the renderer (`__IS_RENDERER__` true) it uses `window.bridge.curlRequest`; otherwise it dynamically imports `curlRequest` from `insomnia/src/main/network/libcurl-promise`.\n- Awaits the curl call, converts the raw `CurlRequestOutput` to a `Response` via `curlOutputToResponse(output, request)`.\n- **Success:** if `cb` is provided, calls `cb(undefined, transformedOutput)`; either way resolves the returned promise with `transformedOutput`.\n- **Failure:** if `cb` is provided, calls `cb(e)` and resolves the promise with `undefined` (does **not** reject); if no `cb` is provided, rejects the promise with `e`.\n\n### `requestToCurlOptions(req, settings)` (not exported)\n```ts\nfunction requestToCurlOptions(req: string | Request | RequestOptions, settings: Settings)\n```\n- If `req` is a `string`: treats it as a bare URL, builds a minimal GET request object (`no body`, `noauth`, empty headers/cookies, `settingRebuildPath: true`, `settingSendCookies: true`, `settingFollowRedirects` derived from `settings.followRedirects ? 'on' : 'off'`), with `requestId` prefixed `pre-request-script-adhoc-req-simple:<uuid>`.\n- If `req` is a `Request` instance or a plain object (`RequestOptions`): coerces to a `Request` (`new Request(req)` if not already one), derives `mimeType` from `finalReq.body.mode` (`raw`→`text/plain`, `file`→`application/octet-stream`, `formdata`→`multipart/form-data`, `urlencoded`→`application/x-www-form-urlencoded`, `graphql`→`application/json`, otherwise `text/plain`), maps headers/body/certificate/auth into the curl request shape, with `requestId` = `finalReq.id` or a generated `pre-request-script-adhoc-req-custom:<uuid>`.\n- Auth mapping beyond `noauth` is largely commented out / marked `TODO` (see Gotchas).\n- Throws `Error('the request type must be: string | Request | RequestOptions.')` for any other input shape.\n\n### `curlOutputToResponse(result, request)` (not exported)\n```ts\nasync function curlOutputToResponse(\n  result: CurlRequestOutput,\n  request: string | Request | RequestOptions,\n): Promise<Response>\n```\n- Throws if `result.headerResults` is empty, or if `result.patch.error` is set (re-throws that error), or if there's no last redirect entry.\n- Normalizes `request` into a `Request` instance for `originalRequest`.\n- Extracts headers from the last redirect's `headerResults` entry; parses any `Set-Cookie` headers via `tough-cookie`'s `Cookie.parse(..., { loose: true })` into cookie option objects (filtering out unparsable ones).\n- If `result.responseBodyPath` is absent, returns a `Response` with `body: ''`.\n- Otherwise reads the body via `services.helpers.readCurlResponse({ bodyPath, bodyCompression })`; throws if that read reports an error; otherwise returns a fully populated `Response` (`code`, `reason`, `header`, `cookie`, `body`, `responseTime: result.patch.elapsedTime`, `originalRequest`). `stream` is always left `undefined` (\"because it is inaccurate to differentiate if body is binary\").\n\n## Script-facing surface\n- `pm.sendRequest(url, (err, response) => { ... })` — string URL, GET request, callback style.\n- `pm.sendRequest({ url, method, header, body, auth, ... }, (err, response) => { ... })` — full `RequestOptions`-shaped object.\n- `pm.sendRequest(requestInstance, (err, response) => { ... })` — passing an existing `Request` instance (e.g. built via `new Request(...)` or `pm.request`).\n- Can also be used promise-style without a callback: `const response = await pm.sendRequest(url)` (see Gotchas for the resulting error-handling difference).\n- Exposed on the object model as `insomnia.sendRequest(request, cb)`, which is a plain instance method (not proxy-trapped) that forwards to this file's `sendRequest(request, cb, this._settings)`.\n\n## Gotchas / notable behavior\n- **Callback vs. promise error handling differ:** if a `cb` is supplied, network/parsing errors are *swallowed* into the callback (`cb(e)`) and the returned promise still resolves (with `undefined`) — it will not reject. If no `cb` is supplied, the same error instead rejects the returned promise. A script mixing `await pm.sendRequest(url, cb)` with `try/catch` will not catch curl errors, because the promise resolves successfully even on failure when a callback is present.\n- **Environment branching:** `__IS_RENDERER__ ? window.bridge.curlRequest : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest` — the actual network call goes through completely different code paths depending on whether the script executes in the renderer (Electron IPC bridge) or Node/main context. Bugs that only reproduce in one context (e.g. inso CLI vs. the desktop app) may live in this branch.\n- **Auth beyond `noauth` is not wired up** for the custom-`Request` branch: there's a large commented-out block (`// const authHeaders = ...`) showing API-key/bearer header injection was planned but not implemented; `fromPreRequestAuth(finalReq.auth)` is still called and passed through as `authentication`, but the `authHeader` field of the curl options is always `undefined` with a `// TODO: add this for bearer and other auth methods` comment.\n- **Certificates:** only a single client certificate is forwarded (`finalReq.certificate`), built directly into the curl options' `certificates` array; several fields (`disabled`, `isPrivate`, `_id`, `type`, `parentId`, `modified`, `created`, `name`) are hardcoded to empty/`false`/`0` since they're \"unused fields because they are not persisted\".\n- **Cookies are not populated from a jar** — `cookieJar: { cookies: [] }` and `cookies: []` are always empty in the outgoing request; the comment notes \"currently cookies should be handled by user in headers\". Response `Set-Cookie` headers are still parsed back out via `tough-cookie`, though.\n- **`suppressUserAgent` detection** only checks for a *disabled* `User-Agent` header (`h.key.toLowerCase() === 'user-agent' && h.disabled === true`) — an enabled custom `User-Agent` header does not suppress the default one; it presumably just overrides via normal header precedence.\n- **Response body/stream:** `stream` is always `undefined` in the returned `Response`, even though `CurlRequestOutput`/`ResponseOptions` support it, specifically to avoid ambiguity between binary and text bodies — callers should rely on `body` (a string) only.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request`, `RequestOptions`, `RequestBody`/`RequestBodyOptions` types this function consumes as input.\n- `packages/insomnia-scripting-environment/src/objects/response.ts` — `Response` class this function constructs as output.\n- `packages/insomnia-scripting-environment/src/objects/auth.ts` — `RequestAuth`, `fromPreRequestAuth` used to translate the SDK's auth model into the curl request's `authentication` field.\n- `packages/insomnia-scripting-environment/src/objects/cookies.ts` — `CookieOptions` type used for parsed `Set-Cookie` results.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — exposes this function as `insomnia.sendRequest(request, cb)`, supplying `this._settings` as the `settings` argument.\n- `insomnia/src/main/network/libcurl-promise` (`curlRequest`, `CurlRequestOutput`) — the main/Node-side curl execution path, dynamically imported when not running in the renderer.\n- `window.bridge.curlRequest` — the renderer-side IPC bridge equivalent used when `__IS_RENDERER__` is true.\n- `insomnia-data` (`services.helpers.readCurlResponse`, `Settings` type) — used to read the response body from disk (`responseBodyPath`) and for the `Settings` (e.g. `followRedirects`) passed in.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/test.md (Tokens: ~1627 | Category: anthropic-skill) -->\n# Test handler (pm.test / test())\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/test.ts`\n\n## Purpose\nImplements the `test()`/`skip()` handler that backs `pm.test(...)` (and `insomnia.test(...)`), plus the bookkeeping used by the script-execution harness to know when all in-flight test callbacks have settled before the script finishes. This is the only file responsible for turning a user's test callback into a `RequestTestResult` record.\n\n## Public API\n\n### `test(msg, fn, log)`\n```ts\nexport async function test(\n  msg: string,\n  fn: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Wraps `fn` in an async closure (`wrapFn`), times its execution with `performance.now()`, awaits it, and calls `log(...)` with a `RequestTestResult`:\n  - On success: `{ testCase: msg, status: 'passed', executionTime, category: 'unknown' }`.\n  - On thrown error `e`: `{ testCase: msg, status: 'failed', executionTime, errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, category: 'unknown' }`.\n- Immediately invokes `wrapFn()`, registers the resulting promise with `startTestObserver`, and returns that same promise (`testPromise`) — i.e. `test()` itself resolves once the wrapped test body (pass or fail) has finished running.\n- `category` is always hardcoded to `'unknown'` here even though `RequestTestResult.category` (in `insomnia-data`) also allows `'pre-request'` and `'after-response'`.\n\n### `skip(msg, _, log)`\n```ts\nexport async function skip(\n  msg: string,\n  _: () => Promise<void>,\n  log: (testResult: RequestTestResult) => void\n): Promise<void>\n```\n- Does **not** call `fn` (the second argument is ignored). Immediately calls `log({ testCase: msg, status: 'skipped', executionTime: 0, category: 'unknown' })`.\n\n### `resetTestPromises()`\n```ts\nexport function resetTestPromises(): void\n```\n- Clears the module-level `testPromises` array (`testPromises = []`). Used to reset state between script executions so promises from a previous run aren't awaited again.\n\n### `waitForAllTestsDone()`\n```ts\nexport async function waitForAllTestsDone(): Promise<void>\n```\n- Awaits `Promise.allSettled(testPromises)` (captured as `NativePromise` at module load, so it isn't affected by any sandbox-level `Promise` patching), then resets `testPromises` back to `[]`.\n- This is the drain point the execution harness calls at the end of a script run to make sure every `pm.test(...)` callback (including ones the script itself didn't `await`) has finished before results are collected.\n\n### `startTestObserver(promise)` (not exported)\n```ts\nfunction startTestObserver(promise: Promise<void>): void\n```\n- Pushes `promise` onto the module-level `testPromises` array. Called once per `test()` invocation.\n\n### `TestHandler` interface\n```ts\nexport interface TestHandler {\n  (msg: string, fn: () => Promise<void>): Promise<void>;\n  skip?: (msg: string, fn: () => Promise<void>) => void;\n}\n```\n- Callable interface shape used by the object model to type the value returned for `pm.test` — a function that also carries an optional `.skip` method.\n\n## Script-facing surface\n- `pm.test('name', async () => { ... })` / `insomnia.test('name', fn)` — registers and immediately runs a test, recording a pass/fail result.\n- `pm.test.skip('name', fn)` — records a `skipped` result without running `fn`.\n- There is no bare global `test`/`skip` exposed directly by this file; script-facing exposure happens through the `insomnia`/`pm` object (see Related).\n\n## Gotchas / notable behavior\n- **Fire-and-forget by default:** `test()` starts executing the test body synchronously (well, as soon as the async function runs) and returns a promise, but scripts are not required to `await pm.test(...)`. Because every test promise is also pushed into the shared `testPromises` array via `startTestObserver`, un-awaited tests are still tracked and will be waited on by `waitForAllTestsDone()` before the script finishes — this is what prevents test results from being lost when a script fires multiple `pm.test()` calls without awaiting them.\n- **Module-level mutable state:** `testPromises` is a module-level array, not per-execution-context state. `resetTestPromises()` must be called before a script runs and `waitForAllTestsDone()` after, or results/timing from different executions could leak into each other (relevant if concurrent script executions ever share this module instance).\n- **Error message shape assumes chai-style errors:** the failure branch does `` `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}` ``, which assumes `e` has `.actual`/`.expected` (true for chai `AssertionError`s from `insomnia.expect(...)`). For a plain thrown `Error` or non-chai exception, `ACTUAL`/`EXPECTED` will stringify as `undefined`.\n- **`category` is never anything but `'unknown'`** from this module — any pre-request vs. after-response distinction in `RequestTestResult.category` must be set elsewhere (or is currently unused/always `'unknown'` for script-originated tests).\n- **`NativePromise` capture:** `const NativePromise = Promise;` is captured at module import time specifically so `waitForAllTestsDone`'s `allSettled` call uses the real, un-sandboxed `Promise`, not a possibly-patched one from the script sandbox.\n\n## Related\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — wires `test`/`skip` into the `InsomniaObject`. The constructor returns a `Proxy` whose `get` trap intercepts `prop === 'test'` and returns a `TestHandler` closure calling `this._test(msg, fn, this.pushRequestTestResult)` (and `.skip` calling `this._skip(...)`); `pushRequestTestResult` appends to `this.requestTestResults: RequestTestResult[]`, later returned from `toObject()`.\n- `packages/insomnia-scripting-environment/src/objects/async-objects.ts` — calls `resetTestPromises()` once at module load alongside its own promise-tracking logic for `ProxiedPromise`.\n- `packages/insomnia/src/scripting/sandbox.ts` (`prepareSandbox`) and `packages/insomnia/src/scripting/run-script.ts` — the harness that injects `__waitForAllTestsDone__` into the generated script function and awaits it (`await __waitForAllTestsDone__();`) before returning the mutated `insomnia` object; `run-script.ts` then reads `mutatedInsomniaObject.toObject().requestTestResults` and includes it in the returned `RequestContext`.\n- `packages/insomnia-data` (`RequestTestResult`, `TestStatus`, `TestCategory` in `src/models/runner-test-result.ts`) — the result shape this file produces.\n- UI consumers of the resulting test results: `packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx` and `request-result-card.tsx`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/urls.md (Tokens: ~3261 | Category: anthropic-skill) -->\n# Url, QueryParam, UrlMatchPattern\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/urls.ts`\n\n## Purpose\n\nModels a request URL (`Url`), its individual query-string parameters (`QueryParam`), and Chrome-extension-style match patterns (`UrlMatchPattern` / `UrlMatchPatternList`) used to test whether a URL matches a proxy bypass rule or a client-certificate host rule. `pm.request.url` is a `Url`; `UrlMatchPattern` backs `ProxyConfig.match` and `Certificate.matches`.\n\n## Public API\n\n### `setUrlSearchParams(provider: any)`\nModule-level override hook: replaces the `URLSearchParams` implementation used internally by `QueryParam` (defaults to the global `URLSearchParams`). Test-only utility, not script-facing.\n\n### `QueryParamOptions` (interface)\n`{ key: string; value?: string; type?: string; multiline?: string | boolean; disabled?: boolean; fileName?: string }`\n\n### `QueryParam` (extends `Property`)\n- `override _kind = 'QueryParam'`\n- `key: string`, `value?: string`, `type?: string`, `multiline?: string | boolean`, `fileName?: string` (the latter two are noted in a comment as \"properties from Insomnia... added here to avoid being dropped\").\n- `constructor(options: QueryParamOptions | string)` — if `options` is a string, it's `JSON.parse`d (wraps parse errors as `Error('invalid QueryParam options ...')`); if it's an object with `key`+`value`, fields are copied directly; otherwise **throws** `Error('unknown options for new QueryParam')`.\n- `static override _index = 'key'`\n- `static parse(queryStr: string): {key, value}[]` — parses a query string via `URLSearchParams`.\n- `static parseSingle(paramStr: string, _idx?, _all?): {key, value}` — parses a single `key=value` pair; **throws** if `QueryParam.parse` yields nothing.\n- `static unparse(params: QueryParamOptions[] | Record<string, string>): string` — builds a query string via `URLSearchParams`, URL-encoding as it goes.\n- `static unparseSingle(obj: {key, value}): string | {}` — returns `''`-joined encoded `\"key=value\"` if the input has both `key` and `value`, otherwise returns `{}` (an empty object, not a string — inconsistent return type).\n- `toString(): string` — URL-encodes via `URLSearchParams` (e.g. spaces become `+`).\n- `toRawString(): string` — `\"key=value\"` with **no encoding**.\n- `update(param: string | {key, value, type?})` — parses a string form via `parseSingle` (only sets `key`/`value`, coercing non-string results to `''`) or copies `key`/`value`/`type` from an object; **throws** on any other input shape.\n\n### `UrlOptions` (interface)\n`{ id?: string; auth?: {username, password}; hash?: string; host: string[]; path?: string[]; port?: string; protocol: string; query: {key, value}[]; variables: {key, value}[] }`\n\n### `Url` (extends `PropertyBase`)\n- `override _kind = 'Url'`\n- `id?: string`\n- Getters (all derived from an internal `URL` object, `this.urlObject`, which is `undefined` if the input couldn't be parsed as a URL — e.g. contains unrendered `{{ }}`/`{% %}` template tags): `auth` (`{username, password}` or `undefined`), `hash` (without leading `#`), `host` (hostname split on `.`), `path` (pathname segments, empty ones filtered out), `port`, `protocol`, `query: PropertyList<QueryParam>` (a **new** `PropertyList` wrapping the current query params on every access), `variables: string[]` (**always returns `[]`** — \"TODO: it's usage is unknown\").\n- `constructor(def: UrlOptions | string)`\n- `private initFields(urlOptions)` — parses `def`; if it's a string containing a template tag (`checkIfUrlIncludesTag`), it's kept as an opaque `origin` string and `urlObject` stays `undefined` (to avoid mangling `{% uuid 'v4' %}`-style tags); if it's an object, a URL string is assembled from `protocol`/`auth`/`host`/`port`/`path`/`query`/`hash` (protocol defaults to `'https://'` if blank). Query params are always parsed out into `this.queryParams` (a private array) separately from the `URL` object (whose own `search` is cleared) — \"query params are handled separately as URL object encodes content\".\n- `static _index = 'id'`\n- `static isUrl(obj: object): boolean`\n- `static parse(urlStr: string): UrlOptions | undefined` — returns `undefined` if `URL.canParse` fails; `variables` is always `[]` in the result.\n- `addQueryParams(params: QueryParamOptions[] | string)` — string form splits on `&` then `=` (not URL-decoded); array form copies each entry into a new `QueryParam`. **Throws** `TypeError` on other input.\n- `getHost(): string` — `''` if the URL didn't parse.\n- `getPath(_unresolved?): string` — `_unresolved` param is accepted but unused; `''` if unparsed.\n- `getPathWithQuery(): string` — path + `?` + query string (or just the query string if path is blank).\n- `getQueryString(): string` — joins **enabled** (`!disabled`) query params via `toRawString()` (i.e. **unencoded**, unlike `QueryParam.toString()`).\n- `getRemote(_forcePort?): string` — `_forcePort` is accepted but unused; returns `urlObject.host` (which already includes the port when non-default) or `''`.\n- `removeQueryParams(params: QueryParam[] | string[] | string)` — filters `this.queryParams` by key; **throws** `TypeError` on unrecognized input shape.\n- `override toString(_forceProtocol?): string` — `_forceProtocol` accepted but unused; rebuilds the URL string with the current query string; falls back to the opaque `origin` string if unparsed; special-cases avoiding an added trailing `/` for root-path URLs.\n- `toStringWithoutQuery(_forceProtocol?): string` — same as `toString()` but with `search` cleared.\n- `update(url: UrlOptions | string)` — re-runs `initFields`.\n\n### `UrlMatchPattern` (extends `Property`)\nImplements [Chrome extension match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns) (`scheme://host/path`, wildcards, `<all_urls>`); comment notes it doesn't support top-level-domain wildcards, and that URLs can't start with `-` (unenforced).\n- `override id = ''`\n- `constructor(pattern: string)`\n- `static override _index = 'id'`\n- `static readonly MATCH_ALL_URLS = '<all_urls>'`\n- `static pattern: string | undefined = undefined` — declared but unused (\"TODO: its usage is unknown\"); shadowed by (unrelated to) the instance's private `pattern`.\n- `static readonly PROTOCOL_DELIMITER = '+'` — multiple schemes are written as `http+https+custom://...`.\n- `getProtocols(): string[]` — `['http', 'https', 'file']` for `<all_urls>`; otherwise splits the scheme segment on `+`.\n- `test(urlStr: string): boolean` — true only if protocol, host, path, and port all match.\n- `testHost(hostStr: string): boolean` — segment-by-segment comparison against the pattern's host (split on `.`); a segment of `*` matches anything; segment **counts must match exactly** (no subdomain-wildcard-prefix support beyond single-segment `*`).\n- `testPath(pathStr: string): boolean` — segment-by-segment comparison (split on `/`); segment counts must match exactly; `*` matches any single segment.\n- `testPort(port: string, protocol: string): boolean` — returns `false` immediately if `testProtocol(protocol)` fails; `*` in the pattern matches any port; if no port is specified in either the pattern or input, falls back to protocol-default-port logic for `http`/`https` only.\n- `testProtocol(protocol: string): boolean` — `*` in the pattern matches any protocol.\n- `override toString(): string` — returns the raw pattern string.\n- `update(pattern: string)`\n\n### `UrlMatchPatternList<T extends UrlMatchPattern>` (extends `PropertyList<T>`)\n- `override _kind = 'UrlMatchPatternList'`\n- `constructor(parent: PropertyList<T> | undefined, populate: T[])`\n- `static isUrlMatchPatternList(obj: any): boolean`\n- `test(urlStr: string): boolean` — true if **any** pattern in the list matches.\n\n### Module-level functions\n- `toUrlObject(url: string | Url): Url` — **throws** `Error('Request URL is not specified')` if `url` is falsy; passes through an existing `Url` unchanged, otherwise constructs a new one from a string.\n- `resolveProtocolForProxy(rawUrl: string): string` — resolves the protocol to use for proxy selection when the URL may still contain unrendered template tags (pre-request scripts run before rendering); tries `new URL(rawUrl).protocol` first, falling back to `'https:'` on parse failure.\n\n## Script-facing surface\n\n`pm.request.url` is a `Url` instance. Common script usage:\n- `pm.request.url.toString()`, `pm.request.url.getHost()`, `pm.request.url.getPath()`, `pm.request.url.query`\n- `pm.request.url.addQueryParams([{key, value}])` / `pm.request.addQueryParams(...)` (delegates)\n- `pm.request.url.removeQueryParams('key')` / `pm.request.removeQueryParams(...)` (delegates)\n- `pm.request.url.hash`, `.host`, `.port`, `.protocol`, `.auth`\n\n`QueryParam` instances populate `pm.request.url.query` and `RequestBody.urlencoded`.\n\n`UrlMatchPattern`/`UrlMatchPatternList` aren't constructed directly from a typical script, but are reachable via `pm.request.certificate.matches` (see `certificates.ts`), which stores its match rule(s) as a `UrlMatchPatternList` directly. `pm.request.proxy.match` (see `proxy-configs.ts`) is different: it's a raw `string` on the instance, with a `UrlMatchPattern` built on the fly, temporarily, inside `ProxyConfig.test()`/`.getProtocols()` — and `ProxyConfig.bypass` is a plain `string[]` checked by exact string equality, not a `UrlMatchPatternList` at all (that type only appears as an unused `static bypass` field on the class, separate from the instance field of the same name).\n\n## Gotchas / notable behavior\n\n- `Url.variables` **always returns `[]`**, even though `UrlOptions.variables` and `Url.parse()`'s return both have a `variables` field — the field is accepted on input but silently discarded; there is no way to read it back out.\n- `Url`'s internal `urlObject` (a real `URL`) is `undefined` whenever the input string contains a template tag (`{{ }}` / `{% %}`) or otherwise fails `URL.canParse` — in that case nearly every getter/method (`host`, `path`, `port`, `protocol`, `getHost()`, `getRemote()`, etc.) silently returns an empty string/array instead of throwing. Only `toString()`/`toStringWithoutQuery()` fall back to the raw `origin` string.\n- Query-string encoding is inconsistent by design: `QueryParam.toString()` URL-encodes (via `URLSearchParams`), but `QueryParam.toRawString()` and `Url.getQueryString()` (which uses `toRawString()`) do **not** encode. `Url.addQueryParams(stringForm)` also splits on raw `&`/`=` without decoding.\n- `QueryParam.unparseSingle()` returns `{}` (not a string) when the input lacks `key`/`value` — inconsistent return type (`string | {}`) that callers must handle.\n- Several methods accept parameters that are **entirely unused**: `Url.getPath(_unresolved)`, `Url.getRemote(_forcePort)`, `Url.toString(_forceProtocol)`, `Url.toStringWithoutQuery(_forceProtocol)`, `QueryParam.parseSingle(_idx, _all)`. Passing anything for these has no effect.\n- `UrlMatchPattern.static pattern` (class-level) is unused/dead and easy to confuse with the instance's own private `pattern` field.\n- `UrlMatchPattern.testHost`/`testPath` require the **segment counts to match exactly** — a pattern like `*.insomnia.com` (2 segments) will not match `bin.download.insomnia.com` (4 segments), confirmed by the test suite (`urls.test.ts`).\n- `UrlMatchPattern` has no protocol-agnostic short-circuit: calling `testProtocol` on a pattern with no `://` returns `[]` for `getProtocols()`, meaning `testProtocol` always returns `false` for such a malformed pattern (per `urls.test.ts`, \"no protocol\" case).\n\n## Related\n\n- `packages/insomnia-scripting-environment/src/objects/properties.ts` — `Property`, `PropertyBase`, `PropertyList` base classes; `PropertyList.toObject()`'s fallback comment specifically calls out `UrlMatchPatternList` as a list with no natural key.\n- `packages/insomnia-scripting-environment/src/objects/utils.ts` — `checkIfUrlIncludesTag`, used by `Url.initFields` to avoid mangling template tags.\n- `packages/insomnia-scripting-environment/src/objects/request.ts` — `Request.url: Url`; `toUrlObject` used when constructing a `Request`; `RequestBody.urlencoded: PropertyList<QueryParam>`.\n- `packages/insomnia-scripting-environment/src/objects/certificates.ts` — `Certificate.matches: UrlMatchPatternList<UrlMatchPattern>`.\n- `packages/insomnia-scripting-environment/src/objects/proxy-configs.ts` — `ProxyConfig.match: string` is used to construct a `UrlMatchPattern` on demand inside `test()`/`getProtocols()`; the instance's `ProxyConfig.bypass` is a plain `string[]` (exact-match only), not a `UrlMatchPatternList`.\n- `packages/insomnia-scripting-environment/src/objects/collection.ts` — re-exports `QueryParam`, `Url`, `UrlMatchPattern`, `UrlMatchPatternList` as part of the public collection API surface.\n- `packages/insomnia-scripting-environment/src/objects/insomnia.ts` — uses `resolveProtocolForProxy` and `toUrlObject` in `initInsomniaObject`.\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/utils.md (Tokens: ~458 | Category: anthropic-skill) -->\n# Utils (misc helpers)\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/utils.ts`\n\n## Purpose\nSingle-function utility module. Currently contains only a helper used to detect whether a request\nURL still contains unresolved template-tag syntax, which affects client-certificate matching in\n`insomnia.ts`.\n\n## Public API\n\n### `function checkIfUrlIncludesTag(url: string): boolean`\n```ts\nexport function checkIfUrlIncludesTag(url: string): boolean {\n  return /{%/.test(`${url}`) || /%}/.test(`${url}`) || /{{/.test(`${url}`) || /}}/.test(`${url}`);\n}\n```\nReturns `true` if the given `url` (coerced to a string via template literal) contains any of the\nfour Liquid/interpolation delimiter substrings: `{%`, `%}`, `{{`, `}}`. Used as a proxy for \"this\nURL hasn't been fully rendered yet\" / \"this URL has dynamic template parts that can't be resolved\nagainst a fixed hostname\".\n\n## Script-facing surface\nNone. This is internal plumbing — it is not exposed on `insomnia`/`pm` and scripts cannot call it.\n\n## Gotchas / notable behavior\n- The check is purely textual (four independent regex tests, not a real template parser) — it will\n  return `true` for a URL that merely contains a literal `{{` or `}}` substring even if it isn't\n  actually meant as an interpolation tag.\n- `` `${url}` `` coercion means passing `undefined`/`null`/non-string values won't throw; they'll be\n  stringified first (e.g. `undefined` becomes the string `\"undefined\"`, which does not match any\n  pattern, so the function returns `false`).\n\n## Related\n- `insomnia.ts` — the only current caller. `initInsomniaObject` uses `checkIfUrlIncludesTag(rawObj.request.url)` together with `filterClientCertificates` (from `insomnia/src/network/certificate`) to decide whether to fall back to an empty default client certificate (since a templated URL's real host can't be matched against certificate rules yet).\n\n<!-- File: .claude/skills/fix-scripting-feature/references/objects/variables.md (Tokens: ~1558 | Category: anthropic-skill) -->\n# Variable / VariableList\n\n**Source:** `packages/insomnia-scripting-environment/src/objects/variables.ts`\n\n## Purpose\nThis file defines the low-level key/value model — `Variable` (a single named, typed value) and `VariableList` (an ordered, keyed collection of `Variable`s) — built on top of `Property`/`PropertyList` from `properties.ts`. Unlike `Environment` (a `Map`-backed flat store used for `pm.environment`/`pm.globals`), `Variable`/`VariableList` is the object-per-entry model used elsewhere in the SDK wherever a Postman-compatible \"list of variable-shaped items\" is needed (e.g. request path parameters); it is not the type behind `insomnia.environment` or `insomnia.variables`.\n\n## Public API\n\n### `interface VariableDefinition`\n```ts\ninterface VariableDefinition {\n  id?: string;\n  key: string;\n  name?: string;\n  value: string;\n  type?: string;\n  disabled?: boolean;\n}\n```\nShape used to construct a `Variable`.\n\n### `class Variable extends Property`\n```ts\nconstructor(def?: VariableDefinition)\n```\nCalls `super()` (no args passed to `Property`'s constructor, so `Property`'s own `id`/`name`/`disabled` defaults are set first, then overwritten below). When `def` is provided: `id = def.id || ''`, `key = def.key`, `name = def.name`, `value = def.value`, `type = def.type || 'Variable'`, `disabled = def.disabled`. When `def` is omitted: `id = ''`, `key = ''`, `name = undefined`, `value = ''`, `type = 'Variable'`, `disabled = false`.\n\n- `key: string` — the variable's lookup key.\n- `value: any` — the stored value (any type, despite `VariableDefinition.value` being typed `string`).\n- `type: string` — defaults to `'Variable'`.\n- `override _kind = 'Variable'` *(`@ignore`)*.\n- `static override _index = 'key'` *(`@ignore`)* — tells `PropertyList`/`indexOf`/`one` to index `Variable`s by `key` instead of the default `id`.\n- `static types()` — **throws** `unsupportedError('types')` unconditionally; not implemented.\n- `cast(value: any)` — if `value` has `_kind === 'Variable'`, returns `value.value` (unwraps a `Variable` to its raw value); otherwise returns `undefined`.\n- `get()` — returns `this.value`.\n- `set(value: any)` — sets `this.value = value`.\n\n### `class VariableList<T extends Variable> extends PropertyList<T>`\n```ts\nconstructor(parent: PropertyList<T> | undefined, populate: T[])\n```\nCalls `super(Variable, undefined, populate)` (always uses `Variable` as `typeClass`, ignoring `T`'s actual class for indexing purposes), then sets `this.parent = parent`.\n\n- `override _kind = 'VariableList'` *(`@ignore`)*.\n- `static isVariableList(obj: any): boolean` — `'_kind' in obj && obj._kind === 'VariableList'`.\n- `override toObject(excludeDisabled?: boolean, _caseSensitive?: boolean, multiValue?: boolean, _sanitizeKeys?: boolean): Record<string, any>` — builds a plain object keyed by each `Variable.key`:\n  - skips entries where `excludeDisabled && variable.disabled`.\n  - if `multiValue` is true and the key already exists in the output, coalesces values into an array (`[existing, value]`, growing it on further duplicates).\n  - otherwise, later entries with the same key overwrite earlier ones (last-write-wins) when `multiValue` is falsy.\n  - `_caseSensitive` and `_sanitizeKeys` are accepted for signature compatibility but unused.\n\nAll other list operations (`add`, `all`, `append`, `assimilate`, `clear`, `count`, `each`, `filter`, `find`, `get`, `has`, `idx`, `indexOf`, `insert`, `insertAfter`, `map`, `one`, `populate`, `prepend`, `reduce`, `remove`, `repopulate`, `toString`, `upsert`) are inherited unmodified from `PropertyList` — see `properties.md`. Because `Variable._index = 'key'`, `indexOf`/`one`/`upsert`/`has` on a `VariableList` match by `key`, not `id`.\n\n## Script-facing surface\nNot directly exposed as `pm.*`/`insomnia.*` on its own — `insomnia.environment` and `insomnia.variables` are backed by `Environment`/`Variables` (see `environments.md`), not by `Variable`/`VariableList`. `Variable`/`VariableList` are the generic building blocks other collection-shaped properties reuse (e.g. anywhere the SDK needs an ordered, keyed set of `{key, value}` items with Postman-style list semantics such as `.upsert()`/`.toObject()`). Exported from the package's public surface via `index.ts` (`export { Variable, VariableList } from './variables'`) and re-exported under the `Collection` namespace via `collection.ts`, so consumers of the SDK's type declarations can reference `Collection.Variable`/`Collection.VariableList`.\n\n## Gotchas / notable behavior\n- `Variable.types()` is a stub that always throws `unsupportedError('types')` — calling it will break a script; it exists only for API-shape completeness.\n- `Variable.cast(value)` assumes `value` is an object with `in` support (`'_kind' in value`) — passing a primitive (e.g. `cast(5)`) will throw a `TypeError` since `in` requires an object operand on the right-hand side. There's no type guard before that check.\n- `VariableList`'s constructor hardcodes `Variable` as the `typeClass` regardless of the generic `T`, so subclasses of `Variable` (if any) still get indexed as plain `Variable` for `_index` purposes.\n- `VariableList.toObject()` **includes disabled variables by default** — callers must explicitly pass `excludeDisabled = true` to filter them out (confirmed by `variables.test.ts`: `toObject()` returns `{h1: 'v1', h2: 'v2'}` even when `h2` is disabled).\n- Duplicate keys: `toObject()` silently drops earlier duplicates unless `multiValue` is passed, in which case duplicates become an array under a single key — this can be surprising if a script assumes object-key uniqueness maps 1:1 to list length.\n\n## Related\n- `properties.ts` — `Variable extends Property`, `VariableList extends PropertyList<T>`; inherits nearly all list/property behavior from there.\n- `environments.ts` — conceptually parallel/alternate model (`Environment`/`Variables`) that is what `insomnia.environment`/`insomnia.variables`/`insomnia.collectionVariables` actually use; do not confuse the two when debugging variable-resolution issues.\n- `index.ts` / `collection.ts` — re-export `Variable`/`VariableList` as part of the SDK's public/namespaced surface.\n\n<!-- File: .claude/skills/fix-scripting-feature/SKILL.md (Tokens: ~2734 | Category: anthropic-skill) -->\n---\nname: fix-scripting-feature\ndescription: 'Debug and fix issues in Insomnia''s pre-request/after-response/test scripting feature (the pm/insomnia API surface). Use when scripts throw sandbox violations, timeouts, module import errors, or produce wrong request/response/variable mutations.'\nargument-hint: 'Provide the failing script snippet, the error message, and whether it happened in the app (hidden window) or inso (CLI)'\n---\n\n# Fix Scripting Feature Issues\n\n## When to Use\n\n- A pre-request script, after-response script, or test script (`pm.*` / `insomnia.*` API) fails, throws, or produces incorrect results.\n- Errors like `SECURITY_POLICY_VIOLATION`, `no module is found for \"...\"`, `Timeout: Running script took too long`, or `insomnia object is invalid or script returns earlier than expected.`\n- `insomnia-scripting-environment` SDK objects (`Request`, `Response`, `Environment`, `Variables`, `Collection`, `test()`) behave unexpectedly.\n\n## Architecture (read this first)\n\nPre-request/after-response/test scripts (the Postman-compatible `pm`/`insomnia` API) have **three execution paths** depending on where the script runs:\n\n| Path | Entry point | Sandboxing |\n|---|---|---|\n| Electron app (default) | `packages/insomnia/src/entry.hidden-window.ts` → `packages/insomnia/src/scripting/run-script.ts`, run in a hidden `BrowserWindow` (see `packages/insomnia/src/main/window-utils.ts`) | AST-based via `packages/insomnia/src/scripting/sandbox.ts` + `script-security-rules.ts` + `require-interceptor.ts`, toggled by `context.settings.scriptSandboxEnabled` (default on) |\n| Electron app, opt-in QuickJS sandbox | `packages/insomnia/src/network/concurrency.renderer.ts` → `packages/insomnia/src/scripting/run-script-quickjs.ts`, run in a Web Worker over a WASM QuickJS VM (`quickjs-script-engine.ts`) | Gated by `settings.useQuickJsScriptSandbox` (default off). PoC with a **deliberately minimal, hand-rolled `insomnia`/`$` API** — not the shared SDK below — with no `test()`/`pm.test()`, `collectionVariables`, vault, cookies, client certificates, or request mutation, and a `sendRequest()` limited to plain-text bodies |\n| `inso` CLI and other Node-runtime callers (e.g. `packages/insomnia/src/runtimes/network/network-adapter.node.ts`) | `packages/insomnia/src/script-executor.ts` (plain `AsyncFunction`/`eval`, no Electron) | None — `script-executor.ts` never calls `sandbox.ts`'s `prepareSandbox`, it's a structurally separate implementation. (Don't confuse this with `canSandbox = !!process.type` in `network-adapter.node.ts` — that gates **plugin** request/response hook sandboxing, an unrelated subsystem.) |\n\nThe full SDK object model (`InsomniaObject`, `Request`, `Response`, `Environment`, `Variables`, `Collection`, `Test`, console/send-request shims) lives in `packages/insomnia-scripting-environment/src/objects/` and is shared across the Electron (default) and `inso`/Node paths only — the QuickJS sandbox does not use it.\n\n## `insomnia-scripting-environment` Folder Hierarchy\n\nThe SDK package (`packages/insomnia-scripting-environment/`) is where the `pm`/`insomnia` object model lives — start here for any bug about a specific script API's behavior:\n\n```\ninsomnia-scripting-environment/\n├── src/\n│   ├── objects/                   # the pm/insomnia API surface — one file per SDK concept\n│   │   ├── index.ts               # public re-exports\n│   │   ├── insomnia.ts            # InsomniaObject — the top-level `insomnia`/`pm` instance scripts see\n│   │   ├── interfaces.ts          # RequestContext, IEnvironment — the context shape passed in/out of a script run\n│   │   ├── execution.ts           # Execution — pm.execution (setNextRequest, skipRequest, location)\n│   │   ├── request.ts             # Request, RequestBody — pm.request\n│   │   ├── request-info.ts        # RequestInfo — pm.info (event name, iteration data)\n│   │   ├── response.ts            # Response — pm.response\n│   │   ├── send-request.ts        # pm.sendRequest() implementation\n│   │   ├── environments.ts        # Environment, Variables — pm.environment / pm.globals\n│   │   ├── variables.ts           # Variable, VariableList — underlying key/value model shared by environments/collection\n│   │   ├── collection.ts          # Collection — pm.collectionVariables / folder & collection variable resolution\n│   │   ├── folders.ts             # Folder, ParentFolders — collection folder hierarchy walked for variable resolution\n│   │   ├── properties.ts          # PropertyBase, PropertyList — base classes most SDK objects extend\n│   │   ├── headers.ts             # Header, HeaderList\n│   │   ├── cookies.ts             # Cookie, CookieJar, CookieList — pm.cookies\n│   │   ├── auth.ts                # RequestAuth — pm.request.auth\n│   │   ├── certificates.ts        # Certificate — client cert modeling\n│   │   ├── proxy-configs.ts       # ProxyConfig, ProxyConfigList\n│   │   ├── urls.ts                # Url, QueryParam, UrlMatchPattern — URL parsing/manipulation\n│   │   ├── console.ts             # Console — console.log capture surfaced back to the app\n│   │   ├── test.ts                # pm.test()/test() handler registration + TestHandler, waitForAllTestsDone\n│   │   ├── async-objects.ts       # ProxiedPromise — Promise plumbing so async script code can be awaited by the host\n│   │   ├── interpolator.ts        # template-tag ({{ }}) interpolation used when resolving variable values\n│   │   ├── utils.ts               # misc helpers (e.g. checkIfUrlIncludesTag)\n│   │   └── __tests__/             # tests for selected objects above (request.test.ts, response.test.ts, etc.)\n│   └── autocomplete-snippets.json # generated editor autocomplete data — see scripts/generate-autocomplete.ts\n├── scripts/\n│   └── generate-autocomplete.ts   # regenerates autocomplete-snippets.json from the objects/ source (CI-checked)\n├── docs/                          # TypeDoc-generated API reference (generated from objects/, don't hand-edit)\n├── typedoc.json                   # TypeDoc config for docs/ generation\n└── vitest.config.ts               # test runner config for `npm test -w insomnia-scripting-environment`\n```\n\nIf a bug is about *how* a script executes (sandboxing, timeouts, hidden window vs. `inso`), look in `packages/insomnia/src/scripting/` (see Architecture above). If it's about *what a specific API returns or mutates* (`pm.request.headers`, `pm.environment.get()`, `pm.response.json()`, etc.), it's almost always in this package's `src/objects/`.\n\nFor property/method-level detail on any single object above (real signatures, script-facing surface, gotchas), see `references/objects/<name>.md` in this skill folder — one file per source file in `src/objects/` (e.g. `references/objects/request.md` for `request.ts`, `references/objects/environments.md` for `environments.ts`).\n\n## Procedure\n\n1. **Identify the execution path first** — ask/check whether the failure is in the Electron app or `inso`. Fixes and even error messages differ per path (see table above).\n2. **Reproduce with the narrowest test** before touching app code:\n   - Unit tests for the sandbox/security logic:\n     ```bash\n     npm test -w insomnia -- src/scripting\n     ```\n     (not `npx vitest run src/scripting -w insomnia` — Vitest's own CLI already owns `-w`/`--watch`, so that form drops into watch mode instead of selecting the `insomnia` workspace.)\n     Relevant files: `packages/insomnia/src/scripting/__tests__/sandbox.test.ts`, `__tests__/script-security-policy.test.ts`, `__tests__/require-interceptor.test.ts`.\n   - SDK object-model unit tests:\n     ```bash\n     npm test -w insomnia-scripting-environment\n     ```\n   - Full app unit suite (if narrowing further isn't obvious):\n     ```bash\n     npm test -w insomnia\n     ```\n3. **If the bug only shows up end-to-end** (real app, real request lifecycle), run the Playwright smoke tests:\n   ```bash\n   npm run test:dev -w insomnia-smoke-test -- --grep \"pre-request\"\n   npm run test:dev -w insomnia-smoke-test -- --grep \"after-response\"\n   ```\n   Relevant specs in `packages/insomnia-smoke-test/tests/smoke/`: `pre-request-script-features.test.ts`, `after-response-script-features.test.ts`, `pre-request-script-window.test.ts`.\n4. **Locate the fix by symptom** (see Notes below for the mapping from error message to file).\n5. **If you change the SDK's public API surface** (new/changed methods on `Request`/`Response`/`Environment`/etc. in `insomnia-scripting-environment`), regenerate autocomplete snippets — CI checks this is committed:\n   ```bash\n   npm run generate:autocomplete -w insomnia-scripting-environment\n   ```\n\n## Notes\n\n- Common failure patterns and where to look:\n  - `SECURITY_POLICY_VIOLATION` / script blocked for using `this`, `globalThis`, `__proto__`, `constructor`, or `import` → AST rule in `packages/insomnia/src/scripting/sandbox.ts` (`checkSandboxViolations`) or the rule lists in `script-security-rules.ts`. These are Electron-app-only; `inso` has no AST sandbox at all.\n  - `no module is found for \"...\"` → the module isn't on the allowlist in `packages/insomnia/src/scripting/require-interceptor.ts`. Currently allowed:\n    - Full-access Node builtins: `path`, `assert`, `url`, `punycode`, `querystring`, `string_decoder`, `stream`, `events`\n    - Method-restricted Node builtins (some methods throw): `timers` (`setImmediate` blocked), `buffer` (`allocUnsafe`/`allocUnsafeSlow` blocked), `util` (`inherits`/`debuglog` blocked)\n    - Shims: `atob`, `btoa`\n    - External/npm modules: `ajv`, `chai`, `cheerio`, `crypto-js`, `csv-parse/lib/sync` (note: not bare `csv-parse`), `lodash` (backed by `es-toolkit/compat`), `moment`, `tv4`, `uuid`, `xml2js`\n    - Special-cased: `insomnia-collection` / `postman-collection` (resolves to the SDK's own `Collection` module)\n\n    Adding a module means adding it here, not just installing the npm package.\n  - `Timeout: Running script took too long` (Electron app) → default 5000ms in `entry.hidden-window.ts:39` (`data.context.timeout`).\n  - `insomnia object is invalid or script returns earlier than expected.` → thrown when the wrapped `AsyncFunction` doesn't resolve to an `InsomniaObject` instance; check both `run-script.ts` and `script-executor.ts` since the same invariant is duplicated in each path.\n  - Hidden window \"closed unexpectedly\" / \"froze\" / busy errors → restart/health-check race conditions in `packages/insomnia/src/main/window-utils.ts` (`hiddenWindowIsBusy`, `createHiddenBrowserWindow`).\n  - Works in unit tests but not in CI → confirm `npm run generate:autocomplete -w insomnia-scripting-environment` output matches the committed `autocomplete-snippets.json`; `test.yml` diffs this file and fails the build if it's stale.\n- The script behavior should be as consistent as possible with [the original implementation](https://www.postmanlabs.com/postman-collection/tutorial-concepts.html). The implementation is hosted in this [repo](https://github.com/postmanlabs/postman-collection).\n- CI workflows relevant to this feature: `.github/workflows/test.yml` (unit tests + autocomplete snippet check, runs on PR/push to develop), `.github/workflows/test-e2e.yml` (Playwright smoke tests, same triggers).\n- Success criteria: the targeted vitest/Playwright command passes, and if the SDK surface changed, the autocomplete snippet diff is clean.\n\n<!-- File: .claude/skills/fix-test-cli-ci/SKILL.md (Tokens: ~617 | Category: anthropic-skill) -->\n---\nname: fix-test-cli-ci\ndescription: 'Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.'\nargument-hint: 'Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso'\n---\n\n# Fix test-cli.yml CI Failures\n\n## When to Use\n\n- `.github/workflows/test-cli.yml` or the `Test CLI` workflow failed in CI.\n- `inso` bundle tests fail locally or in GitHub Actions.\n- You suspect node module/runtime differences between Node.js and Electron.\n\n## Procedure\n\n1. Start with the failing CI evidence.\n   - Use the provided logs or workflow run link to identify whether the failure happened during the `test:bundle` run, the packaged binary run, or the build/package setup before the tests.\n   - Note the failing test name and the first actionable error message before reproducing locally.\n2. Ensure Node.js native dependencies are installed (not Electron-targeted variants):\n   ```bash\n   npm run install-libcurl-node\n   ```\n3. Build `insomnia-inso` to generate `dist`:\n   ```bash\n   npm run build -w insomnia-inso\n   ```\n4. Start the smoke-test echo server used by the CLI tests:\n   ```bash\n   npm run serve -w insomnia-smoke-test\n   ```\n5. In another terminal, run the bundled CLI test suite:\n   ```bash\n   npm run test:bundle -w insomnia-inso\n   ```\n6. If the CI failure was in the packaged binary path or the bundle suite passes locally, run:\n   ```bash\n   npm run test:binary -w insomnia-inso\n   ```\n\n## Notes\n\n- Keep the smoke-test server running while bundle tests execute.\n- Common failure patterns:\n  - Errors involving `@getinsomnia/node-libcurl`, `NODE_MODULE_VERSION`, `dlopen`, or native module loading usually mean the Node-targeted libcurl binary needs to be reinstalled with `npm run install-libcurl-node`.\n  - Connection failures to `http://localhost:4010` usually mean the smoke-test server is not running.\n  - Missing `dist` output or missing `binaries/inso` usually means `npm run build -w insomnia-inso` or the package step needs to be rerun.\n- Success criteria:\n  - `npm run test:bundle -w insomnia-inso` exits successfully and Vitest reports the `inso dev bundle` tests as passing.\n  - If validating the packaged binary path, `npm run test:binary -w insomnia-inso` also exits successfully.\n\n## Teardown\n\n- Stop the `npm run serve -w insomnia-smoke-test` process once validation is complete.\n- Reinstall the electron-targeted libcurl binary for continued Electron development:\n  ```bash\n  npm run install-libcurl-electron\n  ```\n\n<!-- ... +10 more modular skills available via /api/skills/Kong/insomnia?path={path} -->\n</yakaai_skills>"}