@aiaiai-pt/frankctl 0.4.0

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/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@aiaiai-pt/frankctl",
3
+ "version": "0.4.0",
4
+ "description": "Terminal-first client for the Frank Low-Code Pipeline platform",
5
+ "homepage": "https://frank-docs-4k3.pages.dev",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/westeuropeco/frank-low-code.git",
9
+ "directory": "frank-cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/westeuropeco/frank-low-code/issues"
13
+ },
14
+ "license": "MIT",
15
+ "author": "aiaiai-pt",
16
+ "keywords": [
17
+ "frank",
18
+ "frankctl",
19
+ "cli",
20
+ "elt",
21
+ "iceberg",
22
+ "data-pipeline"
23
+ ],
24
+ "type": "module",
25
+ "bin": {
26
+ "frankctl": "./dist/index.js"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "CHANGELOG.md",
35
+ "skills/frank-cli/SKILL.md"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "node scripts/build.mjs",
42
+ "dev": "node --import tsx src/index.ts",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "test:e2e": "FRANK_E2E=1 vitest run --config vitest-e2e.config.ts",
46
+ "coverage": "vitest run --coverage",
47
+ "typecheck": "tsc --noEmit",
48
+ "prepack": "node scripts/build.mjs && mkdir -p skills/frank-cli && cp ../skills/frank-cli/SKILL.md skills/frank-cli/SKILL.md"
49
+ },
50
+ "dependencies": {
51
+ "chalk": "^5.4.1",
52
+ "commander": "^12.1.0",
53
+ "ora": "^8.2.0",
54
+ "yaml": "^2.8.0",
55
+ "zod": "^4.3.6"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.0.0",
59
+ "@vitest/coverage-v8": "^2.1.0",
60
+ "esbuild": "^0.24.0",
61
+ "tsx": "^4.19.0",
62
+ "typescript": "~5.7.0",
63
+ "vitest": "^2.1.0"
64
+ }
65
+ }
@@ -0,0 +1,323 @@
1
+ ---
2
+ name: frank-cli
3
+ description: Use the frankctl CLI to interact with the Frank API — auth, config, pipelines, sources, transforms, runs
4
+ ---
5
+
6
+ # frankctl (Frank CLI) — Phase 1
7
+
8
+ ## 1. Overview
9
+
10
+ `frankctl` is a TypeScript CLI that gives humans and agents scriptable access to the Frank API: authentication against Keycloak, config/profile management, and the core data-flow surface — pipelines, sources, transforms, and runs. Version: `0.2.0`.
11
+
12
+ ## 2. When to use
13
+
14
+ - Before activating a pipeline: `frankctl pipelines validate <id>` to run it end-to-end in sandbox mode, stream step badges to stderr, emit a final JSON summary on stdout.
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
19
+
20
+ ```bash
21
+ cd /home/ork/harnasso/frank/frank-cli
22
+ npm install
23
+ npm run build
24
+ npm link # installs `frankctl` on PATH
25
+ ```
26
+
27
+ The built entry point is `dist/index.js` (ESM, esbuild-bundled, `#!/usr/bin/env node` shebang). Node ≥18 required.
28
+
29
+ ## 4. First-run setup
30
+
31
+ ```bash
32
+ frankctl status # shows defaults, unauthenticated
33
+ frankctl auth login # browser PKCE flow (default)
34
+ frankctl auth status # confirms subject + tenant
35
+ frankctl pipelines list
36
+ ```
37
+
38
+ Credentials are written to `~/.frankctl/credentials.json` (0o600).
39
+
40
+ ## 5. Global options
41
+
42
+ Available on every subcommand:
43
+
44
+ | Flag | Purpose |
45
+ |------|---------|
46
+ | `-p, --profile <name>` | Select config profile (default: `default`) |
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
+
53
+ ## 6. Environment variables
54
+
55
+ | Var | Effect |
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 |
65
+
66
+ ## 7. Profiles & `~/.frankctl/config.yaml`
67
+
68
+ ```yaml
69
+ defaultProfile: default
70
+ profiles:
71
+ default:
72
+ apiUrl: http://localhost:8000
73
+ keycloakUrl: http://localhost:8180
74
+ realm: frank
75
+ clientId: frank-low-code
76
+ prod:
77
+ apiUrl: https://frank.prod.example
78
+ keycloakUrl: https://kc.prod.example
79
+ realm: frank
80
+ clientId: frank-low-code
81
+ ```
82
+
83
+ Precedence (top wins): CLI flag → env → YAML → built-in default.
84
+
85
+ ## 8. Auth flows
86
+
87
+ - **PKCE (default)** — browser-based, best for humans. Requires a free port in `[5173, 4173, 5174]` (matches the `frank-low-code` Keycloak client's registered redirect URIs).
88
+ - **Headless (ROPC)** — `frankctl auth login --headless --username ... --password ...`. Uses `frank-low-code` public client.
89
+ - **Service account** — `frankctl auth login --service-account --client-id frank-api --client-secret $S`. Uses `client_credentials` grant.
90
+
91
+ ## 9. Token handling
92
+
93
+ - Tokens refresh automatically 30s before expiry.
94
+ - Service-account tokens re-acquire via `client_credentials` when `FRANKCTL_CLIENT_SECRET` is set.
95
+ - Agents should use `TOKEN=$(frankctl auth token)` rather than reading the credentials file.
96
+
97
+ ## 10. `frankctl status`
98
+
99
+ Prints version, active profile, URLs, and authentication state. `--json` mirrors all fields.
100
+
101
+ ## 11. `frankctl auth`
102
+
103
+ - `login [--headless | --service-account] [--username --password --client-id --client-secret --no-browser]`
104
+ - `logout` — deletes credentials for active profile.
105
+ - `status` — exits 3 if not logged in.
106
+ - `token` — prints a valid access token to stdout.
107
+
108
+ ## 12. `frankctl config`
109
+
110
+ - `config set <key> <value>` — dotted key (e.g. `profiles.default.apiUrl`).
111
+ - `config get <key>` — returns `{value, source}` where source ∈ `flag | env | config | default`.
112
+ - `config list` — merged effective profile.
113
+
114
+ ## 13. `frankctl pipelines list`
115
+
116
+ `GET /api/v1/pipelines` with optional `--status`, `--search`, `--page`, `--page-size`. Table output by default; `--json` returns the full `PipelineListResponse`.
117
+
118
+ ## 14. `frankctl pipelines get <id>`
119
+
120
+ `GET /api/v1/pipelines/{id}`. Optional `--include-version` to include the active version payload.
121
+
122
+ ## 15. `frankctl pipelines validate <id>`
123
+
124
+ `POST /api/v1/pipelines/{id}/sandbox` then polls `/sandbox/{wf}/status` every 2500 ms (`--poll-interval` to override). Step badges stream to **stderr**; the final status JSON is written to **stdout**.
125
+
126
+ Flags: `--sample-limit <n=1000> --timeout <sec=600> --no-wait`.
127
+
128
+ Exit codes: `completed` → 0, `partial_failure | failed` → 5.
129
+
130
+ ## 16. JSON output contracts
131
+
132
+ - `--json` on `list`/`get`/`status`/`auth status`/`config *` emits a single JSON object/array on stdout.
133
+ - `validate` always emits the final sandbox status as JSON on stdout, regardless of `--json`.
134
+
135
+ ## 17. Exit codes
136
+
137
+ | Code | Meaning |
138
+ |------|---------|
139
+ | 0 | Success |
140
+ | 1 | Generic error |
141
+ | 2 | Usage error |
142
+ | 3 | Authentication error |
143
+ | 4 | API error (non-2xx) |
144
+ | 5 | Validation failed |
145
+
146
+ ## 18. Errors & troubleshooting
147
+
148
+ - FastAPI returns `{detail: str}` or `{detail: [...]}` — both shapes are normalized.
149
+ - `401` triggers a single token refresh + retry before surfacing.
150
+ - Port binding failure on PKCE prints the candidate list — use `--headless` in containers.
151
+
152
+ ## 19. Dev mode
153
+
154
+ Setting `FRANK_DEV_MODE=true` + `FRANKCTL_TENANT_ID=<uuid>` forwards `X-Tenant-ID` to bypass JWT-based tenant resolution (dev only).
155
+
156
+ ## 20. Relationship to `frank-pattern` (Python)
157
+
158
+ The existing Python `frank` CLI in `utils/frank_cli/` handles pattern authoring (`init|validate|test|build`) and is being renamed to `frank-pattern` in a follow-up PR on the `frank-shared-utils` submodule. The TS `frankctl` and Python `frank-pattern` are orthogonal: different domains, different binaries.
159
+
160
+ ## 20b. Data-flow subtrees (WEC-133)
161
+
162
+ ### `sources`
163
+
164
+ ```bash
165
+ frankctl sources list [--type <pattern-id>] [--status <s>] [--search <q>]
166
+ frankctl sources get <id> [--no-streams]
167
+ frankctl sources create -f source.yaml [--name ...] [--type ...]
168
+ frankctl sources update <id> -f patch.yaml
169
+ frankctl sources delete <id> --yes
170
+ frankctl sources discover <id> [--no-wait] [--timeout <s>]
171
+ frankctl sources sync <id> [--streams a,b] [--force-full-refresh] [--no-wait]
172
+ frankctl sources logs <id> <run-id> [-f] [--level INFO|ERROR]
173
+ frankctl sources history <id> [--limit N] [--offset N]
174
+
175
+ frankctl sources streams list <source-id> [--enabled-only]
176
+ frankctl sources streams set <source-id> -f streams.yaml
177
+ frankctl sources streams refresh-schema <source-id> --from-discovery
178
+ frankctl sources streams refresh-schema <source-id> -f discovery.json
179
+ ```
180
+
181
+ `-f` accepts YAML, JSON, or `-` (stdin). `sources create` requires `{name, pattern_id, source_config}`. `sources streams set` body can be a bare array or `{streams: [...]}`.
182
+
183
+ ### `transforms`
184
+
185
+ ```bash
186
+ frankctl transforms list [--status <s>] [--pipeline-id <id>]
187
+ frankctl transforms get <id>
188
+ frankctl transforms runs <id> [--status] [--limit N]
189
+ frankctl transforms logs <id> <run-id> [-f] [--level]
190
+ frankctl transforms trigger <id> # UI "Run Now" path
191
+ ```
192
+
193
+ ### `runs`
194
+
195
+ ```bash
196
+ frankctl runs get <workflow-id>
197
+ frankctl runs wait <workflow-id> [--timeout <s>] [--poll-interval <ms>]
198
+ frankctl runs cancel <id> --yes # UUID cascades through transform-runs → sync-runs; non-UUID hits temporal
199
+ ```
200
+
201
+ ## 20c. AI subtree (WEC-134)
202
+
203
+ ```bash
204
+ frankctl ai compose-pipeline -f spec.yaml [--no-wait] [--timeout <s>] [--poll-interval <ms>]
205
+ frankctl ai generate-transform -f spec.yaml
206
+ frankctl ai review-sql -f spec.yaml
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
212
+ ```
213
+
214
+ All AI commands emit JSON. Every response includes `available: bool`; when `false`, the CLI exits 4 with `AI service unavailable (Martha offline).`.
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.
219
+
220
+ ## 20d. Patterns subtree (WEC-134)
221
+
222
+ ```bash
223
+ frankctl patterns list [--category <c>] [--search <q>]
224
+ frankctl patterns get <id>
225
+ 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
+
232
+ ## 20e. Schedules subtree (WEC-134, source-scoped)
233
+
234
+ ```bash
235
+ frankctl schedules list
236
+ frankctl schedules get <source-id>
237
+ frankctl schedules set <source-id> -f sched.yaml # schedule_value required
238
+ frankctl schedules pause|resume|trigger <source-id>
239
+ frankctl schedules delete <source-id> --yes
240
+ ```
241
+
242
+ Backend expects `schedule_value` even for `schedule_type: manual` (it 422s on omission), so the zod schema requires it. Example:
243
+
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
+ ```bash
285
+ FRANK_E2E=1 npx vitest run --config vitest-e2e.config.ts tests/e2e/sources.e2e.test.ts
286
+ ```
287
+
288
+ Fixtures live in `tests/e2e/fixtures/` (bash scripts). Each test wraps its seed in try/finally so a failed assertion never leaks state. Config is `vitest-e2e.config.ts` (serial, 120s timeouts).
289
+
290
+ Notes on dev-stack quirks the suite tolerates:
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
296
+
297
+ - Prod tenant resolution requires `FRANK_DEV_MODE=true` + `FRANKCTL_TENANT_ID` (backend expects `X-Tenant-ID` today — tracked as follow-up WEC-135).
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
304
+
305
+ Phase 3+: JWT-claim tenant resolution (removes `FRANK_DEV_MODE` requirement), transform-schedules subtree, shell completion bundle, `pipelines create/update`.
306
+
307
+ ## 23. Exit code reference
308
+
309
+ | Code | Meaning |
310
+ |------|---------|
311
+ | 0 | Success |
312
+ | 1 | Generic error |
313
+ | 2 | Usage error |
314
+ | 3 | Authentication |
315
+ | 4 | API (incl. Martha unavailable) |
316
+ | 5 | Validation (sandbox/compose-pipeline terminal failure, validate-params invalid) |
317
+ | 6 | Config (e.g. `PATTERN_WEBHOOK_SECRET` unset for `patterns register`) |
318
+
319
+ ## 24. Changelog
320
+
321
+ - `0.3.0` — WEC-134: ai (compose-pipeline + generate/review/fix-ci/suggest/publish), patterns (list/get/validate-params/register with HMAC), schedules (source-scoped list/get/set/delete/pause/resume/trigger), datasets (list/preview/snapshots). Adds ExitCode 6 (Config).
322
+ - `0.2.0` — WEC-133: sources CRUD + discover/sync/streams/logs/history, transforms list/get/runs/logs/trigger, runs get/wait/cancel.
323
+ - `0.1.0` — WEC-132: status, auth (PKCE/ROPC/client-credentials), config, pipelines list/get/validate.