How Kalup works
The files, the two sources of truth, the IR, and what pull does today
Kalup keeps a HubSpot portal's configuration in files in your repository. You describe what the portal should look like, Kalup reads the portal and brings the files up to date, and your app gets its types from the same files. This version reads portals and never writes to them.
The flow
HubSpot portal your repository your app
(what exists) (what you intend)
┌──────────────┐ kalup pull ┌──────────────────────┐ import ┌────────────────┐
│ properties │ ───────────────► │ kalup/objects/*.ts │ ────────────► │ types, codecs │
│ groups │ read only │ kalup.config.ts │ │ get() and set()│
│ custom │ └──────────┬───────────┘ └────────────────┘
│ objects │ │ parsed, never executed
└──────────────┘ ▼
▲ ┌──────────────────────┐
│ │ IR (ir/1) │ ◄── kalup validate, kalup ir
│ └──────────┬───────────┘
│ coming ▼
└────────────────────────── plan ──► applyThe top row works today. The bottom row, plan and apply, is coming in a later release.
The files
| Path | What it is | Commit it |
|---|---|---|
kalup.config.ts | The project: its targets and which objects are in scope | yes |
kalup/objects/*.ts | One file per object: its property groups and properties | yes |
kalup/index.ts | The barrel your app imports. Kalup writes it | yes |
.kalup/ | Copies of files before Kalup overwrote them, and later, state per target | no |
kalup init creates the first three and adds .kalup/ to .gitignore. Config files explains what goes inside each one.
Parsed, never executed
The config files look like TypeScript, and your app runs them for types and codecs. Kalup itself never runs them. It reads a restricted grammar: the builder calls, plain values and the comments in front of them. It then writes the files back in one canonical form.
This has three consequences:
- A file cannot run code during a pull or a check. An AI agent can edit it without any risk that the edit executes.
pullcan rewrite a file without losing your edits. Kalup understands every part of the file, so it keeps your keys, your comments and your options, and only changes what the portal changed.- Loops, spreads, variables and function calls are rejected.
kalup validatereports them asE_NOT_DATAwith the file, the line and a fix. Write the values out in full.
Two sources of truth
The files are the truth for what you intend. The portal is the truth for what exists. Kalup never pretends one is the other.
Today that means pull brings the portal's side into the files, and your edits in the files stay yours until the portal changes the same field. The later releases add the other direction: plan shows how the portal differs from the files, and apply writes the reviewed differences. Two rules are fixed now for when that arrives:
- Drift is held, not reverted. When someone edits a field in the HubSpot UI, the plan reports it and does not overwrite it unless you ask.
- Absence never deletes. Removing a property from a file never deletes it in the portal. A delete will need an explicit tombstone and a person at a terminal.
The IR is the contract
Kalup compiles the files into one JSON document, the IR (ir/1). Every resource in it is keyed by its address, for example property:companies/billing_status.
npx kalup irThe IR holds no portal IDs inside definitions, no keys and no credentials. Everything that is not the TypeScript files reads the IR, never the files themselves. That includes validate, and later plan, the docs generator and your own tools. The IR has a published JSON Schema and only changes additively within a version. See kalup ir.
What happens on pull today
kalup pull --target sandbox does these steps in order, and stops at the first failure without writing anything:
- It validates the project offline, the same checks as
kalup validate. - It reads the key for the target, from the environment or from
.env. - It asks HubSpot which portal the key belongs to. When that is not the portal pinned for the target, it stops with exit 4. See Targets and credentials.
- It reads the objects in scope: their property groups and properties, and the schemas of custom objects.
- It merges what it read into the object files, keeping your keys, comments and aliases.
- It copies each file it is about to change into
.kalup/history/, then writes the new files and regenerateskalup/index.ts.
A second pull with no change in the portal writes nothing. kalup pull has the scope rules, the merge rules and the flags.
Read-only, by construction
This version sends only read requests to HubSpot. Every request goes through one registry of endpoints, each tagged read or write, and the HTTP layer refuses a path that is not tagged read. The test suite fails if anything else is ever called. See Network and safety.
Coming later: compare, plan, snapshot, docs and apply. In this version they print not implemented yet and exit 1. Do not script against them yet.