@aiaiai-pt/frankctl 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +8 -1
- package/dist/index.js +162 -160
- package/package.json +1 -1
- package/skills/frank-cli/SKILL.md +261 -204
package/package.json
CHANGED
|
@@ -1,170 +1,242 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: frank-cli
|
|
3
|
-
description: Use the frankctl CLI to
|
|
3
|
+
description: Use the frankctl CLI to provision Frank pipelines declaratively — apply/scaffold/export multi-doc YAML — plus auth, sources, transforms, backing datasets, and runs
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# frankctl
|
|
6
|
+
# frankctl — the Frank CLI
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`frankctl` is the command-line client for the Frank platform: a scriptable, repeatable
|
|
9
|
+
way to build and operate end-to-end data pipelines — Sources (Extract/Load into an
|
|
10
|
+
Iceberg bronze lakehouse), Transforms/Pipelines (bronze → silver/gold), and Backing
|
|
11
|
+
Datasets (silver → ontology entities).
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
It is built for two audiences: developers scripting Frank from a terminal or CI, and
|
|
14
|
+
agents composing and self-checking pipelines without a UI. Every command speaks the
|
|
15
|
+
public Frank API and authenticates against Keycloak.
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
The heart of the tool is **declarative provisioning**: describe a whole pipeline in one
|
|
18
|
+
YAML file and `apply` it idempotently. Reach for the imperative subcommands
|
|
19
|
+
(`sources`, `transforms`, …) for inspection, one-off actions, and debugging — but let
|
|
20
|
+
YAML be your source of truth.
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
- Scripting against Frank from bash/CI: `$(frankctl auth token)` injects a fresh access token into any `curl` call.
|
|
16
|
-
- Agents composing pipelines via Martha can self-check their work without going through the UI.
|
|
17
|
-
|
|
18
|
-
## 3. Installation & binary location
|
|
22
|
+
## Install
|
|
19
23
|
|
|
20
24
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
npm run build
|
|
24
|
-
npm link # installs `frankctl` on PATH
|
|
25
|
+
npm install -g @aiaiai-pt/frankctl
|
|
26
|
+
frankctl --version
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Node ≥18. The binary is `frankctl`. Docs: https://franksdocs.nomadriver.co
|
|
28
30
|
|
|
29
|
-
##
|
|
31
|
+
## Connect & authenticate
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
frankctl status
|
|
33
|
-
frankctl auth login
|
|
34
|
-
frankctl auth status
|
|
35
|
-
frankctl pipelines list
|
|
34
|
+
frankctl status # effective config + whether you're logged in
|
|
35
|
+
frankctl auth login # browser PKCE flow (opens Keycloak)
|
|
36
|
+
frankctl auth status # confirms your subject + tenant
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
Point it at an environment by setting the API URL once (or per-invocation with
|
|
40
|
+
`--api-url`):
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
```bash
|
|
43
|
+
frankctl config set profiles.default.apiUrl https://api.frank.nomadriver.co
|
|
44
|
+
frankctl config set profiles.default.keycloakUrl https://auth.frank.nomadriver.co
|
|
45
|
+
```
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
Auth modes:
|
|
48
|
+
- **PKCE (default)** — `frankctl auth login`. Browser-based, best for humans.
|
|
49
|
+
- **Headless** — `frankctl auth login --headless --username … --password …`. For containers/CI.
|
|
50
|
+
- **Service account** — `frankctl auth login --service-account --client-id … --client-secret …`.
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `--json` | Emit machine-readable JSON to stdout |
|
|
48
|
-
| `-v, --verbose` | Verbose logging (stderr) |
|
|
49
|
-
| `-q, --quiet` | Suppress non-error output |
|
|
50
|
-
| `--no-color` | Disable ANSI colors |
|
|
51
|
-
| `--api-url <url>` | Override API base URL for this invocation |
|
|
52
|
+
Tokens are stored per-profile at `~/.frankctl/credentials.json` (0600) and refresh
|
|
53
|
+
automatically before expiry. In scripts, inject a fresh token instead of reading the
|
|
54
|
+
file:
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
```bash
|
|
57
|
+
TOKEN=$(frankctl auth token)
|
|
58
|
+
curl -H "Authorization: Bearer $TOKEN" https://api.frank.nomadriver.co/api/v1/pipelines
|
|
59
|
+
```
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|-----|--------|
|
|
57
|
-
| `FRANKCTL_API_URL` | Override `apiUrl` |
|
|
58
|
-
| `FRANKCTL_KEYCLOAK_URL` | Override `keycloakUrl` |
|
|
59
|
-
| `FRANKCTL_KEYCLOAK_REALM` | Override `realm` |
|
|
60
|
-
| `FRANKCTL_CLIENT_ID` | Override `clientId` |
|
|
61
|
-
| `FRANKCTL_CLIENT_SECRET` | Service-account secret |
|
|
62
|
-
| `FRANKCTL_PROFILE` | Active profile |
|
|
63
|
-
| `FRANKCTL_TOKEN` | Bypass token store; use this raw JWT |
|
|
64
|
-
| `FRANK_DEV_MODE=true` + `FRANKCTL_TENANT_ID` | Forward `X-Tenant-ID` header |
|
|
61
|
+
---
|
|
65
62
|
|
|
66
|
-
##
|
|
63
|
+
## Declarative pipelines — the repeatable path
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
realm: frank
|
|
80
|
-
clientId: frank-low-code
|
|
81
|
-
```
|
|
65
|
+
A pipeline is one multi-document YAML file describing three kinds of object under the
|
|
66
|
+
`frank.platform/v1` API version:
|
|
67
|
+
|
|
68
|
+
| `kind` | Role |
|
|
69
|
+
|--------|------|
|
|
70
|
+
| `Source` | Extract/Load config — connector `pattern_id`, streams, schedule → Iceberg **bronze** |
|
|
71
|
+
| `Pipeline` | Transform steps (SQL/Python) turning bronze → **silver/gold** |
|
|
72
|
+
| `BackingDataset` | Maps a silver table's columns to **ontology** entity properties |
|
|
73
|
+
|
|
74
|
+
Objects reference each other by `metadata.name`, so one file is self-contained and
|
|
75
|
+
version-controllable. Commit these files to git — they *are* your infrastructure.
|
|
82
76
|
|
|
83
|
-
|
|
77
|
+
### The loop: scaffold → edit → apply
|
|
84
78
|
|
|
85
|
-
|
|
79
|
+
**1. Scaffold** a starter manifest from a source you've already synced. Frank reads the
|
|
80
|
+
real physical bronze schema and pre-fills the SQL with the actual Trino `ROW` refs:
|
|
86
81
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
```bash
|
|
83
|
+
frankctl pipelines scaffold --source ipma_indice_uv -o pipeline.yaml
|
|
84
|
+
```
|
|
90
85
|
|
|
91
|
-
|
|
86
|
+
**2. Edit** `pipeline.yaml`. Usually the only required change is naming the ontology
|
|
87
|
+
entity (`BackingDataset.entity_type_id` / `entity_type_name`); optionally refine the
|
|
88
|
+
`primary_key_column`, add casts, or tune the SELECT.
|
|
92
89
|
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
**3. Apply** it idempotently. Re-running is safe — Frank reconciles to the declared
|
|
91
|
+
state rather than creating duplicates:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
frankctl pipelines apply -f pipeline.yaml
|
|
95
|
+
```
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Add `--wait` to drive the whole chain to completion and let a non-zero exit mean "it
|
|
98
|
+
didn't land": sync Sources → bronze, materialize Pipeline steps → silver, sync Backing
|
|
99
|
+
Datasets → ontology.
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
```bash
|
|
102
|
+
frankctl pipelines apply -f pipeline.yaml --wait --timeout 600
|
|
103
|
+
```
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
### Preflight before you apply
|
|
102
106
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
-
|
|
106
|
-
- `
|
|
107
|
+
| Flag | What it does |
|
|
108
|
+
|------|--------------|
|
|
109
|
+
| `--dry-run` | Client-side parse + cross-ref resolve. Catches malformed YAML and broken references. No server call. |
|
|
110
|
+
| `--dry-run-server` | Server-side preflight against the catalog + DB without persisting. Catches unknown `pattern_id`s, would-be immutable-diff conflicts, and unresolved refs. |
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
Run `--dry-run-server` in CI on every change; run `apply --wait` to promote.
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
- `config get <key>` — returns `{value, source}` where source ∈ `flag | env | config | default`.
|
|
112
|
-
- `config list` — merged effective profile.
|
|
114
|
+
### Immutable state & when to force
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
Backing Datasets carry operational state (ontology cursor, sync history). If a change
|
|
117
|
+
would require destroying that state, `apply` returns `409 immutable_diff` and stops —
|
|
118
|
+
it will not silently discard your data. Two explicit opt-ins exist for when you *do*
|
|
119
|
+
mean it:
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
- `--allow-recreate` — DELETE the conflicting row and re-create. **Loses** cursor,
|
|
122
|
+
ledger, snapshot ids, and run history. Use knowingly.
|
|
123
|
+
- `--allow-deprecate` — permit an `ensure_schema` that narrows the ontology schema.
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
Without these, `apply` is fully idempotent.
|
|
119
126
|
|
|
120
|
-
|
|
127
|
+
### Export existing work back to YAML
|
|
121
128
|
|
|
122
|
-
|
|
129
|
+
Already built a pipeline in the UI? Pull it — plus its Sources and Backing Datasets —
|
|
130
|
+
into a manifest you can commit and re-apply elsewhere:
|
|
123
131
|
|
|
124
|
-
|
|
132
|
+
```bash
|
|
133
|
+
frankctl pipelines export <pipeline-id> > pipeline.yaml
|
|
134
|
+
```
|
|
125
135
|
|
|
126
|
-
|
|
136
|
+
### Minimal manifest shape
|
|
127
137
|
|
|
128
|
-
|
|
138
|
+
```yaml
|
|
139
|
+
apiVersion: frank.platform/v1
|
|
140
|
+
kind: Source
|
|
141
|
+
metadata:
|
|
142
|
+
name: ipma_indice_uv
|
|
143
|
+
spec:
|
|
144
|
+
pattern_id: ipma
|
|
145
|
+
source_config:
|
|
146
|
+
streams: [uv_index]
|
|
147
|
+
streams:
|
|
148
|
+
- name: ipma_uv_index
|
|
149
|
+
sync_mode: full_refresh
|
|
150
|
+
write_disposition: replace
|
|
151
|
+
schedule_type: cron
|
|
152
|
+
schedule_value: "0 6 * * *"
|
|
153
|
+
---
|
|
154
|
+
apiVersion: frank.platform/v1
|
|
155
|
+
kind: Pipeline
|
|
156
|
+
metadata:
|
|
157
|
+
name: ipma_indice_uv_pipeline
|
|
158
|
+
spec:
|
|
159
|
+
source_ids: [ipma_indice_uv]
|
|
160
|
+
steps:
|
|
161
|
+
- name: ipma_indice_uv_silver
|
|
162
|
+
kind: custom_sql
|
|
163
|
+
sources: [bronze.tenant_00000000_ipma_indice_uv.ipma_uv_index]
|
|
164
|
+
emits_to: backing_dataset
|
|
165
|
+
config: { output_layer: silver }
|
|
166
|
+
params:
|
|
167
|
+
sql: |-
|
|
168
|
+
SELECT o."date" AS date, o."uv_index" AS uv_index, o."record_key" AS record_key
|
|
169
|
+
FROM tenant_00000000_ipma_indice_uv.ipma_uv_index o
|
|
170
|
+
---
|
|
171
|
+
apiVersion: frank.platform/v1
|
|
172
|
+
kind: BackingDataset
|
|
173
|
+
metadata:
|
|
174
|
+
name: uv_index_forecast
|
|
175
|
+
spec:
|
|
176
|
+
entity_type_id: uv_index_forecast
|
|
177
|
+
entity_type_name: UV Index Forecast
|
|
178
|
+
pipeline: ipma_indice_uv_pipeline
|
|
179
|
+
sync_mode: on_materialization
|
|
180
|
+
primary_key_column: record_key
|
|
181
|
+
property_mappings:
|
|
182
|
+
- { column: date, property: date, type: date }
|
|
183
|
+
- { column: uv_index, property: uv_index, type: long }
|
|
184
|
+
```
|
|
129
185
|
|
|
130
|
-
|
|
186
|
+
---
|
|
131
187
|
|
|
132
|
-
|
|
133
|
-
- `validate` always emits the final sandbox status as JSON on stdout, regardless of `--json`.
|
|
188
|
+
## Global options
|
|
134
189
|
|
|
135
|
-
|
|
190
|
+
Available on every subcommand:
|
|
136
191
|
|
|
137
|
-
|
|
|
192
|
+
| Flag | Purpose |
|
|
138
193
|
|------|---------|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
194
|
+
| `-p, --profile <name>` | Select config profile (default: `default`) |
|
|
195
|
+
| `--json` | Emit machine-readable JSON to stdout |
|
|
196
|
+
| `-v, --verbose` | Verbose logging (stderr) |
|
|
197
|
+
| `-q, --quiet` | Suppress non-error output |
|
|
198
|
+
| `--no-color` | Disable ANSI colors |
|
|
199
|
+
| `--api-url <url>` | Override the API base URL for this invocation |
|
|
145
200
|
|
|
146
|
-
|
|
201
|
+
### Environment variables
|
|
147
202
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
203
|
+
| Var | Effect |
|
|
204
|
+
|-----|--------|
|
|
205
|
+
| `FRANKCTL_API_URL` | Override `apiUrl` |
|
|
206
|
+
| `FRANKCTL_KEYCLOAK_URL` | Override `keycloakUrl` |
|
|
207
|
+
| `FRANKCTL_KEYCLOAK_REALM` | Override `realm` |
|
|
208
|
+
| `FRANKCTL_CLIENT_ID` / `FRANKCTL_CLIENT_SECRET` | Client id / service-account secret |
|
|
209
|
+
| `FRANKCTL_PROFILE` | Active profile |
|
|
210
|
+
| `FRANKCTL_TOKEN` | Use this raw JWT instead of the token store |
|
|
211
|
+
|
|
212
|
+
### Profiles (`~/.frankctl/config.yaml`)
|
|
151
213
|
|
|
152
|
-
|
|
214
|
+
```yaml
|
|
215
|
+
defaultProfile: default
|
|
216
|
+
profiles:
|
|
217
|
+
default:
|
|
218
|
+
apiUrl: https://api.frank.nomadriver.co
|
|
219
|
+
keycloakUrl: https://auth.frank.nomadriver.co
|
|
220
|
+
realm: frank
|
|
221
|
+
clientId: frank-low-code
|
|
222
|
+
```
|
|
153
223
|
|
|
154
|
-
|
|
224
|
+
Precedence (top wins): CLI flag → env var → YAML → built-in default.
|
|
225
|
+
`config get <key>` reports `{value, source}` so you can see which layer won;
|
|
226
|
+
`config list` prints the merged effective profile.
|
|
155
227
|
|
|
156
|
-
|
|
228
|
+
---
|
|
157
229
|
|
|
158
|
-
|
|
230
|
+
## Command reference
|
|
159
231
|
|
|
160
|
-
|
|
232
|
+
Use these to inspect and act on what `apply` provisioned, and to debug.
|
|
161
233
|
|
|
162
|
-
###
|
|
234
|
+
### Sources (Extract/Load → bronze)
|
|
163
235
|
|
|
164
236
|
```bash
|
|
165
237
|
frankctl sources list [--type <pattern-id>] [--status <s>] [--search <q>]
|
|
166
238
|
frankctl sources get <id> [--no-streams]
|
|
167
|
-
frankctl sources create -f source.yaml
|
|
239
|
+
frankctl sources create -f source.yaml # {name, pattern_id, source_config}
|
|
168
240
|
frankctl sources update <id> -f patch.yaml
|
|
169
241
|
frankctl sources delete <id> --yes
|
|
170
242
|
frankctl sources discover <id> [--no-wait] [--timeout <s>]
|
|
@@ -175,149 +247,134 @@ frankctl sources history <id> [--limit N] [--offset N]
|
|
|
175
247
|
frankctl sources streams list <source-id> [--enabled-only]
|
|
176
248
|
frankctl sources streams set <source-id> -f streams.yaml
|
|
177
249
|
frankctl sources streams refresh-schema <source-id> --from-discovery
|
|
178
|
-
frankctl sources streams refresh-schema <source-id> -f discovery.json
|
|
179
250
|
```
|
|
180
251
|
|
|
181
|
-
`-f` accepts YAML, JSON, or `-` (stdin). `
|
|
252
|
+
`-f` accepts YAML, JSON, or `-` (stdin). `streams set` takes a bare array or `{streams: […]}`.
|
|
182
253
|
|
|
183
|
-
###
|
|
254
|
+
### Pipelines & Transforms (bronze → silver/gold)
|
|
184
255
|
|
|
185
256
|
```bash
|
|
257
|
+
frankctl pipelines list [--status <s>] [--search <q>]
|
|
258
|
+
frankctl pipelines get <id> [--include-version]
|
|
259
|
+
frankctl pipelines validate <id> # sandbox run; badges → stderr, JSON summary → stdout
|
|
260
|
+
frankctl pipelines delete <id> # does not delete silver tables
|
|
261
|
+
|
|
186
262
|
frankctl transforms list [--status <s>] [--pipeline-id <id>]
|
|
187
263
|
frankctl transforms get <id>
|
|
188
264
|
frankctl transforms runs <id> [--status] [--limit N]
|
|
189
265
|
frankctl transforms logs <id> <run-id> [-f] [--level]
|
|
190
|
-
frankctl transforms trigger <id> #
|
|
266
|
+
frankctl transforms trigger <id> # "Run Now"
|
|
191
267
|
```
|
|
192
268
|
|
|
193
|
-
|
|
269
|
+
`pipelines validate` runs the pipeline end-to-end in sandbox mode against a sample and
|
|
270
|
+
streams step results — use it as a pre-activation check.
|
|
271
|
+
|
|
272
|
+
### Backing Datasets (silver → ontology)
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
frankctl bd list
|
|
276
|
+
frankctl bd get <id>
|
|
277
|
+
frankctl bd create -f bd.yaml
|
|
278
|
+
frankctl bd update <id> -f patch.yaml
|
|
279
|
+
frankctl bd delete <id> --yes # does not delete underlying data
|
|
280
|
+
frankctl bd sync <id> [--force] [--no-wait] # trigger ontology sync, poll exact run
|
|
281
|
+
frankctl bd entities <id> # read entities back out — confirm values landed
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`bd entities` closes the authoring loop: it reads the ontology to prove your rows
|
|
285
|
+
actually materialized as entities, not just that the sync reported success.
|
|
286
|
+
`bd sync --force` full-replays through REST even when the snapshot and effective
|
|
287
|
+
mapping spec are unchanged. Normal output includes replay reason, row/snapshot
|
|
288
|
+
evidence, and safe attempted/applied SyncSpec fingerprint prefixes. Human output
|
|
289
|
+
prefers the precision-safe decimal string `snapshot_id_exact`; JSON exposes it
|
|
290
|
+
alongside the legacy numeric `snapshot_id`.
|
|
291
|
+
|
|
292
|
+
For an ordinary sync, the CLI falls back to BackingDataset status polling when
|
|
293
|
+
an older API does not return exact-run evidence, and clearly marks replay
|
|
294
|
+
evidence unavailable. `--force` does not fall back: it checks
|
|
295
|
+
`/api/v1/backing-datasets/capabilities` before mutation and sends no sync POST
|
|
296
|
+
unless version 2, exact-run polling, and force replay are explicitly supported.
|
|
297
|
+
After the POST, it also requires `force_replay: true` confirmation.
|
|
298
|
+
|
|
299
|
+
### Runs
|
|
194
300
|
|
|
195
301
|
```bash
|
|
196
302
|
frankctl runs get <workflow-id>
|
|
197
303
|
frankctl runs wait <workflow-id> [--timeout <s>] [--poll-interval <ms>]
|
|
198
|
-
frankctl runs cancel <id> --yes
|
|
304
|
+
frankctl runs cancel <id> --yes
|
|
199
305
|
```
|
|
200
306
|
|
|
201
|
-
|
|
307
|
+
### Datasets (browse the lakehouse)
|
|
202
308
|
|
|
203
309
|
```bash
|
|
204
|
-
frankctl
|
|
205
|
-
frankctl
|
|
206
|
-
frankctl
|
|
207
|
-
frankctl ai fix-ci-failure -f spec.yaml
|
|
208
|
-
frankctl ai suggest target-schema -f spec.yaml
|
|
209
|
-
frankctl ai suggest field-mappings -f spec.yaml
|
|
210
|
-
frankctl ai suggest pattern-params -f spec.yaml
|
|
211
|
-
frankctl ai publish-transform -f spec.yaml
|
|
310
|
+
frankctl datasets list [--layer bronze|silver|gold] [--namespace <ns>] [--page N]
|
|
311
|
+
frankctl datasets preview <layer.namespace.table> [--limit 20]
|
|
312
|
+
frankctl datasets snapshots <layer.namespace.table>
|
|
212
313
|
```
|
|
213
314
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
**compose-pipeline quirks** (observed against the dev stack):
|
|
217
|
-
- With incomplete/unknown `source_tables`, the backend returns `{available:true, execution_id:null, status:"error"}` synchronously. The CLI detects `execution_id==null && !processing` and exits 5 immediately (no poll).
|
|
218
|
-
- With valid inputs, `execution_id` is returned and the CLI polls `/api/v1/ai/compose-pipeline/{id}/status` until `{completed, failed, error, cancelled}` or `--timeout` elapses. Exit 0 on `completed`, 5 on other terminal, 4 on timeout.
|
|
315
|
+
Dataset ids are the 3-part `layer.namespace.table` dotted form used directly in the API
|
|
316
|
+
path. `--json` on `preview` returns `{dataset_id, columns, rows, row_count}`.
|
|
219
317
|
|
|
220
|
-
|
|
318
|
+
### Patterns & schedules
|
|
221
319
|
|
|
222
320
|
```bash
|
|
223
321
|
frankctl patterns list [--category <c>] [--search <q>]
|
|
224
322
|
frankctl patterns get <id>
|
|
225
323
|
frankctl patterns validate-params <pattern-id> -f params.yaml
|
|
226
|
-
frankctl patterns register -f webhook-payload.json # CI only
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
- `validate-params` POSTs `{params: <file>}` to `/api/v1/transform-patterns/{id}/validate`; file contents are a bare params object. Exits 5 if `valid: false`.
|
|
230
|
-
- `patterns register` HMAC-signs the payload with `X-Hub-Signature-256: sha256=<hex>` using `PATTERN_WEBHOOK_SECRET`. **This command is CI-only** — pattern publish workflows in `frank-transforms` invoke it after pushing a digest-pinned image to GHCR. If `PATTERN_WEBHOOK_SECRET` is unset the CLI exits **6** with a message pointing at the env var; there is no override flag because unsigned payloads are rejected by the backend with `403` regardless.
|
|
231
324
|
|
|
232
|
-
## 20e. Schedules subtree (WEC-134, source-scoped)
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
325
|
frankctl schedules list
|
|
236
326
|
frankctl schedules get <source-id>
|
|
237
|
-
frankctl schedules set <source-id> -f sched.yaml
|
|
327
|
+
frankctl schedules set <source-id> -f sched.yaml # schedule_value required, even for manual
|
|
238
328
|
frankctl schedules pause|resume|trigger <source-id>
|
|
239
329
|
frankctl schedules delete <source-id> --yes
|
|
240
330
|
```
|
|
241
331
|
|
|
242
|
-
|
|
332
|
+
### AI assistance
|
|
243
333
|
|
|
244
|
-
```yaml
|
|
245
|
-
schedule_type: cron
|
|
246
|
-
schedule_value: "0 */6 * * *"
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
**Transform schedules are a follow-up.** The backend surface exists (`/api/v1/transforms/{id}/schedule[/pause|resume|trigger]`) but we did not add a parallel `frankctl transform-schedules` tree in WEC-134 to keep scope tight; tracked for a future phase.
|
|
250
|
-
|
|
251
|
-
## 20f. Datasets subtree (WEC-134)
|
|
252
|
-
|
|
253
|
-
```bash
|
|
254
|
-
frankctl datasets list [--layer bronze|silver|gold] [--namespace <ns>] [--page N] [--page-size N]
|
|
255
|
-
frankctl datasets preview <layer.namespace.table> [--limit 20]
|
|
256
|
-
frankctl datasets snapshots <layer.namespace.table>
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
- Dataset IDs are the 3-part `layer.namespace.table` dotted form the backend uses directly in the path (FastAPI `{dataset_id:path}` converter accepts dots verbatim).
|
|
260
|
-
- `preview` renders the payload as a table of column headers + row values with cells capped at 60 chars; `--json` returns `{dataset_id, columns, rows, row_count}` unmodified.
|
|
261
|
-
|
|
262
|
-
## 20a. Running E2E tests
|
|
263
|
-
|
|
264
|
-
E2E tests exercise the compiled CLI against a live Frank stack. They are gated on `FRANK_E2E=1` so `npm test` stays hermetic.
|
|
265
|
-
|
|
266
|
-
Preconditions:
|
|
267
|
-
- Frank stack running (`docker-compose up -d` from repo root; `curl localhost:8002/health` → 200).
|
|
268
|
-
- Test user provisioned (`testuser` / `testuser123` in the `frank` realm).
|
|
269
|
-
- Dev tenant id: `550e8400-e29b-41d4-a716-446655440001`.
|
|
270
|
-
|
|
271
|
-
Recipe:
|
|
272
|
-
```bash
|
|
273
|
-
cd frank-cli
|
|
274
|
-
export FRANKCTL_API_URL=http://localhost:8002
|
|
275
|
-
export FRANK_DEV_MODE=true
|
|
276
|
-
export FRANKCTL_TENANT_ID=550e8400-e29b-41d4-a716-446655440001
|
|
277
|
-
./dist/index.js auth login --headless --username testuser --password testuser123
|
|
278
|
-
export FRANKCTL_TOKEN=$(./dist/index.js auth token)
|
|
279
|
-
npm run build
|
|
280
|
-
npm run test:e2e
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Expect ≥24 tests green in ~90s on a healthy stack (new AI file is skipped unless Martha is available). Run a single file:
|
|
284
334
|
```bash
|
|
285
|
-
|
|
335
|
+
frankctl ai compose-pipeline -f spec.yaml [--no-wait] [--timeout <s>]
|
|
336
|
+
frankctl ai generate-transform -f spec.yaml
|
|
337
|
+
frankctl ai review-sql -f spec.yaml
|
|
338
|
+
frankctl ai suggest target-schema -f spec.yaml
|
|
339
|
+
frankctl ai suggest field-mappings -f spec.yaml
|
|
340
|
+
frankctl ai suggest pattern-params -f spec.yaml
|
|
286
341
|
```
|
|
287
342
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
- `sources discover --wait` against real postgres can exceed 45s — test accepts exit 0 OR clean timeout (exit 4/5).
|
|
292
|
-
- `sources sync` kicks off a Temporal workflow but `sync_run` rows may not materialize synchronously in dev — test asserts sync acceptance + well-formed history response, not a populated row.
|
|
293
|
-
- `transforms trigger` returns 500 when Dagster code location is unreachable — test accepts 2xx JSON OR exit 4/5 with a stderr message.
|
|
294
|
-
|
|
295
|
-
## 21. Known limitations
|
|
343
|
+
AI commands emit JSON. Every response carries `available: bool`; when the AI service is
|
|
344
|
+
offline the command exits 4 with a clear message. `compose-pipeline` returns a manifest
|
|
345
|
+
you can review, edit, and feed straight into `pipelines apply`.
|
|
296
346
|
|
|
297
|
-
|
|
298
|
-
- `runs cancel` against a raw temporal workflow-id currently 501s (backend gap).
|
|
299
|
-
- No `pipelines create/update`.
|
|
300
|
-
- No `transform-schedules` tree (follow-up). No transform-run cancel.
|
|
301
|
-
- PKCE redirect ports are fixed by Keycloak realm JSON.
|
|
302
|
-
|
|
303
|
-
## 22. Roadmap
|
|
347
|
+
---
|
|
304
348
|
|
|
305
|
-
|
|
349
|
+
## Output & exit codes
|
|
306
350
|
|
|
307
|
-
|
|
351
|
+
- `--json` makes `list`/`get`/`status`/`config`/`auth status` emit a single JSON
|
|
352
|
+
object or array on stdout.
|
|
353
|
+
- `pipelines validate` and the `ai` commands always emit their result as JSON.
|
|
354
|
+
- FastAPI error shapes (`{detail: …}`) are normalized; a `401` triggers one automatic
|
|
355
|
+
token refresh + retry before surfacing.
|
|
308
356
|
|
|
309
357
|
| Code | Meaning |
|
|
310
358
|
|------|---------|
|
|
311
359
|
| 0 | Success |
|
|
312
360
|
| 1 | Generic error |
|
|
313
361
|
| 2 | Usage error |
|
|
314
|
-
| 3 | Authentication |
|
|
315
|
-
| 4 | API (incl.
|
|
316
|
-
| 5 | Validation (sandbox/compose
|
|
317
|
-
| 6 | Config (
|
|
362
|
+
| 3 | Authentication error |
|
|
363
|
+
| 4 | API error (non-2xx, incl. AI service unavailable) |
|
|
364
|
+
| 5 | Validation failed (sandbox/compose terminal failure, invalid params) |
|
|
365
|
+
| 6 | Config error (missing required env/secret) |
|
|
366
|
+
|
|
367
|
+
## Troubleshooting
|
|
318
368
|
|
|
319
|
-
|
|
369
|
+
- **PKCE can't bind a port** (containers/headless): use `frankctl auth login --headless`.
|
|
370
|
+
- **401 after login**: your token expired and refresh failed — `frankctl auth login` again.
|
|
371
|
+
- **`409 immutable_diff` on apply**: a Backing Dataset change would destroy state; see
|
|
372
|
+
`--allow-recreate` above and decide deliberately.
|
|
373
|
+
- **`apply` did nothing**: that's idempotency — the declared state already matches. Use
|
|
374
|
+
`-v` to see the reconcile plan, or `--dry-run-server` to preview.
|
|
320
375
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
376
|
+
## Print this reference
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
frankctl skill # prints this document to stdout
|
|
380
|
+
```
|