Sharing a streamline by link: listing.json, what the app computes, signed versions
An organization shares its own streamlines at an unguessable link, https://streamliner.work/s/<id>. Owners and admins do it from the streamline's Library editor (Share Link). The link belongs to the organization: its page says Shared by <organization>, and any of the organization's owners and admins can stop it. Members don't share. Personal streamlines aren't shared by link (move one to an organization first); a .streamline.json file can still be sent by hand. Anyone with the link sees a listing page and taps Get to add it to On this device. Links aren't searchable or reviewed; there is no public catalog yet.
Two switches decide it. The streamline opts in, in its private half, and stays unshareable until an owner or admin turns it on and publishes:
// private/agents/<kind>.json
{ "sharing": { "link": true } }
The organization can turn sharing off for every streamline (private/tenant.json discover: { sharing: "off" }; the default is "admins"). Keep streamlines like payroll off.
What a shared copy is. A recipient's copy always runs on the recipient's own model key, never the organization's (the relay and the organization's key serve invited members only). So the app builds the shared file in the personal format (personal-streamline.md) from both published halves: the instructions (briefs written in first), the model, the servers and their tool rules (a tool that waits for a tap still does; the allowlist becomes tools.allow; deny becomes hidden), the cards, welcome and quick phrases. It leaves out what never leaves the account: credentialRef and any key reference, roles (and tools only some roles may use), members and the directory, network rules, budgets and limits, the fallback model, sealed forms and views, the overview, and everything in private/tenant.json. configcheck warns sharing.leftOut when a shareable streamline relies on one of those. Before the first share, and before any version that widens what runs without a tap, the app shows what becomes public and asks for Face ID. The server can't read the opt-in (the shared file has no private half), so the app enforces it; the server decides who may publish (owners and admins) and scans every file for keys.
This page is the format, so you can prepare and check a listing folder.
Contents
- The folder
listing.json- What the app computes, never you
- How a version is signed
- Checklist
The folder
find-a-flight/
listing.json
kiwi-flights.streamline.json its "id" is the one listing.json names
Check it with node scripts/check.mjs find-a-flight/ (configcheck): the schema, and that every id in streamlines names a streamline file beside it (listing.streamline-missing).
listing.json
{
"$schema": "https://streamliner.work/schema/v1/listing.schema.json",
"schema": "streamline.listing/1",
"name": "Find a Flight",
"subtitle": "Kiwi.com · Made by Streamliner, not by Kiwi.com",
"summary": "Say where and when. Get flights with prices and a button to book.",
"streamlines": ["kiwi_flights"],
"version": "1.0",
"releaseNotes": "First version.",
"service": { "name": "Kiwi.com", "domain": "kiwi.com" },
"visibility": "link",
"ageRating": "4+"
}
| Field | Req | Notes |
|---|---|---|
schema | yes | streamline.listing/1 |
name | yes | ≤ 60 characters; usually the streamline's name |
subtitle | no | ≤ 80; say Made by …, not by <Company> when you built it for someone else's product |
summary | yes | ≤ 300, plain text |
streamlines | yes | exactly one id for a link share (a pack waits for the catalog; listing.pack warns) |
version | yes | what people see: 1, 1.2, 1.2.3 |
releaseNotes | no | ≤ 2000 |
support.url | no | https; never a sign-up or pricing page |
service | no | { name, domain }; never set claimed (only Streamliner does, after a domain check) |
visibility | no | link; public is refused until review exists |
ageRating | no | 4+, 9+, 12+, 17+ |
category | no | business, money, work, developers, everyday (for the catalog, later) |
What the app computes, never you
The listing page's label (what it connects to, whether you sign in, what runs without asking, what asks first, which links buttons may open) comes from the streamline file itself, not from listing.json. Write the tool policy honestly in the streamline (tool-policy.md); the label shows it. scripts/listing-label.mjs in the repo prints the same label.
How a version is signed
The app signs each version with the device's own sharing key: a manifest (streamline.listing-version/1) listing listing.json and streamlines/<id>.streamline.json with their sha256, signed Ed25519 over its JCS bytes. The listing's channel is the key's fingerprint, so a reader checks the signature and the channel without trusting the server. Sharing again publishes what the organization has published since, as the next version at the same link, from the device that first shared it. Stop Sharing (any owner or admin) ends the link; copies people already got keep working.
Checklist
- [ ] The streamline's private half says
"sharing": { "link": true }, published. - [ ] One streamline, its
idset, and the listing names it. - [ ] No key, token or sign-in anywhere (the server refuses a key-shaped string:
has_secret). - [ ] Every MCP server is
https://. - [ ] configcheck is clean on the folder.