Skip to content

Repository files navigation

tecode

CI

Issue #36 "4.5 CI pipeline" (Req 13.1, 13.2, 13.4; design.md §15, §16). .github/workflows/ci.yml runs five independent jobs on every push to main and every pull request; a separate, tag-triggered CircleCI pipeline (.circleci/config.yml) handles releases (see the "Release" section below). Each CI job is reproducible locally with one bun run script:

CI jobLocal commandWhat it checks
lintbun run lintESLint, including the no-restricted-imports/no-restricted-syntax layering rule (eslint.config.mjs) that keeps @tecode/core importable only from packages/cli.
testbun testThe full workspace bun test suite.
contractbun run test:contractThe API_VERSION gate — the extension-API contract suite (packages/core/src/api/create.contract.test.ts) plus the constant's own assertions (packages/api/src/index.test.ts).
snapshotbun run test:snapshotThe headless-renderer cell-grid suite: every *.snapshot.test.tsx file, rendered via @opentui/react/test-utils's testRender (design.md §16, "snapshots the cell grid" — no toMatchSnapshot, every assertion reads real rendered output).
performancebun run test:perfStartup-to-first-frame timing (packages/cli/src/main.integration.test.ts) and the scripted 10,000-line typing benchmark (packages/cli/src/typingBenchmark.test.ts) — thresholds live as named constants in each test file, not in the workflow.

bunx tsc --noEmit is also worth running locally before pushing (not its own CI job — bun test's own module resolution already fails loudly on a real type error in a file any test imports, and lint's typescript-eslint rules catch most of the rest — but a standalone typecheck is the fastest way to confirm a change is clean before opening a PR).

Branch protection (repo configuration, not something a commit can set): for pull requests to actually be blocked on lint/test/contract/ snapshot failures per this issue's completion requirements, a repo admin must add each job as a required status check under Settings → Branches → branch protection rule for main ("Require status checks to pass before merging", then select Lint (ESLint incl. layering rule), Test (full bun test suite), Contract suite (API_VERSION gate), and Snapshot (headless-renderer cell-grid suite) by name — they only appear in that picker after each has run at least once on a branch or PR). performance is deliberately left out of that required list: Req 13.1's thresholds already carry generous headroom specifically to avoid CI flakiness, but a shared runner's noise floor is still less predictable run-to-run than the other four gates, so it is left informational (visible on every PR, blocking main's own push trigger, but not a hard merge gate) rather than risking blocking merges on infrastructure noise rather than a real regression.

Release

bun run release [target ...] builds the compiled-binary release matrix (see scripts/release.ts's TSDoc for the full embedding story and why cross-compilation is not possible from a single machine). Three of the completion requirements from Issue #35 need a platform or a real terminal this repo's own CI/dev environment doesn't have — see docs/manual-release-verification.md for the exact procedure.

Cutting an actual release is one command, run on the project owner's own Apple Silicon Mac: bun run tag <version> (e.g. bun run tag v1.2.3 or bun run tag 1.2.3 — either is normalized to a leading v). The owner declined to install a CircleCI machine runner on their own Mac, so instead of every target being built by CircleCI, scripts/release.ts's RELEASE_TARGETS now splits by builtBy:

TargetBuilt by
bun-darwin-arm64"local"bun run tag, on the owner's Mac
bun-linux-x64"circleci" — CircleCI-hosted executor
bun-linux-arm64"circleci" — CircleCI-hosted executor
bun-windows-x64"circleci" — self-hosted runner, the owner's Windows box

bun run tag <version> (scripts/tagRelease.ts) does, in this exact order — see that script's own TSDoc for the full reasoning behind the order:

  1. Preflight — every check runs and must pass before anything below mutates anything: host is darwin/arm64; TECODE_RELEASE_TOKEN is set (see "Release token" below — notGITHUB_TOKEN); the git working tree is clean; the current branch is main and matches origin/main exactly (fetched first); the version argument is well-formed; the tag doesn't already exist locally or on origin; and no GitHub release (draft or published) already carries that tag_name. Any failure prints exactly what's wrong and what to do — nothing runs until all of them pass.
  2. Buildbun-darwin-arm64 (reusing scripts/release.ts's buildTarget/writeChecksumFile — the size budget and .sha256 sibling are enforced the same way bun run release enforces them). This runs first because it's the step most likely to fail, and a failed build leaves nothing else behind to clean up.
  3. Create a DRAFT GitHub Release for the version and upload the two macOS assets, then verify both actually landed.
  4. Create and push the annotated git tag — last, because it's the one irreversible step: pushing it fires the CircleCI pipeline below immediately, and a push cannot be un-fired the way a draft release can simply be deleted. If step 3 already succeeded and this step fails, the script prints exactly how to recover (the draft release to delete, or the exact git tag/git push command to finish by hand).

Once the tag is pushed, .circleci/config.yml's release workflow builds the three builtBy: "circleci" targets in parallel — one runner per target, one of them self-hosted on the project owner's own Windows box (see that config's own top-of-file comment for the full explanation) — each invoking bun run release <its-own-target>. Every target persists its binary and checksum to a shared workspace for the publish job, which finds the draft release bun run tag already created (by listing releases and matching tag_name + draft: trueGET /releases/tags/{tag} does NOT reliably return draft releases, so publish never uses it), uploads its own three targets' assets on top of the one already there, verifies the release holds all four binaries and four checksums, and only then PATCHes it to draft: false.

Pushing a v* tag by hand, without running bun run tag, does NOT produce a release — it produces a stuck pipeline.publish looks for a draft release matching the tag, finds none (only bun run tag ever creates one), and fails loudly rather than creating a release itself — a release publish created on its own would permanently be missing the macOS binary, since nothing else in this pipeline builds bun-darwin-arm64. If this happens, delete the tag and run bun run tag <version> from the owner's Mac instead.

Only four of the six theoretically possible darwin/linux/windows × x64/arm64 combinations are published: bun-darwin-x64 (Intel macOS) and bun-windows-arm64 (Windows on Arm) are dropped, not deferred — no CI runner of either architecture exists (the release provider removed its Intel-macOS resource class in June 2024 and offers no Windows-arm64 one at all), and the project owner has neither an Intel Mac nor a Windows-on-Arm machine to self-host either on. Cross-compiling either from another platform is impossible for the same @opentui/core reason no target can be cross-compiled at all (scripts/release.ts's TSDoc, "Why this machine cannot produce even the four remaining binaries"). If you're on one of the two dropped platforms, see "From source" below — running from source works today, no release required.

Release token (TECODE_RELEASE_TOKEN)

Both bun run tag (on the owner's Mac) and CircleCI's publish job need their own GitHub credential to talk to the GitHub REST API — create the draft release, upload assets, and flip it to published. Both read it from an environment variable named TECODE_RELEASE_TOKEN, deliberately notGITHUB_TOKEN: that name is common enough ambient convention (the gh CLI, GitHub Actions runners, and other tools all read it) that it may already be set on a given machine to a token with far broader scope than this task needs, and there is no fallback to it — if TECODE_RELEASE_TOKEN is unset, both bun run tag and CircleCI's publish job fail preflight outright rather than silently using something else.

Minimum required scope, for both copies of this token: a fine-grained personal access token, "Repository access" limited to only goofmint/tecode, with exactly one repository permission granted — Contents: Read and write (GitHub files releases under Contents; this is what lets the token create a draft release, upload assets to it, and publish it). A classic PAT is the wrong choice here — its repo scope reaches every repository the token's owner can access, plus issues, with no way to narrow it down to just this one repository's releases.

This token is used only for the GitHub REST API calls described above. Pushing the git tag itself uses the machine's ordinary git credentials, not this tokenTECODE_RELEASE_TOKEN needs no git push/write access at all. The two copies (the owner's Mac, and CircleCI's project settings) are separate credentials in separate places — rotate or revoke either independently of the other.

Install

tecode ships as four self-contained, single-file compiled binaries — one per published target in scripts/release.ts's RELEASE_TARGETS (Req 13.2) — published as GitHub Release assets by bun run tag (the one builtBy: "local" macOS binary) together with the CircleCI pipeline's tag-triggered publish job (the three builtBy: "circleci" binaries) — see "Release" above for the full split. No separate runtime install is required to RUN a downloaded binary; all packages in this monorepo are "private": true, so there is no npm install -g tecode — a compiled binary or a source checkout are the only two ways to run it.

From a published release (once one exists)

  1. Download the binary matching your platform from the release's assets:

    PlatformArchitectureAsset
    macOSApple Silicontecode-darwin-arm64
    Linuxx64tecode-linux-x64
    Linuxarm64tecode-linux-arm64
    Windowsx64tecode-windows-x64.exe

    No binary is published for Intel macOS or Windows on Arm — see "Release" above for why. If you're on either platform, use "From source" below (bun packages/cli/src/main.ts): it works on any platform Bun itself supports, release or no release.

  2. Verify the checksum (each binary ships with a <binary>.sha256 sibling asset — scripts/release.ts's writeChecksumFile):

    • macOS/Linux: shasum -a 256 -c tecode-<platform>-<arch>.sha256
    • Windows (PowerShell): compare (Get-FileHash .\tecode-windows-<arch>.exe -Algorithm SHA256).Hash against the hex digest inside the matching .sha256 file (case-insensitive).
  3. macOS/Linux only: chmod +x tecode-<platform>-<arch>.

  4. Run it against a file or a directory: ./tecode-<platform>-<arch> <path> (.\tecode-windows-<arch>.exe <path> on Windows). First frame renders within ~100 ms (Req 12.2); everything past that — grammars, themes, keybindings.fallback.json — is embedded in the single binary (scripts/release.ts's TSDoc, "Why there is no extra bundler config for embedding"), so nothing else needs to be on the machine.

<path> does not need to exist yet: a file argument that isn't there on disk opens as a new, empty, editable buffer, and saving it (ctrl+s) creates the file — tecode README2.md on a fresh checkout opens an empty README2.md you can start typing into immediately (Req 5.6, Req 12.4, Issue #88). This only applies when the path's parent directory already exists and the argument doesn't end in a trailing / or \ (which always means "directory", never "file"); a typo'd deep path (notes/nested/todo.md with no notes/nested directory) still warns and starts with no file open, exactly as it always has.

macOS Gatekeeper will likely quarantine an unsigned downloaded binary on first run (xattr -d com.apple.quarantine tecode-darwin-<arch> clears it, or use the Finder's "Open" right-click override) — this repo does not currently code-sign or notarize release binaries.

From source (works today, no release required)

  1. Install Bun (this repo pins no specific version beyond bun install/bun test working — CI uses bun-version: latest, .github/workflows/ci.yml).
  2. git clone this repository, then bun install from the repo root (installs all workspace packages — packages/* — per package.json's workspaces).
  3. Run directly, no compile step: bun packages/cli/src/main.ts <path> (or bun run cli <path>, the equivalent package.json script).
  4. To produce your own compiled binary for your own machine's platform: bun run release <target> (e.g. bun run release bun-linux-x64) — see the "Release" section above for why this machine can only ever build ITS OWN host target, not the other three. This does NOT cover Intel macOS or Windows on Arm: RELEASE_TARGETS no longer names bun-darwin-x64/bun-windows-arm64 at all (the "Release" section's dropped-platforms note), so bun run release on those two rejects the target name outright rather than attempting a build — running from source (step 3 above) is genuinely the only option there, not just the convenient one.

Keybindings reference

Default keybindings, VS Code-compatible ({ key, command, when? }, Req 4.1-4.2) and resolved in this precedence, lowest to highest (Req 4.1, 4.8, packages/core/src/keymap/bindingTable.ts): core defaults → the terminal-capability fallback keymap (see "Fallback keymap" below) → extension-contributed bindings → the active bundled keybinding preset (see "Bundled keybinding presets" below) → the user's own keybindings.json, which always wins. Every key string below is already in this codebase's canonical lowercase mod+...+key form (keymap/normalize.ts); return is Enter's real key name, not enter.

This table includes two default-binding sources beyond the four built-in extension manifests: MODAL_DEFAULT_KEYBINDINGS and TAB_DEFAULT_KEYBINDINGS (packages/core/src/ui/modalCommands.ts and tabCommands.ts). Both are registered directly against the core command registry rather than through a contributes.keybindings manifest — the modal overlay (quick pick / input box) and tab switching are core-owned infrastructure other built-ins depend on already existing (see each module's own TSDoc) — but they are genuine default KEYBOARD BINDINGS a user presses every time they use the command palette, quick-open, or multiple open tabs, so they belong in a user-facing reference table just as much as any manifest-contributed one. Leaving them out would silently under-document exactly the keys most used day to day.

Cursor movement and selection (editor-core, when editor text is focused)

KeyCommand
left / right / up / downCursor left / right / up / down
ctrl+left / ctrl+rightCursor word left / right
home / endCursor to line start / end
ctrl+home / ctrl+endCursor to document start / end
shift+left / shift+right / shift+up / shift+downSelect left / right / up / down
ctrl+shift+left / ctrl+shift+rightSelect word left / right
shift+home / shift+endSelect to line start / end
ctrl+shift+home / ctrl+shift+endSelect to document start / end

Editing (editor-core, when editor text is focused)

KeyCommand
returnInsert line break
tab / shift+tabIndent / outdent
ctrl+sSave file
shift+alt+meta+downDuplicate line
alt+meta+up / alt+meta+downMove line up / down
ctrl+shift+kDelete line
ctrl+/ (or ctrl+_ on a non-Kitty terminal)Toggle line comment
ctrl+zUndo
ctrl+shift+z (or ctrl+y)Redo
ctrl+dAdd selection to next find match
()[]{}"'Bracket/quote auto-close (insert-pair, type-over, or wrap a selection)

Find and replace (editor-core)

KeyWhenCommand
ctrl+feditor text focusedOpen find
returnfind widget focusedFind next
shift+returnfind widget focusedFind previous
escapefind widget focusedClose find

Replace ("Replace" / "Replace All") has no default keybinding in this MVP — reachable via the command palette only (editor-core/manifest.ts's own TSDoc explains why: the widget's two inputs don't yet report distinguishable focus states to bind a shortcut against).

Tabs (core TAB_DEFAULT_KEYBINDINGS, no when — always active)

KeyCommand
ctrl+tab (or ctrl+pagedown)Next tab
ctrl+shift+tab (or ctrl+pageup)Previous tab
ctrl+wClose tab

Navigation and command palette (command-palette, no when)

KeyCommand
ctrl+shift+pShow all commands (the command palette)
ctrl+pGo to file (fuzzy quick-open)

Explorer

KeyWhenCommand
ctrl+shift+eFocus the explorer sidebar

Create/rename/delete have no default keybinding — reachable via the command palette only (explorer/manifest.ts's own TSDoc: Req 11.2 asks for the capability, not a specific shortcut per action).

Keybindings editor

KeyCommand
ctrl+k ctrl+sOpen Keyboard Shortcuts (JSON) — a two-stroke chord (Req 4.4)

Bundled keybinding presets (Req 4.8)

Set keybindings.preset in settings.json to layer a bundled keybinding scheme over the defaults above, without hand-editing keybindings.json yourself. Valid values: "default" (none — the schema default), "emacs", "windows". Changing the setting takes effect immediately, no restart. There is deliberately no "vim" preset: every when context in this codebase (editorTextFocus, editorFocus, quickPickFocus, inputBoxFocus, findWidgetFocus, explorerFocus, editorLangId) is purely focus-based, with no mode concept a non-modal "vim" preset could honestly model.

"emacs" (packages/core/src/keymap/presets/emacs.json), while an editor text buffer is focused:

KeyCommandNote
ctrl+a / ctrl+eCursor to line start / end
ctrl+f / ctrl+bCursor right / leftOverrides the default ctrl+f (open find)
ctrl+n / ctrl+pCursor down / upOverrides the default ctrl+p (quick-open) while editor text is focused
alt+f / alt+bCursor word right / left
ctrl+kDelete line (kill-line)
ctrl+sOpen find (isearch-forward)Overrides the default ctrl+s (save)
ctrl+x ctrl+sSave fileEmacs's own save-buffer chord, replacing ctrl+s above

Pressing plain ctrl+k under this preset deletes the line directly — it does not wait for a second stroke. Making that true takes one more entry the table above doesn't show: keybindings-editor's own ctrl+k ctrl+s chord (see "Keybindings editor" above) is removed via { "key": "ctrl+k ctrl+s", "command": "-keybindings.open" }, because a chord's prefix always wins over a same-key exact match (packages/core/src/keymap/chords.ts) — left in place, it would make every ctrl+k press sit in a pending state waiting for ctrl+s instead of ever reaching this preset's own kill-line binding.

"windows" (packages/core/src/keymap/presets/windows.json) is intentionally small: this codebase's defaults are already VS-Code-on-Windows/Linux-shaped throughout, so there is little left to change. The one real difference is that the default line-move/duplicate bindings above (alt+meta+up / alt+meta+down / shift+alt+meta+down) carry a macOS-only meta (Cmd) modifier; this preset adds the Windows/Linux-native equivalents alongside them:

KeyCommand
alt+up / alt+downMove line up / down
shift+alt+downDuplicate line

Quick pick / input box navigation (core MODAL_DEFAULT_KEYBINDINGS)

Active only while the command palette, quick-open, or an input box (e.g. "New File") is open:

KeyWhenCommand
downquick pick focusedSelect next item
upquick pick focusedSelect previous item
returnquick pick or input box focusedAccept
escapequick pick or input box focusedClose

Rebind or remove any of the above in your own keybindings.jsonsamples/keybindings.json (in this repository) is a working, commented starting point, and packages/core/src/ui/keybindingsCommands.ts's KEYBINDINGS_TEMPLATE is what a running tecode writes to ~/.config/tecode/keybindings.json the first time you run "Open Keyboard Shortcuts (JSON)" (ctrl+k ctrl+s) if that file doesn't exist yet.

Settings reference

Settings live in ~/.config/tecode/settings.json (%APPDATA%\tecode\settings.json on Windows — packages/core/src/host/paths.ts's getUserSettingsPath), JSONC (comments and trailing commas accepted, Req 9.1), watched and applied live with no restart (Req 9.4). A workspace's own .tecode/settings.json overlays on top (Req 9.2). samples/settings.json (in this repository) is a working, commented starting point covering every key below.

Pass --config <dir> at startup (e.g. tecode --config /path/to/cfg ./my-project) to read the user settings and user keybindings layers from <dir>/settings.json and <dir>/keybindings.json instead (Req 9.6) — useful for an isolated profile or a CI sandbox. Only the user layer moves; a workspace's own .tecode/settings.json still overlays on top exactly as above. A missing <dir> (or a missing file inside it) is treated the same as a missing home-directory file: an empty layer, not an error. A relative <dir> resolves against the current working directory. --config with no directory argument after it is ignored (no override applied), and it never consumes the directory/file argument that opens a workspace — tecode --config /path/to/cfg ./my-project still opens ./my-project.

Req 9.5 names six MVP settings; the table marks which of them a real contributes.configuration schema registers today, and which do not exist yet:

KeyTypeDefaultSourceDescription
workbench.colorThemestring"tecode.dark-modern"core (config/coreDefaults.ts)The active color theme's id (Req 7.5, 11.4).
editor.lineNumbersbooleantruecoreShow line numbers in the editor gutter.
editor.tabSizenumber4coreThe number of spaces a tab is equal to.
editor.insertSpacesbooleantruecoreInsert spaces (up to the next tab stop) instead of a literal tab when pressing Tab.
explorer.showHiddenbooleanfalseexplorer built-in extension (builtin/explorer/manifest.ts)Show hidden (dot-prefixed) and .gitignore-ignored files in the explorer sidebar.
keybindings.presetstring"default"core (config/coreDefaults.ts)A bundled keybinding scheme layered over the defaults — "default" (none), "emacs", or "windows" (Req 4.8). See "Bundled keybinding presets" above.
editor.wordWrapnot implementedNamed by Req 9.5. No contributes.configuration schema registers this key, and nothing in packages/ reads config.get("editor.wordWrap") outside of test fixtures exercising the config-merge machinery in the abstract (packages/core/src/config/service.test.ts, themeSettingsWriter.test.ts) — those tests use the string purely as a generic example key, not as evidence of a real word-wrap feature. Verified by grepping the whole packages/ tree for both the key string and any wrap-related rendering logic in EditorView; there is none.
files.autoSavenot implementedNamed by Req 9.5. No schema registers it, and no reader ever calls config.get("files.autoSave") anywhere in packages/ (verified the same way as editor.wordWrap above — a plain grep for the key string found zero matches at all, not even in a test fixture).

Extensions declare their own settings via a manifest's contributes.configuration (Req 9.3) and read them back with tecode.config.get(key); explorer.showHidden above is the one example shipped today. A third-party extension's own settings would be documented by that extension, not here.

Terminal support

Req 13.3 names six terminal targets. tecode detects, at startup, whether the attached terminal answers the Kitty Keyboard Protocol capability query (packages/cli/src/terminalCapabilities.ts's resolveKittyKeyboardSupport) and, when it does not, overlays the fallback keymap described below so that otherwise-indistinguishable combinations like ctrl+shift+p stay reachable.

Read this table's "Verified how" column carefully — it is not a claim that every row was manually tested. Only the automated, machine-checked parts of this detection logic (terminalCapabilities.test.ts's mocked- response matrix; a real tmux 3.4's own $TERM/$TERM_PROGRAM values, captured directly in a headless dev sandbox with no real TTY) have actually run against something real; the other five terminals are an unexecuted manual checklist (terminalCapabilities.ts's own TSDoc, "Manual test checklist — Req 13.3's six-terminal matrix") that a human with access to each real terminal program still needs to run and record.

TerminalVerified howKitty-capable?
GhosttyUnexecuted — manual checklist onlyNot run
KittyUnexecuted — manual checklist onlyNot run
WezTermUnexecuted — manual checklist onlyNot run
iTerm2Unexecuted — manual checklist onlyNot run
Windows TerminalUnexecuted — manual checklist onlyNot run
tmux (any outer terminal)Partially verified: a real tmux 3.4 binary's $TERM/$TERM_PROGRAM (tmux-256color / tmux) were captured directly and confirmed to trigger resolveKittyKeyboardSupport's tmux-passthrough correction — this validates the ENV-VAR ASSUMPTION the tmux branch relies on, not the full keyboard-interaction procedure (no real TTY in that sandbox either, so a CliRenderer was never actually exercised against tmux)Forced false regardless of the capability query's own answer (deliberate — tmux's forwarding of Kitty's enable sequence is inconsistent across versions)

The procedure a human runs to fill in the first five rows — launch inside each terminal, press ctrl+shift+p/ctrl+g/ctrl+shift+e/ctrl+shift+k, record pass/fail — is written out in full in packages/cli/src/terminalCapabilities.ts's own TSDoc ("Manual test checklist") and in docs/manual-release-verification.md §3 (the interactive clean-machine check, which folds the same six-terminal requirement into its own procedure).

Fallback keymap

When the attached terminal does not answer the Kitty Keyboard Protocol capability query (or answers it unreliably — see "Terminal support" above), tecode overlays a small fallback keymap that remaps the handful of default bindings whose modifier a legacy terminal cannot disambiguate (Req 4.7; design.md §6.5). This is a distinct LAYER, not a special case of any other layer: the full precedence is core defaults → fallback → extension-contributed bindings → the user's keybindings.json (packages/core/src/keymap/bindingTable.ts's KeymapLayers) — sitting directly above core defaults means a fallback entry can safely remap something a default binds, while still sitting below EVERY extension and user binding means the user's own keybindings.json always wins, with no special-casing needed to override a fallback entry.

The bundled fallback keymap (packages/core/src/keymap/keybindings.fallback.json) ships three entries today:

KeyCommandWhen
ctrl+gworkbench.action.showCommands
ctrl+eexplorer.focus
ctrl+leditor.action.deleteLineeditorTextFocus

Each patches a real ctrl+shift+<letter> ambiguity that a legacy terminal collapses to the same raw control byte as its unshifted counterpart (ctrl+shift+p/ctrl+shift+e/ctrl+shift+k respectively — packages/cli/src/fallbackKeybindingsCompleteness.test.ts enumerates the full hazard set and proves every one of them has either an unambiguous alternate binding already, or a fallback entry like these).

This file is user-overridable, separately from keybindings.json (packages/core/src/host/paths.ts's getUserFallbackKeybindingsPath): a ~/.config/tecode/keybindings.fallback.json (or the Windows equivalent under %APPDATA%\tecode\) ENTIRELY REPLACES the bundled file — not merged with it — letting you pick your own non-colliding alternates for your specific legacy terminal without touching your real keybindings.json. This layer is resolved once at startup, alongside the Kitty-capability verdict it depends on, and is not live-reloaded the way settings.json/keybindings.json are (packages/core/src/keymap/fallbackKeybindings.ts's own TSDoc).

Documentation

docs/extension-authoring-guide.md walks through building a tecode extension end to end — manifest, activation, a command, a sidebar view, a configuration key, a keybinding — documents every tecode.* API namespace, and covers bundling extensions with npm dependencies and the API-version compatibility policy.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages