Skip to content

Manifest format

vibebeacon.json version 1. This is the only thing we accept, and the only thing we hold. If a field is not on this page, the validator rejects it.

A complete example

vibebeacon.json
{
  "schemaVersion": 1,
  "generator": { "name": "vibebeacon-cli", "version": "0.1.0" },
  "generatedAt": "2026-09-03T10:00:00Z",
  "app": {
    "name": "My Coffee Shop POS",
    "description": "Orders and payments for one cafe",
    "kind": "web",
    "hosting": ["vercel", "supabase"],
    "aiTools": ["cursor", "claude-code"]
  },
  "runtimes": [
    { "name": "node", "version": "22.11.0" }
  ],
  "frameworks": [
    { "name": "next", "version": "15.1.0", "ecosystem": "npm" }
  ],
  "dependencies": [
    { "name": "react", "version": "18.3.1", "ecosystem": "npm", "direct": true, "dev": false },
    { "name": "stripe", "version": "17.4.0", "ecosystem": "npm", "direct": true, "dev": false }
  ],
  "services": [
    { "name": "stripe" },
    { "name": "supabase" }
  ],
  "infrastructure": [
    { "name": "postgres", "version": "16" }
  ]
}

Top level

FieldTypeRequiredNotes
schemaVersionnumberYesAlways 1 today. We reject anything we do not understand rather than guessing.
generatorobjectYesname and version of whatever produced the file. Use "manual" if a person or an AI tool wrote it by hand.
generatedAtstringYesISO 8601 in UTC. Used to work out manifest age and to nudge you when it goes stale.
appobjectYesDescribes the app itself. See below.
packageManagerenumNonpm, pnpm, yarn, bun, pip, poetry, uv, cargo, go, bundler or composer. Absent means unknown; it only changes which install command an alert suggests.
workspacesobjectNocount and ecosystems only, for a project spread over several folders. Absent when one lockfile was read. It never holds a folder name or a path.
runtimesarrayNoLanguage and platform runtimes, such as node or python.
frameworksarrayNoFrameworks worth calling out separately, such as next or django.
dependenciesarrayNoPackages. Up to 2000 entries.
servicesarrayNoThird-party services you call, such as stripe or supabase. Name only.
infrastructurearrayNoDatabases, proxies, container bases, such as postgres 16 or docker.

The app object

FieldTypeRequiredNotes
app.appIdstring (uuid)NoThe app's id, printed back into the manifest file after your first upload. When present and you still own it, this identifies the app instead of the name; a mismatched owner is rejected rather than silently reassigned.
app.namestringYes1 to 80 characters. Slugged to identify the app when appId is absent. Uploading the same slug updates that app.
app.descriptionstringNoUp to 300 characters. Only for your own benefit in the dashboard.
app.kindenumYesweb, api, mobile, desktop, cli or other.
app.hostingstring[]NoProvider names such as vercel or fly. Free text up to 40 characters each.
app.aiToolsstring[]NoWhich tools built it, such as cursor or lovable. Used for aggregate stats only.
app.groupstringNo1 to 50 characters. Assigns the app to a group, creating it if it does not exist yet.
app.tagsstring[]NoUp to 20 tags, 1 to 40 characters each. Replaces the app's tags outright, so send the full list, not just new ones.

Component entries

Entries in runtimes, frameworks, dependencies, services and infrastructure share one shape. Services often carry a name and nothing else, which is fine.

FieldTypeRequiredNotes
namestringYesPackage or runtime name, lower-cased on our side before matching.
versionstringYesExact resolved version where possible, such as 18.3.1.
ecosystemenumYesRequired on dependencies and frameworks. Runtimes, services and infrastructure do not carry one.
directbooleanNoTrue if it is in your package file rather than pulled in by something else. Defaults to true.
devbooleanNoTrue for dev-only dependencies. Scanned by default. Instant email and health scoring skip these until you opt in; the app page groups them into their own "Dev tooling" band instead of hiding them.
resolvedbooleanNoSet false when the version came from a range rather than a lockfile. We then match conservatively.

Identity, groups, tags and package manager

A manifest can carry more than the stack. Once an app has an id, the CLI writes it back into the file on disk so later uploads land on the same app when you rename it. group and tags can also be set from the manifest, or later from the dashboard; see Groups and tags. packageManager only changes which install command an alert suggests, for example pnpm add instead of npm install.

A project with a frontend folder and a backend folder is one app, and the scan reads the lockfile in each of them. When it read more than one, the manifest carries a workspaces object with two fields: how many lockfiles were read and which ecosystems they covered. That is all it can hold. Folder names and paths stay on your machine.

vibebeacon.json
{
  "schemaVersion": 1,
  "generator": { "name": "vibebeacon-cli", "version": "0.1.0" },
  "generatedAt": "2026-09-03T10:00:00Z",
  "app": {
    "appId": "0f2c9a1e-6d1b-4a5f-9c3e-2b7d8a4f1c05",
    "name": "My Coffee Shop POS",
    "kind": "web",
    "hosting": ["vercel", "supabase"],
    "aiTools": ["cursor", "claude-code"],
    "group": "Client work",
    "tags": ["backend", "stripe"]
  },
  "packageManager": "pnpm",
  "runtimes": [
    { "name": "node", "version": "22.11.0" }
  ],
  "dependencies": [
    { "name": "react", "version": "18.3.1", "ecosystem": "npm", "direct": true, "dev": false }
  ]
}

Links are never in the manifest

An app can carry up to five links (a repo, a status page, a runbook) but they are set from the app page, not the manifest. A link never appears in an upload, an email, or an audit log entry.

Ecosystems we accept

npm, PyPI, Go, crates.io, RubyGems, Packagist, Maven, NuGet, Hex, Pub. These are spelled the way OSV spells them, which is why the capitals look inconsistent.

Versions

  • Give us the exact resolved version from the lockfile wherever you can.
  • A range like ^18.2.0 is stripped to 18.2.0 and the entry is marked "resolved": false.
  • Unresolved entries only produce alerts when every version in the range is affected, so you get fewer false alarms and occasionally a missed match.
  • npm, Go and crates versions are compared with semver. PyPI uses PEP 440 comparison.

Limits

  • 2000 dependencies, 50 runtimes, 100 frameworks, 100 services, 100 infrastructure entries.
  • 512 KB for the whole file.
  • 80 characters for the app name, 300 for the description, 40 per hosting entry.
  • 50 characters for a group name, 20 tags of up to 40 characters each.
  • Sixty uploads a minute per API key, and 10 new apps a day per account.

What gets rejected

Secret-shaped strings

Every string is scanned before anything is stored. AWS key shapes, sk_live_ and ghp_ prefixes, JWT shapes, runs of 32 or more hex or base64 characters, password=, and credentials inside URLs such as ://user:pass@. A match means the whole upload is refused with an error naming the field. Nothing is stored, and the value is not written to our logs.
  • Unknown top-level fields. We do not silently keep extras.
  • A schemaVersion we do not support.
  • Absolute or relative file paths anywhere in a value.
  • Repository URLs and database names.

The reasoning behind all of this is on the privacy page.

Versions of the format itself

Version 1 is what exists today. Version 2 will add an optional components array so you can import a CycloneDX SBOM if you already produce one. Version 1 files will keep working.

Understand the next scan

Inventory refresh is separate from verified remediation. Keep app identity and scan scope consistent, inspect changed alert IDs and reasons, and use the real score explanation. Read scan results and agent access for pending processing, read-only findings JSON and authentication errors. Choose explicit handoff and upload permissions in Fixing an alert.