Two execution layers

The declarative safe layer loads reviewed data contracts for tools, agents, and Agent Skills without importing package code. Add a trusted runtime only when the extension needs native slash commands, Ink UI, status or help content, keybindings, CLI flags, hooks, providers, or permission policy. Runtime installation requires --trust and executes compiled JavaScript inside the Autohand process.

Package layout

company.release-helper/
  autohand.extension.json
  README.md
  src/
    extension.ts
  dist/
    extension.mjs
  tools/
    release-range.json
  agents/
    release-planner.md
  skills/
    release-workflow/
      SKILL.md

Only files declared in autohand.extension.json contribute capabilities. Source, README, license, tests, fixtures, and bundled dependencies may live beside them, but Autohand neither discovers undeclared capabilities nor compiles TypeScript or installs dependencies during extension installation.

Manifest

{
  "$schema": "https://raw.githubusercontent.com/autohandai/code-extensions/main/schema/autohand.extension.schema.json",
  "schemaVersion": 1,
  "extensionApi": 1,
  "id": "company.release-helper",
  "name": "Release Helper",
  "version": "1.0.0",
  "description": "Prepare and inspect releases.",
  "license": "Apache-2.0",
  "repository": "https://github.com/company/release-helper",
  "contributes": {
    "tools": ["tools/release-range.json"],
    "agents": ["agents/release-planner.md"],
    "skills": ["skills/release-workflow/SKILL.md"],
    "runtime": ["dist/extension.mjs"]
  }
}
FieldContract
schemaVersionRequired and exactly 1
extensionApiRequired and exactly 1
idLowercase qualified id such as company.extension-name, 3–100 characters
versionStrict major.minor.patch semver
name / descriptionRequired human-readable metadata
license / repositoryOptional publishing metadata
contributesAt least one non-empty tools, agents, skills, or runtime list; at most 100 contained paths per list

The manifest is strict. Unknown fields and duplicate JSON keys are rejected so a typo cannot silently change package behavior.

Tool contribution

{
  "name": "find_todos",
  "description": "Find TODO comments under a tracked path",
  "parameters": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Repository-relative file or directory"
      }
    },
    "required": ["path"]
  },
  "handler": "git grep -n TODO -- {{path}}",
  "source": "user"
}

Tool names use lower snake case. parameters must be a JSON Schema object. Every handler placeholder must be declared and required; values are shell escaped when rendered. The handler is screened for unsafe patterns at validation and still requires normal authorization when invoked.

Never embed credentials. Extension packages can be inspected and shared. Let commands read required secrets from the user's environment, and document the variable without including its value.

Agent contribution

JSON agents use the existing fields description, systemPrompt, tools, and optional model. Markdown agents use the file name as the agent name and may declare frontmatter:

---
description: Review maintainability risks
tools: read_file, fff_grep, find_todos
---
Review the requested code and return evidence-backed findings.
Preserve working contracts and propose the smallest safe remediation.

If frontmatter omits tools, the definition requests all available tools. That does not grant access: Autohand resolves names against the final runtime registry, applies context filtering, and enforces permissions on every call.

Agent Skill contribution

Declare the entrypoint SKILL.md for each portable skill. The file uses the normal Agent Skills contract and may reference files inside its own skill directory. Enabled extension skills appear in $ suggestions and /skills; an exact mention activates the instructions for that turn.

---
name: release-workflow
description: Prepare an evidence-backed release checklist.
---

Use `release_range` to establish the exact revision boundary.
Do not claim readiness without current validation evidence.

Pi and other Agent Skills that already use SKILL.md are directly portable when their instructions and referenced resources remain valid in Autohand.

Trusted runtime entrypoints

A runtime path must resolve to compiled .js, .mjs, or .cjs. Validation checks the file and package contract without importing it. Installation requires --trust; trusted code then runs inside the Autohand process with the same operating-system access and is not sandboxed.

export async function activate(api) {
  api.commands.register({
    command: '/deploy',
    description: 'Open the deployment workflow',
    execute(context) {
      return `Preparing ${context.args[0] || 'staging'}`;
    },
  });

  return async () => {
    // Release extension-owned resources.
  };
}

export async function deactivate() {
  // Optional cleanup on reload, disable, or removal.
}

The module may export activate(api), a default activation function, or a default object with activate. api.version is 1. TypeScript authors can import ExtensionRuntimeAPI from autohand-cli for source checking, then ship compiled JavaScript.

--trust is a code-execution decision. It is neither a sandbox nor a permission shortcut. Review source, emitted code, and bundled dependencies before trusting a package.

Runtime registration surfaces

APIContributionKey constraints
api.commands.registerSlash commandsLowercase command such as /deploy; built-ins cannot be replaced.
api.ui.registerViewInk menus, dialogs, renderers, and editor-like viewsUse host api.ui.React and api.ui.Ink; Autohand owns modal pause/resume and terminal cleanup.
api.ui.setStatusLine, setHelpLineStatus/help segmentsStable extension-specific ids and semantic colors.
api.keybindings.registerKeyboard shortcutsRoutes to a registered command; Escape, Enter, Ctrl+C, Ctrl+D, and Shift+Tab are reserved.
api.cli.registerFlagStartup optionsMust include a unique long --kebab-case flag and register before Commander parses input.
api.hooks.onLifecycle handlersUses the normal hook event and response contract.
api.providers.registerLLM providersProvider id uses extension:; settings live under extensionProviders.
api.permissions.registerPolicyPermission policyMay contribute lists, rules, tool patterns, and path/URL policy; cannot replace mode, decision cache, or immutable security.

Registration is transactional per extension. One malformed, duplicate, reserved, or conflicting registration rejects that extension's activation instead of leaving a partial command, UI, provider, or policy surface.

Runtime provider configuration

{
  "provider": "extension:company-release",
  "extensionProviders": {
    "extension:company-release": {
      "model": "release-model",
      "apiKey": "replace-locally",
      "baseUrl": "https://models.example.com"
    }
  }
}

Keep credentials in user configuration or environment variables, never in the package. Provider implementations receive their named extension settings and the complete root config and must implement Autohand's provider contract.

Path and input constraints

  • Contribution paths use /, are relative to the package root, unique in their list, and no longer than 240 characters.
  • Absolute paths, drive-letter paths, backslashes, empty segments, ., .., NUL bytes, and path traversal are rejected.
  • Declared contributions must resolve to regular files inside the real package root. Contribution symlinks are rejected.
  • Manifests are limited to 64 KiB; each contribution is limited to 256 KiB.
  • Manifest and contribution text must be valid UTF-8.
  • A package cannot reuse a built-in, standalone, or already-active declarative or runtime identity.

Compatibility and publishing contract

Package against extensionApi: 1, validate with the oldest Autohand Code version your team supports, and test both linked and copied installation. Keep each package independently installable and include purpose, validation, installation, trust, permission behavior, daily use, and removal instructions in its README.

Extension API v1 installs only from a local directory. For distribution, publish an immutable tag or release, have users check out that pinned version, and install the local directory. Installing directly from an unpinned remote URL or Git branch is intentionally unsupported.

autohand extensions validate ./company.release-helper
autohand extensions install ./company.release-helper --link --trust
autohand extensions show company.release-helper
autohand extensions doctor
autohand extensions disable company.release-helper
autohand extensions enable company.release-helper
autohand extensions remove company.release-helper --yes
autohand extensions install ./company.release-helper --trust
autohand extensions show company.release-helper

Release checklist

  1. Validate the source package and save machine-readable output in CI.
  2. Install with --link during development; add --trust only after reviewing every runtime file and bundled dependency.
  3. Exercise every tool, agent, skill, command, view, line segment, shortcut, flag, hook, provider, and permission contribution the package declares.
  4. Confirm expected approval prompts and denial behavior; never test only unrestricted mode.
  5. Run doctor with both user and project extensions present.
  6. Test disable, enable, and removal, including current-session refresh.
  7. Install a copied package and start a fresh Autohand process.
  8. Test on each supported operating system when handlers or paths are platform-sensitive.
  9. Run component tests plus a real PTY/Tuistory acceptance test for terminal UI and shortcut behavior.
  10. Publish an immutable version and keep the manifest version aligned with the release.

Ready to build? Follow Authoring your first declarative extension or Build a trusted runtime extension.