The account folder, file by file

Folder layout, manifest, tenant.json, private/tenant.json, accepted paths

Raw Markdown the file as the skill ships it All pages ↓

Schema streamline.config/1. JSON Schemas: https://streamliner.work/schema/v1/<kind>.schema.json (also in the Streamline repo under schema/). Run the checker after every edit (SKILL.md step 5).

Contents

  • Layout
  • manifest.json
  • enrolment.json
  • tenant.json
  • private/tenant.json
  • Paths the write API accepts
  • URLs

Layout

<account>/
  tenant.json                    brand, locale, auth, roles, kinds (streamline order)          device-visible
  agents/<kind>.json             one streamline: what people see (streamlines.md)               device-visible
  templates/<id>.json            one view template (templates.md)                               device-visible
  forms/<id>.json                one sealed form / hosted page / sealed view (forms.md)          device-visible
  assets/<sha256>.png|jpg|pdf    logos and images; the file name prefixes the content's sha256   device-visible
  manifest.json                  signed index of every device-visible file
  enrolment.json                 the publisher public keys devices pin (not part of the signed set)
  private/tenant.json            account policy: networks, access seeds, serverKeys, budgets, transcripts
  private/agents/<kind>.json     instructions, model, credentialRef, MCP servers + tool allowlist, network, limits
  private/connections/<id>.json  login details for password / api-key connections (back end only)
  private/briefs/<id>.md         a brief: shared instructions streamlines include (briefs.md)      relay only

Device-visible files are served only to signed-in members, and each member gets only the streamlines their roles open. private/ never reaches a member's device (an owner's or admin's approved device reads it to edit and publish it). Templates and forms are account-level and shared by id across streamlines.

manifest.json

Written by scripts/sign-config.mjs, never by hand.

{ "schema": "streamline.config/1", "tenant": "meridian", "version": 9, "published": "2026-10-09T17:40:00Z",
  "minApp": "1.0.0 (1)", "refresh": { "foregroundSeconds": 900, "minSeconds": 60 },
  "files": [ { "path": "tenant.json", "sha256": "4b1f…", "bytes": 3120 } ],
  "signature": { "alg": "Ed25519", "keyId": "meridian-2026a", "sig": "base64url…" } }
  • version must go up on every publish, including a change to private/ alone.
  • files[] lists every device-visible file (not private/, manifest.json, enrolment.json).
  • signature.sig is Ed25519 over the JCS (RFC 8785) bytes of the manifest without signature, by one of the account's publisher keys. Optional nextKeys: [{keyId, pub}] rotates keys (devices trust them because the current key signed them).

enrolment.json

{ "tenant": "meridian", "configHost": "/", "keys": [ { "keyId": "meridian-2026a", "pub": "<base64url Ed25519 public key>" } ] }

Public halves only. Changing it changes the keys new devices pin; treat it as an owner-only change.

tenant.json

{
  "id": "meridian",
  "mode": "enterprise",
  "brand": { "name": "Meridian Jewelers", "shortName": "Meridian", "logo": "assets/3f9a0c6e1d….png",
             "accent": { "light": "#1F4E79", "dark": "#8DB8E8" } },
  "locale": { "language": "en-US", "currency": "USD", "timeZone": "America/Los_Angeles" },
  "auth": { "kind": "invite" },
  "gateway": { "url": "https://<back end>/v1" },
  "connections": [ { "id": "pos", "name": "Meridian POS", "type": "oauth" } ],
  "roles": [
    { "id": "associate", "name": "Sales associate", "claims": { "groups": ["mj-associates"] } },
    { "id": "store_manager", "name": "Store manager", "claims": { "groups": ["mj-managers"] } }
  ],
  "kinds": ["register", "approvals"],
  "imageHosts": ["img.meridian.example"],
  "policy": { "chatRetentionDays": 2, "appLockAfterSeconds": 300, "sealedScreenshots": "blur" },
  "support": { "text": "Questions? Call the help desk at ext. 400.", "url": "https://help.meridian.example" }
}
FieldNotes
id[a-z0-9-]{3,40}; equals the account id the write API uses.
modeenterprise (an organization), personal, or demo (scripted agent, mock backend; the only mode that may have card fields).
brandname, shortName (≤ 16 chars), logo (an assets/ path), accent and optional background as {light, dark} #RRGGBB, optional tagline (one line under the name on the join screen and in Settings).
themeThe account's baseline theme: a preset (base, the default; quiet, harbour, refined-calm, amber; light-only paperwhite, dark-only nord) or one of themes[].id. Keep brand.accent equal to its accent. Colors only.
themes[]Up to 8 own themes: { id, name, light, dark? }, each side a shared ColorTheme id or a ColorTheme document (hex colors under surfaces, accents, semantics, text, actions).
localeBCP 47 language, ISO 4217 currency (the money formatter's default), IANA time zone.
auth.kindinvite (the default: Sign in with Apple, passkey or email code; memberships from invites or directory sync), oidc (the organization's IdP: issuer, clientId, scopes, resources, roleClaim), connection (sign in to a named connection), demo. See access-and-networks.md.
gateway.urlThe back end's base URL (relay, connections, enrollment). Given to you by Streamliner.
connections[]The systems people sign in to (access-and-networks.md).
roles[]Job roles: id, name, claims (for OIDC: claim → accepted values). Streamlines and tools name role ids.
kinds[]The streamlines, in sidebar order. Each needs agents/<kind>.json and private/agents/<kind>.json.
imageHosts[]The only hosts template images may load from.
policychatRetentionDays (default 2 for organizations), appLockAfterSeconds, sealedScreenshots (blur), attachmentsMaxBytes, uploadRetentionDays (1–30, default 7), voice (false turns the mic and Speak replies off account-wide), syncAccountList (false keeps the organization out of members' organization list in iCloud, which otherwise shows it on their other devices, signed out; default true).
supporttext, url, optional email (where Report an Issue offers a mail link about this account's streamlines).

private/tenant.json

{
  "network": { "mode": "none" },
  "networks": { "head-office": { "name": "Head office", "cidrs": ["203.0.113.0/24"] } },
  "roles": { "regional": { "networks": ["head-office"] } },
  "users": { "u-marco": { "networks": ["head-office"] } },
  "access": { "u-marco": "admin" },
  "serverKeys": [ { "id": "claude-setup-oct", "alg": "Ed25519", "publicKey": "<base64url 32 bytes>", "scopes": ["config"] } ],
  "budgets": { "warnAt": 0.8, "perUser": { "tokensPerDay": 3000000, "costPerDayUSD": 5 } },
  "transcripts": { "retentionDays": 365 }
}
  • serverKeys holds public halves only. A publish that would leave no enabled key with the config scope is refused (would_lock_out), so keep the key you publish with.
  • Networks, budgets and transcripts: access-and-networks.md.

Paths the write API accepts

tenant.json, enrolment.json, manifest.json, agents|templates|forms/<id>.json, assets/<sha256 prefix>.png|jpg|jpeg|pdf, demo/backend.js|agent.js|fixtures.json (demo accounts), private/tenant.json, private/agents|connections/<id>.json, private/briefs/<id>.md. Anything else is refused (bad_path): keep notes, fixtures and drafts outside the folder.

URLs

Absolute https:// everywhere. A URL starting with / resolves against the config host (demo accounts only). http:// is refused except http://localhost for local testing.