@gapi/gcli 1.8.237 → 1.8.238

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.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: changelog
3
+ description: Record every code change as a feature-keyed changelog fragment in docs/changelog.d/ (node scripts/changelog.mjs new) and keep the feature's living doc in docs/features/ current. Use after ANY change to source, schema, config or behavior, and whenever you need the history of a feature to debug or extend it (node scripts/changelog.mjs render --feature <f>). Never edit docs/CHANGELOG.md directly.
4
+ ---
5
+
6
+ # Feature changelog discipline
7
+
8
+ Two artifacts per feature, kept in the SAME commit as the change:
9
+
10
+ | Artifact | Holds | Edited how |
11
+ |---|---|---|
12
+ | `docs/features/<feature>.md` | Current state: what it does, architecture, files, config, how to test, how to debug, known limits | Updated in place - it must always describe what is true NOW |
13
+ | `docs/changelog.d/YYYY-MM-DD-<feature>-<slug>.md` | One change: what changed, how to test it, where to look when it breaks, what was verified | New file per change, never edited after merge |
14
+
15
+ `docs/CHANGELOG.md` is the archive - only `node scripts/changelog.mjs compact`
16
+ writes it (on the default branch, in its own commit).
17
+
18
+ ## Workflow
19
+
20
+ ```bash
21
+ node scripts/changelog.mjs features # existing feature slugs - reuse one
22
+ node scripts/changelog.mjs new local-ai-runners "model visibility per runner" --type feat
23
+ $EDITOR docs/changelog.d/2026-10-11-local-ai-runners-model-visibility-per-runner.md
24
+ $EDITOR docs/features/local-ai-runners.md # bring the living doc up to date
25
+ node scripts/changelog.mjs check # must print OK
26
+ git add docs/ <changed files> && git commit
27
+ ```
28
+
29
+ Starting work on an existing feature? Read its history first:
30
+
31
+ ```bash
32
+ node scripts/changelog.mjs render --feature local-ai-runners
33
+ ```
34
+
35
+ ## Rules
36
+
37
+ - **Feature slug**: kebab-case, shared across the Lambforge repos
38
+ (gql-fission, graphql-server, graphql-server-lambdas,
39
+ graphql-server-functions, gcli) - a cross-repo feature uses the same slug
40
+ in every repo it touches, and each repo's fragment mentions the others.
41
+ `repo` is for changes owned by no feature (setup, tooling, deps).
42
+ - **New feature** = new `docs/features/<feature>.md` (outline in
43
+ `docs/features/README.md`) + a row in that README's index. `check` fails
44
+ without the doc.
45
+ - **Every entry has Test, Debug and Verification lines.** Test = how anyone
46
+ can re-test it later. Debug = where to look when it breaks (log prefixes,
47
+ collections, flags, failure modes). Verification = what YOU ran and saw.
48
+ - Say what is now true, not what you did ("runners heartbeat every 30s",
49
+ not "edited connection.ts").
50
+ - One entry per logical change; several changes in a day = several files.
51
+ - Add `**Commit:** <hash>` when writing an entry after the fact.
52
+ - Not needed for: typo-only doc edits, scratch work, release/version bumps.
53
+
54
+ ## Compaction
55
+
56
+ When `docs/changelog.d/` grows past ~20 files, on the default branch:
57
+ `node scripts/changelog.mjs compact` then
58
+ `git commit -m "docs(changelog): compact N entries"`. Never urgent;
59
+ `render --feature` reads fragments and archive alike.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: cluster-targeting
3
+ description: Add or extend a gcli command that touches a project resource which can live on a private cluster (lambda, environment, logs, load-spec). Use when adding a new --clusterId/--clusterName flag, or when a command's resource lookup needs to disambiguate between the shared cluster and a private one.
4
+ ---
5
+
6
+ # gcli: targeting the right cluster
7
+
8
+ Lambforge projects can have private Talos/Hetzner clusters in addition to
9
+ the implicit shared one (full backend architecture:
10
+ `graphql-server-lambdas/docs/multi-cluster.md`). Every gcli command that
11
+ creates, reads, updates, or deletes a cluster-scoped resource (lambdas,
12
+ environments, logs, `load-spec.ts` manifests) needs to resolve which
13
+ cluster it's targeting instead of assuming the shared one.
14
+
15
+ ## `resolveClusterId` — the one helper every command should use
16
+
17
+ `src/helpers.ts`:
18
+
19
+ ```ts
20
+ export function resolveClusterId(
21
+ projectId: string,
22
+ hint: { clusterId?: string | null; clusterName?: string | null },
23
+ ): Observable<string | undefined>
24
+ ```
25
+
26
+ - `clusterId` wins when present (checked via `!= null`, **not** truthiness —
27
+ an explicit empty string is a deliberate "shared cluster only" signal on
28
+ some commands like `lambda:list`, and truthiness would silently treat
29
+ that the same as "not provided," changing existing behavior).
30
+ - Otherwise, if `clusterName` is given, looks it up via
31
+ `GraphqlClienAPI.projectClusters(projectId)` and resolves to that
32
+ cluster's id — or throws a descriptive error naming
33
+ `cluster:list --project <id>` as the way to see what's available.
34
+ - Neither given → resolves `undefined` (the shared cluster).
35
+
36
+ **Every command that accepts `--clusterId` should also accept
37
+ `--clusterName`** (clusterName is unique per project, so `clusterName +
38
+ projectId` is just as unambiguous as `clusterId` — and far more readable in
39
+ a hand-authored YAML manifest than a raw ObjectId). `clusterId` always wins
40
+ when both are passed. Confirmed call sites: `environment:{create,get,update,
41
+ delete}`, `lambda:{get,list,delete}`, `lambda:logs`/`logs:builder`,
42
+ `lambda/helpers/load-spec.ts` (so a `load-spec.yaml` manifest can reference
43
+ a cluster **by name**, keeping the file human-readable instead of full of
44
+ raw ObjectIds — see that file for the exact YAML shape).
45
+
46
+ ## Adding a new cluster-scoped command/flag
47
+
48
+ 1. Add `--clusterId <id>` AND `--clusterName <name>` flags to the command.
49
+ 2. Call `resolveClusterId(projectId, { clusterId, clusterName })` before the
50
+ actual GraphQL call, and pass the resolved id through.
51
+ 3. If the underlying GraphQL resolver doesn't yet accept a `clusterId` hint
52
+ for this operation, that's a backend gap — see
53
+ graphql-server-lambdas' `multi-cluster-scoping` skill before assuming the
54
+ frontend-side fix alone is sufficient.
55
+
56
+ ## GraphQL layer specifics (different from gql-fission)
57
+
58
+ gcli builds every GraphQL operation as an inline `gql` template string
59
+ directly in `services/gql-client.ts` — there's no `.graphql` file + codegen
60
+ pipeline like gql-fission's. Adding a field/operation here is a plain code
61
+ edit to that file, not a "regenerate then hand-patch" dance. gcli keeps its
62
+ own **separate, hand-maintained** copy of `@introspection/index.ts` — a
63
+ type added on the backend still needs to be added here by hand; it is not
64
+ shared with or generated from gql-fission's copy.
65
+
66
+ ## Testing
67
+
68
+ `jest.config.js` has no `moduleNameMapper` for the `~/` tsconfig path alias
69
+ — new test files must use relative imports (e.g. `'../src/helpers'`), not
70
+ `'~/helpers'`, or the test silently fails to resolve the module.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: gcli-command
3
+ description: Add or change a gcli command (commander registration, lazy action modules, GraphqlClienAPI calls through the gateway, Logger output, tests, build/release, regenerating README-CLI.md). USE BEFORE adding a `gcli <area>:<verb>` command or a new API call in src/services/gql-client.ts.
4
+ ---
5
+
6
+ # Adding a gcli command
7
+
8
+ ## Shape
9
+
10
+ ```
11
+ src/commands/<area>/index.ts registerXCommands(program): .command('<area>:<verb>')
12
+ .description(...).option(...)
13
+ .action(lazy(() => import('./<verb>').then(m => m.default)))
14
+ src/commands/<area>/<verb>.ts export default (cmd: { ...options }) => Promise<...>
15
+ src/commands/index.ts add registerXCommands to `commands`
16
+ ```
17
+
18
+ - Actions are lazy-imported - keep heavy imports inside the action module.
19
+ - Return a promise (`lastValueFrom(observable)` for rxjs flows); throw an
20
+ `Error` with an actionable message (`Run gcli login first`, `--id ...`).
21
+ - Output via `Logger` (`src/services/log.ts`: log/info/warn/error/table).
22
+
23
+ ## API calls
24
+
25
+ `GraphqlClienAPI.query<T>({ query: gql`...`, variables })` in
26
+ `src/services/gql-client.ts`: POSTs to the `gcli login` URL with the stored
27
+ Firebase token (auto-refreshed after 30 min) - i.e. through the federation
28
+ gateway as the logged-in user. Selection sets are plain strings in
29
+ `src/services/types/*.fragment.ts`. Response types: `@introspection/index`
30
+ (regenerated with `npm run introspect` against a live gateway) or a local
31
+ interface next to the command (as `commands/runner/types.ts`).
32
+
33
+ Login state lives in `~/.graphql-server/` (`token`, `refresh`, `url`, `key`,
34
+ `project`, ...; paths in `src/types.ts`). A command needing its own state
35
+ adds a path there (the runner stores `runner.json`, mode 0600).
36
+
37
+ Project/cluster-scoped commands: `parseProjectId(cmd.project)`,
38
+ `resolveClusterId` in `src/helpers.ts`; see the `cluster-targeting` skill.
39
+
40
+ ## Tests, build, docs
41
+
42
+ ```bash
43
+ npx jest --coverage=false # test/*.spec.ts; jest maps ~/ @introspection/ @commands/
44
+ npx tsc --noEmit -p .
45
+ npx eslint --fix <files>
46
+ npm run build # esbuild -> release/index.js (committed!)
47
+ node release/index.js <area>:<verb> --help
48
+ ```
49
+
50
+ - Mock the API in specs: `jest.mock('../src/services/gql-client', ...)`.
51
+ - `release/index.js` is committed and rewritten by every build - don't
52
+ commit it from a feature branch unless releasing (`git checkout -- release/index.js`).
53
+ - `README-CLI.md` is generated: `node release/index.js --all-help > README-CLI.md`
54
+ after a build. User-facing docs: a section in `README.md`.
55
+ - Changelog fragment + feature doc (`changelog` skill).
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: local-ai-runners
3
+ description: Work on the gcli local AI runner (runner:start/list/models/delete) - the subscriptions-transport-ws client that bridges a user's Ollama to the Lambforge chat. USE when the runner won't connect, drops requests, or before changing src/commands/runner/**.
4
+ ---
5
+
6
+ # gcli local AI runner
7
+
8
+ Slice doc: `docs/features/local-ai-runners.md`. Canonical (protocol,
9
+ security, symptom table): `graphql-server/docs/features/local-ai-runners.md`.
10
+
11
+ ## Invariants
12
+
13
+ - Outbound only: one WebSocket to `/subscriptions`
14
+ (`connectionParams.runnerToken`); subscription + mutations share it.
15
+ Never connect to RabbitMQ or open a port.
16
+ - `ALLOWED_REQUESTS` (`ollama.ts`) mirrors the backend's
17
+ `RUNNER_ALLOWED_PATHS` - only `POST /v1/chat/completions`. Change both or
18
+ neither.
19
+ - Every response frame of a request gets the next `seq`; batches are sent
20
+ sequentially (`FrameSender`). The backend re-orders, but don't rely on it.
21
+ - A request queued in Ollama sends `ping` every 30s, or the backend's 10-min
22
+ idle timeout kills it (spawned agent workers queue on one GPU).
23
+ - Socket close aborts every in-flight Ollama request; a refused token is
24
+ fatal (`RunnerAuthError`), everything else reconnects with backoff.
25
+ - Global `WebSocket` -> Node >= 22.
26
+
27
+ ## Run it against a dev stack
28
+
29
+ ```bash
30
+ gcli login --url http://localhost:9004/graphql ... # dev gateway
31
+ npm run build && node release/index.js runner:start # ws://localhost:9000/subscriptions derived
32
+ node release/index.js runner:models --show qwen3.8:27b # keep the picker short
33
+ ```
34
+
35
+ ## Tests
36
+
37
+ `npx jest test/runner.spec.ts --coverage=false` - `FakeServer` plays the
38
+ Lambforge socket (acks, operation replies, pushes requests), `fakeOllama`
39
+ plays Ollama (tags/show/streamed body, optional hold). Add a case there for
40
+ any protocol change.
@@ -0,0 +1,33 @@
1
+ # .claude/ - Claude Code configuration for this repo
2
+
3
+ | Path | Committed? | Purpose |
4
+ |---|---|---|
5
+ | `skills` | yes (symlink) | -> `../.agents/skills`, the single source of truth for project skills. Edit skills THERE. |
6
+ | `settings.json` | yes, if present | Team-wide baseline (permissions, hooks) |
7
+ | `settings.local.json` | no (gitignored) | Your personal overrides for this repo |
8
+ | `worktrees/`, `scheduled_tasks.lock` | no (gitignored) | Ephemeral Claude Code state |
9
+
10
+ Related at the repo root: `AGENTS.md` (the canonical agent guide - tool
11
+ agnostic, read by other agents/editors too), `CLAUDE.md` (a thin loader that
12
+ imports AGENTS.md), `.mcp.json` (project MCP servers, if present).
13
+
14
+ ## How settings layer
15
+
16
+ `~/.claude/settings.json` (you, all projects) -> `.claude/settings.json`
17
+ (this repo, team) -> `.claude/settings.local.json` (this repo, just you).
18
+ Scalars: later wins. Permission arrays merge, and a `deny` in any layer
19
+ beats an `allow` elsewhere - so you can always tighten locally:
20
+
21
+ ```json
22
+ { "permissions": { "ask": ["Bash(git push*)"], "deny": ["Read(./.env)"] } }
23
+ ```
24
+
25
+ ## Skills
26
+
27
+ A skill is `.agents/skills/<name>/SKILL.md`: YAML frontmatter (`name`,
28
+ `description` - the description is what decides when it auto-loads, so say
29
+ WHEN to use it) and then the checklist. Invoke with `/<name>`. When a
30
+ workflow proves itself or bites someone, update or add a skill in the same
31
+ change - same rule as docs.
32
+
33
+ Reference: https://docs.claude.com/en/docs/claude-code/settings
package/AGENTS.md ADDED
@@ -0,0 +1,105 @@
1
+ # gcli - agent & teammate guide (start here)
2
+
3
+ `@gapi/gcli`, the Lambforge platform CLI (package `gapi/packages/gcli` in the
4
+ `gapi` monorepo): login, projects, lambdas (create/update/logs/test, specs,
5
+ `lambforge.yaml`), environments, private clusters, RabbitMQ, plugins, the
6
+ MCP proxy, the esbuild-based `build`/`start`, and the local AI runner
7
+ (`runner:*`). TypeScript, commander, rxjs; bundled by esbuild into
8
+ `release/index.js` (also shipped as a Node SEA binary).
9
+
10
+ ## The Lambforge platform (sibling repos)
11
+
12
+ Lambforge is a serverless-on-Kubernetes platform (lambdas on a Fission fork,
13
+ private Talos clusters, AI chat/agents, billing). The repos live side by
14
+ side in `reactive-solutions/`; each has its own `AGENTS.md`.
15
+
16
+ | Repo | Role |
17
+ |---|---|
18
+ | `gql-fission` | Frontend SPA (Lit + @rxdi/@rhtml, Parcel, Apollo) - talks only to the gateway (+ `/subscriptions` WS) |
19
+ | `graphql-server-federated` | Apollo federation gateway (dev :9004): verifies Firebase/CI tokens, forwards the user as a `user` header, composes subgraphs at startup |
20
+ | `graphql-server` | Main subgraph: users, teams, projects, billing, AI chat & agents, MCP server, local AI runners; hosts `/subscriptions` (dev :9000, RabbitMQ pub/sub) |
21
+ | `graphql-server-lambdas` | Lambdas/Fission, environments, secrets/configs, RabbitMQ, plugins, private clusters (multi-cluster), Atlas connector |
22
+ | `graphql-server-functions` | Monorepo of Fission lambdas: cluster-provisioner, billing/cost scrapers, invoices, code-runner, gateway-federation, mcp, ... |
23
+ | `gapi/packages/gcli` | `gcli` CLI: login, lambdas, environments, clusters, MCP proxy, local AI runner |
24
+ | `graphql-server-common` | Shared Mongoose models (`@stradivario/graphql-server-models`) |
25
+
26
+ **Cross-repo features use one feature slug everywhere** (e.g.
27
+ `local-ai-runners`): one canonical `docs/features/<slug>.md` in the repo
28
+ that owns most of it, a short slice doc in each other repo, and a changelog
29
+ fragment in every repo the change touches.
30
+
31
+ ## Project skills (`.agents/skills/`, mirrored at `.claude/skills`)
32
+
33
+ | Skill | Owns |
34
+ |---|---|
35
+ | `gcli-command` | Adding a command: registration, lazy actions, `GraphqlClienAPI`, tests, build/release, regenerating `README-CLI.md` |
36
+ | `cluster-targeting` | Commands touching resources that can live on a private cluster (`--clusterId/--clusterName`) |
37
+ | `local-ai-runners` | `runner:*` - the outbound WS client bridging a user's Ollama to the chat |
38
+ | `changelog` | Feature-keyed changelog fragments + feature docs |
39
+
40
+ ## Documentation map
41
+
42
+ | You need... | Read |
43
+ |---|---|
44
+ | User guide (every command, `lambforge.yaml`, MCP, build) | [`README.md`](README.md) |
45
+ | Generated `--all-help` output | [`README-CLI.md`](README-CLI.md) (regenerate, don't edit) |
46
+ | Features (current state) | [`docs/features/README.md`](docs/features/README.md) |
47
+ | Change history of a feature | `node scripts/changelog.mjs render --feature <slug>` |
48
+ | Local AI runner (slice; canonical in graphql-server) | [`docs/features/local-ai-runners.md`](docs/features/local-ai-runners.md) |
49
+ | Claude Code config in this package | [`.claude/README.md`](.claude/README.md) |
50
+
51
+ ## Key patterns
52
+
53
+ - Commands: `src/commands/<area>/index.ts` registers `<area>:<verb>` with a
54
+ lazy `import('./<verb>')` action; register the area in
55
+ `src/commands/index.ts`.
56
+ - API: `GraphqlClienAPI.query` (`src/services/gql-client.ts`) talks to the
57
+ gateway as the `gcli login` user (token refreshed after 30 min). Login
58
+ state: `~/.graphql-server/` (paths in `src/types.ts`).
59
+ - `release/index.js` is COMMITTED and rewritten by every `npm run build` -
60
+ restore it (`git checkout -- release/index.js`) unless you are releasing.
61
+ - `README-CLI.md` = `node release/index.js --all-help` output.
62
+
63
+ ## Running & testing
64
+
65
+ ```bash
66
+ npx jest --coverage=false # test/*.spec.ts (jest maps ~/, @introspection/, @commands/)
67
+ npx tsc --noEmit -p .
68
+ npx eslint --fix <files>
69
+ npm run build # -> release/index.js
70
+ node release/index.js <command> --help
71
+ npm run patch # release: build, version bump, publish
72
+ ```
73
+
74
+ CI (repo root `.github/workflows/`): **Tests** runs on every push to a
75
+ non-master branch - `npx @rxdi/bolt install`, then `npm run lint` and
76
+ `npm test` across all gapi workspaces (gcli included). **Publish** repeats
77
+ lint/test on master before publishing; **Release GCLI Binary** builds the
78
+ SEA binary on `v*` tags. Reproduce Tests locally from the repo root:
79
+ `npm run lint && npm test`.
80
+
81
+ ## Docs, skills and changelog - same change, every time
82
+
83
+ This repo is documentation-driven. With every change:
84
+
85
+ 1. **Changelog fragment** - `node scripts/changelog.mjs new <feature> "<summary>"`
86
+ -> fill in what changed + **Test** + **Debug** + **Verification** ->
87
+ `node scripts/changelog.mjs check`. Never edit `docs/CHANGELOG.md` (archive).
88
+ Details: the `changelog` skill, `docs/changelog.d/README.md`.
89
+ 2. **Feature doc** - keep `docs/features/<feature>.md` true (new feature =
90
+ new doc + a row in `docs/features/README.md`).
91
+ 3. **Skills / AGENTS.md** - learned something non-obvious, or a workflow
92
+ bit you? Update the owning skill (`.agents/skills/<name>/SKILL.md`) or
93
+ this file in the same change.
94
+
95
+ Before working on an existing feature: `node scripts/changelog.mjs render
96
+ --feature <slug>` + its feature doc. That is where "how to test" and "how
97
+ to debug" live.
98
+
99
+ ## Pitfalls
100
+
101
+ - `gcli login` overwrites `~/.graphql-server/url`: logging into a dev
102
+ gateway points every later command (and `runner:start`) at dev.
103
+ - Jest needs the `moduleNameMapper` in `jest.config.js` for the `~/` alias -
104
+ without it any spec importing command code fails to resolve.
105
+ - `runner:start` needs Node >= 22 (global `WebSocket`).
package/CLAUDE.md ADDED
@@ -0,0 +1,20 @@
1
+ # Claude Code entry point
2
+
3
+ The canonical agent/teammate guide is AGENTS.md - imported below so it loads
4
+ into every session automatically. AGENTS.md (not this file) is where content
5
+ lives: it is the tool-agnostic standard that other agents and editors read
6
+ too. Keep this file a thin loader; never duplicate guidance here.
7
+
8
+ @AGENTS.md
9
+
10
+ ## Claude-specific notes
11
+
12
+ - Project skills auto-load from `.claude/skills` (a symlink to
13
+ `.agents/skills` - edit skills THERE, one source of truth). Invoke with
14
+ `/<name>`; the table is in AGENTS.md.
15
+ - Documentation-driven: every change ships with a changelog fragment
16
+ (`node scripts/changelog.mjs new <feature> "<summary>"`, then `check`) and an
17
+ up-to-date `docs/features/<feature>.md`. Learned something non-obvious?
18
+ Update the owning skill or AGENTS.md in the same change.
19
+ - Before touching an existing feature, read its history:
20
+ `node scripts/changelog.mjs render --feature <slug>`.