fcode-cli
Runs commands to test your app locally and sync it to the cloud so your whole team works on the same version.
Installation
Paste this into Claude Code, Cursor, or any agent that can run commands.
SKILL.mdShow the author's original SKILL.md
---
name: fcode-cli
description: Use the Factorial Code CLI (fcode) for local development and cloud sync — the pull → add → run → push flow, the local webhook/forms server, workspace versions and aliases, and the workspace config files (metadata.json, settings.json, variables). Use when running fcode commands, testing a process locally, syncing to the cloud, cloning every App of a development team, inspecting or resolving differences between local and cloud, or configuring a process's webhook, form, UI trigger, or version settings.
license: MIT
metadata:
category: factorial-code
---
# Factorial Code — CLI
The `fcode` CLI develops and tests processes locally and syncs them with
Factorial Code Cloud. For the platform model see `fcode-core-concepts`.
## Command flow
First time on a machine: install with `pnpm install -g @factorialco/fcode-cli`,
then **`fcode clone <workspace-slug>`** to bring a cloud workspace down into a
`<workspace-slug>/` folder (it also installs these agent skills; skip that with
`--skipSkillsSetup`). The "How to build locally" guide on an App's Development
tab shows the exact clone command for its dev workspace — copy it from there,
the slug is an encoded token, not the App's UUID. To bring down **every** App
your development team owns in one layout, use `fcode team:clone` instead (below).
When making and testing changes in an already-cloned workspace:
1. **`fcode pull`** *(optional, first)* — if the cloud may have changed, sync
down so you work with the latest version.
2. Edit local files (processes, modules, variables, dependencies).
3. **`fcode add`** — **only when you created NEW resources** (new process,
module, dependency, or variable). Skip for edits to existing code.
4. **`fcode dependencies:install`** — if you changed
`dependencies/package.json` or `dependencies/requirements.txt`.
5. **`fcode run <process-slug>`** — execute the process locally to test.
6. **`fcode push`** — deploy to cloud when ready.
Pushing updates the **current** (unversioned) code only: consumers pinned to the
`stable` alias — which webhook URLs and form embeds should always be — keep
running the released version until the alias moves. Releases (publishing a
workspace version and re-pointing `stable`) happen separately, normally from the
web UI (see below).
## Gotchas
- **Never run `fcode run` before `fcode add`** when the process (or other
resource) was *just created* — you'll hit "Local process not found".
- **`fcode add` is only for NEW resources.** For edits to existing process/module
code or variables, go straight to `fcode push`.
- **`--force` (on `push`/`pull`) overwrites the other side.** Both commands fail
when local and cloud diverge — `settings:pull` included, which refuses to wipe an
unpushed `settings.json`. Run `fcode diff` first to show the user exactly what
would be discarded, and only use `--force` with explicit user confirmation.
- **Editing `versions` / `aliases` in `settings.json` releases on push**: adding a tag
publishes that workspace version, removing one deletes it from the cloud
(cascading), re-pointing an alias is a release or rollback. Same rule as
`settings:versions:*` / `settings:aliases:*` — **don't touch unless explicitly asked.**
- **Never edit inherited resources.** `variables.inherited.env`,
`i18n/<locale>.inherited.yaml`, and inherited processes/modules are owned by
parent workspaces, regenerated on pull, and never pushed (see below).
Override a variable by adding the key to `variables.env` (a locale key to
`i18n/<locale>.yaml`); edit processes and modules in the owning workspace.
## Commands
### `fcode add`
Registers **new** local resources with the CLI so they can be run or deployed.
Run after creating a new process/module/dependency/variable, before `run`/`push`.
Not needed after only editing existing resources.
### `fcode dependencies:install`
Installs dependencies into the local workspace. Run after changing
`dependencies/package.json` or `dependencies/requirements.txt`. Installs the
inherited packages too, so a local run resolves what the cloud does.
### `fcode run <process-slug> --parameters <filepath | json>`
Executes a process locally for development/testing. Uses `variables.env` /
`variables.local.env` and the given parameters (or the process's
`parameters.json` by default). Shows logs, results, and errors.
```sh
fcode run my-process --parameters '{"key": "value"}'
fcode run my-process --parameters ./params.json
fcode run my-process # default parameters.json
fcode run my-process --locale pt-BR # resolve fcode.i18n in that locale
```
Prerequisite: run `fcode add` first if the resource was just created.
`--locale` resolves `fcode.i18n` against the local `i18n/` files exactly as the
cloud does — see `fcode-i18n` (including why a typo in the flag silently
resolves every key to itself).
### `fcode http`
Starts a local HTTP server (`--port`, default `3000`) that replicates the cloud
webhook environment and also serves the workspace's form schemas, so
webhook-triggered processes and forms can be exercised without deploying.
`--auth-user` / `--auth-password` protect the **whole** local server with basic
auth. Per-process webhook auth is separate, and enforced exactly as the cloud
does it: the server reads `webhook.authMode` from the process's `metadata.json`,
resolves `TEAM` against `webhookAuth` in `settings.json`, and requires the named
variable's value in the configured header — `Bearer <token>` in `Authorization`,
the raw value in any other header. Values come from the workspace variables in
the precedence every local run uses — `variables.inherited.env`, overridden by
`variables.env`, overridden by `variables.local.env` (below). Header lookup is
case-insensitive, but a repeated header is rejected rather than joined. A missing
header, a malformed bearer value, an undefined variable, a mismatch, or a
`TEAM` webhook whose workspace configuration is absent all get a `403`, with the
reason printed to the console.
Two things to watch locally:
- A **secret** variable pulls down as the `********` placeholder, so local auth
only accepts `********` until the real value is in `variables.local.env`.
- `--auth-user` / `--auth-password` consume the `Authorization` header, so they
can't be combined with a webhook expecting its credential there. The server
warns about this at startup.
### `fcode pull`
Downloads the latest processes, modules, variables, and dependencies from the
cloud, overwriting local files to match. Run before starting work if others may
have changed cloud resources, or to discard local changes. `--force` only with
user confirmation.
### `fcode push`
Uploads local changes to the cloud. Run after local changes (run `fcode add`
first only if you created new resources); recommended to `fcode run` first.
`--force` only with user confirmation.
### `fcode status`
Reports what differs between local and cloud without changing either —
processes, modules, variables, dependencies, locales, and `settings.json`.
Per-resource variants exist too: `processes:status`, `modules:status`,
`variables:status`, `i18n:status`, `settings:status`.
**Inherited resources the cloud agrees on are hidden**, since there is nothing
to pull or push for them; a line reports how many were left out. An inherited
resource reappears as soon as it differs — out-of-date, new, removed, conflict,
modified, sensitivity-changed — so a parent moving one is never silently
swallowed. `--showInherited` lists them all:
```sh
fcode status --showInherited # or any per-resource variant above
```
An inherited **sensitive** variable counts as unchanged and is hidden too: its
value is never compared, so it could never report as up to date.
### `fcode diff [<slug>]`
Shows **what** differs, where `status` only says **that** something does: a
git-style unified diff of every file the workspace is stored as, with the cloud
as the old side and the local files as the new one. Per-resource variants take
an optional slug: `processes:diff`, `modules:diff`, `variables:diff`,
`i18n:diff`, `dependencies:diff`, `settings:diff`.
```sh
fcode diff # the whole workspace
fcode processes:diff my-process # one process
```
Use it before resolving a `🔥 (conflict)` from `pull` / `push`, so the user sees
what each side would lose before choosing. Notes:
- **Exits `1` when local and cloud differ**, `0` when in sync — usable as a check.
A non-zero exit here is the answer, not a failure to report as an error.
- Sensitive variable values are never printed; both sides render as
`<sensitive value hidden>`, so a changed secret shows in `status` but not here.
- Inherited resources follow the same rule as `status`: the ones the cloud agrees
on are hidden behind a count, and any a parent has moved **are** diffed, marked
with the owning workspace. Fix those with `fcode pull`, never a push — they
stay read-only. Inherited variables diff as `variables.inherited.env`, the
generated file they live in.
### `fcode remote:add <workspace-slug>`
Points an already-cloned workspace folder at a different cloud workspace, so
subsequent `fcode pull` / `push` sync with that one instead — how code is
promoted between workspaces (e.g. dev → prod). Don't use it ad hoc: the gated
promotion procedure is in `fcode-release`.
### `fcode team:clone` / `team:pull` / `team:status`
Work on every App a **development team** owns at once — the team of humans in the
dashboard, not the workspace sense of the word (see `fcode-ama`). `team:clone`
with no argument lists the development teams you belong to so you can copy an id;
with exactly one, it goes ahead:
```sh
fcode team:clone # lists your teams, or clones the only one
fcode team:clone <teamId>
```
It creates one folder per App, each holding a checkout of that App's workspace:
```
acme-payroll/
┣ 📂 .fcode/team.json # the team and the Apps cloned into it
┣ 📂 .claude/skills/ # installed once, symlinked into every App below
┗ 📂 payroll-sync/
┣ 📜 settings.json # the App — name, description, id
┗ 📂 app/ # a normal workspace — settings.json, processes/, …
```
The checkout is called `app`, never the `dev-<token>` slug it came from: the slug
is in the metadata, and the directory is only a checkout — `fcode remote:add` can
re-point it at the prod workspace. Each level's `settings.json` describes the
thing that level holds, and they never collide, being one directory apart.
Inside an App's workspace every ordinary command works as usual; the team
commands only add the ones that span Apps:
- **`team:pull`** re-reads the team: it clones Apps added since the last run,
pulls every workspace, and refreshes each App's `settings.json`. An App that
left the team is **reported, never deleted** — remove the folder yourself if
you want it gone.
- **`team:status`** runs `fcode status` in each App workspace under its own header.
- **There is no `team:push`.** Push from inside a workspace, one App at a time.
- The App's `settings.json` is a **mirror** of the dashboard, refreshed on pull.
Editing it changes nothing upstream — rename an App in the dashboard.
- A failing App doesn't abort the run: it is listed in the summary, and rerunning
`team:pull` retries only what is still missing.
### `fcode settings:pull` / `settings:push` / `settings:status`
Sync the workspace-level settings in `settings.json` on their own: `settings:pull`
writes the cloud settings into the file, `settings:push` applies the file, and
`settings:status` reports whether they changed locally, in the cloud, or both.
Plain `fcode pull` / `fcode push` include them too, running them **last** so a
referenced error-handler process slug resolves against processes that already exist.
The `versions` / `aliases` fields sync like the rest of the file: `settings:push`
diffs them against the cloud and applies the difference — publishing versions
listed locally but not yet in the cloud (carrying their `comment`), asking per
version before deleting one removed from the file (the deletion **cascades**;
`--force` skips the prompt), and creating, re-pointing or deleting aliases to
match. `settings:status` compares them too, and `settings:pull` refuses to
overwrite a `settings.json` with unpushed local edits.
### `fcode settings:versions:*` / `fcode settings:aliases:*`
Workspace versioning publishes a version of the **whole workspace**: every
process and module the team owns gets a version with the same tag, and bare
module imports are pinned to it inside the published snapshots (model in
`fcode-core-concepts`). Releases normally happen from the web UI (team
settings → **Versions** tab) — these commands are the imperative equivalent, and
editing `settings.json`'s `versions` / `aliases` then pushing is the declarative one.
**Don't create versions or move `stable` unless explicitly asked.**
```sh
fcode settings:versions:create v1.0.0 --comment "First stable release"
fcode settings:versions:list
fcode settings:versions:delete v1.0.0 # cascades; asks confirmation unless --force
fcode settings:aliases:set stable v1.0.0 # create or re-point; rollback = older tag
fcode settings:aliases:list
fcode settings:aliases:delete stable
```
- **`settings:versions:create`** skips entities already carrying the exact tag and
reports a per-entity summary (created / skipped / failed — the version's
manifest), then pulls so the `versions/<tag>/` folders and `settings.json` refresh
locally. Re-running the same tag after a partial failure only publishes what
is still missing.
- **`settings:versions:delete` cascades**: every owned process/module version with
the tag is deleted, together with the aliases, executions, and schedules
referencing them.
- **`settings:aliases:set` upserts** — it creates the alias or re-points an existing
one on every owned entity that has the target tag published (entities without
it are skipped and reported). Re-pointing `stable` at an older tag **is** the
rollback: every consumer pinned to `stable` switches in one operation.
### `fcode i18n:*`
`i18n:pull` / `i18n:push` / `i18n:status` / `i18n:add <locale>` /
`i18n:remove <locale>` / `i18n:reset` sync the workspace's translation files —
`i18n/<locale>.yaml`, plus the read-only, gitignored
`i18n/<locale>.inherited.yaml` that `pull` writes. Aggregate `fcode pull` /
`push` / `status` include locales already. File format, inheritance model, and
the internationalization workflow in `fcode-i18n`.
## Process metadata — `metadata.json`
Each process folder holds `processes/<slug>/metadata.json` — the source of
truth for the process's name, description, tags, triggers, and settings. It
round-trips with `push`/`pull`: edit the file and `fcode push` to change these
settings in the cloud, no dashboard needed. Changes show as 🔺 modified in
`fcode status`.
| Field | Type | Meaning |
|---|---|---|
| `name` | string | Display name (defaults to the slug) |
| `description` | string, optional | Process description |
| `tags` | string[] | Tags (defaults to `[]`) |
| `webhook` | object, optional | Webhook trigger: `enabled` (boolean) turns the process's webhook endpoint on; `authMode` (`NONE` \| `TEAM` \| `CUSTOM`) says how callers authenticate — public, inheriting the workspace `webhookAuth` from `settings.json`, or its own; `auth` (`{ headerName?, variableKey }`, only with `CUSTOM`) names the header and the team variable holding the expected token |
| `form` | object, optional | Form settings: `enabled` (boolean) is the Forms flag (see `fcode-forms`); `authMode` (`FACTORIAL` \| `NONE`) restricts who may open the form; `appRole` marks the process's role in a marketplace app: `INSTALL`, `SETTINGS`, `USER_FACING_FORM`, or `UNINSTALL` |
| `uiTrigger` | object, optional | Button inside the Factorial UI (see `fcode-ui-triggers`): `enabled` (boolean); `locationId` (string, required when enabled, ≤ 200 chars) names the Factorial location; `label` (string, may carry `fcode.i18n("key")` tokens); `icon` (string, allowlisted name); `awaitResult` (boolean) runs the process synchronously and shows its result instead of fire-and-forget |
```json
{
"name": "Order sync",
"description": "Syncs Shopify orders into Factorial",
"tags": ["integration", "shopify"],
"webhook": {
"enabled": true,
"authMode": "CUSTOM",
"auth": { "variableKey": "SHOPIFY_WEBHOOK_TOKEN" }
},
"form": { "enabled": false }
}
```
A webhook that inherits the workspace configuration carries
`"webhook": { "enabled": true, "authMode": "TEAM" }`, and a public one only
`"webhook": { "enabled": true }`.
```json
{
"name": "Connect your account",
"tags": ["setup"],
"form": { "enabled": true, "authMode": "FACTORIAL", "appRole": "INSTALL" }
}
```
Notes:
- **`webhook.auth.variableKey` stores only the variable *name*, never a token** —
so the file is safe to commit. The variable doesn't have to exist yet; until it
does, every call to the webhook is rejected with `403`. Both plain and secret
variables work.
- **`authMode: TEAM` inherits `webhookAuth` from `settings.json`.** When that
configuration is missing, the webhook rejects every call — it never reads as
public. Through MCP this matters: an agent can set `authMode: TEAM` but there is
no team-settings tool, so the configuration has to exist already (set it in
`settings.json` and `fcode settings:push`).
- **`webhook.auth.headerName` defaults to `Authorization`**, whose value must be
`Bearer <token>`; any other header carries the raw variable value. Valid names
are RFC 7230 token characters, at most 64 of them, and `Cookie`, `Host` and the
`Fcode-` prefix are rejected. Prefer the default: a bespoke header loses the
redaction proxies and log pipelines give `Authorization`. Use one only when the
sender can't set `Authorization` — Factorial's own webhook sender, which puts
its token in `x-factorial-wh-challenge`, is the case in point.
- **`form.authMode`, `form.appRole` and `webhook.authMode` are omitted when they
are `NONE`**, as is `webhook.auth.headerName` when it is `Authorization`, so a
plain public form carries only `"form": { "enabled": true }`. To lift protection
from a protected form or webhook, write `"authMode": "NONE"` **explicitly** —
omitting the field leaves the stored mode untouched, and sending `auth` without
`authMode: CUSTOM` is rejected.
- Omit `form.appRole` unless the process belongs to a marketplace app.
- **`fcode pull` writes `"uiTrigger": { "enabled": false }` on every process**;
`locationId`, `label` and `icon` appear only when set, and `awaitResult` only
when `true`. Enabling a trigger without a `locationId` is rejected on push.
- If `metadata.json` is missing, `fcode add` scaffolds
`{ "name": "<slug>", "tags": [] }`; invalid JSON falls back to those
defaults with a warning.
### Calling a webhook — pin the version in the URL
The webhook endpoint is
`https://code.factorialhr.com/platform/api/<team-slug>/webhooks/<process-slug>`.
Always pin the version with the `version_tag` query parameter, pointing at the
`stable` alias — subscription systems rarely let you set request headers:
```sh
curl -X POST "https://code.factorialhr.com/platform/api/<team-slug>/webhooks/<process-slug>?version_tag=stable"
```
- `version_tag` takes a version tag (`v1.0.0`) or an alias. Use `stable`: it
always exists, and releases/rollbacks then happen by moving the alias — the
external system is never touched. It is equivalent to the `Fcode-Version-Tag`
header and takes precedence over it. `version_tag`, `async` and `locale` are
reserved names, stripped before the parameters reach the process (`locale`
selects the execution's language — see `fcode-i18n`).
- **An unknown or malformed version does not fail the call.** The process runs
its current version and the platform only logs a server-side warning — a typo
runs the current version silently. When a run behaves unexpectedly, check the
execution's version.
## Workspace settings — `settings.json`
A singleton file at the workspace root holding workspace-level settings. Synced by
`fcode settings:pull` / `settings:push` / `settings:status`, and included in plain
`fcode push` / `pull` (pushed last, so a referenced error-handler process
exists first).
| Field | Type | Meaning |
|---|---|---|
| `parentTeams` | array | Teams this workspace inherits processes, modules **and variables** from (direct parents only, max 5). Each entry is a plain slug (parent resolves live) or `{ slug, version }` to pin that parent to one of its workspace versions — a tag or an alias |
| `zoneId` | string, optional | Team timezone (e.g. for schedules) |
| `errorHandlerConfig` | object, optional | `{ "processSlug": "<slug>", "tag": null }` — process invoked when an execution errors; `tag` pins it to a version tag or alias (`null` = current version) |
| `webhookAuth` | object, optional | `{ headerName?, variableKey }` — the configuration every `authMode: TEAM` webhook inherits |
| `primaryLocale` | string, optional | The workspace's main language: the locale used when a caller names none, and the key-level fallback for untranslated keys (see `fcode-i18n`) |
| `versions` | array, optional | Workspace versions: `{ tag, comment?, createdAt? }` — declaring a new `tag` and pushing publishes it; omit `createdAt`, the cloud fills it in |
| `aliases` | array, optional | Workspace aliases: `{ name, tag }` |
```json
{
"parentTeams": ["shared-utils", { "slug": "base-app", "version": "stable" }],
"zoneId": "Europe/Madrid",
"errorHandlerConfig": { "processSlug": "error-handler", "tag": null },
"webhookAuth": {
"headerName": "x-factorial-wh-challenge",
"variableKey": "FACTORIAL_CHALLENGE_TOKEN"
},
"versions": [
{ "tag": "v1.0.0", "comment": "First stable release", "createdAt": "2026-08-01T10:00:00" }
],
"aliases": [{ "name": "stable", "tag": "v1.0.0" }]
}
```
The error handler is referenced by **slug** (not id) so `settings.json` is
portable across workspaces; the CLI resolves it to the cloud id on push.
A `parentTeams` entry is written as a plain slug while the parent resolves live,
and as `{ slug, version }` once pinned — both forms are accepted on read, so a
hand-written list of slugs stays valid. The `version` is a tag or an alias of
**that parent's** workspace versions, and it must exist there or the push is
rejected. What pinning changes is in `fcode-core-concepts`; don't add or change
one unless explicitly asked.
`versions` and `aliases` are **synced state**, not a read-only mirror: they count
towards the content hash (edits show as modified in `fcode status`) and
`settings:push` applies them, with the diff semantics above. Only the set of tags and
the `name` → `tag` pairings are compared — a published version's `comment` can
no longer be changed, and `createdAt` is server-assigned.
`webhookAuth` is one shared configuration for the whole workspace, so a token used
by several webhooks is named — and rotated — in one place. Two things about it:
- **It is per-workspace and not inherited through `parentTeams`.** Auth is
resolved against the workspace addressed in the webhook URL, not the one that
owns the code, so an app inheriting a webhook process from a base app still
needs its own `webhookAuth` entry.
- **Removing the object and pushing clears the cloud configuration**, which makes
every webhook inheriting it reject all calls.
## Inherited resources on disk
`fcode pull` writes everything inherited from `parentTeams` alongside the
workspace's own resources: `variables.inherited.env`,
`i18n/<locale>.inherited.yaml`, `dependencies/package.inherited.json` /
`requirements.inherited.txt`, and the inherited process/module folders inside
`processes/` and `modules/`. The CLI keeps them parent-owned:
- **Read-only** — process/module files are written mode `444`; needing a
`chmod` to edit is the signal to stop and change them in the owning workspace.
- **Gitignored** via a CLI-managed block in `.gitignore`, regenerated on every
pull — don't hand-edit the block.
- **`fcode push` never uploads them.** One that was modified locally logs a
warning naming the owning team — restore it with `fcode processes:pull` /
`modules:pull` (or the aggregate `fcode pull`).
- The `*:status` commands list only the inherited resources that differ from
the cloud (`--showInherited` for the rest), marking each with an **inherited**
column naming the owning workspace (`🔗 <slug>`).
- **From a pinned parent you get that version's content, not the parent's
current code**, and only the resources the pinned version published — a
process the parent added afterwards is not pulled at all. Changing the pin in
`settings.json` and pulling again is what moves those files.
Inheritance model (resolution order, overrides, pinning) in
`fcode-core-concepts`.
## The three variables files
Team variables live in three `.env` files at the workspace root:
| File | Holds | Synced |
|---|---|---|
| `variables.env` | The variables this workspace **owns** | Committed; pushed and pulled |
| `variables.inherited.env` | The variables inherited from **parent workspaces** (`parentTeams`) | Pull-only; **gitignored** (the CLI adds the entry) |
| `variables.local.env` | Local-only overrides | Never pushed, never pulled |
**Resolution order for a local run** (highest wins), matching what the cloud
does: `variables.local.env` → `variables.env` → `variables.inherited.env`.
Precedence is decided by which file **declares** a key, not by its value — so
blanking a key in `variables.env` overrides the inherited variable with an empty
string rather than falling through to the parent.
### Overriding an inherited variable
**Adding the key to `variables.env` *is* the override.** From that point the CLI
treats it as this workspace's own variable: `fcode status` shows it as new,
`fcode push` creates it here, and `fcode variables:add` offers it.
- **Don't copy a parent's variables into a child workspace** to "make them
available" — they already resolve. Only add a key when this workspace genuinely
needs a different value. (Workspaces provisioned before inheritance existed may
still hold such copies, which now shadow the parent — including untouched
`********` placeholders shadowing a secret that would otherwise resolve. Flag
those to the user rather than deleting them.)
- Deleting your override (removing the key from `variables.env` and pushing)
brings the parent's value back.
Model and web-UI behaviour in `fcode-core-concepts`; the runtime
`fcode.variables` behaviour in `fcode-javascript` / `fcode-python`.
The file names are settings (`variablesFileName`,
`inheritedVariablesFileName`, `localVariablesFileName`) — assume the defaults
above unless the workspace says otherwise.
## Variable sensitivity — `variables.meta.json`
A workspace-root file mapping each variable to its sensitivity flag:
```json
{
"ACME_API_KEY": { "isSensitive": true },
"ACME_BASE_URL": { "isSensitive": false }
}
```
- Create a sensitive variable with `fcode variables:add --sensitive` (then set
its value and push); the flag lands here.
- Variables created at runtime with `fcode.variables.set` are **sensitive by
default** — pass `sensitive: false` (JS) / `sensitive=False` (Python) for
plain config. See `fcode-javascript` / `fcode-python`.
- **Sensitive values never leave the cloud**: `pull` writes the placeholder
`********` into `variables.env` — and into `variables.inherited.env` for an
inherited secret. Don't replace the placeholder in either file — put the real
value in `variables.local.env` for local runs. Remotely, an inherited secret's
real value *is* available to executions (see `fcode-core-concepts`); only the
local copy is masked.
- **`isSensitive` is immutable** once pushed. Editing it in
`variables.meta.json` is rejected on push (🚫 in `fcode status`) — revert to
match remote.
### Getting secret values for local runs
When a local run (`fcode run`, a discovery script) needs a real secret value
that isn't in `variables.local.env` yet, ask the user to provide it. If they
prefer not to share the value with the agent, ask them to add the
`KEY=value` line to `variables.local.env` themselves — local runs pick it up
without the value ever appearing in the conversation.
- **`variables.local.env` values are never pushed.** Remind the user to also
create those secret variables manually in the remote demo environment —
`fcode push` won't carry the values.
- **`FACTORIAL_TOKEN`**: needed **locally only** — the remote environment
populates it automatically, so don't create it there. To obtain it, the user
completes the OAuth flow in the Factorial Code app details page, then copies
the generated token with the copy dropdown option in the OAuth Dev app, and
puts it in `variables.local.env` (or shares it, per their preference). To run
in a specific installed company's context instead, copy that `deploy-`
workspace's token with **Copy FACTORIAL_TOKEN** in the "…"
menu on the Test marketplace app page — same file, same
handling.
- Once obtained, never echo secret values back in output or logs.
## Examples
**Development cycle (new process):**
```sh
fcode add
fcode dependencies:install # if dependencies changed
fcode run shopify-order-sync --parameters '{"dateFrom":"2024-01-01","dateTo":"2024-01-31"}'
fcode push # no need to re-run `add` if nothing new was created
```
**Deploy an existing, tested process:**
```sh
fcode push # `add` not needed — process already registered
```
Mirrored from the author's public source. Install counts from the open skills registry.