Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/onepassword

Resolve 1Password Connect item fields for secret-aware fetch with website host allowlisting.

AGENTS.md

107 lines · 5.2 KB · Markdown

@kentcdodds/onepassword — agent notes

Human intent and setup live in README.md.

Exports

SubpathUse
.Overview / bind checklist / Connect-on-Fly pointers. Safe to import from execute.
./secretProviderSealed. Platform fetch boundary only. Do not kody:@ or invoke.
./connectDescribe Connect-on-Fly sub-exports.
./connect/sourceDeployable compose / machine-config / fly.toml (no secrets).
./connect/deployment-guideMarkdown deploy + bind steps.
./connect/deploy-flyMachines API create/update Fly Connect app. Needs secrets approved.

Overview smoke

import onepassword from 'kody:@kentcdodds/onepassword'

export default async function main() {
	return await onepassword()
}

Connect on Fly

  1. Check secretList for ONEPASSWORD_CONNECT_OP_SESSION before deploy.
  2. If missing, give Kent the prefilled URL — do not invent credentials or deploy.
  3. Value must be Base64(1password-credentials.json), not raw JSON. Package code cannot Base64 without reading the secret; placeholders only expand inside secret-aware fetch.
  4. If only ONEPASSWORD_CONNECT_CREDENTIALS_JSON exists (often raw JSON), do not fall back — tell Kent to save Base64 as ONEPASSWORD_CONNECT_OP_SESSION.
  5. Host allowlist on ONEPASSWORD_CONNECT_OP_SESSION and flyApiToken: api.machines.dev, api.fly.io. Package grant for this package is usually already done.
  6. App name default: kody-onepassword-connect (check @kentcdodds/fly/apps for collisions).
  7. Deploy with Machines API helper (not flyctl):
import deploy from 'kody:@kentcdodds/onepassword/connect/deploy-fly'

export default async function main() {
	return await deploy()
}

Returns { appName, url, region, bind, ... } — never secret values.

Deploy mounts Base64 credentials via Machines config.files.raw_value from {{secret:ONEPASSWORD_CONNECT_OP_SESSION|scope=user}} to /home/opuser/.op/1password-credentials.json, sets container env OP_SESSION to that path, attaches shared temp_dir opdata at /opdata (XDG_DATA_HOME; Fly cannot share one persistent volume across two containers — Connect re-syncs), unsets any Fly app secret named OP_SESSION (so Base64 is not injected as env), starts sync before api, bus 11220/11221, OP_SYNC_TIMEOUT=60s. Default memory 1024mb.

Prefills:

  • OP_SESSION (Base64 credentials file bytes for config.files): https://kody.codes/account/secrets/new?name=ONEPASSWORD_CONNECT_OP_SESSION&description=Base64%20of%201password-credentials.json%20(no%20newlines).%20deploy-fly%20writes%20it%20via%20Machines%20config.files%20raw_value%3B%20OP_SESSION%20env%20is%20the%20credentials%20file%20path.&allowedHosts=api.machines.dev%2Capi.fly.io&scope=user
  • flyApiToken hosts: https://kody.codes/account/secrets/new?name=flyApiToken&description=Fly.io%20API%20token%20for%20Machines%20API%20and%20GraphQL%20(api.machines.dev%20%2B%20api.fly.io)&allowedHosts=api.machines.dev%2Capi.fly.io%2Cdocs.machines.dev%2Cfly.io&scope=user
  • Token (include Fly host in allowedHosts): https://kody.codes/account/secrets/new?name=ONEPASSWORD_CONNECT_TOKEN&description=1Password%20Connect%20Server%20token%20(Authorization%3A%20Bearer)%20used%20as%20the%20door%20key%20for%20the%201password%20secret%20provider%20and%20API%20smoke%20tests&allowedHosts=kody-onepassword-connect.fly.dev&scope=user

Binding (after the door secret exists)

import { kody } from 'kody:runtime'

export default async function main() {
	return await kody.secretProviderBind({
		provider: '1password',
		package_id: '7603a38f-6d3f-44e4-beca-b10196b38008',
		door_secret_name: 'ONEPASSWORD_CONNECT_TOKEN',
		config: { connectHost: 'https://kody-onepassword-connect.fly.dev' },
	})
}

Accepts connectHost, connectUrl, or baseUrl (normalized, no trailing slash). Auth header is Authorization: Bearer <doorSecretValue> from invoke args — never call secret_get for the door key inside the provider.

Resolve / canonicalize contract

  • canonicalize{ canonicalRef } only (local id forms; no value).
  • resolve{ value, hosts, canonicalRef? }.
  • Empty hosts must throw before return (fail closed).
  • Hosts from item.urls[].href and URL-typed/labeled fields (Secure Notes).
  • Never log doorSecretValue or value.
  • Budgets: stay under ~8s resolve / ~5s canonicalize.

Grants

secretProviderLock only accepts locally canonicalizable refs (UUID or 26-char Connect item id ^[\da-z]{26}$ in the item segment). Prefer i/<item-id>/<field> (Connect ids are usually 26-char, not UUID).

Edge cases

  • Scan vaults with GET /v1/vaults, then GET /v1/vaults/{vaultId}/items/{itemId} until HTTP 200 (skip 404).
  • Missing field / missing item / bad token → clear next-action errors, no secret material in messages.
  • Name-based op://Vault/Item/field → clear error asking for item-id form (lock/grant stay local; this package does not crawl titles to canonicalize).
  • Secure Notes: websites list may be empty — require a Website or a URL field pointing at the fetch host so resolve can allowlist hosts.
  • Fly multi-container shares a network namespace: distinct OP_HTTP_PORT / OP_BUS_PORT and localhost peers (no NET_BROADCAST).