# gcf-tool User Guide

Welcome! This guide walks you through the everyday tasks you'll do with `gcf-tool` — deploying a function, pulling an existing one down to edit, testing it, and rolling changes out across a whole org. It's meant to be read start to finish the first time, then dipped into whenever you need a reminder. For the complete flag-by-flag reference, see the package page on [npm](https://www.npmjs.com/package/@wgroovy/gcf-tool) (the published README).

## Who this is for

You build and maintain Genesys Cloud **function data actions** and you'd rather drive them from the command line than click through the UI. Whether you're bootstrapping a brand-new function, backing one up before a risky edit, migrating actions between orgs, or pushing a batch of functions to production, this tool is built for that loop.

## Before you begin

Install it from npm:

```bash
npm install -g @wgroovy/gcf-tool
```

Package page: [@wgroovy/gcf-tool](https://www.npmjs.com/package/@wgroovy/gcf-tool)

You'll need:

- **Node.js 22 or newer.**
- The **`gc` CLI** (v148.0.0+), signed in with at least one profile. Anything that talks to Genesys Cloud goes through it.
- **`npm`** — only if you plan to use `--npmBuild` or `--npmZip`.

### Why `gcf-tool` uses the gc CLI

You might wonder why the tool doesn’t call Genesys Cloud APIs directly from Node. It delegates to the **`gc` CLI** on purpose.

The gc CLI is Genesys’s standard, supported tool for CI/CD and automation. Profile management — who you’re authenticated as, which org you’re targeting, and how you switch between dev, test, and prod — is built in and kept current by Genesys. `gcf-tool` passes your chosen profile (from `-p` or from `gcCliProfile` in `package.json`) straight through to `gc`, so you **don’t maintain a second set of credentials or org settings** inside this tool.

Configure profiles once with `gc`, use them everywhere: other scripts, pipelines, and every `gcf-tool` deploy, extract, or test run.

Two quick checks to confirm you're ready:

```bash
gcf-tool --help          # the tool prints its options
gcf-tool --version       # check which version is installed
gc profiles list         # you have an authenticated profile
```

That's it — you're set.

## How a function directory is organized

`gcf-tool` works on one **function directory** per action. What makes a directory a "function directory" is a `gcFunction` block in its `package.json`. Here's the full layout you'll see as a function matures:

```
my-function/
├── index.mjs          # Entry file: export async function handler(event, context)
├── package.json       # Holds the gcFunction config (integration, function name, profile, secure)
├── src.zip            # Packaged function code — required to deploy
├── src.md5            # Upload state gcf-tool manages for you (keyed per GC CLI profile)
├── request.json       # Request configuration (optional)
├── response.json      # Response configuration (optional)
├── inputSchema.json   # Input schema (optional; used for never-published functions)
├── successSchema.json # Success schema (optional; used for never-published functions)
└── input.json         # Sample test input (the default source for --test)
```

A few friendly notes:

- **`package.json` → `gcFunction`** holds your defaults so you don't retype them every run:

  ```json
  {
    "name": "my-function",
    "gcFunction": {
      "integrationName": "GCF Utilities",
      "functionName": "LookupTagConfig_01",
      "gcCliProfile": "default",
      "secure": false
    }
  }
  ```

  Any of these can be overridden per run with `-i`, `-f`, `-p`, and `-s` — which is exactly how you target a different org without editing the file (more on that in [Bulk mode](#work-across-many-functions-bulk-mode)).

- **`src.zip`** is your packaged code. You can build it yourself, or let the tool run your build step with `--npmBuild` (or `--npmZip`).

- **`src.md5`** is a little bookkeeping file the tool writes and reads for you. It remembers the hash of the last `src.zip` it uploaded *for each GC CLI profile*, so repeat deploys to the same org skip a redundant upload. You never edit it by hand.

## Quick start: your first deploy

Run these from inside a function directory that already has a `gcFunction` config and a built `src.zip`.

**1. Look before you leap (dry-run).** With no `--apply`, nothing changes in your org — the tool just shows you what it *would* do:

```bash
gcf-tool
```

**2. Do it for real, and publish:**

```bash
gcf-tool -a -y
```

**3. Or update the draft but hold off on publishing:**

```bash
gcf-tool -a -n
```

Once that clicks, the rest of the tool is variations on the same theme.

## The ideas worth knowing

A little context makes every command predictable.

### Dry-run is the default, `--apply` opts in

The tool won't change your org unless you pass `--apply` (`-a`). Every remote change is simulated and printed first. Get in the habit: run once to preview, read the output, then add `-a`. It's your safety net.

### Publishing is deliberate

Even in apply mode, publishing won't sneak up on you:

- `--yes` (`-y`) — publish automatically. Great for scripts and CI.
- No `--yes`, in an interactive terminal — you get a confirmation prompt.
- No `--yes`, non-interactive (like CI) — publish is skipped to be safe.
- `--noPublish` (`-n`) — never publish, even with `-a`. Handy when you want the draft updated but not released.

### Uploads are smart about what changed

Uploading `src.zip` is the slow step, so the tool avoids it when it can. After a successful upload it remembers the archive hash **per profile**:

- Same profile, unchanged code → upload skipped.
- Same profile, changed code → uploaded.
- A different profile → uploaded, because that org hasn't received this build yet.

That per-profile memory is why the very first deploy to a new environment always uploads, even if the code is identical to what you shipped elsewhere.

### The handler comes from your zip

The deployed handler is read from inside `src.zip`, not your source folder. Supported entry files are `index.js`, `index.mjs`, and `index.cjs`, and resolution is deterministic:

- A single root-level `index.*` wins (root `index.mjs` → handler `index.handler`).
- Otherwise a single nested `index.*` is used (`src/index.js` → `src/index.handler`).
- If two candidates tie at the same level, the tool stops and asks you to package exactly one entry file rather than guess.

### Once published, the schema contract is locked

This one surprises people, so it's worth spelling out.

When a function action is **published**, its input and success schema contract becomes fixed in Genesys Cloud — later deploys can't change it. To protect you from *thinking* you changed it when you didn't, the tool compares your local `inputSchema.json` and `successSchema.json` against the published contract **before it changes anything** (this check runs in both dry-run and apply mode).

If they differ — a **schema drift** — the tool lists every difference and stops without touching your org:

```
Schema drift detected against published action contract:
  - $.output.errorMessage: local type 'string' does not match published type 'integer'
```

Because the published contract can't be rewritten, **you can't republish a changed schema under the same function name.** You have two ways forward:

1. **Bring your local files back in line.** Run `gcf-tool --extract` to pull the published schemas, fold your other edits back in, and deploy again.
2. **Ship a new contract under a new name.** If you genuinely need different schemas, create a new function action with a different function name and deploy that.

The same rule applies to the `secure` flag — it's fixed at publish time, so changing it means a new function action.

Two things that are *not* affected: **never-published** functions (their schemas are set fresh on the first deploy, so there's nothing to drift from), and the check requires `inputSchema.json` and `successSchema.json` to exist for a published action — if they're missing, the tool points you to `--extract` to fetch them.

## Everyday workflows

### Start a new function from scratch (scaffold)

`--scaffold` (`-c`) writes a `package.json` (with the `gcFunction` block) and a minimal `index.mjs` so you're not copying boilerplate:

```bash
# In the current directory; the function name defaults to the folder name
gcf-tool -c -i "GCF Utilities"

# Spell everything out
gcf-tool -c -i "GCF Utilities" -f LookupTagConfig_01 -p centerpoint-westprod
```

The integration name (`-i`) is required. If a `package.json` already has a `gcFunction` section, or an `index.*` already exists, the tool leaves your work alone and just tells you it skipped. Scaffolding is purely local — no `gc` connection needed — so it's perfect for setting up a project before you're online.

### Build and deploy in one step

If your function has a build script, let the tool run it right before packaging:

```bash
gcf-tool -a -z          # runs `npm run build` in the directory, then deploys
gcf-tool -a --npmZip    # runs `npm run zip` in the directory, then deploys
```

Pick whichever script actually produces your `src.zip`. Use `--npmBuild` (`-z`) when a single `build` script does everything — that is what `--scaffold` generates, since its `build` is itself a `zip` command. Use `--npmZip` when your project keeps them apart, with `build` compiling or bundling and a separate `zip` script doing the packaging. The two flags are mutually exclusive; passing both is an error rather than a guess.

These are the only modes that need `npm`. If the script fails, the tool warns and keeps going, so make sure the run really produced a fresh archive — however you get there, a `src.zip` has to exist by the time the deploy reaches the upload step.

### Understand what a deploy actually does

In apply mode the tool runs a predictable, top-to-bottom sequence. Matching the console output against this list tells you exactly where you are:

1. Resolve the integration and find the current draft/published state of the action.
2. (Optional) run `npm run build` or `npm run zip`.
3. Read `src.zip` and resolve the handler from it.
4. Build the draft from `request.json`, `response.json`, and — for never-published actions — the schema files.
5. Compare the `src.zip` hash against what was last uploaded for this profile.
6. If needed: update the function settings (handler, runtime, timeout), request an upload URL, upload `src.zip`, wait for the deployment to finish, and record the new hash.
7. Validate the draft, then publish according to the publishing rules above.

### Pull an existing action down to edit (extract)

`--extract` (`-e`) is the mirror image of deploy: it reads an action's configuration *from* your org and writes it into local files. It's read-only and never publishes.

```bash
# Pull all four config files into the current directory
gcf-tool -e

# Pull into a specific directory
gcf-tool -e -d /path/to/dir
```

By default it writes `inputSchema.json`, `successSchema.json`, `request.json`, and `response.json`. It prefers the **published** action, and if none exists it falls back to the **draft** — so you can pull a brand-new function's configs even before it's ever been published.

Want just some of the files? Ask for exactly what you need:

```bash
gcf-tool -e --inputSchema            # only the input schema
gcf-tool -e --request --response     # only the request + response config
```

These sub-flags work with `--extract` or `--scaffold`; if you name none, you get all four.

This is the backbone of a few handy patterns:

- **Back up before you edit** — extract, commit the files, then make changes with confidence.
- **Reconcile schema drift** — extract the published schemas, merge your other changes, redeploy.
- **Migrate between orgs** — extract from one org, then `--apply` into another (with a different profile). Extract → apply is a full round trip.

### Scaffold and extract together

Bootstrapping a local copy of an existing function? Combine them:

```bash
gcf-tool -c -i "GCF Utilities" -e
```

Scaffold runs first (directory + `package.json`), then extract pulls the configs into it. Any selective sub-flags apply to the extract half.

### Test an action in the org

`--test` runs the action in Genesys Cloud and reports back:

```bash
gcf-tool --test                          # uses input.json
gcf-tool --test --testJson test-cases.json
gcf-tool --test --testJson '{"datetime":"2026-01-01T00:00:00Z"}'
```

`--testJson` takes either a file or the JSON itself. If the value starts with `{` or `[` it's treated as an inline payload, which saves you writing a throwaway file for a one-off case; anything else is a path. Wrap inline JSON in single quotes so your shell doesn't eat the double quotes. Bad paths and malformed JSON are caught before any call goes out to Genesys Cloud. Combined with `--bulk`, an inline payload goes to every function in the batch, so you'll get a warning reminding you of that.

It prefers the **draft** and falls back to the **published** action, so you can validate changes before you release them. It's always read-only, needs only the `gc` CLI, prints the command and the input it sent, then tells you pass or fail. Add `--verbose` to see the full raw response when you're digging into a problem.

### Delete a function

Deletion has its own guard rails — it's a dry-run until you confirm:

```bash
gcf-tool -r          # preview deleting the draft and published action
gcf-tool -r -y       # actually delete
```

### Work across many functions (bulk mode)

Most modes accept `--bulk` (`-b`) to process several function subdirectories in one go. You can list names, point at a file of names, or use a simple `*` glob:

```bash
gcf-tool -a -b "folder1,folder2"        # deploy a few by name
gcf-tool -a -b "@folders.txt"           # names from a file, one per line
gcf-tool -a -b "billing*"               # every subdirectory starting with "billing"
gcf-tool -e -b "folder1,folder2"        # extract several at once
gcf-tool --test -b "folder1,folder2"    # test several at once
```

Directories run one after another. If one fails, the tool reports it and keeps going, then gives you a per-directory summary at the end — so a single bad function won't sink the whole batch.

**Rolling a batch out to production.** The per-run overrides (`-p`, `-i`, `-s`) apply to *every* directory in the batch, which is exactly how you push the same set of functions to a different org **without editing each `package.json`**. For example, deploy and publish three functions to your production org, ignoring whatever `gcCliProfile` each directory has set:

```bash
gcf-tool -a -y -b "lookup_tags,route_call,log_event" -p prod-west
```

Point a whole integration folder at production in one command:

```bash
gcf-tool -a -y -b "*" -d ./functions -p prod-west
```

Override both the profile and the integration name for the batch — useful when the same code lives under a different integration in prod:

```bash
gcf-tool -a -y -b "@release.txt" -p prod-west -i "GCF Utilities (Prod)"
```

Prefer to stage first and publish later? Update every draft without releasing, review in the UI, then run again with `-y`:

```bash
gcf-tool -a -n -b "*" -d ./functions -p prod-west     # drafts only
gcf-tool -a -y -b "*" -d ./functions -p prod-west     # publish when ready
```

> Note: `-f` (function name) sets a single name, so it doesn't make sense to combine with a multi-directory batch — let each directory keep its own `functionName`. Overriding `-p`, `-i`, or `-s` across a batch is the common case.

## Reading the output

- Each line is tagged: `ⓘ` info, `→` a step in progress, `✓` success, `⚠` a warning, `✗` an error.
- Any `[dry-run]` line describes something that **didn't** happen. If you're seeing them and expected real changes, you likely left off `--apply`.
- Stuck? Add `--verbose` (`-v`) for the full command detail and raw responses.

## Troubleshooting

| What you see | What's going on |
|--------------|-----------------|
| `gcf-tool v...` with `--version` | Shows the installed version, baked in at build time. |
| `requires Node.js 22 or newer` | Upgrade Node — the tool won't run on older majors. |
| `Required tool 'gc' not found` | Install and sign in to the `gc` CLI; check that `gc version` works. |
| `Required tool 'npm' not found` | Only comes up with `-z`/`--npmZip`; install npm or drop the flag. |
| `src.zip not found` | Package the function first (run with `-z`, or build `src.zip` yourself). |
| Handler resolution is ambiguous | Your `src.zip` has more than one `index.*` at the same level — keep exactly one entry file. |
| `Schema drift detected...` | Your local schemas differ from the published, locked contract. Reconcile via `--extract`, or ship a new function name. |
| `inputSchema.json not found` (published action) | Run `--extract` to pull the published schemas, then retry. |
| Nothing published in CI | Expected without `--yes` in a non-interactive shell. Add `-y`. |
| Uploads happen every run | You're probably switching `--gcProfile` between runs — upload memory is per profile. |

## Where to go next

- [@wgroovy/gcf-tool on npm](https://www.npmjs.com/package/@wgroovy/gcf-tool) — the full options table, plus schema and template examples you can copy.
- `gcf-tool --help` — the same reference, always one command away.
