Package authoring guide
Official Kody guide
Use this guide when creating a new Kody package or materially changing an existing one.
Choose an authoring lane
There are two lanes for writing package source. Pick based on whether you have local filesystem and git access:
- Git lane (coding agents — preferred). Call
package_get_git_remotewithcreate: trueand a newkody_idto register a stub saved package and mint a short-lived authenticated remote in one call (for existing packages, omitcreate). Run the returnedsetup_commandsto clone into a temporary directory, edit normally — binary assets, multi-file refactors, and local build/test loops all work — commit, push, then publish withpackage_publish_external_push. - Tool-only lane. Without local filesystem/git access, create with
package_save(complete UTF-8 text file set; no binary files) and edit through repo sessions (repo_open_session,repo_edit_files,repo_write_file,repo_commit, thenrepo_run_checksbeforerepo_publish_session).
If a request needs binary assets, many-file changes, or local build/test loops and you are tool-only, tell the user the task fits a coding-capable agent better and confirm before proceeding.
README Intent section
Package intent is human-authored guidance, not a Kody primitive. Keep it in the
root README.md so agents see it during package creation, updates, and search
detail review.
When you create or materially change a package:
- Include or maintain a
## Intentsection inREADME.md. - Capture the user's goal in a few concrete sentences.
- Ask the user when the intent is unclear or underspecified.
- Update the intent only when you are confident the goal changed.
- If the user expands the package scope, update the section with the new scope.
Do not add a package manifest field, runtime object, saved value, or other Kody primitive solely to track intent.
Minimal shape
# Package Name
## Intent
This package exists to ...Keep the section concise. It should explain why the package exists and what success means for the user, not duplicate every implementation detail.
Package app routing
Hosted package apps are mounted at /@username/packages/<kody-id>/<path>. The
app receives only /<path> in its fetch request. Root-relative links such as
/audio/123 therefore escape the mount and are not routed back to the package
app.
Import packageContext from kody:runtime and build every in-app link,
redirect, share/email URL, and OAuth callback against its public base:
import { packageContext } from 'kody:runtime'
if (!packageContext?.hostedUrl) {
throw new Error('This module must run as a package app.')
}
const audioUrl = new URL(
`${packageContext.appBasePath}/audio/123`,
packageContext.hostedUrl,
)packageContext.hostedUrlis the full public URL of the app mount.packageContext.appBasePathis the origin-relative mount path.
Kody derives both fields from the package's current serving username and
kody.id, including after a rename or fork. Do not hard-code either path
segment.
kody.description (short public tagline)
package.json#kody.description is a short public tagline, not a feature
dump. Aim for about 80–120 characters (hard max 200). Prefer outcome
phrasing such as “Send transactional email via Resend” over inventory lists of
exports, auth, or APIs.
Put feature lists, API surface, auth notes, and longer guidance in README.md
(including ## Intent), kody.searchText, and export/JSDoc docs — not in
kody.description. Community listings and Open Graph share cards reuse this
field, so keep it concise.
Package visibility (private)
Default new saved packages to "private": true in package.json unless the
user explicitly wants public community publishing.
Like npm, "private": true blocks community listings on this deployment.
Account publishing is unaffected, so the owner can run the package privately.
- Set
"private": truewhen creating or forking a package unless the user asks to share it publicly. - Require explicit user approval before changing
"private"or creating a package without"private": true. - Pass
confirm_private_visibility_change: trueonpackage_saveor repo publish only after that approval. - Community publish additionally requires
"private": falseor omittingprivate, plus MIT license and README## Intent.
Secret-using packages
When a package will use user-scoped secrets ({{secret:name}} placeholders or
kody.secretMounts):
- Ensure each secret exists (see
guide: "connect_secret"/guide: "secret_backed_integration"). - Self-authored packages (and community forks adopted with
community_fork_adoptafter a real source review) get automatic read/use access to user secrets (host approval still applies;secret_set/secret_deletestill need anallowed_packagesgrant). After save/publish, readpending_secret_package_approvalsfrom the tool result — it is non-null only for unadopted community forks. - When pending approvals are present, either review the fork source and call
community_fork_adoptwith areview_summary, or send the userbulk_approval_url/ eachapproval_url. - Wait for approval or adoption (when required), then smoke-test with a keyless
packages.invoke(...)call. The smoke test must go throughpackages.invoke— it runs the export in the package's own runtime, sokody.secretMountsmounts are exercised; a static import cannot verify those. Use a read-only export or a package-supported dry-run input that actually reads the approved secret (for example an authenticated read-only API call), so the smoke test proves secret access without external side effects. - Only then treat the package as ready to run.
Host approval (from an earlier ad hoc execute smoke test) is separate from
package approval. Unadopted community-forked packages may need both;
self-authored and adopted packages still need host approval when outbound calls
require it.
Verify your publish
After publish succeeds — and after any required secret approvals — run synthetic smoke tests for every declared surface before calling the package complete. Synthetic invocations are real-surface runs with real side effects; use a deliberately visible irreversible-side-effect guard when a smoke test should stay safe.
- Read
test_hintson thepackage_publish_external_pushresult when present. It lists copy-pasteable calls for declared apps and subscription topics. - Exports and secret mounts — keyless
packages.invoke({ kodyId, exportName, params })against a read-only export or package-supported dry-run input that exercises approved secrets (see Secret-using packages above). - Package apps —
package_app_fetch({ kody_id })with the path, method, and body your handler needs. Confirm{ status, headers, body, truncated }and anypackageStorage()side effects. See Package app fetch. - Subscriptions —
package_subscription_dispatch({ kody_id, topic, … })with exactly one ofparams(fixture) oremail_message_id(stored-mail replay) for each declared topic. See Synthetic event dispatch and the package subscriptions guide. - Optional UI checks — open
hosted_app_urlwhen the publish response includes one; synthetic app fetches do not replace browser verification for layout, OAuth redirects, or websocket facets.
Only after these checks pass (or the user explicitly skips a surface) treat the package as ready to run.
Community icon
Public community packages should include one root community-icon.svg,
community-icon.png, community-icon.webp, community-icon.jpg, or
community-icon.jpeg. Prefer a square visual with a simple silhouette that
remains legible at 56 pixels. Keep it under 2 MiB and 16 megapixels. Kody
generates a package-name fallback when the repository has no icon.
Publishing the package refreshes the community listing icon automatically. The candidate paths win in the order listed above, so when replacing an icon with a different format (for example svg → png), delete the old file in the same commit or the old format keeps being served.