The local tree
celigo pull downloads your account — or one integration and everything it references — as a folder tree of JSON documents. Edit the files with any editor, review them in git, and write them back with celigo push. The tree is the unit of work for a team: pull requests review the diff, CI gates the plan, and a deploy job pushes the merge.
Seven top-level commands work on the tree. None of them is a resource group; they are bare verbs, like git's.
celigo flows graph, apis graph, and tools graph also read the tree: they render a flow's, an API's, or a tool's Flow Builder graph offline. See flows. The Celigo VS Code extension opens the same graph as an interactive canvas from a pulled flow.json, tool.json, or api.json.
Quick start
# Pull one integration and everything it references into ./acme
celigo pull --integration 5f83a9b2c7d3e8f1a2b3c4d5 --dir ./acme --no-symlinks
cd acme
# Edit a flow, then see what changed
celigo status
celigo diff
# Validate offline, plan the push, push
celigo lint
celigo push --dry-run
celigo push--no-symlinks writes pointer files instead of symlinks; use it for any tree you commit to git (see Everything else is a link). A tree pulled without --integration holds the whole account.
Layout
Three rules generate the whole shape.
Every resource is a folder
The folder is named after the resource, and a fixed file name inside says what it is: exports/Get Orders/export.json, connections/Shopify/connection.json. Payloads you author sit beside the document as editable companions:
script
scripts/<Name>/script.json
script.js — the source
lookup cache
lookup-caches/<Name>/lookup-cache.json
data.json — the key-value entries, in the shape lookup-caches put-data accepts
AI agent
ai-agents/<Name>/ai-agent.json
instructions.md — the system prompt, byte for byte
guardrail
guardrails/<Name>/guardrail.json
instructions.md, for an ai_agent check only
sync
integrations/<Int>/syncs/<Name>/sync.json
datasets/<Dataset>.json — one document per dataset selection
A companion is part of the resource. Deleting instructions.md is not read as "the prompt was cleared": a PUT that omits the prompt would clear it on the server, so push refuses the resource and names the missing file. celigo pull restores it.
AI agents and guardrails are /v1/imports rows split by adaptorType, and the tree gives them their own directories — imports/ holds plain imports only. A step that names one through _importId links into the directory that holds it.
Placement follows ownership
A resource of an ownable type that carries an _integrationId lives inside its integration's folder, mirroring the integration's tabs in integrator.io. Everything else pools at the top level.
flow
integrations/<Int>/flows/[<Flow group>/]<Flow>/flow.json; instance flows in the parent's instances/ folder
sync
integrations/<Int>/syncs/<Sync>/sync.json
tool
integrations/<Int>/tools/<Tool>/tool.json
API
integrations/<Int>/apis/[<API group>/]<Api>/api.json when owned; apis/<Api>/api.json when global. A script-mode API is always global.
MCP server
integrations/<Int>/mcp-servers/<Server>/ when owned, else mcp-servers/<Server>/
Celigo Storage
integrations/<Int>/storage/<Folder>/… for a root folder whose metadata names the integration; else storage/…
export, import, AI agent, guardrail, script, connection, iClient, stack, lookup cache, on-premise agent, EDI profile, file definition, async helper, MCP OAuth provider
pooled at the top level: exports/, imports/, ai-agents/, guardrails/, scripts/, connections/, iclients/, stacks/, lookup-caches/, on-premise-agents/, edi-profiles/, file-definitions/, async-helpers/, mcp-oauth-providers/
Pooling is what the platform enforces, not a preference: the API drops _integrationId from an export, import, lookup cache, or MCP server on write. Connections and lookup caches are registered to integrations (_registeredConnectionIds, _registeredLookupCacheIds), never owned.
Everything else is a link
Every document exists exactly once. A reference from one resource to another is a link named after its target, in a typed subfolder of the resource that holds the reference:
Inside the resource that uses something. A flow folder links its
exports/andimports/; an export links itsconnections/andscripts/. A referenced script is linked by both faces:<Name>.js→ its source and<Name>.json→ its document.Inside an integration, for what it registers — its connections and lookup caches.
Inside an integration, for a tool or API it consumes but another integration owns. The owner never links its own.
References the API keeps inside URI strings (/v1/connections/<id>/proxy, /v1/lookupcaches/<id>/getData, /v1/apis/<id>/request) are linked too, from exports, imports, AI agents, guardrails, and script source. A reference to a resource that does not exist produces no link, and pull warns about it.
A link is a real symlink by default. celigo pull --no-symlinks writes a .link.json pointer file instead — a small JSON record holding { _id, type, name, target }. Use pointer files for trees shared through git across platforms; a Windows checkout of a symlink is a text file. Where symlink creation is refused (Windows without Developer Mode), pull warns once and falls back to pointer files. The link style is sticky: the first pull records it in the manifest, later pulls without a flag keep it, and switching an existing tree takes the other flag plus --force.
What a tree looks like
An abridged tree, pulled with symlinks. ⇒ marks a link and its relative target; everything else is a real document.
Folder names
Folder names are display names, sanitized for Windows and capped at 60 characters. Two resources whose names collide within one directory get a short id suffix (Get Orders__bda178); names are compared case-insensitively and after Unicode normalization, the way macOS and Windows compare them. A resource with an empty name becomes unnamed__<shortid>. Ids appear nowhere else: .celigo/manifest.json keeps the id ↔ path ↔ hash map, so identity is the id and a resource can move without losing its tracking.
Renaming a folder changes nothing on the platform. The folder name is derived from the resource's name field; to rename a resource, edit name in its document and push — the next pull moves the folder. A moved folder is one moved event in celigo status, never a delete plus a create, so push --prune cannot delete a resource over a rename.
The .celigo/ directory
.celigo/manifest.json
The tracked resources: type:id → file paths and hashes, the lastModified lock tokens, the tree's scope, its link style, the account it is bound to (baseUrl, accountUserId), and the intent markers of an interrupted push. Deterministic — no timestamps, sorted keys — and validated on every load.
Standard tree: yes. Neutral tree: no.
.celigo/.gitignore
Written by pull: local/, tmp/, bindings.json, *.tmp (a neutral tree's is *).
Written for you.
.celigo/local/
Per-machine state: when this machine last pulled, lint's feedback ledger, the tree lock.
Never.
.celigo/storage/<id>.json
Celigo Storage item metadata (nothing authored lives in it).
Yes, or ignore storage.
.gitattributes (tree root)
Written once by pull: *.json, *.js, *.md as LF text; the manifest as merge=binary, so git stops on a manifest conflict instead of interleaving two JSON documents.
Yes.
A manifest that fails validation — an unknown key, a path outside the tree, git conflict markers — is refused with one message: .celigo/manifest.json is corrupt (<reason>) — re-pull into a fresh directory, or restore the file from git. Nothing is repaired in place. (A symlinked content directory or document is refused on the same load with its own message; see Pull a tree.)
Layout version. The 2026.9.1 release writes layout v5. A tree pulled with an older layout is refused: This tree was pulled with an older CLI (local-tree layout v4; this CLI writes layout v5). Re-pull with this CLI version into a fresh directory, or delete the old tree (including .celigo/) and pull again.
What the tree strips and what it never carries
Volatile fields never reach a file. lastModified, debug timers, cache and storage sizes, a connection's live queues are stripped on pull, so diffs stay meaningful and push never sends them from the file. Cross-references stay as raw _id values, exactly as the API returns them — unless the tree was pulled with --neutral (see Neutral trees).
Credentials never travel. The API masks encrypted fields as "******" on GET, and a PUT replaces the whole document, so a mask pushed back would destroy the real credential. push refuses any connections, iclients, or mcp-oauth-providers document still holding "******". To set a secret through the tree, type the real value in place of the mask, push, and let the write-back mask it again — the file becomes the server's copy immediately. Do not commit between the edit and the push. celigo diff never prints such a value: it renders as ****** → (set), in JSON output too.
Celigo Storage is pull-only. storage/ mirrors the service's hierarchy: a folder is a directory, a file is either its downloaded bytes (with pull --storage-files) or a <name>.json metadata stub — never both. push never writes storage; the celigo storage commands are the write path.
Integration App documents are pull-only. A document carrying _connectorId belongs to an Integration App whose connector code owns it. It pulls like everything else, so a custom flow's reference to an app's connection resolves, but push withholds edits to it, never deletes it under --prune, and never pushes the app's integration.json. Its settings are written by celigo integrations persist-settings; push --allow-connector-resources pushes edited app flows, exports, and imports only.
Notifications are not in the tree. They are per-user subscription records, managed with celigo notifications.
.celigoignore
.gitignore governs what you commit; it cannot stop pull from writing a file or push from sending one. A .celigoignore in the tree root does, in gitignore syntax:
A pattern containing a slash is anchored to the tree root; one without matches that name at any depth. A trailing / covers the directory and everything under it. ! re-includes, and later patterns win.
The contract is "don't touch", in both directions. An ignored resource is not written, not tracked, not pruned, not pushed, and not deleted, so adding a pattern is never destructive. pull counts ignored resources under ignored, their links disappear, status and diff say nothing about them, and naming one on push reports it as ignored, never as "up to date".
The tree is bound to an account
The manifest records the API host the tree was pulled from and the token owner's user id (GET /v1/tokenInfo). pull, diff, and push refuse a profile that targets another host or whose token belongs to another user — however the profile was selected, --profile and CELIGO_PROFILE included. The profile name is only a label: a different name on the same account is a stderr note, and an env-only CI job (CELIGO_API_TOKEN plus CELIGO_BASE_URL, no config file) can push. --allow-account-mismatch on pull and push is the only override. The full rules, including what happens when the identity probe fails, are in Teams, git, and CI.
What's next
Pull a tree — scoped pulls,
--force,--prune, link styles, storage.Status and diff — local changes, live comparison, CI exit codes.
Push changes — the plan, the refusals, the optimistic lock, interrupt safety.
Lint and resolve — offline validation and ref lookup.
Neutral trees — one git branch per environment.
Promote to another account — write the tree into a sandbox or another account.
Teams, git, and CI — what to commit, branches, the PR check and deploy jobs.
Last updated
Was this helpful?