npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zyntax/extension-sdk

v1.0.32

Published

Canonical public contracts and authoring helpers for Zyntax extensions.

Readme

Zyntax Extension SDK

@zyntax/extension-sdk is the canonical public contract for executable Zyntax extension providers. It contains plain-data manifest and RPC types, canonical capability names, and small authoring helpers. It has no editor, WebView, filesystem, network, native-command, or app-private dependencies.

Extension manifests distinguish required dependencies from integrations. Required dependencies are installed with the extension and must activate. Integrations authorize composition with compatible optional packages. They are never installed implicitly and never block activation. extensions.manage can request a user-reviewed install of a declared integration. Both arrays are explicit.

Provider code is bundled by the Zyntax extension tooling into a self-contained providers/*.js module. Import only this package for host DTOs and provider helpers; do not import application source or tooling internals.

Tooling consumes the provider-methods, manifest-constants, and capabilities subpaths so manifest validation and provider bundle validation share one public vocabulary.

EXTENSION_HOST_API_METHODS and EXTENSION_HOST_API_INTERACTIVE_METHODS define the host method vocabulary and which calls wait for user interaction or observed work. The build exports runtime-contract.json from these constants and the provider methods for non-JavaScript hosts. Host adapters consume this artifact instead of maintaining separate method lists. Provider method types are generated from the same TypeScript source as their runtime values.

import {
  EXTENSION_API_VERSION,
  defineCompletionProvider,
  type ExtensionCompletionProvider,
} from "@zyntax/extension-sdk";

export const extensionApiVersion = EXTENSION_API_VERSION;

export const createCompletionProvider = defineCompletionProvider(
  (): ExtensionCompletionProvider => ({
    async provideCompletions(request, cancellation) {
      await cancellation.checkpoint();
      return null;
    },
    dispose() {},
  }),
);

Manifest validation remains host/tooling-owned. The SDK does not grant permissions or platform access.

App variants

An optional signed manifest field limits an extension to specific app editions:

"appVariants": ["full", "dev"]

Omit appVariants to support all editions. When present, it must be a nonempty list of unique lite, full, or dev values; order does not matter. Dev is an explicit edition, not a bypass. The host checks compatibility when installing and activating packages, including required dependencies. The existing package signature protects this plain JSON field; it is not an encrypted licensing system. Tool/runtime requirements and engines.zyntax remain separate checks.

Theme scope selectors

parseTextMateSelector(value) validates a nonempty, trimmed selector and returns an immutable TextMateSelector AST; invalid or unsupported syntax throws TypeError. It has no editor or matching dependency. This bounded subset of TextMate scope selectors supports descendant paths, direct-parent >, wildcard scope names, parentheses, unary exclusion -, intersection &, alternatives | and comma, and binary subtraction. Hyphens inside scope names remain literal: a-b is one name, while a -b subtracts b.

In host matching, a whole dot-separated * atom matches one scope atom; normal prefix specialization can still match further atoms. For example, source.* matches source.js and source.js.embedded, but not source. Asterisks embedded inside an atom remain literal, not a general glob expression. The parser retains the existing scope-name vocabulary without rewriting it.

Paths bind first. Inside a group or selector, |, &, and binary - bind equally and associate left to right, as in TextMate; comma is a lower-precedence alternative. Thus a | b - c means (a | b) - c. Adjacent scope names form a path; separate grouped expressions require an operator. Anchors (^, $), side/injection filters (L:, R:, B:), and unsupported characters are rejected rather than ignored. Only spaces and tabs are internal whitespace.

The AST uses path nodes with scopes and between-scope relations (descendant or child), any/all nodes with terms, and not nodes with a term. Subtraction is all(left, not(right)); groups retain their meaning without a wrapper node. Host matchers evaluate exclusions against the same scope stack as positive paths, not against a fallback stack after a miss. Only matching positive paths contribute specificity; negated conditions filter matches. The SDK defines syntax, not a rendering engine or VS Code theme-rendering equivalence.

TEXTMATE_SELECTOR_RULES limits input to 512 UTF-16 code units, 24 total scope names plus child operators, 64 emitted AST nodes, and nesting depth 8. A group or unary minus increases nesting by one from zero; binary operators do not. Paths, binary combinations, and each unary or subtraction negation count as nodes; parentheses do not. Existing ASCII scope-name syntax is preserved in scopePattern. The build exports these rules as textMateSelectors in runtime-contract.json. JavaScript and native validators consume the same textmate-selector-conformance.json fixture, including boundary and AST cases. Unscoped theme defaults use the asset's empty scope list, not an empty selector.

Cooperative cancellation

Every provider cancellation token exposes checkpoint(). Awaiting it yields isolated provider execution to the host event queue so queued cancellation and control work can run, then rejects with the same host cancellation error as throwIfCancellationRequested() when cancellation is requested. CPU-bound provider work must await checkpoints at safe intervals; reading isCancellationRequested or calling throwIfCancellationRequested() alone does not yield execution to the host.

Providers must not publish partial state from work interrupted by a rejected checkpoint. The synchronous token members remain useful before and after already-asynchronous boundaries, while checkpoint() is the cooperative boundary for long-running isolated work.

Language filename associations

A contributed editor language always declares extensions and may also declare exact filenames or basename-only filenamePrefixes. Prefix matching is case-insensitive. Each prefix must be non-empty and contain no / or \ path separator; the host canonicalizes prefixes before matching.

When several associations match, the host chooses an exact filename first, then the longest matching filename prefix, then the longest matching compound extension. This lets a language claim a filename family such as Dockerfile.* without enumerating every basename.

{
  "contributes": {
    "languages": [{
      "id": "dockerfile",
      "extensions": [],
      "filenames": ["Dockerfile"],
      "filenamePrefixes": ["Dockerfile."]
    }]
  }
}

Notebook file associations

Every contributed notebook type explicitly declares both extensions and filenames. At least one of those arrays must be non-empty. Extensions are canonical lowercase suffixes that include the leading dot, such as .ipynb; filenames are exact basenames without path separators. Matching is case-insensitive, including compound extensions.

These signed associations let the host offer a serializer only for files it claims. The host also enforces the association when deserializing and serializing, so a notebook provider cannot be selected for an unrelated file. Notebook types do not create editor-language identities, and the host does not infer associations from a type name or inspect file contents to choose one.

{
  "contributes": {
    "notebookTypes": [{
      "id": "jupyter-notebook",
      "type": "jupyter-notebook",
      "label": "Jupyter Notebook",
      "module": "providers/jupyter-notebook.js",
      "export": "createJupyterNotebookSerializerProvider",
      "extensions": [".ipynb"],
      "filenames": [],
      "priority": 100
    }]
  }
}

Bundled package lifecycle

An extension extension.json or managed-tool tool.json may declare the optional top-level field "required": true. The field has authority only when that exact package id is shipped in the APK's immutable package bundle. Such a package remains visible and updateable through the normal package pipeline, but the host does not allow its removal. Missing or false means removable. A Store or file-installed package cannot make itself non-removable by declaring the field, and an update cannot change the APK-owned lifecycle policy.

Bundling itself is established by placing a signed .zntx or .ztool in the APK bundle directory; it is not another manifest field or a separate package format. Managed-tool schema and archive validation remain owned by the managed tooling, which applies the same optional boolean contract to tool.json.

Managed-tool package paths use exact case-sensitive POSIX identity on Android. isManagedToolPath checks the shared path syntax: relative forward-slash paths, printable ASCII, at most MANAGED_TOOL_MAX_PATH_BYTES (384) bytes overall and MANAGED_TOOL_MAX_PATH_SEGMENT_BYTES (96) bytes per segment. Empty, . and .. segments, surrounding spaces, trailing dots, and <>:"\\|?* are rejected. Paths are never lowercased or rewritten. payload/include/Name.h and payload/include/name.h are distinct files, as are case-distinct directories. Payloads containing case-distinct names require a case-sensitive build filesystem, such as a Linux container, so staging preserves both names.

Builders and installers still reject exact duplicate paths, file/directory/link conflicts, unsafe paths, and links outside the declared package graph. Archive metadata remains at the package root; tool files and resources remain under payload/. This does not change extension archive paths or manifest version 1.

Notebook kernels

A notebook-kernel provider returns only a symbolic runtime descriptor. Its executable names one manifest runtime requirement and one provider-neutral command. Literal arguments and signed managed-tool resource references are allowed; native paths, environment values, and fallback commands are not. The host resolves the currently selected compatible runtime and signed resources, then owns the adapter process and its full-duplex framed JSON-RPC session. The public protocol defines initialization, execution, strictly ordered status/output/comm events, kernel-to-host input requests, explicit interrupt and restart generations, cancellation, and idempotent shutdown. Output events are authoritative and are not duplicated in the final execution result. Event sequence numbers increase across the complete adapter session, including kernel restarts. A restart response commits the new generation before the adapter emits any event for that generation.

Kernel adapters remain runtime-neutral. They must not expose native paths, process handles, transport sockets, or renderer objects, and the host never infers a runtime from a notebook language or file name.

const descriptor = {
  kind: "runtime" as const,
  executable: {
    requirement: "kernel-runtime",
    command: "runtime",
  },
  args: [{
    $toolResource: {
      tool: "publisher.kernel-adapter",
      resource: "adapter",
    },
  }],
  protocol: "zyntax-notebook-jsonrpc" as const,
};

Runtime Manager

Runtime Manager owns development runtimes and their activation in each project. The user selects a device default for each stable family such as python, node, java, or rust; a shared project selection can override it. Each extension runtime requirement explicitly supports the selected runtime, one exact extension-managed runtime, or both. The host never searches terminal PATH.

A consumer declares runtimeRequirements. The runtimes.execute permission authorizes runtime execution and the project-scoped snapshot, select and observe host methods. The three exact source forms are:

  • { "selected": true } uses only a compatible project selection or inherited device default.
  • { "managed": { "tool": "publisher.runtime-tool" } } uses only that exact owned managed-tool dependency.
  • { "selected": true, "managed": { "tool": "publisher.runtime-tool" } } uses an explicit project selection when present. Without a project override, it uses a compatible device default first and the managed runtime when no usable default is available.

For requirements allowing selected runtimes, an explicit project selection that becomes missing, changed, incompatible or unverifiable blocks new execution until it is replaced or cleared; it never switches to a managed fallback. The declared fallback for an unusable device default does not change the shared selection, and the default's failure remains diagnosable even when managed execution can continue.

SDK 1.0.31 adds host["runtimes.execute"]. Runtime selection requests name an explicit project and one manifest-local requirement; project: null means the current Explorer project, while a string names an owned selected project belonging to the active Explorer context. Stale or unrelated project references are rejected. The host keys shared selections by the active Explorer root's native identity and runtime family; selected subprojects share their parent Explorer project's choice. Extensions targeting that project observe the same choice even when their project references and requirement IDs differ.

snapshot({ project, requirement }, cancellation) returns { project, requirement, revision, candidates, selection }. Candidates contain compatible, selectable terminal-package runtimes and native-admitted existing installations. Extension-managed fallbacks remain host-resolved and cannot be selected through this API. The effective selection reuses the existing ready/unavailable runtime type. With no Explorer project, project: null can read the device default resolution.

select({ project, requirement, expectedRevision, identity }, cancellation) sets the shared project override to an exact identity from the snapshot. identity: null removes that override and resumes the declared default resolution. The requirement must declare sources.selected: true. Stale revisions, changed installations and unauthorized sources are rejected; with no Explorer project, project: null fails with projectRequired. Extensions cannot change the device default. Runtime resolution and command activation remain host-owned; runtime selection requests supply no executable paths or environment maps.

observe({ project, requirement, afterRevision }, cancellation) returns the current snapshot for zero, then waits for a newer revision without timer polling. Revisions are safe nonnegative integers; negative, unsafe and future revisions are rejected. Changes may coalesce into one current snapshot. Cancelling an observation leaves the selection and project work intact, and owner disposal cancels pending waits. snapshot and observe do not change the selection.

SDK 1.0.32 adds selectInstallation({ project, requirement, expectedRevision, installation }, cancellation). It admits an existing app-private installation and selects it atomically through the same project/family authority. The caller needs terminal, a requirement with sources.selected: true, the referenced path permissions and a trusted current project. It cannot write device defaults or register for another owner. No download, installation, copying or startup-file edit is performed.

const installation = {
  id: "chosen-runtime",
  label: "Selected runtime installation",
  capabilities: ["process.execute"],
  commands: [{
    id: "runtime",
    path: { kind: "selectedPath", file: selectedDirectory.id, path: ["bin", "runtime"] },
  }],
  versionProbe: { command: "runtime", args: ["--version"], stream: "stdout", prefix: "runtime " },
  activation: {
    environment: { TOOL_HOME: { kind: "selectedPath", file: selectedDirectory.id, path: [] } },
    path: [{ kind: "selectedPath", file: selectedDirectory.id, path: ["bin"] }],
  },
} satisfies ExtensionRuntimeInstallation;

The host verifies executable authority independently of picker grants, derives the version with the existing bounded probe and fingerprints the admitted launch graph. A grant to read a directory does not permit executing an external binary. The resulting source is { kind: "installed", owner, installationId } inside the existing opaque runtime identity. Other compatible extensions and the app can select it using ordinary select; there is no second custom-tool selection. Disabling/removing its owner or changing an admitted installation makes explicit selections unavailable. An owner-local installation ID must identify one intended installation; replacing its declaration never silently rebinds pinned identities.

Installation command IDs and capability arrays are sorted and unique; bash and sh remain reserved shell aliases. Existing provider limits apply: one to 32 commands, one to 32 base capabilities and at most 32 per command (64 in their union), at most 16 probe arguments of 512 control-free code units each, a nonempty probe prefix up to 160 code units and a label up to 120 code units. The complete declaration is limited to 64 KiB UTF-8 JSON. Activation metadata accepts only existing typed filesystem references, with the same environment/PATH bounds described below; it cannot depend on another selected runtime or store secrets.

Terminal-package descriptors additionally expose optional verified package: { repository, name } metadata. Match this provenance to declared development-stack requirements; never guess provider IDs or installation paths, or select a package solely because its version resembles the requested tool.

{
  "permissions": ["runtimes.execute"],
  "toolRequirements": [{
    "id": "zyntax.python-runtime",
    "version": ">=3.14.6 <4",
    "platforms": [{ "os": "android", "architecture": "arm64-v8a" }],
    "capabilities": ["runtime.python"]
  }],
  "runtimeRequirements": [{
    "id": "python",
    "runtime": "python",
    "minimumVersion": "3.12.0",
    "capabilities": ["process.execute"],
    "sources": {
      "selected": true,
      "managed": { "tool": "zyntax.python-runtime" }
    }
  }]
}

The managed tool must name an entry in the same manifest's toolRequirements. Its signed release must be a runtime tool whose projectRuntime family, version, and capabilities satisfy the requirement. Runtime executions continue to declare only { requirement, command }; for a managed runtime, the host resolves that command through the signed projectRuntime.commands mapping. Extensions never duplicate its entrypoint, runtime root, executable path, or environment.

Runtime inventory, verification, selection, and execution are host-owned. A declarative contributes.runtimeProviders entry may describe how a signed terminal-package repository exposes an installed runtime; it does not inspect packages or execute code. The host verifies the exact owning package and prefix-relative command files, performs the bounded version probe, and then adds the candidate to Runtime Manager. It never searches PATH.

{
  "permissions": ["terminal.packages"],
  "contributes": {
    "runtimeProviders": [{
      "id": "termux.node",
      "runtime": "node",
      "label": "Terminal Node.js",
      "capabilities": ["process.execute"],
      "package": { "repository": "termux-main", "name": "nodejs" },
      "commands": [
        { "id": "node", "path": "bin/node" },
        {
          "id": "npm",
          "path": "lib/node_modules/npm/bin/npm-cli.js",
          "package": { "repository": "termux-main", "name": "npm" },
          "capabilities": ["package-manager.npm"]
        }
      ],
      "versionProbe": {
        "command": "node",
        "args": ["--version"],
        "stream": "stdout",
        "prefix": "v"
      }
    }]
  }
}

The top-level package is the required primary owner. Commands use that owner unless they declare an optional companion package. Companion commands and their sorted, unique capabilities appear only while that package owns the declared file. An owned command may be an ELF or a bounded shebang script. A script is exposed only when its interpreter resolves to another exact command declared by the same provider; the host materializes that interpreter and the script path without consulting ambient PATH, and rejects missing or cyclic interpreter chains. The version probe must use a primary-owned command, so a missing companion never removes the runtime itself. Managed .ztool runtimes instead declare the runtime they actually contain; they do not act as package-provider registries. Selected and managed runtimes share the same provider-neutral symbolic commands; no provider can declare an absolute native path, shell fragment, or inferred fallback command.

SDK 1.0.32 adds optional provider activation metadata, selected atomically with that provider's commands. environment maps nonreserved keys to { path, package? }; path is an ordered list of the same references. Each path is terminal-prefix-relative and must remain owned by the primary package or the explicit companion package. For example, a Java provider can declare JAVA_HOME: { path: "lib/jvm/java-21-openjdk" } and a PATH directory { path: "lib/jvm/java-21-openjdk/bin" }. This keeps command and location selection under the same authority without tool-specific host logic.

Shared project activation

SDK 1.0.32 also adds persistent project activation through the existing runtimes.execute permission and runtime manager revision. Declare runtimeActivation: { environment: ["TOOL_HOME", "BUILD_MODE"], commands: ["build"] } and both runtimes.execute and terminal permissions. Runtime requirements are not needed for an activation-only extension. Empty declaration arrays permit a PATH-only contribution. Environment and command names must be unique; aliases match [a-z0-9][a-z0-9._-]{0,63}, excluding the reserved bash and sh routes.

  • activationSnapshot({ project }, cancellation) returns { project, revision, activation }, containing only the calling extension's effective contribution, never another owner's selected-file references.
  • activationUpdate({ project, expectedRevision, activation }, cancellation) atomically replaces that owner's entire project contribution. activation: null removes its override and resumes inherited defaults; an empty definition is an explicit empty contribution. Other owners' state is not replaced.
  • activationObserve({ project, afterRevision }, cancellation) uses the same cancellable observation rules as observe; zero reads immediately.

Project scope has the same Explorer identity and selected-project checks as runtime selection. Writes require a trusted current project, and extensions cannot write device defaults. Without an Explorer project, project: null may read inherited defaults but updates fail with projectRequired.

An activation definition has three required fields:

const activation = {
  environment: {
    TOOL_HOME: { kind: "selectedPath", file: selectedDirectory.id, path: [] },
    BUILD_MODE: "release",
  },
  path: [{ kind: "selectedPath", file: selectedDirectory.id, path: ["bin"] }],
  commands: {
    build: {
      command: "bash",
      args: [{ kind: "extensionResource", path: "resources/build.bash" }, "build"],
    },
  },
} satisfies ExtensionRuntimeActivationDefinition;

Environment values and prefix argv reuse typed selectedPath, packagePath and extensionResource task bindings, or bounded literals. Persistent activation excludes projectPath and secret bindings. { kind: "installedPath", path: absolutePath } references an existing cached installation inside the shared terminal home or prefix. The host canonicalizes and revalidates it, rejecting controls, colons, traversal, symlink escapes and extension-private locations. Explicit custom locations outside those roots must use an approved selectedPath. These methods do not install, download, copy or relocate tools; existing caches remain in use.

Task environment/argv and activation environment/argv can also contain { kind: "runtimeEnvironment", requirement: "java", name: "JAVA_HOME" }. This read-only reference resolves a location declared in the selected provider's activation.environment. The requirement must belong to the caller and allow selected runtimes. Its project scope comes from the enclosing task or activation, and its value is captured with the same runtime graph as executable commands. It never reads ambient environment variables or exports a raw native path to the extension. Missing metadata or an unavailable selection fails explicitly. This keeps custom and packaged runtime commands and homes under one shared selection. managedTool tasks reject this binding just as they reject packagePath; it is also excluded from PATH lists and installation declarations to prevent recursive runtime dependencies.

There are at most 32 environment bindings (24 KiB resolved total), 16 PATH directories, 32 command aliases and 32 prefix arguments per alias. Environment literals retain the 512-code-unit, control-free task limit; command literals retain the 16 KiB UTF-8 per-literal and 64 KiB resolved-argv limits. Alias targets are bare terminal commands matching [A-Za-z0-9][A-Za-z0-9._+-]{0,127}, resolved natively before activation aliases are applied; caller argv follows the prefix. An extension can supply its signed launcher resource as an argument to native Bash, keeping tool-specific flags and configuration in the extension. The host does not generate launcher scripts or redirect explicit paths such as ./gradlew.

PATH is an ordered list of admitted directories, not a writable environment key. The task-input protected variables remain reserved: PATH, SHELL, PREFIX, HOME, TMPDIR, BASH_ENV, ENV, TERMUX_*, ZYNTAX_*, LD_* and Android substrate variables. Undeclared keys, conflicting owner contributions, stale revisions and invalidated references are rejected. Native launch admission revalidates enabled owners and resolves one immutable environment/PATH/command snapshot for new shells and terminal-domain tasks; running processes keep their captured activation. Consumers should observe this shared state instead of reapplying stale local copies of tool selections.

The exported isExtensionRuntimeActivationDeclaration, isExtensionRuntimeActivationDefinition, isExtensionRuntimeActivationPath and isExtensionRuntimeActivationEnvironmentKey guards share strict wire validation with host adapters. They do not grant filesystem authority or replace native ownership, permission, existence and resolved-size checks. isExtensionRuntimeInstallation and isExtensionRuntimeEnvironmentReference provide the corresponding structural guards for existing installations and symbolic runtime metadata references.

A runtime task references its manifest-local requirement and a provider-neutral command id. Its required console mode chooses an interactive PTY or captured output without changing runtime resolution:

const execution = {
  kind: "runtime" as const,
  requirement: "python",
  command: "python",
  args: ["main.py"],
  workingDirectory: [],
  console: "terminal" as const,
};

ExtensionRuntimeCommandReference is the same two-field executable reference for typed host process descriptors. Arbitrary provider configuration uses the reserved, explicit ExtensionRuntimeCommandConfigurationReference sentinel:

{
  "executable": {
    "$runtimeCommand": {
      "requirement": "language-runtime",
      "command": "runtime"
    }
  }
}

The host interprets only the tagged value; an ordinary JSON object containing requirement and command remains provider-owned JSON. The two strict validators check the exact path-free shapes. The host additionally verifies that requirement belongs to the installed manifest and that the selected provider exposes command.

Public runtime identities contain only an opaque installed-source identity, stable candidate id, and exact observation fingerprint. Native locations never cross the extension boundary. If an installation changes, its fingerprint changes and the affected selection becomes explicitly unavailable until a current candidate is selected.

Incremental structural documents

A structural-region provider owns incremental parser state for each source document that the host opens. openDocument receives an opaque, non-empty documentId and the only complete source-text snapshot transferred for that document generation. The provider retains the corresponding text and parser state.

The host serializes lifecycle calls for each documentId. applyDocumentChanges advances the open document from baseVersion to exactly baseVersion + 1 without changing its generation. Its changes are ordered, non-overlapping UTF-16 ranges in the base revision, and every range in the batch uses that same coordinate space. Lifecycle mutations commit atomically. The host does not cancel them during ordinary editing; cancellation is reserved for teardown. A generation change closes the old document and opens a new identity with a fresh source snapshot.

Each successful open or change acknowledges the exact documentId, version, and generation that was committed. provideRegion asynchronously returns the validated root-to-owner path for one position and association. provideRegionDocument asynchronously returns the complete region document when a document-wide consumer needs it. Both target an exact version and generation, and both results echo the documentId, source URI, version, generation, and provider id so the host can reject stale or cross-document output. The host cancels superseded queries and retains only the latest result; implementations must yield cooperatively and observe the cancellation token while preparing structure by awaiting cancellation.checkpoint() at safe intervals.

closeDocument is required and idempotent. It echoes the released documentId; disposing the provider releases every still-open document. The contract has no stateless provideRegions(snapshot) method or compatibility alias.

Language-server mappings declare only verified routes. Position routes are completion, hover, signatureHelp, definition, implementation, typeDefinition, references, rename, codeActions, typeHierarchy, and documentHighlights. Document/workspace routes are diagnostics, semanticTokens, workspaceSymbols, documentSymbols, foldingRanges, and documentFormatting. The host negotiates the matching standard LSP methods and routes parser-owned source or virtual coordinates; it never infers a claim from the server, language, filename, or installed package.

A language-server contribution may declare projectFiles, a non-empty list of at most EXTENSION_LSP_MAX_PROJECT_FILES (32) distinct project-relative file paths. The server is eligible when any listed path is an actual regular file in the open project. Matching is exact and case-sensitive, uses the current filesystem rather than unsaved editor buffers, and is re-evaluated when the project or those files change. No directory scan or glob matching is performed. Omitting projectFiles makes the server unconditional.

For evaluated project settings, declare configurationProvider: { module, export, watchFiles } on the language server instead of inline initializationOptions or workspaceConfiguration. Export a defineLanguageServerConfigurationProvider factory from the usual isolated provider module. Its provideLanguageServerConfiguration({ project, projectUri, serverId }, cancellation) returns the complete { initializationOptions?, workspaceConfiguration? } JSON. The host uses the server's granted project scope and existing cancellation/payload limits. Results do not grant resource access or executable authority.

watchFiles uses the same exact paths and non-empty 32-file limit as projectFiles. Creation, content changes and deletion invalidate only the affected server's configuration. The host cancels stale work and restarts that server with current document snapshots; it does not evaluate project code or poll on keystrokes. Malformed configuration fails explicitly; no old configuration is substituted. The provider kind languageServerConfiguration belongs to its server and has no independent activation event. Inline and provider configurations cannot be combined.

isExtensionProjectFilePath validates the shared path syntax. Paths use forward slashes and contain at most EXTENSION_LSP_MAX_PROJECT_FILE_LENGTH (384) UTF-16 code units. Unicode and interior spaces are allowed; absolute paths, empty, . or .. segments, surrounding segment whitespace, control characters, backslashes, colons, and glob characters (*?[]{}) are rejected. Conditions never authorize access outside the open project.

Eligible servers retain their declared language priority. At equal priority, a matching project-conditioned exclusive server takes precedence over an unconditional exclusive server. Equal priority and equal project specificity remain an explicit conflict; additive servers remain additive. Catalog default language recommendations consider only unconditional servers, since a catalog has no project context.

Declarative TextMate contributions reference exact UTF-8 JSON (syntaxes/*.tmLanguage.json) or XML plist (syntaxes/*.tmLanguage) assets. The host validates and loads the declared format directly; extensions do not convert or execute grammar assets. A primary grammar declares its sorted canonical languages bindings; one immutable asset may serve multiple identities when the upstream grammar is genuinely shared. Injection-only grammars omit languages.

The published package contains compiled ESM and declaration files in dist/. Extension builds never execute TypeScript source directly from node_modules.

The SDK also defines bounded plain-data contracts for inline completions, assisted edits, AI code actions, decorations, document colors, hover, tasks, terminal profiles, SCM state, and project templates. A task provider returns one reviewed execution plan. A managedTool plan selects a declared tool/entrypoint and requires tools.execute; a runtime plan selects a declared requirement/command and requires runtimes.execute; a command plan selects a bare command from the user's terminal environment and requires terminal. Every route explicitly selects terminal or captured console output, bounded arguments and contained working-directory segments. Arguments may include ExtensionTaskPath bindings. Optional process inputs bind environment values without exposing host routing variables. Package paths belong to declared installed stack requirements; signed resources/* assets belong to the exact extension generation. These references do not change execution domains: managed execution cannot acquire terminal-package paths or switch to terminal tools. The host resolves and reviews inputs before launch and owns cancellation. Terminal-profile and project-template providers likewise return descriptors or host-reviewed plans while the host owns process launch, filesystem writes, approvals, and transactions. Assisted-edit providers expose both bounded unary proposals and one ordered push stream. A streaming invocation emits progress or proposal events through the host-provided sink, observes the cancellation token, and completes once. The native transport owns ordering, byte/count limits, cancellation, and terminal delivery; providers never poll or open a second request.

Hover providers receive the exact parser-owned source region at the requested position and return only bounded plain-text or Markdown content with a snapshot-bound source range. The host sanitizes and renders that content in the same tooltip surface as LSP hover, suppresses the surface while completion is pending or active, and owns pointer and touch activation. Providers cannot return HTML, DOM, editor objects, or infer embedded languages from filenames or source scans.

Managed-tool adapters use the exported EXTENSION_MANAGED_TOOL_INVOCATION_METHOD and JSON-RPC request/response types over the host's Content-Length-framed stdio transport. Extension code selects only a manifest-declared tool and entrypoint; it never supplies an executable path, environment, or terminal command.

The SDK exports the canonical managed-tool request, result, frame, and JSON structure limits plus extensionJsonUtf8ByteLength for exact host-neutral checks. Diagnostics providers have high per-provider item and encoded-byte ceilings plus one document-wide aggregate budget; providers must bound their own results before crossing either RPC boundary.

Long-running managed tools use the declarative persistentServices contribution and the services.manage permission. A service selects only a signed tool and entrypoint and declares bounded stop, health-probe, and log policy; it cannot declare argv, an environment, a path, or shell text. Providers operate only their own symbolic service ids. The host owns the canonical workspace/trust decision, process group, health, bounded UTF-8 logs, and exact activation cleanup.

Terminal development dependencies use contributes.developmentStacks and the independent terminal.packages permission. A stack contains one to 32 required package symbols from the fixed host-known zyntax or termux-main repository identity. It cannot contain repository URLs, keys, versions, package-manager options, commands, paths, environment values, or scripts. inspectStack returns installed, candidate and available versions from current indexes. refreshStack(stack, cancellation) first refreshes host-configured signed indexes through the existing package-operation queue, then returns the same inspection. Use it before selecting missing packages; it does not install or upgrade anything. Transactions require { stack, intent, packages: [{ id, version }] }: exact declared requirement selections for install, repair, update or remove (the installed version for removal). The existing Package Manager reviews the complete impact and owns the mutation queue; stale selections are rejected. This permission does not grant execution. Separate SDK/NDK components remain the responsibility of their installers, not a second package manager in the SDK. waitTransaction(transaction, cancellation) waits for a terminal state without polling. Cancelling that wait leaves the transaction running; cancelTransaction stops it. Both refresh and completion waits are cancellable interactive host methods. SDK 1.0.25 adds only these generic operations, with no project-specific policy or version allowlist.

{
  "permissions": ["extension.execute", "terminal.packages"],
  "contributes": {
    "developmentStacks": [{
      "id": "web-runtime",
      "label": "Web runtime",
      "packages": [{ "id": "node", "repository": "termux-main", "package": "nodejs-lts" }]
    }]
  }
}

Managed execution arguments may contain a signed resource reference instead of a literal string. The native host resolves it immediately before launch and the path never enters extension-provider code. Node entrypoints may additionally declare a sorted nodeModules array which binds a valid npm package name to a signed package-directory resource. The host projects the entrypoint and those packages into one immutable module tree and launches it with normal Node module resolution. Manifests never declare NODE_PATH, installation directories, or package-specific host behavior; each resource package must carry a matching package.json name.

A signed managed-tool tool.json entrypoint may also declare non-empty static args. Each value is either a bounded literal string or the same $toolResource reference, restricted to a direct managed-tool dependency. The host resolves these static arguments immediately before launch and places them before invocation arguments. Omit args when there are no static arguments; an empty array is not a second canonical representation. This gives an adapter an exact signed dependency resource through ordinary process arguments without declaring an installation path, environment variable, shell fragment, or host filesystem layout.

A managed-tool capability probe may declare non-empty stdin containing at most MANAGED_TOOL_PROBE_MAX_STDIN_BYTES (65,536) UTF-8 bytes. The host writes those exact bytes and then closes stdin before it waits for the bounded probe result. When stdin is omitted, the host closes stdin immediately. This supports protocol-aware checks without exposing paths, environments, shell commands, or process control to package metadata.

Managed document formatters declare their exact stdio protocol. Use framed-jsonrpc for an adapter that implements the canonical formatting RPC, or raw-stdio for a direct CLI which accepts the exact document bytes on stdin and returns one bounded UTF-8 document on stdout. Raw formatter arguments are fixed manifest strings or signed resource references; the host does not substitute a language, URI, option, or shell fragment at runtime. The explicit current-document path reference is available only to document formatters. Contribute separate formatter descriptors when a CLI requires a different constant argument for each canonical language.

Project-template providers return either an atomic UTF-8 file plan or a symbolic managed-tool plan. Managed generators run only after host review, in a new app-owned staging root, and must produce the declared regular-file markers before the host atomically publishes the project. The provider and managed tool receive JSON and canonical URIs only; native paths never cross the provider boundary.

Debugger configuration providers receive the active project and document as canonical URIs. Use debugDocumentPath(documentUri) for configuration fields such as program; the host validates that URI against the active project and resolves the native path only when it sends the DAP launch or attach request. Providers and manifests never contain app-private filesystem paths.

For a launch field which the user must select, use debugProjectPath(id, label, kind). The consumer extension can persist the selected canonical document URI per project. The symbolic slot remains in provider requests; only the generic managed transport resolves it to a native path when sending the protocol message.

SDK 1.0.29 adds provider preparation (debug.execute), protocol-neutral framed processes (processes.execute) and owner-scoped editor integration (editor). An optional debugger extension owns its UI, DAP request sequencing, protocol state, breakpoints, watches, inspection and console. The core retains only provider/tool authority, project trust, bounded transport, process cleanup and shared editor rendering; it does not implement a second debugger engine or mandatory panel.

context({ project: null }, cancellation) reads the Explorer project, current document/language and available paired configuration/adapter providers. A selected scope is accepted only when it resolves to that same Explorer project; another root is rejected explicitly. Refresh context when opening the view or refreshing configurations. No project is represented by projectUri: null, not by silently picking a folder.

configurations returns provider configurations with opaque launch IDs; bindConfiguration binds an extension-stored configuration to the current provider. The host keeps provider identity private and rejects launch IDs after that provider changes. selectPath reviews a declared $projectPath slot and returns a project-confined canonical URI (or null when dismissed). prepare accepts previously persisted URI selections for that project's declared path slots; it revalidates each selection's declared kind and no-follow project confinement, rejecting undeclared slots. Valid saved selections need no new picker or per-launch confirmation. It revalidates project trust and provider binding, then resolves the configuration and descriptor exactly once through that provider. It returns the configuration plus an opaque single-use process ticket sealed to the provider, consumer and project. Tagged document paths remain symbolic. Callers cannot claim another provider's executable or managed-tool authority.

An extension may prepare its own signed managed tool with processes.execute.prepare({ project, tool, entrypoint, credentials? }, cancellation). The tool must be in its manifest's toolRequirements with process.framed-json; both processes.execute and tools.execute permissions are required. No executable path or dynamic arguments are accepted. Preparation does not start a process and returns an opaque, one-use ticket bound to the exact calling principal, activation and current trusted Explorer project. credentials: true enables the native-only credential channel described below and requires secrets permission. No environment bindings are accepted. Native start rechecks the manifest, entrypoint, project and credential-channel permission. Native jobs, tasks and services with the owner's storage permission receive ZYNTAX_EXTENSION_DATA, an app-private directory owned by the stable extension ID, without exposing a path API to the view or accepting a caller-selected directory. Without that permission no data directory is created or exposed. Store durable sessions there, not in generation-scoped managed HOME. It survives project closure, disable, updates and extension reset; uninstall removes it only after process cleanup. This does not create an OS sandbox.

processes.execute.open(ticket) consumes that authority to open a managed Content-Length framed JSON process. Inspect its returned state and error: a startup failure after a managed handle exists can return a terminated snapshot, whose ID remains usable for cleanup retries. Failures before a handle exists and cancelled opens reject. send writes one bounded JSON frame, resolving structured {$documentPath: canonicalUri} references through the trusted project with no-follow confinement. The host does not interpret DAP/LSP commands. observe(process, cursor, cancellation) waits for ordered frames and lifecycle changes without timer polling. Zero starts observation; subsequent calls acknowledge the last returned cursor. Unsafe, future or expired cursors fail explicitly. Queues are bounded and lossless: overflow stops the process with an explicit error, never silently drops protocol messages. Cancelling observation or hiding a panel does not stop execution. Project closure, trust loss, provider removal or owner disposal does. stop retries failed cleanup (cleanupPending); release discards results only after termination and complete cleanup.

editor.context supplies the active source and modified documents. save flushes that project's editors. replaceMarkers publishes a named owner-scoped group of themed circles/arrows and optional line highlights, using one-based source coordinates from 1 through 2147483647. Each call accepts up to 512 markers with unique IDs (192 characters), a group name (96), labels (256) and canonical project URIs (4096). These strings must be nonempty and contain no control characters. Shared marker-count, group-count and encoded-byte capacity limits fail explicitly; valid per-call input does not reserve global capacity. Interactive groups receive gutter clicks; mapped marker changes follow edits. observe batches context, click and marker events. Context and group snapshots may coalesce, but click loss/queue overflow is explicit; cursor zero resynchronizes current context and marker groups. The extension stores its own annotation meaning and settings. reveal navigates to a project-confined source; resolvePath maps an adapter's path data to a validated canonical URI or null, never filesystem authority outside the project. No breakpoint-specific storage or debug session state is required in core. Generic bug, pause, stop, step-over, step-into and step-out workbench icons follow the active app theme.

Providers with workspace.read can query the host-owned project index through findFiles({ project, include, exclude?, maxResults }, cancellation). project: null explicitly means the explorer workspace; a selected context ID means that project. Globs are relative to that scope; a basename-only glob matches at any depth. Results contain only canonical document URIs and project-relative paths, are deterministic and bounded, and remain subject to the same project-confined readText boundary. readText, readTextIfExists and relativePath take { project, uri }. relativePath returns its project-relative identity, or null outside the selected project. The isolated provider never receives a native path or direct filesystem access. workspace.write.applyEdits likewise takes { project, edits }.

workspace.read.snapshot({ project, uri }, cancellation) reads the current Explorer project's UTF-8 text, including unsaved editor changes, normalized to LF. It returns { uri, exists, text, revision, dirty }; a file absent from both disk and editors has empty text and exists: false. The opaque revision binds this owner, project activation and exact disk/editor state. Text is limited to 512 KiB; binary files, non-file targets and symlinks fail explicitly. listDirectory({ project, uri }, cancellation) lists direct files and directories, including empty folders, as { uri, name, kind }. It never follows or returns symlinks and fails above 1024 entries or 512 KiB instead of silently truncating.

workspace.write.writeText({ project, uri, revision, text }, cancellation) proposes one replacement or new file in the current trusted Explorer project. The host shows a themed diff overlay; approval saves atomically and updates an open editor through its normal undo/persistence machinery. Unsaved changes are included in the review and explicitly saved on approval. Parent directories must already exist. Changed revisions, revoked access and pre-commit cancellation fail before writing; the caller must read again rather than blindly retry. Dismissal returns { applied: false }. A committed write returns { applied: true, snapshot? }; the snapshot is omitted if cancellation or project closure prevents a fresh read after the definitive commit. Existing newline conventions are preserved.

Project panels and owned tasks

Native runtime credential channel

A managed process prepared with credentials: true and the extension's secrets permission can use createRuntimeCredentialClient({ send }) inside its native worker. send writes its existing Content-Length framed stdout transport. Pass stdin JSON to client.accept(message) before ordinary protocol dispatch; consumed responses must never enter UI events, logs or transcripts. Call dispose() when the worker shuts down. No Node-specific import is required by the helper.

The reserved envelope is { "$zyntax": "credentials", id, method, ...fields }. Native code consumes it before output retention or bridge events and replies on ordered stdin as { "$zyntax": "credentials", id, ok: true, result } or { "$zyntax": "credentials", id, ok: false, error }, with error limited to invalid, conflict or unavailable. Public processes.execute.send cannot send reserved envelopes. Ownership and activation come from the admitted job, never from an envelope field. Access expires on process stop, owner revocation or project closure. The encrypted runtime namespace is separate from UI secret references and existing account stores; uninstall removes it after process cleanup.

get(key) returns { value: string | null, revision: string | null }. set(key, value, revision) requires that exact revision (null for absent) and returns { revision }; delete(key, revision) has the same comparison. list() returns sorted keys only. Concurrent updates fail with an explicit conflict, not an implicit retry. Keys are 1–128 control-free characters, values are at most 32 KiB UTF-8, and the owner store is bounded to 128 keys/256 KiB encoded. Control frames are bounded to 256 KiB; the helper allows 32 pending calls with a 30-second deadline. A timeout does not undo a committed write; read before retrying.

For private interactive input, request an ordinary host-owned secrets.request prompt through the extension controller and send only its opaque reference to the worker. resolve(reference, { consume: true }) returns the value directly from native code and atomically removes that one-use reference. The wire request carries method: "resolve", reference and boolean consume; omitting helper options uses false for reusable references. No secret plaintext crosses provider/view RPC. This boundary is not an OS sandbox against a malicious native program that prints or transmits credentials it was explicitly granted.

projects.select opens the host's location picker without changing the explorer. Relative input is explorer-relative; ~/ uses terminal home. Selection keys are saved locally per extension/workspace, while returned IDs never rebind to another location. projects.bindIntegrations explicitly shares a selected project with declared optional integrations after review; it shares no file/secret selections. Providers receive their scope in context.workspace.project, and task requests carry both scope and canonical project URI. Reads and language services must use that scope, not silently substitute the explorer project.

files.select resolves relative input from its explicit project, supports user selection outside that root, and returns an owned file/directory reference for process bindings. Descendant paths must remain contained; a file reference allows no descendants. files.export copies a scoped artifact to a user-chosen destination. secrets.request uses host-owned private input and encrypted storage. Only references cross provider RPC and enter task environment bindings, never raw values or argv. Programs receiving credentials can print them; this is not a sandbox against a malicious build script. Host records and errors must not disclose credentials.

tasks.execute operates the extension's declared task providers through one list/start/sessions/observe/reveal/stop/release lifecycle. start resolves scope and inputs once. Cancellation before a successful response also stops newly spawned work; after that response, stop owns cancellation. Observation waits for changes after a monotonic cursor and returns bounded output plus current lifecycle state; it does not poll or stop work when cancelled. Closing a panel does not stop builds. Exit frees the PTY, results stay until released, and extension disposal stops owned work. Root changes affect future launches, not already-running tasks.

Command/runtime task arguments accept ordinary script text, including tab, CR and LF: at most 32 caller arguments, 16 KiB UTF-8 per literal and 64 KiB total resolved argv, including native path bindings and runtime prefixes. NUL, other ISO control characters and malformed Unicode are rejected. Managed-tool arguments and all environment literals keep their existing 512-code-unit, control-free policy. This does not change executable selection, project trust, permissions or routing. isExtensionTaskArgumentLiteral and the EXTENSION_TASK_MAX_ARGUMENT_BYTES / EXTENSION_TASK_MAX_ARGUMENTS_BYTES constants expose the command/runtime limits.

workbench updates/reveals/closes manifest-declared panels, including dialog placement. Reuse list/tree/form/detail descriptors and host controls, not a separate UI framework. Updates preserve stable-ID form drafts unless resetForm is requested. Per-control disabled state keeps actions such as Stop usable while a panel is busy. Semantic icons and file/folder icons resolve through app presentation; theme changes apply live. Isolated custom views use presentation(), revision-bound resolveIcons and presentation events for the same colors, typography and icons. They receive no app component imports, stylesheet paths or native asset paths.

SDK 1.0.28 adds the generic projectLifecycle event to the existing custom-view message bridge. When Explorer closes or replaces its project, closedProjects contains only this extension's selected project IDs matching that root or a descendant; unrelated selections are not included. revision increases for the host lifetime, but a view may skip revisions. Queued notices before bridge readiness can combine closed IDs and use the latest revision. Trust-only changes do not close projects. The shared conformance fixture includes single-project and combined notices.

Listen before restoring selected project state, retaining notices until restoration finishes, and stop/release work for matching IDs through the ordinary task and project APIs. Hidden views receive the event too; it does not destroy the renderer or revoke references needed for cleanup. Hiding a panel is still presentation-only and emits no project closure event. This event grants no additional access and exposes no other extension's selections or private paths.

Optional system file actions

SDK 1.0.24 adds kind: "fileOpener" in contributes.workbench, with placement: "explorer.file", id, label, optional themed icon, and the usual when/group/order presentation fields. Declare extensions, filenames, an exact mimeType, and requiredPermissions. Both association arrays are required; at least one must be nonempty. Extensions are lowercase literal suffixes, including compound suffixes; filenames are literal basenames. Matching is case-insensitive. parseFileOpenerOptions and matchesFileOpener provide the shared rules; native hosts consume the same bounds and patterns from runtime-contract.json.

The package declares files and workbench permissions. It needs no provider, tool, activation event or executable code. requiredPermissions lists platform manifest requirements only (use [] when none); it cannot request or grant them. Unavailable actions are hidden. A user-declined platform setting is distinct from an absent manifest permission: the operating system owns its consent UI.

The host offers the action for explicit file clicks and the selected file's menu. Existing language/preview handlers retain click priority; multiple matching actions require user selection, never an arbitrary winner. Restore, code navigation and programmatic extension commands cannot launch these actions. Both project files and user-selected provider documents are supported without exposing native paths or file bytes to extension code. The host validates active owner identity, source access, association and manifest requirements before preparing data and launching, grants only read access to the selected file, and cancels pending work when its owner is disposed. Launching a handler does not mean installation or viewing has completed. This contract has no arbitrary intent, package-target or URL parameters.

Preview providers may select registered editor languages or declare strict paths globs. Project files expose their project-relative path; external and untitled documents expose their canonical display name. Path selectors route previews without claiming syntax or language support. Every provider declares one input mode and one required placement: adjacent toggles in the editor area on phones and sits beside the editor on wider screens, while replace-editor owns the editor area for visual resources that have no useful source view. The host derives layout only from this declaration, never from a package id or filename. The required encoding is utf8 for text documents or base64 for binary source snapshots; text input always requires utf8. The same selected provider metadata controls the bounded file read and read-only editor policy. text receives the bounded canonical text snapshot and may return structured, table, plain-text, or bounded HTML markup data. Markup declares linked or independent scrolling and is limited to inert semantic HTML: scripts, event handlers, active controls, remote resources, classes, ids, and extension styling are not part of the surface. The host sanitizes it and applies theme-aware semantic styling inside the preview pane. document receives metadata only and may select a declared HTML, SVG, or raster renderer for the exact source snapshot; it never receives bytes, resource URLs, markup, DOM, Vue, filesystem or native handles, or a WebView. HTML resources are always script-blocked and declare only whether canonical project subresources are available. The host validates the bounded result, mints and disposes the canonical resource session, confines project resources, and owns the renderer. Preview request and result budgets are exported as EXTENSION_PREVIEW_MAX_INPUT_BYTES, EXTENSION_PREVIEW_MAX_BINARY_INPUT_BYTES, and EXTENSION_PREVIEW_MAX_RESULT_BYTES.

{
  "contributes": {
    "previewProviders": [{
      "id": "preview",
      "module": "providers/preview.js",
      "export": "createPreview",
      "input": "text",
      "placement": "adjacent",
      "encoding": "utf8",
      "languages": [{
        "id": "example",
        "schemes": ["file", "untitled"],
        "group": "example.preview",
        "priority": 100,
        "composition": "exclusive"
      }]
    }]
  }
}